HID 示例工程(键盘/鼠标/遥控器/手柄)
本文档介绍 fw-AC63_BT_SDK 中 apps/hid 目录下的 HID(Human Interface Device)示例工程体系,覆盖经典蓝牙(EDR)HID Profile、BLE HOGP(HID over GATT)以及 USB HID 设备三种传输通道上的键盘、鼠标、遥控器、手柄等示例工程的实现方式、公共模块接口与配置方法。
Purpose and Scope
本页聚焦于 apps/hid 应用工程及其示例代码,内容包括:
apps/hid/examples/下各示例工程(standard_keyboard、keyboard、mouse_single、mouse_dual、voice_remote_control、gamebox、keyfob、keypage、idle)的定位与差异;apps/hid/modules/下 HID 公共模块(edr_hid_user.c经典蓝牙 HID、ble_hogp.cBLE HOGP)的对外接口;apps/hid/board/各芯片平台 Makefile 的编译组织方式;- USB HID 设备侧的实现(描述符、中断端点、上报回调)。
以下内容属于其他页面,本文仅作交叉指引,不展开:RCSP 私有协议(JL_rcsp/rcsp_hid_inter.c)属于 RCSP 协议主题;音频播放链路属于音频示例主题;apps/spp_and_le 下的 dongle 工程属于 SPP/LE 透传主题。
Overview
HID(Human Interface Device)是蓝牙与 USB 生态中最常用的人机交互设备类别。本 SDK 中一个 HID 设备(例如标准键盘)可以同时支持三种上报通道:
- 经典蓝牙 EDR HID:基于 Bluetooth HID Profile,通过 SDP 暴露 HID 服务,使用 L2CAP 控制/中断通道传输 Report;
- BLE HOGP:基于 HID over GATT Profile,通过 GATT 的 HID Service(0x1812)与 Report Characteristic 传输 Report;
- USB HID:设备枚举为 USB HID 类,通过中断端点(Interrupt IN/OUT)收发 Report,典型场景是 2.4G 接收器(USB Dongle)与 PC 连接。
应用层通过统一的"Report 上报"抽象(report_id + data + len)向三条通道发送数据,由 edr_hid_user.c、ble_hogp.c 与 USB 设备栈各自完成协议封装。这种"应用层统一、传输层分离"的设计使同一个键盘矩阵、同一个按键事件处理逻辑可以无差别地运行在蓝牙或 USB 模式下。
apps/hid 目录下的示例工程覆盖了市面上主流 HID 产品形态:
| 示例工程 | 产品形态 | 主要传输通道 |
|---|---|---|
standard_keyboard | 标准键盘(全键矩阵 + FN 组合键) | EDR HID / BLE HOGP / USB HID |
keyboard | 基础键盘 | EDR HID / BLE HOGP |
mouse_single | 单模鼠标 | BLE HOGP |
mouse_dual | 双模(2.4G/BLE)鼠标 | BLE HOGP / USB |
voice_remote_control | 语音遥控器(含音频编解码) | EDR HID |
gamebox | 手柄/游戏盒 | EDR HID |
keyfob | 遥控钥匙扣 | EDR HID |
keypage | 翻页笔/演示器 | BLE HOGP |
idle | 空工程模板 | — |
Architecture
下图展示 apps/hid 示例工程的整体架构:应用示例位于顶层,通过 apps/hid/modules 中的 EDR/BLE HID 公共模块接入协议栈,USB 通道则由公共组件 apps/common/device/usb 提供,HID Report 描述符与键值定义统一来自 apps/common/include/standard_hid.h 与 apps/common/device/usb/host/usb_hid_keys.h。
flowchart TD
subgraph sg_Examples["示例应用 apps/hid/examples"]
SK["standard_keyboard"]
KB["keyboard"]
MS["mouse_single / mouse_dual"]
RC["voice_remote_control"]
GB["gamebox"]
KF["keyfob / keypage"]
end
subgraph sg_Modules["HID 公共模块 apps/hid/modules"]
EDR["edr_hid_user.c(EDR HID Profile)"]
HOGP["ble_hogp.c(BLE HOGP)"]
end
subgraph sg_Common["公共组件 apps/common"]
USBDEV["usb/device/hid.c(USB 设备类)"]
STD["include/standard_hid.h(Report 定义)"]
KEYS["usb/host/usb_hid_keys.h(键值定义)"]
RCSP["JL_rcsp/bt_trans_data/rcsp_hid_inter.c"]
end
subgraph sg_Board["板级编译 apps/hid/board"]
MK["bd19 / bd29 / br23 / br25 / br30 / br34 Makefile"]
end
SK --> EDR
SK --> HOGP
SK --> USBDEV
KB --> EDR
KB --> HOGP
MS --> HOGP
RC --> EDR
GB --> EDR
KF --> EDR
KF --> HOGP
EDR --> STD
HOGP --> STD
USBDEV --> STD
EDR --> RCSP
MK --> SK
MK --> RC
各层职责与连接关系:
- 应用示例层:负责按键扫描/事件处理、设备状态机(配对、回连、关机)、以及把按键事件封装成 HID Report 后调用
hid_report_send分发到各通道; - HID 公共模块层:
edr_hid_user.c封装经典蓝牙 HID Profile 的初始化、Report Map 下发、中断通道发送;ble_hogp.c封装 BLE 端 HID Service 与上报;两者对外接口风格一致(*_hid_data_send(report_id, data, len)),便于应用层切换; - 公共组件层:
standard_hid.h定义标准 HID Report 描述符与报告 ID,usb_hid_keys.h定义键盘键值宏(_KEY_Q、_KEY_MOD_LCTRL等),USB 设备栈提供中断端点收发与枚举描述符; - 板级编译层:每个芯片平台(bd19=AC632N、bd29=AC631N、br23=AC635N、br25=AC636N、br30=AC637N、br34=AC638N)的 Makefile 以宏开关选择示例工程并链接对应源文件。
示例工程与编译组织
apps/hid/board 下每个平台目录包含独立 Makefile,通过 CONFIG_APP_XXX 宏选择编译哪个示例,并显式列出参与编译的源文件。以 bd19/Makefile 为例,它同时链接了标准键盘示例与语音遥控器示例的源文件,并包含 RCSP 协议交互模块:
../../../../apps/hid/examples/standard_keyboard/app_standard_keyboard.c \
../../../../apps/hid/examples/standard_keyboard/usb_hid_devices.c \
../../../../apps/hid/examples/voice_remote_control/app_remote_control.c \
../../../../apps/hid/modules/bt/edr_hid_user.c \
../../../../apps/hid/modules/bt/ble_hogp.c \
../../../../apps/hid/modules/misc.c \
Source: apps/hid/board/bd19/Makefile
Makefile 中的编译宏(如 CONFIG_HID_CASE_ENABLE、CONFIG_TWS_ENABLE、CONFIG_TRANSFER_ENABLE)决定设备特性开关;CONFIG_HID_CASE_ENABLE 使能 HID 充电仓(case)相关逻辑,CONFIG_TRANSFER_ENABLE 使能数据传输。换平台时只需替换 board 目录,示例代码本身跨平台复用。
EDR HID 用户模块
经典蓝牙侧 HID 的对外接口统一在 apps/hid/include/edr_hid_user.h 中声明,是示例工程与 EDR HID Profile 之间的唯一契约。接口设计上刻意保持与 BLE 侧 ble_hid_data_send 相似,让应用层可以用同一套 report_id + data + len 逻辑同时驱动两条蓝牙通道:
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: apps/hid/include/edr_hid_user.h
设计要点:
user_hid_init注册 Output 回调:HID 对端(主机)可能通过控制通道下发 Output Report(例如键盘 LED 状态、鼠标特性配置),该回调把数据交回应用层处理;user_hid_set_ReportMap下发 Report 描述符:必须先于连接建立前调用,主机枚举 HID 服务时读取该描述符;edr_hid_data_send是发送主入口:report_id对应 Report Map 中定义的报告 ID(如键盘 0x01、多媒体 0x02),返回值为HID_USER_ERR_*枚举,调用方可据HID_USER_ERR_SEND_FAIL做重试或提示;edr_hid_is_connected/edr_hid_tx_buff_is_ok用于发送前的链路状态与发送缓冲检查,避免在未连接时把数据丢弃到空中。
API 参考
| 函数 | 参数 | 返回值 | 说明 |
|---|---|---|---|
user_hid_init | user_hid_output_handler:Output Report 回调 | void | 初始化 EDR HID,注册主机下发数据回调 |
user_hid_exit | 无 | void | 退出/反初始化 HID 用户模块 |
user_hid_enable | en:1 使能 / 0 禁止 | void | 动态开关 HID 功能(多连接场景切换) |
user_hid_send_data | buf 数据指针,len 长度 | int | 通用数据发送(非 Report 封装) |
user_hid_disconnect | 无 | void | 主动断开当前 HID 连接 |
user_hid_set_icon | class_type:HID 设备类别图标 | void | 设置主机端显示的设备图标(Class of Device) |
user_hid_set_ReportMap | map 描述符指针,size 长度 | void | 下发 HID Report Map |
edr_hid_data_send | report_id,data,len | int | 按 Report ID 发送中断通道数据 |
edr_hid_data_send_ext | report_type,report_id,data,len | int | 扩展版发送,可指定 Report 类型(Input/Output/Feature) |
edr_hid_is_connected | 无 | int | 链路是否已连接(非 0 表示已连接) |
edr_hid_tx_buff_is_ok | 无 | int | 发送缓冲是否可写(避免阻塞/丢包) |
BLE HOGP 侧对应接口
BLE 通道由 apps/hid/modules/bt/ble_hogp.c 提供,标准键盘示例通过 extern 声明使用其接口:ble_hid_data_send(report_id, data, len) 发送、ble_hid_is_connected() 查询连接状态、le_hogp_set_output_callback() 注册 Output 回调(例如接收主机下发的 CapsLock LED 状态)。应用层在 hid_report_send 中先判断当前处于哪条链路,再选择调用 edr_hid_data_send 或 ble_hid_data_send。
标准键盘示例(standard_keyboard)
app_standard_keyboard.c 是功能最完整的键盘示例,同时支持 EDR HID、BLE HOGP 与 USB 三条通道,是理解整个 HID 体系的入口。文件顶部以 #if(CONFIG_APP_STANDARD_KEYBOARD) 作为编译开关,并定义了一系列特性宏:
#define SUPPORT_RECONN_ADV_OR_DIRECT 0 //0:ADV回连接 1:DIRECT回连接
#define SUPPORT_KEYBOARD_NO_CONFLICT 0 //无冲按键支持
#define SUPPORT_USER_PASSKEY 0
#define CAP_LED_ON_VALUE 1
//2.4G模式: 0---ble ,非0 2.4G配对码
#define CFG_RF_24G_CODE_ID (0)//32bits
#define CFG_RF_24G_CODE_CHANNEL 0x4//2.4g对应的通道
Source: apps/hid/examples/standard_keyboard/app_standard_keyboard.c
这些宏直接决定产品行为:SUPPORT_RECONN_ADV_OR_DIRECT 选择 BLE 回连方式(广播回连或直连回连);CFG_RF_24G_CODE_ID 为 0 时走 BLE,非 0 时启用 2.4G 配对码(配对码为 32bit);CFG_RF_24G_CODE_CHANNEL 指定 2.4G 射频通道。
连接状态机
示例内部维护了几个核心状态量,用于协调 EDR/BLE/USB 三条链路与配对流程:
typedef enum {
EDR_OPERATION_NULL = 0,
EDR_OPERATION_RECONN,
EDR_OPERATION_PAGESCAN_IRQUIRY_SCAN,
} edr_operation_t;
typedef enum {
SYSTEM_IOS,
SYSTEM_WIN,
SYSTEM_ARD,
};
Source: apps/hid/examples/standard_keyboard/app_standard_keyboard.c
edr_operation_t描述 EDR 侧当前操作:空闲 / 回连 / 同时进行寻呼扫描与查询扫描,用于在键盘开机的不同阶段切换扫描策略;SYSTEM_IOS/WIN/ARD表示当前对端主机系统类型,键盘据此切换键值映射(例如 Command 键与 Ctrl 键的差异);- 其他静态变量如
g_auto_shutdown_timer(自动关机计时)、cur_bt_idx(当前蓝牙索引)、paired_flag(是否已配对)、keyboard_system(当前系统)共同驱动整机行为。
键盘矩阵与 FN 键映射
键盘按键布局由二维矩阵表 matrix_key_table[ROW_MAX][COL_MAX] 定义,表项使用 usb_hid_keys.h 中的标准键值宏(_KEY_Q、_KEY_A…),S_KEY() 包裹的项表示组合修饰键(如 S_KEY(_KEY_MOD_LSHIFT))。矩阵表上方有 FN_ROW/FN_COL 指定 FN 键位置:
#define FN_ROW (6)
#define FN_COL (0)
const u16 matrix_key_table[ROW_MAX][COL_MAX] = { //高八位用来标识是否特殊键
/**/{0, _KEY_Q, _KEY_W, _KEY_E, _KEY_R, _KEY_U, _KEY_I, _KEY_O, _KEY_P, 0, 0, 0, 0, 0, 0, 0},
/**/{0, _KEY_TAB, _KEY_CAPSLOCK, _KEY_F3, _KEY_T, _KEY_Y, _KEY_RIGHTBRACE, _KEY_F7, _KEY_LEFTBRACE, 0, _KEY_BACKSPACE, 0, 0, 0, 0, S_KEY(_KEY_MOD_LSHIFT), S_KEY(_KEY_MOD_LALT)},
/**/{0, _KEY_A, _KEY_S, _KEY_D, _KEY_F, _KEY_J, _KEY_K, _KEY_L, _KEY_SEMICOLON, S_KEY(_KEY_MOD_LCTRL), _KEY_BACKSLASH, 0, 0, 0, 0, S_KEY(_KEY_MOD_RSHIFT)},
...
};
Source: apps/hid/examples/standard_keyboard/app_standard_keyboard.c
设计意图:把物理矩阵与逻辑键值解耦。硬件改版(换矩阵走线)时只需改表,业务代码(组合键、配对键、多媒体键处理)完全不动。FN 组合键通过 fn_remap_key 表把物理位置重映射为功能事件(如 _KEY_CUSTOM_CTRL_HOME、_KEY_BRIGHTNESS_INCREASE),并支持自定义事件 KEYBOARD_ENTER_PAIR0~3(一键进入四设备配对)、KEYBOARD_SYSTEM_IOS/WIN/ARD(切换主机系统)。
键盘上报主路径
示例声明了统一的上报入口 hid_report_send(u8 report_id, u8 *data, u16 len),并根据当前链路分发:
- EDR 已连接 →
edr_hid_data_send(report_id, data, len); - BLE 已连接 →
ble_hid_data_send(report_id, data, len)(外部声明); - USB 已连接(PC 模式)→ 走 USB 中断端点发送。
链路状态由 edr_hid_is_connected()、ble_hid_is_connected() 与 usb_hid_register_state_callback 注册的 stdkb_usb_state_callback 维护。此外示例还通过 le_hogp_set_output_callback 接收 BLE 主机的 Output Report(如 LED 状态),实现 CapsLock 指示灯,CAP_LED_ON_VALUE 即定义 LED 点亮电平。
USB HID 设备层(usb_hid_devices.c)
当键盘作为 USB 设备(2.4G 接收器模式或有线模式)连接 PC 时,由 usb_hid_devices.c 提供枚举描述符与端点收发。整个文件由 CONFIG_APP_STANDARD_KEYBOARD && TCFG_PC_ENABLE && TCFG_USB_SLAVE_USER_HID 三个宏同时门控,表明只有标准键盘示例且开启 PC 模式、开启 USB HID 从机时才编译。
描述符定义
static const u8 sHIDDescriptor[] = {
//InterfaceDescriptor:
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
0x00, // Protocol, 0 None
0x00, // Interface Name
//HIDDescriptor:
0x09, // bLength
USB_HID_DT_HID, // bDescriptorType
0x01, 0x02, // bcdHID, HID Class Specification 1.02
0x00, // bCountryCode
0x01, // bNumDescriptors
0x22, // bDescriptorType = Report Desc.
...
};
Source: apps/hid/examples/standard_keyboard/usb_hid_devices.c
描述符定义了两个中断端点:HID_EP_IN(键盘上报,最大包长 MAXP_SIZE_HIDIN)与 HID_EP_OUT(接收主机 Output Report,最大包长 MAXP_SIZE_HIDOUT),轮询间隔 1ms。接口描述符的 Class 字段为 USB_CLASS_HID。
Report Map 注入与回调注册
设备层暴露了三个供应用层调用的钩子,实现"描述符固定、Report Map 可变":
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;
void usb_hid_register_state_callback(void *callback)
{
usb_hid_state_ck = callback;
}
Source: apps/hid/examples/standard_keyboard/usb_hid_devices.c
usb_hid_set_repport_map:把标准键盘的 Report 描述符(来自standard_hid.h)注入设备层,主机请求 Report Descriptor 时通过get_hid_report_desc()返回;若未注入,get_hid_report_desc_len会触发ASSERT报 "report map err";usb_hid_register_state_callback:注册枚举/断开状态回调,应用层据此切换上报通道(stdkb_usb_state_callback)。
端点收发
static u32 hid_tx_data(struct usb_device_t *usb_device, const u8 *buffer, u32 len)
{
const usb_dev usb_id = usb_device2id(usb_device);
return usb_g_intr_write(usb_id, HID_EP_IN, buffer, len);
}
static void hid_rx_data(struct usb_device_t *usb_device, u32 ep)
{
u8 data[MAXP_SIZE_HIDOUT];
...
u32 rx_len = usb_g_intr_read(usb_id, ep, data, MAXP_SIZE_HIDOUT, 0);
if (usb_hid_output_callback) {
usb_hid_output_callback(data, rx_len);
}
}
Source: apps/hid/examples/standard_keyboard/usb_hid_devices.c
hid_tx_data 走中断 IN 端点把键盘 Report 发给 PC;hid_rx_data 在中断 OUT 端点收到主机数据后回调 usb_hid_set_output_callback 注册的应用层函数。端点在 hid_endpoint_init 中通过 usb_g_ep_config 配置,DMA 缓冲由 hid_register 调用 usb_alloc_ep_dmabuffer 分配,hid_itf_hander 处理标准请求(USB_REQ_GET_DESCRIPTOR 时返回 HID 描述符与 Report 描述符长度)。
鼠标 / 遥控器 / 手柄示例
鼠标(mouse_single / mouse_dual)
mouse_single/app_mouse.c:单模鼠标,主通道为 BLE HOGP,上报鼠标移动(X/Y 位移)与按键(左/中/右)Report;mouse_dual/app_mouse_dual.c:双模鼠标,在 BLE 与 2.4G/USB 之间切换,结构上复用标准键盘示例的"统一hid_report_send分发"思路——根据当前链路(BLE 或 USB)选择ble_hid_data_send或 USB 中断端点发送。
鼠标类设备的实现差异主要在 Report 描述符:标准鼠标 Report 为 4 字节(Button + X + Y + Wheel),而键盘为 8 字节按键数组,因此两个示例各自注入不同的 Report Map,但传输层代码完全复用。
语音遥控器(voice_remote_control)
app_remote_control.c 实现带语音功能的遥控器,配套 audio_codec_demo.c 完成语音数据的采集/编解码。遥控器按键(音量、上下曲、语音键)通过 EDR HID 上报;语音数据流则在 HID 之外通过 RFCOMM/自定义通道传输(依赖 RCSP 交互模块 rcsp_hid_inter.c 协调 HID 与透传通道)。该示例同时被链接进各平台 Makefile(与标准键盘示例共存),由 CONFIG_APP_REMOTE_CONTROL 类宏区分。
手柄(gamebox)
gamebox/ 目录包含 app_gamebox.c(应用入口)、gamebox.c(手柄逻辑)与 key_mapping.c(按键映射),走 EDR HID 通道。手柄 Report 结构与键盘/鼠标不同:通常包含方向键状态位、12~16 个按键位以及模拟摇杆数据,Report 长度更长。key_mapping.c 将物理按键映射为手柄 HID 键值(如 A/B/X/Y、Start/Select),与标准键盘的 matrix_key_table 职责相同——物理输入与逻辑键值解耦。
Core Flow:按键上报全流程
以下序列图展示标准键盘一次按键从物理触发到主机收到的完整路径,覆盖三条传输通道:
sequenceDiagram
participant U as User
participant M as matrix_keyboard(矩阵扫描)
participant A as app_standard_keyboard(事件处理)
participant H as hid_report_send(统一分发)
participant E as edr_hid_user / ble_hogp
participant UD as usb_hid_devices(中断端点)
participant P as Host(手机/PC)
U->>M: 按下按键
M->>A: 按键事件(键值 + 组合键)
A->>A: 查 matrix_key_table / fn_remap_key 映射
A->>H: hid_report_send(report_id, data, len)
alt EDR 已连接
H->>E: edr_hid_data_send(report_id, data, len)
E->>P: L2CAP 中断通道 Report
else BLE 已连接
H->>E: ble_hid_data_send(report_id, data, len)
E->>P: GATT HID Report Characteristic
else USB 已连接
H->>UD: hid_tx_data(usb_g_intr_write)
UD->>P: 中断 IN 端点 Report
end
P-->>A: Output Report(LED 等,可选)
A->>A: stdkb_edr_led_status_callback / USB 输出回调
关键设计决策与原因:
- 统一分发入口:应用层不关心底层是 EDR、BLE 还是 USB,只需调用
hid_report_send;链路切换(如 BLE 断开自动切 USB)对业务透明; - 发送前查连接状态:
edr_hid_is_connected/ble_hid_is_connected/ USB 状态回调确保数据只发往已建立的链路,避免无连接时无效发包导致的功耗浪费; - 事件驱动而非轮询:矩阵扫描产生按键事件后才组包上报,空闲时键盘可进入 sniff 模式(见"性能与运维"),这是 HID 设备低功耗的关键。
使用示例
注册 USB HID 状态回调与 Report Map
标准键盘示例初始化时把 USB 设备层与应用层对接起来(USB 通道):
void usb_hid_register_state_callback(void *callback);
static void stdkb_usb_state_callback(u8 state);
extern void le_hogp_set_output_callback(void *cb);
extern int ble_hid_data_send(u8 report_id, u8 *data, u16 len);
extern int ble_hid_is_connected(void);
Source: apps/hid/examples/standard_keyboard/app_standard_keyboard.c
应用层通过 usb_hid_register_state_callback(stdkb_usb_state_callback) 感知 USB 枚举/断开,切换上报通道;le_hogp_set_output_callback 接收 BLE 主机下发的 LED 状态。这种"回调注册 + 统一发送"模式是新增自定义 HID 设备(如新键位的手柄)时最直接的参考模板。
组合键(修饰键)定义
usb_hid_keys.h 提供标准键值宏,S_KEY() 表示修饰键,键盘矩阵中可直接组合:
{0, _KEY_TAB, _KEY_CAPSLOCK, _KEY_F3, _KEY_T, _KEY_Y, ..., S_KEY(_KEY_MOD_LSHIFT), S_KEY(_KEY_MOD_LALT)},
{0, _KEY_A, _KEY_S, _KEY_D, _KEY_F, _KEY_J, ..., S_KEY(_KEY_MOD_LCTRL), _KEY_BACKSLASH, 0, 0, 0, 0, S_KEY(_KEY_MOD_RSHIFT)},
Source: apps/hid/examples/standard_keyboard/app_standard_keyboard.c
S_KEY(_KEY_MOD_LSHIFT) 表示该物理键按下时,Report 中需同时置位 Shift 修饰键位,实现大写字母或符号输入——这是 HID 键盘协议的标准做法:按键数组只放普通键,修饰键单独编码在 Report 第 0~1 字节。
USB HID 描述符注入
设备层以静态描述符 + 动态 Report Map 组合完成 USB 枚举:
void usb_hid_set_repport_map(const u8 *map, int size)
{
hid_report_map_size = size;
hid_report_map = map;
}
Source: apps/hid/examples/standard_keyboard/usb_hid_devices.c
应用层在 USB 初始化时把来自 standard_hid.h 的 Report 描述符(键盘/多媒体等)注入设备层;主机 GET_DESCRIPTOR 请求 Report Descriptor 时,get_hid_report_desc() 直接返回该指针。更换产品(如改造成鼠标)时,只需换注入的 Report Map,无需改动枚举逻辑。
配置选项
下表汇总 HID 示例工程中直接可见的配置项(编译宏与运行时开关):
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
CONFIG_APP_STANDARD_KEYBOARD | 编译宏 | 由 Makefile 决定 | 使能标准键盘示例主体代码 |
TCFG_PC_ENABLE | 编译宏 | 0/1 | 使能 PC(2.4G/有线)模式 |
TCFG_USB_SLAVE_USER_HID | 编译宏 | 0/1 | 使能 USB HID 从机设备类 |
CONFIG_HID_CASE_ENABLE | 编译宏 | 板级决定 | 使能 HID 充电仓(case)功能 |
CONFIG_TRANSFER_ENABLE | 编译宏 | 板级决定 | 使能数据传输功能 |
SUPPORT_RECONN_ADV_OR_DIRECT | 宏 | 0 | 0=ADV 广播回连;1=DIRECT 直连回连 |
SUPPORT_KEYBOARD_NO_CONFLICT | 宏 | 0 | 无冲按键支持开关 |
SUPPORT_USER_PASSKEY | 宏 | 0 | 用户自定义配对 Passkey |
CAP_LED_ON_VALUE | 宏 | 1 | CapsLock LED 点亮电平 |
CFG_RF_24G_CODE_ID | 宏 | 0 | 0 走 BLE;非 0 为 2.4G 配对码(32bit) |
CFG_RF_24G_CODE_CHANNEL | 宏 | 0x4 | 2.4G 模式使用的射频通道 |
FN_ROW / FN_COL | 宏 | 6 / 0 | FN 组合键在矩阵中的位置 |
来源:apps/hid/examples/standard_keyboard/app_standard_keyboard.c、apps/hid/examples/standard_keyboard/app_standard_keyboard.c、apps/hid/board/bd19/Makefile
失败模式、边界情况与并发
基于源码可直接验证的失败处理与边界逻辑:
- Report Map 未注入:
get_hid_report_desc_len中ASSERT(hid_report_map_size != NULL, "report map err")—— 若应用层忘记调用usb_hid_set_repport_map就枚举 USB,会直接断言失败,提示开发者配置缺失(见 usb_hid_devices.c); - 发送失败:
edr_hid_data_send返回HID_USER_ERR_SEND_FAIL,调用方需检查链路状态(edr_hid_is_connected)与发送缓冲(edr_hid_tx_buff_is_ok)后再重发,避免无效发包与缓冲溢出; - 未连接时发包:三通道统一在发送前查询连接状态,键盘开机未配对时不构造 Report,配合
edr_operation_t状态机进入寻呼扫描/回连流程; - 并发/多链路:键盘可能同时维护 EDR 与 BLE 两条链路(多设备配对
cur_bt_idx),volatile u8 cur_bt_idx、volatile u8 paired_flag的 volatile 声明表明这些状态量会被中断上下文或协议栈任务异步修改,应用层读取时需注意时序; - 自动关机边界:
g_auto_shutdown_timer实现无操作自动关机,与stdkb_set_soft_poweroff配合,防止 HID 设备长期空转耗电。
性能与运维考虑
- 低功耗核心手段——sniff:HID 链路建立后进入 sniff 模式,示例声明了
lmp_sniff_t_slot_attemp_reset(slot, attemp)与sniff_support_reset_anchor_point(sniff 状态下是否支持 reset 到最近一次通信点)。该能力对 HID 尤其重要:键盘长时间无按键时深度休眠,按键瞬间需能快速唤醒并维持低延迟上报; - USB 中断端点轮询:端点描述符
bInterval = 0x01(1ms 轮询),MAXP_SIZE_HIDIN/HIDOUT决定单包最大字节数,键盘 8 字节、鼠标 4 字节,均远小于全速 64 字节上限,保证低延迟; - DMA 缓冲:
hid_register通过usb_alloc_ep_dmabuffer为 IN/OUT 端点分配 DMA 缓冲,收发不占 CPU 拷贝路径,适合持续滚动的鼠标位移数据; - 多平台复用:同一份示例代码由 bd19/bd29/br23/br25/br30/br34 六套 Makefile 编译,换芯片只换 board 目录,降低维护成本。
扩展点
在源码中可识别的扩展/定制入口:
- 新增 Report Map:通过
user_hid_set_ReportMap(EDR)与usb_hid_set_repport_map(USB)注入自定义 Report 描述符,即可把同一传输框架复用到任意 HID 设备类型; - Output Report 回调:
user_hid_init的user_hid_output_handler、usb_hid_set_output_callback、le_hogp_set_output_callback三条回调分别接收 EDR/USB/BLE 主机下发的数据(如 LED、特性配置),是双向交互的扩展点; - 键值映射表:修改
matrix_key_table与fn_remap_key/fn_remap_event即可定制键盘布局与 FN 功能,无需改动协议代码;手柄示例的key_mapping.c是同一思路的独立实现; - 系统键值切换:
KEYBOARD_SYSTEM_IOS/WIN/ARD自定义事件允许用户在 iOS/Windows/Android 键位间热切换,新系统适配只需在事件处理中追加映射分支; - 2.4G 模式:
CFG_RF_24G_CODE_ID非 0 时启用 2.4G 私有射频通道(配对码 32bit),为双模(BLE+2.4G)产品提供扩展路径。
相关链接
- edr_hid_user.h(EDR HID 用户接口)
- edr_hid_user.c(EDR HID 用户模块实现)
- app_standard_keyboard.c(标准键盘示例)
- usb_hid_devices.c(USB HID 设备层)
- bd19/Makefile(示例编译组织)
- standard_hid.h(标准 HID Report 定义)
- usb_hid_keys.h(USB HID 键值宏)
- 相关页面:BLE 协议栈 / HOGP 主题页、RCSP 协议页、USB 设备栈页、音频示例页(语音遥控器的音频编解码链路)。