杰理 SDK 文档中心
首页
首页
  • 概述

    • SDK 概览与产品定位
    • 支持芯片平台与蓝牙认证
    • SDK 架构与目录分层
  • 快速开始

    • 环境搭建与编译工具链
    • 编译构建系统
    • 板级工程与配置
    • 烧录与固件升级工具
  • 应用工程

    • 应用选择与工程总览
    • SPP + BLE 数传应用框架
    • 透传与 AT 指令示例
    • BLE 广播/中心与定位示例
    • 2.4G 私有协议与 Dongle 示例
    • 云平台接入示例
    • HID 人机交互应用框架
    • HID 示例工程(键盘/鼠标/遥控器/手柄)
    • Bluetooth Mesh 应用框架
    • Mesh 模型与 Mesh DFU 固件升级
    • Mesh 音频编解码演示
  • 芯片平台与硬件抽象

    • 芯片平台总览与差异
    • 音频编解码与时钟管理
    • 外设驱动接口(ADC/IIC/SPI/PWM/LED/充电)
    • 芯片配置工具与下载支持
  • 蓝牙协议栈

    • 蓝牙控制器层(btctrler)
    • 蓝牙协议栈与 Profile(btstack)
    • 蓝牙模块选择与配置
  • 媒体与音频框架

    • 音频流框架
    • 音频编解码与 A2DP 媒体
    • 音频效果处理(EQ/频谱/变调/环绕/超低音)
    • 本地 TWS 与音频同步
  • 系统服务与运行时

    • 实时操作系统与任务调度
    • 消息事件机制
    • 电源管理与低功耗
    • 存储与配置系统
    • 设备驱动框架(USB/RTC)
  • 应用公共组件

    • 音频应用组件
    • 设备外设抽象(按键/触摸/传感器/存储)
    • 蓝牙公共模块与消息联动
    • 调试与配置组件
    • 杰理关键词唤醒(jl_kws)
  • 第三方协议与云平台接入

    • 杰理 RCSP 私有协议
    • 低功耗蓝牙 Mesh 方案(llsync_mesh)
    • Sig Mesh 方案
    • 涂鸦协议接入
    • 腾讯连连接入
    • 华为 HiLink 接入
  • 固件升级与维护

    • OTA 升级机制
    • 升级补丁与版本维护
    • 升级工具链(BLE OTA / USB Dongle OTA)
  • 文档与开发资源

    • 数据手册与架构文档
    • 协议与云平台开发文档
    • 常见问题与技术支持

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 人机交互设备"通常需要同时打通多条通路:

  1. EDR 通路:经典蓝牙 HID Device Profile,让手机、平板、PC 等 Host 通过 SDP 发现设备并建立 HID 控制通道与中断通道,用于传输键盘/鼠标报告;
  2. USB 通路:SoC 作为 USB Device 枚举成 HID 设备(标准键盘示例),或作为 USB Host 读取外部 HID 设备;
  3. BLE 通路:通过 GATT 的 HID Service(UUID 0x1812)提供服务;
  4. 私有协议通路:杰理 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

实现要点:

  1. user_data_write_sub 先检查剩余空间是否足够(len + 2,多出的 2 字节用于保存长度头),空间不足直接返回 0,避免覆盖未发送数据;
  2. 写入在 OS_ENTER_CRITICAL()/OS_EXIT_CRITICAL() 临界区内完成,防止与读取侧竞争环形缓冲的读写指针;
  3. 写入成功后立即调用 user_data_try_send() 尝试发送;
  4. user_data_try_send 首先检查 bt_send_busy,忙则返回(数据留在缓冲中等下一轮触发);否则置忙并取出一包,调用 user_hid_send_data(底层 L2CAP 中断通道发送);返回真表示发送完成、清忙,可继续发下一包;
  5. 环形缓冲条目格式为 [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.cbpAC632N 芯片 HID 工程
apps/hid/board/bd29/AC631N_hid.cbpAC631N 芯片 HID 工程
apps/hid/board/br23/AC635N_hid.cbpAC635N 芯片 HID 工程
apps/hid/board/br25/AC636N_hid.cbpAC636N 芯片 HID 工程
apps/hid/board/br30/AC637N_hid.cbpAC637N 芯片 HID 工程
apps/hid/board/br34/AC638N_hid.cbpAC638N 芯片 HID 工程
apps/hid/examples/standard_keyboard/标准键盘示例(USB + HID 报告)
apps/hid/include/edr_hid_user.hEDR HID 模块对外接口
apps/hid/modules/bt/edr_hid_user.cEDR 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_ENABLE0/1由 board 配置使能 EDR 蓝牙;edr_hid_user.c 编译前提之一
USER_SUPPORT_PROFILE_HID0/10声明支持 HID Profile;edr_hid_user.c 编译前提之二
CONFIG_APP_STANDARD_KEYBOARD0/10标准键盘应用使能,编译 USB HID 设备示例
TCFG_PC_ENABLE0/1由 board 配置使能 USB PC 枚举
TCFG_USB_SLAVE_USER_HID0/10使能 USB 从机 HID 类
TEST_USER_HID_EN0/10测试模式开关(edr_hid_user.c 内定义)
HID_SEND_MAX_SIZE1616单包 HID 数据最大长度(含 2 字节头)
HID_TMP_BUFSIZE128128发送环形缓冲容量(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

  1. 新增 HID 报告类型:基于 hid_data_info_t(report_type + report_id + 数据)扩展,通过 edr_hid_data_send_ext 上报 DATA_OUTPUT/DATA_FEATURE,实现与主机的双向交互(如接收键盘 LED、消费者控制反馈)。
  2. 多通路复用:同一 Report Descriptor 通过 user_hid_set_ReportMap(EDR)与 usb_hid_set_repport_map(USB)分别注入,应用层按键业务可同时驱动蓝牙与 USB 通路。
  3. 设备类型识别:sdp_callback_remote_type 的 COMMON_EVENT_EDR_REMOTE_TYPE 事件可扩展为"根据 Android/iOS 切换按键映射"的产品逻辑。
  4. LED/状态回调:user_led_status_callback 与 usb_hid_state_callback 分别挂接蓝牙侧与 USB 侧的 LED 指示与枚举状态,可扩展为呼吸灯、连接提示等 UI 行为。
  5. 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)
Prev
云平台接入示例
Next
HID 示例工程(键盘/鼠标/遥控器/手柄)