当前位置: 首页 > news >正文

MAX30102心率血氧算法核心代码逐行解读:从FIFO数据到心率血氧值的计算过程

MAX30102心率血氧算法核心代码逐行解读:从FIFO数据到心率血氧值的计算过程

在可穿戴设备和医疗监测领域,PPG(光电容积脉搏波描记法)技术因其非侵入性和便捷性而广受欢迎。MAX30102作为一款集成式光学传感器,能够同时测量心率和血氧饱和度(SpO2),其核心算法实现却鲜有深入解析。本文将带您深入探究MAX30102项目中最为关键的algorithm.c文件,揭示从原始光学数据到生理参数的全过程。

1. 信号预处理:从原始数据到可用波形

MAX30102通过红外和红光LED照射皮肤,并检测反射光强度来获取PPG信号。原始数据存储在FIFO寄存器中,需要经过多步处理才能用于计算。

1.1 直流分量去除与信号标准化

原始PPG信号包含直流(DC)和交流(AC)成分。直流分量反映组织对光的总体吸收,而交流分量则对应心跳引起的脉动变化。去除直流分量是提取有用信号的第一步:

// 计算IR信号平均值 un_ir_mean = 0; for (k = 0; k < n_ir_buffer_length; k++) un_ir_mean += pun_ir_buffer[k]; un_ir_mean = un_ir_mean / n_ir_buffer_length; // 去除直流分量 for (k = 0; k < n_ir_buffer_length; k++) an_x[k] = pun_ir_buffer[k] - un_ir_mean;

这段代码先计算红外信号的平均值,再从每个数据点中减去该平均值,得到去直流后的信号。这种处理方式能消除个体差异(如皮肤颜色、传感器贴合度)对测量的影响。

1.2 移动平均滤波

为抑制高频噪声,算法采用4点移动平均滤波器:

for (k = 0; k < BUFFER_SIZE - MA4_SIZE; k++) { n_denom = (an_x[k] + an_x[k+1] + an_x[k+2] + an_x[k+3]); an_x[k] = n_denom / (int32_t)4; }

移动平均是最简单的时域滤波方法,能有效平滑信号而不引入相位延迟。选择4点平均是在计算复杂度和滤波效果间的折中。

1.3 差分运算与汉明窗应用

为突出信号变化特征,算法计算相邻采样点的差分:

for (k = 0; k < BUFFER_SIZE - MA4_SIZE - 1; k++) an_dx[k] = (an_x[k+1] - an_x[k]);

差分信号再经过汉明窗加权,减少频谱泄漏:

const uint16_t auw_hamm[31]={ 41, 276, 512, 276, 41 }; // 汉明窗系数 for (i = 0; i < BUFFER_SIZE - HAMMING_SIZE - MA4_SIZE - 2; i++) { s = 0; for (k = i; k < i + HAMMING_SIZE; k++) s -= an_dx[k] * auw_hamm[k-i]; an_dx[i] = s / (int32_t)1146; // 归一化 }

汉明窗的对称结构和特定系数分布使其在频域具有较好的主瓣宽度和旁瓣衰减特性。

2. 心率计算:峰值检测与周期分析

心率检测的核心是识别PPG信号中的脉搏波特征点。MAX30102算法采用差分信号峰值检测结合阈值判定的方法。

2.1 自适应阈值设定

算法首先计算差分信号绝对值的平均值作为初始阈值:

n_th1 = 0; for (k = 0; k < BUFFER_SIZE - HAMMING_SIZE; k++) n_th1 += ((an_dx[k] > 0) ? an_dx[k] : ((int32_t)0 - an_dx[k])); n_th1 = n_th1 / (BUFFER_SIZE - HAMMING_SIZE);

这种自适应阈值方法能适应不同信号强度,比固定阈值更具鲁棒性。

2.2 峰值检测算法

maxim_find_peaks函数实现完整的峰值检测流程:

void maxim_find_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height, int32_t n_min_distance, int32_t n_max_num) { maxim_peaks_above_min_height(pn_locs, pn_npks, pn_x, n_size, n_min_height); maxim_remove_close_peaks(pn_locs, pn_npks, pn_x, n_min_distance); *pn_npks = min(*pn_npks, n_max_num); }

该函数分三步工作:

  1. 找出所有高于阈值的候选峰值
  2. 去除距离过近的冗余峰值
  3. 限制最大峰值数量

2.3 心率计算与验证

根据检测到的峰值间隔计算心率值:

