ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于ESP32 的局域网全双工对讲系统

基于ESP32 的局域网全双工对讲系统 若该文为原创文章转载请注明原文出处。1. 项目概述本项目基于两块 ESP32 开发板ESP32 WROOM32 与 ESP32-S3 N16R8搭建了一套局域网内无按键、自动组网的全双工实时对讲系统。系统采用 UDP 协议传输 PCM 音频数据通过 I2S 接口外接 INMP441 麦克风与 MAX98357 功放实现双向实时语音通信。核心功能无按键设计上电后自动连接 Wi-Fi、自动开始收发音频无需任何人工干预。全双工通信双向同时采集、发送、接收和播放端到端延迟约 50ms。自动重连Wi-Fi 掉线后自动重连无需重启设备。跨平台兼容利用 ESP32 与 ESP32-S3 的 I2S 硬件差异分别采用单端口全双工和双端口独立收发两种架构。音频参数参数数值采样率16000 Hz位深16 bit声道单声道左声道单包大小256 样本512 字节DMA 缓冲8 × 256 样本传输协议UDP低延迟优先端口123452. 硬件接线2.1 ESP32 WROOM32 接线修正后关键设计ESP32 只有I2S0 一个外设支持数据发送因此麦克风和功放必须共享 BCLK 和 WS 时钟线只分离数据线。外设引脚ESP32 GPIO板上丝印说明INMP441VCC3V33V3供电INMP441GNDGNDGND地INMP441WS (LRCLK)GPIO15D15帧同步共享INMP441SCK (BCLK)GPIO4D4位时钟共享INMP441SD (数据)GPIO21D21麦克风数据输入MAX98357VIN3V33V3供电MAX98357GNDGNDGND地MAX98357LRC (WS)GPIO15D15与 INMP441 共享MAX98357BCLKGPIO4D4与 INMP441 共享MAX98357DINGPIO18D18功放数据输出MAX98357SD悬空—一直使能接线示意textESP32 WROOM32 ┌──────────────┐ INMP441 ────┤ │ WS ──────┤ GPIO15 ──┬───┼── MAX98357 LRC SCK ──────┤ GPIO4 ──┼───┼── MAX98357 BCLK SD ──────┤ GPIO21 │ │ │ │ │ MAX98357 ───┤ │ │ DIN ──────┤ GPIO18 │ │ └──────────┴───┘⚠️ 注意MAX98357 的 LRC 和 BCLK不要接 GPIO22 和 GPIO19否则无法收到时钟信号导致完全无声。2.2 ESP32-S3 N16R8 接线关键设计ESP32-S3 的两个 I2S 外设I2S0、I2S1均支持独立收发因此可以使用两个完全独立的 I2S 端口引脚不共享。外设引脚ESP32-S3 GPIO说明INMP441SCK (BCLK)GPIO6I2S0 位时钟INMP441WS (LRCLK)GPIO5I2S0 帧同步INMP441SD (数据)GPIO4I2S0 数据输入MAX98357SCK (BCLK)GPIO9I2S1 位时钟MAX98357WS (LRC)GPIO10I2S1 帧同步MAX98357SD (DIN)GPIO11I2S1 数据输出接线示意textESP32-S3 N16R8 ┌──────────────┐ INMP441 ────┤ GPIO6 (SCK) │ I2S0 │ GPIO5 (WS) │ │ GPIO4 (SD) │ │ │ MAX98357 ───┤ GPIO9 (SCK) │ I2S1 │ GPIO10 (WS) │ │ GPIO11 (SD) │ └──────────────┘3. 系统架构与工作原理3.1 全双工音频流text麦克风(INMP441) ──I2S RX── ESP32 ──UDP── 路由器 ──UDP── 对方ESP32 ──I2S TX── 功放(MAX98357) ↑ │ └────────────────────── 声音反馈可能引起啸叫──────────────────────────────────────┘3.2 ESP32 WROOM32 的 I2S 全双工实现ESP32 仅 I2S0 支持 TX因此采用单端口全双工模式i2s_config_t i2s_config { .mode (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX), // 同时收发 .sample_rate 16000, .bits_per_sample I2S_BITS_PER_SAMPLE_16BIT, .channel_format I2S_CHANNEL_FMT_ONLY_LEFT, .communication_format I2S_COMM_FORMAT_STAND_I2S, .dma_buf_count 8, .dma_buf_len 256, .tx_desc_auto_clear true, // 自动清空DMA防杂音 }; i2s_pin_config_t pin_config { .bck_io_num 4, // 共享 BCLK .ws_io_num 15, // 共享 WS .data_out_num 18, // 输出到 MAX98357 .data_in_num 21 // 从 INMP441 输入 };3.3 ESP32-S3 的双 I2S 独立实现S3 使用两个独立端口互不干扰// I2S0麦克风仅 RX i2s_config_t i2sMicConfig { .mode (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), .sample_rate 16000, // ... }; // I2S1功放仅 TX i2s_config_t i2sSpkConfig { .mode (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), .sample_rate 16000, .tx_desc_auto_clear true, // ... };3.4 Wi-Fi 自动连接机制WiFi.mode(WIFI_STA); WiFi.setAutoReconnect(true); // 底层自动重连 WiFi.begin(SSID, PASSWORD); // 主循环中检测断线 if (WiFi.status() ! WL_CONNECTED) { connectWiFi(); // 主动重连 }3.5 UDP 收发主循环void loop() { // 1. 从麦克风读取一帧数据 i2s_read(I2S_PORT, micBuf, sizeof(micBuf), bytesRead, portMAX_DELAY); // 2. 通过 UDP 发给对方 udp.beginPacket(PEER_IP, UDP_PORT); udp.write((uint8_t*)micBuf, bytesRead); udp.endPacket(); // 3. 接收对方的音频包并播放 int packetSize udp.parsePacket(); if (packetSize 0) { int len udp.read((uint8_t*)spkBuf, sizeof(spkBuf)); i2s_write(I2S_PORT, spkBuf, len, bytesWritten, portMAX_DELAY); } }4. 常见问题与解决方案问题 1S3 编译报错exit status 1缺少自定义分区表原因Tools 菜单选择了PartitionSchemecustom但工程目录下没有partitions.csv文件。解决方案方案 A在工程目录下创建partitions.csv内容如下# Name, Type, SubType, Offset, Size nvs, data, nvs, 0x9000, 0x5000, otadata, data, ota, 0xe000, 0x2000, app0, app, ota_0, 0x10000, 0x400000, app1, app, ota_1, 0x410000, 0x400000, spiffs, data, spiffs, 0x810000, 0x7E0000, coredump, data, coredump,0xFF0000, 0x10000,方案 B将 Tools → Partition Scheme 改为16M Flash (3MB APP/9.9MB FATFS)。问题 2WROOM32 编译报错undefined reference to millis、app_main、__wrap_esp_panic_handler原因WROOM32 的编译缓存core.a损坏可能因编译中断或杀毒软件锁文件导致。解决方案关闭 Arduino IDE。清空C:\Users\Administrator\AppData\Local\arduino\cores\下的所有内容。清空C:\Users\Administrator\AppData\Local\arduino\sketches\下的所有内容。重新打开 IDE 编译。若无效可重命名工程文件夹如ESP32_WROOM32_Intercom强制生成新的缓存哈希。问题 3单向通信S3 → WROOM32 有声音WROOM32 → S3 无声原因WROOM32 的 MAX98357 的LRC 和 BCLK 接在了 GPIO22/GPIO19但代码中 I2S0 实际输出的时钟在GPIO15/GPIO4导致 MAX98357 收不到时钟信号。解决方案将 MAX98357 的LRC 改接 GPIO15BCLK 改接 GPIO4与 INMP441 共享时钟。DIN 保持 GPIO18 不变。代码无需修改。问题 4啸叫/回声原因全双工系统无回声消除AEC麦克风会拾取功放播放的声音形成正反馈。解决方案将麦克风与喇叭物理隔离拉开距离、加隔音棉。降低功放音量。后期可引入 ESP-ADF 框架的 AEC 算法。5. 工程结构ESP32_LAN_Intercom/ ├── ESP32_LAN_Intercom_WROOM32/ │ └── ESP32_LAN_Intercom_WROOM32.ino # WROOM32 主程序 └── ESP32_LAN_Intercom_S3/ ├── ESP32_LAN_Intercom_S3.ino # S3 主程序 └── partitions.csv # S3 自定义分区表若使用 custom6. 关键设计对比特性ESP32 WROOM32ESP32-S3 N16R8I2S 端口数1 个I2S0 全双工2 个I2S0 RX I2S1 TXBCLK/WS麦克风与功放共享各自独立数据引脚DINGPIO18, DOUTGPIO21独立配置Flash4MB16MBPSRAM无8MB建议开启分区方案Default 4MB16M Flash 或 Custom7. 使用说明配置步骤修改两块代码中的WIFI_SSID和WIFI_PASSWORD。先烧录一块板子从串口监视器读取其 IP 地址。将该 IP 填入另一块板子的PEER_IP。分别烧录两块板子。上电系统自动组网并开始对讲。开发板配置WROOM32ESP32 Dev ModuleFlash 4MBPSRAM Disabled。S3ESP32S3 Dev ModuleFlash 16MBPSRAM OPI PSRAM。串口监视器波特率115200上电后会打印本机 IP、UDP 端口、I2S 初始化状态。8. 总结本系统利用 UDP 低延迟传输和 I2S 全双工音频采集/播放实现了一套无需按键、自动组网的局域网对讲方案。核心难点在于ESP32 与 ESP32-S3 的 I2S 硬件差异ESP32只有一个 I2S TX 外设必须共享 BCLK/WS 时钟线。ESP32-S3两个 I2S 外设均可独立收发引脚完全分离。理解这一差异是硬件接线和代码配置正确的前提。/* * ESP32 WROOM32 局域网对讲 - 全双工 * INMP441: WSGPIO15, SCKGPIO4, SDGPIO21 * MAX98357: LRCGPIO22, BCLKGPIO19, DINGPIO18 */ #include WiFi.h #include WiFiUdp.h #include driver/i2s.h // 网络配置 const char* WIFI_SSID yifeng118; // 修改为你的WiFi名称 const char* WIFI_PASSWORD 112233445566; // 修改为你的WiFi密码 const char* PEER_IP 192.168.0.104; // 修改为对方板子的IP const uint16_t UDP_PORT 12345; // I2S 引脚定义 #define I2S_PORT I2S_NUM_0 // INMP441 麦克风 #define MIC_WS_PIN 15 #define MIC_SCK_PIN 4 #define MIC_SD_PIN 21 // MAX98357 功放 #define SPK_LRC_PIN 22 #define SPK_BCLK_PIN 19 #define SPK_DIN_PIN 18 // 音频参数 #define SAMPLE_RATE 16000 #define DMA_BUF_COUNT 8 #define DMA_BUF_LEN 256 // 全局变量 WiFiUDP udp; int16_t micBuf[DMA_BUF_LEN]; // 麦克风读取缓冲 int16_t spkBuf[DMA_BUF_LEN]; // 扬声器播放缓冲 // WiFi 自动连接 void connectWiFi() { WiFi.mode(WIFI_STA); WiFi.setAutoReconnect(true); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print(正在连接 WiFi); unsigned long start millis(); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(.); if (millis() - start 30000) { Serial.println(\n连接超时重启...); ESP.restart(); } } Serial.println(\nWiFi 已连接); Serial.print(本机 IP: ); Serial.println(WiFi.localIP()); } // I2S 初始化全双工 void initI2S() { // I2S 配置标准模式全双工 i2s_config_t i2s_config { .mode (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX), .sample_rate SAMPLE_RATE, .bits_per_sample I2S_BITS_PER_SAMPLE_16BIT, .channel_format I2S_CHANNEL_FMT_ONLY_LEFT, // INMP441 和 MAX98357 都用左声道 .communication_format I2S_COMM_FORMAT_STAND_I2S, .intr_alloc_flags ESP_INTR_FLAG_LEVEL1, .dma_buf_count DMA_BUF_COUNT, .dma_buf_len DMA_BUF_LEN, .use_apll false, .tx_desc_auto_clear true, // 自动清空DMA防止杂音 .fixed_mclk 0 }; // 引脚配置 i2s_pin_config_t pin_config { .bck_io_num MIC_SCK_PIN, // 共享 BCLKGPIO4 .ws_io_num MIC_WS_PIN, // 共享 WSGPIO15 .data_out_num SPK_DIN_PIN, // 输出到 MAX98357 DINGPIO18 .data_in_num MIC_SD_PIN // 从 INMP441 输入GPIO21 }; i2s_driver_install(I2S_PORT, i2s_config, 0, NULL); i2s_set_pin(I2S_PORT, pin_config); i2s_zero_dma_buffer(I2S_PORT); // 清空DMA消除上电爆音 Serial.println(I2S 全双工初始化完成); } // 主程序 void setup() { Serial.begin(115200); delay(500); Serial.println(\n ESP32 WROOM32 对讲系统启动 ); connectWiFi(); udp.begin(UDP_PORT); Serial.printf(UDP 监听端口: %d\n, UDP_PORT); initI2S(); Serial.println(系统就绪开始对讲...); } void loop() { // WiFi 掉线自动重连 if (WiFi.status() ! WL_CONNECTED) { Serial.println(WiFi 断开正在重连...); connectWiFi(); } size_t bytesRead 0; // 1. 从麦克风读取音频数据 esp_err_t result i2s_read(I2S_PORT, micBuf, sizeof(micBuf), bytesRead, portMAX_DELAY); if (result ! ESP_OK || bytesRead 0) return; // 2. 发送 UDP 音频包给对方 udp.beginPacket(PEER_IP, UDP_PORT); udp.write((uint8_t*)micBuf, bytesRead); udp.endPacket(); // 3. 接收对方的 UDP 音频包并播放 int packetSize udp.parsePacket(); if (packetSize 0) { int len udp.read((uint8_t*)spkBuf, sizeof(spkBuf)); if (len 0) { size_t bytesWritten 0; i2s_write(I2S_PORT, spkBuf, len, bytesWritten, portMAX_DELAY); } } }/* * ESP32-S3 N16R8 局域网对讲 - 全双工 * INMP441: SCKGPIO6, WSGPIO5, SDGPIO4 * MAX98357: SCKGPIO9, WSGPIO10, SDGPIO11 * 使用两个独立 I2S 端口I2S0麦克风, I2S1功放 */ #include WiFi.h #include WiFiUdp.h #include driver/i2s.h // 网络配置 const char* WIFI_SSID yifeng118; // 修改为你的WiFi名称 const char* WIFI_PASSWORD 112233445566; // 修改为你的WiFi密码 const char* PEER_IP 192.168.0.102; // 修改为对方板子的IP const uint16_t UDP_PORT 12345; // I2S 引脚定义 // I2S0 用于麦克风RX #define I2S_MIC_PORT I2S_NUM_0 #define MIC_SCK_PIN 6 #define MIC_WS_PIN 5 #define MIC_SD_PIN 4 // I2S1 用于功放TX #define I2S_SPK_PORT I2S_NUM_1 #define SPK_SCK_PIN 9 #define SPK_WS_PIN 10 #define SPK_SD_PIN 11 // 音频参数 #define SAMPLE_RATE 16000 #define DMA_BUF_COUNT 8 #define DMA_BUF_LEN 256 // 全局变量 WiFiUDP udp; int16_t micBuf[DMA_BUF_LEN]; int16_t spkBuf[DMA_BUF_LEN]; // WiFi 自动连接 void connectWiFi() { WiFi.mode(WIFI_STA); WiFi.setAutoReconnect(true); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print(正在连接 WiFi); unsigned long start millis(); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(.); if (millis() - start 30000) { Serial.println(\n连接超时重启...); ESP.restart(); } } Serial.println(\nWiFi 已连接); Serial.print(本机 IP: ); Serial.println(WiFi.localIP()); } // I2S 初始化 void initI2S() { // ---- I2S0麦克风RX ---- i2s_config_t i2sMicConfig { .mode (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), .sample_rate SAMPLE_RATE, .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 DMA_BUF_COUNT, .dma_buf_len DMA_BUF_LEN, .use_apll false, .tx_desc_auto_clear false, .fixed_mclk 0 }; i2s_pin_config_t micPinConfig { .bck_io_num MIC_SCK_PIN, .ws_io_num MIC_WS_PIN, .data_out_num I2S_PIN_NO_CHANGE, .data_in_num MIC_SD_PIN }; i2s_driver_install(I2S_MIC_PORT, i2sMicConfig, 0, NULL); i2s_set_pin(I2S_MIC_PORT, micPinConfig); i2s_zero_dma_buffer(I2S_MIC_PORT); Serial.println(I2S0 麦克风初始化完成); // ---- I2S1功放TX ---- i2s_config_t i2sSpkConfig { .mode (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), .sample_rate SAMPLE_RATE, .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 DMA_BUF_COUNT, .dma_buf_len DMA_BUF_LEN, .use_apll false, .tx_desc_auto_clear true, .fixed_mclk 0 }; i2s_pin_config_t spkPinConfig { .bck_io_num SPK_SCK_PIN, .ws_io_num SPK_WS_PIN, .data_out_num SPK_SD_PIN, .data_in_num I2S_PIN_NO_CHANGE }; i2s_driver_install(I2S_SPK_PORT, i2sSpkConfig, 0, NULL); i2s_set_pin(I2S_SPK_PORT, spkPinConfig); i2s_zero_dma_buffer(I2S_SPK_PORT); Serial.println(I2S1 功放初始化完成); } // 主程序 void setup() { Serial.begin(115200); delay(500); Serial.println(\n ESP32-S3 N16R8 对讲系统启动 ); connectWiFi(); udp.begin(UDP_PORT); Serial.printf(UDP 监听端口: %d\n, UDP_PORT); initI2S(); Serial.println(系统就绪开始对讲...); } void loop() { // WiFi 掉线自动重连 if (WiFi.status() ! WL_CONNECTED) { Serial.println(WiFi 断开正在重连...); connectWiFi(); } size_t bytesRead 0; // 1. 从麦克风读取 esp_err_t result i2s_read(I2S_MIC_PORT, micBuf, sizeof(micBuf), bytesRead, portMAX_DELAY); if (result ! ESP_OK || bytesRead 0) return; // 2. 发送 UDP 音频包给对方 udp.beginPacket(PEER_IP, UDP_PORT); udp.write((uint8_t*)micBuf, bytesRead); udp.endPacket(); // 3. 接收对方的 UDP 音频包并通过功放播放 int packetSize udp.parsePacket(); if (packetSize 0) { int len udp.read((uint8_t*)spkBuf, sizeof(spkBuf)); if (len 0) { size_t bytesWritten 0; i2s_write(I2S_SPK_PORT, spkBuf, len, bytesWritten, portMAX_DELAY); } } }如有侵权或需要完整代码请及时联系博主。
返回列表