USB_HID协议基础

目录

一、HID类设备相关概念

1. USB-HID名词解释

2. HID类设备数据传输特性

3. 按照传输速度对USB设备进行分类

二、USB设备描述符

1.USB标准描述符

1.1 各个描述符之间的关系

三、HID类设备特有的描述符

1. 设备描述符

2. 配置描述符

3. 接口描述符

4. 端点描述符

5. HID描述符

5.1 HID描述符的结构

5.2 HID描述符定义示例

6. 报告描述符

6.1 报告描述符的结构

6.2 示例

7.zephyr设备栈中报告描述符的注册和返回

7.1 报告描述符的注册示例(可忽略本小节)

7.2 注册回调函数

7.3 中断回调流程

7.3.1 控制器驱动层中断

7.3.2 核心层接口被回调

7.3.3 设备类驱动层接口被回调

7.4 报告描述符数据指针的更新以及数据的返回

7.5 总结

四、HID用途表文档简述—按键的用途ID

1. 用途表文档

2. 解析键盘按键值上报

2.1 按键的用途ID(查表可知)

2.2 输入报告的分析

2.3 实例1(单个按键按下)

3. 对比实验数据

3.1 勾选要监听的设备

3.2 开始捕获

3.3 实例2(组合按键按下)

五、解析程序代码(可忽略本小节)

1. HID类设备接口描述符的定义

2. 端点描述符在程序中的定义

3. HID报告描述符在程序中的定义

4. Shell命令行模拟键值的上报和数据的捕获

4.1 shell_hid_keyboard()函数

4.2  USB Hound捕获数据确认


一、HID类设备相关概念

1. USB-HID名词解释

Human Interface Deviece。指的是直接与人进行交互的设备,软硬件架构图如下(Layer Diagram):

USB_HID协议基础_第1张图片

 一些典型的HID类设备包括:USB鼠标(Keyboards)、USB键盘(mouse)、游戏操纵杆(joysticks.)。这些都是标准设备,而HID协议还允许用户开发自定义的USB HID设备。

2. HID类设备数据传输特性

(1)交换的数据存储在被称为报告(report)的数据结构内。

(2)每一笔事务都能携带小量或者中量的数据:

低速设备:每一笔事务最大是8字节

全速设备:每一笔事务最大是64字节

高速设备:每一笔事务最大是1024字节

(3)设备可以在未预定的时间里【取决于用户的随机行为】传数据给主机(因此主机要定时轮询,以获得最新的数据)。

3. 按照传输速度对USB设备进行分类

(1)三种传输速度

低速设备:Low Speed Device    (LS)

全速设备:Full Speed Device    (FS)

高速设备:Hight Speed Deveice  (HS)

(2)USB类(USB Class)和USB速度(USB Speed)的关系:

USB Class是属于Application的。

USB Speed是属于Physical层的。

(3)市面上的HID产品中,绝大多数都是低速设备。[原因:低速设备成本和技术难度都比较低]

二、USB设备描述符

USB是个通用的总线,硬件端口是统一的,但是USB设备却是多种多样的,那么,USB主机如何区分出所接入的不同的设备的呢?答案:依赖于不同设备各自的描述符

当插入USB设备之后,主机会向从机发送一系列的命令,从机收到命令之后,会返回特定的描述符信息。主机通过解析收到的描述符,来识别到从机设备的相关信息。这个过程,就是设备枚举过程。

USB_HID协议基础_第2张图片

 描述符的作用:给主机传达信息,从而让主机知道设备有什么功能、属于哪种类别、占用多少带宽、使用什么类别的传输方式、数据量的大小等等。主机确认这一系类的信息之后,双方才会开始进行数据通信。

1.USB标准描述符

USB有5种标准设备描述符,分别是:设备描述符、配置描述符、字符串描述符、接口描述符、端点描述符。

另外还有:HID描述符、报告描述符等各个类特有的描述符。

1.1 各个描述符之间的关系

一个设备,只有一个设备描述符;一个设备描述符可以包含多个配置描述符;一个配置描述符可以包含多个接口描述符;一个接口描述符可以包含多个端点描述符。

包含关系如下图:

