Files
RemoteMapper-ESP32/DEVELOPMENT.md
T
2026-09-01 14:19:22 +08:00

26 KiB
Raw Blame History

RemoteMapper-ESP32 开发者手册与技术全景文档

小米蓝牙语音遥控器 2 Pro (RC003) ➔ USB 复合硬件桥接固件

适用固件版本:v1.0.0+ (Production Release)
目标主控硬件:ESP32-S3-DevKitC-1 N16R8(16MB Octal SPI Flash + 8MB Octal SPI PSRAM)
适配遥控型号:小米蓝牙语音遥控器 2 Pro(型号:RC003,铝合金金属机身款)
文档受众:嵌入式开发者、代码维护者与后续 AI Agent 协作开发


目录

  1. 系统整体架构与多核任务分配
  2. 硬件选型与存储分区设计 (16MB Flash)
  3. BLE 与 ATVV 语音协议栈全流程解析
  4. DSP 实时音频处理流水线算法设计
  5. 多触发按键映射状态机与键码引擎
  6. Flash NVS 存储引擎与开机自愈容灾机制
  7. USB 复合设备架构 (UAC 1.0 + HID + CDC)
  8. Wi-Fi 双模网络与嵌入式 Web 控制台
  9. PC 本地伴侣程序架构 (pc_companion)
  10. RESTful API 完整接口规范
  11. 源码文件索引与模块依赖关系
  12. 编译、烧录与测试指南
  13. 故障排查手册与常见问题 (Troubleshooting)

一. 系统整体架构与多核任务分配

ESP32-S3 配备 Xtensa® 双核 32 位 LX7 微处理器(主频 240MHz)。为了保证实时音频解码 0 掉帧、USB Isochronous 报文 1000Hz 零抖动、BLE 协议栈秒级响应以及 Web 异步请求不卡顿,系统采用 FreeRTOS 双核物理隔离调度架构:

┌────────────────────────────────────────────────────────────────────────────────────────┐
│                                ESP32-S3 核心分配与流水线                                  │
├───────────────────────────────────────────┬────────────────────────────────────────────┤
│           Core 0 (专职蓝牙与 DSP)          │         Core 1 (专职 USB、网络与业务)        │
├───────────────────────────────────────────┼────────────────────────────────────────────┤
│ • NimBLE Central 协议栈 (扫描/连接/绑定)   │ • TinyUSB 复合设备 (UAC Mic + HID + CDC)   │
│ • HOGP 原始报文解析 (订阅 0x2A4D)         │ • UAC 麦克风 1ms Isochronous 数据泵 (16kHz)│
│ • ATVV 语音握手 (0x0A/0x0C/0x0E/0x00)     │ • Multi-Trigger 状态机 (单击/长按/双击)    │
│ • 4-bit IMA-ADPCM 实时流式解码             │ • Flash NVS 存储引擎 & 自愈容灾校验        │
│ • Declip 尖峰消除 + 3-Tap FIR 低通滤波    │ • Wi-Fi 双模管理器 (AP 192.168.4.1 + STA)  │
│ • 动态 AGC 增益控制与软削波                │ • Captive Portal DNS 强制门户 (Port 53)    │
│ • 音频数据注入 SPSC 无锁环形缓冲区         │ • HTTP WebServer (Port 80 RESTful API)     │
│ • RGB 状态指示灯控制器 (WS2812 / GPIO48)  │ • Serial / CDC CLI 控制台指令调度          │
└───────────────────────────────────────────┴────────────────────────────────────────────┘
                                     │                     ▲
                                     └────── RingBuffer ───┘
                                         (4096 Samples PCM)

二. 硬件选型与存储分区设计 (16MB Flash)

1. 硬件连接规范

  • 主控型号:ESP32-S3-DevKitC-1 N16R8(16MB Flash + 8MB Octal PSRAM);
  • USB 接口:必须连接 ESP32-S3 开发板的 USB/OTG 接口(对应内部硬件 GPIO 19: D-,GPIO 20: D+),不可连接 UART 口;
  • 遥控器型号:小米蓝牙语音遥控器 2 Pro(型号:RC003)。
  • 供电:USB 5V 直接供电,典型运行功耗约 110~130mA。

2. 16MB Flash 分区表 (default_16MB.csv)

