HID 应用架构总览
本文档全面介绍 AW31N BLE SDK 中 HID(Human Interface Device,人机接口设备)应用的完整架构,涵盖应用入口与模式分发、BLE HID GATT 服务定义、USB HID 设备类驱动、RCSP HID 交互层、配置项以及事件/数据流,帮助开发者理解如何构建基于 BLE 的键盘、遥控器、鼠标等 HID 设备。
Purpose and Scope
本页面向 SDK 使用者(固件工程师、BLE 外设开发者)提供 HID 能力端到端的技术参考,覆盖以下内容:
- 应用层:
apps/demo/hid目录下的 HID demo 应用,包括app_main()入口、编译宏驱动的应用分支选择(键盘/遥控/鼠标/Keyfob 等)以及系统事件分发机制; - 协议层:BLE HID GATT 服务(Service UUID
0x1812)及其特征 UUID 定义(standard_hid.h); - 设备类驱动层:基于 USB 协议栈的 HID 键盘/鼠标/多媒体/自定义设备描述符与 Report 定义(
hid_keyboard.c、hid_mouse.c、hid_media.c、custom_hid.c); - 平台交互层:杰理 RCSP(遥控器/自研协议)中的 HID 交互模块
rcsp_hid_inter.c/h; - 配置与日志:HID 连接策略(
lib_profile_config.c)、日志标签(log_config.c)。
以下主题不在本页范围,由兄弟页面单独覆盖:BLE 基础协议栈与 GATT 框架、USB 协议栈整体架构、电源管理与低功耗策略、RCSP 完整协议族。本文仅在与 HID 能力交叉处作简要指引。
Overview
HID 是 BLE 外设最常见的应用场景之一。AW31N SDK 将 HID 能力组织为「应用层 → Profile/协议层 → 设备类驱动层」的分层结构:
- 应用层负责产品逻辑:按键扫描结果如何映射为 HID 报告、蓝牙连接事件如何驱动状态机、用户动作如何触发数据发送;
- 协议层负责 BLE GATT 侧的 HID 服务建模:HID Service(
0x1812)、HID Information、Report Map、Report、Protocol Mode、Control Point 等特征; - 设备类驱动层复用 USB HID 设备类的描述符与报告定义,保证同一套 HID Report 语义同时服务于 USB 与 BLE 两条通道——这是 SDK 设计的关键复用点:
standard_hid.h中注释掉的#include "usb/device/hid.h"正暗示了这种共享关系; - RCSP 层是杰理私有协议对 HID 的封装,用于与杰理手机 App / 生态设备交互。
从产品形态看,apps/demo/hid 是一个完整的可编译 demo 工程(board/bd47/AW31N_hid.cbp),通过编译宏可切换为键盘(CONFIG_APP_KEYBOARD)、遥控器(CONFIG_APP_REMOTE_CONTROL)、单/双模鼠标(CONFIG_APP_MOUSE_SINGLE/CONFIG_APP_MOUSE_DUAL)、低延迟鼠标(CONFIG_APP_MOUSE_LOW_LATENCY)、Keyfob、Keypage 等多种 HID 产品形态。
Architecture
下图展示 HID 能力的完整分层架构与依赖关系(节点名均为仓库中的真实模块/文件):
flowchart TD
subgraph sg_App["应用层 apps/demo/hid"]
AppMain["app_main()<br/>系统主入口"]
GetName["main_app_get_name()<br/>编译宏选择应用分支"]
State["main_application_operation_state()<br/>状态机分发"]
Event["main_application_operation_event()<br/>事件分发"]
BtEvent["bt_event_update_to_user()<br/>BT 事件上报"]
end
subgraph sg_Profile["协议/Profile 层"]
BLEHid["BLE HID GATT 服务<br/>standard_hid.h (0x1812)"]
RcspHid["RCSP HID 交互层<br/>rcsp_hid_inter.c/h"]
end
subgraph sg_DevClass["设备类驱动层<br/>apps/app/bsp/common/usb/device"]
HidKb["hid_keyboard.c<br/>键盘 + 消费控制"]
HidMouse["hid_mouse.c"]
HidMedia["hid_media.c"]
CustomHid["custom_hid.c"]
end
subgraph sg_Cfg["配置层 apps/demo/hid/config"]
LibProfile["lib_profile_config.c<br/>HID 连接策略"]
LogCfg["log_config.c<br/>HID 日志标签"]
end
AppMain --> GetName
GetName --> State
State --> Event
BtEvent --> Event
Event --> BLEHid
BLEHid --> HidKb
BLEHid --> HidMouse
BLEHid --> HidMedia
RcspHid --> BLEHid
LibProfile --> BLEHid
LogCfg -.-> AppMain
架构解读:
- 应用层是唯一的入口:
app_main()由系统启动后调用,先做电源键/LVD 检查,再通过main_app_get_name()依据编译宏确定当前运行的应用(hid_key、hid_rc、mouse_*等),随后main_application_operation_state()遍历应用注册表(list_for_each_app_main)找到匹配的struct application并调用其state_machine; - 事件通道:蓝牙协议栈产生的事件(连接、断开、配对等)通过
bt_event_update_to_user()包装为struct sys_event(类型SYS_BT_EVENT),经main_application_operation_event()以post_msg(3, MSG_TYPE_EVENT, event, ops)投递到目标应用的event_handler——事件处理被移入消息循环,避免在中断/协议栈上下文中直接执行应用逻辑; - 协议层:BLE HID 服务遵循 Bluetooth SIG 标准 GATT 服务定义(
0x1812),特征 UUID 与standard_hid.h一致;RCSP HID 交互层在此基础上叠加杰理私有交互协议; - 设备类驱动层:HID 报告描述符与端点定义集中在 USB 设备类驱动中,BLE HID 与 USB HID 共享同一套 Report 语义,保证跨通道行为一致;
- 配置层:
hid_conn_depend_on_dev_company = 2表示默认不断开 HID 连接,由 Profile 配置决定 HID 连接的生命周期策略。
核心实现解析
系统内存与中断布局(app_main.c)
HID 应用与其他 demo 一样,在 app_main.c 顶部通过 sec_used 段属性固定了系统栈(.sstack/.ustack)、系统堆(.sec_sys_heap)以及蓝牙协议栈的 NK/NV RAM 区域,这是链接脚本(ld.c)约定必须存在的最小占用定义:
static int _sstack_space[SYS_STACK_SIZE_ALL / 4] sec_used(.sstack);
static int _ustack_space[USR_STACK_SIZE_ALL / 4] sec_used(.ustack);
static int _sys_heap_space[SYS_HEAP_SIZE / 4] sec_used(.sec_sys_heap);//最少占用
static int _bt_nk_ram_min[BT_NK_RAM_SIZE_ALL / 4] sec_used(.sec_bt_nk_ram);//最少占用
static int _bt_nv_ram_min[BT_NV_RAM_SIZE_ALL / 4] sec_used(.sec_bt_nv_ram);//最少占用
Source: app_main.c
中断优先级同样在此文件集中配置:USB 中断 IRQ_USB_IP = 3,BLE 事件 IRQ_BLE_EVENT_IP = 5、BLE RX IRQ_BLE_RX_IP = 5,BT 协议栈消息 IRQ_BTSTACK_MSG_IP = 4。HID 设备同时承载 BLE 与 USB 通道,优先级的设计保证了 USB 枚举/传输不被 BLE 事件长期抢占,同时 BLE 事件高于普通任务。
应用分支选择与启动流程
应用入口 app_main()
app_main() 是 HID demo 的系统级入口(位于 app_main.c L116-L141):
void app_main()
{
//TODO
/* if (!UPDATE_SUPPORT_DEV_IS_NULL()) { */
/* int update = 0; */
/* update = update_result_deal(); */
/* } */
log_info(">>>>>>>>>>>>>>>>app_main...\n");
log_info("nk_malloc: %08x,%04x, nv_malloc: %08x,%04x", NK_RAM_MALLOC_START_ADDR, NK_RAM_MALLOC_SIZE,
NV_RAM_MALLOC_START_ADDR, NV_RAM_MALLOC_SIZE);
log_info("sstack:size,top= %04x, %08x,ustack:size,top= %04x, %08x", sizeof(_sstack_space), _sstack_top, sizeof(_ustack_space), _ustack_top);
#if TCFG_POWER_ON_NEED_KEY
check_power_on_key();
#endif
#if TCFG_SYS_LVD_EN
app_power_vbat_check();
#endif
main_application_operation_state(NULL, APP_STA_START);
}
Source: app_main.c
启动顺序的设计意图:
- 先输出内存布局日志(NK/NV RAM、栈顶地址),便于排查 BLE 协议栈 RAM 不足问题;
TCFG_POWER_ON_NEED_KEY开启时,check_power_on_key()循环检测电源键,检测失败则直接软关机(power_set_soft_poweroff()),避免误上电;TCFG_SYS_LVD_EN开启时执行app_power_vbat_check()低电压检测;- 最终以
APP_STA_START状态进入main_application_operation_state(),完成应用状态机初始化。
编译宏驱动的应用分支选择 main_app_get_name()
HID demo 通过一套 CONFIG_APP_* 编译宏在编译期决定产品形态,这是 SDK 多产品共工程的核心设计——同一份源码、同一套 HID 框架,仅通过宏切换成不同的应用(app_main.c L166-L206):
static void main_app_get_name(struct intent *it)
{
init_intent(it);
// 选择应用分支
#if(CONFIG_APP_KEYBOARD)
it->name = "hid_key";
it->action = ACTION_HID_MAIN;
#elif(CONFIG_APP_KEYFOB)
it->name = "keyfob";
it->action = ACTION_KEYFOB;
#elif(CONFIG_APP_KEYPAGE)
it->name = "keypage";
it->action = ACTION_KEYPAGE;
#elif(CONFIG_APP_REMOTE_CONTROL)
it->name = "hid_rc";
it->action = ACTION_REMOTE_CONTROL;
#elif(CONFIG_APP_MOUSE_SINGLE)
it->name = "mouse_single";
it->action = ACTION_MOUSE_MAIN;
#elif(CONFIG_APP_MOUSE_DUAL)
it->name = "mouse_dual";
it->action = ACTION_MOUSE_MAIN;
#elif(CONFIG_APP_MOUSE_LOW_LATENCY)
it->name = "mouse_low_latency";
it->action = ACTION_MOUSE_MAIN;
#elif(CONFIG_APP_IDLE)
it->name = "idle";
it->action = ACTION_IDLE_MAIN;
#else
ASSERT(0, "no app!!!");
#endif
}
Source: app_main.c
struct intent 携带 name(应用名,用于在应用注册表中匹配)与 action(应用入口动作)。所有分支都未定义时触发 ASSERT(0, "no app!!!") 强制失败——这保证了产品配置必须显式选择一种 HID 形态,避免运行到未定义状态。
应用注册表遍历与状态机分发
main_application_operation_state() 通过 list_for_each_app_main(dev) 遍历系统应用注册表,按名称匹配 dev->name 与 it.name,命中后调用 dev->ops->state_machine(app, state, &it)(app_main.c L208-L229)。应用注册表由链接脚本与 list_for_each_app_main 宏共同维护,新增应用只需实现 struct application_operation(state_machine + event_handler)并注册,无需改动分发逻辑——典型的注册表/回调模式,将框架与产品逻辑解耦。
蓝牙事件上报与消息化分发
HID 外设的行为由蓝牙连接状态驱动(如连接成功后进入可配对/可交互状态)。协议栈通过 bt_event_update_to_user() 向应用层上报(app_main.c L262-L279):
void bt_event_update_to_user(u8 *addr, u32 type, u8 event, u32 value)
{
log_info("bt_event_update_to_user type:%d\n", type);
struct sys_event *e = event_pool_alloc();
if (e == NULL) {
log_info("Memory allocation failed for sys_event");
return;
}
e->type = SYS_BT_EVENT;
if (addr != NULL) {
memcpy(e->u.bt.args, addr, 6);
}
e->arg = (void *)type;
e->u.bt.event = event;
e->u.bt.value = value;
main_application_operation_event(NULL, e);
}
Source: app_main.c
要点:
- 事件从事件池
event_pool_alloc()分配,携带对端地址(6 字节)、事件类型、事件码与附加值; - 分配失败时仅打日志并返回——事件池耗尽属于资源紧张信号,应用层不应在此处阻塞协议栈;
main_application_operation_event()同样遍历应用注册表,但通过post_msg(3, MSG_TYPE_EVENT, event, dev->ops)将事件投递到消息队列而非直接调用(app_main.c L241-L260)。消息循环中的main_sys_event_msg_handle()最终调用ops_ptr->event_handler(NULL, event_ptr)并释放事件(app_main.c L231-L238)。这种「上报→排队→处理→释放」的设计将应用逻辑与中断/协议栈上下文隔离,避免长时间处理阻塞 BLE 栈。
Core Flow:从启动到 HID 事件处理的完整时序
sequenceDiagram
participant Boot as 系统启动/链接脚本
participant AppMain as app_main()
participant GetName as main_app_get_name()
participant State as main_application_operation_state()
participant App as HID 应用<br/>(hid_key / hid_rc)
participant BtStack as 蓝牙协议栈
participant MsgLoop as 消息循环<br/>main_sys_event_msg_handle()
Boot->>AppMain: 电源键/LVD 检查通过
AppMain->>State: main_application_operation_state(NULL, APP_STA_START)
State->>GetName: 读取 CONFIG_APP_* 宏
GetName-->>State: intent{name, action}
State->>App: dev->ops->state_machine(app, APP_STA_START, &it)
App->>BtStack: 注册 HID GATT 服务 (0x1812)
BtStack-->>App: 广播/可连接状态
BtStack->>BtStack: 对端连接建立
BtStack->>State: bt_event_update_to_user(addr, type, event, value)
State->>MsgLoop: post_msg(3, MSG_TYPE_EVENT, event, ops)
MsgLoop->>App: ops->event_handler(NULL, event)
App->>App: 更新连接状态/恢复 HID 报告发送
MsgLoop->>MsgLoop: event_pool_free(event)
时序解读:
- 系统完成链接脚本布局与电源检查后进入
app_main(); - 应用框架确定当前 HID 形态(键盘/遥控/鼠标),并将
APP_STA_START投递给对应应用的state_machine; - HID 应用初始化 BLE GATT HID 服务并进入广播/可连接状态;
- 对端(手机/PC/电视)建立连接后,协议栈回调
bt_event_update_to_user(); - 事件经消息队列异步投递到应用的
event_handler,应用据此切换连接状态并开始(或恢复)上报 HID 报告; - 处理完毕后事件对象被释放回事件池,形成完整的「协议栈事件 → 应用处理 → 资源回收」闭环。
协议层:BLE HID GATT 服务定义
BLE HID 遵循 Bluetooth SIG 发布的 HID over GATT Profile(HOGP)。apps/demo/hid/include/standard_hid.h 集中定义了该服务的全部标准 UUID(standard_hid.h L10-L16):
#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 对应的 GATT 角色与用途:
| UUID | 名称 | 角色/用途 |
|---|---|---|
0x1812 | HID Service | HID over GATT 服务声明,主机据此识别 HID 设备 |
0x2A4A | HID Information | 特征:bcdHID 版本、国家码、标志位(只读) |
0x2A4B | Report Map | 特征:HID 报告描述符,定义设备输入/输出/特性报告格式 |
0x2A4C | HID Control Point | 特征:主机下发 Suspend/Exit Suspend 控制命令 |
0x2A4D | Report | 特征:承载具体 HID 报告(键盘按键、鼠标位移等),Input Report 通过 Notification/Indication 上报 |
0x2A4E | Protocol Mode | 特征:Boot Protocol / Report Protocol 切换(键盘鼠标可选) |
0x2908 | Report Reference | 描述符:将 Report 特征与报告 ID/类型关联 |
文件头部被注释掉的 // #include "usb/device/hid.h" 是重要的架构线索:BLE HID 与 USB HID 本可共享同一份头文件,SDK 选择在应用侧单独维护一份 UUID 常量,避免 demo 工程直接依赖 USB 设备驱动,保持分层清晰。
设备类驱动层:HID 报告与描述符
USB 协议栈目录 apps/app/bsp/common/usb/device/ 下集中了 HID 设备类驱动:hid_keyboard.c、hid_mouse.c、hid_media.c、custom_hid.c。这些文件定义了 HID 描述符(sHIDDescriptor)与报告描述符(sHIDReportDesc),是 HID Report 语义的唯一权威来源——BLE HID 通道的 Report Map 与此保持一致,保证同一设备经 USB 或 BLE 连接时行为一致。
以 hid_keyboard.c 为例(hid_keyboard.c L25-L66):
static const u8 sHIDDescriptor[] = {
//HID
//InterfaceDeszcriptor:
USB_DT_INTERFACE_SIZE, // Length
USB_DT_INTERFACE, // DescriptorType
0x00, // bInterface number
0x00, // AlternateSetting
0x01, // NumEndpoint
USB_CLASS_HID, // Class = Human Interface Device
0x01, // Subclass, 0 No subclass, 1 Boot Interface subclass
0x01, // Procotol, 0 None, 1 Keyboard, 2 Mouse
0x00, // Interface Name
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_KEYBOARD_EP_IN, // bEndpointAddress
USB_ENDPOINT_XFER_INT, // Interrupt
LOBYTE(HID_KEYBOARD_EP_IN_MAX_SIZE), HIBYTE(HID_KEYBOARD_EP_IN_MAX_SIZE),// Maximum packet size
0x01, // Poll every 10msec seconds
//EndpointDescriptor:
USB_DT_ENDPOINT_SIZE, // bLength
USB_DT_ENDPOINT, // bDescriptorType, Type
USB_DIR_OUT | HID_KEYBOARD_EP_OUT, // bEndpointAddress
USB_ENDPOINT_XFER_INT, // Interrupt
LOBYTE(HID_KEYBOARD_EP_OUT_MAX_SIZE), HIBYTE(HID_KEYBOARD_EP_OUT_MAX_SIZE),// Maximum packet size
0x01, // Poll every 10msec seconds
};
Source: hid_keyboard.c
该描述符定义了:
- 接口类:
USB_CLASS_HID,子类为 Boot Interface(1),协议为键盘(1),支持 Boot 模式键盘; - 双向中断端点:IN 端点上报按键状态,OUT 端点接收主机回传(如 LED 状态:Num Lock/Caps Lock/Scroll Lock);
- 轮询间隔 10ms,符合标准键盘的轮询节奏。
键盘报告描述符(hid_keyboard.c L68-L115)包含两个 Report ID:
- Report ID
0x3(键盘集合):8 bit 修饰键(Ctrl/Shift/Alt/GUI)+ 1 字节保留 + 6 键无冲突回滚(6KRO),另含 LED Output 报告(Num Lock、Caps Lock、Scroll Lock); - Report ID
0x5(Consumer Control 集合):16 bit 消费类控制用法(音量、播放/暂停等),Logical Maximum 0x28C(652),覆盖 AC Send 等消费用法。
usb_get_hid_keyboard_report_id() 返回 HID_KEYBOARD_REPORT_ID,供上层在发送报告时选择正确的 Report ID(hid_keyboard.c L117-L120)。鼠标(hid_mouse.c)、多媒体(hid_media.c)、自定义(custom_hid.c)遵循同一模式,分别定义各自的 Report 结构,应用层通过编译开关 USB_DEVICE_CLASS_CONFIG & HID_CLASS 启用。
平台交互层:RCSP HID 模块
杰理 RCSP(JieLi Remote Control/Service Protocol)协议族中的 HID 模块位于 apps/app/bsp/common/third_party_profile/jieli/JL_rcsp/rcsp_hid/rcsp_hid_inter.c/h。该模块的作用是在杰理生态内(如杰理手机 App 或配套设备)对 HID 能力进行私有协议封装,实现:
- RCSP 命令与 HID 动作的相互转换(例如 App 下发「切换配对」「音量控制」等命令,映射为对应的 HID 消费报告);
- 与标准 BLE HID GATT 服务的桥接,使私有通道与标准通道共享同一套 HID 报告语义。
RCSP HID 模块依赖 standard_hid.h 定义的标准 UUID,并通过 HID 应用层的事件/命令接口联动——它是 HID 能力在杰理私有生态中的扩展面,第三方 HID 产品若不需要 RCSP 交互可以裁剪该目录。
配置与日志
HID 连接策略(lib_profile_config.c)
/*注意hid_conn_depend_on_dev_company置2之后,默认不断开HID连接 */
const u8 hid_conn_depend_on_dev_company = 2;
Source: lib_profile_config.c
hid_conn_depend_on_dev_company 控制 HID 连接与设备公司(配对绑定)的关联策略:置为 2 后,默认不断开已建立的 HID 连接(即连接不随配对信息变化而释放)。这是 HID 外设常见的产品诉求——键盘/鼠标应保持长连接,避免因配对状态变动导致输入中断。该配置由 Profile 配置层消费,是「连接生命周期」这一系统行为的可调旋钮。
日志标签(log_config.c)
HID demo 为各模块预留了独立日志开关(apps/demo/hid/config/log_config.c),例如 HID_KEY(键盘应用)、HID_RC(遥控应用)、HID_VRC(语音遥控)标签的 v/i/d/w/e 五级开关。开发者可通过调整这些常量控制对应模块的日志输出量,用于定位 HID 连接与报告上报问题。
Usage Examples
以下示例均摘自仓库真实源码,展示如何在 HID 框架上扩展或复用能力。
示例 1:注册新的 HID 产品形态
新增一种 HID 应用(如「语音遥控器」)时,只需在 main_app_get_name() 中增加一个编译宏分支,返回对应的 name 与 action,框架自动完成分发(app_main.c L166-L204):
static void main_app_get_name(struct intent *it)
{
init_intent(it);
// 选择应用分支
#if(CONFIG_APP_KEYBOARD)
it->name = "hid_key";
it->action = ACTION_HID_MAIN;
#elif(CONFIG_APP_REMOTE_CONTROL)
it->name = "hid_rc";
it->action = ACTION_REMOTE_CONTROL;
#elif(CONFIG_APP_MOUSE_SINGLE)
it->name = "mouse_single";
it->action = ACTION_MOUSE_MAIN;
#else
ASSERT(0, "no app!!!");
#endif
}
Source: app_main.c
随后在应用注册表中实现同名 struct application(state_machine + event_handler),即可被 list_for_each_app_main 匹配并驱动。
示例 2:向应用层上报蓝牙事件
协议栈侧需要将连接/断开等事件通知 HID 应用时,统一走 bt_event_update_to_user()(app_main.c L262-L279):
void bt_event_update_to_user(u8 *addr, u32 type, u8 event, u32 value)
{
log_info("bt_event_update_to_user type:%d\n", type);
struct sys_event *e = event_pool_alloc();
if (e == NULL) {
log_info("Memory allocation failed for sys_event");
return;
}
e->type = SYS_BT_EVENT;
if (addr != NULL) {
memcpy(e->u.bt.args, addr, 6);
}
e->arg = (void *)type;
e->u.bt.event = event;
e->u.bt.value = value;
main_application_operation_event(NULL, e);
}
Source: app_main.c
调用方传入对端地址(可选)、事件类型、事件码与附加值;事件最终经消息队列到达应用 event_handler,处理完由消息循环释放。
示例 3:BLE HID 服务特征常量
实现自定义 BLE HID 服务时,直接复用 standard_hid.h 的标准 UUID 常量,确保与主机(Windows/macOS/Android/iOS)的 HOGP 驱动兼容(standard_hid.h L10-L16):
#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
Configuration Options
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
CONFIG_APP_KEYBOARD | 编译宏 | 未定义 | 启用键盘应用(hid_key,ACTION_HID_MAIN),对应 BLE HID 键盘 + 消费控制 |
CONFIG_APP_KEYFOB | 编译宏 | 未定义 | 启用 Keyfob 应用(keyfob,ACTION_KEYFOB) |
CONFIG_APP_KEYPAGE | 编译宏 | 未定义 | 启用翻页器应用(keypage,ACTION_KEYPAGE) |
CONFIG_APP_REMOTE_CONTROL | 编译宏 | 未定义 | 启用遥控器应用(hid_rc,ACTION_REMOTE_CONTROL) |
CONFIG_APP_MOUSE_SINGLE | 编译宏 | 未定义 | 启用单模鼠标应用(mouse_single,ACTION_MOUSE_MAIN) |
CONFIG_APP_MOUSE_DUAL | 编译宏 | 未定义 | 启用双模鼠标应用(mouse_dual,ACTION_MOUSE_MAIN) |
CONFIG_APP_MOUSE_LOW_LATENCY | 编译宏 | 未定义 | 启用低延迟鼠标应用(mouse_low_latency,ACTION_MOUSE_MAIN) |
CONFIG_APP_IDLE | 编译宏 | 未定义 | 启用空闲应用(idle,ACTION_IDLE_MAIN) |
hid_conn_depend_on_dev_company | u8 | 2 | HID 连接与配对公司关联策略;置 2 后默认不断开 HID 连接 |
TCFG_POWER_ON_NEED_KEY | 编译宏 | — | 开启后需按键才能上电(check_power_on_key()) |
TCFG_SYS_LVD_EN | 编译宏 | — | 开启后上电执行低电压检测(app_power_vbat_check()) |
USB_DEVICE_CLASS_CONFIG | 位掩码 | — | 需包含 HID_CLASS 位以启用 HID 设备类驱动 |
HID_KEYBOARD_EP_IN/OUT、HID_KEYBOARD_EP_IN_MAX_SIZE 等 | 宏 | 见驱动 | USB HID 键盘端点地址与最大包长 |
日志标签 log_tag_const_*_HID_KEY/HID_RC/HID_VRC | char | v=0, i/d/w/e=1 | 各 HID 模块日志分级开关 |
说明:
CONFIG_APP_*分支按#elif互斥,同一编译仅一个 HID 形态生效;未定义任何分支将触发ASSERT(0, "no app!!!")。
API Reference
void app_main(void)
系统主入口,由启动代码调用。执行电源键检查(可选)、LVD 检查(可选),然后以 APP_STA_START 进入应用状态机分发。
- 参数:无
- 返回:无
- 注意:此后控制权交给应用注册表中的 HID 应用;
eSystemConfirmStopStatus()返回1表示系统空闲时可进入无超时深睡(Endless Sleep)
static void main_app_get_name(struct intent *it)
依据 CONFIG_APP_* 编译宏填充 struct intent(应用名 + 入口 action)。
- 参数:
it(struct intent *)— 输出参数,先init_intent(it)再赋值 - 返回:无
- 异常:所有分支均未定义时
ASSERT(0, "no app!!!")
static struct application *main_application_operation_state(struct application *app, enum app_state state)
遍历应用注册表(list_for_each_app_main),按名称匹配后调用 dev->ops->state_machine(app, state, &it)。
- 参数:
app(struct application *,可为 NULL)、state(enum app_state,如APP_STA_START) - 返回:
struct application *,当前实现恒返回 NULL
struct application *main_application_operation_event(struct application *app, struct sys_event *event)
遍历应用注册表,按名称匹配后将事件以 post_msg(3, MSG_TYPE_EVENT, event, dev->ops) 投递到消息队列。
- 参数:
app、event(struct sys_event *) - 返回:
struct application *,恒返回 NULL
void main_sys_event_msg_handle(int *msg)
消息循环对 MSG_TYPE_EVENT 消息的处理函数:解包 sys_event 与 application_operation,调用 event_handler(NULL, event_ptr) 后 event_pool_free(event_ptr)。
- 参数:
msg(int *,消息队列载荷) - 返回:无
- 资源语义:事件对象由消息循环统一释放,应用
event_handler内不得再次释放
void bt_event_update_to_user(u8 *addr, u32 type, u8 event, u32 value)
蓝牙协议栈事件上报接口:从事件池分配 sys_event(类型 SYS_BT_EVENT),拷贝 6 字节对端地址,设置事件类型/码/值后交给 main_application_operation_event()。
- 参数:
addr(u8 *,对端 MAC,可为 NULL)、type(事件类型)、event(事件码)、value(附加值) - 返回:无
- 失败路径:事件池分配失败时仅打印日志并返回
Failure Modes, Edge Cases & Concurrency
事件池耗尽
bt_event_update_to_user() 在 event_pool_alloc() 返回 NULL 时仅记录日志并直接返回(app_main.c L265-L269)。该设计避免在协议栈上下文中阻塞,但代价是事件可能丢失——若 BLE 事件爆发(如频繁连接/断开、扫描风暴),应用层可能收不到部分状态变更。排查时应关注 event_pool 配置容量,并在产品上限制异常连接频率。
未定义应用分支
main_app_get_name() 在所有 CONFIG_APP_* 均未定义时触发 ASSERT(0, "no app!!!")(app_main.c L202-L204)。这是编译期即失败的防御策略:宁可系统启动即断言,也不允许运行到无应用状态。新增产品形态时务必在 app_config.h/工程配置中定义且仅定义一个分支(#elif 互斥)。
应用注册表不匹配
main_application_operation_state()/main_application_operation_event() 按 memcmp(dev->name, it.name, strlen(it.name)) 匹配应用。若注册表中不存在该名称,仅打印 log_info("no app run") / log_info("no event run") 并静默返回——事件被丢弃。因此自定义应用名必须与 main_app_get_name() 中返回的 name 完全一致(区分大小写),这是常见的集成错误点。
内存分配失败
app_main.c 顶部以 sec_used 段属性固定栈/堆/BT RAM 的最小占用。若 SYS_STACK_SIZE_ALL、BT_NK_RAM_SIZE_ALL 等配置过小,将导致链接期或运行期内存不足。启动日志中的 nk_malloc/nv_malloc/sstack/ustack 信息(app_main.c L126-L129)是核对内存布局的第一手依据。
并发与上下文
- 协议栈上下文 vs 应用上下文:
bt_event_update_to_user()可能运行在协议栈回调上下文,其内部不直接调用event_handler,而是通过post_msg投递到应用消息循环——这是刻意设计的上下文切换,保证应用逻辑在统一的消息循环中串行执行,避免多线程竞争 HID 状态; - 事件对象所有权:
sys_event从事件池分配,所有权在「协议栈 → 消息队列 →main_sys_event_msg_handle→event_pool_free」之间严格传递,应用event_handler不可提前释放; - IRQ 优先级:USB(3)与 BLE(5)中断优先级不同,HID 双通道(USB + BLE)同时活动时,高优先级 BLE 事件可抢占 USB 处理,需要在驱动层做好临界区保护(由 USB/BLE 协议栈负责)。
Performance & Operational Considerations
- 报告上报路径短:键盘/鼠标按键到 HID 报告仅经过「按键扫描 → 应用逻辑 → 报告发送」,无文件系统/网络等重开销;报告描述符中的 10ms 轮询间隔与 6KRO 设计是功耗与性能的平衡点;
- 低延迟模式:
CONFIG_APP_MOUSE_LOW_LATENCY提供了低延迟鼠标形态,适合对时延敏感的场景,代价通常是更频繁的广播/连接参数(如更短的 connection interval); - 电源管理:
eSystemConfirmStopStatus()返回1表示系统空闲时进入无超时深睡(Endless Sleep),HID 设备在无连接时应尽快进入低功耗状态,连接恢复由 BLE 事件唤醒; - 日志开销:
log_config.c中各级日志开关(v/i/d/w/e)独立可控,量产固件建议关闭 verbose 级以降低串口/存储开销。
Extension Points
- 新增 HID 产品形态:在
main_app_get_name()增加CONFIG_APP_*分支 → 实现同名struct application(state_machine+event_handler)并注册 → 在app_config.h中定义宏。框架的分发逻辑无需改动; - 扩展 HID 报告:在
apps/app/bsp/common/usb/device/下新增/修改设备类驱动(如custom_hid.c),定义新的 Report ID 与报告描述符;BLE 侧保证 Report Map 与之一致; - 对接私有协议:RCSP HID 模块(
rcsp_hid_inter.c/h)是杰理生态扩展面,可在其上增加私有命令到 HID 报告的映射; - 调整连接生命周期:修改
hid_conn_depend_on_dev_company可改变 HID 连接随配对状态释放的行为(置2为默认不断开)。
Tests
仓库中 HID demo 工程以 apps/demo/hid/board/bd47/AW31N_hid.cbp(Code::Blocks 工程)形式提供,覆盖键盘(CONFIG_APP_KEYBOARD)等形态的编译验证;patch_release/ 下另有针对 AW31N 的补丁版本工程(v1.1.0/v1.2.0)。建议验证路径:hid_key 键盘 → 手机/PC 连接后逐键检查 6KRO 与消费控制报告;hid_rc 遥控 → 检查配对与断开重连策略;鼠标形态 → 检查位移/滚轮报告与低延迟参数。单元测试源码未在本仓库发现,集成验证主要依赖 demo 工程与补丁说明。
Related Links
- standard_hid.h(BLE HID GATT 服务 UUID)
- app_main.c(HID 应用主入口与事件分发)
- hid_keyboard.c(USB/BLE HID 键盘描述符与报告)
- hid_mouse.c / hid_media.c / custom_hid.c(其他 HID 设备类驱动)
- rcsp_hid_inter.c/h(RCSP HID 交互层)
- lib_profile_config.c(HID 连接策略配置)
- log_config.c(HID 日志标签配置)
- 兄弟页面指引:BLE 协议栈与 GATT 框架、USB 协议栈架构、电源管理与低功耗、RCSP 协议族(见对应目录文档)