Files
rsgltzyd 52546e35fe feat: 添加 BLE HID 键盘实现规格文档
- 新增 ble-hid-keyboard.md 文件,详细描述 BLE HID 键盘的背景、目标、系统架构、HID 报告格式及对外 API 规格
- 包含实现步骤和配置要求,指导开发者如何将 ESP32-S3 开发板实现为标准 BLE HID 键盘
2026-07-01 23:05:49 +08:00

13 KiB
Raw Permalink Blame History

BLE HID 键盘实现规格

项目:HeavenlyHeroStar
目标芯片:ESP32-S3
ESP-IDF6.0.1
BLE 栈:NimBLE(外设模式)
状态:待实现

1. 背景与目标

1.1 现状

当前 main/services/ble_manager.c 实现为自定义 GATT 外设:

  • 服务 UUID0xFFF0
  • 特征 UUID0xFFF1Read / Write
  • 无 Notify / HID 能力
  • 电脑端无法以系统键盘方式接收按键

1.2 目标

将开发板实现为标准 BLE HID 键盘(HOGP,使 Windows / macOS / Linux 在蓝牙配对后,将设备识别为键盘,无需安装 PC 端接收程序

扩展目标(后续阶段):

  • 快捷键映射保存在设备 NVS
  • 通过网页或 Web BLE 配置快捷键
  • 物理按键(BOOT / KEY)及触摸屏 UI 触发 HID 报告

1.3 方案选型结论

方案 说明 结论
A 自定义 GATT Notify + PC Python 脚本 适合快速验证,需 PC 常驻程序
C BLE HID 键盘 采用OS 原生输入,适合 macropad 产品形态
C + 配置通道 HID 执行 + GATT/WiFi 写 NVS 网页配置快捷键时采用

配置与执行分离:

配置阶段:网页 / Web BLE / 屏上 UI → 写入 NVS 快捷键表
使用阶段:按键 / 触摸 → 读 NVS → 发送 BLE HID Report → OS 输入

2. 系统架构

2.1 模块划分

main/services/
├── ble_manager.c      # GAP:广播、连接、LED、bonding
├── ble_manager.h
├── ble_hid.c          # HID GATT 服务、Report Map、发送报告
├── ble_hid.h
└── hid_keymap.c       # 扫描码表、shortcut → modifier + keycode(可选拆分)

2.2 数据流

sequenceDiagram
    participant User as 用户
    participant Btn as BOOT/KEY/UI
    participant FW as 固件
    participant NVS as NVS
    participant HID as BLE HID
    participant OS as 操作系统

    User->>FW: 网页/GATT 配置快捷键
    FW->>NVS: 保存映射表

    User->>Btn: 按下物理键或 UI 按钮
    Btn->>FW: 触发事件
    FW->>NVS: 查 shortcut
    FW->>HID: keyboard report (press)
    FW->>HID: keyboard report (release)
    HID->>OS: Notify Input Report
    OS->>User: 当前焦点窗口收到快捷键

2.3 GATT 服务设计

阶段一(M2):仅 HID

服务 / 特征 UUID 属性 用途
Human Interface Device 0x1812 Primary 标准 HID 服务
HID Information 0x2A4A Read 版本信息
Report Map 0x2A4B Read USB HID 描述符
HID Control Point 0x2A4C Write w/o resp Suspend / Exit
Report 0x2A4D Read, Notify 键盘 Input Report
Protocol Mode 0x2A4E Read, Write Boot / Report 协议

Report 特征需附带 Report Reference 描述符(Report ID = 1, Input)。

阶段二(M5,可选):配置服务

服务 / 特征 UUID 属性 用途
HHS Config 0xFFF0 Primary 厂商配置
Config Write 0xFFF1 Write 接收 shortcut blob / JSON
Config Read 0xFFF2 Read 读回当前配置(可选)

HID 与配置服务可 并存 于同一 GATT 表。

2.4 广播数据

除设备名 HeavenlyHeroStarCONFIG_ESP_BLE_DEVICE_NAME)外,广播包需包含 HID 服务 UUID,否则 Windows 可能无法识别为键盘:

static ble_uuid16_t adv_uuids16[] = { BLE_UUID16_INIT(0x1812) };
fields.uuids16 = adv_uuids16;
fields.num_uuids16 = 1;
fields.uuids16_is_complete = 1;

3. HID 键盘报告格式

3.1 Input Report8 字节)

与 USB HID 标准键盘 boot/report 格式一致:

偏移 字段 说明
0 modifier 修饰键位图
1 reserved 保留,填 0
27 keycodes[6] 最多 6 个同时按下的键,通常仅用 keycodes[0]
#define HID_MOD_LCTRL  0x01
#define HID_MOD_LSHIFT 0x02
#define HID_MOD_LALT   0x04
#define HID_MOD_LGUI   0x08   /* Win / Command */

