写在前面
前段时间在搞一个通过BLE HID刷抖音的功能,看了许多蓝牙的spec和博文,暂时没有发现一篇博文能够全面解答我在开发过程中遇到的问题,所以写下这篇博文,相对全面地整理自己在学习过程中遇到的问题,希望能给学习BLE HID的朋友们一些帮助。
一、简介
人机接口设备协议(Human Interface Device Protocol)是一个允许主机与外设设备(并非嵌入式设备常说的外设)之间进行通信,并使用外设设备对主机进行控制的协议,它最先被使用在通过USB设备通信的设备上。当出现了蓝牙协议后,HID又被移植到了蓝牙设备上,使得蓝牙设备间可以通过HID来相互通信,实现Device对Host的控制。本文所介绍的HID实际上是HID Over GATT Protocol(HOGP),即基于GATT的HID协议。
HOGP中定义了三种角色:Report Host,Boot Host和Device。其中Device在GATT中做server,Host在GATT中做client。它们的区别是:
- Device:可以上报事件,即发送report;
- Report Host:需要支持HID Parser,即有解析任意report的能力(前提是Report Map中要有定义);
- Boot Host:不需要支持HID Parser,因为数据传输格式已经在Boot Protocol Mode中预先定义过了。


二、服务配置
HIDS
HIDS包含以下characteristics:
在Spec中,HOGP要求设备在支持HID Service的同时也支持DIP中的PnP ID和BATT服务,但在实际使用中,多数Host并未严格要求。
HID Information
bcdHID
是支持的USB HID协议版本,目前最新的是1.11版本,所以这个字段应填写0x0111
。bCountryCode
是HID设备所在的国家,这个字段没啥用,大家一般都填0。Flags
是标志位,bit0代表唤醒能力,即Device是否能够远程唤醒Host,一般为1;bit1代表连接模式,即HID设备在已绑定但未连接时是否会发送BLE广播,一般为1。
HID Control Point
Control Point是一个Host用来通知Device自身状态的characteristic,它表示Host的待机状态,但实际应用中似乎并未被使用。
Protocol Mode
Protocol Mode被用于告知Host本机处于哪种模式,即Boot Protocol Mode或Report Protocol Mode,Host既可以Read也可以Write Command这个characteristic。
Report
Report是Device用来上报数据的characteristic,它共有三种类型:
其中Input用于Device上报数据,Output用于Host下发数据,Feature的数据一般是静态的,一般只在ATT连接时有交互。
Input Report都需要有Client Characteristic Confident Descriptor和Report Reference Characteristic Descriptor,其中Report Reference Characteristic Descriptor中含有对应report的id和type,当Host来write CCCD时,Device应该回复report id和report type。所以每一个input或output id都应对应一个report characteristic。
Report Map
Report Map是HID中最复杂也最重要的部分,它决定了Device能实现怎样的功能。USB组织有专用工具可以生成Report Map:HID Descriptor Tool | USB-IF。Report Map完全移植了USB HID的report map,Report Map由若干Item组成,每条Item的第一个字节表明了数据的属性,包括Item Type,Item Tag和Item Size,Type表明数据类型,Tag表明功能,Size表明长度。
Item有三大类Main、Global和Local,由Tag和Type区分:
Main类项目用于定义报表描述符中的数据项。也可以组合其中的若干数据项成为一个集合。Main项目可以分为带数据的Main项目和不带数据的Main项目。带数据项的Main用于生成报表中的数据项,包括Input、Output 和Feature 项目。不带数据的Main项目不生成报表中的数据项,包括Collection和End Collection项目。
Global类项目实现对数据的描述,用来识别报表并且描述报表内的数据,包括数据的功能、最大与最小允许值以及数据项的大小与数目等。改变由Main类项目生成的项目状态表。Global类项目描述对后续的所有项目有效,除非遇到有新的Global类项目。
Local类项目定义控制的特征,这一类项目的作用域不超过下一个Main项目,所以在每一Main项目之前可能有多个Local项目。Local项目用于描述后面的Input、Output和Feature项目。
例如0x85, 0x03
,其二进制表示为:
0b100001对应report id,0b01代表后续数据为1个字节
Input、Output、Feature
在每一个Input、Output和Feature项目的前缀字之后是32位描述数据,目前最多定义了9个bit,其余的bit则是保留。bit0~8 的定义中只有bit7不能应用于Input 项目,除此之外其他的位定义都适应于Input、Output和Feature项目。一般来说一个Input、Output或Feature应该和一个Usage是对应的,但也允许没有Usage的Input、Output和Feature存在,这种是匿名的项目,一般用做占位符,保证数据对齐。
Collection、End Collection
collection和end collection用于将多个Input、output或feature项目组合在一起(其实可以理解为一个report就是一个结构体,而collection和end collection就是大括号 “{” 和 “}” )。像C/C++一样,这个“结构体”也允许“嵌套定义”,即collection中也可以有其它collection。
Usage Page、Usage
Usage Page可以理解为一级菜单,Usage为具体用途项。例如0x05, 0x0C, 0x09, 0x01
,首先选择了Consumer这个Usage Page,再选择了Consumer下面的0x01
Consumer Control这个用途。
Report Size、Report Count
Report Size是指一个Input、Output或Feature所占的数据大小,单位是bit。Report Count是指数据区域的个数。例如Report Count为3,Report Size为1,表示三个1 bit数据。
需要注意的是Usage数量和Report Count的关系
usage_num == report_count
:这种情况比较简单和常见,每个Usage占据Report Size大小的数据位即可;usage_num < report_count
:这种情况下,前面的Usage分别占据Report Size大小的数据位,最后一个Usage占据剩余所有的数据位。例如:
0x75, 0x01, // Report Size (1)
0x95, 0x0B, // Report Count (11)
0x09, 0xEA, // Usage (Volume Decrement)
0x09, 0xE9, // Usage (Volume Increment)
0x0A, 0xAE, 0x01, // Usage (AL Keyboard Layout)
Volume Decrement
和Volume Increment
分别占1 bit,AL Keyboard Layout
占9 bits。
usage_num > report_count
:这种情况非常少见,而且不是一种标准的用法。目前只知道在Report Count为1且Usage数量大于1时,多个Usage会共用一个数据位。等我后面研究了HID Host之后再完善这种用法的说明。
一个Report Map
static const uint8_t report_map_data[] =
{
0x05, 0x0C, // Usage Page (Consumer)
0x09, 0x01, // Usage (Consumer Control)
0xA1, 0x01, // Collection (Application)
0x85, 0x03, // Report ID (3) keyboard
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x0B, // Report Count (11)
0x09, 0xEA, // Usage (Volume Decrement)
0x09, 0xE9, // Usage (Volume Increment)
0x0A, 0xAE, 0x01, // Usage (AL Keyboard Layout)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, // Report Count (1)
0x75, 0x0D, // Report Size (13)
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0x05, 0x0D, // Usage Page (Digitizer)
0x09, 0x04, // Usage (Touch)
0xA1, 0x01, // Collection (Application)
0x85, 0x02, // Report ID (2)
0x09, 0x22, // Usage (Finger)
0xA1, 0x02, // Collection (Logical)
0x09, 0x42, // Usage (Tip Switch)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x01, // Report Count (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0x32, // Usage (In Range)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x06, // Report Count (6)
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x08, // Report Size (8)
0x09, 0x51, // Usage (0x51)
0x95, 0x01, // Report Count (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x26, 0xFF, 0x0F, // Logical Maximum (4095)
0x75, 0x10, // Report Size (16)
0x55, 0x0E, // Unit Exponent (-2)
0x65, 0x33, // Unit (System: English Linear, Length: Inch)
0x09, 0x30, // Usage (X)
0x35, 0x00, // Physical Minimum (0)
0x46, 0xB5, 0x04, // Physical Maximum (1205)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x46, 0x8A, 0x03, // Physical Maximum (906)
0x09, 0x31, // Usage (Y)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0x05, 0x0D, // Usage Page (Digitizer)
0x09, 0x54, // Usage (0x54)
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x85, 0x08, // Report ID (8)
0x09, 0x55, // Usage (0x55)
0x25, 0x05, // Logical Maximum (5)
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0, // End Collection
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x02, // Usage (Mouse)
0xA1, 0x01, // Collection (Application)
0x85, 0x04, // Report ID (4)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (0x01)
0x29, 0x05, // Usage Maximum (0x05)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, // Report Count (1)
0x75, 0x03, // Report Size (3)
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x08, // Report Size (8)
0x95, 0x01, // Report Count (1)
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x38, // Usage (Wheel)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7F, // Logical Maximum (127)
0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x0C, // Usage Page (Consumer)
0x0A, 0x38, 0x02, // Usage (AC Pan)
0x95, 0x01, // Report Count (1)
0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0x85, 0x05, // Report ID (5)
0x09, 0x01, // Usage (Consumer Control)
0xA1, 0x00, // Collection (Physical)
0x75, 0x0C, // Report Size (12)
0x95, 0x02, // Report Count (2)
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x16, 0x01, 0xF8, // Logical Minimum (-2047)
0x26, 0xFF, 0x07, // Logical Maximum (2047)
0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0xC0, // End Collection
};
参考资料
开源专题(3) - 使用GR533x BLE开发板刷抖音
HID报表描述符(目前最全的解析,也是USB最复杂的描述符)
Human Interface Device Profile 1.1.1
Human Interface Device Service 1.0
Assign Numbers
GATT Specification Supplement
Device Class Definition for HID 1.11
HID Usage Tables 1.5