USB CDC類(communications device class)可用于設備與主機之間的USB通信。有了CDC,再也不需要USB-TTL轉接板啦,數據傳輸也更快。
- 平臺:STM32F405
- 內容:HAL庫與STD庫的USB CDC類實驗
- 實驗效果:設備和電腦通過USB接口通信,完美替代之前的串口
HAL庫實驗
建立工程
- CubeMX中加入USB_OTG_FS,選擇Device Only。MiddleWares中選擇communications device class IP。
-
如圖配置時鐘,USB部分需要48M時鐘。
- 在Project->Settings中把Heap Size調大,因為USB HAL固件庫中使用了malloc,如果按照默認配置將導致 USB設備無法正常驅動。
- Project->Generate Code產生代碼。
代碼分析
(只關心收發(fā)數據函數怎么用時直接看總結,跳過這一部分)
- void MX_USB_DEVICE_Init(void)
void MX_USB_DEVICE_Init(void)
{
USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);
USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC);
USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);
USBD_Start(&hUsbDeviceFS);
}
MX_USB_DEVICE_Init()的功能是對USB的初始化。直接看
USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);
uint8_t USBD_CDC_RegisterInterface (USBD_HandleTypeDef *pdev,
USBD_CDC_ItfTypeDef *fops)
{
uint8_t ret = USBD_FAIL;
if(fops != NULL)
{
pdev->pUserData= fops;
ret = USBD_OK;
}
return ret;
}
函數功能是將USBD_HandleTypeDef類型的結構體pdev的成員pUserData賦值為USBD_CDC_ItfTypeDef類型的結構體fops。
USBD_HandleTypeDef數據類型的定義如下:
typedef struct _USBD_HandleTypeDef
{
uint8_t id;
uint32_t dev_config;
uint32_t dev_default_config;
uint32_t dev_config_status;
USBD_SpeedTypeDef dev_speed;
USBD_EndpointTypeDef ep_in[15];
USBD_EndpointTypeDef ep_out[15];
uint32_t ep0_state;
uint32_t ep0_data_len;
uint8_t dev_state;
uint8_t dev_old_state;
uint8_t dev_address;
uint8_t dev_connection_status;
uint8_t dev_test_mode;
uint32_t dev_remote_wakeup;
USBD_SetupReqTypedef request;
USBD_DescriptorsTypeDef *pDesc;
USBD_ClassTypeDef *pClass;
void *pClassData;
void *pUserData;
void *pData;
} USBD_HandleTypeDef;
可以看出pUserData是一個void*指針。
USBD_CDC_ItfTypeDef數據類型的定義如下:
typedef struct _USBD_CDC_Itf
{
int8_t (* Init) (void);
int8_t (* DeInit) (void);
int8_t (* Control) (uint8_t, uint8_t * , uint16_t);
int8_t (* Receive) (uint8_t *, uint32_t *);
}USBD_CDC_ItfTypeDef;
USBD_CDC_ItfTypeDef有四個成員,分別是四個函數指針。
下面來看用這兩個結構體類型聲明的變量。
usbd_device.c文件中聲明了
USBD_HandleTypeDef hUsbDeviceFS;
usbd_cdc_if.c文件中聲明了
USBD_CDC_ItfTypeDef USBD_Interface_fops_FS =
{
CDC_Init_FS,
CDC_DeInit_FS,
CDC_Control_FS,
CDC_Receive_FS
};
看到這里就明白了void MX_USB_DEVICE_Init(void)函數對USBD_HandleTypeDef hUsbDeviceFS這個結構體進行了初始化。而USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);初始化了其中的一個成員。
具體功能就是ST預留了函數接口,用戶只需要修改USBD_Interface_fops_FS中的四個函數。
USBD_CDC_ItfTypeDef USBD_Interface_fops_FS =
{
CDC_Init_FS,
CDC_DeInit_FS,
CDC_Control_FS,
CDC_Receive_FS
};
這四個函數的函數體在usbd_cdc_if.c文件中。
USB固件庫中已經調用好了這四個函數,用戶只需要在函數體中添加代碼實現(xiàn)功能即可。無需調用函數。固件庫中具體的調用方法舉例:
usbd_cdc.c文件中的
static uint8_t USBD_CDC_Init (USBD_HandleTypeDef *pdev,
uint8_t cfgidx)
{
uint8_t ret = 0;
USBD_CDC_HandleTypeDef *hcdc;
...
...
((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Init();
...
...
}
調用了函數 int8_t CDC_Init_FS(void)
pdev是USBD_HandleTypeDef *指針,pUserData是USBD_HandleTypeDef類型結構體中的成員。這個成員被注冊為了一個USBD_CDC_ItfTypeDef類型的結構體。而Init()是這個結構體中的成員。
((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Init();這一句話其實就是CDC_Init_FS這個函數的地址。
下面來看
USBD_CDC_ItfTypeDef USBD_Interface_fops_FS =
{
CDC_Init_FS,
CDC_DeInit_FS,
CDC_Control_FS,
CDC_Receive_FS
};
這四個函數的功能。
只要會使用這四個函數,就可以用stm32與pc進行usb通信了。
- int8_t CDC_Init_FS(void)
static int8_t CDC_Init_FS(void)
{
/* USER CODE BEGIN 3 */
/* Set Application Buffers */
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBufferFS, 0);
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS);
return (USBD_OK);
/* USER CODE END 3 */
}
CDC初始化函數,設置了收發(fā)Buffer。
- int8_t CDC_Control_FS (uint8_t cmd, uint8_t* pbuf, uint16_t length)
static int8_t CDC_Control_FS (uint8_t cmd, uint8_t* pbuf, uint16_t length)
{
/* USER CODE BEGIN 5 */
switch (cmd)
{
case CDC_SEND_ENCAPSULATED_COMMAND:
break;
case CDC_GET_ENCAPSULATED_RESPONSE:
break;
case CDC_SET_COMM_FEATURE:
break;
case CDC_GET_COMM_FEATURE:
break;
case CDC_CLEAR_COMM_FEATURE:
break;
/*******************************************************************************/
/* Line Coding Structure */
/*-----------------------------------------------------------------------------*/
/* Offset | Field | Size | Value | Description */
/* 0 | dwDTERate | 4 | Number |Data terminal rate, in bits per second*/
/* 4 | bCharFormat | 1 | Number | Stop bits */
/* 0 - 1 Stop bit */
/* 1 - 1.5 Stop bits */
/* 2 - 2 Stop bits */
/* 5 | bParityType | 1 | Number | Parity */
/* 0 - None */
/* 1 - Odd */
/* 2 - Even */
/* 3 - Mark */
/* 4 - Space */
/* 6 | bDataBits | 1 | Number Data bits (5, 6, 7, 8 or 16). */
/*******************************************************************************/
case CDC_SET_LINE_CODING:
break;
case CDC_GET_LINE_CODING:
break;
case CDC_SET_CONTROL_LINE_STATE:
break;
case CDC_SEND_BREAK:
break;
default:
break;
}
return (USBD_OK);
/* USER CODE END 5 */
}
CDC控制命令處理,列舉了主機有可能向設備發(fā)送的一些命令。沒有具體的處理過程,需要用戶自己編寫。其中包括串口參數的設置,要做串口轉USB通信的話需要修改這里。只是為了用USB與PC通信則不用管這里。每個命令具體的意思需要查詢CDC類手冊。
- int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)
static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)
{
/* USER CODE BEGIN 6 */
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
return (USBD_OK);
/* USER CODE END 6 */
}
接收函數,Buf為接收緩存。這個緩存實際上就是CDC_Init_FS()中設置的UserRxBufferFS[]數組。這個全局數組的定義在usbd_cdc_if.c文件中。Len為接收到數據的長度。這個變量不是全局的,需要用戶聲明變量把這個傳出去。
HAL庫源碼已經完成了對CDC_Receive_FS ()的調用,代碼在usb_device.c文件中
static uint8_t USBD_CDC_DataOut (USBD_HandleTypeDef *pdev, uint8_t epnum)
{
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData;
/* Get the received data length */
hcdc->RxLength = USBD_LL_GetRxDataSize (pdev, epnum);
/* USB data will be immediately processed, this allow next USB traffic being
NAKed till the end of the application Xfer */
if(pdev->pClassData != NULL)
{
//這一行完成調用
((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Receive(hcdc->RxBuffer, &hcdc->RxLength);
return USBD_OK;
}
else
{
return USBD_FAIL;
}
}
用戶只需要在這個函數中添加代碼,將數據和數據長度復制到自己的接收緩存中即可。而不需要在自己的程序中調用這個函數。
以下舉一個簡單的例子:
uint8_t my_RxBuf[100];
uint32_t my_RxLength;
static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)
{
/* USER CODE BEGIN 6 */
memcpy(my_RxBuf,Buf,*Len);
my_RxLength=*Len;
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
return (USBD_OK);
/* USER CODE END 6 */
}
發(fā)送函數也定義在usbd_cdc_if.c文件中:
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
{
uint8_t result = USBD_OK;
/* USER CODE BEGIN 7 */
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;
if (hcdc->TxState != 0){
return USBD_BUSY;
}
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);
/* USER CODE END 7 */
return result;
}
Buf表示待發(fā)送數據的指針,Len表示長度。這個函數使用很簡單,發(fā)送時直接調用即可。例如:
uint8_t my_TxBuf[10];
for(i=0;i<10;i++)
my_TxBuf[i]=i;
while(CDC_Transmit_FS(my_TxBuf, 10)!=USBD_OK)
{}
總結
STM32 CDC類HAL庫的使用:
- 使用CubeMx建立工程,注意修改HeapSize。
- usbd_cdc_if.c中CDC_Receive_FS()是接收函數。這個函數不需要調用。直接在函數中添加代碼把接受到的數據和數據長度復制到自己定義的接收緩存。
- usbd_cdc_if.c中CDC_Transmit_FS()是發(fā)送函數。要發(fā)送時調用這個函數,需要傳入待發(fā)送數據的指針和長度。
STD庫實驗
建立工程
如下圖添加文件