分区名 类型 子类型 起始偏移 (Offset) 大小 (Size) 用途说明
nvs data nvs 0x9000 24 KB (0x6000) BLE 配对密钥、Wi-Fi 配置、自定义按键存储
otadata data ota 0xf000 8 KB (0x2000) OTA 升级状态标记
app0 app ota_0 0x20000 6 MB (0x600000) 当前运行的主固件镜像
app1 app ota_1 0x620000 6 MB (0x600000) 后续 OTA 固件备用镜像
spiffs data spiffs 0xc20000 3.75 MB (0x3c0000) 嵌入式静态资源 / 扩展文件系统
coredump data coredump 0xfe0000 128 KB (0x20000) 系统崩溃转储诊断日志

三. BLE 与 ATVV 语音协议栈全流程解析

1. GATT 服务与特征值定义

  • HOGP 规范服务:UUID 0x1812 ➔ HID Report 特征值 0x2A4D(Notify,捕获按键原始报文);
  • ATVV 自定义服务:UUID ab5e0001-5a21-4f05-bc7d-af01f617b664
    • ab5e0002-5a21-4f05-bc7d-af01f617b664 (CMD 通道,Write Without Response,下发握手指令)
    • ab5e0003-5a21-4f05-bc7d-af01f617b664 (AUDIO 通道,Notify,接收 120 字节 ADPCM 音频帧)
    • ab5e0004-5a21-4f05-bc7d-af01f617b664 (CTL 通道,Notify,接收语音启停状态)

2. ATVV 握手与语音状态机时序

ESP32-S3 (Central)                                      小米遥控器 RC003 (Peripheral)
      │                                                           │
      │── 1. 扫描匹配 "MI RC" 或 MAC 前缀 "c0:5d:39" ─────────────►│
      │◄─ 2. 建立 BLE GATT 连接并启用 Bonding ─────────────────────│
      │                                                           │
      │── 3. 订阅 CTL(0x04)、AUDIO(0x03)、HOGP(0x2A4D) 特征值 ───►│
      │                                                           │
      │── 4. 发送 GET_CAPS: [0x0A, 0x01, 0x00, 0x00, 0x03, 0x03] ─►│
      │◄─ 5. 收到 CAPS_RESP [0x0B, ...]: 确认 16kHz ADPCM & 帧大小 ──│
      │                                                           │
      │── 6. 发送 MIC_OPEN: [0x0C, 0x00] ─────────────────────────►│
      │      (遥控器进入 HTT 按住说话待命模式)                     │
      │                                                           │
      │  ═════════════════ 用户按住遥控器语音键 ═════════════════   │
      │◄─ 7. CTL 通道上报 AUDIO_START HTT: [0x04, 0x03, 0x00, SID]│
      │      • 固件重置 ADPCM 预测器 & 滤波器                     │
      │      • 触发 USB HID 注入语音热键 (RAlt + ,)                │
      │      • 开启 UAC 麦克风音频推流                            │
      │                                                           │
      │◄─ 8. AUDIO 通道持续推送 ADPCM 数据帧 (每帧 120 字节) ──────│
      │      • 解码为 240 个 16-bit PCM 采样点并写入 RingBuffer    │
      │                                                           │
      │── 9. 每隔 2000ms 发送 MIC_EXTEND: [0x0E, SID] ───────────►│
      │      (延长语音会话防遥控器超时自动断流)                   │
      │                                                           │
      │  ═════════════════ 用户松开遥控器语音键 ═════════════════   │
      │◄─ 10. CTL 通道上报 AUDIO_STOP: [0x00, 0x02] ──────────────│
      │       • 释放语音热键                                      │
      │       • 停止麦克风推流,清空环形缓冲区                    │
      │       • 重新发送 MIC_OPEN [0x0C, 0x00] 防御下次按键失效    │

四. DSP 实时音频处理流水线算法设计

1. 4-bit IMA-ADPCM 流式解码

  • 采样规格:16000 Hz 单声道 16-bit PCM;
  • 帧解析:BLE 每帧 120 字节(高 4 位优先,低 4 位其次),对应解出 240 个 PCM 采样点;
  • 跨帧连续性:Predictor 与 StepIndex 在帧间保持连续,收到 AUDIO_SYNC(0x0A)时重置同步点。

