做可穿戴设备,尤其是一套能监测心率、血氧、体温这些生理参数的嵌入式系统时,我最大的感受是:难点从来不是传感器能不能读出数据,而是数据在真实场景下依然可靠。手指静止时MAX30102读出的心率很漂亮,一旦佩戴者走动、出汗、环境光变化,波形立刻乱七八糟。这个项目“基于STM32的可穿戴式多生理参数实时监测与预警系统”,本质就是围绕“如何让生理参数在可穿戴场景下测得准、传得稳、报得对”展开的完整实战。
这篇文章我会按实际开发顺序来梳理:从方案选型到硬件设计,再到软件算法和预警逻辑,最后把调试中踩过的坑一并整理。如果你想做类似的毕业设计、医疗电子项目或者个人健康监测设备,这篇文章可以直接当参考框架用。
1. 项目核心思路与方案选型
做这种系统,第一步不是画板子,而是先想清楚三个问题:测什么参数、用什么形态测、预警怎么落地。这三个问题决定了后续所有的硬件选型和软件架构。
1.1 系统要解决的核心问题
先说监测参数的选取。市面上商业手环、医疗级贴片能测的东西很多,比如心电、脑电、肌电、血压、血糖,但这些方案要么传感器昂贵,要么信号调理难度高,要么电极佩戴复杂。作为一整套可落地、可复现的可穿戴项目,我最后选了四项:心率、血氧饱和度、体温、运动状态。
这四项的选择是有理由的:
- 心率是基础生命体征,光电容积脉搏波(PPG)方案成熟,传感器集成度高
- 血氧饱和度在呼吸系统监测、高原场景、睡眠监测中价值很大,和心率可以用同一路PPG信号算出
- 体温是判断发热、炎症等状态的直接指标,模拟前端简单,一个NTC热敏电阻或红外传感器就能解决
- 运动状态本身不是生理参数,但它非常重要——后面预警算法里需要它来区分“安静时的心率升高”和“运动时的心率升高”,区分不了就容易误报
实时监测和预警是一体两面的关系。监测层负责把原始信号变成可用参数,预警层则根据参数和当前运动状态做决策。系统架构上,我采用“终端采集 + 本地边缘预警 + 无线回传”的三层设计,保证没网、没手机的极端场景下终端也能独立报警。
1.2 主控选型:为什么是STM32家族
主控是整个系统的中枢。这个项目里我对比了几类方案,最终锁定STM32。最大的原因有三个:模拟采集能力、低功耗模式、开发生态。
STM32家族里具体的芯片选择,我给个对比表供参考:
| 芯片型号 | 内核 | 主频 | 关键优势 | 适用场景 |
|---|---|---|---|---|
| STM32F103C8T6 | Cortex-M3 | 72MHz | 便宜、资料多、教程多 | 学习验证、非低功耗场景 |
| STM32L431RC | Cortex-M4 | 80MHz | 超低功耗系列、FlexPower控制 | 可穿戴、电池供电 |
| STM32WB55 | Cortex-M4 + M0+ | 64MHz | 集成BLE 5.0射频 | 需内置蓝牙的场合 |
| STM32G431 | Cortex-M4 | 170MHz | 内含运放、比较器、DAC | 模拟信号前端较复杂的场合 |
我做原型时最开始用的是F103C8T6,因为手头库存多,调试方便,Keil工程几分钟就能建好。但做到功耗优化阶段就发现F103的低功耗模式比较基础,STOP模式唤醒逻辑和唤醒源配置不如L4灵活,待机电流也偏大。后来我把核心板换成了STM32L431,用STM32CubeMX重新生成工程,代码基本平移,但低功耗表现明显改善。
如果你只是做课程设计或验证算法,F103完全够用;如果目标是做接近产品的可穿戴设备,建议直接上L4系列。至于WB55,它把BLE射频集成进了芯片里,能省掉一颗外部蓝牙模块,但也意味着射频天线设计、协议栈调试的成本更高,适合有经验的团队。我的做法是外接BLE透传模块,主控和通信解耦,开发快,迭代方便。
1.3 传感器方案的取舍与选型
生理参数测量方案的选型,是这个项目技术含量最高的一道关。
心率与血氧:MAX30102
这个模块几乎是穿戴式项目的标配。它内部集成了红光LED(660nm)、红外LED(880nm,实际典型的为880nm或者940nm,不同批次略有差异)、光电二极管、ADC、环境光抑制电路和数字滤波,通过I2C接口直接输出数字化信号。选它的核心理由是集成度高,模拟前端最难处理的部分已经封装好了,我们不需要自己搭跨阻放大器和滤波电路,大大降低了硬件设计门槛。
体温:NTC热敏电阻
体温测量有几种路线:NTC接触式、红外非接触式、数字温度传感器(如DS18B20)。可穿戴设备接触式场景多,我选了100k NTC热敏电阻。理由很简单:响应速度快、体积小、成本极低、电路就一个分压电阻。测量电路就是一个ADC通道加一个分压网络,标定后精度能做到±0.1℃左右。
运动状态:MPU6050六轴传感器
MPU6050集成了三轴加速度计和三轴陀螺仪,通过I2C输出原始数据。在可穿戴项目中它用来判断佩戴者当前处于静止、正常活动还是剧烈运动状态,也能做跌倒检测。选择它的原因就是资料全、库多、稳定。更新的方案有BMI160、LSM6DS3等,性能更好,但MPU6050作为成熟器件,做原型效率和可靠性都不错。
2. 硬件系统搭建与关键电路设计
方案定型之后,下一步是画原理图和PCB。可穿戴设备的硬件设计跟桌面设备完全不同,体积、功耗、信号完整性三条线要同时考虑。这里我把几个关键模块的设计要点拆开讲。
2.1 整体硬件框架
整个系统的硬件可以划分为五个功能块:
- 主控模块:STM32L431,负责初始化外设、采集数据、运行算法、控制外设
- 生理信号采集模块:MAX30102采集PPG信号,NTC分压网络采集体温
- 运动感知模块:MPU6050采集三轴加速度与角速度
- 无线通信模块:BLE透传模块(如HC-08),通过串口与主控交互
- 人机交互与供电模块:0.96寸OLED,无源蜂鸣器、振动马达、按键、锂电池与充放电电路
模块间通信很简洁:MAX30102和MPU6050挂在同一条I2C总线上(各用不同地址),BLE透传模块用USART1,OLED用软件SPI或硬件SPI,NTC接到ADC的一个通道。整体没有特别复杂的高速总线,所以布线压力不大,真正的重点在电源完整性和模拟信号质量。
2.2 生理信号采集电路设计要点
MAX30102的电路设计要特别注意两点:I2C上拉电平和电源滤波。
MAX30102的工作电压是1.8V,而STM32的I/O是3.3V电平。手册上写I2C引脚可以容忍3.3V,但稳妥起见,我用了PCA9306做电平转换,I2C上拉电阻选4.7k接到1.8V侧。这样做的好处是时序可靠,长时间运行不会出现偶发抖动。
电源滤波方面,MAX30102内部LED驱动电流峰值很大,瞬时电流变化快,电源纹波会直接耦合到PPG信号里。我的做法是在模块电源引脚附近并联一个10uF陶瓷电容和一个100nF高频电容,并且让LED电源(VLED)和模拟电源(VDD)分开走线,避免共享通路。
NTC测温电路就更简单了,典型分压式结构:100k NTC接VCC,下拉电阻100k到GND,中间节点进ADC。这个结构的优点是静态电流小(约16.5uA),几乎不影响整机功耗。测量时注意用ADC的内部参考电压而非VCC,否则电池电压下降会直接导致测温漂移。STM32L431的ADC有内部参考电压(VREFINT),通过校准可以反推出实际VCC,这样即使VCC波动,温度计算结果也稳定。
2.3 电源与低功耗设计
可穿戴设备对功耗极其敏感,直接决定了它的可用性。
电源方案我用的是标准配置:3.7V锂电池 + TP4056充电管理 + TPS63000或XC6206稳压。如果设备空间足够,TPS63000升降压的效率更高,但成本和体积也更大;原型阶段用XC6206-3.3V LDO就够,静态功耗低,外围就两个电容。
整机功耗的实测数据可以作为参考:
| 工作状态 | 电流典型值 | 说明 |
|---|---|---|
| STM32运行态 + 外设全开 | 约25mA | 全部传感器连续工作、OLED常亮 |
| 正常监测态(OLED关) | 约8mA | 传感器按占空比工作、仅BLE暂无数据时保持连接 |
| BLE广播/传输瞬间峰值 | 约30mA | 透传模块发射瞬间 |
| 待机模式 | 约45uA | STM32进入STOP2模式、外设断电 |
按照400mAh电池计算,正常监测态下理论续航约50小时,实际加上损耗大概能跑到一天半到两天。如果你需要更长续航,重点是让MAX30102工作在采样占空比模式——比如心率血氧每秒采5秒数据然后关断,这样能省下大部分电流。
低功耗模式切换上,STM32L431的STOP2模式保留4KB SRAM,足够保存状态参数和最近的一段波形数据。我是用一个定时器定时唤醒采集,采集完立刻配置为待机,形成“采-算-传-睡”的工作节拍。调试时注意把所有GPIO先设置成合理状态(输入上拉或模拟输入)再进低功耗,避免悬空引脚漏电。
3. 软件架构与数据链路实现
硬件是骨架,软件才是让这套系统真正工作起来的血肉。这一章讲程序架构、信号处理和通信协议设计。
3.1 程序框架:时间片轮询为主
系统功能量不算大,但实时性要求却有层次。传感器数据采集需要毫秒级调度,心率算法需要连续数据流,显示刷新可以慢一些。这种场景下我选择了裸机时间片轮询,没有引入RTOS。
原因有两个:一是功能规模可控,状态机加定时器完全能梳理清楚;二是裸机下功耗控制更直接——RTOS的空闲任务钩子虽然也能进低功耗,但上下文切换和信号量唤醒的功耗开销在微安级别场景下不可忽略。
我的任务规划大致如下:
| 任务 | 调度周期 | 优先级 | 说明 |
|---|---|---|---|
| 系统节拍更新 | 1ms | 最高 | 定时器中断,维护时间标志 |
| MPU6050数据读取 | 5ms | 高 | 加速度频繁更新,为体动判断提供基础 |
| MAX30102采样 | 10ms | 高 | 心率血氧的采样率100Hz |
| 算法处理 | 100ms | 中 | 滑动滤波、峰值检测、血氧计算 |
| OLED刷新 | 200ms | 低 | 只在屏幕开启时刷新 |
| BLE数据上报 | 500ms | 低 | 定期上报状态和参数 |
主循环就是一个轮询调度的结构,任务函数里判断对应的时间标志是否置位。这样做的好处是时序清晰、不会互相阻塞,I2C总线访问也通过互斥标志保证同一时刻只有一个任务占用。
3.2 核心算法:从原始信号到可用生理参数
这是整个系统技术含量最高的部分。读传感器原始数据很简单,但要把数据变成可信的“心率值”和“血氧值”,中间经过的几步信号处理必须到位。
心率计算
PPG信号的特点是:包含脉搏波交流分量、呼吸引起的基线漂移、运动伪迹和高频噪声。计算心率的核心链路是“带通滤波 → 峰值检测 → 周期计算”。
带通滤波我用的是二阶IIR滤波器,高频截止5Hz,低频截止0.5Hz。之所以不用FIR,是因为IIR阶数低、计算量小,在以M4内核跑100Hz采样率时非常轻松。下面是核心流程的示意:
// 简化的滤波-峰值检测伪代码 void hr_process_sample(int32_t raw_ppg) { // 1. 低通滤波,去掉高频噪声(截止约5Hz) ppg_lp = lowpass_5hz(raw_ppg); // 2. 高通滤波,去基线漂移(截止约0.5Hz) ppg_bp = highpass_05hz(ppg_lp); // 3. 动态阈值峰值检测 // 维护一个滑动窗口内的最大值/最小值,阈值为其平均 if (ppg_bp > threshold && ppg_bp > prev_peak) candidate_peak = ppg_bp; // 4. 检测到下降沿后确认一个脉搏波峰,计算相邻峰间的时间差 // 60 / (峰值间隔秒数) = 瞬时心率 instant_bpm = 60.0f / peak_interval_sec; // 5. 对最近8次瞬时心率做平均,输出心率 }这个算法在静止状态下效果很好,误差能控制在±2bpm以内。动态状态下需要用加速度数据做运动伪迹消除,这个后面预警章节会讲。
血氧计算
血氧饱和度(SpO2)计算的原理是:氧合血红蛋白和还原血红蛋白对红光(660nm)和红外光(880nm)的吸收率不同。MAX30102同时输出红光和红外两路PPG信号,通过计算交流分量与直流分量的比值得到R值,再查标定曲线得到SpO2。
// SpO2计算的简化模型 float ratio = (AC_red / DC_red) / (AC_ir / DC_ir); float spo2 = 110.0f - 25.0f * ratio; // 经验拟合公式,实际需标定注意这个公式只是简化版,真正的标定曲线需要大量的多受试者数据拟合。原型阶段让血氧值落在95%-99%的合理区间即可,医疗级精度需要严格的临床标定流程。
体温计算
NTC热敏电阻的阻值与温度呈非线性关系,我用的是Steinhart-Hart方程:
float ntc_to_temp(uint16_t adc_val) { float resistance = 100000.0f / (4095.0f / (float)adc_val - 1.0f); float steinhart = log(resistance / 100000.0f); // 对100k NTC、B值约3950,经验参数 float temp_k = 1.0f / (1.0f / 298.15f + (1.0f / 3950.0f) * steinhart); return temp_k - 273.15f; }实际使用时要对着标准温度计做两点或三点标定,修正B值的批次差异。这步不做,测出来的体温可能偏差0.5℃以上。
3.3 通信协议:让数据“说人话”
BLE模块和STM32之间走的是串口透传,但这不意味着直接把裸数据发出去就行。为了让手机端或上位机能准确解析,我定义了一套简洁的帧协议。
| 字节位置 | 内容 | 说明 |
|---|---|---|
| 0 | 帧头 0xAA 0x55 | 两字节帧头,用于帧同步 |
| 2 | 设备ID | 单字节,多设备区分 |
| 3 | 数据长度 | 有效载荷长度 |
| 4 | 数据载荷 | 心率、血氧、体温、运动状态、预警标志等 |
| N | 校验字节 | 累加和校验或CRC8 |
实际解析时用状态机接收,收到0xAA后进入了找0x55的状态。校验失败就丢弃整帧重新找帧头,不采用逐字节重传,因为生理参数的实时性高于可靠性,偶尔丢一帧并不影响整体曲线。
手机端我用了一个通用的BLE串口工具调试,后续做成完整App可以解析这个帧格式,把实时波形和预警弹窗展示出来。
4. 预警判断机制与场景联动
预警系统是整个项目从“监测工具”升级为“健康守护终端”的核心模块。这一部分关键在于:如何减少误报和漏报,因为误报多了用户会关掉预警,漏报少了设备的意义又打了折扣。
4.1 多级阈值与防误报策略
生理参数预警不能简单设一个硬阈值,超过就报警。真实的生理信号本来就存在波动,单次数值超限可能是传感器偶发误差,也可能是佩戴松动的瞬时伪迹。我的方案是“多级阈值 + 连续确认机制”。
| 参数 | 正常范围 | 一级预警(需关注) | 二级预警(需处理) |
|---|---|---|---|
| 心率 | 50-100 bpm | 100-120 或 40-50 | 大于120 或 小于40 |
| 血氧 | 95%-100% | 90%-94% | 小于90% |
| 体温 | 36.0-37.3℃ | 37.3-38.0℃ | 大于38.0℃或低于35.5℃ |
| 跌倒 | — | — | 加速度突变并伴随姿态变化 |
所谓连续确认机制,就是连续5次(每500ms一次)检测到超限,才确认预警触发。这个策略能过滤掉大部分偶发异常。一级预警以OLED提示和蓝牙推送为主,二级预警则直接启动蜂鸣器和振动马达。
心率超限的判断尤其要结合运动状态。静止状态下心率105 bpm确实需要警惕,但如果佩戴者正在跑步机上,105 bpm完全正常。所以我引入了动态阈值调整:根据MPU6050实时计算的运动强度,对心率预警阈值进行动态偏移。剧烈运动时心率阈值上调到180 bpm,血氧阈值下探到92%。
4.2 运动识别与跌倒检测
MPU6050的数据不只是给预警做辅助判断,它本身就是一个独立的监测项,跌倒检测就是其中最有价值的功能之一。
跌倒检测的经典算法是基于合加速度的突变。三轴加速度的合加速度计算公式如下:
float acc_mag = sqrt(ax * ax + ay * ay + az * az);静止站立时合加速度接近1g(约9.8 m/s²)。跌倒过程会有一个明显的特征波形:先是失重阶段(合加速度下降到0.5g以下),然后是撞击阶段(合加速度瞬间冲到2.5g以上),最后是静止阶段(合加速度回到1g且持续数秒)。
我用一个简单状态机来识别这个模式:
- 检测到合加速度小于0.6g,进入“疑似失重”状态
- 在2秒内检测到合加速度大于2.8g,进入“疑似撞击”状态
- 撞击后加速度恢复静止且持续3秒,判定为跌倒
这个算法不是万能的,比如缓慢滑倒的波形就和摔倒不一样。但它能覆盖大部分跌倒场景,作为可穿戴设备的本地预警已经够用。更精确的方案需要结合姿态角、气压计(高度变化)等辅助信息,属于后续迭代方向。
4.3 预警触发后的交互反馈
预警触发后的反馈链路是我验证过比较实用的方案:
本地OLED屏幕显示异常参数和预警等级,蜂鸣器间歇性鸣叫,振动马达以固定节奏震动,BLE模块向手机端推送预警帧。每10秒重复一次,直到用户按按键确认或参数恢复正常。
人机交互设计上有一个细节值得注意:蜂鸣器和振动马达不能同时持续工作,否则在待机状态下耗电很快。我是让蜂鸣器响2秒、停3秒,振动马达响1秒、停4秒,错开工作,并伴随OLED点亮提示。这样既保证提醒效果,又控制功耗。
按键处理上加了一个简单的消抖和长按逻辑:短按切换OLED显示页面,长按3秒关闭报警。这个交互逻辑测试下来比“单次按键直接关”更防误触。
5. 调试过程实录与常见问题排查
这一章整理我实际调试中遇到的典型问题和解法。这些经验在芯片手册上基本学不到,属于必须亲自踩坑才能积累的东西。
5.1 传感器读数异常的经典场景
I2C偶尔通信失败
现象:MAX30102偶发读回全0xFF,程序挂死在等待应答的循环里。排查后发现是I2C上拉电阻选的太(大)——换了4.7k之后稳定。还有一个隐藏问题:I2C中断优先级设置不当,在ADC中断处理过程中I2C被抢占导致时序超时。解决方法是把I2C中断优先级调到最高,并在读操作里加超时退出机制。
PPG波形噪声太大
这是我调得最久的一个问题。第一版PCB做出来后,MAX30102的PPG波形在静止状态下依然毛刺很多。多方排查后定位到两个原因:一个是指夹式或手环式佩戴结构的光路设计不合理,环境光从模块侧面漏进了光电二极管;另一个是LED驱动电流设置过大导致信号饱和。最后处理办法是给传感器模组加了遮光泡棉,并把LED电流从8mA降到4mA,采样动态范围从过载状态恢复正常。
体温读数和实际差距大
这不是电路问题,是标定问题。NTC的B值理论值和实际值有批次差异,加上ADC参考电压的误差,直接算出来偏差0.5℃。解决方法是做两点标定:用恒温槽或标准体温计做对照,修正程序里的温度偏移量。实测下来两点标定后误差能压到±0.1℃以内。
5.2 无线通信中的坑
BLE透传模块的调试也有几个典型问题:
配对后经常掉线
排查发现是模块电源受电机或蜂鸣器工作时的瞬态压降影响,电压跌落超过模块的低压复位阈值。处理办法是增加一个100uF储能电容,并且在蜂鸣器驱动电路上并联续流二极管和RC吸收。这是典型的“电源大电流毛刺干扰”问题,示波器上一测就清清楚楚。
串口数据乱码
重点检查两个模块的地是不是共地。BLE模块和主控用同一个电源时通常没问题,但如果BLE模块单独供电,信号线又不共地,串口的参考电位不同,数据必然乱。我的方案是全部用同一个3.3V电源轨,串口直接连PA9/PA10,没有再隔离。
5.3 实测效果与评估
整套系统完成联调后,我做了几组测试。静止状态下的心率与医用指夹式血氧仪对比,误差在±2bpm以内;血氧在静止状态下和参考设备误差不超过1%;体温在静态环境下误差±0.1℃(标定后);运动状态下心率的跟随性还行,但血氧在剧烈运动时会有明显偏差,这是因为运动伪迹让PPG的交流分量失真了。后面如果要优化,方向就是加自适应滤波,用加速度信号做参考做LMS干扰对消,属于典型的学术研究方向。
低功耗实测,正常监测态整机电流7.8mA,OLED关闭、传感器占空比工作的情况下,400mAh电池能跑接近两天。这个水平作为穿戴设备原型是合格的,离商用设备(一颗纽扣电池跑一个月)还有距离,但优化方向很清楚。
最后的几点个人体会
做完整套系统,我最深的体会是:可穿戴医疗项目最难的不是任何一个单一模块,而是整个系统的“链条完整性”。传感器信号处理、低功耗策略、通信协议、预警算法,每个环节的问题都会在联调阶段集中爆发。尤其是信号链路上的微小细节——滤波电容的位置、I2C上拉的阻值、佩戴结构的遮光设计——任何一环不到位,最终数据就不好看。
如果你是刚开始做类似的STM32项目,我的建议是先别急着追新器件和新框架,把MAX30102和MPU6050这两个成熟模块调好,再一步步叠加功能。先把静态数据测准,再考虑动态场景,最后再上预警和低功耗。这套顺序能帮你少走很多弯路。