杰理 SDK 文档中心
首页
首页
  • 概述与快速入门

    • 芯片平台与 SDK 概述
    • 环境搭建与编译工具链
    • 快速开始:选型、编译与烧录
    • 烧录与量产工具
  • 构建系统与板级工程

    • 顶层 Makefile 与编译目标
    • 板级工程与配置
    • 后处理与配置工具
  • HID 人机交互应用

    • HID 应用架构总览
    • 键盘、翻页器与遥控应用
    • 鼠标应用:单模、双模与低延迟
    • 空闲应用与初始化流程
  • BLE 透传与数传应用

    • 透传应用总览
    • 多连接与无连接传输
    • AT 命令模组应用
    • Dongle 适配器应用
  • BSP 公共模块

    • 蓝牙公共处理
    • 按键、LED 与红外
    • 传感器与编码器
    • 存储、VM 与文件系统
    • 电源管理与低功耗
    • 消息调度与通信外设
  • 协议栈与预编译库

    • 蓝牙协议栈库
    • 设备驱动与文件系统库
    • 音频、升级与其他库
  • 开发资料与补丁发布

    • 文档资料中心
    • 版本补丁与兼容性修复

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

启动顺序的设计意图:

  1. 先输出内存布局日志(NK/NV RAM、栈顶地址),便于排查 BLE 协议栈 RAM 不足问题;
  2. TCFG_POWER_ON_NEED_KEY 开启时,check_power_on_key() 循环检测电源键,检测失败则直接软关机(power_set_soft_poweroff()),避免误上电;
  3. TCFG_SYS_LVD_EN 开启时执行 app_power_vbat_check() 低电压检测;
  4. 最终以 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)

时序解读:

  1. 系统完成链接脚本布局与电源检查后进入 app_main();
  2. 应用框架确定当前 HID 形态(键盘/遥控/鼠标),并将 APP_STA_START 投递给对应应用的 state_machine;
  3. HID 应用初始化 BLE GATT HID 服务并进入广播/可连接状态;
  4. 对端(手机/PC/电视)建立连接后,协议栈回调 bt_event_update_to_user();
  5. 事件经消息队列异步投递到应用的 event_handler,应用据此切换连接状态并开始(或恢复)上报 HID 报告;
  6. 处理完毕后事件对象被释放回事件池,形成完整的「协议栈事件 → 应用处理 → 资源回收」闭环。

协议层: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名称角色/用途
0x1812HID ServiceHID over GATT 服务声明,主机据此识别 HID 设备
0x2A4AHID Information特征:bcdHID 版本、国家码、标志位(只读)
0x2A4BReport Map特征:HID 报告描述符,定义设备输入/输出/特性报告格式
0x2A4CHID Control Point特征:主机下发 Suspend/Exit Suspend 控制命令
0x2A4DReport特征:承载具体 HID 报告(键盘按键、鼠标位移等),Input Report 通过 Notification/Indication 上报
0x2A4EProtocol Mode特征:Boot Protocol / Report Protocol 切换(键盘鼠标可选)
0x2908Report 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_companyu82HID 连接与配对公司关联策略;置 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_VRCcharv=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

  1. 新增 HID 产品形态:在 main_app_get_name() 增加 CONFIG_APP_* 分支 → 实现同名 struct application(state_machine + event_handler)并注册 → 在 app_config.h 中定义宏。框架的分发逻辑无需改动;
  2. 扩展 HID 报告:在 apps/app/bsp/common/usb/device/ 下新增/修改设备类驱动(如 custom_hid.c),定义新的 Report ID 与报告描述符;BLE 侧保证 Report Map 与之一致;
  3. 对接私有协议:RCSP HID 模块(rcsp_hid_inter.c/h)是杰理生态扩展面,可在其上增加私有命令到 HID 报告的映射;
  4. 调整连接生命周期:修改 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 协议族(见对应目录文档)
Next
键盘、翻页器与遥控应用