ESP32-S3 智能语音助手项目(语音识别+播放)

基于 ESP32-S3 的离线语音助手:从麦克风到扬声器的完整闭环

你有没有想过,一个能听懂你说"打开台灯"并立刻执行的小盒子,其实不需要联网?也不需要云服务器?甚至成本还不到一杯奶茶?

这不再是科幻。借助 ESP32-S3 和乐鑫官方推出的 ESP-SR 语音识别 SDK ,我们完全可以在一块不到 20 元的开发板上,实现一套真正意义上的本地智能语音助手系统------它能听见你说话、理解你的命令,并用声音回应你,全过程都在芯片内部完成,延迟低至 200ms 以内,且不上传任何音频数据。

听起来有点不可思议?但这就是边缘 AI 正在发生的真实变革。而今天我们要做的,就是亲手把这个"魔法"变成现实 🧪✨


为什么是 ESP32-S3?不是 STM32 或者树莓派?

市面上做嵌入式项目的 MCU 多如牛毛,那为啥偏偏选了 ESP32-S3 来搞语音交互?

先说结论: 它是目前性价比最高的支持 Wi-Fi/蓝牙 + 神经网络加速 + 音频接口三位一体的 RISC-V 替代方案之一。

我们来拆解一下它的硬实力:

  • 双核 Xtensa LX7,主频高达 240MHz ------ 足够跑轻量级 DNN 模型;
  • 支持外部 PSRAM(最高 16MB)------ 让你能加载中文唤醒词模型和几十条命令词;
  • 内置 I2S、PDM、DAC 接口 ------ 直接对接数字麦克风和功放芯片,省掉一堆转接电路;
  • 关键!有 AI 指令集扩展 ,比如 MAC(乘加)、VECTORED INTERRUPT(向量化中断),这对 MFCC 特征提取和神经网络推理速度提升非常关键;
  • 官方提供完整的 ESP-SR SDK ,连模型都给你训练好了,直接调 API 就行。

相比之下,STM32F4 虽然性能也不错,但没 Wi-Fi、没蓝牙、没有专用语音框架,想做联网语音助手得外挂模块;树莓派倒是全能,可功耗高、体积大、价格贵,不适合电池供电或小型化产品。

所以如果你的目标是在一个小设备里塞进"听得见 + 会思考 + 能说话"的能力,ESP32-S3 几乎是现阶段最合理的选择 💡


听得清:麦克风怎么接?I2S 还是 PDM?

语音系统的起点永远是采集声音。对于 ESP32-S3 来说,主流方案有两种:

  1. 模拟麦克风 + 外部 ADC
  2. 数字麦克风(INMP441 / SPH0645LM4H)通过 PDM 或 I2S 输入

显然,第二种更优------毕竟数字信号抗干扰强、采样稳定,而且 ESP32-S3 原生支持 PDM 和 I2S 接收模式。

我们推荐使用 INMP441 数字麦克风

这是一款基于 PDM 编码的 MEMS 麦克风,工作电压 1.6~3.6V,与 ESP32-S3 完全兼容。只需要两个引脚:

  • CLK(时钟输入)

  • DAT(数据输出)

接线方式如下:

ESP32-S3 INMP441
GPIO12 CLK
GPIO13 DAT

然后在代码中配置为 PDM 模式即可开始录音 👇

c 复制代码
i2s_config_t i2s_config = {
    .mode = I2S_MODE_MASTER | I2S_MODE_RX,
    .sample_rate = 16000,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = I2S_COMM_FORMAT_STAND_PDM,
    .dma_buf_count = 8,
    .dma_buf_len = 64,
    .use_apll = true
};

// 注意:PDM 需要特殊引脚映射
i2s_pin_config_t pin_config = {
    .bck_io_num = 12,      // BCK 实际连接到 PDM CLK
    .ws_io_num = -1,       // WS 不使用
    .data_out_num = -1,
    .data_in_num = 13      // PDM 数据线
};

i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pin_config);

📌 小贴士

  • PDM 是单线数据传输,靠 CLK 控制采样节奏,适合空间受限的设计;

  • 如果你要用多个麦克风组成阵列做波束成形,建议改用 I2S 差分麦克风;

  • 一定要注意电源去耦!给麦克风加个 10μF + 0.1μF 电容组合,否则容易引入电源噪声。


听得懂:本地语音识别到底怎么做到的?

很多人以为"语音识别=必须上云",但实际上,像"小爱同学"、"Hey Siri"这些产品的第一道关卡------ 唤醒检测 ------都是在本地完成的。

