保姆级教程:ESP32-S3 自定义唤醒词,输入关键词直接生成模型,零基础也能搞
保姆级教程:ESP32-S3 自定义唤醒词,输入关键词直接生成模型,零基础也能搞
前言
做智能家居、机器人、语音助手的时候,经常会想:能不能让设备只听我的指令? 比如喊一声"小娜开灯",灯就亮了。
市面上的方案我基本试过一圈:
- 云端 ASR → 贵,要联网,延迟大
- 固定唤醒词芯片 → 改不了词,比如只能"小爱同学"
- 自己训练模型 → 要准备数据、GPU、搞深度学习,门槛太高
今天分享一个完全不同的思路:在 ESP32-S3 上离线跑唤醒词检测,输入关键词就能自动生成模型,不需要数据、不需要 GPU、不需要懂深度学习。从零到跑通,最快一个下午。
硬件准备
| 硬件 | 说明 |
|---|---|
| ESP32-S3 开发板 | 必须有 PSRAM ≥ 8MB,推荐 HMI-DevKit |
| 麦克风模块 | 板载 MEMS 麦克风(ES7210 ADC) |
| Flash | ≥ 16MB |
| 数据线 | USB-C 连接电脑 |
如果用的是其他 ESP32-S3 板子,只要带 PSRAM 就行,改一下 bsp_board.c 里的音频驱动即可。
软件环境:
- ESP-IDF v6.0.1(官方下载)
- Python 3.10+
- Windows 11(本文截图和脚本都是 Windows 的,Linux/Mac 同理)
一、理解唤醒词模型
在动手之前,先花 2 分钟理解一下唤醒词模型到底在算什么。你不需要懂深度学习,看懂这张图就行:
你的声音 → 麦克风采集 → Mel 频谱 → 神经网络 → 概率
16kHz 采样 98帧×32维 0 = 不是唤醒词
1 秒音频 一张"声音图片" 1 = 就是唤醒词
Mel 频谱就是把声音变成一张二维图。横轴是时间(1 秒切 98 帧),纵轴是频率(32 个通道),颜色深浅代表能量。你说"小娜"和你说"开灯",画出来的图是不一样的。
神经网络分两部分:
- Backbone(骨架):负责看图。从 98×32 的频谱图里提取关键特征,输出一个 256 维向量
- Head(头):负责判断。把 256 维向量变成一个 0~1 的概率值
两个文件加起来不到 100KB,塞进 ESP32 的 Flash 里绰绰有余。
关键理解:Backbone 是通用的(不同唤醒词可以共用同一个),Head 是专属的(每个词不一样)。所以换唤醒词 = 换 Head。
二、开源推理引擎 onnx-wakeword
voicute/onnx-wakeword 是我开源的一个 ESP-IDF 项目,把上面这套流程完整工程化了。
2.1 项目目录
git clone https://github.com/voicute/onnx-wakeword.git
cd onnx-wakeword/esp32
tree
esp32/
├── components/voicute/ # 核心推理库(不依赖具体模型)
│ ├── recognizer.cpp/h 推理管线(Mel → TFLite → Head → 概率)
│ ├── detect_logic.c/h 5层检测管线(防误触)
│ ├── model_loader.cpp/h 从 SPIFFS 加载 .tflite
│ └── mel_extractor.c/h Mel 频谱提取
│
├── main/ # Demo 应用
│ ├── main.cpp 完整的唤醒词 + 语音命令示例
│ └── head.h 模型 Head 权重(★★★ 你替换这个)
│
├── spiffs_content/ # 放 .tflite 模型文件
│ └── manbo.tflite 示例模型(曼波唤醒词)
│
├── build.bat # 一键编译
├── flash.bat # 一键烧录
├── wake_monitor.py # 串口监控(实时看概率)
└── sdkconfig.defaults # 已预配 ESP32-S3 + 16MB Flash + PSRAM
2.2 三步跑起来
第一步:编译
git clone https://github.com/voicute/onnx-wakeword.git
cd onnx-wakeword/esp32
build.bat
双击 build.bat 就行了,已经帮你配好了 ESP32-S3 目标、16MB Flash、PSRAM。首次编译 5-10 分钟(需要下载依赖),之后秒编。
第二步:放模型
cp 你的模型.tflite spiffs_content/
cp 你的_head.h main/head.h
第三步:烧录
flash.bat
烧完用 python wake_monitor.py COM5 打开监控,对着设备喊唤醒词,应该能看到概率飙升。
2.3 核心代码
整个唤醒词检测的代码其实就这么多:
#include "recognizer.h"
#include "head.h"
#include "kws_postprocess.h"
// 唤醒回调
static void on_wake(voice_event_t ev, voice_evt_data_t d, void *u) {
if (ev == VOICE_EVT_AWAKEN)
printf(">>> 检测到关键词!\n");
}
void app_main() {
// 1. 初始化:加载模型、Mel 提取器、检测管线
recognizer_config_t cfg = {
.model_path = "/spiffs", // 模型在 SPIFFS 分区
.threshold = 0.70f, // 概率阈值
.l5_enabled = 1, // 开能量跳变过滤
.l5_delta = 200.0f, // RMS 跳变阈值
.postprocess = kws_postprocess, // Head 回调
};
recognizer_start(&cfg);
recognizer_register_callback(0, on_wake, NULL);
// 2. 主循环:不断从麦克风取音频,喂给识别器
while (1) {
int16_t pcm[16192]; // 16192 个采样点 = ~1 秒
float rms = compute_rms(pcm); // 计算音量
int64_t now = esp_timer_get_time() / 1000;
recognizer_run_frame(pcm, rms, now); // 跑一次推理
}
}
Demo 还集成了 MultiNet 语音命令,唤醒后可以说"灯光变红""灯光变蓝"等指令。不需要的话删掉 sdkconfig.defaults 里 CONFIG_SR_* 和 CONFIG_CN_SPEECH_COMMAND_* 开头的配置就行。
三、防误触机制——5 层检测管线
推理只给一个概率值,但真正决定体验好坏的是误触控制。onnx-wakeword 内置了和 Android 端同款的 L1-L5 检测管线:
| 层 | 作用 | 原理 |
|---|---|---|
| L1 | 连续帧确认 | 连续 N 帧超阈值才放行,滤掉瞬时噪声 |
| L2 | 峰/底比 | 1500ms 内峰值 / 背景均值 > 3×,滤掉模型波动 |
| L3 | 冷却 | 1.5s 内不重复触发,防双响 |
| L4 | 突发阻断 | 3 次/3 秒触发 → 拉黑 5 秒,防循环误触 |
| L5 | 能量跳变 | 音量突然升高 + 说完后安静下来,防音乐/视频误触 |
调参指南(重要!)
推荐起步配置:只开 L5,其他关。ESP32 推理一帧约 360ms,唤醒词约 500ms,最多被一帧覆盖,L1 连续确认不适用。
遇到误触发怎么办?两步走:
第一步:确保 L5 开启(
l5_enabled=1),l5_delta=200。适合安静环境,音量比之前 0.5-2 秒的最低音量高 200 才放行。第二步:如果还是容易误触,加大
l5_delta。代码里改成:.l5_delta = 800.0f, // 从 200 → 800,嘈杂环境用
| 你的环境 | 建议 delta | 安静时 RMS | 说话时 RMS |
|---|---|---|---|
| 安静房间 | 200 ~ 400 | 10 ~ 50 | 200 ~ 500 |
| 普通室内 | 400 ~ 800 | 50 ~ 150 | 300 ~ 800 |
| 有电视/音乐 | 800 ~ 1200 | 100 ~ 300 | 500 ~ 1500 |
怎么看日志调参? 跑 python wake_monitor.py,看输出里的 L5 行:
L5 JUMP: curRms=330 > preMin=29 + 200 → 放行 ✅
L5 steady: curRms=42 preMin=26 + 200 → 拦截 ❌ (音量不够)
L5 quiet: curRms=20 preMin=18 → 拦截 ❌ (太安静)
- 频繁误触(没说话也 JUMP)→ l5_delta 调大
- 唤不醒(说话被 steady 拦截)→ l5_delta 调小
四、获取自己的唤醒词模型
这是最爽的环节。不需要准备数据、不需要 GPU、不需要懂深度学习。
- 打开 voicute.com
- 输入你的关键词,比如"小娜"“你好灯”“Hey Robot”(中文英文都支持)
- 点击生成,下载两个文件:
xxx.tflite→ 放到spiffs_content/目录head.h→ 替换main/head.h
- 回第二步重新编译烧录
从决定用什么词到在设备上跑起来,十分钟搞定。想换词了,再生成一套替换就行。
五、常见问题
Q: 安静时 prob 一直在 0.3~0.5,老是误触发?
开 L2(l2_enabled=1),它会学习底噪的概率水平,自动提高有效门槛。
Q: 说话 prob 很高但就是不触发?
检查 head.h 和 .tflite 是不是同一次生成的。不同批次的模型和 Head 不配套。
Q: 编译报错 “No module named esp_idf_monitor”?
没激活 ESP-IDF 环境。执行 C:\esp\v6.0.1\esp-idf\export.bat 或者直接双击 build.bat(已内置环境配置)。
Q: 烧录后设备没反应?
大概率是开发板没 PSRAM。ESP32-S3 必须带 PSRAM ≥ 8MB,否则固件启动不了。检查 sdkconfig.defaults 里有 CONFIG_SPIRAM=y。
Q: 我想用其他开发板?
修改 main/bsp_board.c 里的音频驱动部分,适配你的麦克风和 ADC。核心识别库 components/voicute/ 是纯算法的,不依赖具体硬件。
总结
| 你需要什么 | 去哪搞 |
|---|---|
| ESP-IDF 推理引擎 + 完整 Demo | git clone https://github.com/voicute/onnx-wakeword |
| 自定义唤醒词模型 | voicute.com 输入关键词直接下载 |
整条链路:输关键词 → 下载模型 → 复制到工程 → build.bat → flash.bat → 对设备喊话。完全离线、不要钱、想换就换。
有问题欢迎去 GitHub 提 Issue,或者评论区留言交流。
更多推荐

所有评论(0)