- 新增 ble-hid-keyboard.md 文件,详细描述 BLE HID 键盘的背景、目标、系统架构、HID 报告格式及对外 API 规格 - 包含实现步骤和配置要求,指导开发者如何将 ESP32-S3 开发板实现为标准 BLE HID 键盘
13 KiB
BLE HID 键盘实现规格
项目:HeavenlyHeroStar
目标芯片:ESP32-S3
ESP-IDF:6.0.1
BLE 栈:NimBLE(外设模式)
状态:待实现
1. 背景与目标
1.1 现状
当前 main/services/ble_manager.c 实现为自定义 GATT 外设:
- 服务 UUID:
0xFFF0 - 特征 UUID:
0xFFF1(Read / 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 广播数据
除设备名 HeavenlyHeroStar(CONFIG_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 Report(8 字节)
与 USB HID 标准键盘 boot/report 格式一致:
| 偏移 | 字段 | 说明 |
|---|---|---|
| 0 | modifier | 修饰键位图 |
| 1 | reserved | 保留,填 0 |
| 2–7 | 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 20–50 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);
连接状态与 LED(BOARD_PIN_LED,GPIO16)逻辑保持不变。
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.defaults 或 idf.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
- 从 IDF demo 迁移 Report Map、HID GATT 服务定义
- 实现
ble_hid_init():注册 GATT 服务,保存s_hid_report_handle - 实现
ble_hid_send_report():ble_gatts_notify_custom() - 实现
ble_hid_send_combo():press →vTaskDelay(20ms)→ release - 在
CMakeLists.txt添加"services/ble_hid.c"
阶段 2:改造 ble_manager.c
- 移除现有
0xFFF0/0xFFF1自定义 GATT(阶段一) gatt_svc_init()替换为ble_hid_init()- 广播增加
0x1812服务 UUID gap_event_handler增加BLE_GAP_EVENT_SUBSCRIBE处理- 保留连接 / 断开 / 重广播 / 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);
验证顺序:
- 烧录,串口确认
advertising as "HeavenlyHeroStar" - Windows:设置 → 蓝牙 → 添加设备 → 配对键盘
- 打开记事本并聚焦
- 按 BOOT:验证快捷键(建议先用 F5 等可见效果)
阶段 4:NVS 快捷键表
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_SUBSCRIBE、ble_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. 风险与约束
- HID 不能承载配置协议:网页改映射必须走 NVS + 独立配置通道。
- 配对与配置是两件事:用户需先在 OS 中配对键盘,配置页单独访问。
- 安全:配对后设备可向任意焦点窗口输入;丢失设备需用户在系统中删除配对。
- 资源:ESP32-S3 + LVGL + BLE HID 可行;若 NimBLE host 任务栈不足需调大。
- WiFi + BLE 共存:启用网页配置时需额外验证射频共存与内存。
11. 参考
- ESP-IDF 路径:
C:\esp\v6.0.1\esp-idf\examples\bluetooth\nimble\*hid* - 工程 BLE 入口:
main/services/ble_manager.c - 板级 GPIO:
main/bsp/board_config.h(BOOT=GPIO0, KEY=GPIO4, LED=GPIO16) - USB HID Usage Tables:Keyboard/Keypad Page (0x07)
修订记录
| 版本 | 日期 | 说明 |
|---|---|---|
| 1.0 | 2026-07-01 | 初版:BLE HID 实现步骤与架构规格 |