typedef struct __attribute__((packed)) {
    uint8_t modifier;
    uint8_t reserved;
    uint8_t keycodes[6];
} hid_keyboard_report_t;

3.2 发送组合键流程

组合键必须发送 按下释放 两帧,否则 OS 认为键仍按住:

1. report = { modifier, 0, keycode, 0, 0, 0, 0, 0 }
2. delay 2050 ms
3. report = { 0, 0, 0, 0, 0, 0, 0, 0 }   /* 全零 = 释放 */

示例:Ctrl+C

按下:modifier=0x01, keycode=0x06
释放:全零

3.3 常用扫描码(Keyboard/Keypad Page 0x07

keycode
C 0x06
S 0x16
D 0x07
F5 0x3E
Enter 0x28

完整表见 USB HID Usage Tables

3.4 Report Map

不要手写。从 ESP-IDF 官方 HID 示例复制 report_map[] 字节数组:

cd C:\esp\v6.0.1\esp-idf\examples\bluetooth\nimble
dir /s /b *hid*

4. 对外 API 规格

4.1 ble_hid.h

#pragma once
#include <stdint.h>
#include <stdbool.h>

int ble_hid_init(void);
bool ble_hid_ready(void);

int ble_hid_key_press(uint8_t modifier, uint8_t keycode);
int ble_hid_key_release(void);
int ble_hid_send_combo(uint8_t modifier, uint8_t keycode);
函数 说明
ble_hid_init 注册 HID GATT 服务,在 ble_manager_init 中调用
ble_hid_ready 已连接 Host 已订阅 Report Notify
ble_hid_send_combo 发送按下 → 延迟 → 释放,供按键/UI 调用

4.2 ble_manager.h(保留)

void ble_manager_init(void);
void ble_manager_set_status_cb(ble_status_cb_t cb);
bool ble_manager_is_connected(void);

连接状态与 LEDBOARD_PIN_LEDGPIO16)逻辑保持不变。

4.3 就绪条件

gap_event_handler 中处理 BLE_GAP_EVENT_SUBSCRIBE

case BLE_GAP_EVENT_SUBSCRIBE:
    if (event->subscribe.attr_handle == s_hid_report_handle) {
        s_notify_enabled = event->subscribe.cur_notify;
    }
    break;
bool ble_hid_ready(void)
{
    return s_connected && s_notify_enabled;
}

5. sdkconfig 配置

sdkconfig.defaultsidf.py menuconfig 中确认:

CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y

# Windows 重连稳定性
CONFIG_BT_NIMBLE_SM_LEGACY=y
CONFIG_BT_NIMBLE_SM_SC=y
CONFIG_BT_NIMBLE_NVS_PERSIST=y

ble_manager_init 中 Security Manager

ble_hs_cfg.sm_io_cap = BLE_HS_IO_NO_INPUT_OUTPUT;  /* Just Works */
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_sc = 1;

保留 ble_store_config_init() 以持久化 bonding 信息。

可选(配置通道 / 较大 payload):

CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=256

若后续启用 WiFi + BLE 共存:

CONFIG_ESP_COEX_ENABLED=y

修改后执行:

idf.py fullclean
idf.py reconfigure
idf.py build

6. 实现步骤

阶段 0:准备与基线

步骤 内容
0.1 $IDF_PATH/examples/bluetooth/nimble/ 找到 HID demo,单独编译烧录
0.2 Windows 配对 demo,在记事本验证能输入字符
0.3 确认本工程 ESP-IDF 6.0.1、IDF_TARGET=esp32s3、NimBLE 已启用

