定時器相關
最近在用 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;