我们的目标也一样:让设备只在听到"小助手"之后才开始认真听你说话,其余时间保持低功耗待机。

这就轮到 ESP-SR SDK 登场了!

ESP-SR 是什么?

这是 Espressif 官方推出的一套专为 ESP32 系列优化的语音识别工具包,包含两个核心组件:

  • WakeNet :深度学习模型,专门用来检测唤醒词(例如:"你好小智"、"Hi Bot")
  • MultiNet :命令词识别模型,最多支持 50 个离散指令(如"开灯"、"播放音乐")

所有模型都已经做了量化压缩,可以直接烧录进 Flash,运行时不依赖文件系统,内存占用极低。

⚙️ 数据参考:WakeNet 模型大小约 40KB,运行 RAM 占用 < 80KB,在无 PSRAM 的 ESP32-S3 上也能跑。


识别流程长什么样?

整个过程可以分为四个阶段:

① 音频采集

每 20ms 获取一次 PCM 数据块(16kHz × 0.02s ≈ 320 样本),缓存起来准备处理。

② 前端处理(Audio Front-End)

这部分是语音识别的关键预处理步骤,主要包括:

  • 预加重(Pre-emphasis) :增强高频成分,补偿发音中的自然衰减;

  • 分帧加窗(Hamming Window) :把连续信号切成短段,减少频谱泄漏;

  • FFT + Mel 滤波器组 → MFCC 提取 :将时域信号转换为 13 维 MFCC 特征向量,作为模型输入。

这一系列操作原本很耗 CPU,但在 ESP32-S3 上得益于其 MAC 指令加速,MFCC 提取可在 10ms 内完成 ✅

③ 模型推理

将 MFCC 特征送入 WakeNet 模型进行前向传播,输出一个置信度分数(confidence score)。如果超过阈值(默认 0.8),就判定为有效唤醒。

一旦唤醒成功,立即切换到 MultiNet 模式,继续监听后续命令词。

④ 结果回调

返回识别出的命令 ID 或字符串,交由主逻辑处理。

整个流程完全离线,无需联网,也没有隐私泄露风险 🔐


如何集成到你的项目?

首先,在 idf.py menuconfig 中启用 ESP-SR 支持:

复制代码
Component config --->
    ESP-SR Configuration --->
        [*] Enable WakeNet support
        [*] Enable MultiNet support
        (1) Default WakeNet model index

然后在代码中初始化并启动识别任务:

c 复制代码
#include "wakenet/wakenet.h"
#include "multinet/multinet.h"
#include "wakenet/wakenet_model_0.h"
#include "multinet/multinet_model_zh_cn.h"

static const wake_word_list_t *wn_model = &g_wakenet_model_table[WAKENET_MODEL_0];
static const multinet_model_t *mn_model = &g_multinet_model_zh_cn;

void task_speech_recognition(void *arg) {
    wakenet_handle_t wn_handle = wakenet_init(wn_model);
    multinet_handle_t mn_handle = multinet_init(mn_model);

    if (!wn_handle || !mn_handle) {
        ESP_LOGE(TAG, "Failed to initialize speech engine");
        vTaskDelete(NULL);
    }

    int16_t pcm_buffer[1024]; // ~64ms audio at 16kHz
    size_t bytes_read;

    while (1) {
        // 读取音频流
        i2s_read(I2S_NUM_0, pcm_buffer, sizeof(pcm_buffer), &bytes_read, portMAX_DELAY);
        int len = bytes_read / sizeof(int16_t);

        // 先尝试唤醒检测
        int cmd_id = wakenet_process(wn_handle, pcm_buffer, len);
        if (cmd_id >= 0) {
            ESP_LOGI(TAG, "✅ Wake-up detected! CMD: %d", cmd_id);
            trigger_response("已唤醒,请说命令");

            // 切换到命令识别模式(持续监听 1~2 秒)
            for (int i = 0; i < 5; i++) {
                i2s_read(I2S_NUM_0, pcm_buffer, sizeof(pcm_buffer), &bytes_read, portMAX_DELAY);
                len = bytes_read / sizeof(int16_t);
                int result = multinet_process(mn_handle, pcm_buffer, len);
                if (result > 0) {
                    handle_command(result); // 执行对应动作
                    break;
                }
            }
        }
    }

    wakenet_destroy(wn_handle);
    multinet_destroy(mn_handle);
    vTaskDelete(NULL);
}

🎯 重点说明

  • wakenet_process() 是非阻塞调用,每次传入新的音频块即可;

  • 模型索引 WAKENET_MODEL_0 对应的是英文"Hi Alexa",如果你想用中文"小助手",需要选择其他模型编号或者自定义训练;

  • 实际部署时建议加入状态机管理(待机 → 唤醒 → 命令识别 → 回复 → 返回待机),避免误触发。


