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