摘要:本文复盘了 V3 智控面板中 ESP32-S3 驱动 INMP441 数字麦克风的硬件连接、I2S 外设配置与数据读取逻辑,并指出 V4 需要将驱动分层以提高复用性
1. V3 INMP441 驱动复盘
1.1 信号链路回顾
- 声波信号需要经过如下处理,才能被ESP32-S3 所使用:
声波信号 → 麦克风 → 微弱电压信号 → 放大/滤波等预处理 → ADC采样 → 数字信号(I2S协议格式) → ESP32-S3
- 而 INMP441 是一款高性能、低功耗、数字输出、全向型的MEMS麦克风,内置了一个MEMS传感器、信号调理、一个模数转换器、抗混叠滤波器、电源管理以及一个行业标准的 24 位 I2S 接口
- INMP441 可以直接把上述过程的中间过程在内部完成,把处理好的数据信号按照标准的 I2S 模式发送给 MCU
1.2 ESP32-S3 的 I2S 外设
- 硬件资源: 2 个 I2S 外设,通过配置这些外设,可以借助 I2S 驱动来输入和输出采样数据
- 通信协议:
- 标准 I2S 协议
- PDM 协议
- 外设功能:
- 可用作系统主机或从机
- 可用作发射器或接收器
- DMA 控制器支持流数据采样,CPU 无需单独复制每个采样数据
- 每个控制器都有独立的 RX 和 TX 通道,连接到不同 GPIO 管脚,能够在不同的时钟和声道配置下工作
- 注意:
- 尽管在一个控制器上 TX 通道和 RX 通道的内部 MCLK 相互独立,但输出的 MCLK 信号只能连接到一个通道
- 如果需要两个互相独立的 MCLK 输出,必须将其分配到不同的 I2S 控制器上
1.3 硬件连接与外设配置
- 本项目使用 I2S0 外设驱动 INMP441 模块
- 硬件连接如下:
| INMP441 引脚 | 含义 | ESP32-S3 接线 |
|---|---|---|
| SD | I2S 的 DOUT 信号输出 | 10 |
| L/R | 左右声道选择 | GND,输出左声道 |
| WS | I2S 的 LRCLK 信号输入 | 21 |
| SCK | I2S 的 BCLK 信号输入 | 9 |
- I2S0 外设配置如下
| 配置项 | 设定值 | 说明 |
|---|---|---|
| 采样率 | 16000Hz | 满足语音应用的基本要求 |
| 数据位宽 | 32 bit | INMP441 输出 24 bit 有效数据,但 I2S 帧固定为 32 bit |
| 通道模式 | MONO(单声道) | 只用到一个麦克风 |
| 槽位掩码 | I2S_STD_SLOT_LEFT | 仅接收左声道数据 |
| I2S 主/从 | Master | ESP32-S3 产生 BCLK 和 WS |
| DMA 帧数 | 512 | 每个 DMA 帧包含 512 个 32 bit 样本 |
| DMA 描述符 | 6 | DMA 链描述符数量,影响缓冲区管理 |
| 引脚配置 | BCLK、WS、DIN 已指定,MCLK、DOUT 未使用 | INMP441 不需要 MCLK,DOUT 是输出脚,本端只用 DIN 接收数据 |
- 初始化代码如下:
#define _SAMPLE_RATE_HZ (16000)
#define _DMA_FRAME_NUM (512) // 每DMA帧32位样本数
#define _DMA_DESC_NUM (6)static i2s_chan_handle_t _rx_handle = NULL;bool bsp_i2s_init(void)
{ if (_rx_handle != NULL)
return false; i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(HW_I2S_NUM_INMP441, I2S_ROLE_MASTER);
chan_cfg.dma_desc_num = _DMA_DESC_NUM;
chan_cfg.dma_frame_num = _DMA_FRAME_NUM;
esp_err_t ret = i2s_new_channel(&chan_cfg, NULL, &_rx_handle);
if (ret != ESP_OK)
{
_rx_handle = NULL;
return false;
} i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_SAMPLE_RATE_HZ);
i2s_std_slot_config_t slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(
I2S_DATA_BIT_WIDTH_32BIT, // 必须32位
I2S_SLOT_MODE_MONO); slot_cfg.slot_mask = I2S_STD_SLOT_LEFT; // L/R接地 ⇒ 有效数据在左声道 i2s_std_gpio_config_t gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = HW_PIN_INMP441_BCLK,
.ws = HW_PIN_INMP441_WS,
.dout = I2S_GPIO_UNUSED,
.din = HW_PIN_INMP441_SD,
.invert_flags = {.mclk_inv = false, .bclk_inv = false, .ws_inv = false},
}; i2s_std_config_t std_rx_cfg = {.clk_cfg = clk_cfg, .slot_cfg = slot_cfg, .gpio_cfg = gpio_cfg}; ret = i2s_channel_init_std_mode(_rx_handle, &std_rx_cfg);
if (ret != ESP_OK)
{
i2s_del_channel(_rx_handle);
_rx_handle = NULL;
return false;
} ret = i2s_channel_enable(_rx_handle);
if (ret != ESP_OK)
{
i2s_del_channel(_rx_handle);
_rx_handle = NULL;
return false;
}
return true;
}
1.4 数据读取
- 数据手册中的时序图如下:
-
同时参考例程代码,可知
- INMP441 输出的 I2S 数据为 32 bit 帧,其中有效数据是 24 bit
- 低 8 bit 为填充零或无效
-
所以在读取的时候,需要先右移 8 个 bit,丢弃低 8bit 的无效位,同时可以再右移 6 位,相当于除以 64,将数据进行了压缩,适合常见音频处理库。最后的效果就是右移 14bit
-
若需保留 24 bit 精度,可改为右移 8 位存入 int32_t 数组
-
代码如下:
/***
* @brief 读取I2S多个uin32_t数据
* @param buf 缓冲区
* @param buf_len 缓冲区长度
* @param timeout_ms 读取超时
* @return int32_t类型的样本数
*/
int bsp_i2s_read(int16_t *buf, size_t buf_len, uint32_t timeout_ms)
{
if (_rx_handle == NULL || buf_len == 0 || timeout_ms == 0)
return -1; int32_t *temp = heap_caps_malloc(buf_len * sizeof(int32_t),
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); if (temp == NULL)
return -1; size_t bytes_read = 0;
esp_err_t ret = i2s_channel_read(_rx_handle, temp, buf_len * sizeof(int32_t), &bytes_read, timeout_ms);
if (ret != ESP_OK)
{
free(temp);
return -1;
} size_t samples = bytes_read / sizeof(int32_t); // 样本数
for (size_t i = 0; i < samples; i++)
{
buf[i] = (int16_t)(temp[i] >> 14); // 参考 VUMeterDemo 例程
} free(temp);
return (int)samples;
}
2. V4 优化方向
- V4 应该在原有的 bsp_i2s 的基础上分离出 inmp441 的驱动逻辑为 dri_inmp441 库,保留原有的 i2s 库运用逻辑为新的 bsp_i2s 库,供 drv_i2s 调用
- 这样分层后,bsp_i2s 只负责 I2S 硬件配置与原始数据读写,dri_inmp441 处理 INMP441 特有的数据格式转换(如右移 14 位),便于未来更换麦克风型号或复用 I2S 驱动
