1. 程式人生 > >STM32HAL----USB模擬串列埠(VCP)

STM32HAL----USB模擬串列埠(VCP)

      想要實現的功能是,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位元組的資料時,串列埠助手並不能接收到資料。

      總的來說,USB的bulk協議以傳送小於64位元組或者是字長為0的資料包作為結束動作。

      傳送64位元組的包的時候,只需要末尾再發送個字長為0的包即可。