定时器相关

最近在用 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 时要注意,自己的串口在什么接口,需要进行判断

1
2
3
4
if(UsbSetupBuf->wIndexL == 1)
// inf1
else
// inf2

由于 ch552 不支持浮点波特率,因此可以忽略 host 端传送过来的 divisor 的小数部分, divisor 的低 14 位是整数部分,高两位是小数部分。

1
2
3
divisor = UsbSetupBuf->wValueL |
(UsbSetupBuf->wValueH << 8);
divisor &= 0x3fff;

在接收到 divisor 后,还需要对这个数进行处理。因为 ftdi 驱动中使用的是 48M 的时钟进行波特率的计算的,这里我们需要转换成自己的系统时钟,然后再进行定时器的 THn 进行设置

1
2
3
divisor = divisor / 3; // 16M CPU时钟
if(UsbSetupBuf->wIndexL == 1) // 串口位于接口1
TH1 = 0 - divisor;

知乎链接