HID协议详解 - Report Descriptor报告描述符构建与解析

USB相关基础知识简述

报告描述符是HID协议里比较复杂的一部分,在理解报告描述符之前,可以对USB协议数据传输的一些基础知识做一些了解,更方便理解后续内容。

报告是USB协议里数据传输(Data Transfer)的一种,而报告描述符是对这些传输的数据作用途(Usage)上的说明。

USB协议规范1ms产生一个USB帧(Frame),USB设备可以每一帧中交换(Transaction)一次数据[发送/接收],交换通常由数个包(Packet)组成,而一次传输事务则又由一个或几个交换来完成。

传输事务解决了主机、设备之间交互一次数据的问题,但是有些端点是需要进行多次双向传输或者多次单向传输的,同时因为设备的功能不同,所需要的带宽和传输特性也不同,那么就需要一个更上层的机制解决以上问题,四大各有专场的传输应运而生。

控制传输(Control Transfers)

突发性、非周期性、主机软件发起的请求/响应通信,组态、命令和状态的通讯都可以使用控制型传输,常用于消息型数据(message-type data),控制型传输是当需要时才执行传输要求,是最一般的传输方式。

中断传输(Interrupt Transfers)
中断传输是使用中断事务(Interrupt Transactions)来传输数据的,通常用于低频、有限制的延迟通信,使用在有周期流数据上报的场景,即重复数据更新(Recurring data),在每个有限周期内(Bounded period)作至少一次的小量数据发送或接收,所以适用于流数据(stream-type data),注意这里所谓的周期时间只是在端点描述符中的轮询间隔时间。input,output,和Feature。

批量传输(Bulk Transfers)

批量传输是一种可靠的单向传输,传输延迟没有保证,它会尽可能的利用可以利用的带宽完成传输,适合数据量比较大的传输,当总线上只有批量传输,延迟也是能保证的,毕竟没人和它竞争带宽。U盘就是采用批量传输,因为它对时间延迟不是那么严格,只要可靠的完成大量数据的传输即可。

同步传输(Isochronous Transfers)

同步传输属于不可靠传输,其好处是可以保证带宽,没有延迟,因为不可靠传输,所以没有握手包,也不支持 PID 翻转(传输失败重复发送3次),同步传输拥有最高的优先级。

假如总线中有四类数据需要传输,那么主机将在每帧开始发送SOF包,然后优先安排同步传输,之后安排中断传输,其次是控制传输,最后才是批量传输(大容量传输)

四种传输方式,初接触一般只要了解控制型传输和中断型传输即可。

中断型输入管线(interrupt in pipe)仅可以传送input报告;中断型输出管线(interrupt out pipe)仅可以传送output报告;但是控制型管线(control pipe)可以传送input,output和feature报告。端点描述符有声明所使用的端点为何种管线。

最后,数据本身没有任何意义,而协议是赋予它们意义的GOD;

为了尽可能的描述清楚,后文内容比较长,建议反复咀嚼。

报告描述符(Report Descriptor)

报告描述符(Report Descriptor),由设备端(Device)描述给主机端(Host),后续设备端通过中断端点发送的数据的用途(Usage)说明或称描述数据格式及意义。它在连接建立初期就发送给主机端,主机端依据报告描述符去解析来自中断端点的数据。

通俗的讲,报告描述符就是描述连接建立之后设备端发送给主机端的数据,每一字节(bytes)每一位(bits)所代表的意义。

要更详细全面了解报告描述符,可以查阅下述两个官方文档:

《Device Class Definition for human interface device (HID)》

《Universal Serial Bus HID Usage Tables》

一份是HID 协议,另一个是HID报表描述符(USAGE_PAGE & USAGE)的内容定义。

报告描述符分两种:短条目(Short items)、长条目(long items)

报告描述符的组成:

HID协议详解 - Report Descriptor报告描述符构建与解析_第1张图片 官方报告描述符组成结构示意图

 一个报告描述符可以由多个Report组成,当超过1个report时,需要用collection去组合,collection可以嵌套collection,每个report都可以有多个用途(Usage),其它Main item/Report size/Report count/Logical minium等都是用于修饰usage。

案例简析 

下述代码段提供了一个基于官方鼠标案例改的实用示例,对照该示例可以做一个总体的分析。

Report Description通常表现形式是设为一个大数组,数组内容可分两部分组成:条目标签(item tag) + 条目数据(item data)。

