一、系统概述
本系统以ESP32(或 ESP32-S3) 为核心,融合机器学习技术实现本地化语音识别,无需依赖云端即可完成语音唤醒、命令解析与家居设备控制。系统支持自定义唤醒词(如 "小 ESP")和常用控制命令(如 "打开灯光""关闭风扇"),通过继电器模块控制家电,同时提供语音反馈(如 "已打开空调")。相比传统云端语音控制,本系统具有响应速度快(<500ms)、隐私性强(本地处理)、无网络依赖的优势,适合家庭、办公室等场景的智能控制。
二、核心技术与方案设计
1. 技术架构
系统采用 "本地语音处理 + 机器学习推理 + 设备控制" 的架构,关键技术包括:
- 语音采集与预处理:通过麦克风采集语音信号,进行降噪、分帧、特征提取(MFCC 梅尔频率倒谱系数);
- 机器学习推理:基于 TensorFlow Lite for Microcontrollers(TFLite-Micro)部署轻量级语音识别模型,实现唤醒词检测和命令分类;
- 设备控制:通过继电器 / 蓝牙模块控制灯光、插座、风扇等家居设备;
- 语音反馈:通过扬声器播放预设提示音或简单 TTS(文本转语音)反馈。
2. 机器学习模型设计
- 唤醒词模型:采用轻量级 CNN(卷积神经网络),基于 Google Speech Commands 数据集的自定义唤醒词(如 "小 ESP")训练,模型大小控制在 50KB 以内,适合 ESP32 内存;
- 命令识别模型:采用 DNN(深度神经网络),识别 10-20 条常用命令(如 "打开灯光""调高温度"),输入为 MFCC 特征,输出为命令类别概率。
三、硬件选型
| 组件名称 | 型号规格 | 用途说明 | 数量 |
|---|---|---|---|
| 主控模块 | ESP32-S3-WROOM-1 | 高性能版本,支持 I2S 接口、更大 RAM(320KB),适合运行 TFLite 模型 | 1 |
| 语音采集模块 | INMP441(I2S 数字麦克风) | 低噪声、数字输出,支持 I2S 协议,直接与 ESP32 的 I2S 接口通信 | 1 |
| 音频输出模块 | 8Ω 0.5W 扬声器 + LM386 功放 | 播放反馈提示音(如 "已执行"),LM386 用于驱动扬声器 | 1 套 |
| 家居控制模块 | 4 路继电器模块(5V) | 控制灯光、插座等 220V 家电(需注意强电隔离) | 1 |
| 辅助组件 | 按键(用于模型校准)、LED 指示灯、杜邦线、5V 电源 | 手动触发校准、状态指示、供电 | 若干 |
硬件选型说明:
- ESP32-S3:相比基础款 ESP32,增加了硬件浮点运算单元和更大的 SRAM,能更高效运行机器学习模型,且支持 I2S 接口直接连接数字麦克风,减少信号干扰;
- INMP441:数字麦克风避免了模拟信号的噪声干扰,I2S 接口可通过 DMA 方式高速采集语音数据,适合实时处理;
- 继电器模块:需通过光耦隔离强电与 ESP32,避免干扰或损坏主控。
四、硬件接线图
核心接线表(ESP32-S3 引脚 → 模块引脚)
| ESP32-S3 引脚 | 模块名称 | 模块引脚 | 备注说明 |
|---|---|---|---|
| 3.3V | INMP441 | VDD | 麦克风供电(3.3V) |
| GND | INMP441 | GND | 共地 |
| GPIO18(I2S_BCK) | INMP441 | BCK | I2S 时钟线 |
| GPIO17(I2S_WS) | INMP441 | WS | I2S 声道选择线 |
| GPIO16(I2S_DO) | INMP441 | DO | I2S 数据输出线 |
| GPIO4 | LM386 功放 | IN | 音频信号输入(PWM/DAC) |
| 5V | 继电器模块 | VCC | 继电器供电(需独立 5V,避免 ESP32 过载) |
| GND | 继电器模块 | GND | 共地 |
| GPIO21 | 继电器模块 | IN1 | 控制灯光(高电平触发) |
| GPIO22 | 继电器模块 | IN2 | 控制风扇 |
| GPIO2 | 校准按键 | 一端(另一端接 GND) | 长按进入模型校准模式 |
| GPIO13 | 状态 LED(绿) | 阳极(串 220Ω 电阻) | 系统运行正常指示 |
接线示意图(简化)
plaintext
ESP32-S3 INMP441(麦克风) 继电器模块 音频模块
3.3V ──────── VDD ──────── ────────
GND ──────── GND GND GND
GPIO18──────── BCK
GPIO17──────── WS
GPIO16──────── DO
GPIO21──────── IN1(灯光)
GPIO22──────── IN2(风扇)
GPIO4 ──────── LM386输入
5V ──────── 继电器VCC
五、软件设计
1. 开发环境与依赖
- 开发工具:Arduino IDE 2.0+(需安装 ESP32-S3 开发板支持)
- 核心库:
TensorFlowLite_ESP32:TFLite-Micro 在 ESP32 上的移植库,用于模型推理;ESP32_I2S:驱动 I2S 麦克风采集语音数据;SimpleAudioPlayer:通过 PWM 输出音频反馈;PubSubClient(可选):如需远程控制,可通过 MQTT 连接家居网关。
2. 系统流程图