USB_HID协议基础_第3张图片

 USB描述符之间的关系是一层一层的,所以,在枚举阶段,获取描述符的时候,应该从最顶层开始,顺序:设备描述符---------->配置描述符-------->……

                                                                     英文对照

设备:Device       设备描述符:Device Descriptor 

配置:Configuration  配置描述符:Configuration Descriptor 

字符串:String      字符串描述符:String Descriptor 

接口:Interface     接口描述符:Interface Descriptor 

端点:Endpoint     端点描述符:Endpoint Descriptor 

报告:Report       报告描述符:Report Descriptor

每种描述符都有自己独立的编号(枚举阶段文档里面有详细的说明),使用C宏定义如下:

#define DEVICE_DESCRIPTOR               0x01  //设备描述符  

#define CONFIGURATION_DESCRIPTOR       0x02  //配置描述符  

#define STRING_DESCRIPTOR               0x03  //字符串描述符  

#define INTERFACE_DESCRIPTOR            0x04  //接口描述符   

#define ENDPOINT_DESCRIPTOR            0x05  //端点描述符   

三、HID类设备特有的描述符

一个usb设备可以是单一类类型,也可以由多个类组合或者复合而成。类的定义是在接口描述符中指定的,而不是在设备描述符中设定的。

比如,假定某个设备为HID类设备,那么在定义接口描述符的时候,接口描述符的bInterfaceClass域就要赋值为0x03

协议文档描述如下:

USB_HID协议基础_第4张图片那么,怎么定义一个设备所用的类?一般来说,在设备描述符中,bDeviceClass、bDeviceSubClass、bDeviceProtocol这三个字段都设置为0,然后在接口描述符中,定义具体的类代码、子类代码、协议代码。--------------->单一的类设备定义!如果是组合设备,则设备描述符的这三个字段的值是固定的,必须设置如下:

.bDeviceClass = MISC_CLASS,  //0xEF

.bDeviceSubClass = 0x02,

.bDeviceProtocol = 0x01,

HID类设备相关的描述符,总共有:设备描述符、配置描述符、接口描述符、HID描述符(HID描述符的下级描述符有:报告描述符 和 物理描述符)、端点描述符。

如下图所示:

USB_HID协议基础_第5张图片

1. 设备描述符

略,跟其他设备类一样,通用。

2. 配置描述符

略,跟其他设备类一样,通用。

3. 接口描述符

略,跟其他设备类一样,通用。

4. 端点描述符

注意bInterval这个字段的设定。

5. HID描述符

5.1 HID描述符的结构

偏移量/字节

大小/字节

说明

0

bLength

1

描述符的总长度(取决于下级描述符的个数)

1

bDescriptorType

1

描述符类型(HID描述符 = 0x21)

2

bcdHID

2

HID协议版本

4

bCountyCode

1

国家代码

5

bNumDescriptors

1

下级描述符的数量

6

bDescriptorType

1

下级描述符的类型

7

wDescriptorLenght

2

下级描述符的长度

9

bDescriptorType

1

下级描述符的类型

10

wDescriptorLenght

2

下级描述符的长度

…(可选)

说明:

(1) bLength:大小为1字节,是该描述符的总长度。比如,当只有一个下级描述符的时候,总长度就是:1 + 1 + 2 + 1 + 1 + 1 + 2 = 9 Byte

(2) bDescriptorType是描述符类型编号,HID描述符编号为0x21

(3) bcdHID:2字节,是设备所使用的HID协议的版本号。如果是HID1.1协议,则值为0x0110

(4) bCountyCode:设备所适用的国家。通常使用的键盘是美式键盘,代码为33,即0x21

(5) bNumDescriptors:下级描述符的数量。这个值至少为1,也就是HID类设备至少要有一个报告描述符。下级描述符可以是报告描述符或者物理描述符。

(6) bDescriptorType:下级描述符的类型。报告描述符的编号是0x22,物理描述符编号是0x23

(7) wDescriptorLenght:下级描述符的长度。

5.2 HID描述符定义示例

