STM32老人监护系统开发实战:心率血氧、跌倒检测与远程告警
发布时间:2026/9/6 16:33:08 作者:尧图编辑部 阅读量:1,286

简介面向嵌入式、物联网及智能硬件学习者的一份完整老人监护系统设计文档以STM32F103RCT6为主控整合SIM800C、GPS、MPU6050、MAX30102等模块覆盖脉搏心率、体温监测跌倒检测、定位跟踪与OneNet云平台远程监护等典型场景。资源为单个PDF文档压缩包约29.6MB内容按设计需求、硬件选型、云平台部署、STM32代码设计四大部分展开详细分析了三轴加速度传感器检测摔倒的原理以及SIM800C、GPS、MAX30102等芯片的工作机制与调试要点并给出完整Keil工程实现思路。目前已有174人学习适合正在开展嵌入式课程设计、毕业设计或智能穿戴产品预研的读者参考。通过这份材料可以快速建立老人监护系统的整体框架认知减少硬件选型和代码调试阶段的时间成本。1. 为什么想给独居老人做一套监护系统需求倒推设计目标说实话做这个项目的起因很现实。家里老人独居白天我们去上班晚上才回家。以前总觉得老人身体还行但有一次邻居打电话说老人在楼道里坐了半天起不来问怎么回事老人说就是觉得晕想坐一会儿。话是轻飘飘的可那段时间我心里一直在想如果当时他直接倒地了谁会知道手机在口袋里他自己能拿得出来吗基于STM32做一套老人监护系统本质上就是把这个没人知道的空窗期用嵌入式方案填上。站在毕业设计或者个人项目的角度来看这类题目的技术覆盖度也非常友好底层要配置GPIO、定时器、I2C、ADC、串口上层要处理传感器协议、数据滤波、状态判断再加上WiFi通信、实时操作系统一条链捋下来STM32开发里最重要的知识点几乎全过了一遍。这也是为什么STM32相关的毕业设计题目里老人监护、智能穿戴、健康监测总是排在前列——不是题目没新意是它确确实实能锻炼完整的产品思维。在设计目标上我没有一上来就追求全功能、高端化。跟几个做过类似方案的朋友聊过之后结合自己踩坑的感受我把需求收敛成了下面几条能实时测量心率、血氧饱和度这是最基本的人体体征参数能检测跌倒动作并且在识别到疑似跌倒时主动询问/告警提供一键SOS紧急求救用户主动触发要最可靠异常情况下能远程通知家人不能只在本地响铃设备要便携、可充电数据至少能够脱离手机独立工作。这几条需求看着简单真正做起来每条都不省心。心率血氧牵扯到模拟前端和信号处理跌倒检测牵扯到姿态解算和误报抑制远程通知又牵扯到网络模块的功耗和稳定性。后面我会按模块拆开讲把从选型到调试的完整链路都过一遍包括那些文档里查不到、只有上手跑过才会遇到的坑。2. 硬件选型里的思路主控、传感器和通信模块怎么搭才合理2.1 主控为什么选STM32F103C8T6而不是更小的芯片主控选的是STM32F103C8T6。这颗芯片在如今看来不算新但作为监护系统的主控它的平衡性非常好72MHz主频足够跑传感器数据解算和通信协议栈64KB Flash在Keil5工程里不用精打细算地挤空间20KB SRAM跑FreeRTOS任务也不会太紧张1个I2C、1个USART、几个定时器管脚位分布合理能把外设很舒服地分配开。有人可能会问用STM32L431之类更低功耗的芯片不更好包括我自己最初也纠结过。但实际对比后会发现L系列的低功耗优势要靠仔细配置多个低功耗模式才能发挥而F103在正常运行时功耗也没到不可接受的程度。对初版功能验证来说F103的资料密度、例程数量、调试工具兼容性都有明显优势。项目重点是先把逻辑跑通功耗优化我放在了第二版迭代里后面会专门讲。2.2 传感器选型MAX30102和MPU6050是性价比组合体征采集部分用了MAX30102。这颗芯片把红光LED、红外光LED和光电二极管集成在一起通过I2C直接输出经过AD转换的PPG光电容积脉搏波原始数据省去了前端模拟电路的设计。如果用分立元件搭光电容积脉搏波采集电路光是运放选型、滤波电路调试就够折腾好几周MAX30102的存在让项目的重心回到算法和系统上这是用芯片换时间的一个典型做法。姿态检测用的是MPU6050内置三轴加速度计和三轴陀螺仪。跌倒检测主要靠加速度计的数据来判断冲击和姿态变化陀螺仪则用于计算姿态角变化两个配合能判断出人是直立摔倒还是弯腰下蹲之类的非跌倒动作。MPU6050在平衡小车、手势识别这些项目里被用烂了资料极多驱动代码也好改对开发效率很友好。通信模块我留了两个方案位。室内场景下首选ESP8266因为它便宜、SDK成熟、串口透传方便如果后续要做室外场景可以把通信换成4G Cat.1模块比如Air724UG或者EC200S代码层面抽象出统一的发送接口即可。初版先用WiFi打通远程告警链路这点很关键。2.3 人机交互与电源方案人机交互部分包括一块0.96寸OLED屏、两个物理按键、一颗有源蜂鸣器和一颗震动马达。OLED用来显示当前心率、血氧值、告警状态物理按键一个是SOS急按键一个是复位/确认键蜂鸣器和震动马达负责本地告警实测下来震动马达在嘈杂环境下的提醒效果比蜂鸣器好老人放在口袋里也能感觉明显。电源采用3.7V锂电池方案电池经过TP4056充电模块充电再通过RT9193-3.3V稳压芯片给整个系统供电。刚开始我用AMS1117做稳压但空载电流偏大对电池供电的设备不友好。RT9193的静态电流只有微安级别而且输出电压纹波小给MAX30102这种模拟测量芯片供电更合适。系统休眠的时候整板电流能压到几百微安日常佩戴充电一次可以撑一天以上。3. 心率血氧采集从MAX30102的寄存器到滤波和数值解算3.1 理解PPG信号才能读懂MAX30102的输出先讲一点背景原理。MAX30102内部的两个LED交替点亮光打到皮肤上后一部分被血液吸收一部分反射回来被光电二极管接收。心脏收缩时血管内血容量增加吸收的光变多反射光就变弱心脏舒张时相反。光电二极管输出的光强信号经过跨阻放大和ADC采样就得到了随时间波动的PPG波形。血氧饱和度则是利用含氧血红蛋白和脱氧血红蛋白对红光660nm与红外光940nm吸收率的差异来计算的这是脉搏血氧仪的基本原理。MAX30102的驱动配置看起来简单实际上有几个关键参数直接影响数据质量。LED电流我调到了20mA左右脉冲宽度设成最宽的4096微秒ADC量程选2048。这个组合的好处是宽脉冲让每个采样点积累更多光信号信噪比更高20mA电流在指夹或腕戴场景下既不会太暗导致信号幅度过小也不会因为电流太大把功耗和发热带上去。采样率设置在100Hz对人体的脉搏频带来说足够了平均心率、呼吸干扰都不需要太高的采样率。3.2 I2C读取和FIFO的使用方式MAX30102内部有FIFO缓冲可以把多个采样点暂存在芯片里再一次性读走。代码上非常适合用定时器主循环配合DMA或者轮询I2C读取。我实际的读取流程是一个定时器以10ms为周期触发软件标志位主循环检测到标志位后通过I2C从FIFO寄存器读出两个通道的数据红光和红外光各占3个字节转换成32位整数存在数组里。这里有个细节值得提一下MAX30102的FIFO深度是32个样本如果读得不及时数据会被新数据覆盖。所以读取FIFO的周期不要比采样周期大太多。我直接用100Hz的采样率配10ms的读取周期每次读出来正好是一到两个新样本。实测在标准库和HAL库两种环境下都跑过HAL库的I2C因为中间有多层抽象如果中间打断次数多偶尔会丢样本后来改成用DMA方式读FIFO基本没有再出现掉数的情况。3.3 心率解算滑动平均加阈值检测比FFT更好用从原始PPG信号到心率值大多数人第一反应是上FFT做频谱分析。但FFT在资源受限的MCU上做需要维护较大的计算缓冲而且窗口长度短了频率分辨率不够。我实际试过之后最终采用的是时域方案先做滑动平均滤波去掉高频噪声再用一个简单的一阶高通滤波截止频率约0.5Hz去除基线漂移然后通过自适应阈值法检测脉搏波的峰值计算相邻两个峰的时间间隔换算得到心率值。自适应阈值的思路是维护一个滑动窗口取窗口中信号最大值的70%作为当前检测阈值。当信号从下向上穿过阈值时记录一个峰值点。两次峰值之间的时间差取倒数再乘以60就得到当前的心率。血氧饱和度采用的是经验查表法计算红光和红外光交流分量与直流分量的比值R再代入血氧芯片数据手册中的标准曲线用分段线性插值拟合出SpO2值。这套算法在静止状态下误差可以控制在±2%以内已经能满足日常监护的精度要求。3.4 实操里最容易忽视的信号质量问题调试阶段最让我头疼的不是算法而是信号质量。MAX30102的数据手册会告诉你芯片支持红血氧检测但它不会告诉你戴得不紧、环境光太强、手指晃动时数据会直接变成一堆毛刺。后来我总结了几条实用的排查方法用手指肚按压传感器指尖变白的瞬间如果看不到清晰的脉搏波说明光路没有对准或LED电流太小用黑色海绵遮光袋包住手指和传感器对比遮光前后波形如果遮光后波形幅度变化很大说明环境光干扰严重需要加强结构遮光手指保持静止时波形幅度明显增大稍有晃动幅度就掉一半这种情况下算出的心率值会忽高忽低算法上必须识别信号质量差值信号质量差的时候宁可不上报数据也不能报错数据。关于上不上报错误数据这件事我后来专门做了逻辑保护实时计算两个相邻峰值的间隔如果间隔差异超过30%就判定当前信号不可信心率值显示--直到连续出现5个以上稳定的峰值间隔才恢复显示。这个机制在用户翻身、活动肢体时能有效避免心率值乱跳实际使用体验好很多。4. 跌倒检测的逻辑加速度波形分析加姿态判断4.1 跌倒的波形特征和检测难点跌倒检测是这类监护系统里误报率最高、最容易被用户吐槽的模块。我一开始以为只要检测到合加速度突变就能判定跌倒后来用MPU6050做了一组真实模拟实验包括从站着倒地、从椅子上滑落、快速坐下、弯腰捡东西、原地跳了跳才发现单纯用阈值判断完全不可靠。快速坐下和跳一下同样会产生超过3g的加速度尖峰如果按阈值直接报警系统基本上会一天误报几十次。所以我把跌倒检测拆成了三个阶段来识别第一个是失重阶段人体从站立到失重倒地合加速度会先下降低于0.6g并且持续至少100ms第二个是撞击阶段身体接触地面瞬间合加速度产生一个至少2.5g到3g的尖峰持续时间在30ms到200ms之间第三个是静止阶段倒地后身体保持不动加速度回到1g附近姿态角从接近垂直变成接近水平。4.2 合加速度计算与姿态角获取合加速度的计算很简单三个轴加速度的平方和开根号。由于MPU6050输出的是数字加速度值单位被配置成±2g量程取出的原始值除以16384就得到以g为单位的加速度。姿态角方面我用的是加速度计静态倾角公式pitch atan2(accel_y, sqrt(accel_x^2 accel_z^2)) * 180 / PI roll atan2(-accel_x, sqrt(accel_y^2 accel_z^2)) * 180 / PI为什么要用这个公式而不是直接取某个轴的数值因为装在手环或者口袋里的设备穿戴方向不固定直接用单轴判断是否水平非常不靠谱。用双轴反正切算出的姿态角在设备任意旋转90度的情况下都能稳定反映出人体躯干相对地面的角度。设计上要求可穿戴设备在任意角度佩戴都能使用这是必须过的坎。4.3 三阶段判定算法和误报抑制三阶段判定在FreeRTOS里是作为一个独立任务跑的每50ms读一次MPU6050并更新状态机。还有一个好用的小技巧每1分钟滑动存储最近50秒的姿态角均值当发生疑似跌倒时把当前姿态角与均值对比如果角度差小于30度说明这人本来就躺着大概率是翻身或者起身动作不是跌倒。确认疑似跌倒后系统不会立刻发告警而是进入一段10秒的预告警模式蜂鸣器鸣叫、屏幕上显示检测到跌倒按键取消报警。如果用户在这10秒内按下确认键说明自己没事告警取消如果10秒没有取消系统自动判定为严重跌倒触发远程告警并附带当前GPS位置如果接了定位模块。这个10秒倒计时可取消机制比我最初设计的检测即报警好太多误报率在实际测试里从原来的每周十几次降到了差不多两周一次。4.4 实际测试数据说明问题我整理了一组室内模拟测试的记录每次动作重复20遍统计检出结果测试动作检出次数未检出次数误报情况从站立位直接倒地191无从椅子上滑落200无快速坐下1检出191次弯腰捡东西020无原地跳跃2检出182次躺下翻身1检出191次从数据里能明显看到漫游心跳、下蹲、跳跃这类动作依然会有一定比例的误报这是当前算法方案的天花板。如果想进一步压低误报率可以往里面加陀螺仪的姿态角变化率判断或者在撞击阶段之后加入一个波形恢复时间的特征。但从可靠报警的角度看现在这个方案已经能解决大多数真实跌倒场景后续算法迭代可以作为独立优化点继续深化。5. 远程告警链路ESP8266加MQTT把消息送到手机上5.1 通信方案为什么选MQTT而不是TCP长连接远程告警是整个系统离用户最近、也是价值最直观的部分。告警消息必须推送到家人的手机上而且延迟要尽量低。这里我选了MQTT协议通过ESP8266 Wi-Fi模块连到云服务器。有人会不解STM32直接通过AT指令发个TCP连接不也能把数据传出去吗为什么要绕一层MQTT原因就在于TCP长连接需要自己维护保活、重连、消息确认这些机制而MQTT发布订阅模型天然支持一对多推送家里人用微信小程序订阅设备的告警主题设备只需要发布一条消息所有订阅者都能收到。阿里云物联网平台、EMQX这类公共MQTT Broker都提供免费的开发档位注册一个设备三元组就能用省去自己搭服务器的麻烦。我用的云平台是阿里云IoT的公共实例免费额度对个人项目绰绰有余。5.2 数据帧结构与发送流程设备发送给云端的数据格式用JSON字段包括设备ID、当前心率、血氧、电池电量、告警类型sos/fall、时间戳。因为MCU内存有限JSON字符串不能做得太大我裁掉了不必要的空格整个包的字符串长度控制在220字节以内放在一个256字节的缓冲数组里发送完立即清空。// 告警消息JSON结构示例 {dev:A001,hr:85,spo2:97,bat:78,alarm:fall,ts:1720000000}发送流程设计成三级防抖第一级是本地蜂鸣器和屏幕提示第二级是MQTT发布告警主题第三级是把告警记录存在STM32内部Flash里下次设备收发电时重新上报。这个三级备份设计是我从一次WiFi断开导致的丢事件中总结出来的。当时 ESP8266断网重连花了差不多一分多钟恰好那一次真正的SOS请求没有发出去我意识到所有数据只走网络链路是不可靠的必须在设备端留一份底。5.3 断线重连与电量优化ESP8266作为WiFi客户端最大问题在于断线后不会主动恢复需要通过AT固件的事件上报机制来感知连接断开然后重新发起TCP连接、重新订阅主题。这个逻辑名叫自动恢复保活机制我实现的时候用一个软件定时器每30秒检查一次连接状态不通就自动走重连流程。只要WiFi信号不是弱到-85dBm以下实测重连成功概率很高。WiFi全时在线是这类设备功耗的最大杀手。我实测过MAX30102加MPU6050全速运行加上ESP8266保持常连电池电流平均在120mA到150mA之间一块500mAh电池不到4小时就空了这显然没法用。后来优化的策略是ESP8266平时Power Down心跳数据每30秒通过串口发送完就休眠只有检测到异常告警时才唤醒并建立连接。这样日常模式平均电流降到30mA左右异常模式下才走全功率链路。牺牲了一点数据实时上传的平滑度换来了接近十倍的续航提升这笔买卖非常划算。6. 软件架构FreeRTOS任务划分与模块解耦6.1 多任务划分的依据系统软件采用FreeRTOS管理原因很直接这套方案里同时存在传感器读取、算法判断、显示刷新、按键扫描、WiFi通信、Flash存储多个需要并行处理的环节。如果用裸机轮询逻辑写主循环会被I2C读取的阻塞延时占满按键响应和显示刷新都会变得卡顿。任务划分成这样传感器采集任务100Hz读取MAX30102和MPU6050原始数据算法处理任务20Hz运行PPG滤波、心率血氧计算、跌倒检测状态机显示任务5Hz刷新OLED屏幕更新心率血氧值和图标状态网络任务1Hz处理MQTT收发、心跳保活、断线重连按键与告警任务10Hz扫描SOS和取消键控制蜂鸣器与震动马达。6.2 任务间通信用消息队列代替全局变量任务之间传数据用的是FreeRTOS消息队列而不是到处定义全局变量。全局变量的问题在调试多任务代码时特别明显循环缓冲被多个任务同时写容易产生数据错位和难复现的隐性BUG。消息队列的方式相当于把数据流按管道隔离生产者只管往队列里丢消费者只在队列非空时才取不会出现两个任务同时访问同一块内存的情况。传感采集任务和算法处理任务之间用的是两个队列一条通道传PPG原始波形数据一条通道传MPU6050的姿态数据。队列深度的设置也需要考虑我最后定的是每个队列长度64每项4字节FIFO读出的样本如果短时间没被取走队列会暂存而不是立刻丢降低了高优先级任务被打断时数据缺失的概率。实测在系统负载最高的异常处理场景下队列也不会满没有出现因为队列满导致丢失关键传感器数据的现象。6.3 按键消抖与中断处理的细节SOS按键的可靠性直接关乎生命安全这里不能简单用延迟消抖的常规做法。我的方案是按键触发外部中断中断里只做一件事把一个软件定时器的启动时间重置。定时器超时0.15秒后在任务上下文中做一次完整的电平确认多重确认通过后才判定为一次有效按键。这么做既避免了延迟消抖带来的误触发又不会在中断里做太多事影响其他实时逻辑。7. 实测表现与调试经验哪些坑值得记下来7.1 整体运行效果整套系统做完后我戴着原型机进行了大约三天的日常测试包括做饭、打扫、快走、午睡这几个场景。心率显示在静息状态下与小米手环对比差值在3次/分以内血氧在静止状态下与医用指夹式血氧仪对比误差在1%以内SOS按键响应时间小于1秒跌倒检测模拟测试的识别率约95%误报集中在快速坐下的场景。这个成绩作为毕业设计或者个人项目交差完全够用但离商用产品的准确度和鲁棒性还有差距。7.2 MAX30102数据异常排查的三板斧很多卡在起步阶段的人会问为什么我的MAX30102读数全是0xFFFFFFFF这类问题九成出在I2C通信和芯片配置上。我按经验总结了一个排查顺序确认I2C地址是否冲突。MAX30102的7位地址是0x57如果总线上挂了其他I2C器件需要通过地址区分避免地址冲突导致读写错乱检查I2C上拉电阻一般4.7k到10k都可以。如果上拉电阻太大或太小波形边沿会变差数据就读不回来尤其当I2C线路稍微长一点的时候更明显初始化顺序很重要。要严格按照数据手册的流程先写模式配置寄存器再配置LED电流最后使能FIFO写入。顺序错了会出现在某个状态一直读不到有效数据的情况。7.3 电池电量显示的水分还有一个容易被忽略的坑是电量显示。原计划用ADC直接读电池电压再换算成电量百分比后来发现锂电池的电压和剩余电量并不是线性关系。3.7V到4.2V之间前半段电压掉得慢后半段掉得很快直接线性映射的电量会有将近20%的误差。后来我在网上找了一张比较通用的锂电池电压-容量曲线表用分段线性查表替代了一刀切的线性换算实测误差缩小到5%以内。这个小改动人不多但对每天看电量显示的老年用户来说准确的电量显示就是安全感。7.4 结构设计上的一点建议软件和硬件都完成之后剩下的问题就是怎么装进壳子。我建议用3D打印机打印一个带卡扣的手环外壳传感器部分用透明窗口让光路尽量直射皮肤。感应面不要紧贴外壳表面内部留大概1到2毫米的悬空间隙否则手指按压时受力不均会造成局部透光影响信号采集质量。OLED屏直接嵌在壳体表面朝外方便用户查看。把排线全部用热熔胶固定在壳体内壁避免晃动引起排线虚接这一条对于裸露测试板状态的阶段特别有用。8. 从毕业设计到可用产品还要补哪些功课做到现在这个程度原型机已经能稳定监体征、识别跌倒、发远程告警自己也真实用小半个月。不过如果想让它真正适合给老人长期使用还有几层功课要补。功耗方面当前WiFi方案只适合在有电源环境的家里使用出门就没网。要真正做成户外可用通信方案必须换成4G Cat.1另外STM32的睡眠模式要细分平时除了传感器轮询主控尽量进Stop模式把平均电流压到10mA级别配上1000mAh以上电池才能实现一周一充。算法方面心率血氧在用户转身、说话、抬手时依然会有短时抖动跌倒检测也还没做到老人真实的摔倒数据学习。这些场景的边界性能要进一步提升靠的是大量真实佩戴数据来调试模型参数已经属于数据驱动的范畴了。硬件层面锂电池充电保护电路、电源和传感器铺地隔离、EMC测试这些量产必须考虑的事情原型机上还没有系统做。如果要将这套方案继续推进到实际产品阶段硬件的可靠性是第一优先级这可能比算法迭代更花时间。不过从学习、毕业设计、个人项目的角度衡量基于STM32的这套老人监护系统已经把从硬件到云端全链路打通了。对我来说最大的收获不是那一堆能跑的代码而是习惯了从真实用户场景出发倒推技术方案这个思考方式。在把心率血氧的调试阈值一个个试、把跌倒误报率一点点降下来的过程里你真的能感受到工程实践和课本例题之间的差别——课本告诉你什么是对的踩坑会告诉你什么是足够好。这套系统的调优空间还有很多但作为一个起点它已经给了我继续往深做的理由。本文还有配套的精品资源点击获取