上个月给一个环境监测节点加声音采集功能,板子上的核心就是INMP441麦克风和ESP32,通过I2S外设把音频数据读进来。一路调试下来踩了不少坑,也把I2S这套东西彻底摸了一遍。这篇文章就把整个实战过程拆开来讲,从硬件选型、接线、I2S协议配置到代码实现和排错,适合那些想用ESP32做音频采集、语音识别、声音触发控制的开发者。无论你是第一次接触I2S,还是已经在用模拟麦克风想换数字方案,这篇文章都能给你一条可以直接落地的路线。
先说结论:INMP441这颗数字MEMS麦克风配上ESP32的I2S外设,是我目前在音频采集项目里用得最顺手的组合。它没有模拟麦克风那种偏置电路和放大电路的设计负担,数据以24bit数字格式直接进MCU,一致性、抗干扰性都好很多。
1. 为什么是INMP441+ESP32而不是模拟麦克风
1.1 模拟麦方案在真实项目里遇到的麻烦
很多入门教程推荐用MAX4466或者MAX9814这类模拟麦克风模块,接ESP32的ADC引脚就能读。看起来简单,但实际做产品或者做长时间采集的时候,问题一堆。
ESP32的ADC是12bit分辨率,静态噪声和积分非线性都比较明显,读出来的音频波形底噪大,做语音识别时特征不明显。其次,模拟麦克风输出的是毫伏级别的信号,走线稍长一点就容易耦合电源噪声和数字信号噪声,你需要在PCB布局和屏蔽上下不少功夫。更麻烦的是,模拟麦需要自己搭偏置电路,还要调放大倍数,不同批次麦克风的灵敏度还不一定一致,量产一致性不好保证。
INMP441是数字输出,内置了ADC和放大器。它把模拟信号转成数字这件事在麦克风内部就完成了,输出的是标准的I2S协议信号,直接给MCU就能用。你可以想象成,模拟麦是让信号在线路上裸奔,而数字麦相当于先在一个干净房间内完成模数转换,再穿着数字信号这件防弹衣出门。
1.2 INMP441的关键指标
这颗麦克风的核心参数,我整理了一张表方便对比:
| 参数 | 数值 | 说明 |
|---|---|---|
| 接口协议 | I2S,24bit数据 | 无需MCLK,ESP32的I2S可直接驱动 |
| 灵敏度 | -26 dBFS(94dB SPL @ 1kHz) | 用于后续分贝换算,很重要 |
| 信噪比SNR | 61 dBA | 底噪水平适中,语音采集足够 |
| 声学过载点 | 120 dB SPL | 最大能测很响的声音,不容易削波 |
| 供电电压 | 1.8V~3.3V | 绝对最大3.6V,不能接5V |
| 工作电流 | 约1.4mA | 非常省电,适合电池供电 |
这些参数里面,灵敏度是最容易被忽略的。-26dBFS的意思是,在1kHz、94dB SPL的标准声压下,数字输出大概是满量程的-26dB。这个值在后面做分贝计算的时候要直接用到。
1.3 为什么选择ESP32而不是STM32
STM32当然也能做音频采集,它有I2S接口,性能也够。但STM32的玩法通常是:I2S口外挂一颗音频编解码芯片,比如ES8388、WM8960之类,这又引入了芯片配置、模拟前端设计的问题。而且如果做完采集还要把数据传出去,STM32要么加WiFi模块,要么加蓝牙模块,整体复杂度直线上升。
ESP32自带I2S外设,原生支持Wi-Fi和蓝牙,双核跑起来音频采集和网络传输可以分核处理,开发工具链也成熟。对于要做音频采集、无线传输、语音识别这类应用,ESP32是更省事的选择。说直白点,STM32是一块干净的画布,而ESP32是画布上已经装好了网络模块和音频接口的半成品,后者更适合快速落地。
2. 硬件接线里的四个关键决策点
2.1 GPIO引脚分配
I2S一般涉及三根信号线:BCLK(位时钟)、WS(字选择,也叫LRCLK)、DIN(数据输入)。INMP441的数据输出引脚叫SD,接ESP32的DIN。
我常用的引脚组合是:
| 信号 | GPIO | 说明 |
|---|---|---|
| I2S_BCK | GPIO26 | 位时钟输出 |
| I2S_WS | GPIO25 | 字选择输出 |
| I2S_SD | GPIO22 | 数据输入 |
这个组合在Arduino的I2S示例里很常见,踩坑少。如果你用的开发板引脚比较紧张,也可以换别的GPIO,但要注意避开几个特殊引脚:GPIO16和GPIO17在带PSRAM的开发板上通常被占用,GPIO1和GPIO3是串口调试脚,用了会影响烧录日志。
2.2 供电与去耦
INMP441的供电电压范围是1.8V~3.3V,绝对最大额定值是3.6V。注意,绝对不要接5V,接上去大概率直接烧掉。我见过有人把模块当普通传感器用,接了5V,麦克风瞬间冒烟,就是不看datasheet的代价。
电源质量对音频采集的影响很大。INMP441的电源纹波会直接耦合进数字输出。建议在VDD和GND之间加一个0.1uF陶瓷电容和一个10uF电解电容做去耦,如果空间允许,再串联一个10Ω电阻做一个简单的RC滤波。对长期采集项目来说,这个电容成本不到几毛钱,但能让底噪降一个档次。
2.3 L/R声道选择
INMP441的L/R引脚决定了它在WS信号的哪个相位输出数据。把这个引脚接GND,麦克风会在WS为低电平时输出数据,也就是左声道;接VDD,则在WS为高时输出数据,也就是右声道。
这一点非常关键,因为它必须和代码里的声道配置匹配。如果你把L/R接地了,但代码里配的是右声道通道,那么读出来的数据要么全是零,要么是一堆无意义的数据。大多数教程默认L/R接地,代码里也配左声道,两者对上才能正常工作。
2.4 实物接线清单
整个接线其实很少,完整对照如下:
- VDD → 3.3V
- GND → GND
- SCK → GPIO26
- WS → GPIO25
- SD → GPIO22
- L/R → GND(选左声道)
核心是共地。很多新手把ESP32和麦克风模块分开供电,结果死活读不到有效数据,原因就是参考电平不一致,I2S信号到了ESP32引脚那里电平和本地GND对不上。确保所有的GND接在同一个网络里,这是数字音频调试里最基本的一条规则。
3. I2S协议速成:拿到你的数据之前必须理解的四件事
3.1 I2S四根线与时序关系
I2S(Inter-IC Sound)是数字音频设备之间传输PCM音频数据的总线协议。标准情况下有四根线:
- BCLK:位时钟,每个时钟周期对应一位数据。
- WS:字选择,也叫LRCLK,频率等于采样率,低电平表示左声道,高电平表示右声道。
- DIN:数据线,按位传输音频数据。
- 有些设备还需要MCLK(主时钟),但INMP441以及很多MEMS数字麦克风不需要,ESP32的I2S外设也不强制输出MCLK。这是INMP441一个很大的便利。
时序上,INMP441在BCLK的下降沿改变数据,接收端在上升沿采样。这个细节你不用太纠结,ESP32的I2S外设会自动处理,只要你用的通信格式正确即可。你真正要关心的是,BCLK和WS的频率关系:WS频率就是采样率,BCLK频率则是采样率乘以位深乘以声道数。比如16kHz采样、32bit位深、双声道,BCLK就是16k322=1.024MHz。
3.2 通信格式选错,数据全乱
I2S协议有两个常见变体:标准I2S(Philips格式)和左对齐格式。两者区别在于数据相对WS边沿的起始时间:
- 标准I2S:数据从WS变化后的第二个BCLK上升沿开始。
- 左对齐:数据从WS变化后的第一个BCLK上升沿开始。
ESP32的Arduino驱动中,通信格式可以通过I2S_COMM_FORMAT_STAND_I2S和I2S_COMM_FORMAT_STAND_MSB来配置。对于INMP441,官方手册写的是标准I2S格式。但这里有个有意思的现象:我用标准I2S格式也能正常采到数据,具体取决于ESP32 Arduino core的版本和封装方式。
所以在调试阶段,如果你的数据看起来完全错乱,最大可能是通信格式配置和麦克风实际输出格式不匹配。建议先把格式固定为I2S_COMM_FORMAT_STAND_I2S,如果能出数据但波形看起来不对,再切换为I2S_COMM_FORMAT_STAND_MSB对比。
3.3 采样率、位深和DMA缓冲区怎么配
I2S配置里三个最核心的参数:采样率、位深、DMA缓冲区。
采样率按场景选择:
| 场景 | 推荐采样率 |
|---|---|
| 语音识别 | 16kHz或8kHz |
| 一般声音事件检测 | 16kHz~44.1kHz |
| 音乐录音或音频分析 | 44.1kHz或48kHz |
位深方面,INMP441输出24bit数据,但ESP32的I2S外设在Arduino驱动里支持16bit、24bit、32bit几种配置。我通常配置为32bit,因为I2S外设内部会把24bit数据放入32bit容器中,处理起来比较方便,计算时再根据对齐方式做移位。
DMA缓冲区影响采集的实时性和稳定性。我的推荐配置是:
dma_buf_count = 8dma_buf_len = 64
这个配置下,一次DMA传输的数据量大约是64*4字节*8个缓冲区,也就是2KB左右。缓冲区太小会导致I2S溢出,数据丢帧;缓冲区太大会让延迟增大,不适合实时性要求高的场景。实测下来,8个64长度的缓冲区,在16kHz采样、32bit位深的条件下,能保证稳定连续采集,CPU占用也不高。
3.4 24bit有效数据装进32bit容器时的对齐问题
这是很多人第一次读I2S数据会懵的地方。你明明配置了32bit位深,读出来的int32_t数值却似乎不是满量程的±2^31,而是更小的范围。原因在于INMP441只输出24bit有效数据,ESP32的I2S外设会在内部做对齐,把24bit数据放在32bit容器的某个位置上,其余位补零。
我通常的做法是:先打印几个原始读到的int32_t样本,看数值范围。如果数值大约在±8000000即2^23附近,说明这个数组已经是24bit左对齐后的有效值,直接按24bit处理并计算RMS即可;如果数值能够达到±2^30的级别,说明有效位在高位,计算时先右移8位再使用。
不同版本的ESP32 Arduino core在这方面的行为不完全一致,最稳妥的办法就是实测打印,别凭经验猜。这也是调试音频采集项目最基本的一步。
4. 开发环境准备与工程骨架
4.1 Arduino IDE、PlatformIO和ESP-IDF三选一
做ESP32音频采集,开发环境有三个主流选择:
- Arduino IDE:上手最快,适合验证I2S采集逻辑,库管理简单,烧录方便,缺点是不便于大型工程管理。
- PlatformIO:基于VSCode,可以管理多平台工程,依赖和库版本一目了然,我目前主力用这个。
- ESP-IDF:Espressif官方框架,功能最全,性能也最强,Arduino其实是IDF的一个组件封装。如果要做产品级固件或者需要细粒度控制I2S寄存器,直接上IDF。
我的建议是:第一次跑通用Arduino IDE,一旦确认要长期迭代,就迁到PlatformIO。两者共享大部分代码,迁移成本很低。
4.2 Arduino环境下添加ESP32开发板支持
如果你用的是Arduino IDE,需要先添加ESP32开发板支持:
- 在“文件”->“首选项”->“附加开发板管理器网址”里填入
https://espressif.github.io/arduino-esp32/package_esp32_index.json - 打开“工具”->“开发板”->“开发板管理器”,搜索
esp32,安装Espressif官方包。 - 选择开发板型号,常见的是
ESP32 Dev Module。
烧录时有一个常见的坑:ESP32进入下载模式需要拉低GPIO0。如果开发板没有自动下载电路,你需要按住BOOT按钮,再点烧录,等开始烧录后松开。串口号选对也很重要,大多数情况下选择设备管理器里能看到USB转串口的那个COM口。
4.3 最小工程骨架
一个最小可用的工程骨架只需要三件事:初始化I2S驱动、配置引脚、循环读取数据。先把这三步跑通,再慢慢加音频处理逻辑。
#include <driver/i2s.h> #define I2S_BCK 26 #define I2S_WS 25 #define I2S_SD 22 void i2s_init(); void setup() { Serial.begin(115200); i2s_init(); } void loop() { int32_t data[512]; size_t bytesRead = 0; i2s_read(I2S_NUM_0, data, sizeof(data), &bytesRead, portMAX_DELAY); int sampleCount = bytesRead / sizeof(int32_t); for (int i = 0; i < sampleCount; i++) { if (i % 64 == 0) { Serial.println(data[i]); } } delay(10); }先确认能读到非零且连续变化的数值,再继续后续的开发。
5. 音频采集代码实现与分贝换算
5.1 I2S驱动初始化的完整代码
这是我在项目中稳定运行过的初始化代码,直接可用。
#include <driver/i2s.h> #define I2S_BCK 26 #define I2S_WS 25 #define I2S_SD 22 #define I2S_PORT I2S_NUM_0 #define SAMPLE_RATE 16000 #define SAMPLE_BITS I2S_BITS_PER_SAMPLE_32BIT void i2s_init() { i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), .sample_rate = SAMPLE_RATE, .bits_per_sample = SAMPLE_BITS, .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, .communication_format = I2S_COMM_FORMAT_STAND_I2S, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 8, .dma_buf_len = 64, .use_apll = false, .tx_desc_auto_clear = false, .fixed_mclk = 0 }; i2s_pin_config_t pin_config = { .bck_io_num = I2S_BCK, .ws_io_num = I2S_WS, .data_out_num = I2S_PIN_NO_CHANGE, .data_in_num = I2S_SD }; esp_err_t err = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); if (err != ESP_OK) { Serial.printf("I2S install failed: %d\n", err); return; } err = i2s_set_pin(I2S_PORT, &pin_config); if (err != ESP_OK) { Serial.printf("I2S set_pin failed: %d\n", err); return; } }有几个配置项单独说明一下:
channel_format用了I2S_CHANNEL_FMT_ONLY_LEFT,对应L/R接地的情况。use_apll默认false。APLL能提供更精准的时钟,但对普通语音采集影响不大,保持false即可,开APLL反而可能引入额外功耗。dma_buf_count和dma_buf_len我测过不同组合,最终稳定在这个值,实际项目里你可以在64到256之间调节,看是否有卡顿。
5.2 读取音频数据:DMA缓冲与字节对齐
i2s_read是阻塞式读取,数据由DMA从I2S外设搬运到内存。你给的缓冲区大小决定了每次读取的样本数,bytesRead返回实际读到的字节数。
const int BUFFER_SIZE = 512; int32_t sampleBuffer[BUFFER_SIZE]; void readAudio() { size_t bytesRead = 0; esp_err_t err = i2s_read(I2S_PORT, sampleBuffer, sizeof(sampleBuffer), &bytesRead, portMAX_DELAY); if (err != ESP_OK) { Serial.printf("I2S read error: %d\n", err); return; } int sampleCount = bytesRead / sizeof(int32_t); // 根据实测数据范围选择是否右移 for (int i = 0; i < sampleCount; i++) { int32_t pcm24 = sampleBuffer[i] >> 8; // 如果原始数据是左对齐32位才需要 // pcm24现在是一个24bit有符号数 } }注意bytesRead的单位是字节,所以样本数要除以sizeof(int32_t)。如果你配置为16bit位深,就除以2。这一点和I2S外设的位深配置强相关,别写死。
5.3 从原始数据到分贝值:RMS与dBFS换算
音频采集最常用的指标就是分贝值。先计算一组样本的RMS(均方根),再转为dBFS,最后根据麦克风灵敏度近似换算为dB SPL声压级。
RMS计算:
double calculateRms(int32_t* data, int len) { double sum = 0; for (int i = 0; i < len; i++) { int32_t val = data[i] >> 8; // 按实际对齐调整 sum += (double)val * val; } double rms = sqrt(sum / len); return rms; }dBFS换算的思路是:以24bit有符号满量程为参考,满量程幅度是2^23。但实际用32bit容器时RMS的值域可能不同,最好先记录一组自身数据范围内的最大值,用这个作为归一化基准。比如你在安静环境下读到的RMS大概在几百到几千,大声说话时能达到几十万,那么归一化基准可以设为满量程值对应的数值。
经验公式是:
dBFS = 20 * log10(RMS / MAX_VALUE)
然后换算为dB SPL:
dBSPL = dBFS + 120
为什么是120?因为INMP441在94dB SPL声压下输出-26dBFS,所以dBSPL = dBFS - (-26) + 94 = dBFS + 120。
注意,这个换算只是近似值,它假设麦克风灵敏度一致且声场是标准声场。要做精确声级计,必须用标准声源校准。但对一般的声音触发、语音检测、环境噪声监控来说,这个换算足够用了。
5.4 实际测试:对着麦克风说话观察数值变化
代码跑起来之后,我建议先做一个最简单的手动验证:打开串口监视器,把采样率设为16kHz,静默时观察串口打印的RMS或分贝值,然后对着麦克风说话,观察数值是否明显抬升。
我在实验里得到的结果大致是:
- 安静的室内:RMS大约2000左右,换算过来约40-45dB SPL。
- 正常说话(距离20cm):RMS上升到20000以上,约为55-65dB SPL。
- 拍手或大声喊叫:RMS可以突破十万,接近80dB SPL以上。
如果你的数据一直不动,或者一直是极大值,别急着怀疑麦克风,先看代码里的对齐方式是否匹配你的ESP32 Arduino core版本。
6. 实测验证方案:怎么看采集到的东西对不对
6.1 用串口绘图器看环境噪声本底
Arduino IDE自带的串口绘图器(Serial Plotter)非常适合快速看数据波形。要显示得清晰,建议在loop里按固定的短间隔输出RMS或者采样值,不要一次输出几百个点,不然绘图器会刷新不过来。
写法参考:
while (true) { readAudio(); double rms = calculateRms(sampleBuffer, sampleCount); Serial.println(rms); delay(10); }在串口绘图器里,你会看到一个在某个基准线附近波动的曲线。如果曲线是一条水平线纹丝不动,说明数据异常;如果波动范围很大而且随机,说明底噪偏大,要去检查电源和接地。正常的静音底噪曲线应该是小而密集的毛刺状,而不是大幅脉冲。
6.2 保存为WAV文件验证波形
串口绘图器适合看实时数据,但要验证音频质量,还是得落到WAV文件。ESP32可以直接把PCM数据写到SD卡,生成WAV文件,然后在电脑上用Audacity打开。
WAV文件的头部结构比较简单,44字节就能搞定。写WAV头时注意几个字段:采样率、位深、声道数、数据区大小。位深这一步很关键,INMP441原始输出是24bit,但很多声音软件对24bit支持不如16bit好,建议保存为16bit,做一次右移位深转换即可。
typedef struct { char chunkID[4]; uint32_t chunkSize; char format[4]; char subchunk1ID[4]; uint32_t subchunk1Size; uint16_t audioFormat; uint16_t numChannels; uint32_t sampleRate; uint32_t byteRate; uint16_t blockAlign; uint16_t bitsPerSample; char subchunk2ID[4]; uint32_t subchunk2Size; } wav_header_t;写完头部后,持续读取I2S并写入PCM数据,最后更新subchunk2Size和chunkSize字段。实际操作中,最简单的方式是先在SD卡上预留一块固定位置,先写头部,再直接追加数据,全部录完再回头把文件大小字段补上。
6.3 离线数据分析:采集的音频能否用于语音识别
WAV文件落到电脑后,可以试试几个工具:
- Audacity:直接看波形,听回放,检查是否存在爆音、锯齿状截断。
- Python(librosa/scipy):做FFT看频谱,检查低频噪声是否过重。
- 语音识别测试:把样本丢给本地Whisper或者云端的语音识别接口,看在16kHz采样率下识别准确率如何。
这一步骤的意义在于:音频采集不光是“能读到数据”,还要让后续算法能消费这些数据。我遇到过I2S配置一切正常、波形看着也顺眼,但丢给语音识别API就识别不准的情况,后来发现是采样率实际和配置不一致,导致音频被变速了。用Audacity看WAV的时长和频谱就能发现这种问题。
7. 高频踩坑清单与排查链路
7.1 现象一:读到的数据全是0
这是最常遇到的问题。排查顺序:
- 检查L/R引脚接法是否和
channel_format匹配。 - 检查DIN的GPIO是否配置正确,INMP441的SD引脚是否真的接到了那个GPIO。
- 确认3.3V供电正常,测量VDD引脚。
- 用示波器或逻辑分析仪看BCLK和WS有没有信号。没有示波器的话,在初始化I2S后读取GPIO电平也可判断,不过没有示波器直观。
- 确认共地。
我遇到过一次匪夷所思的情况:SD引脚焊接虚焊,万用表一量通,但实际接触电阻大,导致高电平被拉低。最后用飞线直接跨接才解决。这种硬件级别的坑别忽略。
7.2 现象二:数据忽大忽小、有规律跳变
如果你读到的数据呈现出周期性跳变,大概率是采样率或DMA配置问题。典型的错误是:采样率设置成44.1kHz,但DMA缓冲区太小导致周期性的溢出;或者dma_buf_len设置过大,导致CPU处理不过来,丢数据。
解决方法:
- 提高
dma_buf_count到16。 - 降低采样率到16kHz验证是否改善。
- 检查loop里是否做了太多耗时操作,比如打印大量日志。打印是I2S采集的大忌,
Serial.println会阻塞CPU,导致DMA缓冲区溢出。生产代码里不要每个样本都打印。
7.3 现象三:WiFi一开,音频里全是毛刺噪声
这个问题在ESP32上非常典型。MIC采集和WiFi同时工作,如果你用了i2s_read阻塞读取,WiFi任务可能抢占CPU,来不及搬DMA缓冲区的数据,出现丢数据。丢数据在听感上的表现就是噼啪声、毛刺。
解决思路有三个方向:
- 把I2S读取任务绑到一个核心上,WiFi任务放另一个核心:
xTaskCreatePinnedToCore(...) - 调大
dma_buf_count,给I2S更多缓冲余地。 - 如果只是做简单的声音事件检测,不要全程开WiFi,采集完再临时打开WiFi传输,传完再关掉。
ESP32的WiFi和蓝牙实际上是可以同时工作的(关于“esp32蓝牙和wifi可以一起用吗”这个问题,硬件上是共存的,但在应用层要处理可能的中断干扰),但在高负载下,网络协议栈会频繁触发中断和调度,对实时音频采集的影响还是很明显的。
7.4 排查顺序:先看引脚再看时序最后看电源
我把这套排查顺序总结成一个固定流程,分享出来:
- 用万用表确认所有供电和GND连接正确,测VDD电压。
- 打印I2S初始化返回值,确认
i2s_driver_install和i2s_set_pin返回ESP_OK。 - 用逻辑分析仪抓BCLK、WS、SD三根线的波形,确认有时钟、有数据。
- 打印原始样本,看数值范围是否符合预期。
- 调整通信格式、位深、对齐方式,逐一比对。
- 最后才检查电源噪声和布局。
按这个顺序来,90%的问题能在前三步找到原因。不要一上来就怀疑电源,那样容易绕弯子。
8. 后续扩展:从音频采集到语音交互
8.1 双麦克风波束成形的硬件准备
INMP441的L/R引脚设计天然支持一左一右两颗麦克风构成立体声或者波束成形阵列。把第一颗的L/R接地,第二颗的L/R接VDD,两根SD线接同一个I2S数据引脚,就能在一条I2S总线上同时读取左右声道数据。
代码上,把channel_format从I2S_CHANNEL_FMT_ONLY_LEFT改为I2S_CHANNEL_FMT_RIGHT_LEFT,读取时偶数下标是左声道,奇数下标是右声道。这个特性在做声源定位、降噪算法时特别有用,硬件成本却只是多一颗几块钱的麦克风。
8.2 把音频送到云端识别的构架建议
ESP32采集到音频后,最典型的应用是语音识别。单纯靠ESP32本地跑大模型不现实,常见做法是把音频流通过WiFi发到云端。结构上我建议:
- ESP32负责I2S采集和音频预处理,计算出RMS、过零率等特征。
- 特征数据通过WebSocket或MQTT上报给服务器,服务器做识别或者分类。
- 如果需要传原始音频,先用OPUS编码压缩,UDP发送,注意控制码率。
在ESP-IDF里还可以使用讯飞等语音识别接口,前提是设备联网稳定。ESP32做客户端,用HTTP或WebSocket把录音数据上传,识别结果再回传给设备。这套方案比本地跑识别省事得多。
8.3 本地触发词检测与低功耗循环采样的取舍
如果产品想省电,不可能一直开着I2S采集。低功耗设计的思路是:平时让ESP32进入深度睡眠,用一个阈值电路或者IMU来检测环境声音,超过阈值再唤醒ESP32做完整采集。
不过,INMP441本身功耗很低,即使一直在采集,电流也就1.4mA左右。真正耗电的是ESP32的CPU和WiFi模块。所以更实用的做法是:让ESP32保持在一个低频运行状态,每次只采集一小段音频,用轻量级算法判断是否需要继续处理,比如计算RMS超过阈值后再启动WiFi上传。用一个简单的触发词检测模型在本地做唤醒,效果更好,但这部分计算就比较吃资源了,需要结合PSRAM和ESP32的双核来做。
我实测下来,先I2S采一小段,算RMS,再决定是否开WiFi,总体能耗能比“全程开WiFi+采集”降低60%以上。这个优化思路做环境监测或者电池供电设备时值得尝试。
整个I2S音频采集链路跑通之后,后面接什么都顺了。文章里写的接线方案、DMA配置、分贝换算公式,都是我在实际项目里验证过的,可以放心抄。如果你在自己的板子上遇到和文章里不同的现象,多半是ESP32 Arduino core版本或者开发板引脚差异导致的,按第7节的排查顺序走一遍基本都能定位。