HID
HID
adalucky简介
本篇文章是关于 HID 相关协议的介绍,人机交互 Human Interface Device) 其核心优势在于免驱兼容:操作系统(Windows、Linux、macOS、Android等)内置了标准 HID 驱动,无需安装额外软件即可即插即用
USB HID
注:- 下面关于 USB HID 进行的相关记录学习都是基于 USB2.0 (部分可能会拿 USB3.0 进行对比)
- 默认没有提到的都是基于全速模式 也就是理论带宽=12Mbps
| 接口版本 | 工作模式 | 理论带宽 |
|---|---|---|
| USB2.0 | 低速模式 (Low-Speed) | 1.5Mbps |
| 全速模式 (Full-Speed) | 12Mbps | |
| 高速模式 (High-Speed) | 480Mbps | |
| USB3.0 (USB3.1 Gen1/USB3.2 Gen1) | 超高速模式 (SuperSpeed) | 5000Mbps |
| USB3.1 Gen2 (USB3.2 Gen2) | 超高速增强模式 (SuperSpeed+) | 10000Mbps |
| USB3.2 Gen2x2 | 双通道超高速模式 | 20000Mbps |
| 接口版本 | 功能分类 | 线缆颜色 | 引脚 | 作用 |
|---|---|---|---|---|
| USB2.0 | VBUS(电源+) | 红 | 1 | 5V供电 |
| D-(数据-) | 白 | 2 | 差分数据 | |
| D+(数据+) | 绿 | 3 | 差分数据 | |
| GND(地线) | 黑 | 4 | 接地 | |
| USB3.0 | VBUS(电源+) | 红 | 1 | 5V供电 |
| D-(数据-) | 白 | 2 | 差分数据 | |
| D+(数据+) | 绿 | 3 | 差分数据 | |
| GND(地线) | 黑 | 4 | 接地 | |
| SSTX-(发送-) | 蓝 | 5 | 发送差分对 | |
| SSTX+(发送+) | 黄 | 6 | 发送差分对 | |
| GND(信号地) | 屏蔽层 | 7 | 屏蔽接地 | |
| SSRX-(接收-) | 紫 | 8 | 接收差分对 | |
| SSRX+(接收+) | 橙 | 9 | 接收差分对 |
注:
- USB3.0 有 9 个引脚(USB2.0 有 4 个引脚),并且前面 4 个和 USB2.0 完成想通,所以 USB3.0 是完全兼容 USB2.0 的
- 我们主机就是通过 D- D+ 的差分信号来区分 USB2.0 3.0 的
- D- D+ 是不可以接反的,正常情况下虽然不会烧掉,但是无法工作(D+ D-默认都是低电平)
- OTG中会多一根身份识别(ID)线,也就是会多一个引脚,ID引脚,专门用来实现 OTG 双向角色切换,让手机|电脑|平板 / 开发板既能当主机、也能当从机。
flowchart TD
A[主机检测到设备插入] --> B{检测 D+/D- 电压}
B -->|D+ > 2.8V| C[全速设备 12Mbps]
B -->|D- > 2.8V| D[低速设备 1.5Mbps]
C & D --> E[发送高速检测信号]
E -->|设备响应“Chirp K”| F[切换至高速模式 480Mbps]
E -->|无响应| G[保持全速/低速]
注:
- 这里以 2.0 为例,当设备插入到主设备后,D+ D- 原本默认都是低电平,然后至少有一个引脚电平被拉高,主机就可以知道我们的设备到底支持哪些工作模式了
- 电脑|手机|平板 是如何知道我们的 USB 设备是什么时候连接上的或者什么时候拔掉的?
- 在我们插上后会拉高 D+ | D- 的电平(默认是低电平 0),当被拉高后保持一段时间以后就被认定为是连接了,拔掉了就是没有这个 D+ | D- 的高电平了,也就被认定是断开了
- USB 分为主从设备,必须由主机发起询问,从机才能回应(一般的设计是把我们的电脑|手机|平板这些作为主机,自制的 USB HID 设备作为从机,由电脑|手机|平板 作为主机发起通讯)
sequenceDiagram
participant Host
participant Device
note right of Device: 设备收到 IN Token 后
Host->>Device: 发送 IN Token(请求数据)
Device-->>Host: 返回 DATA Packet(数据包)
Host->>Device: 回复 ACK Token(确认接收)
- 枚举就是一个初始化的行为,是指将一个 USB 设备插入到主机(电脑|手机|平板)后,主机读取 USB 配置信息,并正常使用该 USB 设备的过程
- 具体行为如下:
- 电脑|手机|平板的 USB 接口 D- D+ 默认为低电平,我们插入 USB 设备后,致使 D- 或 D+引脚的电平被拉高,这样电脑就会得到我们插入的 USB 设备支持的工作模式(速率支持多少)
- 电脑|手机|平板 USB 发出 RESET 信号,USB 设备收到后准备开始初始化
- 电脑|手机|平板 USB 发出获取信息信号,USB 设备回应信息(重点学习 )
- 完成枚举过程,正常通讯。
flowchart TD
A[USB设备插入主机] --> B[硬件检测D+/D-电平,协商USB传输速率]
B --> C[主机分配唯一USB设备地址]
C --> D[主机读取**设备描述符**<br>厂商ID/设备ID/设备类型]
D --> E[主机读取**配置描述符**<br>接口数量、端点信息、功耗]
E --> F{是否为HID类设备?}
F -- 否 --> G[执行对应设备类枚举:存储/串口等]
F -- 是 --> H[读取HID**接口描述符**]
H --> I[读取核心:HID**报告描述符**<br>约定按键/位移等数据格式]
I --> J[主机选中配置,激活设备端点]
J --> K[枚举完成,进入中断IN传输<br>设备定时上报HID数据]
sequenceDiagram
participant Host as 主机
participant Device as HID设备(键盘/鼠标/外设)
Note over Host,Device: HID枚举:设备入住登记全过程
Host->>Device: 检测到设备接入,分配唯一设备地址
Host->>Device: 请求读取【设备描述符】
Device-->>Host: 返回厂商、型号、USB版本等基础身份信息
Host->>Device: 请求读取【配置描述符】
Device-->>Host: 返回接口、端点、最大功耗
Host->>Device: 请求读取【HID接口描述符】
Device-->>Host: 声明自身属于HID人机设备
Host->>Device: 请求读取【HID报告描述符】
Device-->>Host: 返回数据格式说明书(上报规则、字节定义)
Host->>Device: 设置设备配置,激活通信端点
Note over Host,Device: 枚举完成,正式开始数据交互
loop 周期性中断IN查询
Host->>Device: 定时发送IN令牌请求数据
Device-->>Host: 上报按键/鼠标位移数据包
end
数据包
- 在 USB 中 数据是以数据包(packets)的形式传输,数据包的大小完全取决于端点的配置
- 数据包在发送的时候是从最低位到最高位,比如一个 16 进制的 0x4F=01001111 在发送数据的时候是从右往左开始的 ->1->1->1->1->0->0->1->0
PID
- 注 (只与高速设备有关) 这个描述是指的 USB2.0 高速模式 (High-Speed) 480Mbps
| PID大类 | PID名称 | PID二进制值 | 含义解释 |
|---|---|---|---|
| Token(令牌包) | IN | 0b1001 | 主机向设备发起读取数据请求,设备上传数据给主机 |
| OUT | 0b0001 | 主机向设备发起下发数据请求,主机发送数据给设备 | |
| SETUP | 0b1101 | (设置事务的开始) 控制传输专用令牌,用于USB设备初始化、配置、标准请求交互 | |
| SOF | 0b0101 | 帧起始包,USB每1ms下发一次,同步全局时序、管理等时传输 | |
| Data(数据包) | DATA0 | 0b0011 | 数据序列0,交替数据包序号,用于数据重传校验、同步 |
| DATA1 | 0b1011 | 数据序列1,和DATA0轮流切换,防止重复接收同一帧数据 | |
| DATA2 | 0b0111 | (只与高速设备有关)高速同步传输专用,多包连续传输时切换序列号 | |
| MDATA | 0b1111 | (只与高速设备有关)碎片数据包,同步传输里分段传输数据片段使用 | |
| Handshake(握手应答包) | ACK | 0b0010 | 接收方正确收到数据,回复确认应答 |
| NAK | 0b1010 | 暂时无法接收/暂无数据可发送,请求主机稍后重试 | |
| STALL | 0b1110 | 端点异常、请求不支持,传输出错,需主机重置端点 | |
| NYET | 0b0110 | (只与高速设备有关)高速批量传输专用,设备已收完当前数据包,还可继续接收后续数据 | |
| Special(特殊功能包) | PRE | 0b1100 | 高速设备发送低速/全速数据包前的前置引导包,切换传输速率 |
| ERR | 0b1100 | (只与高速设备有关)Split事务出错标记,高速集线器传输异常报错标识 | |
| SPLIT | 0b1000 | (只与高速设备有关)高速拆分事务包,高速主机通过集线器访问低速/全速设备所用 | |
| PING | 0b0100 | (只与高速设备有关)高速批量IN传输探测包,查询设备是否有数据准备好可上传 |
- 每次传输数据前最先发送的就是 Token 数据包
- Token(令牌包)中的 IN OUT 都是针对主机而言,IN(设备–>主机) OUT(主机–>设备 )
- Data(数据包)中有 DATA0、DATA1,他们是交替使用的(**由硬件自动完成 **),仅仅是作为一种错误校验的方法,它们唯一的任务就是传输数据
- 数据包中的有效数据是可选的,也就是说其实是可以发送一个空的数据包(实际应用中的具体作用就是TODO)
- 握手包会告诉我们上一个数据包的状态,即它是否成功接收,因此我们有三种状态如下:
- ACK:已确认(接受方告知数据已成功接收)
- NAK:未确认(接受方目前无法接收数据,比如缓冲区已满、或者是发送发目前无法发送数据导致的)
- STALL:异常(由设备方发出,当前端点异常、请求不支持,传输出错,需主机重置或清除该端点的暂停状态 )
- 注: 有些端点不需要握手,所以对于这类端点不需要发送或者等待接收任何握手包
传输机制和主机驱动机制
-
USB 中没有中断机制,都是总线轮询,因此在任何信号或数据到达物理层时,它只会存储在缓冲区中,它不会中断物理层正在进行的工作,换而言之 USB 控制器,只会在它去检查缓冲区中的更新时才会知道这个新数据,它不会被总线上到来的任何信号中断,因为 USB 里面没有中断机制,它只有在采集样本时才知道更新
-
USB 的官方设计是认为这样足够简单,且性能是足够的
-
在 USB 中有种叫做中断传输或者叫做中断端点,但这本质上是通过轮询来实现的
-
USB 是由主机驱动的,而且仅仅由主机驱动,说人话USB 的传输操作都只能由主机发起
-
主机想要给设备发数据: 发一个 OUT 的令牌,然后就可以开始给设备发数据了
-
设备想给主机发数据: 设备无法直接发,只能把数据放在一个端点上待命,当主机需要这些数据时,主机就会发一个 IN 的令牌,当设备收到 IN 令牌后就会开始向主机发送数据
-
设备会把数据准备好放在一个端点里,当主机需要数据或者轮询的时候会会发一个Token IN 的令牌,然后从总线上去取? 如果设备没有当前主机需要的数据就会发个 NAK 的令牌回复 不确认
端点/传输模式
- 好像一个 USB 设备有 16 个输入端点,还有 16 个输出端点,其中 0 号端点是特殊端点,是作为控制端点
- 端点类型有时也会被叫做传输类型,就是指的某个端点是哪种类型的,这个类型就是用来调节控制传输的类型
- USB 中有 4 中端点类型或者说传输类型:中断端点(中断传输)、批量端点(批量传输)、同步端点(同步传输)、 控制端点(控制传输),这些端点的类型是在设备枚举阶段通过设备描述符进行配置的然后主机接受到设备的描述符以后,那么主机总是知道设备的端点类型
- 除控制传输外,另外三种每次都只包含一个事务(事务,一个包完全发完,因为可能包比较大要发两次三次?),控制传输包含多个事务,具体看[tab 控制端点(控制传输)]
- 中断端点的作用是进行中断传输,中断传输:意味该端点的数据会定期+频繁的进行收发
- 常用于定期从 USB 设备获取更新,例如: 鼠标,键盘,操纵杆
- 中断传输的周期性不仅能保证按时发生,而且有指定的最大延迟,换句话说,周期性传输始终能保证总线带宽,且最大延迟始终为 0 (假设我们想通过中断端点从设备向主机发送一些数据,我们想每 3ms 发送一次数据,中断传输保证这些数据最多每 3ms就能从设备发送到主机,任何两次中断传输之间的时间最多不超过 3ms,当然这里的 3ms 只是举例,你可以改为更高的要求)
- 每次 USB 中断传输的最大负载是:全速模式设备[(Full-Speed) |12Mbps|] 64 bytes,高速模式设备[(High-Speed) |480Mbps|]最高可达 4024 字节
- 批量端点的作用是进行批量传输,它是非周期性的(也就是说它不会规律且频繁的进行,它只在有需要时才发送)
- 常用于 USB 闪存盘、USB 硬盘、大量数据传输
- 每次 USB 批量传输的最大负载是:全速模式设备[(Full-Speed) |12Mbps|] 8、16、32、64 bytes,高速模式设备[(High-Speed) |480Mbps|] 512 bytes
- 注: 低速设备低速模式 [(Low-Speed) |1.5Mbps|] 不支持批量传输
- 同步端点(同步传输),也叫等时传输,和中断传输一样,也是周期性的数据传输方式(这就意味着会定期且频繁的发送,且同中断传输一样,要保证总线带宽,这样也就才有对应的最大延迟比如中断传输中举例的 3ms 示例)
- 常用于音频、视频、麦克风、摄像头流媒体等
- 同步/等时传输事务取消了握手,也就是说没有所谓的 ACK 这些,有丢包的风险,这么做的目的是为了降低延迟
- 特点:严格按照固定时间间隔收发数据,允许少量丢包,优先保障流畅度而非 100% 数据无误。
- 之前提到的 0 号端点是个特殊的端点,就是用于控制传输的,当设备和主机进行连接时就会进行枚举
- 0 号端点是 USB 设备的默认控制端点,可以用这个端点传输配置相关内容,因此 USB 设备枚举总是发送在控制端点(因为默认的是 0 号端点,所以也可以说USB 枚举默认发生在 0 号端点)
- 每次 USB 控制传输的最大负载是:全速模式设备[(Full-Speed) |12Mbps|] 8、16、32、64 bytes,高速模式设备[(High-Speed) |480Mbps|] 64 bytes
- 控制传输包含多个事务(另外三种都是只包含单个事务),按照阶段可分为:设置阶段(有 1 个事务)、数据阶段(可选,可以 0 个或多个)、状态阶段(有 1 个事务),每个阶段都是由事务组成,所以每个阶段的事务都会有 [令牌、数据、握手] 这样的包组成
- **设置阶段:**它是所有控制传输的起始阶段, 它只包含一个单一的 USB 事务,被包含的这个事务是一个常规事务,由三个数据包构成[令牌、数据、握手]
- 这里的令牌我们称为设置令牌(setup),之前提到的默认都是输出令牌
- 设置令牌:由主机发出,告诉设备这是一个特殊的令牌
- 输出令牌:由主机发出,告诉设备这是一个 IN/OUT 的方向
- 设置阶段的握手包是不允许返回除ACK(确认)之外的任何状态,可能是非常重要且必须被确认成功,所以在设计时设备必须尽力确保始终能够接收设置令牌,如果出现问题设备只能忽略其收到的内容,不回复任何握手数据包(获取这样会触发主机的重发机制或者去重置该端点确保下次能正常?)
- **数据阶段:**它是一个可选阶段,意味着它可能存在于部分控制传输中,也可能没有,并非每一个控制传输都必须包含它
- 如果控制传输中存在数据阶段,它会是第二阶段
- 如果控制传输中存在数据阶段,那么数据阶段可能由一个或者多个 USB 事务组成,具体多少个是设置阶段中的设置来决定的,比如设置阶段要求主机给设备发送一个数据,并且要求了主机从设备上获取一个数据,那么就至少有 2 个事务了 一个数据为IN的数据输入事务,一个为OUT的数据输出事务,另外可能传输的数据比较大,就有可能被拆分成多个事务来传输
- **状态阶段:**这个只有一个事务,就是反馈上一步的传输的状态
- 值得注意的是,如果有数据阶段,那么状态阶段和数据阶段的方向是相反的,如果数据阶段的是 IN(数据输入事务) 那么状态阶段就会是一个 OUT(状态输出事务),IN表示主机在数据阶段要收设备的数据,状态阶段那么主机就会给出 OUT 传给设备一个状态反馈,反之同理
总线带宽分配
-
当 USB 枚举设备时,主机负责对设备的每个端点分配总线带宽,确保所有在用端点发送或接收数据必须尽可能准时,当然对于中断和等时传输(也就是周期性传输),"尽可能准时"是不可接受的,周期性传输的带宽必须得到保证
-
优先级排序:周期性传输(中断和等时传输) > 控制传输 > 批量传输
-
在全速模式设备[(Full-Speed) |12Mbps|] 下有 90% 的总线带宽预留给了周期性传输,10% 预留给了控制传输,剩余总线空闲状态下的会被分配给批量传输
-
在高速模式设备[(High-Speed) |480Mbps|] 下有 80% 的总线带宽预留给了周期性传输,20% 预留给了控制传输,剩余总线空闲状态下的会被分配给批量传输
-
批量传输的分配应该是在其它传输类型空闲状态下,没有被使用时才会给到它,因为其它的传输是不会永远一直被占用(如果一致占用就说明总线过于繁忙),因为批量传输的设计初衷并非周期性且不保证延迟
流程
当你把 USB 连接上主机以后,主机会主动发出一个"获取设备描述符"的请求,这个时候设备必须向主机发送"设备描述符"去响应这个请求,这是一个向主机提供基本必要信息的一个流程,因为只有提前沟通确认好,才能保证后续的正确通讯交流方式










