STM32自定義USB設備開發詳細流程講解及全套資料源碼下載(基於libusb)

本帖最後由 飛鴻踏雪 於 2014-10-16 13:05 編輯

前言
USB的用途就很少說了,下面的內容主要就是講解如何利用ST提供的USB驅動庫和libusb上位機驅動庫實現一個USB數據傳輸功能,爲了下降開發難度,咱們僅僅講解Bulk傳輸模式,固然這也是用得比較多的傳輸模式。

開發流程
1,完成STM32單片機端的USB程序;
2,利用linusb自帶的inf-wizard工具生成USB驅動;
3,基於libusb編寫USB通訊程序;
4,測試PC和單片機的數據通訊;

STM32程序編寫
1,完成描述符的修改,修改後的描述符以下(在usb_desc.c文件中)
設備描述符:
[C]  純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
const uint8_t CustomHID_DeviceDescriptor[CUSTOMHID_SIZ_DEVICE_DESC] =
{
     0x12,                       /*bLength */
     USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType*/
     0x00,                       /*bcdUSB */
     0x02,
     0x00,                       /*bDeviceClass*/
     0x00,                       /*bDeviceSubClass*/
     0x00,                       /*bDeviceProtocol*/
     0x40,                       /*bMaxPacketSize40*/
     LOBYTE(USBD_VID),           /*idVendor*/
     HIBYTE(USBD_VID),           /*idVendor*/
     LOBYTE(USBD_PID),           /*idVendor*/
     HIBYTE(USBD_PID),           /*idVendor*/
     0x00,                       /*bcdDevice rel. 2.00*/
     0x02,
     1,                          /*Index of string descriptor describing manufacturer */
     2,                          /*Index of string descriptor describing product*/
     3,                          /*Index of string descriptor describing the device serial number */
     0x01                        /*bNumConfigurations*/
}; /* CustomHID_DeviceDescriptor */


配置描述符:
[C]  純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
const uint8_t CustomHID_ConfigDescriptor[CUSTOMHID_SIZ_CONFIG_DESC] =
{
     0x09, /* bLength: Configuation Descriptor size */
     USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType: Configuration */
     CUSTOMHID_SIZ_CONFIG_DESC,
     /* wTotalLength: Bytes returned */
     0x00,
     0x01,         /* bNumInterfaces: 1 interface */
     0x01,         /* bConfigurationValue: Configuration value */
     0x00,         /* iConfiguration: Index of string descriptor describing
                                  the configuration*/
     0xE0,         /* bmAttributes: Bus powered */
                   /*Bus powered: 7th bit, Self Powered: 6th bit, Remote wakeup: 5th bit, reserved: 4..0 bits */
     0xFA,         /* MaxPower 500 mA: this current is used for detecting Vbus */
     /************** Descriptor of Custom HID interface ****************/
     /* 09 */
     0x09,         /* bLength: Interface Descriptor size */
     USB_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType: Interface descriptor type */
     0x00,         /* bInterfaceNumber: Number of Interface */
     0x00,         /* bAlternateSetting: Alternate setting */
     0x04,         /* bNumEndpoints */
     0xDC,         /* bInterfaceClass: Class code = 0DCH */
     0xA0,         /* bInterfaceSubClass : Subclass code = 0A0H */
     0xB0,         /* nInterfaceProtocol : Protocol code = 0B0H */
     0,            /* iInterface: Index of string descriptor */
     /******************** endpoint descriptor ********************/
     /* 18 */
     0x07,         /* endpoint descriptor length = 07H */
     USB_ENDPOINT_DESCRIPTOR_TYPE, /* endpoint descriptor type = 05H */
     0x81,         /* endpoint 1 IN */
     0x02,                                        /* bulk transfer = 02H */
     0x40,0x00,    /* endpoint max packet size = 0040H */
     0x00,         /* the value is invalid when bulk transfer */
 
     0x07,         /* endpoint descriptor length = 07H */
     USB_ENDPOINT_DESCRIPTOR_TYPE, /* endpoint descriptor type = 05H */
     0x01,         /* endpoint 1 OUT */
     0x02,                                        /* bulk transfer = 02H */
     0x40,0x00,    /* endpoint max packet size = 0040H */
     0x00,         /* the value is invalid when bulk transfer */
                 
     0x07,         /* endpoint descriptor length = 07H */
     USB_ENDPOINT_DESCRIPTOR_TYPE, /* endpoint descriptor type = 05H */
     0x82,         /* endpoint 2 IN */
     0x02,                                        /* bulk transfer = 02H */
     0x40,0x00,    /* endpoint max packet size = 0040H */
     0x00,         /* the value is invalid when bulk transfer */
                 
     0x07,         /* endpoint descriptor length = 07H */
     USB_ENDPOINT_DESCRIPTOR_TYPE, /* endpoint descriptor type = 05H */
     0x02,         /* endpoint 2 OUT */
     0x02,                                        /* bulk transfer = 02H */
     0x40,0x00,    /* endpoint max packet size = 0040H */
     0x00,         /* the value is invalid when bulk transfer */
}; /* CustomHID_ConfigDescriptor */