/* HID Descriptor */

       sizeof(struct usb_hid_descriptor),   /* bLength */

       USB_HID_DESC,                           /* bDescriptorType */

       LOW_BYTE(USB_1_1),                   /* bcdHID */

       HIGH_BYTE(USB_1_1),

       0x00,                              /* bCountryCode */

       0x01,                              /* bNumDescriptors */

       USB_HID_REPORT_DESC,                   /* bDescriptorType */

       LOW_BYTE(sizeof(hid_report_desc)),  /* wDescriptorLength */

       HIGH_BYTE(sizeof(hid_report_desc)),

6. 报告描述符

报告描述符和普通描述符一样,都是通过控制输入端点0来返回,主机下发获取报告描述符指令来获取设备的报告描述符。而且,这个请求是发送到接口的,并不是发送到设备(这是与获取其他标准描述符描述符的区别)。

报告描述符是最复杂的、结构最特异的一种描述符。关于HID用图表,在另一篇文章有介绍。

报告描述符包含多个报告,不同的报告通过报告ID来识别,报告的第一个字节就是报告ID。当报告描述符中没有定义报告ID时,开始就是数据。

6.1 报告描述符的结构

报告描述符没有固定的长度,也没有固定的数据类型。而是由条目(item)来组成,一个条目占据一行。HID协议规定了两种条目:短条目和长条目,常用的是短条目。

短条目的构成:一字节的前缀 + 可选的数据字节。可选的数据字节可以是0、1、2、4字节。实际中所使用的条目,大部分是1字节的可选数据。

一字节前缀的结构(各个bit都有不同的含义):

7 6 5 4                       3 2                        1 0

bTag

bType

bSize

说明:

1)最低两位D1~D0表示后面紧跟着的数据的字节数

0—0字节、1—1字节、2—2字节、4—4字节

2)D3~D2表示该条目的类型

0—主条目(main item)、1—全局条目(global item)、2—局部条目(local item)、3—保留

3)bTag表示该条目的功能(对照HID用图表[下文])。

主条目用来划分数据域,主条目共有5个

Input(输入) 、Output(输出) 、Feature(特性)、Collection(集合)、End Collection(闭集合)

4)全局条目用来选择用途页。常用的全局条目有

Usage Page(用途页)、Logical Mininum(逻辑最小值) 、Logical Maxmum(逻辑最大值) 、Physical Mininum(物理最小值) 、Physical Maxmum(物理最大值)、Report Size(数据域大小)、 Report Count(数据域数量)、Report ID(报告ID)

5)局部条目用来定义控制的属性。局部条目只在局部有效,遇到一个主条目之后,有效范围就被终止。常用的局部条目有

Usage(用途)、Usage Minmum(用途最小值)、Usage Maxmum(用途最大值)

关于键盘的报告描述符、鼠标的报告描述符,可以用官方网站提供的HID描述符工具(HID Descriptor tool)生成(文末有共享链接);还可以使用现成的报告描述符进行修改;HID协议和用途表文档中,也有很多现成的例子。

6.2 示例

键盘的报告描述符(只实现了几个按键)

const uint8 KeyBoardReportDescriptor[63] =

{
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x06,                    // USAGE (Keyboard)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x05, 0x07,                    //  USAGE_PAGE (Keyboard)

//(1)键盘上的Ctrl、Shift、Win、Alt 8个按键[在HID用图表文章中有详细说明]
    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)

//(2)这个字节是全0数据,凑上去的,可以不要
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)

//(3)LED灯用途页,用到了5个bit
    0x95, 0x05,                    //   REPORT_COUNT (5)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x05, 0x08,                    //   USAGE_PAGE (LEDs)
    0x19, 0x01,                    //   USAGE_MINIMUM (Num Lock)
    0x29, 0x05,                    //   USAGE_MAXIMUM (Kana)
    0x91, 0x02,                    //   OUTPUT (Data,Var,Abs)

//(4)和上面的5个bit凑成一字节数据,所以这3个bit全0,没任何意义。
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x75, 0x03,                    //   REPORT_SIZE (3)
    0x91, 0x03,                    //   OUTPUT (Cnst,Var,Abs)

//(5)对应键盘上的所有的键值
    0x95, 0x06,                    //   REPORT_COUNT (6)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0xFF,                    //   LOGICAL_MAXIMUM (255)
    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,                              //  END_COLLECTION
};  

这五部分的解释如下:

