想要實現的功能是,USB模擬串口收發數據。串口助手發送數據至MCU,MCU接收後返回給串口助手。
當初是想用標準庫做這個功能的。但是因爲後來瞭解到STM32CubeMX這個軟件,在嘗試之後實在是感覺,太方便了。所以,並沒有使用標準庫,而是直接用STM32CubeMX生成HAL庫的代碼用了。
(1)先點New Project,然後輸入自己的MCU型號
(2)配置引腳與外設
這裏我用的是ST-LINK進行DeBug,Tim5提供系統延時節拍,PE5與PB5點亮LED。而SysTick,用在FreeRtos提供系統節拍。
(3)時鐘樹配置
(4)配置外設
這個頁面可以對外設進行功能的設置,比如GPIO的輸出類型或者引腳初始電平。在這裏主要設置FreeRTOS,創建2個初始任務。其他的比如USB,默認就可以使用了。
(5)Poject Settings
這裏注意兩個地方,我使用的是MDK。所以IDE選項選擇的是MDK-ARM V5。CodeGenerator選項卡下,將Generated files下的第一個選項打上勾,這樣就會啓用模塊化編程,不同的外設封裝不同的.c .h文件。至於Project Name跟Project Location,
自行設置便可。
然後,點擊STM32CubeMX主界面的Project,Generate Code。就能在我們指定的文件夾內直接生成工程文件。生成之後軟件提示你打開項目,點擊打開後,工程內分組如圖:
因爲使用了RTOS,所以編程主要圍繞兩個文件,“usbd_cdc_if.c”以及“freertos.c”
“usbd_cdc_if.h”添加一個USB管理結構體的定義,並將“usbd_cdc_if.c”中兩個定義移到“usbd_cdc_if.h”
/* USER CODE BEGIN PRIVATE_DEFINES */
/* Define size for the receive and transmit buffer over CDC */
/* It's up to user to redefine and/or remove those define */
#define APP_RX_DATA_SIZE 1000
#define APP_TX_DATA_SIZE 1000
typedef struct
{
uint8_t OutFlag;
uint8_t EFlag[2];
uint8_t SFlag;
uint16_t ReLen;
}USB_Dev;
/* USER CODE END PRIVATE_DEFINES */
/* USER CODE BEGIN INCLUDE */
“usbd_cdc_if.c”聲明USB管理結構體變量並賦值,且修改“CDC_Receive_FS”函數。
/* Private typedef -----------------------------------------------------------*/
USB_Dev USB_S =
{
0,
{0x0D,0x0A},
0,
0,
};
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5); //開啓接收指示燈
//將已接收數據長度賦值給USB_S.ReLen
USB_S.ReLen += *Len;
//判斷是否有結束標誌以及接收數據長度是否達到UserRxBufferFS長度上限
if( USB_S.ReLen<APP_RX_DATA_SIZE && \
UserRxBufferFS[USB_S.ReLen-2] != USB_S.EFlag[0] && \
UserRxBufferFS[USB_S.ReLen-1] != USB_S.EFlag[1]
)
{
//設置下一次接收數據的位置
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS + USB_S.ReLen);
USBD_CDC_ReceivePacket(&hUsbDeviceFS); //準備接收數據
}
else //長度達到,或者檢測到標誌位,觸發數據輸出
{
USB_S.OutFlag = 1;
}
return (USBD_OK);
}
“freertos.c”中,添加頭文件“usbd_cdc_if.h”並在對應的任務中添加對應功能:
/* LED_Toggle function */
void LED_Toggle(void const * argument)
{
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
osDelay(500);
}
}
extern USB_Dev USB_S;
extern USBD_HandleTypeDef hUsbDeviceFS;
extern uint8_t UserRxBufferFS[APP_RX_DATA_SIZE];
/* USB_SendMess function */
void USB_SendMess(void const * argument)
{
uint16_t timeout = 0xffff;
uint8_t temp;
for(;;)
{
timeout = 0xffff;
if(USB_S.OutFlag)
{
temp = !(USB_S.ReLen%64); //判斷長度是否爲64整數倍
while( CDC_Transmit_FS(UserRxBufferFS, USB_S.ReLen - temp) != USBD_OK && timeout--);
if(temp) //當發送數據爲64整數倍時,無法發送成功,故分成2次發送
{
while( CDC_Transmit_FS(UserRxBufferFS + USB_S.ReLen -1, temp) != USBD_OK && timeout--);
}
USB_S.ReLen = 0;
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
USB_S.OutFlag = 0;
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET); //接收指示燈關閉
}
osDelay(1);
}
}
啓動文件:需將堆的容量調大,因爲USB緩存使用的是堆空間。我將它調爲4K:
Heap_Size EQU 0x1000
至此,代碼創建並修改完畢。
下載代碼到板子,下載完畢後關閉重上電,然後打開串口助手對應的串口。波特率之類全都不需設置。
發送一篇長文章,MCU接收後返回給串口助手
現象如圖:
寫在最後
(1)MCU端接收數據問題。
USB傳輸一次最多64字節。所以,如果想一次傳輸大量數據給MCU。需制定協議。如圖的串口助手,在發送完畢數據之後,會補上2字節結束標誌:0x0D,0x0A。MCU端可根據結束標誌判斷數據是否接收完畢。需注意的是,不同的串口助手協議不一定相同。有的是根據數據包長度判斷是否接收完畢。
(2)連續輸出的問題。
1、需要輪詢發送函數返回值是否是“USBD_OK”
2、輸出字符串,如果不去掉字符串最後的結束符。某些上位機軟件,只能顯示1次發送的數據,第二次
發送的數據不能正常顯示。(本博客使用的串口助手就是如此。。。。) ,但使用過另外一款串口
助手,並未發現此情況。
3、輪詢返回值有可能導致程序卡死,因爲主機若是沒有接收MCU發送的數據,MCU會一直輪詢
直到主機接收完數據爲止,解決辦法是加個timeout--到while裏面,到時間跳出循環。
(3)WINDOWS下不能識別的串口有黃色感嘆號。
這個有可能是堆設置不夠大。我在F4下遇到了這問題,將堆空間“Heap_Size EQU 0x200”
設置爲“Heap_Size EQU 0x1000”,黃色感嘆號消失
(4)通訊速度問題
參考“http://bbs.21ic.com/icview-811704-1-1.html”,附件中有測試速度的軟件。
串口助手當通訊速度40KB/S左右,接收不到數據。當然,如果是連續發送'0',串口助手上窗口不
顯示字符,是可以接收到數據的,但當速度超過500KB/S,依舊不能接收數據。所以,網上很多反映
VCP速度只有幾十KB/S的,估計是上位機軟件問題。
使用附件中的軟件,可以成功測出速度。STM32CubeMX生成的程序,經測試發送到主機的速度可以
達到1000KB/S以上。
(5)“CDC_Receive_FS”函數的解析
這個函數的作用是兩個,一是設置下一次接收數據的Buff,二是處理接收端點。
這個函數是在MCU接收完數據之後才調用的,而不是進入這個函數纔開始接收數據。比方MCU接收到64byte的數據,
接收完成後進入這個函數,設置下一次接收數據的Buff。然後調用“USBD_CDC_ReceivePacket”處理接收端點。
如果不調用“USBD_CDC_ReceivePacket”,是無法進行下一次的接收的,但發送是可以的。
(6)CDC_Transmit_FS 發送64整數倍字節數的數據出錯
當我調用“CDC_Transmit_FS”發送64字節的數據時,串口助手並不能接收到數據。
網上有帖子解釋了這種情況:http://bbs.21ic.com/icview-159300-1-1.html
總的來說,USB的bulk協議以發送小於64字節或者是字長爲0的數據包作爲結束動作。
發送64字節的包的時候,只需要末尾再發送個字長爲0的包即可。