前言
USB的用途就不多说了,下面的内容主要就是讲解如何利用ST提供的USB驱动库和libusb上位机驱动库实现一个USB数据传输功能,为了降低开发难度,我们仅仅讲解Bulk传输模式,当然这也是用得比较多的传输模式。
开发流程
1,完成STM32单片机端的USB程序;
2,利用linusb自带的inf-wizard工具生成USB驱动;
3,基于libusb编写USB通信程序;
4,测试PC和单片机的数据通信;
STM32程序编写
1,完成描述符的修改,修改后的描述符如下(在usb_desc.c文件中)
设备描述符:
|
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 |
const{ 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 0x02, 1, /*Index 2, /*Index 3, /*Index 0x01 /*bNumConfigurations*/};/* |
配置描述符:
|
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{ 0x09,/* USB_CONFIGURATION_DESCRIPTOR_TYPE,/* CUSTOMHID_SIZ_CONFIG_DESC, /* 0x00, 0x01, /* 0x01, /* 0x00, /* the 0xE0, /* /*Bus 0xFA, /* /************** /* 0x09, /* USB_INTERFACE_DESCRIPTOR_TYPE,/* 0x00, /* 0x00, /* 0x04, /* 0xDC, /* 0xA0, /* 0xB0, /* 0, /* /******************** /* 0x07, /* USB_ENDPOINT_DESCRIPTOR_TYPE,/* 0x81, /* 0x02, /* 0x40,0x00, /* 0x00, /* 0x07, /* USB_ENDPOINT_DESCRIPTOR_TYPE,/* 0x01, /* 0x02, /* 0x40,0x00, /* 0x00, /* 0x07, /* USB_ENDPOINT_DESCRIPTOR_TYPE,/* 0x82, /* 0x02, /* 0x40,0x00, /* 0x00, /* 0x07, /* USB_ENDPOINT_DESCRIPTOR_TYPE,/* 0x02, /* 0x02, /* 0x40,0x00, /* 0x00, /*};/* |
配置描述符就包含了端点描述符,我们用了4个端点,两个BULK-OUT端点,两个BULK-IN端点。
其他的描述符:
|
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 |
/*const{ CUSTOMHID_SIZ_STRING_LANGID, USB_STRING_DESCRIPTOR_TYPE, 0x09, 0x04};/*const{ CUSTOMHID_SIZ_STRING_VENDOR,/* USB_STRING_DESCRIPTOR_TYPE, /* // 'M','y','U','S','B','_','H','I',0,'D',0};const{ CUSTOMHID_SIZ_STRING_PRODUCT, /* USB_STRING_DESCRIPTOR_TYPE, /* 'B','y',','e','m','b','e',0,'d',0,'-',0,'n',0,'e',0,'t',0};uint8_t{ CUSTOMHID_SIZ_STRING_SERIAL, /* USB_STRING_DESCRIPTOR_TYPE, /* 'x','x','x','x','x','x','x',}; |
2,根据端点缓冲区大小配置端点缓冲区地址,配置信息如下(在usb_conf.h文件中):
|
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 |
/*#define/*/*#define#define/*/*//地址为32位对其,位4的倍数,不能超过//EP1#define#define////EP2#define#define |
3,初始化每个端点(在usb_prop.c文件中的CustomHID_Reset函数中)
|
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/*SetEPType(ENDP0,SetEPTxStatus(ENDP0,SetEPRxAddr(ENDP0,SetEPTxAddr(ENDP0,Clear_Status_Out(ENDP0);SetEPRxCount(ENDP0,SetEPRxValid(ENDP0);/* SetEPType(ENDP1, SetEPRxAddr(ENDP1, SetEPTxAddr(ENDP1, SetEPRxCount(ENDP1, SetEPRxStatus(ENDP1, SetEPTxStatus(ENDP1,/* SetEPType(ENDP2, SetEPRxAddr(ENDP2, SetEPTxAddr(ENDP2, SetEPRxCount(ENDP2, SetEPRxStatus(ENDP2, SetEPTxStatus(ENDP2, |
4,实现端点的回调函数(需要在usb_conf.h中注释掉对应的回调函数宏定义)
|
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 |
/*******************************************************************************************************************************************************************/voidvoid){ EP1_ReceivedCount PMAToUserBufferCopy(USB_Receive_Buffer, SetEPRxStatus(ENDP1,}/*******************************************************************************************************************************************************************/voidvoid){ EP2_ReceivedCount PMAToUserBufferCopy(USB_Receive_Buffer, SetEPRxStatus(ENDP2,} |
5,完成主函数的测试程序
|
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 |
intvoid){ uint8_t uint32_t Set_System();//系统时钟初始化 USART_Configuration();//串口1初始化 printf("x0c ");printf("x0c ");//超级终端清屏 printf(" 33[1;40;32m");//设置超级终端背景为黑色,字符为绿色 printf("
*******************************************************************************"); printf("
************************); printf("
***************************); printf("
*****************************); printf("
*******************************************************************************"); printf("
"); USB_Interrupts_Config(); Set_USBClock(); USB_Init(); while(1) { if(EP1_ReceivedCount USB_GetData(ENDP1,data,EP1_ReceivedCount); USB_SendData(ENDP1,data,EP1_ReceivedCount); printf("usb,EP1_ReceivedCount); for(i=0;i<EP1_ReceivedCount;i++){ printf("0x%02X,data[i]); } printf("
"); EP1_ReceivedCount=0; } if(EP2_ReceivedCount USB_GetData(ENDP2,data,EP2_ReceivedCount); USB_SendData(ENDP2,data,EP2_ReceivedCount); printf("usb,EP2_ReceivedCount); for(i=0;i<EP2_ReceivedCount;i++){ printf("0x%02X,data[i]); } printf("
"); EP2_ReceivedCount=0; } }} |
到此,STM32的程序基本上编写完成,然后编译下载程序,如果一切顺利,系统会检测到一个新的设备并试图加载对应的驱动,由于我们还没做驱动程序,所以肯定会加载驱动失败,如下图所示:
驱动程序生成
下面我们就利用libusb自带的inf-wizard工具生成USB驱动程序,该工具可以到本文章的附件下载,其具体过程如下:
运行该程序,出现下图对话框,点击“Next”;
出现下图对话框后选择我们需要生成驱动程序的设备;
这里可以写该Device Name,我们保持默认值,其他的都不需要修改;
点击Next后出现下图对话框,我们选择一个目录保存这个inf文件;
保存后的文件
若要立即安装驱动,可以点击下面对话框的红色框按钮;
Win7下可能会出现如下对话框,点击始终安装;
到此,USB驱动程序自动生成完毕,若安装了驱动,则在设备管理器里面会看到如下信息
基于libusb的上位机驱动程序编写
首先建立一个驱动程序工程,然后将libusb的库(附件有下载)添加到工程里面,编写以下几个函数
设备扫描函数,该函数用来找到插入电脑上的USB设备
|
01 02 03 04 05 06 07 08 09 10 11 12 13 14 |
/** * * * */intint{ if(NeedInit){ usb_init();/* usb_find_busses();/* usb_find_devices();/* } return} |
打开设备
|
01 02 03 04 05 06 07 08 09 10 11 12 13 14 |
/** * * * */intint{ pBoardHandle[DevIndex] if(pBoardHandle[DevIndex]==NULL){ return }else{ return }} |
关闭设备
|
01 02 03 04 05 06 07 08 09 |
/** * * * */intint{ return} |
BULK端点写数据
|
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 |
/** * * * * * * * */intintintcharintint{ int if(pBoardHandle[nBoardID] return }#ifdef if { usb_close(pBoardHandle[nBoardID]); return }#endif#ifdef if { usb_close(pBoardHandle[nBoardID]); return }#endif#if // ret#else ret /*if((len%64) usb_bulk_write(pBoardHandle[nBoardID], }*/#endif#ifdef usb_release_interface(pBoardHandle[nBoardID],#endif return} |
BULK端点读数据
|
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 |
/** * * * * * * * */intintintcharintint{ int if(pBoardHandle[nBoardID] return }#ifdef if { usb_close(pBoardHandle[nBoardID]); return }#endif#ifdef if { usb_close(pBoardHandle[nBoardID]); return }#endif#if // ret#else ret#endif#ifdef usb_release_interface(pBoardHandle[nBoardID],#endif return} |
到此,PC端的驱动程序编写基本完成,下面就是驱动程序的测试,我们可以把之前这个程序生成为一个dll文件,然后单独建立一个测试工程来测试这个dll文件中的函数,测试程序如下:
|
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 |
////#include#define #define intint{ int int char char for(int WriteTestData[i] } //扫描设备连接数,需要初始化 DevNum printf("设备连接数为:%d
",DevNum); //打开设备0 ret if(ret printf("打开设备失败!
"); return }else{ printf("打开设备成功!
"); } //端点1写数据 ret if(ret printf("端点1写数据失败!%d
",ret); return }else{ printf("端点1写数据成功!
"); } //端点1读数据 ret if(ret printf("端点1读数据失败!%d
",ret); return }else{ printf("端点1读数据成功!
"); for(int printf("%02X,ReadTestData[i]); if(((i+1)%16)==0){ printf("
"); } } printf("
"); } Sleep(100); //端点2写数据 ret if(ret printf("端点2写数据失败!%d
",ret); return }else{ printf("端点2写数据成功!
"); } //端点2读数据 ret if(ret printf("端点2读数据失败!%d
",ret); return }else{ printf("端点2读数据成功!
"); for(int printf("%02X,ReadTestData[i]); if(((i+1)%16)==0){ printf("
"); } } printf("
"); } getchar(); return} |
到此,整个开发流程基本完成,下面是本套程序的测试图片
串口打印输出
PC端测试程序输出
Bus Hound抓取到的USB数据