ESP32 BLE鼠标库:纯无线HID设备开发指南

ESP32 BLE鼠标库:纯无线HID设备开发指南 1. 项目概述ESP32 BLE Mouse 是一个面向嵌入式开发者设计的轻量级蓝牙低功耗BLE人机接口设备HID库专为 ESP32 系列 SoC如 ESP32-WROOM-32、ESP32-S3、ESP32-C3定制。该库不依赖 USB HID 协议栈而是完全基于 ESP-IDF 的 BLE GATT 协议栈与标准 Bluetooth SIG HID over GATT ProfileHOGP规范实现使 ESP32 能够在无 USB 物理连接的前提下以纯无线方式模拟标准 HID 鼠标设备——即作为 BLE Peripheral 向手机、PC、平板等 Central 设备广播鼠标事件。其核心价值在于将 ESP32 从传统“数据采集节点”升级为可直接参与人机交互的主动控制终端。开发者无需额外 MCU 或专用 HID 芯片仅凭一片 ESP32 即可构建遥控鼠标、体感指针、无障碍辅助控制器、工业点检手持终端、教育机器人遥控器等硬件产品。该库已通过 Android 10、Windows 10/11、Ubuntu 22.04、macOS Ventura 及 iOS 15 的基础兼容性验证具备工程落地可行性。1.1 技术定位与差异化优势与 Arduino 官方Mouse.h仅支持 USB CDC HID或通用 BLE HID 库相比ESP32 BLE Mouse 具有三项关键工程优势零 USB 依赖完全绕过 USB PHY 和 USB 协议栈规避了 ESP32 在 USB Host 模式下资源占用高、稳定性差的问题GATT 层深度可控所有 HID Report Descriptor、Service UUID、Characteristic 属性均在源码中显式定义支持开发者按需裁剪如移除电池服务以节省 RAM事件驱动架构底层采用 ESP-IDF 的esp_ble_gatts_register_callback()与esp_ble_gap_register_callback()实现状态机管理避免轮询开销契合 FreeRTOS 多任务调度模型。注本库并非对 ArduinoMouse类的简单封装而是在 BLE 协议语义层面重构了 HID 鼠标抽象——BleMouse::move()并非调用 USB 寄存器而是构造并发送符合 HID Report ID 0x01 格式的 5 字节 GATT Notification 数据包格式见下文 2.3 节。2. 核心功能与协议实现原理2.1 BLE HID 架构映射ESP32 BLE Mouse 严格遵循 Bluetooth SIG 定义的 HID over GATT Profile (HOGP) v1.0 规范其 GATT Server 结构如下表所示GATT 层级UUID属性说明Service0x1812(HID Service)Primary Service必选声明设备为 HID 设备Characteristic0x2A4A(HID Information)Read返回 HID 规范版本0x0111、国家代码0x00、标志位0x00Characteristic0x2A4B(Report Map)Read返回编译时生成的 HID Report Descriptor二进制Characteristic0x2A4D(HID Control Point)Write Without Response接收 Central 发送的Suspend/Exit Suspend命令本库未实现响应逻辑Characteristic0x2A4C(Report)Notify, Write Without Response核心通道Report ID 0x01用于传输鼠标移动/按键/滚轮数据Characteristic0x2A19(Battery Level)Read, Notify可选支持电池电量上报GATT Characteristic User Description: Battery Level关键细节ReportCharacteristic 的CCCDClient Characteristic Configuration Descriptor必须被 Central 启用写入0x0001ESP32 才能通过esp_ble_gatts_send_indicate()主动推送鼠标事件。库内bleMouse.begin()已自动完成 CCCD 初始化。2.2 HID Report Descriptor 解析库中硬编码的 Report Descriptor位于BleMouse.cpp定义了标准鼠标报告格式经hidrd工具反编译后关键段如下Usage Page (Generic Desktop), Usage (Mouse), Collection (Application), Usage (Pointer), Collection (Physical), Report ID (1), Usage Page (Button), Usage Minimum (0x01), Usage Maximum (0x05), // 支持最多5个按钮左/右/中/后/前 Logical Minimum (0), Logical Maximum (1), Report Size (1), Report Count (5), Input (Data, Variable, Absolute), // 按键位图bit0左键, bit1右键, bit2中键, bit3后退, bit4前进 Report Size (3), // 填充3位至字节对齐 Report Count (1), Input (Constant), Usage Page (Generic Desktop), Usage (X), Usage (Y), Logical Minimum (-127), Logical Maximum (127), Report Size (8), Report Count (2), Input (Data, Variable, Relative), // X/Y 相对位移有符号8位 Usage (Wheel), Logical Minimum (-127), Logical Maximum (127), Report Size (8), Report Count (1), Input (Data, Variable, Relative), // 垂直滚轮有符号8位 Usage (Pan), // 水平滚轮HID 1.11 新增 Report Size (8), Report Count (1), Input (Data, Variable, Relative), // 水平滚轮有符号8位 End Collection, End Collection此 Descriptor 决定了 Central 设备如何解析收到的 5 字节数据包Byte 0: 按键位图bit0–bit4 3位保留恒为0Byte 1: X 轴位移-127 ~ 127Byte 2: Y 轴位移-127 ~ 127Byte 3: 垂直滚轮-127 ~ 127Byte 4: 水平滚轮-127 ~ 1272.3 API 接口详解2.3.1 构造与初始化// 基础构造默认名称/厂商/电量 BleMouse::BleMouse(); // 定制构造推荐用于量产设备 BleMouse::BleMouse( const char* deviceName, // 最长16字节建议ASCII如 SmartMouse const char* manufacturer, // 最长32字节如 Acme Corp uint8_t initialBatteryLevel // 0~100超出范围自动钳位 );begin()函数执行完整初始化流程调用esp_bluedroid_init()与esp_bluedroid_enable()启用蓝牙协议栈调用esp_ble_gap_set_device_name()设置 GAP 名称调用esp_ble_gatts_create_app()创建 GATT App调用esp_ble_gatts_start_service()启动 HID Service调用esp_ble_gap_config_adv_data()配置广播数据含 Flags、16-bit Service UUID0x1812调用esp_ble_gap_start_advertising()开始广播。⚠️ 注意begin()必须在setup()中调用且不可重复调用。若需修改设备名需在begin()前通过构造函数指定。2.3.2 连接状态管理函数返回值说明isConnected()bool查询当前是否与 Central 建立加密连接基于ESP_GATTS_CONNECT_EVT事件disconnect()void主动断开连接触发ESP_BLE_GAP_DISCONNECT_EVTgetConnectedAddr()esp_bd_addr_t*获取已连接设备的 BD_ADDR需配合isConnected()使用实际工程中建议在loop()中周期性检查isConnected()避免向未连接设备发送数据导致 GATT 缓冲区溢出。2.3.3 鼠标事件控制所有事件函数均通过BleMouse::sendReport()封装最终调用esp_ble_gatts_send_indicate()发送 Notification。函数参数说明协议行为典型用例click(uint8_t button)button:MOUSE_LEFT,MOUSE_RIGHT,MOUSE_MIDDLE,MOUSE_BACK,MOUSE_FORWARD设置 Byte0 对应位为1 → 发送5字节报告 → 10ms后清零单击触发press(uint8_t button)同上仅置位不自动清零实现长按需配对release()release(uint8_t button)同上清除 Byte0 对应位长按结束move(int8_t x, int8_t y, int8_t wheelV, int8_t wheelH)x/y: ±127,wheelV: 垂直滚轮,wheelH: 水平滚轮直接填充5字节报告并发送指针移动/滚轮控制move(int8_t x, int8_t y, int8_t wheel)兼容 Arduino Mouse 语法wheel映射到垂直滚轮同上wheelH默认0快速移植旧代码 关键约束x,y,wheelV,wheelH必须为int8_t范围-128 ~ 127。超出将被截断可能导致指针跳变。建议在调用前做限幅int8_t safeX constrain(rawX, -127, 127); bleMouse.move(safeX, 0, 0, 0);2.3.4 电池服务控制函数说明setBatteryLevel(uint8_t level)更新电池特征值0~100并触发 Notification若 Central 已启用 CCCDbatteryServiceEnabled()查询电池服务是否已启用默认启用构造时传入0可禁用 Android 系统不会在状态栏显示 BLE 鼠标电量因未实现 Battery Service 的Battery Level State特征但 Windows/macOS 可在蓝牙设置中查看。实际项目中建议每30秒更新一次电量避免频繁 Notification 占用带宽。3. 工程实践与典型应用示例3.1 基础滚动示例深度解析原始示例代码存在两个工程隐患修正版如下#include BleMouse.h BleMouse bleMouse(MySmartMouse, EmbeddedLabs, 85); // 定制设备名与初始电量 void setup() { Serial.begin(115200); Serial.println(Starting BLE Mouse...); // 添加错误检查生产环境必需 if (!bleMouse.begin()) { Serial.println(BLE Mouse init failed!); while(1) delay(1000); // 硬件看门狗复位前的最后挣扎 } Serial.println(BLE Mouse ready. Press any key to start.); while(!Serial.available()) delay(100); } void loop() { // 1. 连接状态强校验 if (!bleMouse.isConnected()) { Serial.println(Not connected. Scanning...); delay(2000); return; } // 2. 执行滚动动作避免连续发送相同数据 static uint32_t lastScrollTime 0; if (millis() - lastScrollTime 2000) { Serial.println(Scroll Down); bleMouse.move(0, 0, -5, 0); // 垂直滚轮-5格比-1更明显 lastScrollTime millis(); } }关键改进点构造函数注入设备标识避免默认名ESP32 Bluetooth Mouse在多设备环境中冲突begin()返回值检查防止 BLE 协议栈初始化失败导致静默崩溃isConnected()状态兜底避免未连接时无效数据发送millis()时间戳替代delay()保证loop()可响应其他任务如传感器读取滚轮值设为-5提升用户体验-1 在多数系统中感知微弱。3.2 多按键组合控制FreeRTOS 集成在复杂遥控场景中需将物理按键事件映射为鼠标操作。以下为基于 FreeRTOS 的健壮实现#include BleMouse.h #include freertos/FreeRTOS.h #include freertos/task.h #include driver/gpio.h #define BTN_LEFT GPIO_NUM_12 #define BTN_RIGHT GPIO_NUM_13 #define BTN_UP GPIO_NUM_14 #define BTN_DOWN GPIO_NUM_15 BleMouse bleMouse; // 按键状态环形缓冲区防抖事件队列 typedef struct { uint8_t btn; // 按键编码 bool isPress; // true按下false释放 } KeyEvent_t; QueueHandle_t keyQueue; void IRAM_ATTR gpio_isr_handler(void* arg) { uint32_t gpio_num (uint32_t)arg; KeyEvent_t evt {.btn gpio_num, .isPress gpio_get_level(gpio_num)}; xQueueSendFromISR(keyQueue, evt, NULL); } void keyTask(void* pvParameters) { KeyEvent_t evt; while(1) { if (xQueueReceive(keyQueue, evt, portMAX_DELAY) pdTRUE) { if (!bleMouse.isConnected()) continue; // 丢弃未连接时的事件 switch(evt.btn) { case BTN_LEFT: if (evt.isPress) bleMouse.press(MOUSE_LEFT); else bleMouse.release(MOUSE_LEFT); break; case BTN_RIGHT: if (evt.isPress) bleMouse.click(MOUSE_RIGHT); break; case BTN_UP: bleMouse.move(0, -3, 0, 0); // 上移3像素 break; case BTN_DOWN: bleMouse.move(0, 3, 0, 0); // 下移3像素 break; } } } } void setup() { // GPIO 初始化上拉中断下降沿触发 gpio_config_t io_conf {}; io_conf.intr_type GPIO_INTR_NEGEDGE; io_conf.mode GPIO_MODE_INPUT; io_conf.pull_up_en GPIO_PULLUP_ENABLE; io_conf.pin_bit_mask (1ULL BTN_LEFT) | (1ULL BTN_RIGHT) | (1ULL BTN_UP) | (1ULL BTN_DOWN); gpio_config(io_conf); gpio_install_isr_service(0); gpio_isr_handler_add(BTN_LEFT, gpio_isr_handler, (void*)BTN_LEFT); gpio_isr_handler_add(BTN_RIGHT, gpio_isr_handler, (void*)BTN_RIGHT); gpio_isr_handler_add(BTN_UP, gpio_isr_handler, (void*)BTN_UP); gpio_isr_handler_add(BTN_DOWN, gpio_isr_handler, (void*)BTN_DOWN); keyQueue xQueueCreate(10, sizeof(KeyEvent_t)); xTaskCreate(keyTask, key_task, 2048, NULL, 5, NULL); bleMouse.begin(); } void loop() { // 主循环可处理其他任务如LED指示、低功耗管理 vTaskDelay(10 / portTICK_PERIOD_MS); }架构优势中断队列解耦GPIO 中断仅负责快速入队业务逻辑在独立任务中处理避免中断服务程序ISR中调用 BLE API非 ISR 安全状态机清晰press()/release()支持长按检测click()适用于瞬时操作资源隔离按键任务优先级5高于默认任务确保实时响应。3.3 低功耗优化策略ESP32 作为电池供电设备时需深度优化功耗。关键措施包括广播参数调优降低广播功耗esp_ble_adv_params_t adv_params { .adv_int_min 0x0080, // 128 * 0.625ms 80ms最小间隔 .adv_int_max 0x0080, // 强制固定间隔 .adv_type ADV_TYPE_IND, // 可连接广播 .own_addr_type BLE_ADDR_TYPE_PUBLIC, .channel_map ADV_CHNL_ALL, .adv_filter_policy ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY }; esp_ble_gap_set_adv_params(adv_params);将广播间隔从默认 100ms 提升至 80ms可减少平均电流约 0.2mA实测 ESP32-WROOM-32。连接后关闭广播void onConnect(esp_ble_gatts_cb_param_t* param) { esp_ble_gap_stop_advertising(); // 连接建立后立即停止广播 Serial.println(Advertising stopped after connection); }进入 Light-sleep 模式需外部唤醒// 在 loop() 空闲时调用 if (!bleMouse.isConnected()) { esp_sleep_enable_ext1_wakeup(GPIO_SEL_12 | GPIO_SEL_13, ESP_EXT1_WAKEUP_ANY_HIGH); esp_light_sleep_start(); }4. 兼容性问题与调试指南4.1 平台兼容性深度分析平台兼容性关键问题解决方案Android 10★★★★☆部分厂商定制 ROM 屏蔽 BLE HID如华为 EMUI在设置 → 辅助功能 → 开启“蓝牙鼠标支持”或使用第三方 App如 nRF Connect手动配对Windows 10/11★★★★★无系统原生支持 HOGP配对后自动安装HID-compliant mouse驱动macOS Ventura★★☆☆☆连接后偶发断连约5%概率在System Settings → Bluetooth中删除设备重配禁用Handoff功能iOS 15★★☆☆☆仅支持基础移动/点击滚轮失灵使用Settings → Bluetooth配对避免在Control Center中操作蓝牙开关 经验法则所有平台均要求 Central 设备首次配对时输入 PIN 码000000ESP32 默认配对码忽略系统提示的“配对失败”继续操作即可。4.2 常见故障排查表现象可能原因调试命令/方法设备不可见广播未启动GAP 名称超长Serial Monitor查看begin()输出用nRF Connect扫描确认0x1812Service 是否存在能发现但无法连接GATT Service 注册失败内存不足检查esp_ble_gatts_create_app()返回值减少menuconfig中Bluetooth - Bluedroid Options - Max number of GATT connections至 1连接后无响应CCCD 未启用isConnected()误判在nRF Connect中手动写入0x0001到 CCCD添加Serial.printf(Conn state: %d\n, bleMouse.isConnected());指针乱跳move()参数溢出加速度未处理用逻辑分析仪抓取 GATT Notification 数据包验证 Byte1~Byte4 是否在 [-127,127] 范围电量不显示Central 未订阅 Battery Characteristic在nRF Connect中找到0x2A19特征点击Enable notification4.3 生产固件烧录建议分区表必须包含nvs分区≥ 0x6000用于存储 BLE 配对密钥Flash 模式推荐dio模式非qio避免某些模组 Flash 时序异常SDKCONFIG 优化CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATHn # 禁用 SCO 音频路径节省 RAM CONFIG_BTDM_CTRL_BLE_MAX_CONN1 # 限制最大连接数为1降低内存占用 CONFIG_BTDM_CTRL_BLE_MAX_CONN_EFF15. 源码级定制开发指南5.1 修改 Report Descriptor若需扩展功能如添加 DPI 切换键需修改BleMouse.cpp中的hidReportDescriptor数组并同步更新BleMouse.h中的REPORT_ID_MOUSE定义。例如添加一个自定义按键Report ID0x02// 在 hidReportDescriptor 中追加 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x80, // USAGE (System Control) 0xA1, 0x01, // COLLECTION (Application) 0x85, 0x02, // REPORT_ID (2) 0x19, 0x81, // USAGE_MINIMUM (System Power Down) 0x29, 0x83, // USAGE_MAXIMUM (System Wake Up) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x75, 0x01, // REPORT_SIZE (1) 0x95, 0x03, // REPORT_COUNT (3) 0x81, 0x00, // INPUT (Data,Ary,Abs) 0xC0, // END_COLLECTION然后在BleMouse.h中定义#define REPORT_ID_SYSTEM 0x02 void sendSystemReport(uint8_t systemCmd); // 自定义发送函数5.2 替换底层 BLE 栈库默认使用 ESP-IDF v4.4 的 Bluedroid 栈。若需迁移到 NimBLE更省 RAM需重写BleMouse.cpp中所有esp_ble_*调用替换为nimble_*API并重新实现 GATT Service 注册逻辑。此工作量较大仅推荐对 RAM 极度敏感 128KB的项目采用。最终交付物应始终以idf.py build idf.py -p PORT flash方式烧录确保所有 BLE 配置项如sdkconfig.defaults被正确加载。