(1)这部分实际上为键盘的八个控制键,包括:左/右CTL,在/右ALT,在/右SHIFT,左/右WIN键盘,所以其范围为如下所示(HID_Usage_Tables.pdf从53页开始,展示了所有的keyborad page)   

0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
    0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)

     八个键一个键对应于一个位所以:

    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x08,                    //   REPORT_COUNT (8)

report size单位为bit,report count为8,所以1*8共占用一个字节;

由于按键的值要么为1(按下),要么为0(松开),所以逻辑最大值为1,最小值为0

    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)

由于按键为输入(对主机来说),所以为INPUT,并且为数据(Data),变量(var),绝对值(Abs)

    0x81, 0x02,                    //   INPUT (Data,Var,Abs)

(2)这部分由于键盘数据的八个字节的第二个字节是保留的(第一个字节就是上面所描述的控制键部分),所以

    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)

1bit*8 = 1 byte ,前且为常量

(3)该部分LED输出,即如键盘上的大小灯,数字锁定灯等,只用了五个,所以report count为5.

(4)由于(3)部分只用了5个bits,但发送肯定是一字节一字节地发送,所以要把不用的3个bits也要凑起来,但其又是没有实际意义的,所以定义为常量。

    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x75, 0x03,                    //   REPORT_SIZE (3)
    0x91, 0x03,                    //   OUTPUT (Cnst,Var,Abs)

(5)这部分主要就是六个字节的键盘键值了(一个字节表示一个键),所以一次最多可以发送六个键值,即六个按键按下(当然有没有效,操作系统说了算,一般三个键同时按下系统就报错),所以这一整个报告描述符,包括一组输入的键,一组输出的LED。键一共有八个字节,即一起发送要发八个字节的数据,第1个字节是八个控制键,第2个字节是保留,第三至第八个字节为普通按键键值,没有固定位置,只需要往上填上HID Usage Tables上的键值系统即会确认为该键按下。输出的LED只有一个字节,一个位对应一个LED灯,只使用了五个位。

    0x95, 0x06,                    //   REPORT_COUNT (6)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0xFF,                    //   LOGICAL_MAXIMUM (255)
    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)

report size为8bits,有6个,所以刚好是六个字节。

参考:HID协议文档、HID用途表文档

7.zephyr设备栈中报告描述符的注册和返回

报告描述符的注册和其他描述符的注册流程是一样的,由应用层向下注册,中断向上回调。通过更新数据指针来返回主机所要获取的描述符数据。

7.1 报告描述符的注册示例(可忽略本小节)

示例基于zephyr操作系统(非原生,实际上公司内部的设备栈已经基本上全部重构,唯一相似的就是调用调用框架还保留着)。

报告描述符单独定义在一个文件中,属于应用层。

USB_HID协议基础_第6张图片

 报告描述符的注册是在应用层中完成的,示例如下:

文件:\samples\actions_sdk_demo\usb_audio_dongle\src\usb_handler.c

USB_HID协议基础_第7张图片

 实质上是把定义好的描述符数据赋值给一个全局变量hid_device,返回给主机的数据从全局变量中取,更新指针的指向即可。

跳转到设备类驱动层分析:

USB_HID协议基础_第8张图片

在usb_hid_init()中,注册了整个报告描述符的长度信息:

USB_HID协议基础_第9张图片

7.2 注册回调函数

注册一个HID设备实际上是创建一个HID设备,使用的是基本框架。在应用层/设备类驱动层中定义类请求处理句柄函数、厂商请求处理句柄函数、自定义请求处理句柄函数等,通过调用核心层提供的接口usb_set_config()向设备栈注册中断回调函数,最后调用usb_enable()创建一个类设备。

文件:subsys\usb\class\hid\core.c(设备类驱动层)

USB_HID协议基础_第10张图片

 设备配置结构体如下

USB_HID协议基础_第11张图片

指向报告描述符的指针的更新是在自定义请求处理句柄函数中完成,函数如下: USB_HID协议基础_第12张图片

 所以,接下来就要分析设备类驱动层里面这个回调函数什么时候会被回调。

7.3 中断回调流程

7.3.1 控制器驱动层中断

控制器驱动层:\drivers\usb\device\aotg_udc.c,从控制器中断服务函数usb_aotg_isr_handler()开始分析