2. Declip 单点尖峰消除

消除蓝牙丢包或电磁干扰引起的孤立单点脉冲毛刺:

\text{若 } |x_i - x_{i-1}| > 1000 \text{ 且 } |x_i - x_{i+1}| > 1000 \text{ 且 } \min(dp, dn) > 2 \cdot |x_{i+1} - x_{i-1}| \implies x_i = \frac{x_{i-1} + x_{i+1}}{2}

3. 3-Tap 三角 FIR 低通平滑滤波器

抑制高频量化白噪,提升人声音质厚度与转写清晰度:

y_i = \frac{x_{i-1} + 2x_i + x_{i+1}}{4}

4. 动态 AGC (自动增益控制)

  • 目标电平:28000.0
  • 包络衰减率:0.9997(约 144ms 慢释放,避免呼吸底噪);
  • 最大增益限制:30.0 倍(避免空闲无声时放大本底噪声);
  • 软削波:防止波形硬切顶产生爆音。

五. 多触发按键映射状态机与键码引擎

1. 遥控器全按键物理编码转换表

遥控器物理按键 小米原始 HID 报文 ESP32 输出的 USB 信号 功能效果
音量+ 0x80 Consumer: Volume Up (0x00E9) Windows 原生系统全局音量增加
音量- 0x81 Consumer: Volume Down (0x00EA) Windows 原生系统全局音量减小
返回键 0xF1 Consumer: AC Back (0x0224) 浏览器/文件管理器后退、播放器退出全屏
语音键 ATVV 事件 注入 RAlt + , + UAC 麦克风 唤醒微信输入法语音并实时录音
电源键 0xFF / 0x66 单击 Alt + F4 / 长按 Sleep (0x0032) 关闭当前窗口 / 系统睡眠
主页键 0x24 Keyboard: Win + D 一键返回桌面
菜单键 0x5D Keyboard: Space 视频播放 / 暂停
电视键 (TV) 0xC0 Keyboard: F8 自定义快捷键 / 触发伴侣脚本
方向上/下/左/右 0x52/51/50/4F Keyboard: Up/Down/Left/Right 播放器快进快退调进度
确定键 (OK) 0x28 Keyboard: Return 确认 / 回车

2. Multi-Trigger 三重触发状态机设计 (key_state_machine.c)

每个按键独立维护一个插槽状态结构体,支持 3 种互不干扰的手势:

  • 单击(Click):瞬时点按或按下保持(Hold 直通);
  • 长按(Long Press):按压时长超过 long_ms(默认 600ms,支持 200~2000ms 自定义),触发长按动作,松开时不触发短按;
  • 双击(Double Click):按键在 double_ms(默认 250ms,支持 100~600ms 自定义)内再次按下,触发双击动作;
  • 防卡键瞬时脉冲机制:超时触发的长按操作,释放时会补发释放包,彻底杜绝键值卡死。

3. 全链路 16 进制 / 10 进制双模解析引擎

  • 键码统一支持 0x... 标准 16 进制与 10 进制无缝互通;
  • parse_u32_or_hex() 智能识别数字或 "0x2C" 字符串格式。

六. Flash NVS 存储引擎与开机自愈容灾机制

为确保在 ESP32-S3 Flash 单扇区(4KB)限制下可靠持久化,存储系统采用分层加固设计:

┌────────────────────────────────────────────────────────┐
│               Flash NVS 存储安全防御体系                 │
├────────────────────────────────────────────────────────┤
│ 1. 紧凑型 JSON 序列化 (未启用手势字段自动省略, < 1.4KB)   │
│ 2. 局部原子事务 (Preferences.begin -> putString -> end) │
│ 3. 强制刷写提交 (nvs_flash_commit)                     │
│ 4. 开机自愈容灾 (校验失败/坏块自动降级为 13 键出厂默认) │
│ 5. 底层分区防误删 (phy / nimble_bond / net80211 只读保护)│
└────────────────────────────────────────────────────────┘
  1. 紧凑型 JSON 压缩:仅序列化启用的触发手势,13 键全部配置 3 触发时总数据量仅约 1.2KB(< 35% 扇区容量);
  2. 开机自愈保护:key_config_storage_load() 启动时严格校验 JSON 完整性,若异常断电损坏,自动恢复出厂默认值,杜绝系统变砖。

