1. 项目概述
在嵌入式AI项目中,音频采集是基础且关键的功能模块。本文将详细介绍如何在ESP32-S3平台上实现麦克风录音并保存到SD卡的功能方案。这个方案采用了乐鑫官方推荐的ES7210 ADC芯片,通过I²C和I²S接口与ESP32通信,最终将音频数据封装为WAV格式存储。
这个方案特别适合需要语音交互的AI硬件项目,如智能音箱、语音控制设备等。相比市面上常见的方案,我们的实现具有以下优势:
- 采用官方推荐的硬件方案,稳定性有保障
- 完整的WAV格式封装,兼容各类播放设备
- 模块化设计,便于集成到更大系统中
- 经过实际项目验证的可靠方案
2. 硬件选型与原理
2.1 核心硬件组件
本方案的核心硬件包括:
- ESP32-S3主控芯片:乐鑫推出的高性能Wi-Fi/BLE双模MCU,具有丰富的外设接口和足够的处理能力
- ES7210音频ADC芯片:专为语音采集优化的四通道ADC,支持I²S输出
- 模拟麦克风:选用灵敏度为-38dB的MEMS麦克风,信噪比达到65dB
- Micro SD卡模块:用于存储录音文件,支持SPI接口
2.2 接口连接方案
硬件连接采用以下配置:
- I²C接口:用于配置ES7210寄存器
- SDA:GPIO2
- SCL:GPIO1
- 设备地址:0x41
- I²S接口:用于音频数据传输
- BCK:GPIO14
- WS:GPIO13
- DATA:GPIO12
- MCK:GPIO38(主时钟)
硬件连接注意事项:
- I²S的时钟线长度应尽量保持一致
- 模拟麦克风供电需要良好的滤波
- ES7210的MICBIAS引脚需要接适当容值的电容
2.3 工作原理详解
音频采集的完整流程如下:
- 声波被麦克风转换为模拟电信号
- ES7210对模拟信号进行放大和模数转换
- 数字音频数据通过I²S接口传输到ESP32
- ESP32将PCM数据封装为WAV格式
- WAV文件通过SPI接口写入SD卡
关键参数配置:
- 采样率:16kHz(适合语音场景)
- 位深度:16bit
- 声道数:2(立体声)
- 数据格式:I²S标准格式
3. 开发环境搭建
3.1 ESP-IDF环境配置
推荐使用ESP-IDF v4.4及以上版本。环境配置步骤如下:
- 安装必要的工具链:
bash复制sudo apt-get install git wget flex bison gperf python3 python3-pip cmake ninja-build ccache libffi-dev libssl-dev dfu-util
- 获取ESP-IDF:
bash复制git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
git checkout v4.4
./install.sh
- 设置环境变量:
bash复制. ./export.sh
3.2 项目依赖组件
除了ESP-IDF基础组件外,还需要添加以下组件:
- ES7210官方驱动:
bash复制idf.py add-dependency "espressif/es7210^1.0.1~1"
idf.py reconfigure
-
FAT文件系统支持(已包含在ESP-IDF中)
-
SPI驱动(用于SD卡)
4. 核心代码实现
4.1 ES7210驱动初始化
创建audio_es7210.cpp文件实现驱动初始化:
cpp复制#include "audio_es7210.h"
#include "esp_log.h"
#define TAG "ES7210"
i2s_chan_handle_t AudioEs7210::es7210_i2s_init() {
i2s_chan_handle_t i2s_rx_chan = NULL;
i2s_chan_config_t i2s_rx_conf = I2S_CHANNEL_DEFAULT_CONFIG(EXAMPLE_I2S_NUM, I2S_ROLE_MASTER);
ESP_ERROR_CHECK(i2s_new_channel(&i2s_rx_conf, NULL, &i2s_rx_chan));
i2s_tdm_config_t i2s_tdm_rx_conf = {
.clk_cfg = {
.sample_rate_hz = EXAMPLE_I2S_SAMPLE_RATE,
.clk_src = I2S_CLK_SRC_DEFAULT,
.mclk_multiple = EXAMPLE_I2S_MCLK_MULTIPLE
},
.slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(
EXAMPLE_I2S_SAMPLE_BITS,
I2S_SLOT_MODE_STEREO,
EXAMPLE_I2S_TDM_SLOT_MASK),
.gpio_cfg = {
.mclk = EXAMPLE_I2S_MCK_IO,
.bclk = EXAMPLE_I2S_BCK_IO,
.ws = EXAMPLE_I2S_WS_IO,
.dout = GPIO_NUM_NC,
.din = EXAMPLE_I2S_DI_IO
},
};
ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(i2s_rx_chan, &i2s_tdm_rx_conf));
return i2s_rx_chan;
}
void AudioEs7210::es7210_code_init() {
// I2C初始化
i2c_config_t i2c_conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = EXAMPLE_I2C_SDA_IO,
.scl_io_num = EXAMPLE_I2C_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master = {.clk_speed = EXAMPLE_ES7210_I2C_CLK}
};
ESP_ERROR_CHECK(i2c_param_config(EXAMPLE_I2C_NUM, &i2c_conf));
ESP_ERROR_CHECK(i2c_driver_install(EXAMPLE_I2C_NUM, i2c_conf.mode, 0, 0, 0));
// ES7210初始化
es7210_dev_handle_t es7210_handle = NULL;
es7210_i2c_config_t es7210_i2c_conf = {
.i2c_port = EXAMPLE_I2C_NUM,
.i2c_addr = EXAMPLE_ES7210_I2C_ADDR
};
ESP_ERROR_CHECK(es7210_new_codec(&es7210_i2c_conf, &es7210_handle));
es7210_codec_config_t codec_conf = {
.sample_rate_hz = EXAMPLE_I2S_SAMPLE_RATE,
.mclk_ratio = EXAMPLE_I2S_MCLK_MULTIPLE,
.i2s_format = EXAMPLE_I2S_TDM_FORMAT,
.bit_width = (es7210_i2s_bits_t)EXAMPLE_I2S_SAMPLE_BITS,
.mic_bias = EXAMPLE_ES7210_MIC_BIAS,
.mic_gain = EXAMPLE_ES7210_MIC_GAIN,
.flags = {.tdm_enable = true}
};
ESP_ERROR_CHECK(es7210_config_codec(es7210_handle, &codec_conf));
ESP_ERROR_CHECK(es7210_config_volume(es7210_handle, EXAMPLE_ES7210_ADC_VOLUME));
}
4.2 WAV文件封装
创建wav_recorder.cpp实现WAV封装:
cpp复制#include "wav_recorder.h"
#include "format_wav.h"
esp_err_t WavRecorder::record(uint16_t seconds) {
esp_err_t ret = ESP_OK;
uint32_t byte_rate = EXAMPLE_I2S_SAMPLE_RATE * EXAMPLE_I2S_CHAN_NUM * EXAMPLE_I2S_SAMPLE_BITS / 8;
uint32_t wav_size = seconds * byte_rate;
// 创建WAV头部
wav_header_t wav_header = WAV_HEADER_PCM_DEFAULT(
wav_size,
EXAMPLE_I2S_SAMPLE_BITS,
EXAMPLE_I2S_SAMPLE_RATE,
EXAMPLE_I2S_CHAN_NUM);
// 写入WAV头部
if (m_file->write_file((char*)&wav_header, sizeof(wav_header)) != ESP_OK) {
ESP_LOGE(TAG, "Failed to write WAV header");
return ESP_FAIL;
}
// 录音数据采集
size_t wav_written = 0;
static int16_t i2s_readraw_buff[8192];
ESP_GOTO_ON_ERROR(audio_input->enable(), err, TAG, "Failed to enable audio input");
while (wav_written < wav_size) {
size_t bytes_read = 0;
ESP_GOTO_ON_ERROR(
audio_input->read(i2s_readraw_buff, sizeof(i2s_readraw_buff), &bytes_read),
err, TAG, "Failed to read audio data");
if (m_file->write_file((char*)i2s_readraw_buff, bytes_read) != ESP_OK) {
ESP_LOGE(TAG, "Failed to write audio data");
goto err;
}
wav_written += bytes_read;
}
err:
audio_input->disable();
return ret;
}
4.3 SD卡存储实现
创建sd_card.cpp实现文件存储:
cpp复制#include "sd_card.h"
#include "esp_vfs_fat.h"
esp_err_t SdCard::open(const char* path, const char* mode) {
if (!mounted) {
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 5,
.allocation_unit_size = 16 * 1024
};
ESP_ERROR_CHECK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card));
mounted = true;
}
file = fopen(path, mode);
return file ? ESP_OK : ESP_FAIL;
}
esp_err_t SdCard::write_file(const char* data, size_t size) {
return fwrite(data, 1, size, file) == size ? ESP_OK : ESP_FAIL;
}
5. 系统集成与测试
5.1 主程序实现
main.cpp中集成各模块:
cpp复制#include "wav_recorder.h"
#include "sd_card.h"
#include "audio_es7210.h"
extern "C" void app_main() {
// 初始化SD卡
auto sdFile = std::make_shared<SdCard>();
ESP_ERROR_CHECK(sdFile->open("/sdcard/recording.wav", "w+"));
// 初始化音频输入
auto audio = std::make_shared<AudioEs7210>(16000, 2);
// 创建录音器
WavRecorder recorder(sdFile, audio);
// 开始录音
ESP_ERROR_CHECK(recorder.record(10));
// 关闭文件
sdFile->close();
}
5.2 常见问题排查
在实际开发中可能会遇到以下问题:
-
无音频数据:
- 检查I²S线序是否正确
- 确认ES7210的MICBIAS电压正常(2.87V)
- 检查I²C是否能正常读写寄存器
-
音频失真:
- 调整麦克风增益(通过ES7210寄存器)
- 检查电源是否干净(建议增加滤波电容)
- 确认采样率设置正确
-
SD卡写入失败:
- 检查卡是否格式化(FAT32)
- 确认SPI时钟不超过20MHz
- 检查电源供电是否充足
调试技巧:
- 使用逻辑分析仪检查I²S信号
- 先单独测试各模块功能
- 逐步增加录音时长测试稳定性
6. 性能优化建议
经过实际测试,本方案还有以下优化空间:
-
内存优化:
- 使用双缓冲机制减少内存占用
- 动态调整I²S缓冲区大小
-
功耗优化:
- 在不录音时关闭ES7210电源
- 使用低功耗模式
-
功能扩展:
- 增加音频压缩功能(如ADPCM)
- 实现语音激活检测
- 支持多段录音
这个方案已经在多个AI硬件产品中实际应用,稳定录制时长可达数小时。关键是要确保电源稳定和良好的PCB布局,特别是模拟音频部分的走线需要特别注意。
