Arduino Nano 33 BLE Sense麦克风串口打印崩溃问题排查
Arduino Nano 33 BLE Sense PDM乒乓缓冲崩溃问题排查与修复
核心错误点
- 中断回调内执行阻塞串口操作:
onPDMdata是PDM麦克风的中断回调函数,在中断上下文里调用Serial.write会触发串口中断,引发中断嵌套冲突,直接导致系统崩溃。串口输出属于慢操作,绝对不能放在中断回调中执行。 - 乒乓缓冲同步逻辑缺失:当前代码未区分读写缓冲区状态,切换写缓冲区时直接触发读操作,可能出现读写缓冲区重叠(读操作未完成时,写操作覆盖同一块缓冲区)的情况。
- 缓冲区边界处理不严谨:
totalBytes累加时未考虑单次bytesAvailable可能导致totalBytes超过half的情况,会造成缓冲区越界写入。
修复方案
- 将串口输出移到
loop()函数中执行,通过标志位判断是否有可用缓冲区需要输出。 - 完善乒乓缓冲状态管理,确保读写缓冲区互不干扰,用独立标志位标记哪块缓冲区可读。
- 修正缓冲区写入的边界判断,避免越界。
修正后的代码
#include <PDM.h> // PDM采样缓冲区:每个采样16位,512字节对应256个采样 char sampleBuffer[512]; // 乒乓缓冲:两块32KB的缓冲区,总大小64KB #define BUFFER_HALF_SIZE 32768 char audio[BUFFER_HALF_SIZE * 2]; volatile long int writeBytes = 0; volatile int writeBufferIdx = 0; // 0表示第一块,1表示第二块 volatile bool bufferReady[2] = {false, false}; void setup() { Serial.begin(500000); while (!Serial); PDM.onReceive(onPDMdata); // 初始化PDM:单声道、16kHz采样率 if (!PDM.begin(1, 16000)) { Serial.println("PDM初始化失败!"); while (1); } } void loop() { // 检查第一块缓冲区是否可读 if (bufferReady[0]) { Serial.write(audio, BUFFER_HALF_SIZE); bufferReady[0] = false; } // 检查第二块缓冲区是否可读 if (bufferReady[1]) { Serial.write(audio + BUFFER_HALF_SIZE, BUFFER_HALF_SIZE); bufferReady[1] = false; } } void onPDMdata() { int bytesAvailable = PDM.available(); if (bytesAvailable <= 0) return; // 读取采样数据到临时缓冲区 PDM.read(sampleBuffer, bytesAvailable); // 计算当前写缓冲区的起始地址 char* currentWriteBuf = audio + (writeBufferIdx * BUFFER_HALF_SIZE); // 检查剩余空间是否足够写入当前数据,避免越界 if (writeBytes + bytesAvailable > BUFFER_HALF_SIZE) { // 剩余空间不足,先写入剩下的部分 int remaining = BUFFER_HALF_SIZE - writeBytes; memcpy(currentWriteBuf + writeBytes, sampleBuffer, remaining); // 标记当前缓冲区可读 bufferReady[writeBufferIdx] = true; // 切换到另一块缓冲区 writeBufferIdx = 1 - writeBufferIdx; // 写入剩余的数据到新缓冲区 memcpy(audio + (writeBufferIdx * BUFFER_HALF_SIZE), sampleBuffer + remaining, bytesAvailable - remaining); writeBytes = bytesAvailable - remaining; } else { // 空间足够,直接写入 memcpy(currentWriteBuf + writeBytes, sampleBuffer, bytesAvailable); writeBytes += bytesAvailable; // 如果当前缓冲区已满,标记可读并切换 if (writeBytes >= BUFFER_HALF_SIZE) { bufferReady[writeBufferIdx] = true; writeBufferIdx = 1 - writeBufferIdx; writeBytes = 0; } } }
代码说明
- 用
bufferReady数组标记两块缓冲区是否准备好输出,loop()函数轮询标志位执行串口输出,避免在中断中做阻塞操作。 - 严格处理缓冲区边界,当单次采样数据导致缓冲区溢出时,拆分数据写入两块缓冲区,避免越界。
- 用
volatile修饰共享变量,确保中断上下文和主上下文之间的变量可见性,防止编译器优化导致的逻辑错误。
内容的提问来源于stack exchange,提问作者Cristian Cutite
相关产品推荐
相关产品推荐