代碼分析
使用函數
USBD_Init(&USB_OTG_dev,USB_OTG_FS_CORE_ID,&USR_desc,&USBD_CDC_cb,&USR_cb);
初始化USB。
這里注意,usbd_cdc_if_template文件對應于HAL庫中的usbd_cdc_if.c。而usbd_cdc_vcp.c文件是用usbd_cdc_if_template修改得到的。這個文件不屬于USB庫。實際上直接使用usbd_cdc_if_template也是可以的。我之所以使用usbd_cdc_vcp.c文件是因為我下載的官方例程就這么寫的,懶得再改~
usbd_cdc_vcp.c中聲明了VCP_fops 。
CDC_IF_Prop_TypeDef VCP_fops =
{
VCP_Init,
VCP_DeInit,
VCP_Ctrl,
VCP_DataTx,
VCP_DataRx
};
與HAL庫中不同的是,這里可以注冊五個fops。將發(fā)送函數也注冊到了fops中。數據收發(fā)函數分別是
uint16_t VCP_DataRx(uint8_t* Buf, uint32_t Len)
uint16_t VCP_DataTx(uint8_t* Buf, uint32_t Len)
與HAL庫中的使用方法完全一致,這里就不再寫了。
因為發(fā)送函數加了static,所以使用時需要聲明:
extern CDC_IF_Prop_TypeDef VCP_fops
調用時:
VCP_fops.pIf_DataTx(my_Tx_Buf,10);
當然直接拿掉static也可以。
實驗效果
下載stm官方VCP驅動,官網資料編號stsw-stm32102。按照readme.txt安裝驅動。
連接設備,可以正常使用。
打開串口調試助手,不需要管波特率等配置,可以正常收發(fā)數據。36k的圖像秒傳,達到了預期效果。
