STM32 USB HID详解.docx
《STM32 USB HID详解.docx》由会员分享,可在线阅读,更多相关《STM32 USB HID详解.docx(19页珍藏版)》请在冰豆网上搜索。
STM32USBHID详解
STM32USBHID详解
1、USB简介
2、USB描述符
USB只是一个总线,只提供一个数据通路而已。
USB总线驱动程序并不知道一个设备具体如何操作,有哪些行为。
具体的一个设备实现什么功能,要由设备自己来决定。
那么,USB主机是如何知道一个设备的功能以及行为呢?
这就要通过描述符来实现了。
描述符中记录了设备的类型、厂商ID和产品ID(通常依靠它们来加载对应的驱动程序)、端点情况、版本号等众多信息。
标准的USB设备有5种USB描述符:
设备描述符,配置描述符,接口描述符,端点描述符,字符串描述符。
下面详解:
2.1、设备描述符
一个USB设备只有一个设备描述符。
设备描述符主要记录的信息有:
设备所使用的USB协议版本号、设备类型、端点0的最大包大小、厂商ID(VID)和产品ID(PID)、设备版本号、厂商字符串索引、产品字符串索引、设备序列号索引、可能的配置数等。
偏移量
域
大小/字节
说明
0
bLength
1
该描述符的长度(0x12=18字节)
1
bDescriptorType
1
描述符类型(0x01设备描述符)
2
bcdUSB
2
本设备使用的USB协议版本
4
bDeviceClass
1
类代码
5
bDeviceSubClass
1
子类代码
6
bDeviceProtocol
1
协议码
7
bMaxPacketSize
1
端点0最大包长
8
idVendor
2
厂商ID
10
idProduct
2
产品ID
12
bcdDevice
2
设备版本号
14
iManufacturer
1
描述厂商的字符串索引
15
iProduct
1
描述产品的字符串索引
16
iSerialNumber
1
产品序列号的字符串索引
17
bNumConfigurations
1
可能的配置数
2.2、配置描述符
设备描述符里决定了该设备有多少种配置,每种配置都有一个配置描述符。
配置描述符主要记录的信息有:
配置所包含的接口数、配置的编号、供电方式、是否支持远程唤醒、电流需求量等。
偏移量
域
大小/字节
说明
0
bLength
1
该描述符的长度(0x09字节)
1
bDescriptorType
1
描述符类型(0x02配置描述符)
2
wTotalLength
2
配置、接口、端点和类描述符字节总和
4
bNumInterfaces
1
支持接口数
5
bConfigurationValue
1
本配置描述符标识
6
iConfiguration
1
配置描述符说明字符串索引
7
bmAttributes
1
电源及唤醒
8
MaxPower
1
设备耗电电流
2.3、接口描述符
在每个配置描述符中又定义了该配置有多少个接口,每个接口都有一个接口描述符。
接口描述符主要记录的信息有:
接口的编号、接口的端点数、接口所使用的类、子类、协议等。
偏移量
域
大小/字节
说明
0
bLength
1
该描述符的长度(0x09字节)
1
bDescriptorType
1
描述符类型(0x04接口描述符)
2
bInterfaceNumber
1
本接口描述符标识
3
bAlternateSetting
1
4
bNumEndpoints
1
接口端点数
5
bInterfaceClass
1
接口类代码
6
bInterfaceSubClass
1
启动类型1=BOOT,0=NoBOOT
7
bInterfaceProtocol
1
0=None,1=Keyboard,2=Mouse
8
iInterface
1
接口描述符说明字符串索引
2.4、[类描述符]
该描述符不是必须的,如果配置的USB类型有类特殊描述符(如HID类),它跟在相应的接口描述符之后。
2.5、端点描述符
在接口描述符里又定义了该接口有多少个端点,每个端点都有一个端点描述符。
端点描述符主要记录的信息有:
端点号及方向、端点的传输类型、最大包长度、查询时间间隔等。
偏移量
域
大小/字节
说明
0
bLength
1
该描述符的长度(0x07字节)
1
bDescriptorType
1
描述符类型(0x5端点描述符)
2
bEndpointAddress
1
端点地址
3
bmAttributes
1
端点类型
4
wMaxPacketSize
2
端点发送接收最大包长
6
bInterval
1
中断端点轮训时间间隔
2.6、[字符串描述符]
字符串描述符主要是提供一些方便人们阅读的信息,它不是必需的。
偏移量
域
大小/字节
说明
0
bLength
1
该描述符的长度(0x04/0xXX字节),第一个字符串描述符0x04
1
bDescriptorType
1
描述符类型(0x3字符串描述符)
2
wLANGID
2/XX
第一个字符串描述符时2字节,表示语言编码,其他自定义
3、USBHID
为了把一个设备识别为HID类别,设备在定义描述符的时候必须遵守HID规范。
除了USB标准定义的一些描述符外,HID设备还必须定义HID描述符。
另外设备和主机的通信是通过报告的形式来实现的,所以还必须定义报告描述符;而物理描述符不是必需的。
还有就是HID描述符是关联于接口(而不是端点)的,所以设备不需要为每个端点都提供一个HID描述符。
详情参看《USBHID协议中文版_USBHID设备》
3.1、HID描述符
HID描述符是HID类特有的描述符,保证设备正确识别,遵循规定的格式。
偏移量
域
大小/字节
说明
0
bLength
1
该描述符的长度(0x09字节)
1
bDescriptorType
1
描述符类型(0x21HID描述符)
2
bcdHID
2
HID规范版本
4
bCountryCode
1
国家代码
5
bNumDescriptors
1
支持的描述符个数
6
bDescriptorType
1
支持的描述符类别0x22报表
7
wItemLength
2
支持的描述符长度
3.2、报告描述符
报告描述符比较复杂,它是以item形式排列组合而成,无固定长度。
为了准确描述来自一个控制管道的数据,一个报告描述符必须包括以下内容:
偏移量
域
大小/字节
说明
0
USAGE_PAGE
2
2
USAGE
2
4
COLLECTION
2
6
USAGEID
2
8
LOGICAL_MINIMUM
2
10
LOGICAL_MAXIMUM
3
13
REPORT_SIZE
2
15
REPORT_COUNT
2
17
INPUT/OUTPUT
2
…上述重复
XX
END_COLLECTION
3.3、[物理描述符]
该描述符不是必须的。
4、STM32USBHID
在STM32上实现USBHID的功能,首先芯片选择要选则带有USB接口的系列。
正确搭建硬件环境,添加使用官方USB库。
4.1、USB库简介
详细正确内容请参看《深入解析STM32_USB-FS-Device_Lib库V0.2》以下仅个人整理。
包含官方库文件,及自己封装的一些函数。
1、hw_config
其他工程摘的USB配置(IO、中断等)函数,也可自行实现在其他文件中。
2、usb_core.c
这个c文件是个庞大的文件,主要是定义了usb2.0的标注协议处理函数。
3、usb_desc.c
描述符相关
4、usb_endp.c
这个文件很简单,就是定义了结果几个端点输入输出函数。
5、usb_init.c
这个文件是主要是初始化。
6、usb_int.c
一看就知道跟中断相关。
在该文件中定义了两个函数,分别为低优先级的端点正确传输中断服务程序CTR_LP()和高优先级端点正确传输的中断服务程序CTR_HP()。
7、usb_io.c
自己封装USB操作函数,初始化、发送数据等。
8、usb_istr.c
这个c文件,主要是注册一些端点响应函数,如上面的端点输入输出回电函数,还有就是ISTR中断状态状态寄存器的中断处理。
9、usb_mem.c
从文件名就能知道跟内存有关,这个文件主要定义了两个函数,一个读双缓冲区PMA的数据PMAToUserBufferCopy(),另一个是写数据到双缓冲区PMA,UserToPMABufferCopy。
如果,当你的usb设备接收到了数据,当然数据存放在PMA中了,我们要读出数据就要用到PMAToUserBufferCopy()函数了,如果我们想要发送数据给usb主机,就要将你要发送的数据拷贝到PMA缓冲区中了,这样才能发送出去,原理跟串口类似。
10、usb_prop.c
usb_prop.c文件可以说是一个蛮重要的文件,因为USB的许多处理函数都在这里定义。
在无论是在USB的建立阶段、数据阶段还是状态阶段的一些处理都在这个文件,USB标准函数请求的函数也在这个文件里。
11、usb_pwr.c
这个文件看文件名就知道跟功耗有关了,有很多的状态:
上电、掉电、挂起、恢复。
12、usb_regs.c
13、usb_sil.c
这个文件主要是简单接口层的初始化,和端点的读写操作函数。
总共有3个函数:
USB_SIL_Init();USB_SIL_Write();USB_SIL_Read()。
14、platform_config.h
其他工程摘的USB上拉IO定义。
4.2、USBHID设备自定义
在各种STM32学习板自带的演示例程中,都有几个USB的例程。
如果我们想实现一个USB功能,可以拿里面的例子来改。
那么具体要改哪些地方呢?
首先要改各种描述符,然后是具体的数据处理。
1、更改设备描述符
描述符在文件usb_desc.c中。
设备描述符的结构都标准的,长度也是固定的。
更改如下:
constuint8_tDeviceDescriptor[SIZ_DEVICE_DESC]=
{
0x12,/*bLength*/
USB_DEVICE_DESCRIPTOR_TYPE,/*bDescriptorType*/
0x00,/*bcdUSB*/
0x02,
0x00,/*bDeviceClass*/
0x00,/*bDeviceSubClass*/
0x00,/*bDeviceProtocol*/
0x40,/*bMaxPacketSize40*/
0x83,/*idVendor(0x0483)*/
0x04,
0x50,/*idProduct=0x5750*/
0x57,
0x00,/*bcdDevicerel.2.00*/
0x02,
0x01,/*Indexofstringdescriptordescribingmanufacturer*/
0x02,/*Indexofstringdescriptordescribingproduct*/
0x03,/*Indexofstringdescriptordescribingthedeviceserialnumber*/
0x01/*bNumConfigurations*/
};
2、更改配置描述符集合
配置描述符集合包括配置描述符、接口描述符、类特殊描述符(这里是HID描述符)、以及端点描述符。
constuint8_tConfigDescriptor[SIZ_CONFIG_DESC]=
{
/*配置描述符*/
0x09,/*bLength:
ConfiguationDescriptorsize*/
USB_CONFIGURATION_DESCRIPTOR_TYPE,/*bConfigurationType*/
CUSTOMHID_SIZ_CONFIG_DESC,/*wTotalLength:
Bytesreturned*/
0x00,
0x01,/*bNumInterfaces:
1interface*/
0x01,/*bConfigurationValue:
Configurationvalue*/
0x00,/*iConfiguration*/
0xC0,/*bmAttributes:
Buspowered*/
0x96,/*MaxPower0x96*2=300mA*/
/*接口描述符*/
0x09,/*bLength:
InterfaceDescriptorsize*/
USB_INTERFACE_DESCRIPTOR_TYPE,/*bInterfaceType*/
0x00,/*bInterfaceNumber:
NumberofInterface*/
0x00,/*bAlternateSetting:
Alternatesetting*/
0x02,/*bNumEndpoints*/
0x03,/*bInterfaceClass:
HID*/
0x00,/*bInterfaceSubClass:
1=BOOT,0=noboot*/
0x00,/*nInterfaceProtocol:
0=none*/
0,/*iInterface:
Indexofstringdescriptor*/
/*HID描述符*/
0x09,/*bLength:
HIDDescriptorsize*/
HID_DESCRIPTOR_TYPE,/*bDescriptorType:
HID*/
0x10,/*bcdHID:
HIDClassSpecreleasenumber*/
0x01,
0x00,/*bCountryCode:
Hardwaretargetcountry*/
0x01,/*bNumDescriptors:
NumberofHIDclassdescriptorstofollow*/
0x22,/*bDescriptorType*/
CUSTOMHID_SIZ_REPORT_DESC,/*wItemLength:
TotallengthofReportdescriptor*/
0x00,
/*端点描述符*/
0x07,/*bLength:
EndpointDescriptorsize*/
USB_ENDPOINT_DESCRIPTOR_TYPE,/*bDescriptorType:
*/
0x82,/*bEndpointAddress:
EndpointAddress(IN)*/
//bit3...0:
theendpointnumber
//bit6...4:
reserved
//bit7:
0(OUT),1(IN)
0x03,/*bmAttributes:
Interruptendpoint*/
0x40,/*wMaxPacketSize:
64Bytesmax*/
0x00,
0x02,/*bInterval:
PollingInterval(2ms)*/
0x07,/*bLength:
EndpointDescriptorsize*/
USB_ENDPOINT_DESCRIPTOR_TYPE,/*bDescriptorType:
*/
0x01,/*bEndpointAddress:
*/
/*EndpointAddress(OUT)*/
0x03,/*bmAttributes:
Interruptendpoint*/
0x40,/*wMaxPacketSize:
64Bytesmax*/
0x00,
0x02,/*bInterval:
PollingInterval(2ms)*/
};
3、更改字符串描述符
字符串描述符主要是设备的显示名称等。
constuint8_tStringLangID[SIZ_STRING_LANGID]=
{
CUSTOMHID_SIZ_STRING_LANGID,
USB_STRING_DESCRIPTOR_TYPE,
0x09,
0x04
};/*LangID=0x0409:
U.S.English*/
constuint8_tStringVendor[SIZ_STRING_VENDOR]=
{
CUSTOMHID_SIZ_STRING_VENDOR,/*SizeofVendorstring*/
USB_STRING_DESCRIPTOR_TYPE,/*bDescriptorType*/
'M',0,'y',0,'U',0,'S',0,'B',0,'_',0,'H',0,'I',0,'D',0
};
constuint8_tStringProduct[SIZ_STRING_PRODUCT]=
{
CUSTOMHID_SIZ_STRING_PRODUCT,/*bLength*/
USB_STRING_DESCRIPTOR_TYPE,/*bDescriptorType*/
'C',0,'S',0,'C',0,'',0,'R',0,'e',0,'a',0,'d',0,'e',0,'r',0,'',0
};
uint8_tStringSerial[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
};
4、更改报告描述符
报告描述符比较复杂,这里就不详述了
constuint8_tReportDescriptor[SIZ_REPORT_DESC]=
{
//
0x05,0x8c,/*USAGE_PAGE(STPage)*/
0x09,0x01,/*USAGE(DemoKit)*/
0xa1,0x01,/*COLLECTION(Application)*/
//TheInputreport
0x09,0x03,//USAGEID-Vendordefined
0x15,0x00,//LOGICAL_MINIMUM(0)
0x26,0x00,0xFF,//LOGICAL_MAXIMUM(255)
0x75,0x08,//REPORT_SIZE(8bit)
0x95,0x40,//REPORT_COUNT(64Byte)
0x81,0x02,//INPUT(Data,Var,Abs)
//TheOutputreport
0x09,0x04,//USAGEID-Vendordefined
0x15,0x00,//LOGICAL_MINIMUM(0)
0x26,0x00,0xFF,//LOGICAL_MAXIMUM(255)
0x75,0x08,//REPORT_SIZE(8bit)
0x95,0x40,//REPORT_COUNT(64Byte)
0x91,0x02,//OUTPUT(Data,Var,Abs)
0xc0/*END_COLLECTION*/
};
5、更改端口初始化
对应描述符中的端口设置对端口初始化。
在usb_prop.c文件中,找到voidCustomHID_Reset(void)函数,该函数是负责初始化端点的。
…
/*InitializeEndpoint1*/
SetEPType(ENDP1,EP_INTERRUPT);
SetEPRxAddr(ENDP1,ENDP1_RXADDR);
SetEPRxCount(ENDP1,REPORT_COUNT);
SetEPRxStatus(ENDP1,EP_RX_VALID);
/*InitializeEndpoint2*/
SetEPType(ENDP2,EP_INTERRUPT);
SetEPTxAddr(ENDP2,ENDP2_TXADDR);
SetEPTxCount(ENDP2,REPORT_COUNT);
SetEPTxStatus(ENDP2,EP_TX_NAK);
…
设置端点1接收,端点2发送。
6、更改数据接收函数
在usb_conf.h中找到#defineEP1_OUT_CallbackNOP_Process一行,将它屏蔽。
在usb_endp.c中增加voidEP1_OUT_Callback(void)回调函数
voidEP1_OUT_Callback(void)
{
u16count_tmp;
count_tmp=GetEPRxCount(ENDP1);//获取接收到数据长度
PMAToUserBufferCopy(USB_Receive_Buffer+USB_Receive_DataLen,ENDP1_RXADDR,count_tmp);//拷贝出数据
SetEPRxValid(ENDP1);//完成拷贝后置有效状态,从而EP1发送ACK主机可以进行下一个数据包的发送
…//自行处理
}
7、实现数据发送函数
发送过程就是,UserToPMABufferCopy拷贝要发送的数据至缓冲区,SetEPTxCount设置发送大小,SetEPTxValid启动发送,GetEPTxStatus等待发送完成。
封装发送数据的函数,方便使用。
voidUSB_IO_SendData()
{
USB_Send_Frame=0;
if(USB_Send_DataLen>64)
{
while(USB_Send_DataLen>=64)
{