建立事务的令牌包和数据包都会引起中断,DATA0数据包是8字节的标准请求(端点0上的数据包中断)。

USB_HID协议基础_第13张图片

8字节标准请求:81 06 00 22   00 00 51 01

结合串口打印的信息:

[general] [WRN] usb_handle_control_transfer: bmRequestType:0x81

[general] [WRN] usb_handle_control_transfer: bRequest:0x06

[general] [WRN] usb_handle_control_transfer: wValue:0x2200

[general] [WRN] usb_handle_control_transfer: wIndex:0x0000

[general] [WRN] usb_handle_control_transfer: wLength:0x0151

标准请求分析如下:

0x81:这是一个标准请求,请求的接收者是接口。

0x06:这是一个获取描述符的标准请求。

0x2200:HID协议定义的报告描述符。

0x0151:主机请求337 Byte的报告描述符数据。

7.3.2 核心层接口被回调

首先,USB控制器产生中断事件,核心层的usb_handle_control_transfer()最先被回调。在处理SETUP令牌包中断时,调用usb_handle_request()。

USB_HID协议基础_第14张图片

在usb_handle_request()中,根据请求的类型(标准请求[type=0]、类请求[type=1]、厂商[type=2]、自定义)再调用核心层的接口。

USB_HID协议基础_第15张图片

这是来自主机的标准请求。而对于来自主机的标准请求、类请求、厂商请求或者是自定义的请求,在配置USB设备的时候,都注册了一系列的处理句柄函数。

USB_HID协议基础_第16张图片

所以,接下来在核心层被调用的是usb_handle_standard_request(),传入的是8字节请求数据和数据指针。

USB_HID协议基础_第17张图片

首先,都会先查询应用层是否注册了自定义请求的处理句柄,如果应用层向设备栈中注册了自定义请求处理句柄函数,则自定义请求处理句柄函数就会被回调。上文7.2小节分析到,在core.c中,HID类设备向设备栈中注册了自定义请求处理句柄函数,所以指针usb_dev.custom_req_handler不为空,于是就进行回调,回传的参数是8字节的数据字以及数据指针。

7.3.3 设备类驱动层接口被回调

hid_custom_handle_req()被回调

USB_HID协议基础_第18张图片

7.4 报告描述符数据指针的更新以及数据的返回

数据指针在设备类驱动层的hid_custom_handle_req()中被更新,指针指向最初注册报告描述符时,保存报告描述符数据的全局变量hid_device。

USB_HID协议基础_第19张图片

 数据指针得到更新之后,回到核心层中调用usb_data_to_host()将报告描述符的数据返回给主机。

USB_HID协议基础_第20张图片  USB_HID协议基础_第21张图片 

7.5 总结

(1)报告描述符是单独分开进行注册的,注册描述符的数据信息和长度信息。

(2)设备类驱动层在函数hid_custom_handle_req()中处理主机获取报告描述符的请求。

(3)对于主机的标准请求,接收者可以是设备、端点、接口。而在设备栈中最先判断的是自定义请求处理句柄函数是否有定义。所以,在设备类驱动层,一般都会定义自定义请求处理句柄函数,直接返回0即可。

(4)可以注册shell子系统,模拟按键。

(5)主机请求HID报告描述符时,请求的数据长度一般都会比实际长度大得多。且报告描述符是通过端点0返回的。

四、HID用途表文档简述—按键的用途ID

1. 用途表文档

HID_Usage_Tables.pdf从53页开始。定义了各个按键的用途ID。

USB_HID协议基础_第22张图片

USB_HID协议基础_第23张图片

USB_HID协议基础_第24张图片

…………………………………………….

内容太多,等实际用上,再去查HID用途表。

2. 解析键盘按键值上报

以小键盘上的数字键为例,Keypad指的是小键盘。

2.1 按键的用途ID(查表可知)

Keypad 1 and End: 小键盘上的数字键1

Keypad 2 and Down: 小键盘上的数字键2

Keypad 3 and PageDown: 小键盘上的数字键3

……

对应的键盘实物图如下:

从用图表中,我们可以查到这小键盘上几个数字按键的用途ID,当按下时,用途ID就会被上报,如下:

第一列:十进制数  第二列:十六进制数