配置描述符就包含了端點描述符,咱們用了4個端點,兩個BULK-OUT端點,兩個BULK-IN端點。

其餘的描述符:
[C]  純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* USB String Descriptors (optional) */
const uint8_t CustomHID_StringLangID[CUSTOMHID_SIZ_STRING_LANGID] =
{
     CUSTOMHID_SIZ_STRING_LANGID,
     USB_STRING_DESCRIPTOR_TYPE,
     0x09,
     0x04
}; /* LangID = 0x0409: U.S. English */
 
const uint8_t CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR] =
{
     CUSTOMHID_SIZ_STRING_VENDOR, /* Size of Vendor string */
     USB_STRING_DESCRIPTOR_TYPE,  /* bDescriptorType*/
     // Manufacturer: "STMicroelectronics"
     'M' , 0, 'y' , 0, 'U' , 0, 'S' , 0, 'B' , 0, '_' , 0, 'H' , 0, 'I' ,0, 'D' ,0
};
 
const uint8_t CustomHID_StringProduct[CUSTOMHID_SIZ_STRING_PRODUCT] =
{
     CUSTOMHID_SIZ_STRING_PRODUCT,          /* bLength */
     USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */
     'B' , 0, 'y' , 0, ' ' , 0, 'e' , 0, 'm' , 0, 'b' , 0, 'e' ,0, 'd' ,0, '-' ,0, 'n' ,0, 'e' ,0, 't' ,0
};
uint8_t CustomHID_StringSerial[CUSTOMHID_SIZ_STRING_SERIAL] =
{
     CUSTOMHID_SIZ_STRING_SERIAL,           /* bLength */
     USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */
     'x' , 0, 'x' , 0, 'x' , 0, 'x' , 0, 'x' , 0, 'x' , 0, 'x' , 0
};


2,根據端點緩衝區大小配置端點緩衝區地址,配置信息以下(在usb_conf.h文件中):
[C]  純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
/* buffer table base address */
#define BTABLE_ADDRESS      (0x00)
 
/* EP0  */
/* rx/tx buffer base address */
#define ENDP0_RXADDR        (0x18)
#define ENDP0_TXADDR        (0x58)
 
/* EP1  */
/* tx buffer base address */
//地址爲32位對其,位4的倍數,不能超過 bMaxPacketSize
//EP1
#define ENDP1_RXADDR        (0x98)
#define ENDP1_TXADDR        (0x98+64)
////EP2
#define ENDP2_RXADDR        (0xA0+64+64)
#define ENDP2_TXADDR        (0xA0+64+64+64)


3,初始化每一個端點(在usb_prop.c文件中的CustomHID_Reset函數中)
[C]  純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* Initialize Endpoint 0 */
SetEPType(ENDP0, EP_CONTROL);
SetEPTxStatus(ENDP0, EP_TX_STALL);
SetEPRxAddr(ENDP0, ENDP0_RXADDR);
SetEPTxAddr(ENDP0, ENDP0_TXADDR);
Clear_Status_Out(ENDP0);
SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
SetEPRxValid(ENDP0);
 
/* Initialize Endpoint 1 */
        SetEPType(ENDP1, EP_BULK);
        SetEPRxAddr(ENDP1, ENDP1_RXADDR);
        SetEPTxAddr(ENDP1, ENDP1_TXADDR);
        SetEPRxCount(ENDP1, EP_SIZE);
        SetEPRxStatus(ENDP1, EP_RX_VALID);
  SetEPTxStatus(ENDP1, EP_TX_NAK);
 
/* Initialize Endpoint 2 */
        SetEPType(ENDP2, EP_BULK);
        SetEPRxAddr(ENDP2, ENDP2_RXADDR);
        SetEPTxAddr(ENDP2, ENDP2_TXADDR);
        SetEPRxCount(ENDP2, EP_SIZE);
        SetEPRxStatus(ENDP2, EP_RX_VALID);
        SetEPTxStatus(ENDP2, EP_TX_NAK);


4,實現端點的回調函數(須要在usb_conf.h中註釋掉對應的回調函數宏定義)
[C]  純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*******************************************************************************
* Function Name  : EP1_OUT_Callback.
* Description    : EP1 OUT Callback Routine.
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void EP1_OUT_Callback( void )
{
         EP1_ReceivedCount = GetEPRxCount(ENDP1);
         PMAToUserBufferCopy(USB_Receive_Buffer, ENDP1_RXADDR, EP1_ReceivedCount);
         SetEPRxStatus(ENDP1, EP_RX_VALID);
}
/*******************************************************************************
* Function Name  : EP2_OUT_Callback.
* Description    : EP2 OUT Callback Routine.
* Input          : None.
* Output         : None.
* Return         : None.
*******************************************************************************/
void EP2_OUT_Callback( void )
{
         EP2_ReceivedCount = GetEPRxCount(ENDP2);
         PMAToUserBufferCopy(USB_Receive_Buffer, ENDP2_RXADDR, EP2_ReceivedCount);
         SetEPRxStatus(ENDP2, EP_RX_VALID);
}


5,完成主函數的測試程序
[C]  純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
int main( void )
{
         uint8_t data[256];
         uint32_t i=0;
         Set_System(); //系統時鐘初始化
         USART_Configuration(); //串口1初始化
         printf ( "\x0c\0" ); printf ( "\x0c\0" ); //超級終端清屏
         printf ( "\033[1;40;32m" ); //設置超級終端背景爲黑色,字符爲綠色
         printf ( "\r\n*******************************************************************************" );
         printf ( "\r\n************************ Copyright 2009-2012, EmbedNet ************************" );
         printf ( "\r\n*************************** [url=http://www.embed-net.com]http://www.embed-net.com[/url] **************************" );
         printf ( "\r\n***************************** All Rights Reserved *****************************" );
         printf ( "\r\n*******************************************************************************" );
         printf ( "\r\n" );
 
         USB_Interrupts_Config();
         Set_USBClock();
         USB_Init();
 
         while (1)
         {
                 if (EP1_ReceivedCount > 0){
                         USB_GetData(ENDP1,data,EP1_ReceivedCount);
                         USB_SendData(ENDP1,data,EP1_ReceivedCount);
                         printf ( "usb EP1 get data %d byte data\n\r" ,EP1_ReceivedCount);
                         for (i=0;i<EP1_ReceivedCount;i++){
                                 printf ( "0x%02X " ,data[i]);
                         }
                         printf ( "\n\r" );
                         EP1_ReceivedCount=0;
                 }
                 if (EP2_ReceivedCount > 0){
                         USB_GetData(ENDP2,data,EP2_ReceivedCount);
                         USB_SendData(ENDP2,data,EP2_ReceivedCount);
                         printf ( "usb EP2 get data %d byte data\n\r" ,EP2_ReceivedCount);
                         for (i=0;i<EP2_ReceivedCount;i++){
                                 printf ( "0x%02X " ,data[i]);
                         }
                         printf ( "\n\r" );
                         EP2_ReceivedCount=0;       
                 }
         }
}


到此,STM32的程序基本上編寫完成,而後編譯下載程序,若是一切順利,系統會檢測到一個新的設備並試圖加載對應的驅動,因爲咱們還沒作驅動程序,因此確定會加載驅動失敗,以下圖所示:
 

驅動程序生成
下面咱們就利用libusb自帶的inf-wizard工具生成USB驅動程序,該工具能夠到本文章的附件下載,其具體過程以下:
 

運行該程序,出現下圖對話框,點擊「Next」;
 

出現下圖對話框後選擇咱們須要生成驅動程序的設備;
 

這裏能夠寫該Device Name,咱們保持默認值,其餘的都不須要修改;
 

點擊Next後出現下圖對話框,咱們選擇一個目錄保存這個inf文件;
 

保存後的文件
 

若要當即安裝驅動,能夠點擊下面對話框的紅色框按鈕;
 

Win7下可能會出現以下對話框,點擊始終安裝;
 

到此,USB驅動程序自動生成完畢,若安裝了驅動,則在設備管理器裏面會看到以下信息
 

基於libusb的上位機驅動程序編寫
首先創建一個驅動程序工程,而後將libusb的庫(附件有下載)添加到工程裏面,編寫如下幾個函數
設備掃描函數,該函數用來找到插入電腦上的USB設備
[C]  純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
/**
   * @brief  掃描設備鏈接數
   * @param  NeedInit 是否須要初始化,第一次調用該函數須要初始化
   * @retval 識別到的指定設備個數
   */
