对小智说一句“帮我打:今天天气不错”,电脑键盘自己哒哒哒敲出来——这个效果演示极佳,但拆开全是算术题。这篇把语音键鼠项目(EC600M UART2 → CH9329 → USB HID)的关键数字全摆出来,欢迎对账。
硬件链路与一个容易忽略的事实
EC600M 通过 UART2 接 CH9329(UART 转 USB HID 芯片),CH9329 的 USB 插在电脑上,枚举出标准 HID 键盘 + 鼠标(VID 6666 / PID 1D63,设备管理器 Status=OK)——对电脑来说这就是个普通键鼠,不用装驱动。语音控制的“手”就是这么伸进电脑的。ESP32 旁路做 BLE HID,同一套语音指令还能控手机。
容易忽略的事实:HID 类设备的瓶颈从来不在主控芯片,在物理接口。所以先算账,再设计交互。
串口账:理论上限 68.6 帧/秒,实际限速 10 指令/秒
CH9329 走 UART 9600bps、8N1(8 数据位 + 1 起始 + 1 停止 = 每字节 10 bit)。一帧键盘报文 14 字节:
9600 bit/s ÷ 10 bit/字节 ÷ 14 字节/帧 ≈ 68.6 帧/秒 ← 物理上限
一帧线上时间 = 140 bit ≈ 14.6 ms
明明能发 68.6 帧/秒,我却在设备端做了 100ms 速率限制(≤10 指令/秒),主动砍到六分之一。为什么?实测结论:CH9329 收完报文要内部处理再给应答,单动作(按下+释放)实测 ≤40ms。按物理上限猛发,它处理不过来——丢帧、乱序、丢按键,表现就是“字打着打着少了一个”。
所以宁可限速保完整:100ms 窗口发一个完整动作,实测稳定打字 ≈9 字符/秒。对语音指令完全够用——正常人说话也就每秒几个字。串口参数和帧格式是这类芯片的通用套路,但“物理上限”和“稳定上限”之间的差距,只能实测出来。
端到端延迟账:每一段的毫秒都抠过
| 环节 | 耗时 | 说明 |
|---|---|---|
| PTT 按下 → 首帧 Opus 音频上传 | ≈100~150ms | 板端录音启动 + 上行 |
| 语音上行速率 | Opus 60kbps / 60ms 包 = 450B ≈ 15 帧/s ≈ 7.5KB/s | 4G 上行无压力 |
| 云端 STT + LLM 决策 | 取决于网络和模型 | 不可控,只能兜底 |
| MCP 指令下发 → 设备解析 | 4G 空口 + WebSocket | — |
| UART 写 CH9329 | 14.6ms/帧,单动作 ≤40ms | 物理层 |
| 电脑 HID 响应 | ≤10ms | 系统级 |
按键类操作另有一条线:主循环跑 100Hz,按键延迟 ≤10ms。心跳 20s 一发,服务端 84s 判断掉线——留 4.2 倍安全边界,网络抖一下不会误掉线。线程栈分配上,recv/录音线程给 64KB,其余 8KB——QuecPython 线程对应 RTOS 任务,栈溢出是直接 dump,不给侥幸。
一个已知短板,诚实交代
目前 MCP 的 tools/call 是在接收线程里同步执行的。这意味着执行一个长 type_text(几百字的文本要拆成几百帧发几分钟)时,TTS 播报会被堵住——你能听到 AI 说“我在打字”,但声音是卡的。正确解法已经明确:独立工作线程 + 队列,接收线程只解析入队,执行全回工作线程,项目里正在改。发出来是想说:这类“回调/接收线程里干重活”的架构问题,在资源受限设备上特别常见,欢迎有兴趣的同学一起讨论方案。
心得
- 先把“一帧多少字节、一帧多少 ms”算清楚,再设计交互。不知道 14.6ms/帧和 68.6 帧/s 上限的人,才会承诺“实时同步鼠标轨迹”这种物理上做不到的需求。
- 物理上限 ≠ 稳定上限。中间隔着芯片内部处理时间、你的重试策略、对方的缓冲深度——差距只能实测。
- 演示现场丢一个字,比功能少一个更丢分。限速保完整,是这个项目最重要的一个“反性能”决策。
输入