七. USB 复合设备架构 (UAC 1.0 + HID + CDC)

ESP32-S3 原生 USB OTG 在枚举时向操作系统呈现标准复合多接口设备 (Composite Device):

  1. Interface 0 & 1:USB Audio Class 1.0 (物理麦克风)

    • 格式:PCM 16-bit Mono, 16000Hz;
    • 端点:Isochronous IN 端点;
    • 独创 bInterval=2 / wMaxPacketSize=64 降频缓冲设计,完美绕过 DWC2 控制器无 SOF 中断引起的 500Hz 丢帧魔咒;
    • 硬件 SOF / DSTS 状态感知:电脑休眠唤醒时自动重置 UAC 状态机。
  2. Interface 2:USB HID Keyboard

    • 标准 6KRO 键盘报文,负责发送 Win+D, Alt+Tab, RAlt+,, F13~F24 等修饰组合键。
  3. Interface 3:USB HID Consumer Control

    • 16-bit Usage 控制报文,负责发送原生系统音量(0x00E9/0x00EA)、静音(0x00E2)与 AC 后退(0x0224)。
  4. Interface 4:USB CDC Serial

    • 虚拟串口,用于串口监控与 JSON 命令行交互。

八. Wi-Fi 双模网络与嵌入式 Web 控制台

1. 网络拓扑

  • AP 模式(配置热点):
    • SSID: RemoteMapper-AP (开放免密)
    • IP: 192.168.4.1 (子网掩码 255.255.255.0)
    • DNS Captive Portal: 自动拦截探测域名并重定向到 192.168.4.1。
  • STA 模式(局域网接入):
    • 连接路由器 2.4GHz Wi-Fi;
    • mDNS: 局域网访问地址为 http://remotemapper.local。

2. Web 前端架构 (src/web/web_ui.h)

  • 纯原生单页架构:HTML/CSS/JS 经 PROGMEM 嵌入 Flash,零外部 CDN 依赖;
  • 1:1 遥控器仿真面板:实体按键按下时界面实时高光联动;
  • 敲击即录制:改键窗口直接按下键盘即可自动识别并填入 HID 键码;
  • NVS 纯文本高级编辑器:内置语法校验、格式化与一键备份导出/导入。

九. PC 本地伴侣程序架构 (pc_companion)

为了弥补 USB HID 键盘无法直接执行本地程序/脚本的不足,随固件配备了独立的 Windows 伴侣守护进程:

┌────────────────────────┐      USB HID Keycode (F13 ~ F24)      ┌────────────────────────┐
│                        │ ────────────────────────────────────► │  Windows 宿主机 PC     │
│   ESP32-S3 硬件桥接器  │                                       │                        │
│   (发送零冲突高阶 F 键) │                                       │  pc_companion 守护进程 │
└────────────────────────┘                                       │  (Win32 RegisterHotKey)│
                                                                 └───────────┬────────────┘
                                                                             │ 异步派发
                                                                             ▼
                                                                 ┌────────────────────────┐
                                                                 │ • 打开网页 (抖音/Gemini)│
                                                                 │ • 批处理 (.bat 切 PS5) │
                                                                 │ • PowerShell / Python  │
                                                                 │ • 启动应用程序 (Steam) │
                                                                 └────────────────────────┘
  • 零冲突暗号:使用 Windows 系统预留但物理键盘没有的 F13 ~ F24 键码;
  • 极速无感:原生 Win32 RegisterHotKey 消息循环,待机 CPU 0.0%,内存仅几 MB;
  • 热重载配置:修改 config.json 自动生效无需重启。

十. RESTful API 完整接口规范

