libusb开发流程
前言
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 |
/******************************************************************************* * * * * * *******************************************************************************/ void
void ) { EP1_ReceivedCount PMAToUserBufferCopy(USB_Receive_Buffer, SetEPRxStatus(ENDP1, } /******************************************************************************* * * * * * *******************************************************************************/ void
void ) { 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 |
int
void ) { uint8_t uint32_t Set_System(); //系统时钟初始化 USART_Configuration(); //串口1初始化 printf ( "\x0c\0" ); printf ( "\x0c\0" ); //超级终端清屏 printf ( "\033[1;40;32m" ); //设置超级终端背景为黑色,字符为绿色 printf ( "\r\n*******************************************************************************" ); printf ( "\r\n************************ ); printf ( "\r\n*************************** ); printf ( "\r\n***************************** ); printf ( "\r\n*******************************************************************************" ); printf ( "\r\n" ); 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 ( "\n\r" ); 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 ( "\n\r" ); 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 |
/** * * * */ int
int
{ 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 |
/** * * * */ int
int
{ pBoardHandle[DevIndex] if (pBoardHandle[DevIndex]==NULL){ return
} else { return
} } |
关闭设备
01 02 03 04 05 06 07 08 09 |
/** * * * */ int
int
{ 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 |
/** * * * * * * * */ int
int
int
char
int
int
{ 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 |
/** * * * * * * * */ int
int
int
char
int
int
{ 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 int
int
{ int
int
char
char
for ( int
WriteTestData[i] } //扫描设备连接数,需要初始化 DevNum printf ( "设备连接数为:%d\n" ,DevNum); //打开设备0 ret if (ret printf ( "打开设备失败!\n" ); return
} else { printf ( "打开设备成功!\n" ); } //端点1写数据 ret if (ret printf ( "端点1写数据失败!%d\n" ,ret); return
} else { printf ( "端点1写数据成功!\n" ); } //端点1读数据 ret if (ret printf ( "端点1读数据失败!%d\n" ,ret); return
} else { printf ( "端点1读数据成功!\n" ); for ( int
printf ( "%02X ,ReadTestData[i]); if (((i+1)%16)==0){ printf ( "\n" ); } } printf ( "\n" ); } Sleep(100); //端点2写数据 ret if (ret printf ( "端点2写数据失败!%d\n" ,ret); return
} else { printf ( "端点2写数据成功!\n" ); } //端点2读数据 ret if (ret printf ( "端点2读数据失败!%d\n" ,ret); return
} else { printf ( "端点2读数据成功!\n" ); for ( int
printf ( "%02X ,ReadTestData[i]); if (((i+1)%16)==0){ printf ( "\n" ); } } printf ( "\n" ); } getchar (); return
} |
到此,整个开发流程基本完成,下面是本套程序的测试图片
串口打印输出
PC端测试程序输出
Bus Hound抓取到的USB数据