HID 人机交互应用框架
HID(Human Interface Device,人机交互设备)应用框架是 AC63 系列蓝牙 SoC SDK 中用于实现键盘、鼠标、遥控器等输入设备能力的完整应用层框架,统一覆盖 EDR(经典蓝牙 HID Device Profile)、USB HID 设备/主机、BLE HID over GATT 以及杰理私有 RCSP 协议对 HID 的互操作支持。
Purpose and Scope
本文档介绍 apps/hid 应用工程及其依赖的公共 HID 组件,说明 HID 应用框架的整体结构、数据通路、核心模块职责与扩展方式,内容包括:
- EDR 经典蓝牙 HID 用户模块
edr_hid_user(协议消息、发送通路、连接管理); - USB HID 设备描述符与回调接口(
usb_hid_devices.c); - BLE HID GATT 服务 UUID 定义(
standard_hid.h); - HID 应用工程(
apps/hid)的板级工程与示例结构。
与 HID 相关的底层 USB 协议栈(apps/common/device/usb/device/hid.c、apps/common/device/usb/host/hid.c)与 RCSP 私有协议(rcsp_hid_inter.c)仅在本页作为边界引用,其完整细节属于 USB 协议栈与 RCSP 框架各自的主题页。BLE GATT 服务的整体框架请参见 BLE 应用框架相关页面。
Overview
在 AC63 蓝牙 SoC 上实现一个"HID 人机交互设备"通常需要同时打通多条通路:
- EDR 通路:经典蓝牙 HID Device Profile,让手机、平板、PC 等 Host 通过 SDP 发现设备并建立 HID 控制通道与中断通道,用于传输键盘/鼠标报告;
- USB 通路:SoC 作为 USB Device 枚举成 HID 设备(标准键盘示例),或作为 USB Host 读取外部 HID 设备;
- BLE 通路:通过 GATT 的 HID Service(UUID 0x1812)提供服务;
- 私有协议通路:杰理 RCSP 协议对 HID 事件的拦截与转发(用于 TWS 耳机等场景的 HID 同步)。
框架的核心设计意图是:上层应用只关心"报告内容",不关心"传输通道"。edr_hid_user 模块把 HID 协议封装为简洁的 API(edr_hid_data_send、user_hid_send_data 等),内部通过环形缓冲 + 忙标志 + 临界区保护,把应用线程与蓝牙协议栈线程之间的并发问题隔离在模块内部。
flowchart TD
subgraph sg_App["应用层 apps/hid"]
AppMain["app_main / 按键业务"]
KeyDeal["edr_hid_key_deal_test"]
end
subgraph sg_Framework["HID 框架层"]
EdrHid["edr_hid_user<br/>EDR HID Device Profile"]
UsbHidDev["usb_hid_devices.c<br/>USB HID 设备"]
BLEHid["standard_hid.h<br/>BLE HID GATT 0x1812"]
RcspHid["rcsp_hid_inter.c<br/>RCSP HID 互操作"]
end
subgraph sg_Stack["协议栈/驱动层"]
Avctp["btstack avctp_user"]
UsbDevHid["usb/device/hid.c"]
UsbHostHid["usb/host/hid.c"]
GattSrv["BLE GATT 服务"]
end
subgraph sg_Hw["硬件"]
BT["蓝牙控制器"]
USB["USB 控制器"]
end
AppMain --> EdrHid
AppMain --> UsbHidDev
KeyDeal --> EdrHid
EdrHid --> Avctp
EdrHid --> RcspHid
UsbHidDev --> UsbDevHid
UsbHostHid --> UsbHidDev
BLEHid --> GattSrv
Avctp --> BT
UsbDevHid --> USB
UsbHostHid --> USB
GattSrv --> BT
上图中,edr_hid_user 是 EDR 通路的核心封装,usb_hid_devices.c 是 USB 设备通路的示例实现,standard_hid.h 为 BLE 通路提供标准 UUID 定义,rcsp_hid_inter.c 将 HID 事件桥接到 RCSP 私有协议。应用层按键业务通过 edr_hid_key_deal_test 或直接调用发送 API 完成报告上报。
核心模块与实现
EDR HID 用户模块(edr_hid_user)
apps/hid/modules/bt/edr_hid_user.c 是框架中信息量最大的模块,其编译条件为 TCFG_USER_EDR_ENABLE && (USER_SUPPORT_PROFILE_HID == 1),即只有使能 EDR 且声明支持 HID Profile 时才参与编译。
HID 协议消息类型
模块按 HID 规范(Device Class Definition for HID)定义了消息类型常量:
/*message type*/ /*hex*/ /*sent by*/
#define HID_HANDSHAKE 0x00 /*Device*/
#define HID_CONTROL 0x10 /*Device&host*/
/**0x20,0x30 Reserved*/
#define HID_GET_REPORT 0x40 /*host*/
#define HID_SET_REPORT 0x50 /*host*/
#define HID_GET_PROTOCOL 0x60 /*host*/
#define HID_SET_PROTOCOL 0x70 /*host*/
#define HID_GET_IDLE 0x80 /*host DEPRECATED*/
#define HID_SET_IDLE 0x90 /*host DEPRECATED*/
#define HID_DATA 0xA0 /*Device&host*/
#define HID_DATC 0xB0 /*Device&host DEPRECATED*/
/*C-F Reserved*/
Source: edr_hid_user.c
这些类型对应 HID 规范中的 Handshake、Control、Get/Set Report、Get/Set Protocol、Get/Set Idle 与 Data 消息。设计上保留 0x20/0x30 为 Reserved、0xC0-0xF0 为 Reserved,以便后续扩展。HID_GET_IDLE/HID_SET_IDLE/HID_DATC 被标注为 DEPRECATED,反映 HID 规范演进中这些消息已不推荐使用,但协议层仍保留兼容。
报告数据类型
/*DATA*/
#define DATA_OTHER 0x00
#define DATA_INPUT 0x01
#define DATA_OUTPUT 0x02
#define DATA_FEATURE 0x03
Source: edr_hid_user.c
DATA_INPUT 对应设备上报给主机的输入报告(如按键、鼠标位移),DATA_OUTPUT/DATA_FEATURE 用于接收主机下发的输出/特性报告(如键盘 LED 状态)。
全局状态与缓冲
static u8 *report_map;
static u16 report_map_size;
#define HID_REPORT_MAP_DATA report_map
#define HID_REPORT_MAP_SIZE report_map_size
static void (*user_hid_send_wakeup)(void) = NULL;
static u16 hid_channel;//inter_channel
static u16 hid_ctrl_channel;//ctrl_channel
static volatile u8 hid_run = 0;
static volatile u8 is_hid_active = 0;
static volatile u8 hid_s_step = 0;
int hid_timer_id = 0;
#define HID_SEND_MAX_SIZE (16) //描述符数据包的长度
static volatile u8 bt_send_busy = 0;
void (*user_led_status_callback)(u8 *buffer, u16 size) = NULL;
#define HID_TMP_BUFSIZE (64*2)
#define cbuf_get_space(a) (a)->total_len
static cbuffer_t user_send_cbuf;
static u8 hid_tmp_buffer[HID_TMP_BUFSIZE];
Source: edr_hid_user.c
关键设计点:
report_map/report_map_size保存 HID Report Descriptor,由上层通过user_hid_set_ReportMap注入,SDP 初始化时使用(hid_sdp_init(hid_descriptor, size));hid_channel(中断通道)与hid_ctrl_channel(控制通道)是蓝牙协议栈返回的 L2CAP 通道号,HID 数据走中断通道,控制消息走控制通道;hid_run、is_hid_active、hid_s_step为volatile状态标志,用于跨线程(应用线程/协议栈线程)安全地判断模块运行与连接状态;bt_send_busy是发送忙标志,避免协议栈尚未确认上一包时再次发送导致丢包;user_send_cbuf是 128 字节(64*2)的环形发送缓冲,配合hid_tmp_buffer实现"先入队、后异步发送";HID_SEND_MAX_SIZE为 16 字节,即单个 HID 数据包(含 2 字节类型/ID 头)的最大长度,描述符数据包长度受限。
事件上报
模块通过统一的系统事件通道把协议栈事件转发给应用:
static void edr_bt_evnet_post(u32 arg_type, u8 priv_event, u8 *args, u32 value)
{
struct sys_event e;
e.type = SYS_BT_EVENT;
e.arg = (void *)arg_type;
e.u.bt.event = priv_event;
if (args) {
memcpy(e.u.bt.args, args, 7);
}
e.u.bt.value = value;
sys_event_notify(&e);
}
void sdp_callback_remote_type(u8 remote_type)
{
log_info("edr_hid:remote_type= %d\n", remote_type);
edr_bt_evnet_post(SYS_BT_EVENT_FORM_COMMON, COMMON_EVENT_EDR_REMOTE_TYPE, NULL, remote_type);
//to do
}
Source: edr_hid_user.c
edr_bt_evnet_post 构造 SYS_BT_EVENT 系统事件并调用 sys_event_notify,把蓝牙事件(如远端设备类型识别结果)投递到系统事件循环。sdp_callback_remote_type 在 SDP 完成后回调,识别远端手机系统(REMOTE_DEV_UNKNOWN/REMOTE_DEV_ANDROID/REMOTE_DEV_IOS),并以 COMMON_EVENT_EDR_REMOTE_TYPE 事件通知应用层——这为"根据手机系统切换 HID 报告内容"(例如 Android 与 iOS 对媒体键的兼容处理)提供了挂钩点。
发送数据通路(环形缓冲 + 忙标志)
发送通路是模块的"心脏"。其设计是典型的生产者-消费者 + 单飞(single-flight)发送模式:应用线程作为生产者把 HID 报告写入环形缓冲,协议栈发送完成回调作为消费者触发下一包发送;bt_send_busy 保证同一时刻只有一个包在协议栈中飞行。
static u16 user_data_read_sub(u8 *buf, u16 buf_size)
{
u16 ret_len;
if (0 == cbuf_get_data_size(&user_send_cbuf)) {
return 0;
}
OS_ENTER_CRITICAL();
cbuf_read(&user_send_cbuf, &ret_len, 2);
if (ret_len && ret_len <= buf_size) {
cbuf_read(&user_send_cbuf, buf, ret_len);
}
OS_EXIT_CRITICAL();
return ret_len;
}
static void user_data_try_send(void)
{
u16 send_len;
if (bt_send_busy) {
return;
}
bt_send_busy = 1;//hold
u8 tmp_send_buf[HID_SEND_MAX_SIZE];
send_len = user_data_read_sub(tmp_send_buf, HID_SEND_MAX_SIZE);
if (send_len) {
if (user_hid_send_data(tmp_send_buf, send_len)) {
bt_send_busy = 0;
}
} else {
//not send
bt_send_busy = 0;
}
}
static u32 user_data_write_sub(u8 *data, u16 len)
{
u16 wlen = 0;
u16 buf_space = cbuf_get_space(&user_send_cbuf) - cbuf_get_data_size(&user_send_cbuf);
if (len + 2 > buf_space) {
return 0;
}
OS_ENTER_CRITICAL();
wlen = cbuf_write(&user_send_cbuf, &len, 2);
wlen += cbuf_write(&user_send_cbuf, data, len);
OS_EXIT_CRITICAL();
user_data_try_send();
return wlen;
}
Source: edr_hid_user.c
实现要点:
user_data_write_sub先检查剩余空间是否足够(len + 2,多出的 2 字节用于保存长度头),空间不足直接返回 0,避免覆盖未发送数据;- 写入在
OS_ENTER_CRITICAL()/OS_EXIT_CRITICAL()临界区内完成,防止与读取侧竞争环形缓冲的读写指针; - 写入成功后立即调用
user_data_try_send()尝试发送; user_data_try_send首先检查bt_send_busy,忙则返回(数据留在缓冲中等下一轮触发);否则置忙并取出一包,调用user_hid_send_data(底层 L2CAP 中断通道发送);返回真表示发送完成、清忙,可继续发下一包;- 环形缓冲条目格式为
[2 字节长度][数据],长度由user_data_read_sub读出后再按长度读取数据,两次读取均在临界区内完成,保证读取的原子性。
这套机制解决了两个典型并发问题:应用线程高频上报按键时不会阻塞(最多占满 128 字节缓冲即返回 0,由上层决定丢弃策略);协议栈慢速发送时不会乱序(单飞模式保证 FIFO 顺序)。
HID 数据包格式与发送 API
发送时应用层可选用两个入口:
int edr_hid_data_send(u8 report_id, u8 *data, u16 len);
int edr_hid_data_send_ext(u8 report_type, u8 report_id, u8 *data, u16 len);
Source: edr_hid_user.h
数据包内部结构定义如下,report_type 与 report_id 各占 1 字节头,随后为最多 HID_SEND_MAX_SIZE - 2 = 14 字节的报告数据:
typedef struct {
u8 report_type;
u8 report_id;
u8 data[HID_SEND_MAX_SIZE - 2];
} hid_data_info_t;
Source: edr_hid_user.c
edr_hid_data_send 默认按 DATA_INPUT 类型组包,edr_hid_data_send_ext 允许显式指定报告类型(DATA_OTHER/DATA_INPUT/DATA_OUTPUT/DATA_FEATURE)。这两个 API 是应用层与 EDR HID 交互的主要入口,调用后数据进入发送通路(写入环形缓冲并触发 user_data_try_send)。
模块生命周期 API
edr_hid_user.h 声明的完整接口如下:
enum {
HID_USER_ERR_NONE = 0x0,
HID_USER_ERR_DONE,
HID_USER_ERR_SEND_FAIL,
};
void user_hid_init(void (*user_hid_output_handler)(u8 *packet, u16 size, u16 channel));
void user_hid_exit(void);
void user_hid_enable(u8 en);
int user_hid_send_data(u8 *buf, u32 len);
void user_hid_disconnect(void);
void user_hid_set_icon(u32 class_type);
void user_hid_set_ReportMap(u8 *map, u16 size);
int edr_hid_data_send(u8 report_id, u8 *data, u16 len);
int edr_hid_data_send_ext(u8 report_type, u8 report_id, u8 *data, u16 len);
void edr_hid_key_deal_test(u16 key_msg);
int edr_hid_is_connected(void);
int edr_hid_tx_buff_is_ok(void);
Source: edr_hid_user.h
各 API 职责:
| 函数 | 职责 |
|---|---|
user_hid_init | 初始化模块,注册输出回调(接收主机下发数据的 handler,含通道号) |
user_hid_exit | 反初始化,释放资源 |
user_hid_enable(en) | 使能/禁止 HID 功能 |
user_hid_send_data | 直接发送一包 HID 数据(协议栈层) |
user_hid_disconnect | 主动断开 HID 连接 |
user_hid_set_icon | 设置设备图标类型(class type,用于 SDP 记录) |
user_hid_set_ReportMap | 注入 HID Report Descriptor |
edr_hid_data_send / edr_hid_data_send_ext | 应用层上报报告(见上文) |
edr_hid_key_deal_test | 测试用按键处理入口 |
edr_hid_is_connected | 查询当前 HID 是否已连接 |
edr_hid_tx_buff_is_ok | 查询发送缓冲是否可写(用于背压控制) |
错误码约定:HID_USER_ERR_NONE(0x0) 表示成功,HID_USER_ERR_DONE 表示处理完成,HID_USER_ERR_SEND_FAIL 表示发送失败。
Core Flow:EDR HID 数据上报时序
sequenceDiagram
participant App as 应用按键任务
participant Edr as edr_hid_user
participant Cbuf as user_send_cbuf 环形缓冲
participant Stack as 蓝牙协议栈 (L2CAP)
participant Host as HID Host (手机/PC)
App->>Edr: edr_hid_data_send(report_id, data, len)
Edr->>Cbuf: user_data_write_sub (临界区写 len+data)
Edr->>Edr: user_data_try_send()
alt bt_send_busy == 0
Edr->>Cbuf: user_data_read_sub 取一包
Edr->>Stack: user_hid_send_data(packet, len)
Stack-->>Host: 中断通道发送 HID 报告
Stack-->>Edr: 发送完成回调
Edr->>Edr: bt_send_busy = 0,继续取下一包
else bt_send_busy == 1
Edr->>Edr: 数据留在缓冲,等待回调触发
end
Host-->>Edr: 主机下发 OUTPUT 报告
Edr->>App: user_hid_output_handler(packet, size, channel)
发送路径的关键约束是 HID_SEND_MAX_SIZE = 16:一次上报最多携带 14 字节有效数据,超过部分需要上层自行分帧;环形缓冲满时 user_data_write_sub 返回 0,上层应通过 edr_hid_tx_buff_is_ok 做背压判断,或采用覆盖最新报告的策略。
USB HID 设备通路(usb_hid_devices.c)
apps/hid/examples/standard_keyboard/usb_hid_devices.c 演示了 SoC 作为 USB Device 枚举成标准键盘 HID 设备的实现。其编译条件为 CONFIG_APP_STANDARD_KEYBOARD && TCFG_PC_ENABLE && TCFG_USB_SLAVE_USER_HID,即标准键盘应用 + 使能 PC(USB 枚举)+ 使能 USB 从机 HID 时才编译。
描述符定义
static const u8 sHIDDescriptor[] = {
//InterfaceDeszcriptor:
USB_DT_INTERFACE_SIZE, // Length
USB_DT_INTERFACE, // DescriptorType
0x00, // bInterface number
0x00, // AlternateSetting
0x02, // NumEndpoint
USB_CLASS_HID, // Class = Human Interface Device
0x00, // Subclass, 0 No subclass, 1 Boot Interface subclass
0x00, // Procotol, 0 None, 1 Keyboard, 2 Mouse
0x00, // Interface Name
//HIDDescriptor:
0x09, // bLength
USB_HID_DT_HID, // bDescriptorType, HID Descriptor
0x01, 0x02, // bcdHID, HID Class Specification release NO.
0x00, // bCuntryCode, Country localization (=none)
0x01, // bNumDescriptors, Number of descriptors to follow
0x22, // bDescriptorType, Report Desc. 0x22, Physical Desc. 0x23
0, // LOW(ReportLength)
0, // HIGH(ReportLength)
//EndpointDescriptor:
USB_DT_ENDPOINT_SIZE, // bLength
USB_DT_ENDPOINT, // bDescriptorType, Type
USB_DIR_IN | HID_EP_IN, // bEndpointAddress
USB_ENDPOINT_XFER_INT, // Interrupt
LOBYTE(MAXP_SIZE_HIDIN), HIBYTE(MAXP_SIZE_HIDIN), // Maximum packet size
0x01, // Poll every 10msec seconds
//Endpoint Descriptor:
USB_DT_ENDPOINT_SIZE, // bLength
USB_DT_ENDPOINT, // bDescriptorType, Type
USB_DIR_OUT | HID_EP_OUT, // bEndpointAddress
USB_ENDPOINT_XFER_INT, // Interrupt
LOBYTE(MAXP_SIZE_HIDOUT), HIBYTE(MAXP_SIZE_HIDOUT), // Maximum packet size
0x01, // bInterval
};
Source: usb_hid_devices.c
描述符定义了 2 个中断端点(IN 用于键盘报告上报,OUT 用于接收主机下发数据),MAXP_SIZE_HIDIN/MAXP_SIZE_HIDOUT 为端点最大包长宏,bInterval=0x01 表示每 1ms 轮询一次(全速设备轮询间隔单位)。
报告映射与回调注册
static const u8 *hid_report_map;
static int hid_report_map_size = 0;
void usb_hid_set_repport_map(const u8 *map, int size)
{
hid_report_map_size = size;
hid_report_map = map;
}
static void (*usb_hid_state_ck)(u8 state) = NULL;
static void hid_state_callback(u8 state)
{
if (usb_hid_state_ck) {
usb_hid_state_ck(state);
}
}
void usb_hid_register_state_callback(void *callback)
{
usb_hid_state_ck = callback;
}
static void (*usb_hid_output_callback)(u8 *buffer, u16 size) = NULL;
void usb_hid_set_output_callback(void *cb)
{
usb_hid_output_callback = cb;
}
Source: usb_hid_devices.c
usb_hid_set_repport_map注入 Report Descriptor,get_hid_report_desc_len/get_hid_report_desc供 USB 协议栈按需取描述符;usb_hid_register_state_callback注册枚举/连接状态回调(usb_hid_state_ck);usb_hid_set_output_callback注册输出报告回调(接收主机下发的键盘 LED 等报告)。
这种"设置回调 + 设置报告映射"的接口风格与 EDR 侧的 user_hid_init/user_hid_set_ReportMap 对称,便于应用层用同一套按键业务逻辑同时驱动蓝牙与 USB 两条通路。
BLE HID 通路(standard_hid.h)
apps/common/include/standard_hid.h 集中定义了 BLE HID Service 的 16-bit UUID:
#define HID_UUID_16 0x1812
#define HID_INFORMATION_UUID_16 0x2A4A
#define HID_REPORT_MAP_UUID_16 0x2A4B
#define HID_CONTROL_POINT_UUID_16 0x2A4C
#define HID_REPORT_UUID_16 0x2A4D
#define PROTOCOL_MODE_UUID_16 0x2A4E
#define HID_REPORT_REFERENCE_UUID_16 0x2908
Source: standard_hid.h
这些 UUID 完全遵循 Bluetooth SIG 的 HID over GATT Profile(HOGP)规范:0x1812 为 HID Service,0x2A4A~0x2A4E 分别为 HID Information、Report Map、Control Point、Report、Protocol Mode 特征,0x2908 为 Report Reference 描述符。BLE 通路使用同一套 Report Descriptor(user_hid_set_ReportMap 注入的内容),实现了"一份报告描述、多通路复用"的框架目标。
RCSP HID 互操作(边界引用)
apps/common/third_party_profile/jieli/JL_rcsp/bt_trans_data/rcsp_hid_inter.c/.h 负责把 HID 事件桥接到杰理私有 RCSP 协议,典型场景是 TWS/音箱等设备通过 RCSP 通道同步 HID 报告(如耳机按键控制手机媒体播放)。该模块与 edr_hid_user 属于不同能力边界,其细节请参见 RCSP 框架相关页面。
应用工程与板级配置
apps/hid 目录包含完整应用工程结构:
| 路径 | 说明 |
|---|---|
apps/hid/board/bd19/AC632N_hid.cbp | AC632N 芯片 HID 工程 |
apps/hid/board/bd29/AC631N_hid.cbp | AC631N 芯片 HID 工程 |
apps/hid/board/br23/AC635N_hid.cbp | AC635N 芯片 HID 工程 |
apps/hid/board/br25/AC636N_hid.cbp | AC636N 芯片 HID 工程 |
apps/hid/board/br30/AC637N_hid.cbp | AC637N 芯片 HID 工程 |
apps/hid/board/br34/AC638N_hid.cbp | AC638N 芯片 HID 工程 |
apps/hid/examples/standard_keyboard/ | 标准键盘示例(USB + HID 报告) |
apps/hid/include/edr_hid_user.h | EDR HID 模块对外接口 |
apps/hid/modules/bt/edr_hid_user.c | EDR HID 模块实现 |
同一套 edr_hid_user 也被复用到了其他应用工程(如 apps/spp_and_le/examples/dongle/ 的 dongle 模式),说明框架把 HID 能力做成可移植模块,不同产品只需配置 USER_SUPPORT_PROFILE_HID 等宏即可引入。
Usage Examples
示例一:初始化 EDR HID 并注入报告描述符
// 初始化 HID,注册主机下发数据的输出回调
user_hid_init(user_hid_output_handler);
// 注入 HID Report Descriptor(SDP 与协议栈使用)
user_hid_set_ReportMap(hid_report_map, hid_report_map_size);
// 使能 HID
user_hid_enable(1);
Source: edr_hid_user.h
示例二:上报键盘/鼠标输入报告
// 上报 Input 报告(report_id = 1,数据为按键码数组)
u8 key_data[6] = {0, 0, 0, 0, 0, 0};
edr_hid_data_send(1, key_data, sizeof(key_data));
// 显式指定报告类型上报
edr_hid_data_send_ext(DATA_INPUT, 1, key_data, sizeof(key_data));
Source: edr_hid_user.h
示例三:USB 侧设置报告映射与回调
void usb_hid_set_repport_map(const u8 *map, int size);
void usb_hid_register_state_callback(void *callback);
void usb_hid_set_output_callback(void *cb);
Source: usb_hid_devices.c
Configuration Options
HID 框架的编译期配置宏:
| 宏 | 取值 | 默认行为 | 说明 |
|---|---|---|---|
TCFG_USER_EDR_ENABLE | 0/1 | 由 board 配置 | 使能 EDR 蓝牙;edr_hid_user.c 编译前提之一 |
USER_SUPPORT_PROFILE_HID | 0/1 | 0 | 声明支持 HID Profile;edr_hid_user.c 编译前提之二 |
CONFIG_APP_STANDARD_KEYBOARD | 0/1 | 0 | 标准键盘应用使能,编译 USB HID 设备示例 |
TCFG_PC_ENABLE | 0/1 | 由 board 配置 | 使能 USB PC 枚举 |
TCFG_USB_SLAVE_USER_HID | 0/1 | 0 | 使能 USB 从机 HID 类 |
TEST_USER_HID_EN | 0/1 | 0 | 测试模式开关(edr_hid_user.c 内定义) |
HID_SEND_MAX_SIZE | 16 | 16 | 单包 HID 数据最大长度(含 2 字节头) |
HID_TMP_BUFSIZE | 128 | 128 | 发送环形缓冲容量(64*2) |
注:以上宏的具体默认值由各 board 的 app_config.h/board_*.h 决定,编译时以实际配置为准。
Failure Modes、边界与并发
发送缓冲满(背压)
user_data_write_sub 在剩余空间不足 len + 2 时直接返回 0,不阻塞、不覆盖。这意味着高频上报时数据可能被静默丢弃。设计意图是:HID 报告是"最新状态"语义(如按键当前状态、鼠标相对位移),中间帧丢失比阻塞应用线程更可接受。应用层若需保证可靠性,应先调用 edr_hid_tx_buff_is_ok() 查询缓冲状态,再决定是否丢弃或合并报告。
发送忙竞争
bt_send_busy 是 volatile 标志,读写不在临界区内(仅环形缓冲的读写指针在临界区内)。这属于"宽松的单飞"实现:user_data_try_send 只在应用线程(写入侧)调用,而发送完成回调可能来自协议栈线程。若两个线程同时进入 user_data_try_send,理论上可能出现双发;实际中发送完成回调触发的重发逻辑与写入侧触发路径在 SDK 调度下由同一上下文串行化,因此该实现依赖"单线程驱动发送"的约束。扩展时若引入多线程直接调用发送 API,需自行加锁保护 bt_send_busy。
报告描述符未注入
get_hid_report_desc_len 在 hid_report_map_size == NULL 时会触发 ASSERT(0, "report map err")。因此 user_hid_set_ReportMap/usb_hid_set_repport_map 必须先于枚举/连接流程调用,否则协议栈取描述符时直接断言复位。
单包长度限制
HID_SEND_MAX_SIZE = 16 意味着单次最多发送 14 字节数据。超过 14 字节的报告(如 6KRO 键盘 + 修饰键 + 厂商自定义扩展)必须由上层自行分帧为多个 HID 报告或压缩为短报告,模块不做自动分包。
连接状态与异常断开
edr_hid_is_connected() 与 hid_run/is_hid_active 标志用于查询连接状态。协议栈异常断开时,模块依赖协议栈回调(SDP 完成、通道关闭)复位状态;应用层应在 COMMON_EVENT_EDR_REMOTE_TYPE 等事件中处理重连与 UI 状态刷新。
Performance 与运维
- 发送热路径:
user_data_write_sub → user_data_try_send → user_hid_send_data,一次上报在无竞争时开销极低(两次cbuf操作 + 一次协议栈调用);临界区仅覆盖环形缓冲读写,粒度最小化。 - 缓冲容量:
HID_TMP_BUFSIZE = 128字节(约 7 个 16 字节包),对键盘连发场景足够;高频鼠标报告(如游戏场景 1000Hz 报点)建议评估背压,必要时增大HID_TMP_BUFSIZE。 - 日志开关:模块日志由
#if 1开关控制,可切换为y_printf或关闭,便于量产时降低打印开销;log_info_hexdump通过put_buf输出原始数据,调试协议问题时非常有用。 - 测试钩子:
TEST_USER_HID_EN与edr_hid_key_deal_test提供按键注入测试入口,可在不接真实按键矩阵的情况下验证 HID 上报链路。
Extension Points
- 新增 HID 报告类型:基于
hid_data_info_t(report_type+report_id+ 数据)扩展,通过edr_hid_data_send_ext上报DATA_OUTPUT/DATA_FEATURE,实现与主机的双向交互(如接收键盘 LED、消费者控制反馈)。 - 多通路复用:同一 Report Descriptor 通过
user_hid_set_ReportMap(EDR)与usb_hid_set_repport_map(USB)分别注入,应用层按键业务可同时驱动蓝牙与 USB 通路。 - 设备类型识别:
sdp_callback_remote_type的COMMON_EVENT_EDR_REMOTE_TYPE事件可扩展为"根据 Android/iOS 切换按键映射"的产品逻辑。 - LED/状态回调:
user_led_status_callback与usb_hid_state_callback分别挂接蓝牙侧与 USB 侧的 LED 指示与枚举状态,可扩展为呼吸灯、连接提示等 UI 行为。 - RCSP 桥接:
rcsp_hid_inter为私有协议桥接点,TWS 主从同步 HID 事件时在此扩展。
Related Links
- edr_hid_user.h(接口定义)
- edr_hid_user.c(EDR HID 实现)
- standard_hid.h(BLE HID UUID)
- usb_hid_devices.c(USB HID 设备示例)
- USB 设备 HID 类实现
- USB 主机 HID 类实现
- RCSP HID 互操作
- 相关框架页面:USB 协议栈、BLE GATT 服务框架、RCSP 框架、按键扫描框架(adkey/iokey/irkey)