USB_HID协议基础_第25张图片

 需要关注的是,第二列的数值及其对应的按键。

2.2 输入报告的分析

数据从设备返回到主机,对于HID类设备,数据的返回通过报告来实现。端点发送报告数据。

通过分析BusHound上捕获到的报告描述符数据可知:

键盘返回8字节的输入报告。通过报告描述符和HID用途表文档可知:

1)Buf[0]这个字节的位0 D0:左Ctrl键

               位1 D1:左Shift键

               位2 D2:左Alt键

               位3 D3:左win键

               位4 D4:右Ctrl键

               位5 D5:右Shift键

               位6 D6:右Alt键

                位7 D7:右win键

按下,则对应的位为1。

2)Buf[1]:保留,即0000 0000(HEX)=0(DEC)

3)Buf[2]~ Buf[7]是按键值。普通键最多只有5个,因此不会超过6个。对于实际的键盘,如果同时按下几个按键,则后面的六个字节Buf[2]~ Buf[7]都为0xFF。表示按下的键太多,无法正确返回。

2.3 实例1(单个按键按下)

以小键盘上的数字键为例,并且操作是:一次只按一个按键!下表就是理论上,键盘返回的8个字节的数据。

按键

Buf[0]

Buf[1]

Buf[2]

Buf[3]

Buf[4]

Buf[5]

Buf[6]

Buf[7]

1

0x00

0x00

0x59

0x00

0x00

0x00

0x00

0x00

2

0x00

0x00

0x5a

0x00

0x00

0x00

0x00

0x00

3

0x00

0x00

0x5b

0x00

0x00

0x00

0x00

0x00

4

0x00

0x00

0x5c

0x00

0x00

0x00

0x00

0x00

5

0x00

0x00

0x5d

0x00

0x00

0x00

0x00

0x00

6

0x00

0x00

0x5e

0x00

0x00

0x00

0x00

0x00

7

0x00

0x00

0x5f

0x00

0x00

0x00

0x00

0x00

8

0x00

0x00

0x60

0x00

0x00

0x00

0x00

0x00

9

0x00

0x00

0x61

0x00

0x00

0x00

0x00

0x00

注意:Buf[2]的值和用图表上用途ID

3. 对比实验数据

使用USB Hound这个软件来捕获键盘的数据。

3.1 勾选要监听的设备

USB_HID协议基础_第26张图片

3.2 开始捕获

以按下数字键1为例,进行实际捕获到的数据和理论数据的对比。

USB_HID协议基础_第27张图片

USB_HID协议基础_第28张图片

 其他按键可依次类推,进行验证,这个例子是验证单个按键按下的。

3.3 实例2(组合按键按下)

比如:Ctrl+v ,这里的Ctrl指的是左边的Ctrl。

8字节的理论值:

按键

Buf[0]

Buf[1]

Buf[2]

Buf[3]

Buf[4]

Buf[5]

Buf[6]

Buf[7]

Ctrl+v

0x01

0x00

0x19

0x00

0x00

0x00

0x00

0x00

首先,左边的Ctrl按下,那么Buf[0]的第一位就是1,所以字节数据就是0x01;经过查表可知,v键的用途ID为0x19。

USB_HID协议基础_第29张图片

 按下Ctrl不放,再按v键,数据捕获如下:

USB_HID协议基础_第30张图片

可以看见实验值和理论值对应。

在捕获数据进行分析的过程中,还可以发现,按键按下上报一次数据,按键松开上报一次。这是因为开发键盘的人,在程序里,逻辑是这样的:在判断按键事件时,只有当按键情况发生改变的时候(要么按下、要么松开),才需要返回报告,按键一直按住,是不需要返回报告的。

如:按下小键盘数据1键不放:

USB_HID协议基础_第31张图片

 然后松开,发现多出了一次上报的数据:

USB_HID协议基础_第32张图片

因此,按下一个按键再松开,应该是有两次数据的。

弄清按键上报的值(Buf[0]~Buf[7])是至关重要的,接下来就可以实现标准的USB键盘设备(只要能使用USB芯片将这些数据发给主机,即可实现一模一样的键盘)。

五、解析程序代码(可忽略本小节)

USB HID设备是通过报告来传送数据的。报告分为:输入报告 输出报告