请求方法 路由 Path 描述 请求体示例 响应体示例
GET /api/status 获取系统全量运行状态与统计 无 {"version":"1.0.0","ble_state":3,"frames_decoded":1240,"free_heap":241500,"sta_ip":"192.168.2.179"}
GET /api/logs 获取最近 250 行环形运行日志 无 {"logs":["[0012.340] [BLE] Connected","[0014.120] [VOICE] PRESSED"]}
POST /api/logs/clear 清空运行日志缓冲区 无 {"status":"cleared"}
GET /api/wifi/scan 扫描周围 2.4GHz Wi-Fi 无 {"networks":[{"ssid":"Home-WiFi","rssi":-58,"secure":true}]}
POST /api/wifi/config 配置并连接家庭 Wi-Fi {"ssid":"MyHome","pass":"12345678"} {"status":"ok"}
GET /api/keymap 获取当前按键映射 JSON 无 {"ver":2,"bindings":[{"source_vk":102,"has_click":true,...}]}
POST /api/keymap/save 保存按键映射到 Flash NVS {"ver":2,"bindings":[...]} {"status":"saved"}
POST /api/keymap/reset 重置按键映射为出厂默认 无 {"status":"reset_ok"}
GET /api/keymap/telemetry 获取当前按键实时触发遥测 无 {"source_vk":102,"is_pressed":true,"duration_ms":120,"key_code":44}
GET /api/ble/scan 扫描周围可连接的蓝牙设备 无 {"devices":[{"name":"Xiaomi Voice Remote","mac":"c0:5d:39:...","rssi":-45}]}
POST /api/ble/connect 连接并绑定指定 MAC 遥控器 {"mac":"c0:5d:39:c3:94:ab"} {"status":"connected"}
POST /api/ble/unpair 解绑当前遥控器并清空 NVS 无 {"status":"unpaired"}
GET /api/ble/info 获取当前绑定的遥控器信息 无 {"name":"Xiaomi Voice Remote","mac":"c0:5d:39:...","bound":true}
POST /api/ble/reconnect 手动触发重新扫描与连接 无 {"status":"reconnecting"}
GET /api/nvs 获取全量 NVS 命名空间纯文本 无 {"wifi_conf":{"ssid":"..."},"keymap_conf":{"cfg_json":{...}}}
POST /api/nvs/save 将编辑后的 JSON 写入 NVS {"keymap_conf":{"cfg_json":{...}}} {"status":"saved","message":"NVS写入成功"}
POST /api/nvs/reset 清空 NVS 扇区恢复出厂重启 无 {"status":"erased","message":"NVS已清空..."}
POST /api/system/restart 软重启 ESP32-S3 无 {"status":"rebooting"}

十一. 源码文件索引与模块依赖关系

D:\tool\RemoteMapper-ESP32/
├── platformio.ini              # PlatformIO 构建配置文件 (Flash/PSRAM/编译宏)
├── default_16MB.csv            # 16MB Flash 分区表
├── build.bat                   # 一键编译脚本
├── flash.bat                   # 一键烧录脚本
├── monitor.bat                 # 串口监视器脚本
├── test.bat                    # 单元测试运行脚本
├── include/
│   ├── app_config.h            # 全局配置 (引脚、UUID、缓冲区与看门狗宏)
│   └── version.h               # 固件版本号
├── src/
│   ├── main.cpp                # 系统主入口与 FreeRTOS 双核任务调度
│   ├── led_indicator.h/.cpp    # WS2812 RGB 状态指示灯驱动 (蓝/绿/黄/红呼吸灯)
│   ├── log/
│   │   ├── app_log.h           # 全局格式化日志接口
│   │   └── app_log.cpp         # 250 行环形内存日志缓冲区实现
│   ├── audio/                  # DSP 实时音频处理流水线
│   │   ├── adpcm_decoder.h/.c  # 4-bit IMA-ADPCM 流式解码器
│   │   ├── audio_filter.h/.c   # Declip 尖峰消除与 3-Tap FIR 低通平滑
│   │   ├── audio_agc.h/.c      # 动态 AGC 增益控制与软削波
│   │   ├── audio_ring_buffer.h/.c # SPSC 无锁环形缓冲区
│   │   └── audio_pipeline.h/.c # 音频流水线主协调器
│   ├── ble/                    # 蓝牙与协议栈模块
│   │   ├── ble_remote_client.h # BLE Central 状态机接口
│   │   └── ble_remote_client.cpp # NimBLE 驱动、HOGP 监听与 ATVV 握手客户端
│   ├── usb/                    # USB 复合设备模块
│   │   ├── usb_composite.h     # TinyUSB 抽象接口
│   │   └── usb_composite.cpp   # UAC 1.0 麦克风 + HID 键盘 + Consumer 控制器
│   ├── keymap/                 # 按键映射与手势引擎
│   │   ├── key_definitions.h   # 遥控器物理键码与 USB HID 键码定义表
│   │   ├── key_state_machine.h # Multi-Trigger 状态机接口
│   │   ├── key_state_machine.c # 单击/长按/双击手势引擎实现
│   │   ├── key_config_storage.h# 按键映射 Flash NVS 持久化接口
│   │   └── key_config_storage.cpp# 紧凑 JSON 序列化、自愈容灾与十六进制解析
│   ├── nvs/                    # 底层 Flash 分区管理
│   │   ├── nvs_manager.h       # NVS 遍历、读写与重置接口
│   │   └── nvs_manager.cpp     # nvs_entry_find 动态扫描与安全防护
│   ├── wifi/                   # 网络模块
│   │   ├── wifi_manager.h      # Wi-Fi 管理接口
│   │   └── wifi_manager.cpp    # AP 192.168.4.1、DNS 强制门户与 mDNS
│   ├── web/                    # 嵌入式 Web 控制台
│   │   ├── web_ui.h            # HTML/CSS/JS 单页应用 (PROGMEM 存储)
│   │   ├── web_server.h        # WebServer 接口
│   │   └── web_server.cpp      # 18+ 条 RESTful API 路由处理
│   └── cli/                    # 串口诊断模块
│       ├── cli_manager.h       # CDC CLI 接口
│       └── cli_manager.cpp     # JSON 命令行解析器
├── pc_companion/               # PC 本地伴侣程序
│   ├── remotemapper_host.py    # Win32 RegisterHotKey 高性能守护进程
│   ├── config.json             # F13 ~ F24 本地脚本绑定配置文件
│   ├── start_companion.bat     # 前台调试启动脚本
│   ├── silent_start.vbs        # 后台静默启动脚本
│   ├── enable_autostart.bat    # 注册 Windows 开机自启
│   └── build_exe.bat           # PyInstaller 一键打包独立 EXE
└── test/
    └── native/test_suite.py    # 原生算法单元验证测试套件