其中Tag大小固定,短条目(short item)为1Byte,长条目(long item tag)为3Byte。示例中是一个短条目(short item),其Tag组成又分两个部分:标签(Tag)、数据大小(DataSize),bSize(2bits) + bType(2bits) + bTag(4bits) = 1byte

bSize = 数据大小

bType = 标签(Tag)类型

bTag = 标签(Tag)

上述定义在后文有详细提供,这里只先给个总体的概念。

如上,可以得出一个简单的解析报告描述符的方式,即数组第一个字节肯定为Item,从其中能解析出标签和数据大小,数据大小就是标签后跟着的连续数据。例如item = 0x05,我们能解析出Tag =B(0),Type=B(01),Size=B(01),其中数据长度为1byte,所以后面的0x01是0x05的数据内容。然后就是下一个item,依旧按照前面的分析方式,就能将整个描述符给解析出来。

uint8_t report_desc[] = 
{
    0x05, 0x01, //USAGE_PAGE (Generic Desktop)
    0x09, 0x02, //USAGE (Mouse)
    0xa1, 0x01, //COLLECTION (Application)
    0x85, 0x01, //REPORT_ID (1)
    0x09, 0x01, //USAGE (Pointer)
    0xa1, 0x00, //COLLECTION (Physical)
    0x05, 0x09, //Usage Page (Buttons)
    0x19, 0x01, //Usage Minimum (01)
    0x29, 0x03, //Usage Minimum (03)
    0x15, 0x00, //Logical Minimum (0)
    0x25, 0x01, //Logical Minimum (1)
    0x95, 0x03, //Report Count (3)
    0x75, 0x01, //Report Size (1)
    0x81, 0x02, //Input (Data, Variable, Absolute)
    0x95, 0x01, //Report Count (1)
    0x75, 0x05, //Report Size (5)
    0x81, 0x01, //Input (Constant)
    0x05, 0x01, //USAGE_PAGE (Generic Desktop)
    0x09, 0x30, //USAGE (x)
    0x09, 0x31, //USAGE (y)
    // 0x09, 0x38, //USAGE (wheel)
    0x15, 0x00, //Logical Minimum (0)
    0x26, 0x38, 0x04, //Logical Minimum (1080)
    0x75, 0x10, //Report Size (16)
    0x95, 0x03, //Report Count (2)
    0x81, 0x02, //Input (Data, Variable, Relative)
    0xc0,       //END_COLLECTION
    0xc0       //END_COLLECTION
};

 下文将对报告描述符的类型(bType)和标记(bTag)做一个详细的描述。

短条目(Short items)

短条目格式将条目大小(bSize)、类型(bType)和标记(bTag)打包到第一个字节中。第一个字节后面可以跟着0、1、2或4个可选数据字节(byte),这取决于数据的大小。Host主要依据【bSize + bType + bTag】去了解其后面所跟的数据的意义。

HID协议详解 - Report Descriptor报告描述符构建与解析_第2张图片

 bSize:描述数据内容的大小,占2bit,共4个选项

b00 = 0 bytes
b01 = 1 byte
b10 = 2 bytes
b11 = 4 bytes

bType:描述条目类型(可以理解为命令类型),占2bit,共3个选项以及1个保留项

b00 = Main (主要类型)
b01 = Global (全局类型)
b10 = Local (局部类型)
b11 = Reserved (保留)

 bTag:描述各条目类型下的各细分条目(可以理解为命令),占4bit,最多15个选项,具体后文列表描述。

长条目(long items)

长条目的条目大小(bSize)、类型(bType)和标记(bTag)固定为0xFE(B11111110),表明该条目为长条目类型。

bDataSize:描述长条目数据大小,占1byte,最大255。

bLongItemTag:描述长条目标签,占2byte,但是官方暂时没有对其有做定义(截止hid1_11.pdf),但其中0xF0~0xFF提供给厂商做自定义。

data:长条目具体数据最大255个字节。

长条目较少用到,只在一些专门设备领域有用到,暂时不做了解,后文主讲短条目。

短条目bTag选项 - Main项标签

Main Item Tag表
Main item tag
(bType=0)

Tag Code

(bTag)

Item code

if(bSize=1)