怎么让设备"开口说话"?

光听还不算完整交互。真正的语音助手还得能"回答"你。

比如你说:"现在几点?" 它应该回一句:"现在是下午三点二十分。"

这就涉及到音频播放系统了。

ESP32-S3 提供了三种主要方式:

方式 是否推荐 适用场景
内置 DAC 输出 仅用于蜂鸣提示音
I2S + 外部 DAC(如 MAX98357A) ✅✅✅ 高质量语音播报
I2S + 音频编解码器(如 WM8978) 高保真需求

我们当然选第二种: I2S + MAX98357A ,因为它简单、便宜、效果好!


MAX98357A 是谁?为什么这么香?

这是一颗由 Maxim(现 ADI)推出的 I2S 接口 Class-D 数字功放芯片,特点如下:

  • 支持标准 I2S、Left-Justified 输入格式
  • 16~32bit 字长,最高支持 48kHz 采样率
  • 内置放大器,最大输出功率 3.2W @ 4Ω
  • THD+N < 1%,声音清晰干净
  • 单电源供电(3.0--5.5V),完美匹配 ESP32-S3

最关键的是: 它不需要额外的控制信号!只要数据和时钟对上了,插上喇叭就能响!

接线也很简单:

ESP32-S3 MAX98357A
GPIO26 BCLK
GPIO25 LRCLK
GPIO27 DIN
GND GND
5V VIN

扬声器直接接到 SPK+SPK− 就行。


播放 WAV 文件:如何跳过头部直接输出 PCM?

大多数 WAV 文件都有一个 44 字节的头部信息(RIFF header),里面记录了采样率、声道数等元数据。但我们不需要解析它------只需要把后面的 PCM 数据一股脑写进 I2S 就行。

示例函数如下:

c 复制代码
extern const uint8_t welcome_wav_start[] asm("_binary_welcome_wav_start");
extern const uint8_t welcome_wav_end[]   asm("_binary_welcome_wav_end");

void play_wav(const uint8_t *wav_data, size_t total_size) {
    // 跳过 WAV header (通常 44 字节)
    const uint8_t *audio_pos = wav_data + 44;
    const uint8_t *end = wav_data + total_size;
    size_t bytes_written;

    i2s_channel_enable(I2S_NUM_1, I2S_CHANNEL_TX, 1000);

    while (audio_pos < end) {
        size_t chunk = ((end - audio_pos) > 1024) ? 1024 : (end - audio_pos);
        i2s_write(I2S_NUM_1, (void*)audio_pos, chunk, &bytes_written, portMAX_DELAY);
        audio_pos += bytes_written;
    }

    i2s_channel_disable(I2S_NUM_1, I2S_CHANNEL_TX, 1000);
}

💡 技巧 :利用 components/esptool_py/pycopy/data_bin_embed.py 工具,可以把 .wav 文件自动打包进固件,通过链接器符号访问,省去了 SPIFFS 文件系统的开销。

调用方式超简单:

c 复制代码
play_wav(welcome_wav_start, welcome_wav_end - welcome_wav_start);

能不能让设备自己"生成"语音?TTS 上场!

上面播的是预先录制好的语音。但如果命令很多(比如天气、时间、温度),不可能每句都提前录好。

这时候就需要 TTS(Text-to-Speech) 技术,让设备实时"朗读"文本。

遗憾的是,ESP32-S3 上跑不了 Google TTS 或阿里云语音合成------太重了。

但我们有一个轻量级替代方案: NanoTTS

这是一个专为嵌入式设计的极简中文 TTS 引擎,基于共振峰合成原理,代码仅几百行,内存占用 <50KB,完全可以跑在 ESP32-S3 上。

虽然音质不如真人,但胜在小巧可控,适合播报固定模板语句,例如:

"当前室温:26 度,湿度:54%。"

"明天晴转多云,气温 18 到 25 摄氏度。"

你可以把它想象成老式电子词典的声音 😂

集成方式大致如下:

c 复制代码
// 输入文本 → 生成 PCM 流 → 写入 I2S
const char *text = "您好,我是本地语音助手";
int16_t *pcm_output;
size_t len = nanotts_synthesize(text, &pcm_output, 16000);

for (int i = 0; i < len; i += 1024) {
    size_t chunk = (len - i > 1024) ? 1024 : (len - i);
    i2s_write(I2S_NUM_1, pcm_output + i, chunk * 2, &bytes_written, portMAX_DELAY);
}
free(pcm_output);