策略选择:

  • 阶段一:删除现有 0xFFF0/0xFFF1 自定义服务,仅保留 HID(最快跑通)
  • 阶段二HID + 恢复 0xFFF0 配置服务(网页 / Web BLE

阶段 1:新增 ble_hid.c

  1. 从 IDF demo 迁移 Report Map、HID GATT 服务定义
  2. 实现 ble_hid_init():注册 GATT 服务,保存 s_hid_report_handle
  3. 实现 ble_hid_send_report()ble_gatts_notify_custom()
  4. 实现 ble_hid_send_combo()press → vTaskDelay(20ms) → release
  5. CMakeLists.txt 添加 "services/ble_hid.c"

阶段 2:改造 ble_manager.c

  1. 移除现有 0xFFF0/0xFFF1 自定义 GATT(阶段一)
  2. gatt_svc_init() 替换为 ble_hid_init()
  3. 广播增加 0x1812 服务 UUID
  4. gap_event_handler 增加 BLE_GAP_EVENT_SUBSCRIBE 处理
  5. 保留连接 / 断开 / 重广播 / LED 逻辑

初始化顺序:

nimble_port_init();
ble_svc_gap_init();
ble_svc_gap_device_name_set(BLE_DEVICE_NAME);
ble_hid_init();
ble_hs_cfg.sync_cb = on_stack_sync;
ble_hs_cfg.reset_cb = on_stack_reset;
ble_hs_cfg.sm_io_cap = BLE_HS_IO_NO_INPUT_OUTPUT;
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_sc = 1;
ble_store_config_init();
nimble_port_freertos_init(nimble_host_task);

阶段 3:按键触发(最小验证)

app_main.c 中:

#include "boot_button.h"
#include "ble_hid.h"

static void on_boot_press(void)
{
    if (ble_hid_ready()) {
        ble_hid_send_combo(HID_MOD_LCTRL, HID_KEY_C);
    } else {
        ESP_LOGW("app", "HID not ready");
    }
}

/* ble_manager_init() 之后 */
boot_button_init(on_boot_press);

验证顺序:

  1. 烧录,串口确认 advertising as "HeavenlyHeroStar"
  2. Windows:设置 → 蓝牙 → 添加设备 → 配对键盘
  3. 打开记事本并聚焦
  4. 按 BOOT:验证快捷键(建议先用 F5 等可见效果)

阶段 4NVS 快捷键表

4.1 数据结构

typedef struct {
    uint8_t source;     /* 0=BOOT, 1=KEY(GPIO4), 2+=UI slot */
    uint8_t modifier;
    uint8_t keycode;
    uint8_t reserved;
} shortcut_entry_t;

#define SHORTCUT_MAX 16

4.2 存储

NVS namespace: "shortcuts"
Key:           "map"
Format:        shortcut_entry_t[SHORTCUT_MAX] blob

启动时加载;无有效数据时使用默认映射(例如 BOOT → F5)。

4.3 触发

shortcut_entry_t *e = shortcut_find(SOURCE_BOOT);
if (e && ble_hid_ready()) {
    ble_hid_send_combo(e->modifier, e->keycode);
}

阶段 5:网页 / 配置通道(后续)

方式 通道 说明
WiFi SoftAP + HTTP esp_http_server PC/手机浏览器 POST 配置,需 BOARD_WIFI_ENABLED=1
Web Bluetooth 自定义 GATT 0xFFF0 Write 无需 WiFi,需 Chrome/Edge
屏上 LVGL UI 本地 已有触摸屏,可不依赖网页

网页仅写入 NVS;运行时仍通过 HID 发送,PC 无需安装程序。


7. 与现有代码对应关系

现有 HID 实施后
0xFFF0/0xFFF1 Read/Write 阶段一删除;阶段二改为配置专用
chr_access() 移至配置服务或删除
ble_manager_is_connected() 保留
notify_status() + LED 保留
Notify 方案 ble_manager_send() 替换为 ble_hid_send_combo()
boot_button.c 已具备框架,app_main 中注册回调
BOARD_WIFI_ENABLED=0 网页配置阶段再启用

8. Windows 联调清单

现象 可能原因 处理
搜不到设备 广播未含 0x1812 检查 adv fields
能连上但无按键 未订阅 Notify BLE_GAP_EVENT_SUBSCRIBEble_hid_ready()
键一直按住 未发 release 报告 发送全零 8 字节 report
配对后难重连 bonding 未持久化 确认 ble_store_config_init()、NVS
部分键无效 扫描码错误 对照 HID Usage Table
连接后很快断开 reason=531 远端主动断开 531 = Remote User Terminated;完成配对后再测
安全软件拦截 蓝牙键盘权限 检查杀毒 / 企业策略

9. 里程碑

编号 交付物 验收标准
M1 IDF HID demo 独立跑通 Windows 配对,记事本可输入
M2 ble_hid.c 迁入工程 替换自定义 GATT,编译通过
M3 BOOT → ble_hid_send_combo 记事本 / 浏览器验证 F5 或 Ctrl+C
M4 NVS 快捷键表 多按键 / UI 槽位可配置
M5 配置通道(GATT 或 WiFi 网页) 浏览器修改映射,重启后仍生效

建议严格按 M1 → M5 顺序推进,避免同时调试 HID、NVS、网页。


10. 风险与约束

  1. HID 不能承载配置协议:网页改映射必须走 NVS + 独立配置通道。
  2. 配对与配置是两件事:用户需先在 OS 中配对键盘,配置页单独访问。
  3. 安全:配对后设备可向任意焦点窗口输入;丢失设备需用户在系统中删除配对。
  4. 资源ESP32-S3 + LVGL + BLE HID 可行;若 NimBLE host 任务栈不足需调大。
  5. WiFi + BLE 共存:启用网页配置时需额外验证射频共存与内存。

11. 参考

  • ESP-IDF 路径:C:\esp\v6.0.1\esp-idf\examples\bluetooth\nimble\*hid*
  • 工程 BLE 入口:main/services/ble_manager.c
  • 板级 GPIOmain/bsp/board_config.hBOOT=GPIO0, KEY=GPIO4, LED=GPIO16
  • USB HID Usage TablesKeyboard/Keypad Page (0x07)

修订记录

版本 日期 说明
1.0 2026-07-01 初版:BLE HID 实现步骤与架构规格