Description
Input
0x8 0x81  Input、Output、Collection项可选值表
Output
0x9 0x91  Input、Output、Collection项可选值表
Feature
0xB 0xB1  Input、Output、Collection项可选值表
Collection
0xA 0xA1

 Collection项可选值表

Collection 和 End Collection。以鼠标而言,在实体上是一个指针(pointer),只是应用为计算机鼠标﹔而这个指针含有三个按键和二个平移轴X 和Y。所以指针的报告是由不同格式的数据所构成,因而需要用到Collection 和End Collection 将几个Input 项目集结成一组,其用途为指针,再用Collection 和End Collection 将指针括起来说明其应用为鼠标。标签End Collection 没有跟随任何数据。标签Collection 会跟随一个字节的数据,例如指针的数据名为Physical,而鼠标的为Application,参考下文collection项可选值表。

End Collection
0xC 0xC0 集合项结束符,End Collection 不跟随任何数据,仅0xC0
Reserved
0xD~0xF \ 保留
bSize = x; itemCode = (tagCode<<4)|x;

Input/Output/Feature具有相通的选项数据定义

input/output都是相对主机端而言的。

Input(e.g. 0x81 , 0x**),输入报告 ,是设备端将设备操作发送到主机,这种报告通常通过中断端点发送到主机,以此保障一个固定周期的输入报告,它也可以控制端点的get report(input)来传输。

Output(e.g. 0x91, 0x**),输出报告,是主机端将软件需求发送给设备,输出报告通常以控制端点的set report(output)来传输,只需要响应软件的实际需求既可,它也可以使用中断端点来传输数据。

Feature(e.g.0xb1, 0x**),特征报告,是主机端发送到设备端的数据组态格式,特征报告只能以get report()/set report()来获取或设置设备的特征值。

Input、Output、Feature项可选值表

Main (后面带的数据格式以及含义)中Input,Output和Feature三个标签,产生报告数据的格式具有共通的数据定义: 

Bit 0

{Data (0) | Constant (1)}

主项目之数据为可变值(设为Data),或为固定不可变值(设为Constant)。Constant 都用于Feature 的报告,或是用于填充位(padding),使报告长度以字节为单位。

Bit 1

{Array (0) | Variable (1)}

主项目之数据的每个字段可以表示几个不同的操作的其中一个被触发(设为Array),或是每个字段仅表示一个操作(设为Variable)。如果是Variable,则Report Count 的数据值等于报告数据的字段数。若是Array,则Report Count 的数据值表示可以同时被触发的最多操作数目。

Bit 2

{Absolute (0) | Relative (1)}

主项目的数据是以相对于固定的基准点方式提供绝对数值(设为Absolute),或是提供相对于前次报告的相对值(设为Relative)。

Bit 3

{No Wrap (0) | Wrap (1)}

主项目的数据值达到极值后会转为极低值,反之亦同,称作卷绕(设为Wrap)。例如一个转钮可以做360°旋转,输出值从0 至10,若设定为Wrap,则值达10 后,在同方向旋转则值变为0,反之若达到0,再转就得到10。

Bit 4

{Linear (0) | Non Linear (1)}

主项目的数据与操作刻度为线性关系(设为Linear),或为非线性(设为Nonlinear)。

Bit 5

{Preferred State (0) | No Preferred (1)}

主项目对应的操作再不被触发时会自动恢复到初始状态(设为Preferred State),或是不会恢复原状(设为No Preferred)。例如键盘的按键和会自动置中(self-centering)的游戏杆,皆为Preferred State。

Bit 6

{No Null position (0) | Nullstate(1)}

主项目对应的操和有一个状态,其不会送出有意义的数据,即数据将不在Logical Minimum 和Logical Maximum 之间,这种操控要标注Null State,否则为No Null Position。例如几个按键,而无键被按下的用途没有声明在Usage 之列,则可以在主项目的数据中设Null State,将无键被按下的状态排除在Logical Minimum 和Logical Maximum区间之外,进一步请参看Universal Serial Bus HID Usage Tables 文件的Appendix A.3 节中范例。

Bit 7

Reserved (0)

主项目Feature 的数据不允许被主机改变(设为Non Volatile),或是允许被主机改变(设为Volatile)。注意主项目Input 和Output,此标注设定无意义,所以bit 7 的代码必须为0。

Bit 8

{Bit Field (0) | Buffered Bytes (1)}

主项目的数据格式要以字节为单位,不足构成字节时自动填充成字节则设Buffered Bytes。

