
简介这套基于STM32的人体健康监测装置项目适合计算机、通信、自动化等相关专业学生作为毕业设计或课程设计参考。系统通过传感器检测体温与心率并利用MPU6050判断老人是否摔倒摔倒后可将告警信息发送给子女整体方案完整。资源共230个文件压缩包约29.34MB以C语言源码.c/.h、编译中间文件.o/.d/.crf及Keil工程配置为主同时提供烧录hex文件、说明文档与效果图片工程组织清晰便于直接打开进行二次修改。代码已经过调试测试答辩评审达到98分可直接运行学习也可在此基础上扩展报警、云端上报等功能。目前已有109人学习适合希望入手STM32综合项目或完善健康监测类毕业设计的读者下载使用。1. 这个STM32健康监测项目的骨架与设计取舍老人跌倒后如果长时间无人发现后果往往比跌倒本身更严重。这个项目没有做云平台、没有App却把最关键的检测链路做在了本地STM32F103系列主控负责读取MPU6050的加速度和角速度通过阈值判据识别摔倒动作同时采集体温和心率在本地屏幕上显示通过出厂默认串口透传把告警发出去。它适合三类人正在做嵌入式课程设计和毕业设计的学生想快速验证跌倒检测算法但不想从零写传感器驱动的工程师以及需要一个“能跑通但又不只是点灯”的STM32综合例程来练手的开发者。整个工程基于标准外设库SPL从stm32f10x_tim.c就能看出定时器用于采样节拍inv_mpu.c是MPU6050的DMP驱动lcd.c完成本地交互Flash则用来存历史事件。下面按硬件、算法、通信、调试的顺序把这个工程拆开讲。2. 硬件架构与模块选型传感器链路与引脚分配这个设计的主控选型是STM32F103系列面向健康监测场景它的ADC、I2C、USART和定时器资源够用外围电路也不需要特别处理。传感器端温度、心率和MPU6050来源不同必须分组供电和隔离信号。2.1 主控与传感器的选型理由STM32F103C8T6是零死角覆盖一切中低端健康监测方案的芯片。它有两个I2C一个用来挂MPU6050和数字温度传感器另一个可以挂心率模块或预留给OLED屏。心率采集通常用光电容积脉搏波PPG方案比如MAX30100/MAX30102通过I2C读出红外光和红光原始值如果工程资料里没有硬件I2C也可以用GPIO软件模拟项目里出现stm32f10x_tim.c说明定时器被用于控制采样周期和软件延时。温度传感器建议用单总线的DS18B20而不是热敏电阻。原因在于单个引脚就能读温度且精度在0.5摄氏度内老人体温监测场景不需要医疗级绝对值只要趋势不变态。要注意DS18B20每次转换时间是12bit精度下的750ms如果和心率采样共用定时器中断需要把温度转换放到主循环状态机里而不是中断里。MPU6050则通过I2C读取三轴加速度和角速度。它内部自带DMP数字运动处理器可以通过inv_mpu.c直接输出四元数省去大量解算工作。但跌倒检测主要依赖加速度幅值变化DMP的四元数反而有延迟所以这个项目直接用原始加速度数据算SVM向量模和姿态倾斜角而非DMP输出。2.2 硬件引脚映射表工程源码里stm32f10x_gpio.c和stm32f10x_usart.c会定义具体端口以典型F103C8T6核心板为例模块信号STM32引脚说明MPU6050SCL/SDAPB10/PB11I2C2外部4.7kΩ上拉MPU6050AD0GNDI2C地址为0x68DS18B20DQPA1开漏输出4.7kΩ上拉心率模块SCL/SDAPB6/PB7I2C1用于MAX30100LCD1602/OLEDRS/EN/D4-D7PA2~PA84线模式节省引脚SIM800A/ESP8266TX/RXPA9/PA10USART1用于告警发送调试串口TX/RXPA2/PA3USART2打印日志这个映射表不是死的。如果用的是一块集成核心板和扩展板注意检查PB10和PB11是否被板载其他器件占用。我一般建议把所有传感器供电分开VCC统一5V传感器板载3.3V稳压但MPU6050和心率模块必须共用同一个地不能浮地否则数据会出现周期性尖刺。2.3 供电与硬件连接调整电池供电时整机瞬时电流在GSM模块发短信时接近2A普通锂电池不够撑需要并联1000μF电解电容并在GSM模块电源端加一个10Ω/1W电阻做软启动。如果用ESP8266替代GSM模块电流尖峰小得多但需要额外拉高EN引脚。接线时优先保证重心低MPU6050尽量焊接在PCB中心而不是板边因为跌倒检测的加速度参考点是传感器自身远离重心会导致旋转时引入额外向心加速度动作幅度稍有变化就容易误判。如果这个项目只用一根杜邦线把MPU6050悬空那检测到的震动会比你真实跌倒剧烈得多调试时容易被误导。心率模块的佩戴方式比接线更关键。MAX30100这类光学传感器需要在相对静止的皮肤表面工作如果直接贴在衣服上红外光的反射率几乎为零。正确做法是让被测者食指轻轻按压模块表面避免压迫过度导致血流阻断。硬件说明里如果只提供了模块引脚没有提到这一点多半需要再加一段固定绑带否则测试时怎么调阈值都白搭。3. MPU6050跌倒检测算法与代码实现跌倒检测是这套装置的核心判断逻辑。MPU6050输出的原始加速度范围在±2g、±4g、±8g、±16g间可选原始量除以对应的灵敏度就能得到g值。项目里通常配置为±2g也就是16384 LSB/g分辨率足够检测人体跌倒动作。3.1 数据读取与坐标归一化inv_mpu.c里提供了运动中断和数据就绪中断的底层读取函数更直接的方式是从寄存器手动读取。标准外设库的第一个坑是MPU6050的寄存器地址是8位I2C一次最多写32字节读6字节加速度加6字节角速度必须分两次读。工程里如果沿用官方例程一般会封装一个MPU6050_Read_Accel()函数。读取代码如下void MPU6050_Read_Accel(short* ax, short* ay, short* az) { uint8_t buf[6]; MPU6050_I2C_ReadRegs(MPU6050_ACCEL_XOUT_H, buf, 6); *ax (int16_t)((buf[0] 8) | buf[1]); *ay (int16_t)((buf[2] 8) | buf[3]); *az (int16_t)((buf[4] 8) | buf[5]); }逻辑说明MPU6050_I2C_ReadRegs是从指定寄存器地址连续读取字节数组的函数。加速度输出寄存器地址MPU6050_ACCEL_XOUT_H为0x3B依次排列X、Y、Z的高低位。因为加速度可能为负所以用int16_t而不是uint16_t否则负加速度会变成两万多的大数整条判定逻辑全部失效。参数说明-2g到2g量程时敏感度为16384 LSB/g。静止平放时理论值应为ax0, ay0, az16384即1g。如果你的板子不是水平的az会略低ax或ay会出现非零值这是正常的不影响跌倒判定因为最终的判据是加速度模长而非单一轴。3.2 跌倒判定SVM冲击、姿态倾角、静置时长三级判定跌倒过程最典型的物理特征是先出现一个加速度突变身体撞击地面然后姿态发生明显翻转且持续一定时间保持静止。单纯的阈值比较会有两个问题一是弯腰捡东西也会让姿态角变化但不会产生撞击二是被人快速推一下会产生撞击但很快站起来不满足静置条件。因此采用三级判据加速度向量模长SVM超过冲击阈值撞击后的若干秒内躯干与水平面的倾斜角超过阈值该倾斜状态保持超过2秒。#define IMPACT_THRESHOLD 2.6f // 单位g大于此值认为发生剧烈撞击 #define TILT_THRESHOLD 40.0f // 单位度躯干与垂直轴的夹角 #define KEEP_TILT_TICKS 20 // 20 * 100ms 2s 静置时长 void Fall_Detect(float ax, float ay, float az, uint32_t tick_100ms) { static uint8_t impact_flag 0; static uint8_t tilt_counter 0; float svm sqrtf(ax*ax ay*ay az*az); float pitch atanf(ax / sqrtf(ay*ay az*az)) * 57.2958f; float roll atanf(ay / sqrtf(ax*ax az*az)) * 57.2958f; float tilt_angle fabsf(pitch) fabsf(roll) ? fabsf(pitch) : fabsf(roll); if (svm IMPACT_THRESHOLD) { impact_flag 1; tilt_counter 0; } if (impact_flag) { if (tilt_angle TILT_THRESHOLD) { tilt_counter; if (tilt_counter KEEP_TILT_TICKS) { Fall_Event_Trigger(); impact_flag 0; tilt_counter 0; } } else { impact_flag 0; tilt_counter 0; } } }逻辑说明先算SVM冲击再算俯仰角/横滚角中较大的那个作为倾斜角。impact_flag一旦置位后续只是检测倾斜和静置不再要求svm持续超过阈值。这很关键因为撞击后人体处于减速或反弹状态加速度可能瞬间接近0如果你用“svm连续超阈值”做判断永远等不来第二次触发。atanf需要调用数学库在Keil中勾选MicroLIB否则链接体积会增大且出现__aeabi_f2iz相关报错。参数说明IMPACT_THRESHOLD2.6g是经验值。站立静止时SVM为1g慢走时峰值约1.8g快跑约2.3g剧烈跌倒可超过3g。如果老人有帕金森或不自主抖动建议提高到2.8g以上。TILT_THRESHOLD取40度而不是90度是因为绝大多数跌倒场景身体不会完全平躺儿童跌倒会翻滚老人侧倒卡在家中扶手上可能只有45度。KEEP_TILT_TICKS按100ms一个调度周期算2秒足够区分“跌倒后意识清醒自己站起来”和“真的倒下起不来”。3.3 采样节拍与定时器配置检测函数不能放在main的死循环里裸奔因为DS18B20的750ms转换会卡住主循环导致跌倒检测超时。工程中用stm32f10x_tim.c配置一个100ms的时基中断每进一次中断把加速度换算成工程值放入全局结构体主循环里再跑Fall_Detect。void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); MPU6050_Read_Accel(ax_raw, ay_raw, az_raw); axg ax_raw / 16384.0f; ayg ay_raw / 16384.0f; azg az_raw / 16384.0f; get_ds18b20_temp(Cmd_ConvertT); // 非阻塞发起温度转换 } }这段代码里定时器中断只负责更新数据温度转换只“发起”不“等待结果”。主循环里查询温度转换标志位的设计能把传感器最慢的环节隔离开。注意MPU6050_Read_Accel用I2C读取需要耗时约2ms中断里占这么久对系统节律不太友好。更稳妥的做法是中断里置一个采样标志位主循环检测到标志后再读传感器配合外部中断引脚实现数据同步。3.4 温度和心率数据采集要点DS18B20单总线时序需要严格延时代码放在stm32f10x_gpio.c中自己翻转GPIO。初始化分三步复位脉冲、存在脉冲、写ROM指令。常见错误是在for循环里用空转delay编译器开优化后会把空循环优化掉导致时序完全乱掉。建议用定时器或SysTick提供的delay_us函数并加volatile修饰循环变量。心率模块如果是MAX30100I2C寄存器操作时不光要读FIFO数据还要先配置采样率、LED电流和ADC范围。代码中一个典型配置是MAX30100_WriteReg(MAX30100_SPO2_CONFIG, 0x07); // 50Hz采样率16bit ADC MAX30100_WriteReg(MAX30100_LED_CONFIG, 0xA0); // 红LED 11mAIR LED 11mA MAX30100_WriteReg(MAX30100_MODE_CONFIG, 0x03); // 血氧心率模式温度、心率、加速度这三类数据如果要在同一个时间线上对齐需要给每个传感器打时间戳。项目中如果没有RTC芯片就利用系统上电后的毫秒计时器作为时间戳事件告警信息里带一个相对时间例如“FALL at 233s”远程端看到也能判断事件顺序。4. LCD显示与GSM短信通知事件状态机的工程化跌倒检测不只是本地屏幕显示项目要求把摔倒信息发送给子女。发送的载体可以是GSM模块SIM800A也可以是ESP8266转WiFi。无论哪种方式核心是事件状态机的设计避免一次跌倒触发几十条短信。4.1 事件状态机定义状态机分为正常、撞击待确认、已确认告警、告警已发送四个状态。每个状态都有超时处理防止卡死。状态进入条件动作超时时间超时后NORMAL上电或恢复清标志位无无PRE_ALARMSVM超阈记录当前时间3秒回NORMALALARM倾斜持续点亮LED5秒回NORMALSENT发送成功记录Flash60秒回NORMAL当检测函数触发Fall_Event_Trigger()时状态机不等于立刻发短信而是先置PRE_ALARM。如果紧接着3秒内倾斜保持则置ALARM并发送如果只是撞击但人马上起来了3秒超时自动恢复。这个设计避免了“摔了一跤但拍下裤子站起来”时让人虚惊一场。4.2 LCD显示刷新策略LCD1602或OLED不能每100ms刷新一次频率过高会导致字符闪烁和串口干扰。合理做法是每秒刷新一次页面结合按键切换显示温度、心率、步数和状态。typedef enum { PAGE_TEMP, PAGE_HR, PAGE_STATUS } DisplayPage; void LCD_Refresh_Task(uint32_t now_ms) { static uint32_t last_refresh 0; char line_buf[17]; if (now_ms - last_refresh 1000) return; last_refresh now_ms; switch (current_page) { case PAGE_TEMP: snprintf(line_buf, sizeof(line_buf), Temp: %.1fC, temp_value); LCD_Line1(line_buf); snprintf(line_buf, sizeof(line_buf), HR: %d bpm, heart_rate); LCD_Line2(line_buf); break; case PAGE_STATUS: if (fall_alarm_flag) { LCD_Line1(** FALL **); LCD_Line2(SENDING ALERT); } else { LCD_Line1(MONITORING); LCD_Line2(SAFE); } break; } }逻辑说明last_refresh实现简单非阻塞节流Unix时间戳用毫秒差值做比较。snprintf限制缓冲区长度防止温度值超过LCD显示宽度时内存越界。LCD1602的写时序中使能信号高电平要保持至少40ns如果用的是GPIO模拟并口在EN1后加一个delay_us(1)再拉低即可。4.3 短信通知命令序列SIM800A通过USART1与STM32连接波特率115200。发短信的AT命令流程固定为ATCMGFA先发出去等待模块返回OK接着发送ATCMGS手机号再等一下返回最后发送正文和十六进制结束符0x1A。void GSM_Send_Alert(const char* phone_num, const char* msg) { char cmd[64]; snprintf(cmd, sizeof(cmd), ATCMGS\%s\\r\n, phone_num); UART1_SendString(cmd); while (!UART1_Receive_NoWait()) { ; } UART1_SendString(msg); UART1_SendByte(0x1A); // CtrlZ 结束符 UART1_Receive_Until(CMGS:, 10000); }这里有个细节GSM模块上电后需要约6秒搜索网络不能在主控上电后立刻发AT命令否则第一个AT会丢失。工程里一般用延时或查询模块返回寄存器的方式等待。发送失败时不能死等要设置超时并重发两次仍失败则把告警事件写入Flash等待网络恢复后下次开机再补发。4.4 用Flash保存告警历史资源列表里出现了stm32f10x_flash.c作用是掉电保存跌倒事件。STM32F103的内置Flash每页1KB写入前需要先擦除FLASH擦写寿命约1万次。告警频率不高足够使用。写入时要注意只能由高到低写0不能把1写回0所以每次追加事件都重新读出一整页、修改、擦除再写回。typedef struct __attribute__((packed)) { uint32_t timestamp; float temp; uint16_t heart_rate; uint8_t type; // 0跌倒, 1心率异常, 2温度异常 } HealthEvent_t; void Flash_SaveEvent(const HealthEvent_t* evt) { uint32_t addr 0x0801F800; // 最后一页 FLASH_Unlock(); while (FLASH_GetFlagStatus(FLASH_FLAG_BSY) ! RESET) { ; } FLASH_ErasePage(addr); for (int i 0; i sizeof(HealthEvent_t) / 2; i) { uint16_t w ((uint16_t*)evt)[i]; FLASH_ProgramHalfWord(addr i * 2, w); } FLASH_Lock(); }这里的packed确保结构体内存布局按1字节对齐否则在ARM默认4字节对齐下sizeof会有间隙写入Flash后读出会错位。Flash地址选在片上代码尾部之前的空闲页实际地址需要看工程里代码段大小不能随便写0x0801F800否则会覆盖代码。5. 调试与校准陀螺仪漂移、心率噪声与Keil避坑很多使用者拿到这个源码跑起来发现两个现象一是在桌子上轻轻敲一下板子就报跌倒二是心率数值在 30 和 180 之间乱跳。这两个问题都不是主控坏了而是数据没有被校准和滤波就把参数喂给了判定逻辑。5.1 加速度零偏与陀螺仪漂移MPU6050出厂有每颗芯片的零偏误差平均值在±50mg。虽然加速度计不像陀螺仪那样持续积分但零偏会影响静止时SVM的大小。如果某轴零偏是-40mg静止SVM是1.0g加减0.04g在阈值2.6g时不影响冲击判断但会在跌倒后姿态倾角计算中引入3度到5度误差。早期调试建议先采集100组静止数据的均值作为补偿值。陀螺仪的漂移主要影响姿态角长时间解算。本项目的倾斜角直接用加速度比算不受陀螺仪漂移影响但如果你在代码里融合了陀螺仪的积分必须加入高通滤波或定期重置。使用互补滤波时陀螺仪权重一般设0.98加速度计权重0.02权重比取决于你更信任哪个传感器。静止场景下加速度计更稳动态场景下陀螺仪更稳取0.98只是通用起步点。5.2 心率信号的运动伪差PPG心率传感器对运动极其敏感手指轻轻一动就会产生与心跳频率接近的伪峰。常规思路是加巴特沃斯低通滤波原理是用心率的频谱范围是0.8Hz到3.3Hz48~198BPM滤掉高频抖动。除了滤波还必须在代码里加一个信号质量检查发现红外值波动范围超过1秒内平均值的30%直接丢弃该段数据返回前一次有效心率。int16_t IR_ADC(void) { static int16_t last_val 0; static uint16_t skip_count 0; uint16_t raw Read_IR_ADC(); if (abs((int32_t)raw - last_val) (last_val * 0.3f)) { skip_count; return last_val; } skip_count 0; last_val raw; return raw; }这段代码是过滤毛刺不能替代真正的数字滤波器。测试时用示波器观察I2C读到的FFT或者直接串口打印原始序列在Excel里查看曲线。如果原始曲线里基线上下浮动幅度大于峰值的20%说明信号源接触不良此时怎么加滤波都没用。5.3 Keil工程配置常见报错下载资源里的Healthy.uvguix.15399这类文件是Keil的GUI布局配置不是工程文件本身。真正需要打开的是.uvprojx或者旧版本的.uvproj。换电脑打开时常见的报错是找不到器件需要双击右下角的魔术棒在Device标签里切换到STM32F103C8或者打开STM32F10x_StdPeriph_Driver的宏定义检查。编译链接时报Error: L6218E: Undefined symbol inv_mpu_initialize大多是没把inv_mpu.c和inv_motion_driver.c加入工程。标准外设库工程还容易撞上__disable_irq未定义原因是启动文件选择的是带浮点中断保护的芯片版本F103没有FPU启动文件要选择startup_stm32f10x_hd.s而非带_fp后缀的文件。调试时要在Keil里开启仿真下载但使用ST-Link烧录时如果调试器连接不稳定最容易出现“No target connected”。新款ST-Link需要固件升级到V2.1以上USB线要插主板的USB2.0接口而不是USB3.0转接器很多接触不良问题来自劣质转接线与代码无关。5.4 复现工程时的验证清单把样例工程烧录后不要直接调阈值按下面的顺序逐项验证串口打印原始加速度值板子水平静止时输出接近0接近016384如果你读到az8192说明重置化已经动了量程不是±2g平放板子上升高10cm自然掉落SVM最大值应超过2.5g如果不到说明掉落的加速度被板子缓冲吸收把板子竖起来靠在墙上倾斜角应显示约90度如果显示45度检查atanf参数有没有和g转换后的值一起算数据波动剧烈且伴有周期性的尖峰检查I2C总线上拉电阻和杜邦线长度超过20cm必加10k上拉。这一套验证做完再跑跌倒测试才可能有效果。跳过这步直接调阈值只会不断改变阈值然后发现行为毫无逻辑。6. 从毕设到产品低功耗看门狗与远程上报的进阶技巧项目跑通后距离可用还有三个障碍系统死机后无人重启、电池坚持不了一天、子女收不到消息也不知道设备是否还活着。工程上最常见的是加看门狗、降功耗、以及心跳包。交给用户的产品板必须在主循环里喂狗我一般配置独立看门狗IWDG超时4秒在主循环每个周期末尾喂狗。如果某个传感器卡死导致主循环超过4秒系统自动重启重启后根据Flash告警标志决定是否补发短信。补发逻辑要限制次数防止陷入无限重启发短信的循环。功耗方面最耗电的是GSM模块和心率传感器。GSM模块在空闲时进入睡眠模式有效电流可降到1mA以下但需要串口发送ATCSCLK1且该引脚拉低。更实用的降低功耗技巧是把MLX90614这类数字温度传感器的转换周期从1Hz降到0.2Hz心率模块以100ms时间内完成一次采样后立刻关掉LEDMPU6050进入睡眠模式并靠运动中断唤醒。void LOWPOWER_Mode_Enter(void) { GSM_SendCmd(ATCSCLK1); MPU6050_Set_SleepMode(1); HAL_DBGMCU_EnableDBGStandbyMode(); // 低功耗调试兼容 __WFI(); // 等待唤醒 }这段代码示意性的写法是主循环没有事件时进入STANDBY模式心率与加速度的阈值变化通过外部中断唤醒STM32。注意在Keil在线调试时直接开WFI会丢失仿真连接所以调试阶段要把__WFI()用宏封起来。低功耗调通后整机平均电流能控制在50mA以内用1500mAh锂电池才能支撑一天一夜。远程上报不是必修项但如果你想把告警发给子女手机却不想用GSM短信费可以用ESP8266加一个MQTT协议。做法很简单用USART3与ESP8266通信发送ATCIPSTART建立TCP连接然后MQTT的CONNECT报文与PUBLISH报文用协议栈拼装。STM32上资源少不用移植整个MQTT库只需手工构建固定报文。心跳包每60秒发一次到服务器服务器超过180秒没收到就标记设备离线子女App能显示“设备已离线”避免设备坏了还没人知道。最后说一个实用性技巧跌倒检测的最终测试不要只在开发板上测把整个装置装在一个布袋里系到胸口然后做前倒、侧倒、坐倒在软垫三个动作。前倒的SVM冲击最大侧倒姿态角变化最明显坐倒是复合动作。如果这三个动作都能正确触发告警并且日常走路、弯腰捡钥匙不误触那阈值就基本调好了。本文还有配套的精品资源点击获取