You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ESP32第二核心运行音频copy函数异常问题求助

问题解决:ESP32音频任务断言错误与看门狗重启

问题核心

在ESP32上尝试将音频播放任务audioSD.copy()绑定到核心0以实现并行执行时,先后遇到两个问题:

  • 初始代码触发互斥锁断言错误:互斥锁跨任务访问状态混乱
  • 简化代码触发任务看门狗重启:音频任务长期占用CPU,导致IDLE任务无法执行看门狗喂狗操作

错误原因分析

  1. 断言错误:主任务(loop函数)中的radio_functions直接调用audioSD.copy(),未通过互斥锁保护,同时音频任务也在持有互斥锁操作audioSD,导致互斥锁状态冲突。此外,音频任务的循环逻辑会在播放完成后立即再次获取互斥锁,引发重复锁问题。
  2. 看门狗错误: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。

初始代码问题点回顾

  1. 主任务直接调用audioSD.copy(),未通过互斥锁保护,与音频任务操作冲突。
  2. 音频任务循环不断获取互斥锁并执行阻塞式播放,长期占用CPU触发看门狗。
  3. 播放完成后未关闭audioFile,存在资源泄漏风险。

内容的提问来源于stack exchange,提问作者adi_s

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 08:45:12