ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32驱动MAX30102心率血氧监测:从I2C配置到算法实现

STM32驱动MAX30102心率血氧监测:从I2C配置到算法实现

简介:这套STM32工程代码面向需要实现血氧与心率检测的嵌入式开发者,基于MAX30102传感器完成数据采集、算法处理与功能验证,可用于健康监测类项目、课设或产品原型评估。压缩包共356个文件,约6.06MB;其中221个h头文件和4个cpp源文件构成上层逻辑,94个o、axf、hex、map等为编译链接产物,uvprojx/uvoptx/scvd等为Keil/MDK工程配置,htm/json/ini等包含辅助说明与参数,目录结构完整,还原工程后可快速编译运行。配套algorithm.cpp、MAX30102.cpp、main.cpp及SysClockConf.cpp,覆盖传感器驱动、采样配置、血氧算法与主流程,便于理解关键实现。目前已有3019人学习/下载,适合有一定STM32基础、希望直接参考代码或移植算法的开发者。

1. MAX30102不是拿到手就能出数的:这份STM32代码解决的是从ADC到心率血氧的最后一段路

MAX30102 这颗传感器在淘宝上几十块就能买到,但大多数人照着一个串口例程跑起来,屏幕上只能看到两个原始 ADC 数在跳,却算不出心率,更算不出血氧。原因很简单:MAX30102 硬件只负责用红光和红外光打到皮肤上、接收反射光、再输出 ADC 原始值;真正把 PPG 波形变成 BPM 和 SpO2 的那套东西,全在包里叫max30102algorithm的这部分代码里。这套资源是围绕 STM32 写的 MAX30102 驱动与算法程序,包含寄存器配置、FIFO 数据读取、干扰滤波、心率峰值检测和血氧比值计算,适合正在做指夹血氧仪、手环、睡眠监测或者健康类毕业设计的开发者直接移植。

2. 从引脚到寄存器:MAX30102的I2C初始化与FIFO读取,先把数据流打通

折腾 MAX30102 的第一步不是调算法,而是让传感器稳定、连续地把两路光信号送到 STM32。这个阶段大部分问题不出在代码逻辑,而出在硬件时序和寄存器配置。我见过有人写了一整天 I2C,最后发现是 SDA 和 SCL 没上拉。MAX30102 是标准 I2C 从机,7 位地址默认0x57,很多模块会占用两路线,先把 SDA/SCL 各接一个 4.7kΩ 上拉到电源,否则波形沿不够陡,通信会在跳变沿丢失数据。

2.1 引脚连接与电源滤波:算法再准也救不了烂供电

MAX30102 内部有两路 LED 驱动,红外 LED 瞬间脉冲电流能到几十毫安。如果供电端没有储能电容,采样瞬间电压会跌一下,叠在模拟前端上就成了周期性毛刺。我一般会在模块的 VDD 和 GND 之间放 10μF 钽电容,再在靠近 VDD 引脚放 0.1μF 陶瓷电容。如果板子空间够,建议把模拟地和数字地单点汇合,LED 驱动回流的路径要短。电源没处理好时,读出来的 FIFO 数据里会出现高频等间隔尖峰,FFT 后反而能量集中在 50Hz 附近,这时候任何滤波算法都会把工频干扰当成有效信号。

另外一个容易翻车的点是电平。MAX30102 的 VDD 是 1.8V 逻辑,但绝大多数红色模块板载了 3.3V 转 1.8V 稳压器和电平转换,所以 STM32 直接接模块的 SDA/SCL 没问题。如果是自己做的板子,别把传感器 1.8V 引脚直接接 STM32 的 3.3V GPIO,否则长期会损伤片子。

2.2 寄存器初始化关键参数:FIFO、采样率、LED 电流和 ADC 量程

数据手册里的寄存器名字绕,实际初始化只需要配清零指针、FIFO 配置、采样率、LED 电流这四个部分。先看代码里是怎么写的:

