前言
USB的用途就不多说了,下面的内容主要就是讲解如何利用ST提供的USB驱动库和libusb的上位机驱动库实现一个USB数据传输功能,为了降低开发难度,我们仅仅讲解Bulk传输模式,当然这也是用得比较多的传输模式。
开发流程
1,完成STM32单片机端的USB程序;
2,利用libusb自带的inf-wizard工具生成USB驱动;
3,基于libusb编写USB通信程序;
4,测试PC和单片机的数据通信;
STM32程序编写
1,完成描述符的修改,修改后的描述符如下(在usb_desc.c文件中)
设备描述符:
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 */
配置描述符:
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 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
到此,STM32的程序基本上编写完成,然后编译下载程序,如果一切顺利,系统会检测到一个新的设备并试图加载对应的驱动,由于我们还没做驱动程序,所以肯定会加载驱动失败,如下图所示:
驱动程序生成
下面我们就利用libusb的自带的INF-向导工具生成的USB驱动程序,该工具可以到本文章的附件下载,其具体过程如下:
运行该程序,出现下图对话框,点击“下一步”。
出现下图对话框后选择我们需要生成驱动程序的设备;
这里可以写该设备名称,我们保持默认值,其他的都不需要修改;
点击下一步后出现下图对话框,我们选择一个目录保存这个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
到此,整个开发流程基本完成,的英文下面本。套程序的测试图片
串口打印输出
PC端测试程序输出
Bus Hound抓取到的USB数据
程序源码下载
libusb的驱动生成工具下载: inf_tool.rar
STM32程序源码下载: USB_DriverSTM32F103.rar
PC端USB驱动下载: USB Driver.rar
PC端USB驱动程序源码下载: USB_DriverBulk.rar
PC端USB驱动测试程序源码下载: USB_DriverTest.rar
libusb的驱动包下载: libusb-win32-bin-1.2.6.0.rar