目录
[一. 传输轮询配置](#一. 传输轮询配置)
[二. Logic 2 抓包示例](#二. Logic 2 抓包示例)
[2.1 抓包工具型号](#2.1 抓包工具型号)
[2.2 USB默认空闲传输](#2.2 USB默认空闲传输)
[2.2.1. 先拆解看到的时序(SOF 间隔 1ms)](#2.2.1. 先拆解看到的时序(SOF 间隔 1ms))
[2.2.2. 为什么不是严格 5ms?核心原因有 3 个](#2.2.2. 为什么不是严格 5ms?核心原因有 3 个)
(1)bInterval=5的本质:「最小轮询间隔」而非「精准周期」
[(2)IN 包必须跟随 SOF 帧,只能是「整数 ms」间隔](#(2)IN 包必须跟随 SOF 帧,只能是「整数 ms」间隔)
[(3)设备回复 NAK 会影响主机调度](#(3)设备回复 NAK 会影响主机调度)
[3. 补充:bInterval=5 的 "正确预期"](#3. 补充:bInterval=5 的 “正确预期”)
[2.3 全速USB周期性SOF(上图SOF包 解释)](#2.3 全速USB周期性SOF(上图SOF包 解释))
[2.4 USB 全速模式下「主机轮询设备 IN 中断端点」的流程(上图SOF+IN+NAK包 解释)](#2.4 USB 全速模式下「主机轮询设备 IN 中断端点」的流程(上图SOF+IN+NAK包 解释))
[1. 第 1 包:SOF 帧同步包](#1. 第 1 包:SOF 帧同步包)
[2. 第 2 包:IN 令牌包](#2. 第 2 包:IN 令牌包)
[3. 第 3 包:NAK 响应包](#3. 第 3 包:NAK 响应包)
[整体流程 + 与bInterval=5的关系](#整体流程 + 与bInterval=5的关系)
[3.1 设备描述符](#3.1 设备描述符)
[宏 1: USB_MAX_EP0_SIZE = 64U](#宏 1: USB_MAX_EP0_SIZE = 64U)
[宏 2: USBD_VID = 1151](#宏 2: USBD_VID = 1151)
[宏 3:USBD_PID_FS = 22351](#宏 3:USBD_PID_FS = 22351)
[宏 4/5/6:MFC_STR/PRODUCT_STR/SERIAL_STR](#宏 4/5/6:MFC_STR/PRODUCT_STR/SERIAL_STR)
[宏 7:USBD_MAX_NUM_CONFIGURATION = 1](#宏 7:USBD_MAX_NUM_CONFIGURATION = 1)
[3.2 配置描述符](#3.2 配置描述符)
[3.2.1 配置描述符数组参考](#3.2.1 配置描述符数组参考)
[3.2.2 关键缩写解释](#3.2.2 关键缩写解释)
[3.2.3 USB 描述符的通用规则](#3.2.3 USB 描述符的通用规则)
[第一部分:USB 标准配置描述符 (9 字节,数组第 0~8 位)](#第一部分:USB 标准配置描述符 (9 字节,数组第 0~8 位))
[第二部分:USB 标准接口描述符 (9 字节,数组第 9~17 位)](#第二部分:USB 标准接口描述符 (9 字节,数组第 9~17 位))
[第三部分:USB 自定义 HID 类专用描述符 (9 字节,数组第 18~26 位)](#第三部分:USB 自定义 HID 类专用描述符 (9 字节,数组第 18~26 位))
[第四部分:USB 标准端点描述符 (IN 端点,7 字节,数组第 27~33 位)](#第四部分:USB 标准端点描述符 (IN 端点,7 字节,数组第 27~33 位))
[第五部分:USB 标准端点描述符 (OUT 端点,7 字节,数组第 34~40 位)](#第五部分:USB 标准端点描述符 (OUT 端点,7 字节,数组第 34~40 位))
[1. IN/OUT 端点的方向定义(USB 协议的要求)](#1. IN/OUT 端点的方向定义(USB 协议的要求))
[2. 端点传输类型 bmAttributes = 0x03(HID 设备的强制要求)](#2. 端点传输类型 bmAttributes = 0x03(HID 设备的强制要求))
[3. 其他端点参数解释](#3. 其他端点参数解释)
[对比2:接口描述符的 3 个关键字节全部修改(本质变化,决定设备类型)](#对比2:接口描述符的 3 个关键字节全部修改(本质变化,决定设备类型))
[3.3 自定义HID描述符和鼠标HID描述符对比](#3.3 自定义HID描述符和鼠标HID描述符对比)
[3.3.1 鼠标和自定义HID描述符对比](#3.3.1 鼠标和自定义HID描述符对比)
[STM32 官方库的「自定义 HID 标准模板」;](#STM32 官方库的「自定义 HID 标准模板」;)
[STM32 官方库的「HID鼠标模板」](#STM32 官方库的「HID鼠标模板」)
[3.3.2 HID自定义和HID鼠标对比](#3.3.2 HID自定义和HID鼠标对比)
[差异 1:USAGE_PAGE(用途页)](#差异 1:USAGE_PAGE(用途页))
[差异 2:【设备功能】功能是否被协议锁死](#差异 2:【设备功能】功能是否被协议锁死)
[差异 3:【通信能力】数据传输方向和通道](#差异 3:【通信能力】数据传输方向和通道)
[差异 4:【数据格式】数据包的长度和含义](#差异 4:【数据格式】数据包的长度和含义)
[差异 5:电脑识别和驱动](#差异 5:电脑识别和驱动)
前言
详解 :设备描述符,配置描述符,接口描述符,端点描述符,hid描述符
HID中断端点(接收/发送) 最大数据包:设置为64字节(最大)和2字节对比
全速模式下中断端点轮询间隔:5ms和1ms对比
USB协议中的:自定义HID和HID鼠标进行对比
USB全速中HID传输流程,对比设置的端点大小与轮询间隔对整个通信周期的影响;
一. 传输轮询配置
如下图,我仍然拿上篇文章:stm32 HAL配置usb全速 自定义HID类详解 进行对比
定义自定义 HID 设备接收「主机下发数据」的缓冲区长度为 64 字节。
#define USBD_CUSTOMHID_OUTREPORT_BUF_SIZE 64
指定自定义 HID 设备「报告描述符」的字节长度为 34 字节。
#define USBD_CUSTOM_HID_REPORT_DESC_SIZE 34
定义自定义 HID 设备「全速模式下中断端点」的轮询间隔为 5ms。
#define CUSTOM_HID_FS_BINTERVAL 5
CUSTOM_HID_EPIN_SIZE 和
CUSTOM_HID_EPOUT_SIZE 定义了这些端点可以处理的最大数据包大小(这里为2字节)
对比
usb全速设置:轮询间隔改为 1ms(HID类最快)
HID类设备的中断端点:改为64;目前为中断端点 发送/接收 处理的最大数据包
二. Logic 2 抓包示例
2.1 抓包工具型号
我这里使用Logic 8 逻辑分析仪抓的数据;(可以同时抓数字量和模拟量)
数字量最高 100MS/S
模拟量最高抓 10MS/S,这里不需要
数字量和模拟量可以同时抓,但我抓全速USB数字量设置为100MS时,模拟量最多设置2.5M,所以没配置了,直接看数字量的USB数据解析即可,如下文
逻辑分析仪连接:
GND ------ 逻辑分析仪GND
PA12(USB D+) --------逻辑分析仪通道0
PA11(USB D-) ---------逻辑分析仪通道1
Logic软件 选择全速连接
**当全速设备连接时:**D+为高电平,D-为低电平,主机通过检测这种状态来识别设备为全速设备。
对于设备断开的情况:USB 2.0协议规定,当主机端D+或D-的电压小于0.8V,并持续Tpps(最小值为2us)时间长度,主机就认为设备已经断开;USB全速模式下D+为高、D-为低的配置用于设备识别,而这种状态的持续时间主要取决于设备插入和断开的具体过程。
2.2 USB默认空闲传输
我前面已经配置中断间隔5ms(不影响包长) #define CUSTOM_HID_FS_BINTERVAL 5
为什么是(SOF + IN + NAK) (SOF) (SOF) (SOF) (SOF + IN + NAK);这里每4ms就一包IN;
不应该是(SOF + IN + NAK) (SOF) (SOF) (SOF) (SOF) (SOF + IN + NAK):每1ms 一包SOF包,每5ms一包IN;
先明确核心结论:
bInterval=5是「最小轮询间隔」,而非「绝对精准的轮询周期」,USB 主机的调度规则 + SOF 帧的特性,导致实际轮询间隔出现 1ms 偏移(4ms)是完全正常的。2.2.1. 先拆解看到的时序(SOF 间隔 1ms)
假设帧编号为连续的数字,你看到的抓包时序如下:
时间(ms) 帧编号 抓包内容 说明 0 n SOF + IN + NAK 第 1 次轮询 IN 端点 1 n+1 仅 SOF 无 IN 包(未到轮询窗口) 2 n+2 仅 SOF 无 IN 包 3 n+3 仅 SOF 无 IN 包 4 n+4 SOF + IN + NAK 第 2 次轮询 IN 端点(间隔 4ms) 2.2.2. 为什么不是严格 5ms?核心原因有 3 个
(1)
bInterval=5的本质:「最小轮询间隔」而非「精准周期」USB 规范对全速中断端点的
bInterval定义是:主机「至少」每bInterval个帧(5 帧 = 5ms)轮询一次该端点,而非 "必须精准每 5ms 轮询一次"。
- 主机的 USB 调度器需要兼顾所有连接的 USB 设备(比如鼠标、键盘、你的 HID 设备),会在 "5ms 窗口内" 找空闲时机发起轮询;
- 若调度器在第 4ms(4 帧)时有空余带宽,就会提前发起 IN 轮询,表现为两次 IN 包间隔 4ms,这完全符合 USB 规范(只要不超过 5ms 即可)。
(2)IN 包必须跟随 SOF 帧,只能是「整数 ms」间隔
USB 全速的 SOF 是每 1ms 一个,且主机只能在 "SOF 帧开始后" 发起 IN/OUT 令牌包(不能在两个 SOF 之间发起)。
- 你的轮询间隔只能是 1ms、2ms、3ms... 等整数;
- 即使主机想精准 5ms 轮询,也可能因 SOF 帧的 "边界对齐",实际变成 4ms 或 5ms(±1ms 偏移)。
(3)设备回复 NAK 会影响主机调度
你看到的设备回复是
NAK(表示 "当前无数据可上传"),主机收到 NAK 后,可能会微调下一次轮询的时机:
- 若设备多次回复 NAK,主机可能 "提前一点" 轮询(比如从 5ms 缩短到 4ms),减少等待;
- 若设备后续回复 DATA 包,主机又会回归到接近 5ms 的轮询周期。
3. 补充:bInterval=5 的 "正确预期"
你配置
bInterval=5,应该预期的是:
- 两次 IN 轮询的间隔 **≥5ms**(或接近 5ms,±1ms);
- 主机不会超过 5ms 才轮询(否则违反规范),但提前 1ms(4ms)是允许的;
- 若你让设备持续上传数据(回复 DATA 而非 NAK),轮询间隔会更接近 5ms(主机调度会更规律)。
总结
bInterval=5是「最小轮询间隔(5 帧 / 5ms)」,主机调度允许 ±1ms 偏移,出现 4ms 间隔是正常的;- IN 令牌包只能跟随 SOF 帧发起,因此间隔必为整数 ms;
- 设备回复 NAK 时,主机会微调轮询时机,进一步导致间隔偏离 5ms。
如果想让轮询间隔更接近 5ms,可以让设备持续向主机发送数据(回复 DATA 包而非 NAK),主机的调度会更规律。
我现在配置中断间隔1ms(不影响发送64字节的包长)
#define CUSTOM_HID_FS_BINTERVAL 1
每间隔1ms:空闲状态为 SOF + IN + NAK
每1ms间隔为:(SOF + IN + NAK )(SOF + IN + NAK )(SOF + IN + NAK )(SOF + IN + NAK)
我直接把轮询间隔设置为1:可以看到SOF包合PIDIN包已经合在一起了;
2.3 全速USB周期性SOF (上图SOF包 解释)
全速/低速:1 ms 一个 Frame 主机每 1 ms 打一次 SOF 包(PID=SOF):
里面带 11-bit 帧号。 总线在这 1 ms 里可以塞多个事务(不同设备/端点)。
SOF (Start-of-Frame)令牌包只在"全速/高速"总线上、由主机 、每 1 ms(全速)或每 125 µs(高速) 、无论总线忙闲 地周期性广播 ------不存在任何条件判断,就像心跳一样永远打下去。低速总线根本没有 SOF。
低速 USB 传输中主机每 1ms 会发送一次 SOF (Start-of-Frame) 包 ,但低速设备不处理 SOF 包,而是通过Keep Alive 信号 (End of Packet) 来维持同步和防止挂起。
下图为:STM32全速USB的SOF包结构,每过1ms传输一次
SOF 包放大如下图
SOF 帧同步包:含义如下
字段 含义 发送方 SYNC USB 包的同步字段(固定 8 位,用于时钟同步) 主机 PID SOF 包类型:帧起始包(Start Of Frame) 主机 Frame #0x4C8 当前帧的编号(USB 全速每 1ms 发 1 个 SOF,用于计时 / 同步) 主机 CRC OK 0x05 包的 CRC 校验正确 - EOP 包结束标志(End Of Packet) 主机
2.4 USB 全速模式下「主机轮询设备 IN 中断端点」的流程(上图SOF+IN+NAK包 解释)
bInterval=5表示主机每 5ms 轮询一次设备的中断端点:前面介绍的#define CUSTOM_HID_FS_BINTERVAL 5
(SOF + IN + NAK)
这是USB 全速模式下「主机轮询设备 IN 中断端点」的流程 ,3 个包是连续的传输交互,先拆分每个包的含义 + 发送方,再结合
CUSTOM_HID_FS_BINTERVAL = 5解释逻辑:1. 第 1 包:SOF 帧同步包
字段 含义 发送方 SYNC USB 包的同步字段(固定 8 位,用于时钟同步) 主机 PID SOF 包类型:帧起始包(Start Of Frame) 主机 Frame #0x4C8 当前帧的编号(USB 全速每 1ms 发 1 个 SOF,用于计时 / 同步) 主机 CRC OK 0x05 包的 CRC 校验正确 - EOP 包结束标志(End Of Packet) 主机 2. 第 2 包:IN 令牌包
字段 含义 发送方 SYNC 同步字段 主机 PID IN 包类型:IN 令牌(主机请求从设备读取数据,传输方向:设备→主机) 主机 Address=0x0B Endpoint=0x01 目标设备地址(0x0B)+ 目标端点(0x01,即你配置的中断 IN 端点) 主机 CRC OK 0x12 CRC 校验正确 - EOP 包结束标志 主机 3. 第 3 包:NAK 响应包
字段 含义 发送方 SYNC 同步字段 设备 PID NAK 包类型:否定确认(表示设备当前没有数据可以发给主机) 设备 EOP 包结束标志 设备 整体流程 + 与
bInterval=5的关系配置的
CUSTOM_HID_FS_BINTERVAL = 5,表示主机每 5ms(5 个 SOF 帧)轮询一次该中断 IN 端点:
- 主机每 1ms 发 1 个 SOF(所以你会看到连续的 SOF 包);
- 当累计到 5 个 SOF(5ms)时,主机发送
IN令牌包,请求从设备的 Endpoint 0x01 读取数据;- 但此时设备没有待上传的数据(比如 HID 输入报告还没准备好),所以设备回复
NAK响应包,告诉主机 "现在没数据,你稍后再问"。关键说明
- 这 3 个包是主机与设备的一次交互回合:主机先发 SOF 同步→再发 IN 请求读数据→设备回复 NAK 表示无数据;
- 只有
IN令牌包是 "主机请求读取数据" 的指令,NAK是设备的回应,SOF 是基础同步包;- 若设备后续准备好数据,当主机再次发 IN 令牌包时,设备会回复 DATA 包(而非 NAK)。
三.USB和自定义HID类配置详解
3.1 设备描述符
设备描述符数组参考
cpp__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = { 0x12, /* bLength:设备描述符的固定总长度 → 18字节 (0x12=18),USB协议强制,不可改 */ USB_DESC_TYPE_DEVICE, /* bDescriptorType:描述符类型 → 设备描述符,宏定义值=0x01,USB协议固定值 */ 0x00, /* bcdUSB[低字节]:USB协议版本号 → 0x0200 = USB 2.0 协议 */ 0x02, /* bcdUSB[高字节]:高字节是0x02,组合起来就是 USB 2.0 */ 0x00, /* bDeviceClass:设备的顶层类代码 → 0x00=延迟指定类 */ 0x00, /* bDeviceSubClass:设备的顶层子类代码 → 0x00=无子类 */ 0x00, /* bDeviceProtocol:设备的顶层协议代码 → 0x00=无协议 */ USB_MAX_EP0_SIZE, /* bMaxPacketSize0:EP0端点的最大包长 → 就是宏定义的64字节,全速USB固定64 */ LOBYTE(USBD_VID), /* idVendor[低字节]:厂商ID的低8位,拆分宏USBD_VID(1151)的低字节 */ HIBYTE(USBD_VID), /* idVendor[高字节]:厂商ID的高8位,拆分宏USBD_VID(1151)的高字节 */ LOBYTE(USBD_PID_FS), /* idProduct[低字节]:产品ID的低8位,拆分宏USBD_PID_FS(22351)的低字节 */ HIBYTE(USBD_PID_FS), /* idProduct[高字节]:产品ID的高8位,拆分宏USBD_PID_FS(22351)的高字节 */ 0x00, /* bcdDevice[低字节]:设备的硬件版本号 → 0x0200 = 版本2.00 */ 0x02, /* bcdDevice[高字节]:版本号高字节,自定义即可,无协议限制 */ USBD_IDX_MFC_STR, /* iManufacturer:厂商名称字符串的索引 → 宏定义的0x01 */ USBD_IDX_PRODUCT_STR, /* iProduct:产品名称字符串的索引 → 宏定义的0x02 */ USBD_IDX_SERIAL_STR, /* iSerialNumber:设备序列号字符串的索引 → 宏定义的0x03 */ USBD_MAX_NUM_CONFIGURATION /* bNumConfigurations:设备支持的配置数量 → 宏定义的1 */ };
cpp#define USB_MAX_EP0_SIZE 64U #define USBD_VID 1151 #define USBD_PID_FS 22351 #define USBD_IDX_MFC_STR 0x01U #define USBD_IDX_PRODUCT_STR 0x02U #define USBD_IDX_SERIAL_STR 0x03U #define USBD_MAX_NUM_CONFIGURATION 1宏 1: USB_MAX_EP0_SIZE = 64U
含义:USB 设备的 0 号控制端点 (EP0) 的单次最大传输字节数;
数值说明:64是全速 USB (FS) 的强制固定值,不能改!USB 协议规定:全速 USB 的 EP0 最大包长就是 64 字节,高速 USB 是 512 字节;
核心知识点:EP0 是所有 USB 设备的「标配端点」,每一个 USB 设备都必须有 EP0,没有例外,EP0 是「控制端点」,负责USB 枚举的所有指令交互(电脑发指令、设备回传描述符),是双向端点,也是唯一不需要在配置描述符里定义的端点。
宏 2: USBD_VID = 1151
英文全称:Vendor ID → 厂商 ID;
含义:这是你的 USB 设备的「厂商编号」,是一个16 位的数值,用来标识「这个 USB 设备是哪个公司 / 厂商生产的」;
关键说明:正规的 VID 是需要向 USB-IF 组织 付费申请的,是全球唯一的;
你这里的1151是 STM32 官方库的默认测试 VID,非正规编号,用于开发调试完全没问题,电脑能正常识别;可以自己随便改这个数值(比如改成0x0483,这是 ST 官方的正规 VID),不影响设备工作。
宏 3:USBD_PID_FS = 22351
英文全称:Product ID → 产品 ID;
含义:这是你的 USB 设备的 产品型号编号,是一个16 位的数值,由厂商自己定义,用来标识「这个 VID 厂商下的哪一款产品」;
关键说明:
VID+PID 组合起来,是 USB 设备的唯一身份标识(比如:VID=1151,PID=22351 就是你这款 USB 鼠标设备的唯一编号);
电脑识别 USB 设备、加载对应驱动,核心就是靠 VID+PID;
这个数值也是 STM32 库的默认值,你可以随便改,开发调试无影响。
补充:VID 和 PID 的组合示例比如你电脑上的罗技鼠标,VID 是罗技的编号,PID 是这款鼠标的型号编号;你的 STM32 设备,VID=1151,PID=22351,电脑就会认为这是一款编号 22351 的 1151 厂商设备」。
**宏 4/5/6:**MFC_STR/PRODUCT_STR/SERIAL_STR
USBD_IDX_MFC_STR=0x01、
USBD_IDX_PRODUCT_STR=0x02、
USBD_IDX_SERIAL_STR=0x03
英文全称:Index → 索引值,这三个宏是「USB 字符串描述符的索引编号」;
含义拆解:
USBD_IDX_MFC_STR = 0x01 → 厂商名称字符串的索引是 1;
USBD_IDX_PRODUCT_STR = 0x02 → 产品名称字符串的索引是 2;
USBD_IDX_SERIAL_STR = 0x03 → 设备序列号字符串的索引是 3;
核心知识点:
USB 的「字符串描述符」是可选的,是给人看的文本信息(比如:厂商名 ="STMicroelectronics",产品名 ="STM32 USB Mouse",序列号 ="123456789");
这些索引值0x01/0x02/0x03,就是告诉电脑:「你要读厂商名,就去读索引 1 的字符串描述符;读产品名,读索引 2 的」;
USB 协议规定:索引 0 是预留的,不能用,所以所有字符串描述符的索引都从0x01开始。
宏 7:USBD_MAX_NUM_CONFIGURATION = 1
含义:这个 USB 设备支持的「配置描述符数量」;
数值说明:1表示你的设备只有 1 套配置参数(就是你之前问的那个鼠标配置描述符);
关键说明:USB 协议允许一个设备有多个配置(比如:一个 USB 声卡可以有「耳机模式」「麦克风模式」两个配置),但 STM32 的 USB 开发中,绝大多数场景都只需要 1 个配置,这个值固定写1即可,改大了反而会增加代码复杂度,无意义。
3.2 配置描述符
3.2.1 配置描述符数组参考
代码详细参考下文
STM32 USB 库 中「全速 (FS) USB 自定义 HID 设备」的核心配置描述符数组 ,是 USB 协议中最核心的代码之一,也是 USB 设备能被电脑(主机)成功识别、正常工作的基础。这是一个按 USB 协议标准、严格按字节顺序拼接的「二进制配置描述符块」,是一个uint8_t字节数组,所有数值都是十六进制的字节值。USB 设备插入电脑(主机)后,电脑会发起「USB 枚举流程」,第一步就是读取这个描述符数组;这个数组告诉电脑所有关键信息:设备是 USB 全速设备、是 HID 类型、有几个接口、有几个数据通道(端点)、每个通道的工作方式 / 传输大小 / 轮询频率等;这个数组的字节顺序、每个字节的含义都是 USB 协议强制规定的,错 1 个字节、少 1 个字节、顺序乱 1 个字节 → USB 枚举直接失败,电脑可能识别不到这个 USB 设备。
3.2.2 关键缩写解释
USB:通用串行总线协议
FS = Full Speed: 全速 USB,传输速率 12Mbps(STM32 最常用的 USB 速率)
HID = Human Interface Device: 人机接口设备(鼠标、键盘、游戏手柄、扫码枪都是标准 HID 设备)
CUSTOM_HID: 自定义 HID 设备(区别于标准鼠标 / 键盘),最灵活的 HID 类型,也是你这段代码的核心,自定义 HID 可以自己定义收发的数据格式,既能给电脑发数据、也能接收电脑的指令,是 STM32 做 USB 外设的首选方式。
__ALIGN_BEGIN/__ALIGN_END :STM32 的专用字节对齐宏,作用是让这个字节数组在内存中按 4 字节对齐存储,避免 USB 硬件读取描述符时出现「字节错位」的致命错误,必须加。
**static:**数组只在当前文件生效,节省内存 + 防止误修改。3.2.3 USB 描述符的通用规则
所有 USB 描述符的前 2 个字节,是【固定格式】,没有例外!
第 1 个字节: bLength → 当前描述符的总字节长度(十六进制),比如配置描述符固定是0x09(9 字节)、端点描述符固定是0x07(7 字节);
**第 2 个字节:**bDescriptorType → 当前描述符的类型标识(USB 协议给的固定值),比如配置描述符 = 0x02、接口描述符 = 0x04、端点描述符 = 0x05、HID 类描述符 = 0x21。这个数组,是5 段描述符「顺序拼接」而成 ,USB 协议强制要求这个拼接顺序,不能打乱:
标准配置描述符 (9 字节) → 标准接口描述符 (9 字节) → 自定义 HID 类描述符 (9 字节) → IN 端点描述符 (7 字节) → OUT 端点描述符 (7 字节)
总长度:9+9+9+7+7 = 41 字节(对应代码最后的注释/* 41 */)
注意:所有0xXX都是单字节的十六进制值,USB 协议中「双字节数值」都是小端序(低字节在前,高字节在后),这是 USB 协议的强制要求,
cpp/* USB CUSTOM_HID device FS Configuration Descriptor */ __ALIGN_BEGIN static uint8_t USBD_CUSTOM_HID_CfgFSDesc[USB_CUSTOM_HID_CONFIG_DESC_SIZ] __ALIGN_END = { 0x09, /* bLength: Configuration Descriptor size */ USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */ USB_CUSTOM_HID_CONFIG_DESC_SIZ, /* wTotalLength: Bytes returned */ 0x00, 0x01, /*bNumInterfaces: 1 interface*/ 0x01, /*bConfigurationValue: Configuration value*/ 0x00, /*iConfiguration: Index of string descriptor describing the configuration*/ 0xC0, /*bmAttributes: bus powered */ 0x32, /*MaxPower 100 mA: this current is used for detecting Vbus*/ /************** Descriptor of CUSTOM HID interface ****************/ /* 09 */ 0x09, /*bLength: Interface Descriptor size*/ USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/ 0x00, /*bInterfaceNumber: Number of Interface*/ 0x00, /*bAlternateSetting: Alternate setting*/ 0x02, /*bNumEndpoints*/ 0x03, /*bInterfaceClass: CUSTOM_HID*/ 0x00, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/ 0x00, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/ 0, /*iInterface: Index of string descriptor*/ /******************** Descriptor of CUSTOM_HID *************************/ /* 18 */ 0x09, /*bLength: CUSTOM_HID Descriptor size*/ CUSTOM_HID_DESCRIPTOR_TYPE, /*bDescriptorType: CUSTOM_HID*/ 0x11, /*bCUSTOM_HIDUSTOM_HID: CUSTOM_HID Class Spec release number*/ 0x01, 0x00, /*bCountryCode: Hardware target country*/ 0x01, /*bNumDescriptors: Number of CUSTOM_HID class descriptors to follow*/ 0x22, /*bDescriptorType*/ USBD_CUSTOM_HID_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/ 0x00, /******************** Descriptor of Custom HID endpoints ********************/ /* 27 */ 0x07, /*bLength: Endpoint Descriptor size*/ USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/ CUSTOM_HID_EPIN_ADDR, /*bEndpointAddress: Endpoint Address (IN)*/ 0x03, /*bmAttributes: Interrupt endpoint*/ CUSTOM_HID_EPIN_SIZE, /*wMaxPacketSize: 2 Byte max */ 0x00, CUSTOM_HID_FS_BINTERVAL, /*bInterval: Polling Interval */ /* 34 */ 0x07, /* bLength: Endpoint Descriptor size */ USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: */ CUSTOM_HID_EPOUT_ADDR, /*bEndpointAddress: Endpoint Address (OUT)*/ 0x03, /* bmAttributes: Interrupt endpoint */ CUSTOM_HID_EPOUT_SIZE, /* wMaxPacketSize: 2 Bytes max */ 0x00, CUSTOM_HID_FS_BINTERVAL, /* bInterval: Polling Interval */ /* 41 */ };
第一部分:USB 标准配置描述符 (9 字节,数组第 0~8 位)
USB 标准配置描述符 (9 字节,数组第 0~8 位)
cpp0x09, // bLength: 配置描述符的固定长度 → 9字节(USB协议强制) USB_DESC_TYPE_CONFIGURATION, // bDescriptorType: 类型=配置描述符(宏定义值=0x02) USB_CUSTOM_HID_CONFIG_DESC_SIZ, // wTotalLength[低字节]: 本配置描述符的总字节数(41) 0x00, // wTotalLength[高字节]: 总长度高字节,41是单字节,所以填0 0x01, // bNumInterfaces: 本设备只有【1个USB接口】(HID设备固定1个) 0x01, // bConfigurationValue: 配置编号=1(主机枚举时会选中这个配置) 0x00, // iConfiguration: 无配置相关的字符串描述符(填0即可) 0xC0, // bmAttributes: 设备供电属性 + 特性(重中之重!) 0x32, // MaxPower: 设备最大功耗(单位:2mA/1个单位)bmAttributes = 0xC0:二进制是1100 0000,USB 协议中这个字节的位定义:
第 7 位(最高位):必须为1 → 表示总线供电(设备的供电来自 USB 线的 5V,STM32 USB 设备基本上都是总线供电);
第 6 位:1= 自供电,0= 无自供电 → 这里是 0;
第 5 位:1= 支持远程唤醒,0= 不支持 → 这里是 0;
低 5 位:固定填 0;→ 总结:0xC0 = 总线供电 + 不自供电 + 不支持远程唤醒(STM32 HID 设备的标准配置)。
MaxPower = 0x32:0x32=50,USB 协议规定这个值的单位是 2mA → 50 × 2mA = 100mA。→ 含义:告诉主机,这个 USB 设备最多从 USB 总线取 100mA 电流,主机据此分配供电,这个值是 STM32 USB 的「标配值」,不用改。
第二部分:USB 标准接口描述符 (9 字节,数组第 9~17 位)
紧跟配置描述符,告诉主机:当前 USB 接口的类型、功能、使用的端点数量,HID 设备的核心接口定义都在这里,注释标注/* 09 */
cpp0x09, // bLength: 接口描述符固定长度 →9字节 USB_DESC_TYPE_INTERFACE, // bDescriptorType: 类型=接口描述符(宏定义值=0x04) 0x00, // bInterfaceNumber: 接口编号=0(只有1个接口,编号从0开始) 0x00, // bAlternateSetting: 无备用接口(填0即可) 0x02, // bNumEndpoints: 该接口使用【2个端点】(IN+OUT,核心值!) 0x03, // bInterfaceClass: 接口类型=HID类(USB协议固定值=0x03,重中之重!) 0x00, // bInterfaceSubClass: 子类=0 → 自定义HID(1=标准HID/键鼠/鼠标) 0x00, // bInterfaceProtocol: 协议=0 → 无标准协议(1=键盘,2=鼠标) 0, // iInterface: 无接口相关字符串描述符(填0即可)本部分关键字节解释(重中之重,一个都不能错)
bNumEndpoints = 0x02:当前接口用2 个端点,正好对应后面的「IN 端点 + OUT 端点」,数量必须和后面的端点描述符数量一致!
bInterfaceClass = 0x03:USB 协议的绝对固定值!只要是 HID 设备(不管标准 / 自定义),这个值必须是0x03,改了这个值,电脑就不会把设备识别为 HID,枚举直接失败!
bInterfaceSubClass=0x00 + bInterfaceProtocol=0x00:组合起来表示「自定义 HID 设备」,没有遵循键鼠 / 鼠标的标准协议,这也是你代码里CUSTOM_HID的核心体现。如果是标准键盘,这里会是0x01+0x01,标准鼠标是0x01+0x02。
第三部分:USB 自定义 HID 类专用描述符 (9 字节,数组第 18~26 位)
这是HID 设备独有的描述符 ,告诉主机:遵循的 HID 协议版本、HID 的核心配置、报告描述符的信息,注释标注
/* 18 */
cpp0x09, // bLength: HID类描述符固定长度 →9字节 CUSTOM_HID_DESCRIPTOR_TYPE, // bDescriptorType: 类型=HID类描述符(宏定义值=0x21) 0x11, // bHID[低字节]: HID协议版本号 → 0x0111 = HID 1.11规范 0x01, // bHID[高字节]: 协议版本高字节 0x00, // bCountryCode: 硬件目标国家码 → 0=无地域限制(通用) 0x01, // bNumDescriptors: 跟随的HID类描述符数量 →1个(固定值) 0x22, // bDescriptorType: 跟随的描述符类型=报告描述符(USB协议固定值=0x22) USBD_CUSTOM_HID_REPORT_DESC_SIZE, // wItemLength[低字节]: HID报告描述符的总长度 0x00, // wItemLength[高字节]: 报告描述符长度高字节(一般长度<256,填0)本部分关键字节解释(核心)
**0x11 0x01:**组合是0x0111 → 表示遵循 USB HID 1.11 协议规范,这是目前最通用的 HID 协议版本,不用改。
**0x22:**USB 协议固定值,代表(接下来的长度描述的是【HID 报告描述符】的大小)。
HID 报告描述符:这是自定义 HID 的(灵魂),你的这段代码只是配置描述符,报告描述符是另一个独立的数组,它的作用是(定义 HID 设备的收发数据格式):比如一次发多少字节、每个字节的含义、是输入还是输出数据等。USBD_CUSTOM_HID_REPORT_DESC_SIZE就是这个报告描述符的字节长度。
第四部分:USB 标准端点描述符 (IN 端点,7 字节,数组第 27~33 位)
端点(Endpoint)是 USB 设备的数据通道,是 USB 通信的「最小单位」,所有 USB 数据收发都必须通过端点完成。
USB 的端点是「单向的」,所以需要分IN和OUT两个端点,注释标注/* 27 */,端点描述符的固定长度是7 字节(0x07),所有端点都遵循这个格式。
cpp0x07, // bLength: 端点描述符固定长度 →7字节 USB_DESC_TYPE_ENDPOINT, // bDescriptorType: 类型=端点描述符(宏定义值=0x05) CUSTOM_HID_EPIN_ADDR, // bEndpointAddress: IN端点地址(核心!) 0x03, // bmAttributes: 端点的传输类型(USB协议固定值,重中之重!) CUSTOM_HID_EPIN_SIZE, // wMaxPacketSize[低字节]: IN端点一次最大传输字节数 0x00, // wMaxPacketSize[高字节]: 最大传输字节数高字节(填0) CUSTOM_HID_FS_BINTERVAL,// bInterval: 端点轮询间隔(单位:ms,全速USB专属)
第五部分:USB 标准端点描述符 (OUT 端点,7 字节,数组第 34~40 位)
和 IN 端点描述符格式完全一致,只是「端点地址」不同,注释标注
/* 34 */
cpp0x07, // bLength: 端点描述符固定长度 →7字节 USB_DESC_TYPE_ENDPOINT, // bDescriptorType: 类型=端点描述符(0x05) CUSTOM_HID_EPOUT_ADDR, // bEndpointAddress: OUT端点地址(核心!) 0x03, // bmAttributes: 端点的传输类型(和IN端点一致=0x03) CUSTOM_HID_EPOUT_SIZE, // wMaxPacketSize[低字节]: OUT端点一次最大传输字节数 0x00, // wMaxPacketSize[高字节]: 填0 CUSTOM_HID_FS_BINTERVAL,// bInterval: 轮询间隔(和IN端点一致)
第六部分:配置描述符详解
1. IN/OUT 端点的方向定义(USB 协议的要求)
USB 的方向,永远是**相对于电脑(主机)**而言的,不是相对于 STM32!
CUSTOM_HID_EPIN_ADDR:IN 端点 → 数据流向:STM32(设备) → 电脑(主机)作用:STM32 主动给电脑发数据(比如:采集的传感器数据、按键状态、串口数据转发等),这是「上报数据」。宏定义值一般是0x81(地址 1,IN 方向),USB 中 IN 端点的地址最高位固定是 1(二进制)。
CUSTOM_HID_EPOUT_ADDR:OUT 端点 → 数据流向:电脑(主机) → STM32(设备)作用:电脑主动给 STM32 发指令(比如:控制 LED 亮灭、设置参数、控制电机等),这是「接收指令」。宏定义值一般是0x01(地址 1,OUT 方向),USB 中 OUT 端点的地址最高位固定是 0(二进制)。
2. 端点传输类型 bmAttributes = 0x03(HID 设备的强制要求)
这个字节的低 2 位定义传输类型,0x03 = 二进制0000 0011 → 代表:中断传输 (Interrupt Transfer)。核心规则:所有 HID 设备(标准 / 自定义)的端点,只能使用【中断传输】,不能用其他传输方式!
为什么用中断传输?中断传输的特点是「固定频率轮询、低延迟、可靠」,适合传输少量的、实时性要求高的数据(比如按键、鼠标移动、传感器值),完美匹配 HID 设备的需求;
USB 的其他传输方式:批量传输(U 盘)、同步传输(声卡)、控制传输(枚举用),都不能用在 HID 端点上,改了就枚举失败。
3. 其他端点参数解释
CUSTOM_HID_EPIN_SIZE / CUSTOM_HID_EPOUT_SIZE:代码里是0x02 → 代表每个端点一次最多传输 2 字节数据。这个值可以改(全速 USB 最大 64 字节),按需调整即可。
CUSTOM_HID_FS_BINTERVAL:轮询间隔,全速 USB 下单位是毫秒 (ms),常用值是0x0A(10ms) → 含义:电脑会每隔 10ms 主动查询一次这个端点,看有没有数据要收发。这个值越小,实时性越高,一般填 10ms 足够。这里使用1ms 和 5ms 进行测试;
如果我这样子修改
对比1:参考自定义hid 和 鼠标类的对比( 新增「远程唤醒」功能**)**
bmAttributes = 0xC0 变为 0XE0
二进制:0xE0 = 1110 0000,USB 协议这个字节的位规则不变;
变化仅在第 5 位:从0→1,代表该 USB 设备 新增「远程唤醒」功能;
其他不变:第 7 位还是1(总线供电,靠 USB 5V 供电),第 6 位还是0(无自供电);
通俗讲:这个鼠标设备可以主动唤醒休眠的电脑(USB 标准功能,不影响核心使用)。
补充:0x32(MaxPower 100mA) 没改,功耗不变。
对比2:接口描述符的 3 个关键字节全部修改(本质变化,决定设备类型)
cpp// 原配置(自定义HID): 0x02, // bNumEndpoints: 2个端点(IN+OUT双向) 0x00, // bInterfaceSubClass : 0=自定义HID 0x00, // nInterfaceProtocol : 0=无标准协议 // 新对比配置(鼠标): 0x01, /*bNumEndpoints: 1个端点 【改1】*/ 0x03, /*bInterfaceClass: HID 【不变】*/ 0x01, /*bInterfaceSubClass : 1=BOOT子类 【改2】*/ 0x02, /*nInterfaceProtocol : 2=标准鼠标协议 【改3】*/bNumEndpoints = 0x01:端点数量从2 个 → 1 个
原自定义 HID:2个端点(IN+OUT),双向通信 → 既能 STM32 发数据给电脑,也能电脑发指令给 STM32;
新标准鼠标:1个端点(仅IN),单向通信 → 只有 STM32 → 电脑 这一个方向,电脑不会给这个鼠标发任何数据;
原因:USB 标准鼠标的逻辑就是「只上报鼠标状态(移动、按键)给电脑,不需要接收电脑的指令」,所以 OUT 端点完全不需要,直接删掉。
bInterfaceSubClass = 0x01:子类从0(自定义) → 1(BOOT)
USB 协议定义:0x00 = 自定义 HID 设备(无标准规范,自己定义数据格式);
0x01 = BOOT 类 HID 设备(标准启动类设备,仅键鼠);
关键特性:BOOT 类鼠标 / 键盘,电脑的 BIOS 阶段就能识别,开机进系统前就能用,优先级远高于自定义 HID,这是标准键鼠的专属标识。
bInterfaceProtocol = 0x02:协议类型从0(无) → 2(标准鼠标)
USB 协议固定值:0x00= 无协议、0x01= 标准键盘、0x02= 标准鼠标;
改完这个值,电脑会直接调用系统自带的鼠标驱动,免装任何驱动,即插即用,这是标准鼠标的核心特征!
结论:
把原来的自定义 HID 设备 改成了 USB 协议标准的BOOT 类鼠标设备带摇杆功能的标准鼠标,当然鼠标后续的报告描述符和端点长度这些也需要需要修改,后续有机会介绍
3.3 自定义HID描述符和鼠标HID描述符对比
3.3.1 鼠标和自定义HID描述符对比
前面的设备描述符和配置描述符没有太大的差别,HID设备最重要的是HID描述符以下是官方的自定义HID描述符和鼠标HID描述符;
STM32 官方库的「自定义 HID 标准模板」;
描述符是 「对称双向 64 字节」,输入和输出的格式完全一致,这是自定义 HID 最常用的写法,兼容性最好,开发也最方便。
cpp0x06, 0x00, 0xff, // 声明:厂商自定义设备,脱离标准类 0x09, 0x01, // 声明:自定义用途ID=1 0xa1, 0x01, // 开始:应用级集合(HID逻辑的总入口) // ↓↓↓ 设备→电脑:64字节输入报告 ↓↓↓ 0x09, 0x01, // 关联自定义用途ID 0x15, 0x00, // 数据最小值:0 0x26, 0xff, 0x00, // 数据最大值:255(单字节无符号数) 0x75, 0x08, // 每个数据项8bit=1字节 0x95, 0x40, // 共64个数据项 → 64字节 0x81, 0x02, // 输入属性:可读写的有效数据,绝对值 // ↓↓↓ 电脑→设备:64字节输出报告 ↓↓↓ 0x09, 0x01, // 关联自定义用途ID 0x15, 0x00, // 数据最小值:0 0x26, 0xff, 0x00, // 数据最大值:255 0x75, 0x08, // 每个数据项8bit=1字节 0x95, 0x40, // 共64个数据项 → 64字节 0x91, 0x02, // 输出属性:可读写的有效数据,绝对值 0xC0 // 结束:应用级集合(闭合逻辑)STM32 官方库的「HID鼠标模板」
cpp#define HID_MOUSE_REPORT_DESC_SIZE 74U // 标准USB HID鼠标报告描述符 (74字节),支持左键/右键/中键 + X轴/Y轴移动 + 滚轮滚动 + 预留扩展功能位 // 遵循USB HID 1.11协议规范,电脑免驱识别为标准USB鼠标,BIOS级兼容,配套4字节鼠标数据包 __ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END = { 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 定义用途页:通用桌面设备(协议固定值) 0x09, 0x02, // USAGE (Mouse) 定义设备类型:鼠标(协议固定值) 0xA1, 0x01, // COLLECTION (Application) 开始定义应用级集合,总逻辑体开始,与最后0xC0配对 0x09, 0x01, // USAGE (Pointer) 定义子设备:指针(鼠标的核心指针功能) 0xA1, 0x00, // COLLECTION (Physical) 开始定义物理级集合,鼠标硬件功能体开始,与第45行0xC0配对 0x05, 0x09, // USAGE_PAGE (Button) 定义用途页:按键功能 0x19, 0x01, // USAGE_MINIMUM (Button 1) 定义最小按键编号:左键(按键1) 0x29, 0x03, // USAGE_MAXIMUM (Button 3) 定义最大按键编号:中键(按键3) 0x15, 0x00, // LOGICAL_MINIMUM (0) 按键逻辑最小值:0(按键松开状态) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 按键逻辑最大值:1(按键按下状态) 0x95, 0x03, // REPORT_COUNT (3) 报告数据个数:3个(对应左/右/中键) 0x75, 0x01, // REPORT_SIZE (1) 每个报告数据位宽:1bit (1个按键占1bit) 0x81, 0x02, // INPUT (Data,Var,Abs) 输入报告属性:数据位、可变值、绝对值 | 3个按键共占3bit 0x95, 0x01, // REPORT_COUNT (1) 报告数据个数:1组 0x75, 0x05, // REPORT_SIZE (5) 每个报告数据位宽:5bit 0x81, 0x01, // INPUT (Cnst,Arr,Abs) 输入报告属性:常量、数组、绝对值 | 5bit填充位(凑满1字节,无实际意义) 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 重新定义用途页:通用桌面设备 0x09, 0x30, // USAGE (X) 定义X轴移动 (协议固定值:0x30=X轴) 0x09, 0x31, // USAGE (Y) 定义Y轴移动 (协议固定值:0x31=Y轴) 0x09, 0x38, // USAGE (Wheel) 定义滚轮滚动 (协议固定值:0x38=滚轮) 0x15, 0x81, // LOGICAL_MINIMUM (-127) 逻辑最小值:0x81=-127 (左移/上移/滚轮下滚) 0x25, 0x7F, // LOGICAL_MAXIMUM (127) 逻辑最大值:0x7F=+127 (右移/下移/滚轮上滚) 0x75, 0x08, // REPORT_SIZE (8) 每个轴/滚轮数据位宽:8bit=1字节 0x95, 0x03, // REPORT_COUNT (3) 报告数据个数:3个(X轴+Y轴+滚轮) 0x81, 0x06, // INPUT (Data,Var,Rel) 输入报告属性:数据位、可变值、相对值 | 三轴数据各占1字节,共3字节 0xC0, // END_COLLECTION 结束物理级集合,与第10行0xA1 0x00配对 0x09, 0x3C, // USAGE (System Control) 定义用途:系统控制功能(预留扩展) 0x05, 0xFF, // USAGE_PAGE (Vendor Defined) 定义用途页:厂商自定义区域(0xFF00) 0x09, 0x01, // USAGE (Vendor Usage 1) 定义厂商自定义用途ID:1 0x15, 0x00, // LOGICAL_MINIMUM (0) 自定义数据最小值:0 0x25, 0x01, // LOGICAL_MAXIMUM (1) 自定义数据最大值:1 0x75, 0x01, // REPORT_SIZE (1) 自定义数据位宽:1bit 0x95, 0x02, // REPORT_COUNT (2) 自定义数据个数:2bit (预留2个功能位) 0xB1, 0x22, // FEATURE (Data,Var,Abs,NVol,NPref) 特征报告属性:自定义功能位为可读写数据 0x75, 0x06, // REPORT_SIZE (6) 填充位宽:6bit 0x95, 0x01, // REPORT_COUNT (1) 填充位个数:1组 0xB1, 0x01, // FEATURE (Cnst,Arr,Abs,NVol,NPref) 特征报告属性:6bit填充位,无实际意义 0xC0 // END_COLLECTION 结束应用级集合,与第6行0xA1 0x01配对,描述符结束 };
3.3.2 HID自定义和HID鼠标对比
**差异 1:**USAGE_PAGE(用途页)
差异 1:【协议根基】核心指令 USAGE_PAGE(用途页) 完全不同(绝对核心!0 容错)
这是第一个字节就体现的差异,也是 USB 协议里「区分标准 / 自定义」的铁律,改这个字节,设备身份立刻变!
标准鼠标描述符 → 第一行指令:
0x05, 0x01**含义:**告诉电脑「我属于 USB 标准化的『通用桌面设备』家族」,这个家族里只有鼠标、键盘、游戏手柄、摇杆等常见外设,所有设备的指令都是标准化的;
**取值:**0x01 是 USB 协议强制固定值,所有标准键鼠都必须写这个值。
自定义 HID 描述符 → 第一行指令:
0x06, 0x00, 0xFF**含义:**告诉电脑「我不属于任何 USB 标准化的设备家族,我是厂商自己定义的私有设备」,0xFF00 是 USB 协议为厂商预留的私有用途页地址,全世界所有自定义 HID 设备都用这个值;
**取值:**0xFF00 是 USB 协议的固定预留值,所有自定义 HID 都必须这么写,这是「自定义」的官方入口。
**补充铁律:**USAGE_PAGE 是 HID 描述符的「根指令」,它决定了后续所有指令的「归属和含义」,标准鼠标的指令在0x01页里有效,自定义的指令在0xFF00页里有效,二者完全互斥。
差异 2:【设备功能】功能是否被协议锁死
差异 2:【设备功能】功能是否被协议锁死 → 标准鼠标「固定死」,自定义「无限自由」
标准鼠标描述符 → 功能是 USB 协议强制固定的,你只能用、不能改、不能加、不能减
能实现的功能:永远只有「左键 / 右键 / 中键 + X 轴移动 + Y 轴移动 + 滚轮」,这是鼠标的全部功能,协议里写死了;
数据的含义:每个字节、每个 bit 的含义都是固定的,比如 Byte0 的 bit0 一定是左键,Byte1 一定是 X 轴,改了电脑就识别不了;
**举个例子:**你想让鼠标的 Byte1 变成「控制 LED 亮灭」,不行!电脑依然会把它当成 X 轴移动,结果就是「LED 没反应,鼠标乱飞」。
自定义 HID 描述符 → 功能是 你说了算,无限自由,无任何限制
能实现的功能: 想定义什么功能,就定义什么功能,比如: 上报传感器数据(温湿度、加速度、电压电流); 上报按键 / 摇杆 / 旋钮的状态; 接收电脑下发的指令(控制 LED、蜂鸣器、电机、舵机);
数据的含义:每个字节、每个 bit 的含义,由你自己定义,电脑不会解析,只会原封不动的收发,数据的含义只有你自己知道;
**举个例子:**你定义 Byte0 的 bit0 是「LED1 亮灭」,bit1 是「LED2 亮灭」,Byte1 是「电机转速」,完全没问题,USB 协议不会有任何意见。
差异 3:【通信能力】数据传输方向和通道
差异 3:【通信能力】数据传输方向 & 通道 → 鼠标是「单向残废」,自定义是「双向满血」
这是开发中最实用的差异,也是改配置描述符时「端点数量变化」的根本原因;
标准鼠标描述符 → 仅支持【单向通信】:设备 → 主机 (只有 IN 端点)
**通信逻辑:**鼠标的本质是「只上报自己的状态给电脑」,电脑只需要「接收鼠标数据」,不需要「给鼠标发指令」;
**描述符特征:**描述符里只有 INPUT 指令,没有任何 OUTPUT 指令;
配置描述符匹配:只需要1 个 IN 中断端点,前面鼠标配置描述符里 bNumEndpoints=0x01;
**核心限制:**你永远只能给电脑发数据,电脑永远不能给你发任何指令,想做「电脑控制 STM32 的 LED / 电机」,用标准鼠标描述符完全做不到。
自定义 HID 描述符 → 支持【双向全双工通信】:设备 ↔ 主机 (IN+OUT 双端点)
**通信逻辑:**自定义 HID 的设计初衷就是「自由通信」,既可以让 STM32 主动给电脑发数据(上报),也可以让电脑主动给 STM32 发数据(下发指令),二者互不干扰,同时进行;
**描述符特征:**前面自定义HID代码里同时有 INPUT 和 OUTPUT 两段完整的指令,是「输入报告 + 输出报告」的完整组合;
0x81,0x02 → INPUT:设备→主机,STM32 给电脑发 64 字节自定义数据;
0x91,0x02 → OUTPUT:主机→设备,电脑给 STM32 发 64 字节自定义数据;
**配置描述符匹配:**必须用2 个中断端点(IN+OUT),所以你最开始的配置描述符里 bNumEndpoints=0x02;
**核心优势:**既能上报数据,又能接收指令,这是自定义 HID 最核心的价值,也是绝大多数 STM32 USB 项目选择自定义 HID 的原因。
差异 4:【数据格式】数据包的长度和含义
差异 4:【数据格式】数据包的长度含义 → 鼠标「固定死」,自定义「自己定」
这是开发中最直接的差异,也是写代码时「最直观的感受」,和前面的配置描述符里 HID_EPIN_SIZE/CUSTOM_HID_EPIN_SIZE 强绑定!
标准鼠标描述符 → 数据包格式是 USB 协议强制固定,字节数不可改
精简版 50 字节描述符(拿描述符生成器DT.exe生成的) → 固定3 字节数据包:按键位 (1) + X 轴 (1) + Y 轴 (1);
完整版 74 字节描述符(stm32cubemx生成,参考这个) → 固定4 字节数据包:按键位 (1) + X 轴 (1) + Y 轴 (1) + 滚轮 (1);
**数据含义:**每个字节的含义是全世界统一的,比如 Byte0 的 bit0 = 左键,Byte1=X 轴,改一个字节就会出问题;
**字节数限制:**鼠标的数据包永远只有 3/4 字节,想多发一个字节都不行,协议不允许。
自定义 HID 描述符 → 数据包格式是 你自己定义,字节数按需设置
代码里定义的是 双向 64 字节数据包(已经设置为最大了),这个数值是你自己定的,核心指令解析:
cpp0x75,0x08 // REPORT_SIZE (8) → 每个数据项占8bit=1字节 0x95,0x40 // REPORT_COUNT (64) → 一共64个数据项 // 数据包总长度 = 8 × 64 = 64字节**灵活性:**想改成「双向 1 字节、双向 8 字节、双向 32 字节」都可以,只需要改 0x95 的值即可,无任何协议限制;
**数据含义:**这 64 个字节里,第 1 字节存什么、第 2 字节存什么,完全由自己决定,比如: 第 1 字节:按键状态(bit0 = 按键 1,bit1 = 按键 2);第 2-5 字节:温湿度传感器数据;第 6-9 字节:摇杆的 X/Y 轴数值;剩下的字节:预留备用;
**核心特点:**数据是「裸数据」,电脑不会解析,只会原封不动的传给你的上位机软件,由你自己写代码解析。
差异 5:电脑识别和驱动
差异 5:【电脑识别 & 驱动】→ 鼠标「免驱即用」,自定义「无驱动,需写上位机」
这是使用层面最核心的差异,也是「标准 HID」和「自定义 HID」最直观的体验区别,没有之一!
标准鼠标描述符 → 【极致友好】电脑自动识别 + 自动加载驱动
**电脑识别:**插入 USB 后,电脑的设备管理器里会显示 「鼠标和其他指针设备」→「USB 鼠标」;
**驱动:**完全不需要你写任何驱动,Windows/Linux/Mac 都自带鼠标驱动,即插即用,无需任何额外操作;
**数据解析:**电脑自动解析鼠标数据包,直接转换成「鼠标移动、按键点击」的系统事件,你不需要写任何上位机软件。
自定义 HID 描述符 → 【需要开发】电脑仅识别为 HID 设备,无驱动,需自己写上位机
**电脑识别:**插入 USB 后,电脑的设备管理器里会显示 「人体学输入设备」→「USB 输入设备」,名字是通用的,没有具体设备名;
驱动:Windows 会自动加载一个「通用 HID 驱动」,保证数据能收发,但这个驱动不会解析任何数据,只是一个「数据通道」;
**数据解析:**我这里使用免费的PortHelper1.8.0软件收发数据 或者 自己写上位机软件(比如用 C#/Python/Qt/ 串口助手),通过 HID 通信库读取 / 写入这 64 字节的裸数据,然后在软件里解析数据含义、实现功能(比如显示传感器数据、发送控制指令)。
**补充:**这不是缺点,而是自定义 HID 的「特性」------ 正因为没有系统驱动的限制,你才能实现完全自定义的功能,这是标准 HID 做不到的。


