#define MAX30102_I2C_ADDR 0x57 #define REG_INTR_ENABLE_1 0x02 #define REG_FIFO_WR_PTR 0x04 #define REG_OVF_COUNTER 0x05 #define REG_FIFO_RD_PTR 0x06 #define REG_FIFO_CONFIG 0x08 #define REG_SPO2_CONFIG 0x09 #define REG_LED1_PA 0x0A #define REG_LED2_PA 0x0B void max30102_init(void) { // 先软复位并等待传感器内部稳定 max30102_write_reg(0x01, 0x40); // RESET 位置 1 HAL_Delay(100); // 三个 FIFO 指针清零,避免进入满/溢出状态 max30102_write_reg(REG_FIFO_WR_PTR, 0x00); max30102_write_reg(REG_OVF_COUNTER, 0x00); max30102_write_reg(REG_FIFO_RD_PTR, 0x00); // 中断使能:只开 PPG 数据就绪中断,按需再加 FIFO 满中断 max30102_write_reg(REG_INTR_ENABLE_1, 0x40); // FIFO_CONFIG=0x10:不取平均,FIFO 溢出后自动滚写 max30102_write_reg(REG_FIFO_CONFIG, 0x10); // SPO2_CONFIG=0xB9:SPO2 模式,100Hz 采样率,411us LED 脉宽,ADC 量程 2048nA // 各位含义:bit7=SPO2_EN,bit[6:5]=01(100Hz),bit[4:3]=11(411us),bit[2:0]=001(2048nA) max30102_write_reg(REG_SPO2_CONFIG, 0xB9); // 红光和红外 LED 电流都设为 0x24,约 7.2mA // 手指贴紧后原始值应在 2 万到 6 万之间,过大就往下调 max30102_write_reg(REG_LED1_PA, 0x24); max30102_write_reg(REG_LED2_PA, 0x24); }

0xB9这个值容易写错,展开成二进制是1011 1001:bit7 置 1 让传感器进入 SpO2 模式,bit6:bit5 是01对应 100Hz,bit4:bit3 是11对应 411μs 脉宽,这组搭配能把信号幅度和功耗平衡在合理区间。如果手指血氧仪的电池是纽扣电池,可以把采样率降到 50Hz,也就是 bit6:bit5 改00,但要注意心率上限只能到 150 左右,运动场景不够用。

LED 电流寄存器是 8 位分辨率,0x24不是固定值。透明度高的手指可以降到0x18,肤色深或者传感器和皮肤间隙大时,我会先提到0x30。判断标准是 FIFO 数据不能持续顶到三个字节的高位,如果满量程说明 ADC 饱和了,此时波形顶部削平,峰值检测会数出错误的频率。

2.3 FIFO 数据读取:6 字节一组,别只读 4 字节

MAX30102 每采集一次,FIFO 里会写入一组 6 字节数据:红光 3 字节加红外 3 字节。我这里说的“一组”对应一个 18 位 ADC 样本,很多从 MAX30100 移植过来的例程只读 4 字节,结果红光和红外数据错位,算法输出全是乱码。正确做法是读取当前写指针和读指针算出差值,然后连续读 FIFO 数据寄存器。代码如下:

