1. 概述
我們已經(jīng)對(duì) USB 硬件和數(shù)據(jù)的四種傳輸類型有了一個(gè)基本的了解。
控制傳輸(Control Transfers)
批量傳輸(Bulk Data Transfers)
中斷傳輸(Interrupt Data Transfers)
同步傳輸(Isochronous Data Transfers)
下面我們通過一個(gè)例子看一下 USB 的具體工作過程。在此我們用一個(gè)比較實(shí)用的例子,就是把我們的板子用 USB 連接至 PC,然后在 PC 端出現(xiàn)一個(gè)模擬串口,通過串口助手打開這個(gè)串口,然后實(shí)現(xiàn)數(shù)據(jù)的雙向傳輸。最后我們聊一下很多工程師都會(huì)忽視的 USB 認(rèn)證問題。
2. 例程
我們打開 ST 的 Cube 庫中的 CDC 例程:
STM32Cube_FW_F1_V1.8.0ProjectsSTM3210C_EVALApplicationsUSB_DeviceCDC_StandaloneMDK-ARMProject.uvprojx
這個(gè)例程用到 USB 的同時(shí)還會(huì)用到 USART,USB 從 PC 端收到數(shù)據(jù)后會(huì)轉(zhuǎn)發(fā)到 USART,從 USART 接收到消息會(huì)上傳至 PC。我們可以把 USART 的 TX 和 RX 短接,這樣從 PC 端下發(fā)的數(shù)據(jù)會(huì)原樣回傳給 PC 端。
這個(gè)例程使用的硬件是 STM3210C-EVAL,原理圖可以在 stmcu.org.cn 找到。如果我們使用的是其它板子,就需要在這個(gè)工程基礎(chǔ)上做一些改動(dòng)。比如現(xiàn)在我們使用 STM32F105RBT6,8M 晶振,串口用 PTA2,PTA3,那么我們的要做如下修改:
首先,修改使用的 MCU:
然后修改時(shí)鐘初始化部分。下圖為 STM32F105 時(shí)鐘模塊示意圖。USB 工作需要 48MHz 的時(shí)鐘。
(STM32F105xx Datasheet)
如果板子的晶振是 8M,那么參數(shù)需要做如下配置:
(OSC IN = 8M)
PREDIV1SRC? =? 0b0, HSE oscillator clock selected as PREDIV1 clock entry
PREDIV1 = 0b0, PREDIV1 input clock not divided
PLLSRC = 0b1, Clock from PREDIV1 selected as PLL input clock
PLLMUL = 0b0111, PLL input clock x 9
(PLLCLK = 72M)
SW = 0b10, PLL selected as system clock
PLLVCO = 2*PLLCLK? = 144M
USBPRE = 0, PLL clock is divided by 1.5 (or PLLVCO/3)
USB Clock =? = PLLCLK/1.5 = 72M/1.5 = 48M
例程中的對(duì)應(yīng)代碼修改:
最后修改使用的串口引腳:
stm32f1xx_hal_msp.c
//AFIOCOMx_REMAP(0);??? // Remap USART2 to PTD5/6
usbd_cdc_interface.h
/* Definition for USARTx Pins */
//#define USARTx_TX_PIN? ? ?? GPIO_PIN_5
//#define USARTx_TX_GPIO_PORT? ?GPIOD
//#define USARTx_RX_PIN? ? ? ?GPIO_PIN_6
//#define USARTx_RX_GPIO_PORT? ?GPIOD
#define USARTx_TX_PIN? ? ? ? GPIO_PIN_2
#define USARTx_TX_GPIO_PORT? ? GPIOA
#define USARTx_RX_PIN? ? ? ? GPIO_PIN_3
#define USARTx_RX_GPIO_PORT? ? GPIOA
終于可以編譯運(yùn)行了,用 USB 線把板子連到 PC 的 USB 口,記得把板子的 PTA2和 PTA3引腳短接起來。在設(shè)備管理器我們看到多出來一個(gè)串口,看它的屬性會(huì)看到它的 VID,PID 跟我們程序中設(shè)置的一致。
用串口助手打開此串口,發(fā)送字符串,會(huì)看到返回同樣的字符串。
下面我們來看一下具體的工作過程。
3.USB 枚舉(Enumeration)
當(dāng)我們給設(shè)備上電,程序控制芯片內(nèi)集成的上拉電阻連接至 USBDP 時(shí),USB 主機(jī)(PC 端)會(huì)檢測(cè)到這一變化并向設(shè)備供電。此時(shí)設(shè)備處于 Powered 狀態(tài)。
主機(jī)等待 100ms 設(shè)備穩(wěn)定后復(fù)位并使能此端口,此時(shí)設(shè)備可以從 Vbus 獲取不超過 100mA 的電流,其默認(rèn)地址是 0,處于 Default 狀態(tài)。
主機(jī)通過 0 地址向該設(shè)備發(fā)送 Get_Descriptor 標(biāo)準(zhǔn)請(qǐng)求,獲取設(shè)備的描述符。主機(jī)再次復(fù)位該 PORT,并發(fā)送標(biāo)準(zhǔn)請(qǐng)求 Set_Address 給設(shè)備分配一個(gè)地址,之后的通信都是用此地址,設(shè)備進(jìn)入 Address 狀態(tài)。
主機(jī)通過新地址向設(shè)備再次發(fā)送 Get_Descriptor 標(biāo)準(zhǔn)請(qǐng)求,獲取設(shè)備描述符。發(fā)送 Get_Configuration 請(qǐng)求,獲取配置描述符。一個(gè)設(shè)備可以有多個(gè)配置,主機(jī)選擇合適配置,通過 Set_Configuration 請(qǐng)求對(duì)設(shè)備而進(jìn)行配置,設(shè)備進(jìn)入 Configured 狀態(tài)。
USB 的枚舉過程是標(biāo)準(zhǔn)的,所以庫里也有對(duì)應(yīng)的標(biāo)準(zhǔn)處理代碼。我們可以不用關(guān)心。好了,現(xiàn)在可以開始數(shù)據(jù)的雙向傳輸了。
4. 數(shù)據(jù)傳輸
我們已經(jīng)了解所有 USB 傳輸都是由 USB 主機(jī)(Host)發(fā)起的,作為 USB 設(shè)備只能是被動(dòng)的等待。當(dāng) Host 下發(fā)請(qǐng)求時(shí)會(huì)在設(shè)備中產(chǎn)生各種中斷,設(shè)備完成各種中斷的處理就行了。其中需要特別關(guān)注的有兩個(gè):
OEPINT(Output Endpoint Int),表明主機(jī)下發(fā)了數(shù)據(jù)。
IEPINT(Output Endpoint Int)。表明主機(jī)請(qǐng)求設(shè)備上傳數(shù)據(jù)。
那么用戶在代碼里如何收發(fā) USB 數(shù)據(jù)的呢?
我們?cè)?usbd_cdc_interface.c 里關(guān)注下面這些就夠了:
uint8_t UserRxBuffer[APP_RX_DATA_SIZE]; //USB 下發(fā)數(shù)據(jù)緩沖區(qū)
uint8_t UserTxBuffer[APP_TX_DATA_SIZE]; // 需要發(fā)給 USB 上位機(jī)的數(shù)據(jù)緩沖區(qū)
下面這個(gè)函數(shù)是用戶用來處理接收緩沖區(qū)數(shù)據(jù)的,在初始化時(shí)需要傳遞給 USB 驅(qū)動(dòng),然后驅(qū)動(dòng)收到 USB 下發(fā)的數(shù)據(jù)后會(huì)回調(diào)此函數(shù)。在例程中此函數(shù)把接收數(shù)據(jù)轉(zhuǎn)發(fā)給了 USART2。當(dāng)然你也可以什么都不做。
static int8_t CDC_Itf_Receive(uint8_t * Buf, uint32_t * Len);
那么如果有數(shù)據(jù)需要發(fā)給上位機(jī)呢?我們可以用下面這個(gè)函數(shù):
USBD_CDC_TransmitPacket(&USBD_Device);
注意此數(shù)據(jù)是先放入 IN 端點(diǎn),然后等待 IEPINT 中斷發(fā)生時(shí)才被取走發(fā)送。
5. 一個(gè)重要又容易被忽視的問題
至此好像萬事大吉了。
等等,如果產(chǎn)品這樣發(fā)出去,你可能給公司惹麻煩了!
還有一個(gè)很重要的問題我們千萬不要忽視,就是 VID 和 PID,既廠商識(shí)別符(Vendor ID)和產(chǎn)品識(shí)別符(Product ID)。我們例程中使用的是 VID 0x0483, PID 0x5740。這個(gè) VID 是專門分配給 ST 的,雖然我們用這個(gè)號(hào)程序也能運(yùn)行,但是不符合規(guī)范的。我們的可以在 usb.org/developers 網(wǎng)站查到當(dāng)前為所有 USB 廠商分配的 VID。如果我們要開發(fā) USB 設(shè)備,還要向 USB 組織申請(qǐng)自己的 VID,之后還要做微軟徽標(biāo)認(rèn)證,就可以暢行無阻了。
參考資料:
UM1734 STM32Cube USB device library
USB Specification 2.0
Universal Serial Bus Class Definitions for Communications Devices 1.2
STM32F105xx Datasheet
STM32F105xx RM
關(guān)注公眾號(hào),掃碼加入嵌入式交流群: