前言 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
|
|