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

Arduino Due信号处理项目串口输出极慢无结果求助

Arduino Due信号处理串口输出缓慢/无输出问题排查与解决

问题背景

在Arduino Due上开展呼吸率计算项目,使用125Hz采样率采集7500个样本(对应1分钟的bidmc03m pleth信号),编写代码后串口监视器输出极慢甚至无输出,无法获取呼吸率结果。

原代码

#include <SPI.h>
#include <SD.h>

#define PI 3.1415926535897932384626433832795

/* 样本来自bidmc数据集的bidmc03m pleth信号 */

void dft(const double inrealn[],double outreal[],double outimg[],double magi[],size_t n);//for n number of samples
float maxi(float a, float b);

double inreal[7500]={// 此处填入样本值      
};

void setup() {
  Serial.begin(115200);
}

void loop() {
  // 预处理 
  int n=7500;

  double sum1=0;
  for(int j=0;j<n;j++){ 
    sum1=sum1+inreal[j];
  }
  
  double mean=sum1/n;
         
  for(int i=0;i<n;i++)
  {
    inreal[i]=inreal[i]-mean;
  }
  
  // 寻找最大值
  double maxVal = inreal[0];
  
  for (int i = 1; i < n; i++) {
      if (inreal[i] > maxVal) {
         maxVal = inreal[i];
      }
   }
   
   // 信号归一化
   for(int j=0;j<n;j++){
     inreal[j]=inreal[j]/maxVal;
   }

   // 谐振器系数计算
   double a1=-2*0.997*cos(0.3*2*PI);
   double a2=0.997*0.997;
   double b=(1-0.997)*sqrt(1+(0.997*0.997)-2*0.997*cos(2*0.3));
   
  // 谐振器滤波
  double inrealn[7500]={};
  
  for(int j=2;j<n;j++){
    inrealn[j]=-a1*inrealn[j-1]-a2*inrealn[j-2]+b*inreal[j];
  }
  
  // DFT计算
  double outreal[7500]={};
  double outimg[7500]={};
  double magi[7500]={};
  
  dft(inrealn,outreal,outimg,magi,n);
  
  // 寻找频谱峰值
  double fmaxi = -100000.00;
  
  for (int i = 0; i < n; i++) {
    fmaxi = maxi(magi[i],fmaxi);
  }
  
  // 计算呼吸率
  double resp_rate=fmaxi*60;
  Serial.println(resp_rate);
}

// DFT实现
void dft(const double inrealn[],double outreal[],double outimg[],double magi[],size_t n){
    for (int k=0;k<n;k++){
      double sumreal=0;
      double sumimg=0;
      for (int t=0;t<n;t++){
          double angles=2*PI*t*k/n;
          sumreal+=inrealn[t]*cos(angles);
          sumimg+=-inrealn[t]*sin(angles);
      }
      
      outreal[k]=sumreal;
      outimg[k]=sumimg;
      magi[k]=sqrt(pow(outreal[k],2)+pow(outimg[k],2));
    }
}

float maxi(float a, float b){
    return (a>b) ? a : b;
}

核心问题分析

  1. DFT计算量过载:采用O(n²)复杂度的DFT,7500个样本需要执行5600万次循环,每次循环包含三角函数、平方、开方等高开销浮点运算,Arduino Due的运算能力无法在短时间内完成,程序长时间卡在DFT计算中无响应。
  2. loop()无限重复计算:所有处理逻辑放在loop()中,程序会无限重复执行均值计算、归一化、谐振器、DFT等步骤,永远无法稳定输出结果。
  3. 内存溢出风险:多个7500元素的double数组(每个占8字节)总内存占用超过300KB,而Arduino Due的SRAM仅96KB,内存溢出会导致程序崩溃,直接无输出。
  4. 浮点运算效率低下:使用double类型进行运算,相比float会占用更多内存且运算速度更慢,对于呼吸率计算的精度需求完全没必要。

解决方案

1. 转移处理逻辑到setup()

样本是固定的,不需要重复计算,将所有处理代码移到setup()中,仅执行一次,完成后输出结果即可。

2. 替换DFT为FFT

使用FFT(O(n log n)复杂度)替代DFT,计算量大幅降低。可以使用Arduino官方生态的arduinoFFT库。

3. 优化内存使用

  • 用float替代double,将内存占用减半;
  • 移除不必要的数组,仅保留计算必需的存储单元;
  • 限制峰值搜索的频率范围(呼吸率对应0.1-0.5Hz,对应频率点索引6-30),减少遍历次数。

4. 预计算常量

将谐振器的三角函数常量提前计算为数值,避免每次循环重复计算。

5. 优化串口输出

仅在所有计算完成后一次性输出结果,避免计算过程中频繁IO操作拖慢程序。

修改后的示例代码

#include <SPI.h>
#include <arduinoFFT.h>

#define PI 3.141592653589793
#define SAMPLES 7500
#define SAMPLING_RATE 125.0

arduinoFFT FFT = arduinoFFT();

float inreal[SAMPLES] = { /* 填入你的样本数据 */ };
float vReal[SAMPLES];
float vImag[SAMPLES];

void setup() {
  Serial.begin(115200);
  while(!Serial); // 等待串口连接完成

  // 去均值预处理
  float sum = 0.0;
  for(int i = 0; i < SAMPLES; i++){
    sum += inreal[i];
  }
  float mean = sum / SAMPLES;
  for(int i = 0; i < SAMPLES; i++){
    vReal[i] = inreal[i] - mean;
    vImag[i] = 0.0; // FFT虚部初始化为0
  }

  // 归一化
  float maxVal = vReal[0];
  for(int i = 1; i < SAMPLES; i++){
    if(vReal[i] > maxVal) maxVal = vReal[i];
  }
  for(int i = 0; i < SAMPLES; i++){
    vReal[i] /= maxVal;
  }

  // 谐振器处理(预计算常量)
  float cosVal = cos(0.3 * 2 * PI);
  float a1 = -2 * 0.997 * cosVal;
  float a2 = 0.997 * 0.997;
  float b = (1 - 0.997) * sqrt(1 + a2 - 2 * 0.997 * cos(2 * 0.3));
  
  float filtered[SAMPLES] = {0};
  for(int j = 2; j < SAMPLES; j++){
    filtered[j] = -a1 * filtered[j-1] - a2 * filtered[j-2] + b * vReal[j];
    vReal[j] = filtered[j];
  }

  // FFT计算
  FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
  FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);

  // 仅在呼吸频率范围内搜索峰值(0.1-0.5Hz)
  float maxMag = 0.0;
  int peakIndex = 0;
  int startIdx = (0.1 * SAMPLES) / SAMPLING_RATE;
  int endIdx = (0.5 * SAMPLES) / SAMPLING_RATE;
  for(int i = startIdx; i < endIdx; i++){
    if(vReal[i] > maxMag){
      maxMag = vReal[i];
      peakIndex = i;
    }
  }

  // 计算并输出呼吸率
  float respFreq = (peakIndex * SAMPLING_RATE) / SAMPLES;
  float respRate = respFreq * 60;
  Serial.print("呼吸率:");
  Serial.println(respRate);
}

void loop() {
  // 处理完成后闲置,无需重复计算
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 09:03:13