if (n_npks >= 2) { for (k = 1; k < n_npks; k++) n_peak_interval_sum += (an_dx_peak_locs[k] - an_dx_peak_locs[k-1]); n_peak_interval_sum = n_peak_interval_sum / (n_npks - 1); *pn_heart_rate = (int32_t)(6000 / n_peak_interval_sum); // 转换为BPM *pch_hr_valid = 1; } else { *pn_heart_rate = -999; *pch_hr_valid = 0; }

这里6000是采样率(100Hz)与分钟转换因子(60秒)的乘积。算法要求至少检测到两个有效峰值才计算心率,否则标记为无效。

3. 血氧饱和度计算:AC/DC比值法

血氧饱和度计算基于红光和红外光信号的不同吸收特性。含氧血红蛋白和脱氧血红蛋白对这两种光的吸收比例不同。

3.1 信号谷值精确定位

首先在粗略估计的谷值位置附近寻找精确的最低点:

for (k = 0; k < n_npks; k++) { un_only_once = 1; m = an_ir_valley_locs[k]; n_c_min = 16777216; // 2^24 if (m+5 < BUFFER_SIZE-HAMMING_SIZE && m-5 > 0) { for (i = m-5; i < m+5; i++) { if (an_x[i] < n_c_min) { un_only_once = 0; n_c_min = an_x[i]; an_exact_ir_valley_locs[k] = i; } } if (un_only_once == 0) n_exact_ir_valley_locs_count++; } }

这种局部搜索方法能克服移动平均导致的波形偏移问题,准确定位每个心跳周期的起点。

3.2 AC/DC分量计算

在两个相邻谷值之间计算红光和红外信号的AC和DC分量:

for (k = 0; k < n_exact_ir_valley_locs_count - 1; k++) { n_y_dc_max = -16777216; n_x_dc_max = -16777216; if (an_exact_ir_valley_locs[k+1]-an_exact_ir_valley_locs[k] > 10) { for (i = an_exact_ir_valley_locs[k]; i < an_exact_ir_valley_locs[k+1]; i++) { if (an_x[i] > n_x_dc_max) { n_x_dc_max = an_x[i]; n_x_dc_max_idx = i; } if (an_y[i] > n_y_dc_max) { n_y_dc_max = an_y[i]; n_y_dc_max_idx = i; } } // 计算红光AC分量 n_y_ac = (an_y[an_exact_ir_valley_locs[k+1]] - an_y[an_exact_ir_valley_locs[k]]) * (n_y_dc_max_idx - an_exact_ir_valley_locs[k]); n_y_ac = an_y[an_exact_ir_valley_locs[k]] + n_y_ac / (an_exact_ir_valley_locs[k+1] - an_exact_ir_valley_locs[k]); n_y_ac = an_y[n_y_dc_max_idx] - n_y_ac; // 计算红外AC分量(类似红光计算) // ... // 计算比值 n_nume = (n_y_ac * n_x_dc_max) >> 7; n_denom = (n_x_ac * n_y_dc_max) >> 7; if (n_denom > 0 && n_i_ratio_count < 5 && n_nume != 0) { an_ratio[n_i_ratio_count] = (n_nume * 20) / n_denom; n_i_ratio_count++; } } }

AC分量反映脉动变化,DC分量反映总体吸收。右移7位(>>7)相当于除以128,是定点数优化技巧。

3.3 查表法获取SpO2值

为减少计算量,算法使用预先计算的查找表将AC/DC比值转换为SpO2值:

const uint8_t uch_spo2_table[184] = { 95, 95, 95, 96, 96, 96, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 99, // ... 表格数据省略 ... 1 }; // 取中值 maxim_sort_ascend(an_ratio, n_i_ratio_count); n_middle_idx = n_i_ratio_count / 2; if (n_middle_idx > 1) n_ratio_average = (an_ratio[n_middle_idx-1] + an_ratio[n_middle_idx]) / 2; else n_ratio_average = an_ratio[n_middle_idx]; // 查表 if (n_ratio_average > 2 && n_ratio_average < 184) { n_spo2_calc = uch_spo2_table[n_ratio_average]; *pn_spo2 = n_spo2_calc; *pch_spo2_valid = 1; } else { *pn_spo2 = -999; *pch_spo2_valid = 0; }

查表法避免了实时计算复杂数学表达式,显著降低了对处理器性能的要求。表格数据基于以下公式预先计算:

SpO2 = -45.060 * ratio² + 30.354 * ratio + 94.845

4. 工程实践中的优化技巧

在实际应用中,MAX30102算法还需要考虑多种工程因素才能获得稳定可靠的测量结果。

4.1 动态范围调整

信号强度会因传感器佩戴松紧、皮肤特性等变化。算法包含自动调整逻辑:

un_min = 0x3FFFF; // 初始最小值 un_max = 0; // 初始最大值 // 更新信号范围 for(i = 0; i < n_ir_buffer_length; i++) { if(un_min > aun_red_buffer[i]) un_min = aun_red_buffer[i]; if(un_max < aun_red_buffer[i]) un_max = aun_red_buffer[i]; }

动态跟踪信号范围可适应不同测量条件,避免信号饱和或信噪比过低。

4.2 运动伪迹处理

运动是影响PPG信号质量的主要因素。算法通过多种策略提高鲁棒性:

  1. 滑动窗口处理:保留最新400个样本,与新增100个样本组合计算
for(i = 100; i < 500; i++) { aun_red_buffer[i-100] = aun_red_buffer[i]; aun_ir_buffer[i-100] = aun_ir_buffer[i]; }
  1. 有效性验证:检查心率和SpO2值是否在生理合理范围内
if((ch_hr_valid == 1)) { if(n_heart_rate < 120) dis_hr = n_heart_rate; else dis_hr = 0; dis_spo2 = n_sp02; }
  1. 多周期平均:计算5个连续周期的比值取中值,减少瞬时干扰影响

4.3 低功耗优化

对于电池供电设备,算法设计考虑了功耗优化:

  1. 采样率选择:默认100Hz平衡了精度与功耗
#define FS 100 // 采样率
  1. 间歇工作模式:非连续测量时可配置传感器进入低功耗状态
max30102_Bus_Write(REG_MODE_CONFIG, 0x40); // 复位/低功耗模式
  1. LED电流控制:根据信号质量动态调整LED驱动电流
max30102_Bus_Write(REG_LED1_PA, 0x24); // 红光LED电流~7mA max30102_Bus_Write(REG_LED2_PA, 0x24); // 红外LED电流~7mA
http://www.rkmt.cn/news/1507885.html

相关文章:

  • 从PSG到FSG:聊聊芯片里那些“玻璃”层是怎么用CVD“吹”出来的
  • 2026年海棠树苗选购指南:从品种到产地,一次说清! - 优质品牌商家
  • Moneta Markets亿汇:注重效率的使用者更在意的市场覆盖,这里做个路径分析
  • Python 高手编程系列三千四百三十六 :命名和使用
  • 别再只看跑分了!聊聊那些真正影响你NVMe SSD游戏加载和文件传输速度的‘隐形杀手’
  • 骁龙X2 Elite边缘AI应用开发实战(3): 端侧智能语音助手全链路实现
  • 2026年新发布针织衫品牌厂商有哪些?实力工厂的选型与推荐 - 品牌鉴赏官2026
  • OpenClaw+AWS 深度应用:自动生成 CloudFormation 模板、批量管理 S3 存储桶
  • Vivado Utility Buffer IP全解析:从IBUFDS到BUFGCE,手把手教你时钟与IO缓冲器选型
  • Go 微服务 Saga 模式:分布式事务的补偿与一致性实践
  • 不止看功耗:Vivado里Report RAM和Control Sets的隐藏用法与优化技巧
  • 5分钟掌握PKHeX自动合法性插件:让宝可梦数据合规变得简单
  • 5分钟快速上手:免费开源的暗黑破坏神2存档编辑器完整指南
  • 别再为测正负电压发愁了!手把手教你用LTspice仿真两种绝对值电路(附ADA4522/LT1001实测对比)
  • 【趣味算法】韩信点兵:从枚举到中国剩余定理(附多语言源码)
  • 从SPI到QSPI:当你的SD卡和Flash嫌SPI太慢时,我们该怎么办?
  • Mermaid Live Editor终极指南:5分钟掌握实时图表编辑神器
  • 给3DGS/NeRF新手的球面谐波(SH)极简图解:从‘外星生物’到‘颜色魔法’
  • Python 高手编程系列三千四百三十五 :Hy
  • EFI Boot Editor:终极UEFI启动管理工具完整指南
  • 从用户到创作者:用Mi-Create重新定义你的小米穿戴体验
  • 突破游戏资源编辑壁垒:Harepacker-resurrected一站式解决方案深度解析
  • CXL DVSEC寄存器详解:从PCIe配置空间到CXL设备识别的实战指南
  • 2026年EN45545认证避坑指南:进口与国产材料常见问题深度测评分析 - 优质品牌商家
  • 3个简单步骤实现PC微信QQ防撤回:告别“已撤回“消息的终极方案
  • 别再死记硬背了!用几个真实案例帮你彻底搞懂TS的export interface和type
  • ChatGLM2-6B的GLMBlock里到底发生了什么?一次注意力与MLP的深度游
  • 从‘你好’到完整回复:一步步图解ChatGLM2-6B的推理循环(附KV Cache原理)
  • 深入IR2104数据手册:被忽略的SD引脚用法和死区时间调节实战
  • 2026年新消息:湖北口味好的酱鸭翅中选购全攻略 - 品牌鉴赏官2026