typedef struct { uint32_t red; // 红光通道 ADC 值 uint32_t ir; // 红外通道 ADC 值 } max30102_sample_t; uint8_t max30102_read_sample(max30102_sample_t *sample) { uint8_t wr = max30102_read_reg(REG_FIFO_WR_PTR); uint8_t rd = max30102_read_reg(REG_FIFO_RD_PTR); uint8_t avail = (wr - rd) & 0x1F; // FIFO 深度 32,按环形缓冲计算 if (avail == 0) return 0; uint8_t buf[6]; // 连续读 6 字节;FIFO 读指针会在读取后自动递增 max30102_read_continuous(MAX30102_I2C_ADDR, 0x07, buf, 6); sample->red = ((uint32_t)buf[0] << 16) | ((uint32_t)buf[1] << 8) | buf[2]; sample->ir = ((uint32_t)buf[3] << 16) | ((uint32_t)buf[4] << 8) | buf[5]; return 1; }

(wr - rd) & 0x1F利用了无符号数回绕,真的读空和 FIFO 溢出都不会导致死循环。如果一次读 8 组数据再批量交给算法,吞吐会更好,但意味着你需要在 RAM 里开一块环形缓冲区。对 STM32F103 这种平台,一组样本只占 8 字节,600 组也就 4.8KB,完全放得下。我第一次移植时偷懒改成每读一组就喂一次滤波,结果算法每次只拿到 1 个点,滤波器状态根本收敛不到,所以后面统一改成“先攒够 64 组再处理”,效果立刻稳定。

读数据的 I2C 底层函数在资源包里已经封装好了,标准库是查询模式,HAL 库是HAL_I2C_Master_Transmit/Receive。注意 HAL 的地址参数需要MAX30102_I2C_ADDR << 1,也就是0xAE,很多人第一版在这翻车。

3. algorithm不是查表那么简单:心率与血氧的计算链路

很多新手以为算法就是从库函数里抄一个getHeartRate()出来,实际上 MAX30102 的算法链路是:原始 ADC 值 → 去直流 → 带通滤波 → 峰值/周期检测 → 心率;同时用红光和红外通道的交流/直流比值算出血氧。这套max30102algorithm代码把中间过程拆成了函数,调试时可以一帧一帧跟踪。

3.1 原始信号的预处理:滑动均值去直流

PPG 波形的最大问题是直流分量占总幅值的绝大部分。手指安静放着时,红外通道 ADC 大约 30000,但搏动引起的交流分量只有 500 上下。如果不把直流底去掉,峰值检测会把缓慢漂移的基线当成心跳。最简单可靠的方法是维护一个 N 点滑动窗口求均值,再用当前值减均值得到 AC 分量:

#define PPG_WINDOW_SIZE 100 // 100Hz 采样下对应 1 秒窗口 typedef struct { float buffer[PPG_WINDOW_SIZE]; uint16_t index; float sum; } movavg_t; float movavg_update(movavg_t *avg, float value) { avg->sum -= avg->buffer[avg->index]; avg->buffer[avg->index] = value; avg->sum += value; avg->index++; if (avg->index >= PPG_WINDOW_SIZE) avg->index = 0; return avg->sum / PPG_WINDOW_SIZE; }

movavg_update每次返回窗口均值,用raw - baseline就得到交流信号。窗口长度直接影响精度:100Hz 采样率下选 100 点,意味着基线能跟随 1Hz 以内的呼吸起伏和手臂微动;如果窗口太短,比如 20 点,基线会吃掉一部分真实的心跳能量,导致幅值变小;如果窗口太长,基线又跟不上呼吸引起的低频漂移,后续峰值检测会误判。

我一般会把这个预处理单独放到一个函数里,方便后面用串口打印对比原始信号和滤波后的信号。整个 algorithm 里最影响结果的就是这一步,它不产生“看起来像心率的假波”,比任何后续参数都重要。

3.2 心率计算:时域峰值检测加不应期

心率计算这一节里,algorithm没有用浮点 FFT,是在 MCU 上更实用的时域峰值检测。判断一个点是峰值需要同时满足两个条件:信号大于动态门限,且离上一个有效峰值至少 250ms。这个“不应期”就是很多移植代码漏掉的。

#define MIN_PEAK_INTERVAL_MS 250 // 对应最高 240bpm,超出就丢弃 #define PEAK_THRESHOLD_FACTOR 0.6f // 门限相对动态幅度取 60% int heartrate_update(float signal_ir, uint32_t now_ms, float *bpm) { static float max_val = 0.0f, min_val = 0.0f; static float threshold = 0.0f; static uint32_t last_peak_ms = 0; static float last_bpm = 0.0f; // 动态跟踪信号峰谷,每隔一段时间自动调整门限 if (signal_ir > max_val) max_val = signal_ir; if (signal_ir < min_val) min_val = signal_ir; if ((now_ms - last_peak_ms) > 800) { float amplitude = max_val - min_val; threshold = min_val + amplitude * PEAK_THRESHOLD_FACTOR; max_val = signal_ir; min_val = signal_ir; } // 有效峰值判定 if (signal_ir > threshold && signal_ir > 0 && (now_ms - last_peak_ms) >= MIN_PEAK_INTERVAL_MS) { uint32_t interval = now_ms - last_peak_ms; last_peak_ms = now_ms; if (interval > 0) { last_bpm = 60000.0f / interval; *bpm = last_bpm; return 1; } } *bpm = last_bpm; return 0; }

参数里有三个点容易踩坑。第一,MIN_PEAK_INTERVAL_MS=250并不是拍脑袋:如果设置太短,PPG 信号上的一个重搏波会被当成第二个心跳,心率直接乘 2;设置太长,真性心动过速时又会漏掉。这个值也可以根据当前 bpm 动态调整,但静置场景固定 250 就够。第二,门限每 800ms 重置一次,是给基线漂移一个跟随能力,如果重置太快,阈值会被呼吸起伏拉低,导致噪声过闸。第三,代码里signal_ir > 0是防止手指拿开后传感器过零乱跳,这个在穿戴设备上很常见。

3.3 SpO2 算法:红光和红外通道的 AC/DC 比值

血氧计算的核心公式只依赖两路信号的比例关系。医学上有大量校准模型,但在 MAX30102 这类消费级传感器上,通行的做法是用一个近似线性关系:先分别求出红光和红外的交流分量和直流分量,再算比值 R,最后映射到血氧值。

float max30102_spo2(uint32_t red_raw, uint32_t ir_raw, float red_dc, float ir_dc, float red_ac, float ir_ac) { // 分母加最小保护,防止手指抬起瞬间出现除零 float red_ratio = red_ac / (red_dc > 1000 ? red_dc : 1000); float ir_ratio = ir_ac / (ir_dc > 1000 ? ir_dc : 1000); float R = (red_ratio + 1e-6f) / (ir_ratio + 1e-6f); // 经验公式,来自消费级血氧传感器的常用标定:SpO2 = 110 - 25*R float spo2 = 110.0f - 25.0f * R; // 钳位到合理生理范围 if (spo2 > 100.0f) spo2 = 100.0f; if (spo2 < 70.0f) spo2 = 70.0f; return spo2; }

R的值和血氧呈负相关,正常人在静置时 R 大约在 0.4 到 0.8,对应 SpO2 95% 到 100%。这个公式不能拿去做医疗诊断,但做人体相对变化监测已经够用。需要注意,red_ac和ir_ac必须来自同一时刻的同一个心跳周期,我在算法里是取最近 4 秒窗口内的峰值差和均值来计算,这比每帧都算稳定得多。

血氧对光源遮挡特别敏感。传感器用指夹式结构时,手指厚度、指甲油、甚至按压力度都会改变 LED 的透射路径,导致 R 值整体偏移。所以max30102algorithm里预留了一个静态校准系数:spo2_offset,默认值为 0,在办公室对比指夹式医用血氧仪后,用两次测量的差值修正这个偏移即可。这就是为什么群里会说“血氧算法是门玄学”,因为同一个公式在不同机械结构下偏移量不一样。

4. 移植到自己的STM32工程:HAL库与标准库之间的四个连接点

光有算法,没有正确的工程接线,代码一样跑不起来。这章说的移植不是复制粘贴,而是把与 STM32 相关的四件事理清楚:I2C 底层读写、延时函数、采样定时器、以及算法进 ARR 里还是进主循环。资源里的代码把这两层拆开了:BSP 层放 STM32 相关的内容,algorithm 层保持纯净,方便你换芯片继续用。

4.1 用宏隔离寄存器读写:HAL 和标准库都能用

我不建议在算法文件里直接调用HAL_I2C_Master_Transmit,这会逼着用标准库的人改代码。正确的方式是给 BSP 层定义两个函数,算法层调用它:

// bsp_max30102.c int8_t max30102_write_reg(uint8_t reg, uint8_t value) { uint8_t buf[2] = {reg, value}; if (HAL_I2C_Master_Transmit(&hi2c1, 0xAE, buf, 2, 100) != HAL_OK) { return -1; } return 0; } int8_t max30102_read_continuous(uint8_t reg, uint8_t *buf, uint16_t len) { if (HAL_I2C_Master_Transmit(&hi2c1, 0xAE, &reg, 1, 100) != HAL_OK) { return -1; } if (HAL_I2C_Master_Receive(&hi2c1, 0xAE, buf, len, 100) != HAL_OK) { return -1; } return 0; }

0xAE是0x57 << 1后的 8 位写地址。使用标准库时只需要把HAL_I2C_Master_Transmit换成I2C_SendData和I2C_GenerateSTOP的时序实现,函数名和形参可以不换。分两层的好处是,后面想把传感器换成 MAX30100 或者 MAX86150,只需要改 BSP 层,algorithm不需要动。

这里有个要注意的坑:HAL 的MaxTransmit等待时间 100ms 在 400kHz I2C 下是足够的,但如果你把 I2C 时钟配成 100kHz,100 字节以上连续读时容易超时。我会把超时改成HAL_MAX_DELAY,然后在状态机里用返回码做超时保护,比盲目塞 100ms 更可靠。

4.2 定时器驱动采样:100Hz 不是靠HAL_Delay挤出来的

MAX30102 的算法要求数据是均匀采样的,两个相邻样本点间隔必须稳定在 10ms。如果靠主循环里轮询 FIFO,一旦遇到 LCD 刷新或者计算量大的逻辑,间隔会抖到 15ms 甚至 20ms,心率结果就会忽高忽低。解决方案是用定时器中断驱动采样:配置一个TIM2实现 10ms 周期中断,中断里只做两件事:读 FIFO、把数据丢进环形缓冲区。算法在主循环里慢慢处理。

// 定时器中断回调,100Hz 采样 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { max30102_sample_t sample; if (htim == &htim2) { if (max30102_read_sample(&sample)) { ppg_ringbuf_push(sample.red, sample.ir); } } }

把传感器读取放到中断后,主循环可以放心做 LCD、按键、串口打印。但这里要防止中断被别的更高优先级抢占,比如串口 DMA 回调如果比定时器优先级高,且 DMA 服务时间超过 10ms,会出现丢采样。我一般把定时器中断优先级设为高于串口和 USB,但低于系统滴答这类绝对基础的中断。

如果用的是标准库没有 HAL,就在TIM2_IRQHandler里清标志并调用同一个函数。资源包里两种工程的定时器初始化都写好了,重点是自己确认Prescaler和Period,不要照抄,因为每个人的 APB1 时钟不一样。

4.3 算法重计算的位置:固定窗口处理一次,而不是每点处理

算法里心率检测是逐点调用的,但 SpO2 计算需要 4 秒窗口内累计的红外峰值和均值,所以不要在主循环里每一帧都算血氧,这会浪费 CPU。我会用两个计数器:采样计数累加到 400 帧(4 秒)时,调用一次algorithm_update(),内部一次性计算窗口参数。

static uint16_t sample_count; void main_loop_health_check(void) { max30102_sample_t sample; while (ppg_ringbuf_pop(&sample)) { float ir_ac = sample.ir - movavg_update(&ir_avg, sample.ir); heartrate_update(ir_ac, HAL_GetTick(), &bpm); sample_count++; } if (sample_count >= 400) { float spo2 = max30102_spo2_accumulate(); sample_count = 0; } }

sample_count清零后算法进入下一轮。这个设计避免了浮点 FFT 在 STM32F1 上耗时过长的问题,因为整 module 只用了一个动态门限和几个除法,主频 72MHz 跑起来剩余算力非常多。如果希望心率刷新得更快,可以把窗口改成 3 秒,但低于 3 秒时脉搏波周期数量不足,心率误差会明显变大。这是我在长时间调算法后最深的体会:血氧和心率的刷新率,不能靠缩短窗口“硬挤”。

5. 避坑指南:MAX30102调试中五个典型的翻车现场

这一章是写给所有准备把代码烧进板子开调的人。每条都是实际踩过的坑,按“现象 → 原因 → 解决”的顺序写,遇到问题可以直接对照。

5.1 I2C 无法通信,读 PART_ID 永远是 0xFF

现象:max30102_read_reg(0xFF)返回 0xFF,用示波器看 SDA 有波形,但 STM32 的 ack 信号异常。

原因:模块供电电压不对,或者 STM32 的引脚开漏模式没配置好。MAX30102 对 I2C 引脚的灌电流要求很严格, GPIO 配置成推挽输出时,电平被模块的 1.8V 域拉低,通信时序完全乱掉。

解决:先把 SDA/SCL 配置成开漏输出并打开上拉电阻,外部再接 4.7kΩ 上拉到模块的 VDD(一般已经是 3.3V)。再次读取 PART_ID,正常应该返回0x15。如果返回0xFF,多半是模块坏或者地址不对,换一片确认。

5.2 FIFO 里有数据但红光和红外完全相等

现象:传感器能读到数据,但两个通道 ADC 值一模一样,且波形没有呼吸起伏。

原因:用户把 MAX30102 的例程接到了 MAX30100 模块上。MAX30100 的数据包是 4 字节,里面的红和红外顺序与 MAX30102 不同;另一种情况是你穿过了 IRQ 引脚但没接对中断,导致读到的寄存器是同一个地址。

解决:先看模块上丝印是 MAX30102 还是 MAX30100。MAX30102 一定是 6 字节一组的 FIFO,代码里读 6 字节;MAX30100 则读 4 字节。如果硬件没问题,检查REG_FIFO_DATA是不是0x07,不要沿用 MAX30100 的0x05。

5.3 手指静止时心率图标显示“正常”,实际是真实心率的两倍

现象:用手按住传感器,算法输出 140bpm,但自己摸脉搏只有 70,去掉重搏波后波形才正常。

原因:PPG 信号里存在重搏波,它出现在主峰后面,幅度低但依然超过动态门限。MIN_PEAK_INTERVAL_MS设得太短,重搏波被当成了新心跳。

解决:把MIN_PEAK_INTERVAL_MS从 200 改成 250,并检查门限重置逻辑是否把阈值压得过低。另外可以在检测到峰值后强制把阈值抬高到当前峰值的 70%,保持 200ms 再放下来,通常也能压住重搏波。

5.4 SpO2 显示 88%,但商用血氧仪测出来是 97%

现象:所有代码都跑通,IR 波形也很漂亮,血氧结论却差出 9 个百分点。

原因:LED1 和 LED2 电流设置不同,导致红光和红外通道的 AC/DC 比值偏移。另一个常见原因是指甲油或手指厚度改变了光路,使红光和红外的衰减比例不再落在传感器标准校准曲线上。

解决:先确保两路 LED 电流一致,再检查 ADC 量程。如果还不能修正,使用静态偏移调整:测量 2 分钟,取平均差,把spo2_offset设为这个差值,有效范围在 ±5 之间。医用设备是拿大量人群标定出来的,消费级模块只能靠这个偏移找回精度。

5.5 手指刚离开时心率猛跳到 250

现象:传感器拿开或手指没夹紧时,bpm突然飙到 250 以上,接着输出 0。

原因:手指离开后信号幅度瞬间变小,动态门限没来得及更新,零基线上的噪声被当成有效脉冲。

解决:在heartrate_update里增加最小幅度保护。只有amplitude大于某个阈值,比如 200 ADC 档位,才允许更新阈值和峰值判断。当幅度不足时,保留上一次有效 bpm 并置一个“信号丢失”标志。这样手指没放好时,界面显示的是上一次的正常值,而不是一个吓人的 250。

6. 验证与进阶:用串口波形把心率血氧调到可信

代码移植完、能出数,不等于能出准的数和稳的数。我在调这套 MAX30102 资源时,最后一件事永远是验证,而且只做三件事:看原始波形、对比商用血氧仪、做 1 分钟稳定性测试。

先在主循环里把红光、红外、滤波后的 AC 信号和 bpm 一起格式化输出,接串口绘图器:

printf("%lu,%lu,%d\n", sample.red, sample.ir, (int)bpm);

用 USB-TTL 接到 PC,打开串口绘图器后,能看到一条周期性正弦样波形。正常的 PPG 波形是主峰后挂一个小鼓包,鼓包位置约在主峰后 250ms 左右,这就是重搏波。如果波形没有明显峰谷,先别调算法,回头查 LED 电流和量程。如果波形毛刺多,看电源地。

第二件事是对比血氧仪。把 MAX30102 做成指夹,静置 30 秒,记录串口最后的 SpO2 均值,再对照医用指夹血氧仪。差值就是spo2_offset,写死到算法里。注意每次更换机械结构,比如换壳、换手指套,都要重新做一遍这个标定,这是消费级血氧传感器的宿命。

第三件事是稳定性测试。保持手指不动,连续测 1 分钟,记录 bpm 的方差。正常静置状态下,bpm 波动不应超过 ±3。如果你发现每分钟波动超过 10,说明采样率不均匀或者门限重置太快,按上一章的方法改定时器,不要硬套别人的参数。

进阶来说,这套代码的静置精度已经够了,但想做到运动场景,需要在算法前加一个运动伪影识别:当输入信号的幅值变化超过某个斜率阈值时,暂停心率输出,用加速度计数据做自适应滤波。这是可穿戴产品的下一步,和整包代码里现成的静置算法并不冲突。从那以后我每次做完一个健康监测项目,都会强制走一遍“看波形、对比设备、测方差”这三步,没有这个验证流程,我绝对不会把数据拿到需求方面前。这套代码的完整工程和注释版algorithm在下载页可以拿到,先跑通串口波形再改算法,比自己从头写省一整个调试周期,希望帮到你。

本文还有配套的精品资源,点击获取

返回列表