如何将iDuino SE049脉搏传感器的模拟值转换为BPM?
iDuino SE049 模拟值转BPM 代码修复方案
问题背景
使用iDuino SE049传感器将模拟输出转换为BPM(心率值),需完成以下核心步骤:
- 消除传感器漂移:采集X个值→计算平均值→每个值减平均值(初始X建议设为50)
- 光照鲁棒性处理:将漂移消除后的信号归一化(最大幅值为1)→设置阈值分割提取峰值
- 基于峰值时间间隔计算BPM
以下是对两段错误代码的分析及修复后的完整实现:
第一段代码的问题分析与修复
原代码错误点
- 时间逻辑错误:
currentTime在loop()内初始化为0,导致millis() - currentTime始终远大于20ms,直接进入死循环 - 数组越界与累加错误:
i自增后直接累加tab[i],此时tab[i]未被赋值;且未限制i的范围,会超出数组tab[20]的索引(0-19) - 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; } }
第二段测试代码的问题分析与修复
原代码错误点
- 阻塞式采集:
delay(40)导致每次采集50个样本需要2秒,无法实时处理心率信号 - BPM计算逻辑错误:仅统计数组中大于0.95的元素数量再乘以4,未基于峰值时间间隔计算,结果不准确
- 时间计算精度丢失:
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
相关产品推荐
相关产品推荐

