ESP32S3 I2C从机模式下音频播放功能异常问题求助
问题
我正在使用ESP32S3开发板进行IoT项目,通过I2C将两块板设置为主从关系:主ESP32连接传感器,负责将数据传输给从ESP32;从ESP32需通过PCM5102 DAC模块播放SD卡中的音频文件。代码逻辑较为简单,但在从机模式下音频播放功能无法正常工作。请问这是因为I2C同一时间只能单向传输导致的吗?有没有解决办法?
我使用的音频库为ESP32-audioI2S,开发环境为PlatformIO下的Arduino框架,以下是从机的代码:
#include "Arduino.h" #include "Audio.h" #include "SD.h" #include "FS.h" //SPI Pins #define SD_CS D9 #define SPI_MOSI MOSI #define SPI_MISO MISO #define SPI_SCK SCK // I2S Pins #define I2S_DOUT SDA #define I2S_BCLK SCL #define I2S_LRC D6 Audio audio; // Function to receive signal - Toggles volume based on data received void receiveSignal(int numBytes) { while(Wire.available()) { bool c = Wire.read(); if(c) { audio.setVolume(21); } else { audio.setVolume(0); } } Serial.println(); } // Setup function - Initialises Serial, SPI, SD and Audio Pins void setup() { pinMode(SD_CS, OUTPUT); digitalWrite(SD_CS, HIGH); SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI); Serial.begin(115200); delay(5000); SD.begin(SD_CS); audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT); audio.setVolume(21); audio.connecttoFS(SD, "/audio.wav"); Wire.begin(8); Wire.onReceive(receiveSignal); } // Loop function - Loops audio void loop() { audio.loop(); }
解答
你的问题并非I2C单向传输特性导致,核心冲突出在引脚复用上:
- 你把I2S的
I2S_DOUT定义为SDA,I2S_BCLK定义为SCL——这两个引脚同时被I2C(Wire库)和I2S音频库占用。当从机通过Wire监听I2C数据时,I2S的音频输出会被中断;反之,音频播放时也会干扰I2C通信,直接导致音频功能失效。
解决办法分两种,按优先级推荐:
1. 更换引脚,彻底避免复用
ESP32S3支持多组I2C和I2S引脚,重新分配不重叠的硬件资源即可解决问题:
- 给I2S分配独立引脚,比如将
I2S_DOUT改为D10、I2S_BCLK改为D11,避开默认的SDA/SCL引脚; - 或者改用软件I2C,将I2C通信转移到其他GPIO引脚(ESP32S3几乎所有GPIO都支持软件I2C)。
修改后的引脚定义与初始化示例:
// I2S Pins(更换为非I2C引脚) #define I2S_DOUT D10 #define I2S_BCLK D11 #define I2S_LRC D6 // I2C Pins(指定独立引脚) #define I2C_SDA D2 #define I2C_SCL D3 // 在setup中初始化Wire时指定引脚 Wire.begin(8, I2C_SDA, I2C_SCL);
2. 优化中断处理,减少阻塞(不推荐,仅作应急方案)
如果因硬件限制必须复用引脚,需修改I2C中断回调逻辑,避免在中断上下文直接操作音频库:
- I2C的
onReceive回调运行在中断上下文,而音频库的setVolume涉及底层硬件操作,中断内的阻塞会打乱I2S的音频输出时序; - 可以将接收到的控制信号存入全局变量,在
loop函数中延迟处理音量更新。
修改后的核心代码示例:
bool volumeToggle = true; // 全局变量存储控制信号 void receiveSignal(int numBytes) { while(Wire.available()) { volumeToggle = Wire.read(); // 仅在中断中存储数据,不执行其他操作 } } void loop() { audio.loop(); // 在loop中处理音量更新,避免中断阻塞 static bool lastToggle = volumeToggle; if(lastToggle != volumeToggle) { audio.setVolume(volumeToggle ? 21 : 0); lastToggle = volumeToggle; } }
此方法仍可能存在信号干扰风险,优先推荐第一种引脚分离方案。
内容的提问来源于stack exchange,提问作者amatsuraikou
相关产品推荐
相关产品推荐