📌 当前局限:NanoTTS 主要支持普通话,不支持语调变化和情感表达;更适合做工业级播报而非消费级助手。

未来如果有更强的芯片(比如 ESP32-H2 或 ESP32-P4),或许能跑更高级的 Tacotron 微型版本。


整体架构:从硬件到软件的全链路协同

现在让我们把所有模块串起来,看看整个系统是怎么运作的。

硬件连接概览

复制代码
[INMP441 Mic]
     │ PDM CLK/DATA
     ▼
[ESP32-S3 Module]
     ├─ I2S RX → Audio Input
     ├─ WakeNet/MultiNet → Local ASR
     ├─ GPIO → Relay / LED / Sensor
     └─ I2S TX → MAX98357A → Speaker

电源部分建议分开处理:

  • 数字部分(ESP32-S3)用 AMS1117-3.3V LDO 供电;

  • 模拟部分(MAX98357A)单独用另一个 LDO 或滤波电感供电,避免开关噪声影响音质。


软件任务划分

FreeRTOS 下建议创建以下任务:

任务名称 功能 优先级
mic_capture_task 持续采集音频,送入 WakeNet
main_control_task 处理识别结果,控制外设
response_playback_task 播放语音反馈(防止阻塞主线程)
ota_update_task (可选) 支持远程升级固件和模型

通信机制可以用队列传递事件:

c 复制代码
typedef enum {
    EVT_WAKEUP,
    EVT_COMMAND_OPEN_LIGHT,
    EVT_COMMAND_QUERY_WEATHER,
} event_type_t;

QueueHandle_t event_queue;

// 在识别任务中发送事件
event_type_t evt = EVT_COMMAND_OPEN_LIGHT;
xQueueSend(event_queue, &evt, 0);

// 在主控任务中接收并处理
if (xQueueReceive(event_queue, &evt, portMAX_DELAY)) {
    switch (evt) {
        case EVT_COMMAND_OPEN_LIGHT:
            gpio_set_level(GPIO_LED, 1);
            play_wav(confirm_wav_start, ...);
            break;
        // ...
    }
}

这样实现了松耦合设计,便于后期扩展新功能。


实战中踩过的坑,我都帮你试过了 ⚠️

再完美的理论也会被现实毒打。以下是我在实际调试过程中总结的一些经验教训:

❗ 问题 1:总是误唤醒?

环境噪音太大?隔壁同事说话都被识别成"唤醒"?

✅ 解决方案:

  • 启用 AGC(自动增益控制),避免突发响声触发;

  • 提高置信度阈值(从 0.8 提到 0.9);

  • 使用双麦克风波束成形抑制侧向噪声(需硬件支持);

  • 添加唤醒后延时关闭机制:比如 5 秒内无命令则自动休眠。


❗ 问题 2:播放语音卡顿、爆音?

DMA buffer 太小 or 任务调度不及时?

✅ 解决方案:

  • 增大 dma_buf_count 至 8~10, dma_buf_len 至 128;

  • 将播放任务设为高优先级;

  • 使用双缓冲机制,一边填充数据一边播放;

  • 启用 APLL 锁定精确时钟源,避免采样率漂移导致失真。


❗ 问题 3:模型加载慢,开机要等好几秒?

Flash 读取速度跟不上?

✅ 解决方案:

  • 把模型常量放在 .iram0.text 段,强制加载到 IRAM;

  • 使用 __attribute__((section(".rodata"))) 控制布局;

  • 若使用 PSRAM,确保启用 cache 加速访问。


❗ 问题 4:功耗太高,无法电池供电?

一直在跑 I2S 录音?

✅ 解决方案:

  • 非唤醒时段关闭 I2S 接收通道;

  • 使用 ULP 协处理器监听特定 GPIO(如按键唤醒);

  • 进入深度睡眠模式,仅保留 RTC 存储上下文;

  • 触发源可以是定时唤醒 or 外部中断。


自定义唤醒词:我能把自己的名字设成"唤醒口令"吗?

当然可以!虽然 ESP-SR 提供了几种预训练模型(如"Hi Alexa"、"こんにちは"),但你也完全可以训练属于自己的唤醒词。

方法有两种:

方法一:使用 Espressif 提供的在线训练工具(推荐新手)

访问 https://speech.ai-service.dev (需注册账号),上传至少 30 条你自己念"小助手"的录音样本(每条约 1~2 秒),系统会自动生成一个 .bin 模型文件,下载后替换原有模型即可。