3. 核心代码实现
(1)初始化与模型加载
#include <Arduino.h>
#include <TensorFlowLite.h>
#include "tensorflow/lite/micro/all_ops_resolver.h"
#include "tensorflow/lite/micro/micro_error_reporter.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
// 引入模型(唤醒词模型和命令识别模型,需提前转换为C数组)
#include "wake_word_model.h" // 唤醒词模型(自定义"小ESP")
#include "command_model.h" // 命令识别模型(10条命令)
// I2S麦克风配置
#include <driver/i2s.h>
#define I2S_BCK_PIN 18
#define I2S_WS_PIN 17
#define I2S_DO_PIN 16
// 设备控制引脚
#define RELAY_LIGHT 21 // 灯光继电器
#define RELAY_FAN 22 // 风扇继电器
#define LED_STATUS 13 // 状态指示灯
// TFLite相关变量
tflite::MicroErrorReporter tflite_error_reporter;
tflite::AllOpsResolver tflite_resolver;
const tflite::Model* wake_word_model = nullptr;
tflite::MicroInterpreter* wake_word_interpreter = nullptr;
const tflite::Model* command_model = nullptr;
tflite::MicroInterpreter* command_interpreter = nullptr;
// 模型输入输出缓冲区(根据模型输入维度定义)
const int WAKE_WORD_INPUT_SIZE = 490; // 唤醒词模型输入长度(MFCC特征)
float wake_word_input_buffer[WAKE_WORD_INPUT_SIZE];
const int COMMAND_INPUT_SIZE = 980; // 命令模型输入长度
float command_input_buffer[COMMAND_INPUT_SIZE];
void setup() {
Serial.begin(115200);
pinMode(RELAY_LIGHT, OUTPUT);
pinMode(RELAY_FAN, OUTPUT);
pinMode(LED_STATUS, OUTPUT);
digitalWrite(LED_STATUS, HIGH); // 系统启动完成
// 初始化I2S麦克风
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = 16000, // 语音识别常用16kHz采样率
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 单声道
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 4,
.dma_buf_len = 512
};
i2s_pin_config_t pin_config = {
.bck_io_num = I2S_BCK_PIN,
.ws_io_num = I2S_WS_PIN,
.data_out_num = I2S_PIN_NO_CHANGE, // 仅输入,无输出
.data_in_num = I2S_DO_PIN
};
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, nullptr);
i2s_set_pin(I2S_NUM_0, &pin_config);
// 加载唤醒词模型
wake_word_model = tflite::GetModel(wake_word_model_data); // 模型数据来自wake_word_model.h
if (wake_word_model->version() != TFLITE_SCHEMA_VERSION) {
Serial.println("唤醒词模型版本不匹配!");
while (1);
}
// 为解释器分配内存(根据模型大小调整)
static uint8_t wake_word_tensor_arena[8 * 1024]; // 8KB内存池
wake_word_interpreter = new tflite::MicroInterpreter(
wake_word_model, tflite_resolver, wake_word_tensor_arena,
sizeof(wake_word_tensor_arena), &tflite_error_reporter
);
wake_word_interpreter->AllocateTensors();
// 加载命令识别模型(流程同上)
command_model = tflite::GetModel(command_model_data);
static uint8_t command_tensor_arena[16 * 1024]; // 16KB内存池
command_interpreter = new tflite::MicroInterpreter(
command_model, tflite_resolver, command_tensor_arena,
sizeof(command_tensor_arena), &tflite_error_reporter
);
command_interpreter->AllocateTensors();
Serial.println("系统初始化完成,等待唤醒词...");
}
(2)语音采集与预处理(MFCC 提取)
// 采样缓冲区(16位PCM数据)
int16_t audio_buffer[1024];
// MFCC特征提取(简化版,实际需实现梅尔滤波器组)
void extractMFCC(int16_t* pcm_data, int pcm_len, float* mfcc_out, int mfcc_len) {
// 步骤:1. 预加重(高通滤波);2. 分帧(20ms一帧,16kHz下320点);3. 加窗(汉宁窗);
// 4. FFT变换;5. 梅尔滤波;6. 取对数;7. DCT变换得到MFCC特征
// 此处为简化代码,实际需根据语音处理库实现(如使用kissFFT)
for (int i = 0; i < mfcc_len; i++) {
mfcc_out[i] = (float)pcm_data[i % pcm_len] / 32768.0; // 临时占位,需替换为真实MFCC
}
}
// 采集唤醒词语音(1秒)并提取MFCC
bool collectWakeWordAudio() {
size_t bytes_read;
i2s_read(I2S_NUM_0, audio_buffer, sizeof(audio_buffer), &bytes_read, portMAX_DELAY);
int samples_read = bytes_read / 2; // 16位数据,字节数/2=样本数
if (samples_read < 16000) { // 16kHz下1秒=16000样本
Serial.println("唤醒词采集数据不足!");
return false;
}
extractMFCC(audio_buffer, samples_read, wake_word_input_buffer, WAKE_WORD_INPUT_SIZE);
return true;
}
(3)机器学习推理与命令执行
// 唤醒词识别(返回是否检测到唤醒词)
bool detectWakeWord() {
if (!collectWakeWordAudio()) return false;
// 将MFCC特征输入模型
float* input = wake_word_interpreter->input(0)->data.f;
memcpy(input, wake_word_input_buffer, WAKE_WORD_INPUT_SIZE * sizeof(float));
// 推理
TfLiteStatus status = wake_word_interpreter->Invoke();
if (status != kTfLiteOk) {
Serial.println("唤醒词推理失败!");
return false;
}
// 输出为"唤醒词"概率(假设输出索引0为唤醒词,1为非唤醒词)
float* output = wake_word_interpreter->output(0)->data.f;
return (output[0] > 0.8); // 概率>80%认为检测到
}
// 命令识别(返回命令ID:0=打开灯光,1=关闭灯光,2=打开风扇...)
int recognizeCommand() {
// 采集3秒命令语音(16kHz*3=48000样本)
size_t total_samples = 0;
while (total_samples < 48000) {
size_t bytes_read;
i2s_read(I2S_NUM_0, audio_buffer, sizeof(audio_buffer), &bytes_read, portMAX_DELAY);
total_samples += bytes_read / 2;
}
// 提取MFCC特征
extractMFCC(audio_buffer, total_samples, command_input_buffer, COMMAND_INPUT_SIZE);
// 输入模型推理
float* input = command_interpreter->input(0)->data.f;
memcpy(input, command_input_buffer, COMMAND_INPUT_SIZE * sizeof(float));
TfLiteStatus status = command_interpreter->Invoke();
if (status != kTfLiteOk) {
Serial.println("命令推理失败!");
return -1;
}
// 取概率最大的命令ID
float* output = command_interpreter->output(0)->data.f;
int max_idx = 0;
float max_prob = 0;
for (int i = 0; i < 10; i++) { // 假设有10条命令
if (output[i] > max_prob) {
max_prob = output[i];
max_idx = i;
}
}
return (max_prob > 0.7) ? max_idx : -1; // 概率>70%认为有效
}
// 执行命令
void executeCommand(int cmd_id) {
switch(cmd_id) {
case 0: // 打开灯光
digitalWrite(RELAY_LIGHT, HIGH);
playAudio("打开灯光"); // 播放反馈(需提前录制或TTS生成)
break;
case 1: // 关闭灯光
digitalWrite(RELAY_LIGHT, LOW);
playAudio("关闭灯光");
break;
case 2: // 打开风扇
digitalWrite(RELAY_FAN, HIGH);
playAudio("打开风扇");
break;
case 3: // 关闭风扇
digitalWrite(RELAY_FAN, LOW);
playAudio("关闭风扇");
break;
default:
playAudio("未识别命令");
}
}
(4)主循环
void loop() {
// 监听唤醒词
if (detectWakeWord()) {
Serial.println("检测到唤醒词!");
digitalWrite(LED_STATUS, LOW); // 状态灯闪烁提示
delay(300);
digitalWrite(LED_STATUS, HIGH);
playAudio("请说命令"); // 提示用户输入命令
// 识别命令并执行
int cmd = recognizeCommand();
if (cmd != -1) {
executeCommand(cmd);
} else {
playAudio("未听清,请重试");
}
Serial.println("返回唤醒词监听...");
}
delay(100); // 降低CPU占用
}
六、机器学习模型训练与部署
1. 数据准备
- 唤醒词数据:录制 500 条 "小 ESP" 语音(不同人、不同距离、带环境噪声),以及 5000 条非唤醒词语音(日常对话、背景噪声);
- 命令数据:每条命令(如 "打开灯光")录制 300 条,覆盖不同口音和语速。
2. 模型训练(基于 TensorFlow)
# 简化的模型训练代码(Python)
import tensorflow as tf
from tensorflow.keras import layers
# 唤醒词模型(轻量级CNN)
model = tf.keras.Sequential([
layers.Reshape((49, 10), input_shape=(490,)), # 490=49帧×10维MFCC
layers.Conv1D(16, 3, activation='relu'),
layers.GlobalMaxPooling1D(),
layers.Dense(32, activation='relu'),
layers.Dense(2, activation='softmax') # 2类:唤醒词/非唤醒词
])
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
# 训练模型(使用准备好的MFCC特征数据)
model.fit(train_x, train_y, epochs=20, validation_data=(val_x, val_y))
# 转换为TFLite-Micro模型
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT] # 量化压缩
tflite_model = converter.convert()
with open('wake_word_model.tflite', 'wb') as f:
f.write(tflite_model)
3. 模型部署到 ESP32
-
使用
xxd工具将.tflite模型转换为 C 数组(可被 Arduino 代码引用):bash
xxd -i wake_word_model.tflite > wake_word_model.h -
生成的
wake_word_model.h中包含wake_word_model_data数组,直接在代码中引用(见初始化部分)。
七、系统优化与扩展
1. 性能优化
- 模型轻量化:通过量化(INT8)将模型体积压缩 50% 以上,减少内存占用;
- 推理加速:利用 ESP32-S3 的硬件浮点单元(FPU),或使用 TFLite-Micro 的 CMSIS-NN 优化库;
- 降噪处理:添加自适应滤波算法(如 NLMS)去除环境噪声,提高识别准确率。
2. 功能扩展
- 多设备控制:增加红外发射模块(如 IRremote 库),控制空调、电视等红外设备;
- 蓝牙联动:通过 ESP32 的 BLE 连接智能手环,实现 "靠近自动解锁" 等场景;
- OTA 模型更新:支持通过 WiFi 远程更新语音模型,无需重新烧录固件;
- 自定义命令:通过手机 APP 录制新命令,本地增量训练模型(需简化训练逻辑)。
八、注意事项
- 电源设计:继电器和功放模块需独立 5V 供电,避免 ESP32 电源波动导致语音采集噪声;
- 麦克风安装:远离继电器、电机等电磁干扰源,建议加装海绵防风罩;
- 模型校准:首次使用需在实际环境中校准(长按校准按键,采集背景噪声更新模型阈值);
- 唤醒词误触:通过增加 "双唤醒词"(如 "小 ESP + 确认")或设置灵敏度调节,减少误触发。
总结
本系统通过 ESP32-S3 的本地计算能力与机器学习结合,实现了脱离云端的智能语音控制,兼顾响应速度与隐私保护。核心难点在于语音预处理优化和轻量级模型设计,需通过大量实际数据训练和调试提升识别稳定性。扩展后可支持多设备联动,成为完整的智能家居控制中枢,适合个人 DIY 或小型场景应用。