定时器相关
最近在用 ch552 芯片做 usb 转串口的调试板
串口需要设置波特率,在 ch552 上需要给串口提供设定的波特率,这里使用定时器产生
初值计算问题
一般使用定时器的模式2,8位自动重装,使用 TL 计数,使用 TH 重装
波特率初值的计算公式如下
THn = TLn = 256 - fsys/12/16/波特率/2
ch552 有 1T 的模式,所以可以不用除以 12,还可以设置波特率倍频,也不用除以 2,这样的话,计算公式改为
THn = TLn = 256 - fsys/16/波特率
这里顺便提一下除以 16 的原因。在51单片机内置的串口模块中,他采取的方式是把一位信号采集16次,然后把第7、8、9次取出来,如果这三次中其中有两次是高电平的话,就认定这一位数据是1,如果两次数据是低电平,就认为是0。这样可以提高通信的容错率。【参考来源】
(PS:STC12C5A60S2 的 datasheet 8.2.2 节有更详细的介绍)
波特率误差问题
异步串口有起始和停止位,再加校验位,8位字节最多可有12位。51单片机的串口模块通常在位中间采样,如此12位偏差50%就可能采样错误造成通信失败,对应通信双方波特率偏差约50%/12=4%。
串口通信误码率与通信双方波特率高低无关,不过波特率和通信距离的乘积有上限。【参考来源】
本文中使用的 ch552 ,系统时钟为 16M,下面对是使用的常见的波特率进行误差分析。因为 ch552 不支持浮点波特率,所以对于小数部分进行截断
| 波特率 | THn | 误差 | THn | 误差 |
|---|---|---|---|---|
| 2400 | 416 | 0.16% | 417 | 0.08% |
| 9600 | 104 | 0.16% | 105 | 0.79% |
| 19200 | 52 | 0.16% | 53 | 1.73% |
| 38400 | 26 | 0.16% | 27 | 3.55% |
| 43000 | 23 | 1.11% | 24 | 3.1% |
| 56000 | 17 | 5% | 18 | 0.79% |
| 57600 | 17 | 2.12% | 18 | 3.55% |
| 115200 | 8 | 8.5% | 9 | 3.55% |
| 128000 | 7 | 11.6% | 8 | 2.3% |
虽然说理论上 5% 的误差对于异步串口通信来说都是可以容忍的,但是可能存在收发双方都存在偏差的情况,所以需要控制偏差在 2.5% 以下。
本次实验中使用发现,表中误差达到 3.55% 的波特率,在接收数据的时候都会乱码
usb 相关
既然 ch552 上的串口波特率需要定时器的支持,那在 host 设备上设置波特率的时候,如何将 host 设备上对于波特率的需求传达给 ch552 呢
ch552 的 usb 驱动是 ftdi 的,通过对 ftdi 的 usb 驱动反汇编可以知道设置波特率的 usb 非标准请求编码(当然不是我反汇编的☺),然后通过这个编码获取 usb 驱动送过来的一个 divisor 值。计算公式如下
divisor = 48M/16/波特率
这个计算是在 usb 驱动中完成的
在接收 host 端发送过来的 divisor 时要注意,自己的串口在什么接口,需要进行判断
if(UsbSetupBuf->wIndexL == 1)
// inf1
else
// inf2
由于 ch552 不支持浮点波特率,因此可以忽略 host 端传送过来的 divisor 的小数部分, divisor 的低 14 位是整数部分,高两位是小数部分。
divisor = UsbSetupBuf->wValueL |
(UsbSetupBuf->wValueH << 8);
divisor &= 0x3fff;
在接收到 divisor 后,还需要对这个数进行处理。因为 ftdi 驱动中使用的是 48M 的时钟进行波特率的计算的,这里我们需要转换成自己的系统时钟,然后再进行定时器的 THn 进行设置
divisor = divisor / 3; // 16M CPU时钟
if(UsbSetupBuf->wIndexL == 1) // 串口位于接口1
TH1 = 0 - divisor;