基于STM32的智能头盔:心率监测与跌倒报警系统设计

📅 发布时间:2026/10/3 10:30:22
基于STM32的智能头盔:心率监测与跌倒报警系统设计
1. 智能头盔想解决的真正问题从需求倒推系统架构1.1 我为什么用 STM32 做头盔主控先交代一下这个项目的起因。我日常骑电动车通勤有一次夜间走了一段没有路灯的非机动车道前车突然急刹我虽然刹住了但明显感觉身体晃了一下。回家后我就在想如果当时摔了周围没人、手机又锁屏我该怎么办这个场景催生了这个 DIY 项目——做一个能感知佩戴者心率、能在跌倒时主动报警的智能头盔。选 STM32 做主控最直接的原因是社区积累太厚了。无论是标准外设库、HAL 库还是 LL 库网上都能找到大量可参考的工程GD32、MM32 这些国产替代虽然便宜但很多例程还是从 STM32 移植过来的遇到问题排查时STM32 的资料量能省下大量时间。我用的具体型号是 STM32F103C8T6蓝板网红芯片淘宝几块钱一片对头盔这种消费级原型验证来说性能和成本刚好卡在甜点上。有人可能会问既然要做低功耗可穿戴为什么不选 STM32L0 系列或者直接上 nRF52这个问题的答案后面会展开。F103 的 STOP 模式待机电流确实不如 L 系列好看但它电源管理逻辑简单、外设丰富原型阶段先把功能跑通再考虑低功耗换型迭代成本最低。1.2 系统架构的“三路感知、一路决策、两路输出”任何可穿戴设备第一步都是把“要感知什么”和“要输出什么”理清楚。我这个头盔最终敲定的系统架构可以这样拆感知层三路输入心率感知MAX30102 模块放在头盔前额衬垫位置通过 PPG 光电容积脉搏波采集心率数据。运动感知MPU6050 六轴传感器采集三轴加速度和三轴角速度用于姿态解算和跌倒判定。按键输入一个独立物理按键用于主动求助、解除误报和功能切换。决策层STM32F103C8T6 作为核心通过 I2C1 总线挂载两个传感器MAX30102 地址 0x57MPU6050 地址 0x68通过 GPIO 管理按键、蜂鸣器、振动马达和 OLED 屏幕。输出层两路响应本地警示OLED 实时显示心率、运动状态、跌倒预警蜂鸣器和振动马达在跌倒确认后发出声光震动提醒。远端求助预留串口接口可外接 4G 或蓝牙模块把报警消息发给紧急联系人。这个架构最核心的原则是传感器和处理逻辑严格解耦。心率算法不需要关心姿态数据怎么算跌倒检测也不需要了解 PPG 波形的滤波细节每个模块独立调试通过后再集成。坏处是占用了不少 GPIO好处是排查问题时哪一路出了问题直接看对应的数据流即可。2. 心率监测MAX30102 的读取、滤波与有心跳的波形2.1 选型逻辑MAX30102 对比 PulseSensor市面上 DIY 心率方案有两个主流方向。PulseSensor 是一个模拟输出模块原理是红外 LED 照射皮肤后光敏管把反射光强度变化转成模拟电压MCU 通过 ADC 采样再算心率。这种方案电路简单但模拟信号极易受环境光和运动干扰头盔在骑行状态下噪声会淹没有效信号。MAX30102 则是数字输出内部集成了红光 LED660nm、红外 LED880nm和光电检测器输出的已经是模数转换后的原始数据而且支持 100Hz 以上的采样率。为什么选它还有一个关键点它内部 FIFO 可以缓存 32 组采样数据MCU 可以批量读取而不用实时盯住每个采样点给主控省下大量中断开销。两块我都测过。PulseSensor 在静态坐姿下精度尚可一旦走路或骑行波形完全没法看MAX30102 配合算法做运动伪影剔除虽然不能做到医疗级但在智能头盔这个场景下已经足够可靠。2.2 I2C 时序与寄存器配置MAX30102 本质上就是一个 I2C 从设备配置核心寄存器不多。我用标准库写的初始化流程大概是// 关闭传感器所有中断避免启动时误触发 write_reg(0x02, 0x00); // 中断使能寄存器 write_reg(0x03, 0x00); // 中断状态清除 // 配置模式为红光红外同时输出0x03 对应 SpO2/HR 模式 write_reg(0x09, 0x03); // 采样率 100Hz脉冲宽度 411us write_reg(0x0A, 0x03); // SPO2_CONFIG // LED 电流我设的是红光 6.4mA红外 6.4mA write_reg(0x0C, 0x3F); // LED1_PA 红光 write_reg(0x0D, 0x3F); // LED2_PA 红外 // 开启 FIFO 滚动模式 write_reg(0x08, 0x01); // FIFO 配置采样平均 1FIFO 滚动使能初始化完成后每次读数据只需要两步查中断状态寄存器0x00确认 FIFO 有新数据然后连续读取 0x04-0x07 四个字节的 FIFO 数据寄存器。注意一点每次读取实际是 6 个字节红光 3 字节 红外 3 字节但通过 0x04 连续读时只要把寄存器地址设到 0x04后面按顺序读即可。这里有一个新手非常容易踩的问题I2C 速率。MAX30102 在 400kHz 快速模式下没问题但部分杂牌模块板载上拉电阻阻值偏大高速通信时上升沿太慢会出现随机读回 0xFF。我在这个项目里把 I2C 时钟降到 200kHz稳定性和速度平衡最好。2.3 从原始波形到心率值的三步处理拿到原始 AD 值后直接算频率是行不通的。PPG 信号里混着环境光噪声、呼吸导致的基线漂移、运动伪影等多种成分。我用了三步处理每一步都是为了解决一个具体问题。第一步是直流剔除和带通滤波。MAX30102 原始信号在波形上是一个几百到几千 AD 值的大直流偏压上叠加一个很小的脉搏波动。我用一阶高通滤波器截止频率约 0.5Hz滤掉直流和大尺度漂移再用一阶低通滤波器截止频率约 5Hz滤掉高频噪声。两个一阶 IIR 级联系数可以在 startup 时计算好运行时就是三次乘加运算对 F103 来说负担忽略不计。第二步是波峰检测和 R-R 间期计算。对滤波后的波形用一个滑动窗口扫描局部极大值。判定波峰的条件是当前值比前后 30 个采样点都大且波峰幅度高于滑动平均幅度的 40%。为什么加幅度阈值因为滤波后依然有残留噪声如果只看局部极大值一个毛刺就会被当成心跳。第三步是动态心率计算。我采用的是取最近 4 个 R-R 间期的平均值再取倒数换算成每分钟心跳数// 最近4个R-R间期之和单位ms uint32_t rr_sum rr[0] rr[1] rr[2] rr[3]; // 平均R-R周期 rr_sum / 4 // 采样率为100Hz每周期对应10ms所以周期ms 周期点数 * 10 // 心率 60000 / 平均周期(ms) uint16_t bpm 60000 / (rr_sum / 4 * 10);为什么用 4 个周期平均而不是 1 个因为单周期受呼吸调制影响大会出现 10% 左右的抖动。4 个周期平均后正常心率下的波动能控制在 3% 以内。3. 跌倒报警用 MPU6050 做一次“会判断的姿态感知”3.1 六轴数据融合的工程简化跌倒检测是本项目里算法复杂度最高、也最容易做砸的部分。MPU6050 给的是原始三轴加速度和三轴角速度不能直接用需要先解算出姿态角度。常见的方法是使用 DMPDigital Motion Processor直接读取四元数然后转欧拉角。DMP 确实方便但有个问题DMP 固件库代码大、初始化时间长而且跌倒判定的核心其实不是精确角度而是“是否发生了姿态突变”。所以我最后没有用 DMP改用了互补滤波解算角度。互补滤波的思路很简单加速度计在长时间尺度上准确但不抗抖动陀螺仪短时间精确但会积分漂移把两者按比例融合pitch 0.98 * (pitch gyro_y * dt) 0.02 * accel_pitch; roll 0.98 * (roll gyro_x * dt) 0.02 * accel_roll;0.98 和 0.02 这个系数是工程经验值。dt 是姿态解算周期我用的是 10ms。融合后的角度用于判断头盔倾斜程度而跌倒判定则主要依赖加速度幅值。3.2 基于加速度幅值与角度的多级判定跌倒这个过程物理上有一个非常明显的特征序列先有一个失重阶段加速度接近 0然后是剧烈撞击加速度瞬间冲到 2g 以上撞击后行为姿态发生变化比如人躺倒了。针对这个特征我实现了四状态状态机状态 IDLE正常站立/骑行持续计算加速度幅值 SMV sqrt(ax² ay² az²)。当检测到 SMV 0.6g 持续超过 100ms切换到 FREE_FALL 状态。状态 FREE_FALL疑似失重继续监测 SMV。若 SMV 2.6g切换到 IMPACT 状态。若 500ms 内没有出现撞击回到 IDLE说明只是蹬腿或者颠簸。状态 IMPACT撞击发生记录撞击时刻的加速度峰值和撞击后的角度值。启动 2s 的静置检测窗口若 2s 内有累计 1s 的时间 SMV 0.3g说明人已经停止移动进入 POSTURE_CHECK 状态。如果 2s 内仍有持续大幅运动说明可能只是摔倒后快速爬起回到 IDLE。状态 POSTURE_CHECK姿态确认在撞击后 5s 时比较当前 pitch/roll 与撞击前 5s 的平均 pitch/roll。如果倾角变化大于 45°且当前仍处于低活动度SMV 0.3g则确认跌倒触发报警。否则回到 IDLE。这套状态机的核心思想是跌倒不是看单个物理量而是看一整个事件的时序是否符合“失重-撞击-静止-姿态改变”的特征。每个状态都有超时回退机制避免卡死在中间状态。3.3 误报抑制与触发后的救援链路没有任何阈值能做到零误报。我实测最典型的误报场景有三个上下楼梯快速跑动、蹦跳、弯腰系鞋带后迅速起身。前两个问题在于没有“失重-撞击”连续特征第三个问题在于倾角变化大但没有撞击尖峰。处理策略是增加“人为确认窗口”。当状态机确认跌倒后立即触发本地声光报警蜂鸣器连续鸣叫 振动马达高频振动 OLED 显示“跌倒检测即将求救”并进入 15 秒倒计时。如果佩戴者在 15 秒内按下按键则取消报警如果超时无响应才执行真正的“远端求助”动作。这个设计能保留自动报警的高灵敏度同时把误报造成的骚扰降到最低。远端求助链路我在原型阶段留的是串口协议接一个 4G 模块后可以自动向紧急联系人发送短信和位置。考虑到不同模块的 AT 指令集差异大我把这部分抽象成了一个接口函数只需实现send_alert(char* message)就能把报警消息送出去具体走短信、微信推送还是物联网平台完全由后续模块决定。4. 供电、低功耗与机械装配可穿戴项目翻车的重灾区4.1 电池容量估算与 DC-DC 选型做可穿戴设备电池和供电拓扑往往比主控选型更影响体验。我的电源需求表如下器件典型工作电流峰值电流电压STM32F103C8T6 72MHz约 35mA50mA3.3VMAX301020.6mA6.4mALED脉冲3.3VMPU60503.6mA3.9mA3.3VOLED 0.96寸15-25mA30mA3.3V蜂鸣器振动马达5mA40mA3.3V静态运行合计约 60mA报警状态会瞬时到 130mA。我用的是 800mAh 聚合物锂电池理论续航约 13 小时实际骑行场景下因为 LED 脉冲和无线模块间断工作基本能用 6-8 小时满足单日通勤需求。电源芯片我选了国产的 ME6218 低压差稳压器静态功耗只有 30µA 左右输入电压范围到 6V输出纹波在 50mV 以内。为什么没上 Buck-Boost DC-DC因为电池直接 3.7V 到 3.3V 的压差只有 0.4VLDO 的转换效率在 85% 以上DC-DC 节省的功耗有限却会引入开关噪声可能污染 PPG 模拟信号。这个取舍往往被忽略。4.2 F103 的 STOP 模式与其他待机优化说完运行功耗说待机。STOP 模式是 F103 在保持 SRAM 内容前提下功耗最低的模式官方典型值 14µA 左右。但我实测中芯一级的板子经常做到 50µA 以上原因基本都在 GPIO 配置上。进入 STOP 之前必须把外设引脚全部置为确定状态。我踩过的坑是MAX30102 和 MPU6050 的 I2C 引脚不做处理进入 STOP 后 SDA 线被模块内部上拉电阻拉高电流从 MCU 引脚漏走。解决方法是进入 STOP 前把所有外设设为睡眠模式把 I2C 引脚重新配置为普通推挽输出并输出低电平进入低功耗后唤醒再重新初始化。唤醒方式我用的是外部中断按键。PA0 配置为下降沿中断可以把 MCU 从 STOP 模式唤醒。注意 F103 的 STOP 模式唤醒后PLL 不会自动恢复需要在唤醒中断里重新配置系统时钟否则 CPU 以 8MHz HSI 运行所有定时器延时都会乱套。4.3 外壳固定、减震与线材走过的弯路头盔内部的机械结构直接影响所有传感器的数据质量。MAX30102 必须在衬垫上开一个窗口让传感器贴住前额皮肤但又不能压得太紧——压力过大会压迫毛细血管反而测不到脉搏信号。我用的是 2mm 厚的高密度泡棉作为缓冲垫中间挖出传感器开孔这样既保证贴合又不过压。MPU6050 的位置也有讲究。一开始我把它装在头盔侧面结果发现转弯时离心力会导致加速度读数异常偏大。后来改到头盔顶部正中靠近整个系统的几何中心转弯时的离心力在头顶位置影响最小。模块固定用的不是螺丝而是双面胶加尼龙扎带因为螺丝硬连接会把头盔壳体的高频振动直接传导到传感器影响姿态解算精度。线材是这个项目里最容易被低估的部分。头盔是每天戴的东西太阳暴晒后内部温度能到 50 度以上骑行时持续振动。我第一版用的普通杜邦线大概二周后就出现接触不良。后来全部换成 28AWG 硅胶线插座接头全部点热熔胶固定传感器端用柔性 FPC 转接才彻底解决。5. 调试过程踩过的四个坑完整排查链路复盘5.1 坑一MPU6050 的 I2C 总线“读卡死”项目进行到第二周出现了最诡异的一个问题系统运行大约半小时后MPU6050 的 I2C 读取偶尔会卡死MCU 一直等 ACK 信号程序停在while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY))里。用示波器抓 SDA 线后发现SCL 还在正常翻转但 SDA 被从设备拉低后不释放。排查到最后发现是 I2C 通信时序异常时的总线锁死。I2C 协议规定主设备收到 NACK 或者总线上出现异常起始条件时需要发送 9 个时钟脉冲把从设备复位。但我用的是标准库的I2C_GenerateSTOP直接退出没有做总线恢复。解决方案是加了一个函数检测到 BUSY 超时后手动把 SCL 翻转 9 次让从设备释放 SDA然后再重新初始化 I2C 外设。这个问题的根源是硬件上 SDA 线上拉电阻和传感器板内电容形成了 RC 延迟在高温下时序余量进一步劣化。后来我把 I2C 速率从 400kHz 降到 200kHz并加了超时看门狗卡死问题再没出现过。5.2 坑二骑行振动下的心率数据漂移心率模块在静止状态测试非常正常但装到头盔上骑了一趟车后心率数值开始乱跳甚至出现 40 到 200 的剧烈波动。我的排查链路是这样的先用串口把 MAX30102 原始红外波形导出来发现波形里叠加了一个频率约 2Hz 的大幅波动幅值比脉搏波还大。结合 MPU6050 数据一对比这个波动频率和头盔的振动频率完全一致。这确认了是运动伪影进入 PPG 信号。处理分两层。硬件层面我把 MAX30102 从刚性固定改为泡棉缓震固定减小传感器和皮肤之间的相对位移。软件层面增加了运动伪影丢弃机制读取心率 FIFO 的同时读取 MPU 的加速度幅值如果 SMV 超过 0.5g则暂停心率计算并把心率值标记为无效等运动平稳后再恢复显示。这种“模棱两可数据丢弃”的策略远比强行滤波处理更可靠。5.3 坑三上下楼梯疯狂误报跌倒检测算法刚跑通时上下楼梯和跑步几乎每次都会触发报警倒计时。先在 ROS 环境核对状态机日志发现绝大多数误报都是这么走的FREE_FALL 状态被触发然后 IMPACT 也被触发但 IMPACT 后的静置检测窗口却没能回到 IDLE——因为跑步状态下 2 秒内不可能有 1 秒的安静期于是错误地进入了 POSTURE_CHECK。根因清楚了我的状态机只判断“是否静止”没区分“跌倒后静止”和“运动期间的短暂停歇”。修复方式是增加了两个条件。第一撞击后必须立即出现一个短时的低加速度窗口0.5s 内 SMV 0.3g模拟人摔倒后失去行动力的瞬间第二撞击时刻的姿态变化必须发生在撞击后 2 秒内如果 2 秒后姿态才开始变化说明人是自主运动而不是失去意识。打上这两个补丁后跑步误报率下降了九成以上。5.4 坑四OLED 刷新造成的心率采样延迟系统跑到后期发现心率偶尔会出现明显的采样断档串口打印显示每过一段时间就有一小段波形缺失。定位后发现是 OLED 刷新函数太耗时。我用的是 Software I2C 驱动 OLED每次全屏刷新要发送 1KB 数据GPIO 翻转频率受限一次刷新要卡住 MCU 约 80ms。在这 80ms 内MAX30102 的 FIFO 继续写入新数据我却没有及时读取导致多组数据被 FIFO 覆盖。解决方式有两个我用的是第二个。一是把 OLED I2C 改成硬件 I2C配合 DMA 在后台传输二是降低刷新频率到 1Hz 并在刷新的临界点主动查询 FIFO 水位。实际上头盔场景下实时刷新不需要太高最终方案把刷新率降到 1Hz同时在 OLED 刷新前先读出 FIFO 数据彻底避免了冲突。6. 从 DEMO 到日常可用的差距校准、扩展与可靠性设计6.1 传感器校准和个体差异项目做到能跑只是第一步要做到“能信”必须解决个体差异问题。MAX30102 对不同肤色、不同皮肤厚度的响应差异非常大同一个 LED 电流在 A 身上信号饱和、在 B 身上信号微弱。校准方式是使用自动增益控制采集前先用低 LED 电流测试信号如果信号幅值太低逐步调高 LED 电流如果 ADC 满量程溢出就降低电流。这个自动标定逻辑在开机时执行一次佩戴中途也周期性检查保证不同人戴上都能量到有效波形。MPU6050 的校准则相对简单。上电后保持头盔静止 3 秒采集 100 组加速度和角速度数据取平均作为零偏值。姿态解算时先减去这个零偏能有效避免因为模块虚焊或者安装倾斜导致的初始角度误差。6.2 模块扩展定位、通信、语音提示头盔原型预留的串口是给后续功能模块留的接口。我目前验证过三类扩展定位模块用的是常用的 GPS/北斗模组输出 NMEA 协议F103 通过串口解析$GNGGA语句即可提取经纬度。跌倒报警时可以把位置信息附加在报警消息里让紧急联系人知道人在哪里。通信模块目前实测过两款一款是低功耗蓝牙模组优点是手机连接后可以把报警消息转发到微信或短信另一款是 4G 模组优点是脱离开手机直接拨号或者发短信但功耗和体积都明显增加。考虑到头盔的空间和续航我最终倾向蓝牙方案。语音提示模块可以给用户更多反馈心率过高时语音提示“心率过高请休息”跌倒报警前语音确认“即将发出求助十秒内取消请按键”。这一层能大幅降低被误报骚扰的烦躁感。6.3 看门狗、日志与长期可靠性消费级产品和 DIY 原型的最大差距在于可靠性。我做了三件事让它更接近前者。一是启动独立看门狗 IWDG超时时间设为 2 秒主循环里每次都喂狗。如果程序因为干扰跑飞2 秒后 MCU 自动复位重启。这个在骑行振动环境中尤其重要静电和火花塞辐射可能让 CPU 异常。二是关键数据日志。我把每次报警事件的时间、触发原因状态机路径、心率值和姿态角都记录到片内 Flash 的末尾扇区。每次被误报之后可以通过串口把日志拖出来分析是哪一条判定条件太灵敏再针对性调整阈值。三是批量烧录。原型阶段用 ST-Link Keil 直接下载但做了两三台样机后发现每次都打开 Keil 太慢。后来改用 STM32CubeProgrammer 写脚本把编译生成的 HEX 文件在命令行批量烧录配合 J-Link 的 SWD 接口一台设备的烧录时间从两分钟缩短到十秒。在做这个项目之前我也犹豫过觉得智能头盔是不是已经有成熟产品了DIY 还有没有意义。实际做下来收获比预想大得多这个项目不是简单地把几个传感器拼在一起而是逼着你去理解信号处理、状态机设计、低功耗管理和机械结构之间的耦合关系。有一点我想特别提醒想复刻的朋友不要一开始就追求把所有功能一次做齐先让心率显示跑通再单独调跌倒报警每个子模块都稳定了再考虑集成。我在集成阶段踩的坑八成以上是子模块单独没问题、合在一起才暴露出来的交互问题这也是嵌入式系统最有意思的地方。