软件篇(二)—— USB-HID
USB HID全称为USB Human Interface Devices,即USB人机接口设备,顾名思义HID设备是直接与人交互的设备,例如键盘、鼠标、游戏杆等。不过HID设备并不一定要有人机接口,只要符合HID类别规范的设备都是HID设备。
这里推荐一本参考书《圈圈教你玩USB》,这本书以实例的方式,一步步讲解USB设备与驱动程序和应用程序开发的详细过程及步骤,同时在光盘中附带了完整的源代码。
一、描述符
USB HID的核心就是描述符,描述符中记录了设备的类型、厂商ID、产品ID、端点情况、版本号等信息,通过描述符USB主机才能知道一个设备的信息、功能以及行为。HID设备的描述符除了5个USB的标准描述符(设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符)外,还包括三个HID设备类特定的描述符:HID描述符、报告描述符、实体描述符。
1. 设备描述符
设备描述符主要信息包括:设备所使用的USB协议版本号、设备类型、端点0的最大包大小、厂商ID、产品ID、设备版本号、厂商字符串索引、产品字符串索引、设备序列号索引、可能的配置数等。
2. 配置描述符
配置描述符主要信息包括:配置所包含的接口数、配置的编号、供电方式、是否支持远程唤醒、电流需求量等。
3. 接口描述符
接口描述符主要信息包括:接口的编号、接口的端点数、接口所使用的类、子类、协议等。
4. 端点描述符
端点描述符主要信息包括:端点号及方向、端点的传输类型、最大包长度、查询时间间隔等。
5. 字符串描述符
字符串描述符主要信息是提供一些人们阅读的信息,是个非必要描述符。
一个设备有且只有一个设备描述符。设备描述符决定了该设备有多少种配置,每种配置都有一个配置描述符。
二、HID设备枚举
USB主机检测到USB设备插入后,即开始对设备进行枚举。整个枚举过程是基于USB的一种传输模式–控制传输。
控制传输分为三个步骤:建立过程、可选的数据过程、状态过程。
其中,建立过程都是由USB主机发起,它开始于一个Setup令牌包,后面紧跟着一个DATA0包,
-
如果是控制输入传输,那么数据过程就是输入数据;
-
如果控制输出传输,那么数据过程就是输出数据。
数据过程之后是状态过程。状态过程刚好与数据过程的数据传输方向相反。
首先,主机检测到USB设备插入后,就会先对设备进行复位。所有的USB设备在总线复位后其地址都为0,这样主机就可以跟那些刚刚插入的设备通过地址0通信。
复位后,USB主机就会对地址为0的设备发送获取设备描述符的标准请求。获取到设备描述符后,主机就会获取配置描述符9个字节。主机获取到配置描述符后,根据里面的配置集合总长度,再获取配置集合。配置集合包括配置描述符,接口描述符,端点描述符等。
详细流程如下:
1. 主机发起第一个控制传输(获取设备描述)
(1). 主机SETUP包(发往地址0端点0)、主机数据包(请求设备描述符)、设备握手包ACK。
设备产生端点0数据输出中断,固件程序要根据数据包中的主机要求做好准备,这里是在端点0输入缓冲区准备好设备描述符。
(2). 数据过程:主机先发一个IN令牌包、设备发一个数据包(这个数据已经准备好,SIE收到IN令牌后,直接送到总线上,用户此时不干预)、主机发ACK包。
此时SIE产生端点0数据输入中断,表明主机已经取走了设备所准备的数据,用户也可以在该中断处理程序中作自己的处理(如清理操作等)。主机只接受一次数据,最少8个字节。如果用户数据没有发完,又在控制端点输入缓冲区,准备了数据,主机也不理会。
(3). 状态过程:主机发OUT包(通知设备要输出)、主机发0字节状态数据包(这个是0字节,表明自己收到设备描述符)、设备发握手ACK包。
此时设备不会产生端点0数据输出中断,此时没有数据。
2、枚举过程中,第二个来回:设置地址
第一个来回成功以后,主机再次复位总线。进入地址设置控制传输阶段。
(1). 主机SETUP包(发往地址0端点0)、主机数据包(请求设置地址)、设备握手包ACK。SETUP包后面都会跟一个表明主机SETUP目的的数据包,要么GET,要么SET。
设备产生端点0数据输出中断,固件程序要根据数据包中的主机要求做好准备,这里是在根据主机发来的地址写入自己的地址控制寄存器。
(2). 数据过程,本次传输没有数据。
(3). 状态过程:主机发IN包(通知设备要返回数据)、设备发0字节状态数据包(表明地址设置已经成功)、主机发握手ACK包(地址设置已经生效)。
此时设备不会产生端点0数据输入中断,没有数据。
3、枚举过程中,第三个来回:主机使用新地址获取完整的设备描述符
主机采用新地址发起第一个控制传输:
(1). 主机SETUP包(发往新的地址端点0)、主机数据包(请求设备描述符)、设备握手包ACK。
设备产生端点0数据输出中断,固件程序要根据数据包中的主机要求做好准备,这里是在端点0输入缓冲区准备好设备描述符。
(2). 数据过程,主机先发一个IN令牌包、设备发一个数据包(这个数据已经准备好,SIE收到IN令牌后,直接送到总线上,用户此时不干预)、主机发ACK包。
此时SIE产生端点0数据输入中断,表明主机已经取走了设备所准备的数据,用户可以该中断处理程序中要做如下处理:如果一次没有将描述符送完,要再次将剩下的内容填充端点0输入缓冲区。第二次数据传输:主机再发一个IN令牌包、设备发一个数据包、主机发ACK包。此时SIE再次产生端点0数据输入中断,如果数据已经发完了。这里就不处理了。进入状态过程。
(3). 状态过程:主机发OUT包(通知设备要输出)、主机发0字节状态数据包(表明自己收到设备描述符)、设备发握手ACK包。
接下来获取配置描述符、配置集合、字符串描述符、报告描述符的过程差不多,这里就不再叙述了。
/* 设备描述符 */
const uint8_t CustomHID_DeviceDescriptor[CUSTOMHID_SIZ_DEVICE_DESC] =
{
0x12, /*bLength:长度,设备描述符的长度为18字节*/
USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType:类型,设备描述符的编号是0x01*/
0x00, /*bcdUSB:所使用的USB版本为2.0*/
0x02,
0x00, /*bDeviceClass:设备所使用的类代码*/
0x00, /*bDeviceSubClass:设备所使用的子类代码*/
0x00, /*bDeviceProtocol:设备所使用的协议*/
0x40, /*bMaxPacketSize:最大包长度为8字节*/
0x78, /*idVendor:厂商ID为0x7788*/
0x67,
0x12, /*idProduct:产品ID为0x1122*/
0x01,
0x00, /*bcdDevice:设备的版本号为2.00*/
0x02,
1, /*iManufacturer:厂商字符串的索引*/
2, /*iProduct:产品字符串的索引*/
3, /*iSerialNumber:设备的序列号字符串索引*/
0x01 /*bNumConfiguration:设备有1种配置*/
}; /* CustomHID_DeviceDescriptor */
/* 配置描述符(配置、接口、端点、类、厂商) */
const uint8_t CustomHID_ConfigDescriptor[CUSTOMHID_SIZ_CONFIG_DESC] =
{
0x09, /* bLength: Configuration 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*/
0xC0, /*bmAttributes: Self powered */
0x96, /*MaxPower 300 mA: this current is used for detecting Vbus*/
/************** Descriptor of Joystick Mouse interface ****************/
/* 09 */
0x09, /*bLength: Interface Descriptor size*/
USB_INTERFACE_DESCRIPTOR_TYPE,/*bDescriptorType: Interface descriptor type*/
0x00, /*bInterfaceNumber: Number of Interface*/
0x00, /*bAlternateSetting: Alternate setting*/
0x02, /*bNumEndpoints*/
0x03, /*bInterfaceClass: HID*/
0x00, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
0x01, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
0, /*iInterface: Index of string descriptor*/
/******************** Descriptor of Joystick Mouse HID ********************/
/* 18 */
0x09, /*bLength: HID Descriptor size*/
HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
0x00, /*bcdHID: HID Class Spec release number*/
0x01,
0x00, /*bCountryCode: Hardware target country*/
0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/
0x22, /*bDescriptorType*/
CUSTOMHID_SIZ_REPORT_DESC,/*wItemLength: Total length of Report descriptor*/
0x00,
/******************** Descriptor of Joystick Mouse endpoint ********************/
/* 27 */
0x07, /*bLength: Endpoint Descriptor size*/
USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
0x81, /*bEndpointAddress: Endpoint Address (IN)*/
0x03, /*bmAttributes: Interrupt endpoint*/
0x40, /*wMaxPacketSize: 4 Byte max */
0x00,
0x05, /*bInterval: Polling Interval (32 ms)*/
/* 34 */
0x07, /*bLength: Endpoint Descriptor size*/
USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
0x01, /*bEndpointAddress: Endpoint Address (OUT)*/
0x03, /*bmAttributes: Interrupt endpoint*/
0x01, /*wMaxPacketSize: 8 Byte max */
0x00,
0x05, /*bInterval: Polling Interval (32 ms)*/
/* 41 */
}; /* CustomHID_ConfigDescriptor */
/* 报告描述符 */
const uint8_t CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
{
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard)
0xa1, 0x01, // COLLECTION (Application)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x08, // REPORT_COUNT (8)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0x95, 0x02, // REPORT_COUNT (5)
0x75, 0x01, // REPORT_SIZE (1)
0x05, 0x08, // USAGE_PAGE (LEDs)
0x19, 0x01, // USAGE_MINIMUM (Num Lock)
0x29, 0x02, // USAGE_MAXIMUM (Caps Lock)
0x91, 0x02, // OUTPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x06, // REPORT_SIZE (3)
0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
0x95, 0x3e, // REPORT_COUNT (62)
0x75, 0x08, // REPORT_SIZE (8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0xFF, // LOGICAL_MAXIMUM (1)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
0x81, 0x00, // INPUT (Data,Ary,Abs)
0xC0
}; /* CustomHID_ReportDescriptor */
/* USB String Descriptors (optional) */
/* 语言ID描述符 */
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"
'K', 0, 'e', 0, 'y', 0, 'B', 0, 'o', 0, 'a', 0, 'r', 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, 'W', 0, 'A', 0, 'N', 0, 'G', 0, 'W', 0, 'A', 0, 'N', 0, 'G', 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
};