int __stdcall USBScanDev( int NeedInit)
{
         if (NeedInit){
                 usb_init(); /* initialize the library */
                 usb_find_busses(); /* find all busses */
                 usb_find_devices(); /* find all connected devices */
         }
         return scan_dev(pBoard);
}


打開設備
[C]  純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
/**
   * @brief  打開指定的USB設備
   * @param  devNum        須要打開的設備號
   * @retval 打開狀態
   */
int __stdcall USBOpenDev( int DevIndex)
{
         pBoardHandle[DevIndex] = open_dev(DevIndex,pBoard);
         if (pBoardHandle[DevIndex]==NULL){
                 return SEVERITY_ERROR;
         } else {
                 return SEVERITY_SUCCESS;
         }
}


關閉設備
[C]  純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
/**
   * @brief  關閉指定的USB設備
   * @param  devNum        須要關閉的設備號
   * @retval 打開狀態
   */
int __stdcall USBCloseDev( int DevIndex)
{
         return close_dev(DevIndex,pBoardHandle);
}


BULK端點寫數據
[C]  純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
   * @brief  USB Bulk端點寫數據
   * @param  nBoardID 設備號
   * @param  pipenum 端點號
   * @param  sendbuffer 發送數據緩衝區
   * @param  len 發送數據字節數
   * @param  waittime 超時時間
   * @retval 成功發送的數據字節數
   */
 
int __stdcall USBBulkWriteData(unsigned int nBoardID, int pipenum, char *sendbuffer, int len, int waittime)
{
         int ret=0;
         if (pBoardHandle[nBoardID] == NULL){
                 return SEVERITY_ERROR;
         }
#ifdef TEST_SET_CONFIGURATION
     if (usb_set_configuration(pBoardHandle[nBoardID], MY_CONFIG) < 0)
     {
         usb_close(pBoardHandle[nBoardID]);
         return SEVERITY_ERROR;
     }
#endif
 
#ifdef TEST_CLAIM_INTERFACE
     if (usb_claim_interface(pBoardHandle[nBoardID], 0) < 0)
     {
         usb_close(pBoardHandle[nBoardID]);
         return SEVERITY_ERROR;
     }
#endif
 
#if TEST_ASYNC
     // Running an async write test
     ret = transfer_bulk_async(dev, pipenum, sendbuffer, len, waittime);
#else
         ret = usb_bulk_write(pBoardHandle[nBoardID], pipenum, sendbuffer, len, waittime);
         /*if((len%64) == 0){
                 usb_bulk_write(pBoardHandle[nBoardID], pipenum, sendbuffer, 0, waittime);
         }*/
#endif
#ifdef TEST_CLAIM_INTERFACE
     usb_release_interface(pBoardHandle[nBoardID], 0);
#endif
     return ret;
}


BULK端點讀數據
[C]  純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
   * @brief  USB Bulk讀數據
   * @param  nBoardID 設備號
   * @param  pipenum 端點號
   * @param  readbuffer 讀取數據緩衝區
   * @param  len 讀取數據字節數
   * @param  waittime 超時時間
   * @retval 讀到的數據字節數
   */
int __stdcall USBBulkReadData(unsigned int nBoardID, int pipenum, char *readbuffer, int len, int waittime)
{
         int ret=0;
         if (pBoardHandle[nBoardID] == NULL){
                 return SEVERITY_ERROR;
         }
#ifdef TEST_SET_CONFIGURATION
     if (usb_set_configuration(pBoardHandle[nBoardID], MY_CONFIG) < 0)
     {
         usb_close(pBoardHandle[nBoardID]);
         return SEVERITY_ERROR;
     }
#endif
 
#ifdef TEST_CLAIM_INTERFACE
     if (usb_claim_interface(pBoardHandle[nBoardID], 0) < 0)
     {
         usb_close(pBoardHandle[nBoardID]);
         return SEVERITY_ERROR;
     }
#endif
 
#if TEST_ASYNC
     // Running an async read test
     ret = transfer_bulk_async(pGinkgoBoardHandle[nBoardID], pipenum, sendbuffer, len, waittime);
#else
         ret = usb_bulk_read(pBoardHandle[nBoardID], pipenum, readbuffer, len, waittime);
#endif
#ifdef TEST_CLAIM_INTERFACE
     usb_release_interface(pBoardHandle[nBoardID], 0);
#endif
     return ret;
}


到此,PC端的驅動程序編寫基本完成,下面就是驅動程序的測試,咱們能夠把以前這個程序生成爲一個dll文件,而後單獨創建一個測試工程來測試這個dll文件中的函數,測試程序以下:
[C]  純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
相關文章
相關標籤/搜索