1. 概述
我們已經對USB硬體和資料的四種傳輸型別有了一個基本的瞭解。
控制傳輸(Control Transfers)
批次傳輸(Bulk Data Transfers)
中斷傳輸(Interrupt Data Transfers)
同步傳輸(Isochronous Data Transfers):
下面我們透過一個例子看一下USB的具體工作過程。在此我們用一個比較實用的例子,就是把我們的板子用USB連線至PC,然後在PC端出現一個模擬串列埠,透過串列埠助手開啟這個串列埠,然後實現資料的雙向傳輸。最後我們聊一下很多工程師都會忽視的USB認證問題。
2. 例程
我們開啟ST的Cube庫中的CDC例程:
STM32Cube_FW_F1_V1.8.0\Projects\STM3210C_EVAL\Applications\USB_Device\CDC_Standalone\MDK-ARM\Project.uvprojx
這個例程使用的硬體是STM3210C-EVAL,原理圖可以在stmcu.org.cn找到。如果我們使用的是其它板子,就需要在這個工程基礎上做一些改動。比如現在我們使用STM32F105RBT6,8M晶振,串列埠用PTA2,PTA3,那麼我們的要做如下修改:
首先,修改使用的MCU:
然後修改時鐘初始化部分。下圖為STM32F105時鐘模組示意圖。USB工作需要48MHz的時鐘。
(STM32F105xx Datasheet)
如果板子的晶振是8M,那麼引數需要做如下配置:
(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
例程中的對應程式碼修改:
最後修改使用的串列埠引腳:
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
終於可以編譯運行了,用USB線把板子連到PC的USB口,記得把板子的PTA2和PTA3引腳短接起來。在裝置管理器我們看到多出來一個串列埠,看它的屬性會看到它的VID,PID跟我們程式中設定的一致。
用串列埠助手開啟此串列埠,傳送字串,會看到返回同樣的字串。
下面我們來看一下具體的工作過程。
3. USB列舉(Enumeration)
當我們給裝置上電,程式控制晶片內整合的上拉電阻連線至USBDP時,USB主機(PC端)會檢測到這一變化並向裝置供電。此時裝置處於Powered狀態。
主機等待100ms裝置穩定後復位並使能此埠,此時裝置可以從Vbus獲取不超過 100mA 的電流,其預設地址是0,處於Default狀態。
主機透過0地址向該裝置傳送Get_Descriptor標準請求,獲取裝置的描述符。主機再次復位該PORT,併發送標準請求Set_Address給裝置分配一個地址,之後的通訊都是用此地址,裝置進入Address狀態。
主機透過新地址向裝置再次傳送Get_Descriptor標準請求,獲取裝置描述符。傳送Get_Configuration請求,獲取配置描述符。一個裝置可以有多個配置,主機選擇合適配置,透過 Set_Configuration請求對裝置而進行配置,裝置進入Configured狀態。
USB的列舉過程是標準的,所以庫裡也有對應的標準處理程式碼。我們可以不用關心。好了,現在可以開始資料的雙向傳輸了。
4. 資料傳輸
我們已經瞭解所有USB傳輸都是由USB主機(Host)發起的,作為USB裝置只能是被動的等待。當Host下發請求時會在裝置中產生各種中斷,裝置完成各種中斷的處理就行了。其中需要特別關注的有兩個:
OEPINT(Output Endpoint Int),表明主機下發了資料。
IEPINT(Output Endpoint Int)。表明主機請求裝置上傳資料。
那麼使用者在程式碼裡如何收發USB資料的呢?
我們在usbd_cdc_interface.c裡關注下面這些就夠了:
uint8_t UserRxBuffer[APP_RX_DATA_SIZE]; //USB下發資料緩衝區
uint8_t UserTxBuffer[APP_TX_DATA_SIZE]; //需要發給USB上位機的資料緩衝區
static int8_t CDC_Itf_Receive(uint8_t * Buf, uint32_t * Len);
那麼如果有資料需要發給上位機呢?我們可以用下面這個函式:
USBD_CDC_TransmitPacket(&USBD_Device);
注意此資料是先放入IN端點,然後等待IEPINT中斷髮生時才被取走傳送。
5. 一個重要又容易被忽視的問題
至此好像萬事大吉了。
等等,如果產品這樣發出去,你可能給公司惹麻煩了!
還有一個很重要的問題我們千萬不要忽視,就是VID和PID,即廠商識別符(Vendor ID)和產品識別符(Product ID)。我們例程中使用的是VID 0x0483, PID 0x5740。這個VID是專門分配給ST的,雖然我們用這個號程式也能執行,但是不符合規範的。我們的可以在 usb.org/developers 網站查到當前為所有USB廠商分配的VID。如果我們要開發USB裝置,還要向USB組織申請自己的VID,之後還要做微軟徽標認證,就可以暢行無阻了。
參考資料:
UM1734 STM32Cube USB device library
USB Specification 2.0
Universal Serial Bus Class Definitions for Communications Devices 1.2
STM32F105xx Datasheet
STM32F105xx RM