ESP32第二核心运行音频copy函数异常问题求助
问题解决:ESP32音频任务断言错误与看门狗重启
问题核心
在ESP32上尝试将音频播放任务audioSD.copy()绑定到核心0以实现并行执行时,先后遇到两个问题:
- 初始代码触发互斥锁断言错误:互斥锁跨任务访问状态混乱
- 简化代码触发任务看门狗重启:音频任务长期占用CPU,导致IDLE任务无法执行看门狗喂狗操作
错误原因分析
- 断言错误:主任务(
loop函数)中的radio_functions直接调用audioSD.copy(),未通过互斥锁保护,同时音频任务也在持有互斥锁操作audioSD,导致互斥锁状态冲突。此外,音频任务的循环逻辑会在播放完成后立即再次获取互斥锁,引发重复锁问题。 - 看门狗错误:
audioSD.copy()是阻塞式调用,会持续运行直到整个音频文件播放完毕,期间音频任务完全占用CPU0,导致该核心的IDLE任务无法执行,触发ESP32的任务看门狗机制。
修复后的代码
#include "Arduino.h" #include <Adafruit_GFX.h> #include <Adafruit_ST7735.h> #include "SPI.h" #include <SdFat.h> #include <Adafruit_ImageReader.h> #include "AudioTools.h" #include "AudioCodecs/CodecMP3Helix.h" // SPI定义 #define SPI_MOSI 23 #define SPI_MISO 19 #define SPI_SCLK 18 #define SD_CS 2 #define TFT_CS 13 #define TFT_RST 12 #define TFT_DC 14 // I2S定义 #define I2S_DOUT 25 #define I2S_BCLK 27 #define I2S_LRC 26 // 全局变量 bool radio_intro_played = false; bool audio_playing = false; SemaphoreHandle_t xAudioSemaphore; // 用信号量触发播放,避免循环占用CPU SemaphoreHandle_t xAudioMutex; // 保护音频资源的互斥锁 TaskHandle_t audioTask; SdFat SD_fat; Adafruit_ImageReader reader(SD_fat); Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); I2SStream i2s; EncodedAudioStream decoder(&i2s, new MP3DecoderHelix()); StreamCopy audioSD; File audioFile; void audio_loop(void * parameter) { for (;;) { // 等待播放触发信号,阻塞期间释放CPU给IDLE任务 if (xSemaphoreTake(xAudioSemaphore, portMAX_DELAY) == pdTRUE) { xSemaphoreTake(xAudioMutex, portMAX_DELAY); audio_playing = true; // 分块处理音频,每次处理后短暂释放CPU while(audioSD.copy() > 0){ vTaskDelay(pdMS_TO_TICKS(1)); // 给IDLE任务留时间喂狗 } audioFile.close(); // 播放完成后关闭文件释放资源 audio_playing = false; xSemaphoreGive(xAudioMutex); } } } void setup() { Serial.begin(115200); if (!SD_fat.begin(SD_CS, SD_SCK_MHZ(10))) { Serial.println("SD卡初始化失败!"); for(;;); } Serial.println("SD卡初始化成功!"); AudioLogger::instance().begin(Serial, AudioLogger::Info); // 初始化I2S auto config = i2s.defaultConfig(TX_MODE); config.pin_bck = I2S_BCLK; config.pin_ws = I2S_LRC; config.pin_data = I2S_DOUT; i2s.begin(config); decoder.setNotifyAudioChange(i2s); decoder.begin(); // 创建信号量和互斥锁 xAudioSemaphore = xSemaphoreCreateBinary(); xAudioMutex = xSemaphoreCreateMutex(); // 将音频任务绑定到核心0,调整栈大小适配音频处理 xTaskCreatePinnedToCore(audio_loop, "audioloop", 8192, NULL, 1, &audioTask, 0); tft.initR(INITR_BLACKTAB); } void play_audio(const char *filename) { xSemaphoreTake(xAudioMutex, portMAX_DELAY); // 关闭之前未释放的文件 if(audioFile.isOpen()){ audioFile.close(); } audioFile = SD_fat.open(filename, FILE_READ); audioSD.begin(decoder, audioFile); xSemaphoreGive(xAudioMutex); // 发送信号触发音频任务开始播放 xSemaphoreGive(xAudioSemaphore); } void loop() { radio_functions(); vTaskDelay(pdMS_TO_TICKS(10)); // 主任务定期释放CPU } void radio_functions() { if(!radio_intro_played) { reader.drawBMP("/app_radio/radio_intro.bmp", tft, 0, 0); play_audio("/app_radio/radio_intro.mp3"); radio_intro_played = true; } }
关键修复点
- 用二进制信号量替代循环互斥锁:音频任务平时处于阻塞等待状态,仅收到播放信号时才启动播放,避免持续占用CPU。
- 分块处理音频+延时释放CPU:将阻塞式的
audioSD.copy()改为循环分块处理,每次处理后调用vTaskDelay,确保IDLE任务能执行看门狗喂狗操作。 - 严格保护音频资源:所有访问
audioFile、audioSD的操作都通过互斥锁xAudioMutex保护,避免跨任务访问冲突。 - 主任务添加延时:主循环中加入短暂延时,防止主任务过度占用CPU。
初始代码问题点回顾
- 主任务直接调用
audioSD.copy(),未通过互斥锁保护,与音频任务操作冲突。 - 音频任务循环不断获取互斥锁并执行阻塞式播放,长期占用CPU触发看门狗。
- 播放完成后未关闭
audioFile,存在资源泄漏风险。
内容的提问来源于stack exchange,提问作者adi_s
相关产品推荐
相关产品推荐

