INMP441 引脚 | 连接 ESP32 引脚 | 说明 |
---|---|---|
VDD | 3.3V | 供电 |
GND | GND | 共地 |
SCK (BCLK) | GPIO26 | I²S BCLK |
WS (LRCLK) | GPIO25 | I²S LRCLK |
SD (DOUT) | GPIO22 | I²S 数据输出(麦到ESP32) |
L/R | GND | 左声道(或接3.3V右声道) |
CHIPEN | 3.3V | 常开 |
MAX98357A 引脚 | 连接 ESP32 引脚 | 说明 |
---|---|---|
VIN | 5V | 功放电源(喇叭供电) |
GND | GND | 共地 |
BCLK | GPIO14 | I²S BCLK |
LRC | GPIO12 | I²S LRCLK |
DIN | GPIO27 | I²S 数据输入(ESP32 到功放) |
SD/EN | 3.3V | 常开(静音脚,低电平关机) |
GAIN | 悬空 | 默认增益 9dB |
platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
upload_speed = 921600
; 可选:更安静的编译输出与更快的串口监视器
monitor_filters = time
; 如果遇到 I2S 兼容性问题,可尝试固定核心版本,例如:
; platform = espressif32@6.11.0
main.cpp
#include <Arduino.h>
#include "driver/i2s.h"/* -------- Pin Map -------- */
// INMP441 (I2S RX on I2S0)
#define I2S_RX_PORT I2S_NUM_0
#define RX_BCLK 26
#define RX_LRCLK 25
#define RX_DOUT 22
#define CH_FMT_RX I2S_CHANNEL_FMT_ONLY_LEFT // L/R=GND=左;若接3V3则改为 ONLY_RIGHT// MAX98357A (I2S TX on I2S1)
#define I2S_TX_PORT I2S_NUM_1
#define TX_BCLK 14
#define TX_LRCLK 12
#define TX_DIN 27// Amplifier SD/EN (mute control, HIGH=enable, LOW=shutdown)
#define AMP_SD_PIN 33/* -------- Audio Params -------- */
#define SAMPLE_RATE 16000 // 可改 22050/32000/44100/48000(INMP441常用16k/48k)
#define RX_SAMPLES 512 // 每次搬运的“单声道样本数”
#define BITS_PER_SAMPLE I2S_BITS_PER_SAMPLE_32BIT // INMP441 24bit 装在 32bit 框
#define SOFT_GAIN_DB 0.0f // 简单软件增益(dB),负值为衰减,如 -6.0f
#define LIMIT_ENABLE 1 // 开启简易限幅(避免溢出爆音)
#define HPF_ENABLE 1 // 开启简易高通(去直流),一阶 IIR
#define HPF_ALPHA 0.999f // 0.99~0.999 间可调;越大越慢/* -------- Helpers -------- */
// 计算线性增益系数
static float db_to_gain(float db) {return powf(10.0f, db / 20.0f);
}// 将 32bit 框中的 24bit(位于[31:8])取出并符号扩展为 32bit
static inline int32_t unpack24_from32(int32_t x32) {int32_t s24 = x32 >> 8;if (s24 & 0x00800000) s24 |= 0xFF000000;return s24;
}// 把 24bit 有效位放回 32bit 框(左对齐至[31:8])
static inline int32_t pack24_to32(int32_t s) {// 限制到 24bit 范围if (s > 0x7FFFFF) s = 0x7FFFFF;if (s < -0x800000) s = -0x800000;return (s << 8);
}/* -------- I2S Init -------- */
static void i2s_rx_init() {i2s_config_t cfg = {.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),.sample_rate = SAMPLE_RATE,.bits_per_sample = BITS_PER_SAMPLE,.channel_format = CH_FMT_RX, // 单声道(左或右).communication_format = I2S_COMM_FORMAT_I2S,.intr_alloc_flags = 0,.dma_buf_count = 6,.dma_buf_len = 256, // 每个 DMA 缓冲的样本数.use_apll = false,.tx_desc_auto_clear = false,.fixed_mclk = 0};i2s_pin_config_t pin = {.bck_io_num = RX_BCLK,.ws_io_num = RX_LRCLK,.data_out_num = -1,.data_in_num = RX_DOUT};i2s_driver_install(I2S_RX_PORT, &cfg, 0, nullptr);i2s_set_pin(I2S_RX_PORT, &pin);i2s_set_clk(I2S_RX_PORT, SAMPLE_RATE, BITS_PER_SAMPLE, I2S_CHANNEL_MONO);
}static void i2s_tx_init() {i2s_config_t cfg = {.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),.sample_rate = SAMPLE_RATE,.bits_per_sample = BITS_PER_SAMPLE, // 给 MAX98357A 32bit frame(里头放 24bit).channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // 立体声帧(我们 L=R).communication_format = I2S_COMM_FORMAT_I2S,.intr_alloc_flags = 0,.dma_buf_count = 6,.dma_buf_len = 256,.use_apll = false,.tx_desc_auto_clear = true,.fixed_mclk = 0};i2s_pin_config_t pin = {.bck_io_num = TX_BCLK,.ws_io_num = TX_LRCLK,.data_out_num = TX_DIN,.data_in_num = -1};i2s_driver_install(I2S_TX_PORT, &cfg, 0, nullptr);i2s_set_pin(I2S_TX_PORT, &pin);i2s_set_clk(I2S_TX_PORT, SAMPLE_RATE, BITS_PER_SAMPLE, I2S_CHANNEL_STEREO);
}/* -------- Simple Audio Processing -------- */
// 直流阻断(单通道),y[n] = x[n] - x[n-1] + alpha*y[n-1]
typedef struct {float prev_x;float prev_y;
} DCBlocker;static DCBlocker dcblk = {0};static inline float dc_block(float x, float alpha) {float y = x - dcblk.prev_x + alpha * dcblk.prev_y;dcblk.prev_x = x;dcblk.prev_y = y;return y;
}void setup() {Serial.begin(115200);delay(300);// SD/EN 控制脚pinMode(AMP_SD_PIN, OUTPUT);digitalWrite(AMP_SD_PIN, HIGH); // 先开机(如需开机静音可先 LOW,准备好后再 HIGH)i2s_rx_init();i2s_tx_init();Serial.println("ESP32 INMP441 -> MAX98357A passthrough ready.");
}void loop() {static int32_t rxBuf[RX_SAMPLES]; // I2S 读回的 32bit 框static int32_t txBuf[RX_SAMPLES * 2]; // 立体声 L=Rsize_t rxBytes = 0;// 读单声道样本esp_err_t er = i2s_read(I2S_RX_PORT, (void*)rxBuf, sizeof(rxBuf), &rxBytes, portMAX_DELAY);if (er != ESP_OK || rxBytes == 0) return;const size_t n = rxBytes / sizeof(int32_t);const float g = db_to_gain(SOFT_GAIN_DB);// 处理并复制到双声道size_t j = 0;for (size_t i = 0; i < n; ++i) {int32_t s = unpack24_from32(rxBuf[i]); // [-2^23, 2^23-1]float xf = (float)s; // 转浮点做处理// 简易高通去直流if (HPF_ENABLE) xf = dc_block(xf, HPF_ALPHA);// 软件增益xf *= g;// 限幅至 24bitif (LIMIT_ENABLE) {if (xf > 8388607.0f) xf = 8388607.0f;if (xf < -8388608.0f) xf = -8388608.0f;}int32_t s_proc = (int32_t)lrintf(xf);int32_t s32 = pack24_to32(s_proc);txBuf[j++] = s32; // LefttxBuf[j++] = s32; // Right}size_t txBytes = 0;i2s_write(I2S_TX_PORT, (const void*)txBuf, j * sizeof(int32_t), &txBytes, portMAX_DELAY);// (可选)打印一点信息// static uint32_t t0 = millis();// if (millis() - t0 > 1000) {// Serial.printf("rx %u samp, tx %u bytes\n", (unsigned)n, (unsigned)txBytes);// t0 = millis();// }
}