蓝牙HID协议概述
各位同学,今天咱们来聊聊蓝牙HID协议。说实话,这个协议是我在蓝牙开发中打交道最多的一个。鼠标、键盘、游戏手柄,甚至一些工业上的遥控器,底层都是靠它来通信的。
你可能会问:为什么蓝牙设备之间能识别出「这是个键盘」?为什么按下一个键,电脑就知道该显示哪个字母?答案就藏在HID协议里。今天我就带你把它拆开看看。
蓝牙HID协议栈架构
先看整体结构。蓝牙HID协议栈,说白了就是一层层叠起来的「通信管道」。从上到下,我习惯这么分:
- 应用层:你的鼠标固件、键盘固件,负责采集按键、移动数据
- HID Profile:定义了设备怎么描述自己、怎么传输数据
- L2CAP:负责把数据切成小包,通过蓝牙传出去
- HCI + 链路层:底层硬件控制,射频收发
嗯,这里要注意:HID Profile并不是一个独立的「层」,它更像是一套规则,告诉上层应用和下层协议该怎么配合。我在项目中见过不少新手,一上来就调L2CAP,结果发现数据发过去了,对方根本不认——其实就是HID Profile没配对。
HID Profile规范
HID Profile规范,你可以把它理解成「蓝牙设备之间的握手协议」。它主要干了三件事:
- 设备角色定义:谁是HID Host(主机,比如电脑),谁是HID Device(设备,比如键盘)
- 服务发现:设备连上后,主机得知道「你是个键盘还是个鼠标」
- 数据传输通道:建立一条专用的中断通道,用来实时传按键数据
我记得有一次调试一个蓝牙键盘,连上后主机死活不认。折腾了半天,发现是SDP(服务发现协议)里漏了一个UUID。你想想看,主机连你的设备是什么类型都不知道,怎么可能正常工作?
关键点:HID Profile规定,所有HID设备必须实现SDP记录,并且要包含HID PSM(协议服务多路复用器)的值。这个值通常是0x0011。
HID Report Descriptor基础概念
这个部分,我觉得是HID协议里最「烧脑」但也最核心的。Report Descriptor,直译过来就是「报告描述符」。它是一段二进制数据,用来告诉主机:
- 你的设备有多少个按键
- 每个按键是什么类型(普通键、修饰键、消费者控制键)
- 数据怎么打包(比如一个字节代表一个按键,还是两个字节)
说白了,它就是一份「数据说明书」。主机拿到这份说明书,才知道怎么解析你发过来的数据包。
举个例子。一个简单的键盘,它的Report Descriptor可能长这样:
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x05, 0x07, // Usage Page (Key Codes)
0x19, 0xE0, // Usage Minimum (224)
0x29, 0xE7, // Usage Maximum (231)
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)
0x19, 0x00, // Usage Minimum (0)
0x29, 0x65, // Usage Maximum (101)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x65, // Logical Maximum (101)
0x75, 0x08, // Report Size (8)
0x95, 0x06, // Report Count (6)
0x81, 0x00, // Input (Data,Ary,Abs)
0xC0 // End Collection
这段描述符定义了:键盘有8个修饰键(Ctrl、Shift等),每个占1位;还有6个普通按键,每个占1字节。主机读到这个,就知道每次收到的报告里,前1个字节是修饰键状态,后面6个字节是按键码。
我的经验:刚开始写Report Descriptor时,我建议你从简单的设备开始。比如先做一个单按键的鼠标,描述符只有十几行。等跑通了,再慢慢加功能。我曾经一口气写了个104键全键盘的描述符,结果调试了三天才发现有个位域定义错了。
避坑指南
最后,分享几个我踩过的坑:
- Report ID:如果你的设备有多个报告(比如键盘和鼠标功能合在一个设备里),一定要加Report ID。否则主机分不清哪个数据是键盘的,哪个是鼠标的。
- 报告长度:描述符里定义的Report Size和Report Count,必须和你实际发送的数据长度一致。多一个字节或少一个字节,主机都会报错。
- Usage Page:不要随便用。比如键盘要用Generic Desktop和Keyboard页,鼠标要用Generic Desktop和Pointer页。混用了,主机可能不识别。
好了,这一章的内容就到这里。HID协议看起来复杂,但只要你理解了「描述符就是说明书」这个本质,后面就好办了。下一章,咱们会手把手写一个最简单的HID Report Descriptor,然后把它烧进开发板里跑起来。