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

Arduino Nano 33 BLE Sense麦克风串口打印崩溃问题排查

Arduino Nano 33 BLE Sense PDM乒乓缓冲崩溃问题排查与修复

核心错误点

  • 中断回调内执行阻塞串口操作:onPDMdata是PDM麦克风的中断回调函数,在中断上下文里调用Serial.write会触发串口中断,引发中断嵌套冲突,直接导致系统崩溃。串口输出属于慢操作,绝对不能放在中断回调中执行。
  • 乒乓缓冲同步逻辑缺失:当前代码未区分读写缓冲区状态,切换写缓冲区时直接触发读操作,可能出现读写缓冲区重叠(读操作未完成时,写操作覆盖同一块缓冲区)的情况。
  • 缓冲区边界处理不严谨:totalBytes累加时未考虑单次bytesAvailable可能导致totalBytes超过half的情况,会造成缓冲区越界写入。

修复方案

  1. 将串口输出移到loop()函数中执行,通过标志位判断是否有可用缓冲区需要输出。
  2. 完善乒乓缓冲状态管理,确保读写缓冲区互不干扰,用独立标志位标记哪块缓冲区可读。
  3. 修正缓冲区写入的边界判断,避免越界。

修正后的代码

#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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 06:15:08