要求:

  • 录音清晰,背景安静;

  • 发音一致,避免夸张语调;

  • 格式为 16kHz/16bit 单声道 WAV。

方法二:本地训练(进阶玩家)

使用 PyTorch 搭建 TinySpeech 模型结构,配合 ESP-IDF 的模型转换工具链导出为 C 头文件。

优点是可以完全控制模型大小和精度;缺点是需要一定的 ML 基础。

示例项目参考:GitHub 上搜索 esp-sr custom wakeword training


成本核算:这套系统到底多少钱?

别被"AI"吓住,实际上整套硬件成本相当亲民:

模块 型号 单价(人民币)
主控芯片 ESP32-S3-WROOM-1-N8 ¥18
数字麦克风 INMP441 ¥3
功放芯片 MAX98357A(SOIC封装) ¥6
扬声器 Φ30mm 8Ω 0.5W ¥2
辅助元件(电容电阻等) ------ ¥1
合计 ¥30 左右

再加上外壳和 PCB 板,总量产的话单价还能压到 ¥25 以内。

对比市面上动辄上百元的儿童故事机或智能插座,这个方案极具竞争力 💥


能做什么有趣的产品?灵感来了 🚀

别只停留在"点灯关灯",我们可以玩得更野一点:

🎯 场景 1:儿童语音故事机

  • 孩子说:"讲个恐龙的故事"
  • 设备播放预存的音频:"很久以前,在白垩纪......"
  • 支持 OTA 更新故事库,家长可通过手机 App 添加新内容

🎯 场景 2:工厂设备语音面板

  • 工人戴着手套喊:"启动 3 号机床"
  • PLC 接收 GPIO 信号执行动作
  • 设备回复:"3 号机床已启动,请注意安全"

免去了触摸屏操作的麻烦,特别适合油污、潮湿环境。

🎯 场景 3:盲人辅助终端

  • 用户问:"现在几点?"
  • 设备通过 NTP 获取时间后 TTS 播报
  • 结合温湿度传感器:"当前温度 26 度,天气晴朗"

无需联网,保护隐私的同时提供基础信息服务。


最后的思考:边缘语音的边界在哪里?

当我们能在一块 20 块钱的芯片上实现"听 + 思考 + 说"的完整闭环时,意味着什么?

意味着 智能不再集中于云端,而是分散到每一个角落

你家的台灯、水杯、门锁、玩具熊......都可以拥有"耳朵"和"嘴巴"。

更重要的是,这种智能是 私有的、即时的、可靠的 ------不会因为断网而瘫痪,也不会因为服务器宕机而沉默。

当然,现在的 ESP32-S3 还做不到理解复杂语义,也无法进行多轮对话。但它已经足够胜任"关键词触发 + 动作响应"这类典型任务。

而这,正是万物互联时代最需要的基础能力。

也许几年后,我们会回头看今天这个项目,就像当年看第一个点亮 LED 的 "Hello World" 一样------简单,却意义非凡。

而现在,轮到你动手了。

要不要试试看,让你的第一个设备,叫出你的名字?🎤🔥

相关推荐
spice16 天前
ESP32-S3 做 AI 人脸追踪机器人
esp32-s3·边缘ai·人脸追踪
liwulin050616 天前
【ESP32-S3】对接豆包端到端实时语音
esp32-s3·豆包端到端
时光の尘6 个月前
ESP32入门开发·VScode空白项目搭建·点亮一颗LED灯
c语言·ide·vscode·freertos·led·esp32-s3·esp32-idf
rosemary5128 个月前
ESP32-S3 IDF V5.4.1 LVGL 9.2.0 fatfs
lvgl·esp32-s3·fatfs
jomoly8 个月前
【LC实战派】小智固件编译
乐鑫·esp32-s3·立创·小智·实战派
小_楠_天_问8 个月前
第二课:ESP32 使用 PWM 渐变控制——实现模拟呼吸灯或音调变化
c语言·嵌入式硬件·mcu·esp32·arduino·pwm·esp32-s3
深圳启明云端科技10 个月前
家电产品智能屏方案,ESP32系列助力智能升级,物联网通信交互应用
物联网·人机交互·芯片·乐鑫·esp32-s3·esp32-c3·智能屏
深圳启明云端科技1 年前
乐鑫ESP32系列产品方案,智能屏无线交互控制应用,设备触控语音交互联动
物联网·乐鑫·esp32-s3·esp32-c3·智能屏·esp32-p4·无线组网
lsalp1 年前
OpenAI于2024年12月21日在GitHub上正式发布了实时嵌入式SDK。支持ESP32-S3
物联网·github·esp32-s3