输入报告:USB设备发给主机的。比如:USB鼠标将移动和点击等信息返回给电脑。

输出报告:是主机发送数据给USB设备的。比如:数字键盘锁定灯和大写字母锁定灯。即:输入和输出,数据流的方向是相对主机而言的。

报告是一个数据包,数据包里面包含着所要传送的数据(比如:按键值Buf[0]~Buf[7])。

报告的传输方式

1)对于输入报告,则一定是通过中断输入端点输入的。

2)对于输出报告,如果没有中断输出端点时,就是用控制输出端点0输出;如果有中断输出端点,则通过中断输出端点发出数据包

下面的分析基于zephyr操作系统。

1. HID类设备接口描述符的定义

USB_HID协议基础_第33张图片

2. 端点描述符在程序中的定义

USB_HID协议基础_第34张图片

 解析

(1)信息1:在程序中,定义了两个端点描述符。其中,他们的都是:“中断传输类型”的端点。

.bmAttributes = USB_DC_EP_INTERRUPT

在源码中,USB_DC_EP_INTERRUPT的定义处:

USB_HID协议基础_第35张图片

由此可知,USB_DC_EP_INTERRUPT的值为3,也就是0x03。而对于端点描述符的bmAttributes字段,在枚举阶段解析文档中,有讲过其含义。

bmAttributes描述端点的属性

(1)最低两位D1~D0:表示该端点的传输类型。0为控制传输;1为等时传输;2为批量传输;3为中断传输。两个位,也就四种情况:00   01   10   11

(2)如果是非等时传输(控制传输、批量传输、中断传输中的一种),那么D7~D2都为0

(3)如果该端点时等时传输,则D3~D2表示同步的类型:0为无同步、1为异步、2为适配、3为同步。

 D5~D4表示用途:0为数据端点、1为反馈端点、2为暗含反馈的数据端点、3是保留值。

 D7~D6:保留,设为0。

即:目前该USB设备定义的两个端点,使用的数据传输类型为:中断传输类型。

(2)信息2:关于端点地址bEndpointAddress

从枚举阶段,主机请求配置描述符集合的130个字节数据中,提取出来(如果是捕获其他厂商的产品)。

主机请求配置描述符集合数据。

USB_HID协议基础_第36张图片

定义配置描述符集合时,各个描述符的先后顺序如下:

配置描述符----->接口描述符---->HID描述符----->端点描述符---->……

把定义的两个端点描述符数据提取出来,如下:

07 05 81 03 40 00 01

07 05 01 03 40 00 01

D7  D6~D4(保留,都为0)       D3~D1

第一个端点描述符的地址数据为:0x81= 1        000                  0001

第一个端点描述符的地址数据为:0x01= 0        000                  0001

D7:表示数据的传输方向,输入为1,输出为0(输入或者输出相对于主机而言)

D3~D1:表示端点号,两个端点的端点号都为1。

中断输出端点1

中断输入端点1

该USB设备的非0端点就有两个,并且设备的类为HID类。这两项重要的信息,被定义在接口描述符中(根据描述符之间的包含关系,一个接口描述符可以包含多个端点描述符,所以一些信息,得返回它的上一级描述符里面去确认),回顾一下接口描述符的定义:

USB_HID协议基础_第37张图片

那么,对于这个HID设备,输入报告通过中断输入端点发送到主机。而输出报告,由中断输出端点将数据发给设备。

3. HID报告描述符在程序中的定义

USB_HID协议基础_第38张图片

4. Shell命令行模拟键值的上报和数据的捕获

HID类设备模拟键值上报的所有函数,都在这个文件里:

ATS350B\samples\actions_sdk_demo\usb_audio_dongle\src\hid_handler.c

4.1 shell_hid_keyboard()函数

该函数完成了8个字节键值的上报。定义如下:

USB_HID协议基础_第39张图片

4.2  USB Hound捕获数据确认

模拟上报小键盘数据1键,并使用USB Hound捕获

USB_HID协议基础_第40张图片

USB_HID协议基础_第41张图片

下一篇:HID设备实例  USB_HID设备实例_卡卡的博客-CSDN博客

你可能感兴趣的:(设备类驱动层之HID类,mcu,usb)