本篇筆記主要介紹 STM32 相關(guān)的知識(shí)點(diǎn),畢竟之后的 CDC 教程是用 STM32 開(kāi)發(fā)的。
為了寫(xiě)這一篇,魚(yú)鷹把 STM32 中文參考手冊(cè) USB 相關(guān)的從頭到尾看了一遍,雖然以前就已經(jīng)看過(guò)了,但這次看,收獲又是不同。
不過(guò)限于篇幅,魚(yú)鷹不會(huì)面面俱到,只介紹和 CDC 相關(guān)的一些東西。
要完成 USB 模擬串口(CDC)的實(shí)驗(yàn),STM32 手冊(cè)是必須細(xì)細(xì)閱讀的,不然代碼里面很多操作你是無(wú)法看懂的。
其實(shí)理解了前面的一些東西,你會(huì)發(fā)現(xiàn) STM32 中的 USB 知識(shí)和前面的大同小異,畢竟開(kāi)發(fā)芯片的廠家也是按照 USB 標(biāo)準(zhǔn)來(lái)實(shí)現(xiàn)的,不會(huì)差到哪里去。
硬件基礎(chǔ)
首先,STM32F103 使用 PA11(USBDM,D-)和 PA12(USBDP,D+)完成數(shù)據(jù)的收發(fā)。但看過(guò)前面章節(jié)的道友應(yīng)該知道,全速 USB 在 D+ 引腳是需要有一個(gè)上拉電阻的,同時(shí)兩根數(shù)據(jù)線需要各自串聯(lián)一個(gè) 22 Ω的電阻。
這就是你需要的硬件基礎(chǔ),如果說(shuō)你的開(kāi)發(fā)板有 USB 接口,但是沒(méi)有這些條件,那么你的 USB 接口只能用于供電,無(wú)法進(jìn)行數(shù)據(jù)傳輸。
當(dāng)然,STM32F103 的速度為全速 12 Mbit,換算成字節(jié)為 1.5 MB,除去 USB 協(xié)議的開(kāi)銷(令牌、打包等),大概能達(dá)到 1 MB/s 速度。
魚(yú)鷹在測(cè)試給各位道友的 CDC 例程發(fā)現(xiàn)只能達(dá)到 100 KB 左右,原以為是主機(jī)沒(méi)有及時(shí)發(fā)送令牌包導(dǎo)致帶寬很低,后來(lái)發(fā)現(xiàn) USB 設(shè)備發(fā)出的數(shù)據(jù)包只有幾個(gè)字節(jié),而不是最大包 64B,才知道是發(fā)送的數(shù)據(jù)太少了,后來(lái)增加發(fā)送的數(shù)據(jù)量(一次往緩沖多寫(xiě)幾百個(gè)字節(jié)),帶寬達(dá)到了 400~700KB,但離 1MB 還差了點(diǎn)。
通過(guò)邏輯分析儀查看才知道,主機(jī)發(fā)送 IN 令牌包時(shí),設(shè)備有可能還沒(méi)準(zhǔn)備好,浪費(fèi)了帶寬,不過(guò)在看 STM32 資料中發(fā)現(xiàn),對(duì)于批量傳輸(CDC 使用批量傳輸),可以使用雙緩沖提高傳輸量,估計(jì)用了雙緩沖,傳輸速率能達(dá)到 1MB/s,比串口的 115200 Bit/s 快的多,也穩(wěn)定的多,畢竟人家可是自帶了 CRC 校驗(yàn)和數(shù)據(jù)重傳功能的。
軟件基礎(chǔ)
現(xiàn)在看一看 STM32F103 的 USB 有哪些功能
第一點(diǎn),支持 USB2.0 全速,而不是 2.0 高速 480Mbit/s。
有 1~8 個(gè)(雙向)端點(diǎn),這是能完成組合設(shè)備的基礎(chǔ),按照 CDC + DAP 組合設(shè)備來(lái)說(shuō),一共需要 1(控制傳輸)+ 2(CDC)+1(HID) = 4 個(gè)端點(diǎn)的,更不要說(shuō)再模擬一個(gè) U 盤(pán)了。
CRC、NRZI 編解碼,這個(gè)可以讓你不必關(guān)心每一位是什么情況,你只需要處理底層給你的字節(jié)數(shù)據(jù)即可。
支持雙緩沖,最大程度的利用 USB 的帶寬。
支持 USB 掛起和恢復(fù)操作,其實(shí)還支持設(shè)備遠(yuǎn)程喚醒操作,即由設(shè)備發(fā)起喚醒請(qǐng)求(比如鼠標(biāo)移動(dòng)后喚醒設(shè)備)。
后面有一個(gè)注意點(diǎn),就是 USB 和 CAN 共用 512 字節(jié)的緩存,也就是說(shuō)同一時(shí)刻只能有一個(gè)外設(shè)可以工作,當(dāng)然你可以通過(guò)軟件在不同時(shí)刻使用不同的外設(shè)。
可以看看 USB 設(shè)備框圖,了解一下 USB 是由哪些結(jié)構(gòu)組成的。
為了實(shí)現(xiàn) USB 通信,有以下基礎(chǔ)步驟需要完成:
1、打開(kāi) Port A 的外設(shè)時(shí)鐘(PA11 和 PA12)
2、打開(kāi) USB 時(shí)鐘(其實(shí)還需要設(shè)置 USB 時(shí)鐘頻率,一般 SystemInit 會(huì)替你完成,當(dāng) USB 時(shí)鐘打開(kāi)后, PA11 和 PA12 引腳由 USB 接管,不歸 GPIO 控制)。
3、打開(kāi)相應(yīng)中斷(一共有三個(gè)中斷)
低優(yōu)先級(jí)中斷是我們主要關(guān)注的,因?yàn)?USB 枚舉過(guò)程就在這個(gè)中斷完成,所以這個(gè)中斷必須開(kāi)啟,其他兩個(gè)就看需求了。
4、配置 USB 寄存器,使 USB 可以正常工作。
5、之后所有的操作都在低優(yōu)先級(jí)中斷進(jìn)行(包括復(fù)位、枚舉、SOF 檢測(cè)等)。
以上步驟具體可以看魚(yú)鷹提供的例程實(shí)現(xiàn),不再多說(shuō)。
USB 寄存器
USB 中有三類寄存器:端點(diǎn)寄存器、通用寄存器、緩沖區(qū)描述表,再加上和描述表對(duì)應(yīng)的緩沖區(qū)(數(shù)據(jù)收發(fā)緩存區(qū),USB 所有的數(shù)據(jù)傳輸都首先要經(jīng)過(guò)這里),我們要做的就是在合適的時(shí)候?qū)@些寄存器進(jìn)行相應(yīng)的操作即可。
地址 0x 0x4000 5C00 開(kāi)始為端點(diǎn)寄存器,因?yàn)橛?8 個(gè)(雙向)端點(diǎn),所以有 8 個(gè)寄存器管理。
之后的寄存器為通用寄存器,用于管理整個(gè) USB 模塊的,具體可查看參考手冊(cè)。
以上寄存器有些位很特殊,比如可能寫(xiě) 0 有效,寫(xiě) 1 無(wú)效,所以有如下要求:
所以以往的讀 - 改 - 寫(xiě)不能在這里使用,不然你這邊讀回了 0,但是硬件修改了變成 1,如果往回寫(xiě) 0 ,那么就把硬件設(shè)置的 1 清除了,肯定會(huì)有影響,所以針對(duì)這種位,需要對(duì)不操作的位設(shè)置為 1 ,這樣就不會(huì)意外修改了。
還有可能寫(xiě) 1 翻轉(zhuǎn),寫(xiě) 0 無(wú)效,這時(shí)你會(huì)發(fā)現(xiàn)代碼中使用異或(^)來(lái)設(shè)置需要的位,非常巧妙。
總之,在學(xué)習(xí) USB 過(guò)程中,可以鍛煉你的位操作能力。
上述兩類寄存器在參考手冊(cè)其實(shí)是比較詳盡的,但緩沖區(qū)描述表(描述表的作用就是描述端點(diǎn)發(fā)送和接收緩存區(qū)的地址和大?。┚惋@得晦澀難懂了,所以這里詳細(xì)說(shuō)一下緩沖區(qū)描述表(以下表述可能有問(wèn)題,需要各位自行驗(yàn)證)。
首先,描述表的地址在 0x4000 6000,也就是說(shuō)前面所說(shuō)的 512 Byte 的基地址。但是按照參考手冊(cè)中的描述來(lái)看,這個(gè)空間大小應(yīng)該是 512 Byte * 2,這是因?yàn)?USB 模塊尋址采用 16 位尋址的,而應(yīng)用程序使用 32 位尋址,也就是說(shuō),按照我們的軟件角度,空間分布應(yīng)該是這樣的:
低地址的兩個(gè)字節(jié)可以被我們?cè)L問(wèn)(有顏色部分),高地址的兩個(gè)字節(jié)不可訪問(wèn)(但是按照雙緩沖描述來(lái)看,好像可以訪問(wèn)到,以后在驗(yàn)證一下)。
所以地址范圍應(yīng)該有 1 KB 的空間,但只有一半是可以使用的。
還有一點(diǎn)就是這塊空間不僅用于存放 USB 傳輸?shù)臄?shù)據(jù),還用來(lái)存放緩存區(qū)描述表,這個(gè)緩沖區(qū)描述表可以在這塊空間的任何一個(gè)位置(上圖在緩沖區(qū)的最開(kāi)始位置),只要滿足 8 字節(jié)對(duì)齊即可,畢竟一個(gè)端點(diǎn)需要 16 字節(jié)記錄(這里可能會(huì)感到疑惑,為什么一個(gè)端點(diǎn) 16 字節(jié),但卻是 8 字節(jié)對(duì)齊,這就是 16 位 和 32 訪問(wèn)的區(qū)別,在 USB 寄存器中,USB 模塊通過(guò) 16 位訪問(wèn),所以寄存器里面的值都是按照 16 位來(lái)保存偏移的)。
這個(gè)表的基地址存放在 USB_BTABLE 寄存器中,一般設(shè)置為 0,表示這個(gè)表放在上述空間的開(kāi)始處。
根據(jù)需要,依次安排描述表。比如 CDC 有三個(gè)端點(diǎn),前 16 個(gè)字節(jié)安排端點(diǎn) 0,負(fù)責(zé)描述發(fā)送緩存區(qū)的地址和大小,接收緩存區(qū)的地址和大?。ǚ乐菇邮諘r(shí)溢出)
端點(diǎn) 1 和端點(diǎn) 2 供 CDC 使用,占用 32 字節(jié)。所以前 48 字節(jié)被描述表占用了,剩下的(1024 – 48)/ 2 就是數(shù)據(jù)緩沖區(qū)了。比如將端點(diǎn) 0 的發(fā)送緩沖區(qū)地址指向 0x18(相對(duì)地址 0x4000 6000 偏移,16 位訪問(wèn)),大小為 64 字節(jié),端點(diǎn) 0 的接收緩存區(qū)指向 0x58(寄存器 USB_ADDR0_RX 寫(xiě)入的值,16 位訪問(wèn)),大小為 64 字節(jié)(注意這里的值為 16 位尋址,即 USB 模塊的尋址,和應(yīng)用層 32 位尋址不同,兩者之間需要轉(zhuǎn)化)。
按理應(yīng)該像上面分布空間的,但實(shí)際上你會(huì)發(fā)現(xiàn)分布如下:
那么是否可以將端點(diǎn) 0 的緩存地址安排在 0x40006030 位置(即 USB_ADDR0_TX 值為 0x18 而不是上圖的 0x30 呢),而不是 0x40006060 呢,這樣就不會(huì)浪費(fèi)那些空間了。
因?yàn)檫@個(gè)改動(dòng)會(huì)較大,感興趣的可以嘗試一下。
當(dāng) USB 模塊寫(xiě)入端點(diǎn) 0 的數(shù)據(jù)時(shí),首先根據(jù) USB_BTABLE 的值找到描述表的位置,然后再根據(jù)描述表第一個(gè)表項(xiàng)的 USB_ADDR0_RX 找到接收緩沖區(qū)的地址,最后寫(xiě)入數(shù)據(jù)(寫(xiě)入過(guò)程中會(huì)判斷是否超出限制,防止破壞其他緩沖區(qū),這個(gè)通過(guò) USB_COUNT0_Rx 判斷),當(dāng)應(yīng)用程序進(jìn)行讀取上述地址的數(shù)據(jù)時(shí),因?yàn)椴捎昧?32 位訪問(wèn),所以對(duì) USB_BTABLE 和 USB_ADDR0_RX 偏移地址 x2,這樣就可以找到我們需要的緩存地址,從而讀取到主機(jī)發(fā)給設(shè)備的數(shù)據(jù),然后進(jìn)行相應(yīng)的處理。
設(shè)備發(fā)送同理。
具體實(shí)現(xiàn)可參考魚(yú)鷹給出的源代碼。