保姆级教程: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.defaultsCONFIG_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、不需要懂深度学习。
在这里插入图片描述

  1. 打开 voicute.com
  2. 输入你的关键词,比如"小娜"“你好灯”“Hey Robot”(中文英文都支持)
  3. 点击生成,下载两个文件:
    • xxx.tflite → 放到 spiffs_content/ 目录
    • head.h → 替换 main/head.h
  4. 回第二步重新编译烧录

从决定用什么词到在设备上跑起来,十分钟搞定。想换词了,再生成一套替换就行。


五、常见问题

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,或者评论区留言交流。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