TA的每日心情 | 衰 2017-11-2 11:47 |
---|
签到天数: 105 天 连续签到: 1 天 [LV.6]常住居民II
|
前言
USB的用途就不多说了,下面的内容主要就是讲解如何利用ST提供的USB驱动库和libusb上位机驱动库实现一个USB数据传输功能,为了降低开发难度,我们仅仅讲解Bulk传输模式,当然这也是用得比较多的传输模式。
开发流程
1,完成STM32单片机端的USB程序;
2,利用linusb自带的inf-wizard工具生成USB驱动;
3,基于libusb编写USB通信程序;
4,测试PC和单片机的数据通信;
STM32程序编写
1,完成描述符的修改,修改后的描述符如下(在usb_desc.c文件中)
设备描述符:
- <pre style="color: rgb(0, 0, 0); line-height: normal; widows: auto;">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 */</pre>
复制代码
配置描述符:- 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端点。
其他的描述符:- /* 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文件中):- /* 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函数中)- /* 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中注释掉对应的回调函数宏定义)- /*******************************************************************************
- * 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,完成主函数的测试程序- 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设备- /**
- * @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);
- }
复制代码 打开设备- /**
- * @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;
- }
- }
复制代码 关闭设备- /**
- * @brief 关闭指定的USB设备
- * @param devNum 需要关闭的设备号
- * @retval 打开状态
- */
- int __stdcall USBCloseDev(int DevIndex)
- {
- return close_dev(DevIndex,pBoardHandle);
- }
复制代码 BULK端点写数据- /**
- * @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端点读数据- /**
- * @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文件中的函数,测试程序如下:- // USB_DriverTest.cpp : 定义控制台应用程序的入口点。
- //
-
- #include "stdafx.h"
-
- #define EP1_OUT_SIZE 64
- #define EP1_IN_SIZE 64
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- int DevNum;
- int ret;
- char WriteTestData[256]={1,2,3,4,5,6,7,8,9};
- char ReadTestData[256]={0};
- for(int i=0;i<256;i++){
- WriteTestData[i] = i;
- }
- //扫描设备连接数,需要初始化
- DevNum = USBScanDev(1);
- printf("设备连接数为:%d\n",DevNum);
- //打开设备0
- ret = USBOpenDev(0);
- if(ret == SEVERITY_ERROR){
- printf("打开设备失败!\n");
- return SEVERITY_ERROR;
- }else{
- printf("打开设备成功!\n");
- }
-
- //端点1写数据
- ret = USBBulkWriteData(0,EP1_OUT,WriteTestData,EP1_OUT_SIZE,500);
- if(ret != EP1_OUT_SIZE){
- printf("端点1写数据失败!%d\n",ret);
- return SEVERITY_ERROR;
- }else{
- printf("端点1写数据成功!\n");
- }
- //端点1读数据
- ret = USBBulkReadData(0,EP1_IN,ReadTestData,EP1_IN_SIZE,500);
- if(ret != EP1_IN_SIZE){
- printf("端点1读数据失败!%d\n",ret);
- return SEVERITY_ERROR;
- }else{
- printf("端点1读数据成功!\n");
- for(int i=0;i<EP1_IN_SIZE;i++){
- printf("%02X ",ReadTestData[i]);
- if(((i+1)%16)==0){
- printf("\n");
- }
- }
- printf("\n");
- }
- Sleep(100);
- //端点2写数据
- ret = USBBulkWriteData(0,EP2_OUT,WriteTestData+64,64,500);
- if(ret != 64){
- printf("端点2写数据失败!%d\n",ret);
- return SEVERITY_ERROR;
- }else{
- printf("端点2写数据成功!\n");
- }
- //端点2读数据
- ret = USBBulkReadData(0,EP2_IN,ReadTestData,64,500);
- if(ret != 64){
- printf("端点2读数据失败!%d\n",ret);
- return SEVERITY_ERROR;
- }else{
- printf("端点2读数据成功!\n");
- for(int i=0;i<64;i++){
- printf("%02X ",ReadTestData[i]);
- if(((i+1)%16)==0){
- printf("\n");
- }
- }
- printf("\n");
- }
- getchar();
- return 0;
- }
复制代码 到此,整个开发流程基本完成,下面是本套程序的测试图片
串口打印输出
PC端测试程序输出
Bus Hound抓取到的USB数据
程序源码下载
libusb驱动生成工具下载: inf_tool.rar (778.26 KB, 下载次数: 1192)
STM32程序源码下载: USB_DriverSTM32F103.rar (677.81 KB, 下载次数: 1017, 售价: 10 金币)
PC端USB驱动下载: USB Driver.rar (266.56 KB, 下载次数: 860)
PC端USB驱动程序源码下载: USB_DriverBulk.rar (20.61 KB, 下载次数: 580, 售价: 10 金币)
PC端USB驱动测试程序源码下载: USB_DriverTest.rar (12.34 KB, 下载次数: 618, 售价: 10 金币)
libusb驱动包下载: libusb-win32-bin-1.2.6.0.rar (821.57 KB, 下载次数: 981)
|
|