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