Bit 31-9 Reserved (0)

 

Collection/End Collection组合

项目实际应用中,一个设备通常不止一个功能点,以上述鼠标示例为例,具有3个按钮(左键、右键、中键),X/Y轴方向位移,滚轮等,它们各自具有不同的数据格式,聚集组成鼠标各个功能,此时我们需要Collection/End Collection去集合组织这些数据格式。某些设备还可能具有多组Collection,表明它有多方面的功能集合,例如某些手绘板同时具有触摸板、键鼠功能或某些键盘上有触摸功能。

Collection选项数据定义在下文有表格详细列出说明,size为1个字节。

End Collection通常不跟随数据,0xC0既可,表明Collection作用域的结束。

 Collection项可选值表

Collection 的数据名称很难有一个准则来给定,《Universal Serial Bus HID Usage Tables档中将各种用途的用途种类(usage type)列出,使用者必须依据用途种类来指定Collection 的数据名称,例如鼠标,键盘和游戏杆的用途种类为CA,所以要用Collection (Application),而指针为CP,所以用Collection (Physical)。 

0x00
Physical (group of axes)
0x01 Application (mouse, keyboard)
0x02 Logical (interrelated data)
0x03 Report
0x04 Named Array
0x05 Usage Switch
0x06 Usage Modifier
0x07-0x7F Reserved
0x80-0xFF Vendor-defined

 

短条目bTag选项 -  Global项标签

 Global item tag表及其意义理解描述 

Global Item Tag表
Global item tag
(bType=1)
Tag Code

(bTag)

Item code

if(bSize=1)

Description
Usage Page
0x0 0x05

HID Usage Pages Tables(Usage选项表文档)

Logical Minimum
0x1 0x15 以逻辑单位表示的区段值。这是变量或数组项将报告的最小值。例如鼠标移动XY轴的最小值为-127
Logical Maximum
0x2 0x25 以逻辑单位表示的区段值。这是变量或数组项将报告的最大值。例如鼠标移动XY轴的最大值为127
Physical Minimum
0x3 0x35 变量项的物理范围的最小值。这表示逻辑最小值加上单位

当 Physical  == UNDEFINED;

则 Physical  = Logical;

Physical Minimum和Physical Maximum 一定要同时定义,否则任何一个没定义就会被Logical替代

Physical Maximum
0x4 0x45 变量项的物理范围的最大值。
Unit Exponent
0x5 0x55

以10为底的单位指数的值。

未定义时默认为0

Unit表中列出常用单位,包括长度、质量、时间、温度、电流和光强,大多数复杂单位都可以由这些基本单位推导出来
Unit
0x6 0x65 单位值
Report Size
0x7 0x75 无符号整数,以比特(Bit)为单位指定报表字段的大小。这允许解析器构建供报表处理程序使用的项映射。

Size*Count = UsageDataSize;

注意的是,size的单位是Bit,这里涉及到不满一个字节场景下的常量填充问题,例如,前面鼠标示例中Usage Page(buttons),三个按键只需3bits,此时没有其它合适的有效数据字段时可以选择填充5bits常量:

Report Count (1)
Report Size (5)
Input (Constant)

Report Count
0x9 0x95 无符号整数,指定该项的数据字段数;确定报告中为此特定项包含了多少个字段
(以及因此有多少位被添加到报告中)。
Report ID
0x8 0x85

当一个报告数据被分成数个部分单独上报时,需要为各部分添加一个ID前缀。例如,一个数据部分,其长为3字节,定义Report ID为01,则该设备将生成一个4字节的数据报告,其中第一个字节是01。

设备还可能生成其他报告,每个报告都有唯一的ID,ID值从1起始,不允许应用0作为ID值,0被保留给未赋值ID的报告使用。

Push
0xA 0xA5 当遇到 Push 项目时,项目状态表被复制并放置在堆栈上供以后检索。

Unit (Meter), 
Unit Exponent (-3), 
Push, 
Unit Exponent (0)
pop,
input()

上述在push之后,将毫米(10^-3)压栈,重定义米单位,在后面pop,单位重新恢复成毫米单位,input时单位是毫米

Pop
0xB 0xB5 当找到一个 Pop 项目时,项目状态表被堆栈中的顶部表替换。
Reserved
0xC~0xF \ 保留
bSize = x; itemCode = (tagCode<<4)|(0x01<<2)|x;

