【ESP32-S3】对接ms3040后,板子能正常启动,但是一进入录音就报错Stack canary watchpoint triggered

【ESP32-S3】对接ms3040后,板子能正常启动,但是一进入录音就报错Stack canary watchpoint triggered

具体报错

复制代码
21:55:20.913 -> 开始录音...
21:55:20.913 -> Guru Meditation Error: Core  1 panic'ed (Unhandled debug exception). 
21:55:20.944 -> Debug exception reason: Stack canary watchpoint triggered (EnergyWake) 
21:55:20.944 -> Core  1 register dump:
21:55:20.944 -> PC      : 0x420ac737  PS      : 0x00060236  A0      : 0x820a58ac  A1      : 0x3fcb6410  
21:55:20.981 -> A2      : 0x3fcb6604  A3      : 0x3fcb6730  A4      : 0x3fcb6420  A5      : 0x3fcb68c0  
21:55:20.981 -> A6      : 0x3fcb6890  A7      : 0x00000008  A8      : 0x00000000  A9      : 0x00000000  
21:55:20.981 -> A10     : 0xffffffff  A11     : 0x3c0ec7b7  A12     : 0x000000ff  A13     : 0x0000ff00

Stack canary watchpoint triggered

报错信息 Stack canary watchpoint triggered (EnergyWake) 表明 EnergyWake 任务的栈空间不足,导致栈溢出触发了 ESP32 的栈保护机制(Stack Canary)。核心原因:

energy_wake_detection_task 任务创建时分配的栈大小(4096 字节)过小,录音 / 音频处理操作需要更大的栈空间;

录音函数 recordAudio() 内部有大量数组操作、循环计算,直接在低栈空间的任务中调用会导致栈溢出;

多个任务(音频任务、能量检测任务、HTTP 任务)同时运行,栈资源竞争加剧。

原代码

setup里面创建了一个task

c++ 复制代码
void setup(){
	xTaskCreate(energy_wake_detection_task, "EnergyWake", 4096, NULL, 2, NULL);
}


void on_wake_word_detected(){
  // 唤醒后的处理逻辑
  Serial.println("设备已被唤醒!");
  recordAudio();
  uploadAudioToServer();

}

void energy_wake_detection_task(void *pvParameters) {
    int16_t audio_buffer[BUFFER_SIZE];
    size_t bytes_read;
    
    while (true) {
        // 读取音频数据
        esp_err_t result = i2s_read(I2S_PORT, audio_buffer, 
                                  sizeof(audio_buffer), &bytes_read, portMAX_DELAY);
        
        if (result == ESP_OK && bytes_read > 0) {
            size_t samples_read = bytes_read / sizeof(int16_t);
            uint32_t current_energy = calculate_energy(audio_buffer, samples_read);
            
            // 检测能量是否超过阈值
            if (current_energy > ENERGY_THRESHOLD) {
                last_activity_time = millis();
                
                if (!is_awake) {
                    is_awake = true;
                    Serial.println("能量检测唤醒!");
                    on_wake_word_detected();
                }
            }
            
            // 检查是否需要进入睡眠(长时间静音)
            if (is_awake && (millis() - last_activity_time) > SILENCE_DURATION) {
                is_awake = false;
                Serial.println("进入睡眠模式");
            }
        }
        
        vTaskDelay(10 / portTICK_PERIOD_MS);
    }
}

优化后的代码

新增一个队列和录音的task

c++ 复制代码
  // 新增:创建录音任务队列和任务(分配更大栈空间,绑定到Core 1)
  recordQueue = xQueueCreate(2, sizeof(bool));
  xTaskCreatePinnedToCore(recordTask, "Record Task", 16384, NULL, 2, &recordTaskHandle, 1);

然后在recordTask中利用队列循环检测是否要录音

c++ 复制代码
// 新增:录音任务(独立高栈空间)
void recordTask(void *parameter) {
  while (1) {
    bool startRecord = false;
    if (xQueueReceive(recordQueue, &startRecord, portMAX_DELAY) == pdTRUE && startRecord) {
      if (!audioBuffer) {
        Serial.println("PSRAM缓冲区未分配!");
        continue;
      }

      // 创建WAV文件头
      createWavHeader(audioBuffer, PSRAM_BUFFER_SIZE);

      size_t totalBytesRead = 0;
      const size_t readSize = 512;  // 减小读取缓冲区,降低栈压力
      uint32_t startTime = millis();
      const int softwareGain = 8;

      Serial.println("开始录音...");
      Serial.printf("软件增益设置: %d倍\n", softwareGain);

      // 录音指定时长
      while ((millis() - startTime) < RECORD_TIME_MS) {
        size_t bytesRead = 0;
        // 临时缓冲区改为静态分配(避免栈动态分配过大)
        static int16_t tempBuffer[256];  // 从512减小到256

        esp_err_t result = i2s_read(I2S_PORT,
                                    tempBuffer,
                                    readSize,
                                    &bytesRead,
                                    portMAX_DELAY);

        if (result == ESP_OK && bytesRead > 0) {
          size_t samplesRead = bytesRead / 2;
          for (size_t i = 0; i < samplesRead; i++) {
            int32_t amplifiedSample = (int32_t)tempBuffer[i] * softwareGain;
            if (amplifiedSample > 32767) amplifiedSample = 32767;
            if (amplifiedSample < -32768) amplifiedSample = -32768;

            ((int16_t *)(audioBuffer + 44))[totalBytesRead / 2 + i] = (int16_t)amplifiedSample;
          }

          totalBytesRead += bytesRead;

          if (totalBytesRead % 4096 == 0) {
            int progress = ((millis() - startTime) * 100) / RECORD_TIME_MS;
            Serial.printf("录音进度: %d%%, 已录制: %d KB\n",
                          progress, totalBytesRead / 1024);
          }

          if (totalBytesRead + 44 >= PSRAM_BUFFER_SIZE + 44) {
            Serial.println("缓冲区已满,停止录音");
            break;
          }
        } else {
          Serial.printf("I2S读取错误: %d\n", result);
          break;
        }
      }

      audioDataSize = totalBytesRead + 44;
      Serial.printf("录音完成! 总数据量: %d 字节 (%.2f KB)\n",
                    audioDataSize, audioDataSize / 1024.0);
      Serial.println("音频数据已保存到PSRAM");

      // 上传音频
      uploadAudioToServer();
    }
  }
}

最后在xQueueSend加入到on_wake_word_detected

复制代码
void on_wake_word_detected() {
  Serial.println("设备已被唤醒!");
  // 向录音任务队列发送开始录音的信号
  bool startRecord = true;
  xQueueSend(recordQueue, &startRecord, 0);
}

总结

  1. 先不改代码结果,先可以尝试将xTaskCreate(energy_wake_detection_task, "EnergyWake", 4096, NULL, 2, NULL);改成16392
  2. on_wake_word_detected里的录音和http请求都放在task里,类似于java的线程中套线程
  3. 全部代码见:https://gitee.com/likexiang/like-code/blob/2ad94e93d16f211c5b32f73786c32192040ffa90/ESP32-S3-CAM/ ControlHttpServer-1802.ino