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

如何将iDuino SE049脉搏传感器的模拟值转换为BPM?

iDuino SE049 模拟值转BPM 代码修复方案

问题背景

使用iDuino SE049传感器将模拟输出转换为BPM(心率值),需完成以下核心步骤:

  • 消除传感器漂移:采集X个值→计算平均值→每个值减平均值(初始X建议设为50)
  • 光照鲁棒性处理:将漂移消除后的信号归一化(最大幅值为1)→设置阈值分割提取峰值
  • 基于峰值时间间隔计算BPM

以下是对两段错误代码的分析及修复后的完整实现:


第一段代码的问题分析与修复

原代码错误点

  1. 时间逻辑错误:currentTime在loop()内初始化为0,导致millis() - currentTime始终远大于20ms,直接进入死循环
  2. 数组越界与累加错误:i自增后直接累加tab[i],此时tab[i]未被赋值;且未限制i的范围,会超出数组tab[20]的索引(0-19)
  3. 20ms内采集逻辑失效:未正确记录采集起始时间,无法控制在20ms内完成数据采集

修复后的漂移消除代码(非阻塞采集)

#include <Arduino.h>

const int SENSOR_PIN = A0;
const int SAMPLE_COUNT = 20; // 20ms内采集20个样本
float samples[SAMPLE_COUNT] = {0};
unsigned long collectStartTime = 0;
int sampleIndex = 0;
float baseline = 0;

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

void loop() {
  // 20ms内完成样本采集
  if (millis() - collectStartTime < 20) {
    if (sampleIndex < SAMPLE_COUNT) {
      samples[sampleIndex] = analogRead(SENSOR_PIN);
      sampleIndex++;
    }
  } else {
    // 计算基线平均值
    float sum = 0;
    for (int i = 0; i < SAMPLE_COUNT; i++) {
      sum += samples[i];
    }
    baseline = sum / SAMPLE_COUNT;

    // 输出消除漂移后的数据
    Serial.print("Baseline: ");
    Serial.print(baseline);
    Serial.print(" | Adjusted values: ");
    for (int i = 0; i < SAMPLE_COUNT; i++) {
      Serial.print(samples[i] - baseline);
      Serial.print(" ");
    }
    Serial.println();

    // 重置采集状态
    collectStartTime = millis();
    sampleIndex = 0;
  }
}

第二段测试代码的问题分析与修复

原代码错误点

  1. 阻塞式采集:delay(40)导致每次采集50个样本需要2秒,无法实时处理心率信号
  2. BPM计算逻辑错误:仅统计数组中大于0.95的元素数量再乘以4,未基于峰值时间间隔计算,结果不准确
  3. 时间计算精度丢失:millis()/1000为整数除法,丢失毫秒级精度;时间窗口判断逻辑混乱

修复后的完整BPM计算代码

#include <Arduino.h>

const int SENSOR_PIN = A0;
const int WINDOW_SIZE = 50; // 漂移消除窗口大小
float rawSamples[WINDOW_SIZE] = {0};
float adjustedSamples[WINDOW_SIZE] = {0};
unsigned long lastPeakTime = 0;
float lastValue = 0;
float normalizedMax = 0;

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

void loop() {
  // 1. 采集窗口内的原始样本(非阻塞延迟)
  for (int i = 0; i < WINDOW_SIZE; i++) {
    unsigned long targetTime = millis() + 40;
    rawSamples[i] = analogRead(SENSOR_PIN);
    while (millis() < targetTime);
  }

  // 2. 消除漂移:计算基线并调整样本
  float baseline = 0;
  for (int i = 0; i < WINDOW_SIZE; i++) {
    baseline += rawSamples[i];
  }
  baseline /= WINDOW_SIZE;
  for (int i = 0; i < WINDOW_SIZE; i++) {
    adjustedSamples[i] = rawSamples[i] - baseline;
  }

  // 3. 归一化处理(避免除以0)
  normalizedMax = 0.0001; // 防止最大值为0
  for (int i = 0; i < WINDOW_SIZE; i++) {
    if (abs(adjustedSamples[i]) > normalizedMax) {
      normalizedMax = abs(adjustedSamples[i]);
    }
  }
  for (int i = 0; i < WINDOW_SIZE; i++) {
    adjustedSamples[i] /= normalizedMax;
  }

  // 4. 峰值检测(上升沿触发,阈值设为0.6)
  for (int i = 0; i < WINDOW_SIZE; i++) {
    if (adjustedSamples[i] > 0.6 && lastValue <= 0.6) {
      unsigned long currentTime = millis();
      if (lastPeakTime != 0) {
        // 计算相邻峰值的时间差,转换为BPM
        unsigned long interval = currentTime - lastPeakTime;
        float bpm = 60000.0 / interval;
        // 过滤异常值(正常心率范围60-180BPM)
        if (bpm >= 60 && bpm <= 180) {
          Serial.print("BPM: ");
          Serial.print(bpm);
          Serial.println(" ♥️");
        }
      }
      lastPeakTime = currentTime;
    }
    lastValue = adjustedSamples[i];
  }
}

关键实现要点

  • 非阻塞采集:用millis()替代delay(),确保程序能响应其他逻辑
  • 漂移消除:通过滑动窗口计算基线,实时修正传感器的直流偏移
  • 归一化处理:将信号幅值统一到0-1范围,避免光照变化影响阈值判断
  • 峰值检测:采用上升沿触发+合理阈值(0.6左右可根据实际调整),避免重复计数
  • BPM计算:基于相邻峰值的时间间隔计算,结果更准确,同时过滤异常值

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 08:20:32