STM32F4discovery_CDC_Device資料傳輸的一步步實現(USB2.0FS)
1. 需求
某專案需要微控制器把ADC資料上傳到電腦。方法有兩種:1、USB;2、乙太網。百度必應了一番,發現有人用過NXP的LPC的一款帶USB2.0 HighSpeed phy的片子,但是採用BGA封裝,開發難度上了一個層次。這兩年內使用的STM32F1、F4比較多,對其開發比較熟悉,資料也豐富。因此決定使用STM32F4跑一下CDC_Device例程,調一下這部分的資料傳輸。
2. 環境
2.1 軟體
Win7、Atollic TrueSTUDIO for STM32
STM32CubeMX v4.27.0,裝上STM32CubeF4 Firmware Package V1.21.0
2.2 硬體
我採用的是STM32F4discovery。也可以是STM32F4的開發板。
3. 試驗
3.1 使用STM32Cube生成專案
需要用到的IO口資源:連線4個指示燈的GPIO(PD12-PD15)。連線了按鍵的PA0(但實際未用到)。連線了外部8MHz晶振的PH0和PH1。連線到USB插座的PA11和PA12。連線到STLINK模擬器的PA13、PA14。
3.1.1 晶振
Peripherals中需要配置RCC,STM32F4discovery未焊接外部低速晶振。因此LSE預設disable,而有8MHz晶振,因此配置為Crystal/Ceramic。
3.1.2 模擬/下載
由於使用的是STLINK(STM32discovery板載stlinkv2),配置debug模式為Serial Wire。
3.1.3 USB裝置
STM32F407VG晶片支援USB2.0 Fullspeed。但是usb2.0 high speed需要外接控制器如usb3300。這裡的方式是STM32F407VG的引腳pa11和pa12經過電阻直接連線USB插座。需要配置為usb_device_fullspeed。
配置USB_OTG_FS如下:PC為usb host,而STM32作為usb device。
選好了device_only後,Configuration欄的USB_DEVICE即有效。把USB_DEVICE中,配置Class For FS IP為Communication Device Class(CDC)。
3.1.4 時鐘樹配置
STM32F407使用外部的8MHz晶振,Input frequency輸入8後,選選單欄中的clock Configuration -> Resolve Clock Issues即可自動為晶片內部的各個模組配置好時鐘頻率。這裡需要注意,STM32F4內建的usb controller時鐘需要48HMz才能正常工作。
在這個頁面沒有紅色字型後,這個頁面的配置也就完成了。
3.1.5 內部模組配置
這個頁面的各個部分採用預設值就可以了。不需要改動。(這也是我第一次跑這個demo,儘量根據前人的步驟來就不怕出錯了)
3.1.6 生成程式碼
生成程式碼前需要選擇project-> Settings配置工程目錄以及IDE。記得把mimimum heap size和mimmum stack size提高。有網友反映這個size低了是會出error的。我把兩個size都改大了,分別設為0x600和0x1000。其他預設,點選OK。
然後點Project -> Generate Code。工程初始化已經完成。
4. 使用者程式碼修改
在main.c的合適區域增加以下程式碼:
4.1 引用標頭檔案以及引數宣告
顧名思義,第一個引數是接受資料,雙緩衝陣列結構。第二個引數是傳送資料。cdc每接收一個包,會重新整理接收資料。需要把其當前資料包的長度記錄。
#include "usbd_cdc_if.h"
extern uint8_t UserRxBufferFS[2][2048];
extern uint8_t UserTxBufferFS[2048];
extern uint32_t nRxLength; //// 接收到的資料長度
extern uint8_t uRxBufIndex; //// 當前使用的緩衝區索引號
extern uint8_t uLastRxBufIndex;//// 上次使用的接收緩衝區索引號
int bSendMark = 0; //// 傳送資料的標誌
4.2 main函式內的引數初始化區域
我想知道上傳到電腦的資料是否準確連續。故自行按順序初始化了傳送陣列中的每個數值。
for(i=0;i<APP_TX_DATA_SIZE;i++)
{
UserTxBufferFS[i]=i;
}
4.3 main函式內的死迴圈
HAL_GPIO_TogglePin(LD4_GPIO_Port, LD4_Pin);
// 每次CDC_Itf_Receive()接收到新資料都會更換快取,所以發現快取切換過就是有新的資料。
// 這裡必須保證資料處理的速度足夠快,否則快取切換了兩次才處理的話,就沒法識別有新資料到來了。
if (uLastRxBufIndex != uRxBufIndex)
{
// --> 指令譯碼開始。
for (i=0; i<nRxLength; i++)
{
if (UserRxBufferFS[uLastRxBufIndex][i] == 0x55) // 0x55, 開始傳送資料指令
bSendMark = 1;
if (UserRxBufferFS[uLastRxBufIndex][i] == 0xAA) // 0xAA, 停止傳送資料指令
bSendMark = 0;
}
// <-- 指令譯碼結束。
uLastRxBufIndex = (uLastRxBufIndex + 1) & 1;
}
if (bSendMark)
{
int32_t k = 0;
while (CDC_Transmit_FS(UserTxBufferFS, APP_TX_DATA_SIZE) != USBD_OK)
k++;
}
// 處理接收到的資料:解碼資料流中的指令。可以改成你自己的資料處理過程。
// 每次CDC_Itf_Receive()接收到新資料都會更換快取,所以發現快取切換過就是有新的資料。
// 這裡必須保證資料處理的速度足夠快,否則快取切換了兩次才處理的話,就沒法識別有新資料到來了。
if (uLastRxBufIndex != uRxBufIndex)
{
// --> 指令譯碼開始。
for (i=0; i<nRxLength; i++)
{
if (UserRxBufferFS[uLastRxBufIndex][i] == 0x55) // 0x55, 開始傳送資料指令
bSendMark = 1;
if (UserRxBufferFS[uLastRxBufIndex][i] == 0xAA) // 0xAA, 停止傳送資料指令
bSendMark = 0;
}
// <-- 指令譯碼結束。
uLastRxBufIndex = (uLastRxBufIndex + 1) & 1;
}
if (bSendMark)
{
int32_t k = 0;
while (CDC_Transmit_FS(UserTxBufferFS, APP_TX_DATA_SIZE) != USBD_OK)
k++;
}
5 編譯錯誤
5.1 錯誤1
..\Src\usbd_cdc_if.c:201:39: warning: passing argument 2 of 'USBD_CDC_SetRxBuffer' makes pointer from integer without a cast [-Wint-conversion]
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS[0]);
STM32CubeMX自動生成的UserRxBufferFS是1維陣列,我們的是2維資料。
因此需要把
uint8_t UserRxBufferFS[APP_RX_DATA_SIZE];
改為:
uint8_t UserRxBufferFS[2][APP_RX_DATA_SIZE];
6 試驗結果
硬體照:
紅色USB線用於下載,模擬除錯,stlink。下面的黑色USB線用於和電腦傳輸使用者資料,CDC。
下載好程式後,我的電腦可以自行下載了VCP驅動。可以看到虛擬出串列埠26。
使用網友提供的上位機軟體。測得傳送速度為828.48KB/s。接收速度為837.29KB/s。
開啟XCOM串列埠除錯助手。
往STM32傳送55,STM32接收到0x55後,會把傳送資料內的資料上傳到電腦。然後對STM32傳送0xaa即可停止STM32的傳送。
可以看到,資料是0x00-0xff,試驗中沒看到明顯的丟包、以及誤碼。
7. 小結
本文記錄了基於STM32F4的USB2.0FS資料傳輸的試驗過程。採用了最新的HAL庫,好在網友提供了不少資料。昨天蒐集資料,今天上機,現在是中午12:00。已經實現了demo功能。在此特別感謝http://bbs.21ic.com/icview-811704-1-1.html。