Unit

Unit指示实际单位项,理解如下:

Unit的数据取值使用7个Nibbe(半字节4位)来描述所有可能的单位,包括6种基础单位及其可以组合出的复杂单位。

7种国际基础单位:长度(米)、质量(千克)、时间(秒)、电流(安培)、热力学温度(开尔文)、物质的量(摩尔)和发光强度(坎德拉/Candela)

Nibbe:

7

6

5

4

3

2

1

0

Unit:

保留

 光亮

Luminous Intensity

电流
Current

温度
Temperature

时间
Time

质量
Mass

长度
Length

系统
System

 7个Nibbe中最低的4bits用于描述该Unit属于哪个单位系统

Nibble System

None

SI Linear

SI Rotation

English Linear

English Rotation

0

Lowest Nibble(4bits)

0x0

0x1

0x2

0x3

0x4

0x05~0x0E 为保留值,0x0F提供给厂商作为自定义选项

其它的高Nibble描述各物理量单位,

物理量单位表
Nibble

None

SI Linear

SI Rotation

English Linear

English Rotation

1

Length

None

公分(cm)

弧度(Radians)

英寸(Inch)

角度(Degrees)

2

Mass

None

公克(g)

公克

Slug ①

Slug

3

Time

None

秒(s)

4

Temperature

None

开尔文(Kelvin)

开尔文

华氏(Fahrenheit)

华氏

5

Current

None

安培(Ampere)

安培

安培

安培

6

Luminous intensity

None

Candela

Candela

Candela

Candela

 ① Slug(英制质量单位),1Slug=32.174049lbs(磅)=14.593904kg(千克)

report descriptor通过一个指数(Exponent),去将各基础单位组合出复杂单位,7个Nibble中除了最低4bits外,其它6Nibble都将以Exponent次方去相乘(当Exponent小于0时,既是相除),由此可以乘除出大部分单位。后文有一个示例表,可以对照验证理解。

各基础单位Nibble指数(Exponent)-代码,取值为4位补码(补码:首位表示符号),对照参考下表

Codes - Exponent 对照表

-8

-7

-6

-5

-4

-3

-2

-1

0 1 2 3 4

5

6

7

0x08

0x09

0x0A

0x0B

0x0C

0x0D

0x0E 

0x0F 

0x00 0x01 0x02 0x03 0x04

0x05

0x06

0x07

 下面贴一个官方示例常见复杂单位表

HID协议详解 - Report Descriptor报告描述符构建与解析_第3张图片

把上表理解一下,Codes - Exponent 对照表配合物理量单位表,换算成下表

Nibbe:

7

6

5

4

3

2

1

0

Unit:

保留

 光亮

Luminous Intensity

电流
Current

温度
Temperature

时间
Time

质量
Mass

长度
Length

系统
System

距离 = Distance(cm) = Unit(0x0011) = cm^1 = cm 1 1
质量 = Mass(g) = Unit(0x0101) = g^1 = g 0 1 0 1
时间 = Time(s) = Unit(0x1001) = s^1 = s 1 0 0 1

速度 = Velocity(cm/s)

        = Unit(0xF011)

        = F(s^-1)*1(cm^1)

        = cm/s

F 0 1 1

动量 = Momentum(kg·m/s)

        = Unit(0xF111)

        = F(s^-1)*1(g^1)*1(cm^1)

        = g*cm/s

F 1 1 1

加速度 = Acceleration(m/s²)

            = Unit(0xE011)

            = E(s^-2)*1(cm^1)

            = cm/s^2

E 0 1 1

力 = Force(kg·m/s²)

     = Unit(0xE111)

     = E(s^-2)*1(g^1)*1(cm^1)

     = g*cm/s^2

E 1 1 1

能量 = Energy

        = Unit(0xE121)

        = E(s^-2)*1(g^1)*1(cm^2)

        = cm^2*g/s^2

E 1 2 1

角加速度 = Angular Acceleration

               = Unit(0xE012)

               = E(s^-2)*1(r^1)

               = r/s^2

E 0 1 2

电压 = Voltage

        = Unit(x00F0D121)

        = F(a^-1)*D(s^-3)*1(g^1)*2(cm^2)

        = g*cm^2/a*s^3

F 0 D 1 2 1

Unit Exponent 