十二. 编译、烧录与测试指南

1. 编译固件

build.bat

(底层调用 python -m platformio run -e esp32s3_n16r8)

2. 烧录固件

将 ESP32-S3 开发板通过 USB 数据线连接到电脑的 USB/OTG 接口:

flash.bat

3. 查看实时串口调试日志

monitor.bat

4. 运行单元测试

test.bat

十三. 故障排查手册与常见问题 (Troubleshooting)

Q1: 遥控器按键没有反应,串口一直显示 Scanning for Xiaomi Remote...?

  • 排查步骤:
    1. 遥控器是否已被电脑原本的蓝牙占用?请在 Windows 蓝牙设置中先点击「删除设备/断开连接」;
    2. 同时按住遥控器的 主页键 + 菜单键 进行配对,直到遥控器指示灯快闪;
    3. 网页后台进入「📡 蓝牙配对管理」,点击「🔍 扫描附近蓝牙设备」找到 Xiaomi Voice Remote 点击连接。

Q2: 语音键按下后输入法唤醒了,但没有声音录入?

  • 排查步骤:
    1. 打开 Windows「声音设置」➔「录制设备」,查看是否存在 RemoteMapper Mic;
    2. 将微信输入法或讯飞输入法的默认麦克风选择为 RemoteMapper Mic;
    3. 确认输入法快捷键与 Web 控制台中语音键绑定的热键一致(默认为 右Alt + 逗号)。

Q3: 手机连接 RemoteMapper-AP 后无法打开配置界面?

  • 排查步骤:
    1. 手机连上热点后若提示「当前 Wi-Fi 无法访问互联网」,请选择「保持连接」;
    2. 手动在手机浏览器地址栏输入 http://192.168.4.1;
    3. 确保电脑/手机未开启全局代理或 VPN。

Q4: 音量加减在 Windows 下变成打开搜索栏?

  • 排查步骤:
    1. 检查 WebUI 下拉菜单或 NVS 中音量键的键码,音量加标准 USB Consumer 键码为 0x00E9 (233),音量减为 0x00EA (234);切勿使用旧版错误的 545(0x0221 代表 AC Search 全局搜索)。

📄 开源许可证

本项目遵循 MIT License 开源许可协议。