当Unit Exponent被定义时,表示换算最终单位时还需要乘一个10的指数,其数值为4位补码(补码:首位表示符号),代表10的指数范围从-7~+8,如下:

UnitExponent 00H 01H 02H 03H 04H 05F 06H 07H 08H 09H 0AH 0BH 0CH 0DH 0EH 0FH
数值 0 1 2 3 4 5 6 7 -8 -7 -6 -5 -4 -3 -2 -1

 

 Unit Exponent/ Unit 的关系

Unit Exponent牵扯到两组Tag:逻辑范围(LogicalMaximum /LogicalMinimum),实际范围(PhysicalMaximum/PhysicalMinimum)。

单位分辨率 = (逻辑范围/实际范围)*10^UnitExponent = (LogicalMaximum - LogicalMinimum) /((PhysicalMaximum - PhysicalMinimum) * 10^UnitExponent)

if ((Physical Maximum == UNDEFINED)
|| (Physical Minimum == UNDEFINED)
|| ((Physical Maximum == 0) && (Physical Minimum == 0)))
{
    Physical Maximum = Logical Maximum;
    Physical Minimum = Logical Minimum;
}
If (Unit Exponent == UNDEFINED)
Unit Exponent = 0;
Resolution = (Logical Maximum – Logical Minimum) / ((Physical Maximum – Physical Minimum) * (10^Unit Exponent))

示例理解

 以一个400DPI的鼠标为例,理解一下Unit Exponent/Unit的关系,下表描述了鼠标X/Y轴的移动单位:

参数
Logical Minimum -127
Logical Maximum 127
Physical Minimum -3175
Physical Maximum 3175
Unit Exponent -4
Unit Inches

Resolution = (127-(-127)) / ((3175-(-3175)) * 10^-4) = 400 counts per inch

对上述公式及单位的理解

DPI是dots per inch的缩写,意思是每英寸的像素数,CPI是count per inch的缩写,意思是每英寸的采样率。DPI和CPI都可以用来表示鼠标的分辨率,但是DPI反应的是个静态指标,用在打印机或扫描仪上显得更为合适。由于鼠标移动是个动态的过程,用CPI来表示鼠标的分辨率更为恰当。
上述单位设定,当我们把鼠标向左移动一英寸时,400cpi的鼠标会向计算机发出400次“左移”信号,而如果是800cpi的鼠标就会发送800次。做个假设,我们把鼠标移动1/800英寸,那么800cpi的鼠标会向计算机传送一次移动信号,而400cpi的鼠标却没有反应,我们必须再移动1/800英寸它才会传送移动信号。

 

短条目bTag选项 - Local项标签

Local item tag(bType=2)
Tag Code

(bTag)

Item code

if(bSize=1)

Description
Usage
0x0 0x09

HID Usage Pages Tables(Usage选项表文档)

Usage Minimum
0x1 0x19 定义Usage实例的起始值。
Usage Maximum
0x2 0x29

定义Usage实例的结束值。

起始值 - 结束值定义了一个连续范围,这个范围内都是实例,举例:

Usage Minimum(1),Usage Minimum(3)等效于Usage(1),Usage(2),Usage(3),

这样在定义多个同属性Usage时便利。

Designator Index
0x3 0x39 确定控件使用的主体部分。Index 指向Physical描述符中的一个指示符。
Designator Minimum
0x4 0x49 定义与数组或位图关联的开始指示符的索引
Designator Maximum
0x5 0x59 定义与数组或位图关联的结束指示符的索引。
String Index
0x7 0x79 String描述符的String索引;允许将字符串与特定项或控件关联。
String Minimum
0x8 0x89 指定将一组连续字符串分配给数组或位图中的控件时的第一个字符串索引。
String Maximum
0x9 0x99 指定将一组连续字符串分配给数组或位图中的控件时的最后一个字符串索引
Delimiter
0xA 0xA9 定义一组局部项的开始或结束(1 = open set, 0 = close set).
Reserved
0xB~0xF \ 保留
bSize = x; itemCode = (tagCode<<4)|(0x10<<2)|x;

 

参考链接

 1、​​​​​​HID 官方手册

 2、USB HID报告描述符教程 - 知乎

3、USB 协议分析之 HID 设备

4、HID Usage Pages Tables(Usage选项表文档)

5、HID官方描述符工具

6、USB中文网-HID介绍

你可能感兴趣的:(USB/HID,HID,USB,Report,报告描述符)