基于STM32和MPU6050的跌倒检测系统设计与实现

📅 发布时间:2026/8/31 20:13:09
基于STM32和MPU6050的跌倒检测系统设计与实现 简介本资源是一套面向嵌入式初学者与物联网实践者的老年人防跌倒报警系统完整工程基于STM32F103系列微控制器与MPU6050六轴姿态传感器实现跌倒状态实时识别与声光报警。项目聚焦居家养老安全场景融合I2C通信、传感器数据滤波、姿态解算与阈值判断等典型嵌入式开发技能适合课程设计、毕业设计及智能硬件入门实战。压缩包共89个文件含42个头文件.h定义外设驱动与算法接口、38个源文件.c覆盖主控逻辑、OLED显示、MPU6050驱动、DMP初始化及定时器中断处理等核心模块另有hex可执行文件、Keil工程配置.uvprojx、调试脚本keilkill.bat及固件库STM32F10x_FWLib总大小仅407KB结构清晰、模块解耦度高。已有919人学习下载提供即编译即运行的完整软硬件协同方案包含从传感器采集、姿态角计算到跌倒触发蜂鸣报警的全流程实现是掌握STM32惯性传感应用的高实用性参考工程。 摔倒是老年人群体里最要命的安全隐患之一而且往往不是摔那一下本身有多严重真正致命的是摔倒后没人及时发现错过了最佳救助时间。我这两年一直在做智能穿戴相关的项目这套基于STM32和MPU6050的跌倒检测方案前前后后改了四个版本从最早的“能触发报警”一路做到“误报率能接受、家人敢用”的程度。这篇文章就把从硬件搭建、姿态解算到跌倒判断算法的完整过程拆开讲如果你正准备做健康监护类项目或者手里正好有这两颗芯片想练手这篇内容可以直接照着做。这套方案的核心思路不复杂用MPU6050六轴传感器采集人体运动数据STM32负责读取和运算通过合加速度突变加姿态角度变化来判断是否发生跌倒一旦确认就驱动蜂鸣器报警。但“能跑”和“能用”之间隔着大量细节比如阈值怎么定、状态机怎么切、误报怎么压、数据怎么滤波这些我会结合我的实际踩坑经历一条条说清楚。1. 方案设计与硬件选型思路1.1 为什么选STM32加MPU6050这个组合先说传感器。跌倒检测领域有人用单加速度计、有人用加速度加气压计、有人直接上毫米波雷达但MPU6050至今仍是性价比最高、资料最全、最适合入门的选择。它内部集成了三轴加速度计和三轴陀螺仪能同时输出六个维度的原始数据加速度计负责感知重力方向和线性加速度变化陀螺仪负责感知旋转角速度两者结合正好能覆盖“跌倒”这个动作的两个关键特征——身体发生剧烈加速、身体姿态发生明显翻转。STM32作为主控就更不用多说Cortex-M3内核的F103系列在淘宝上十几块钱就能买到最小系统板性能完全够用官方HAL库把I2C、串口、定时器这些外设封装得比较完善开发效率远高于纯寄存器操作。而且STM32的生态积累非常厚网上随便一搜就是大把现成的MPU6050驱动遇到问题基本都能找到解决方案。1.2 检测逻辑的初步建模在动手写代码之前我建议你先想清楚一个问题跌倒到底怎么定义我自己的建模思路是这样的。正常站立或行走时人体合加速度保持在1g附近g为重力加速度约9.8m/s²身体倾角在0到30度之间变化。跌倒过程可以拆成三个阶段先是身体失重下坠加速度短暂降到1g以下然后身体与地面或障碍物碰撞出现一个明显的高加速度冲击峰值幅度往往超过2.5g甚至3g最后人体躺倒身体倾角接近90度并保持相对静止。所以检测算法要抓的就是这三个特征失重、冲击、静止倾斜。只抓冲击峰值容易误报比如老人猛一下坐进沙发里也会出现峰值但坐完姿态还是接近垂直的只抓倾角也不行因为老人躺床上休息时倾角同样很大。三者结合误报率才能压下来。1.3 硬件清单与接线方式这是我最终定型的一版硬件配置STM32F103C8T6最小系统板MPU6050模块带稳压和上拉电阻的常见GY-521模块即可有源蜂鸣器模块低电平触发0.96寸OLED屏幕I2C接口调试和显示状态用一个按键用于解除报警3.7V锂电池加一个AMS1117-3.3降压模块接线如下注意I2C总线的上拉电阻在GY-521模块上已经集成不需要额外加。模块引脚STM32引脚MPU6050VCC3.3VMPU6050GNDGNDMPU6050SCLPB6I2C1_SCLMPU6050SDAPB7I2C1_SDAMPU6050AD0GNDI2C地址0x68OLEDVCC/GND/SCL/SDA与MPU6050并联到同一I2C总线蜂鸣器I/OPA0按键一端接PA1另一端接GNDPA1AD0接地时I2C器件地址是0x68接高电平是0x69这个后面驱动代码里会用到别搞错。2. 工程搭建与MPU6050数据读取2.1 基于HAL库快速搭建STM32工程现在的开发方式我强烈建议直接用STM32CubeMX生成HAL库工程不要再用标准库手写寄存器了。CubeMX里选好STM32F103C8T6芯片配置RCC时把HSE设为Crystal/Ceramic ResonatorSYS里Debug选Serial Wire保留调试口I2C1设为标准模式100kHzUSART1打开用于打印调试信息。GPIO方面要把PA0和PA1配置为GPIO_Output和GPIO_Input注意PA1会用作按键读取需要设置为上拉输入这样按键按下接地时读到的是低电平。工程生成后在main.c里把I2C1句柄和串口句柄的声明找出来后面驱动代码要用。CubeMX生成工程不是终点有几个细节得注意。I2C速率不要一上来就拉到400kHzMPU6050虽然支持高速模式但飞线连接时信号完整性没保障100kHz最稳。另外在SystemClock_Config里确认系统时钟确实跑到了72MHz很多莫名其妙的问题其实都源自时钟配置不对。2.2 MPU6050寄存器配置与初始化流程MPU6050的驱动本质上就是通过I2C读写一组寄存器核心就那么几个。初始化顺序我踩过坑之后固定成了这样先读WHO_AM_I寄存器确认设备在线地址是0x75读到的值应为0x68。如果读出来是全0或者0xFF先别急着查代码八成是接线问题。确认设备在线后向PWR_MGMT_10x6B写入0x00唤醒芯片并选择内部时钟源。然后配置SMPLRT_DIV0x19为0x07设置采样率为1kHz除以(17)即125Hz。接着配置CONFIG0x1A写入0x04把DLPF低通滤波器设为21Hz带宽可以有效滤掉高频抖动和电源噪声。量程配置是接下来的重点。GYRO_CONFIG0x1B写入0x08把陀螺仪量程设为±500°/sACCEL_CONFIG0x1C写入0x00加速度计量程设为±2g。这里选±2g是有讲究的跌倒冲击峰值虽然可能到3g以上但±2g量程下加速度计的分辨率最高达16384 LSB/g测量精度最好。稍后检测算法里我会用2.5g作为冲击阈值±2g量程下读到的原始值会饱和在32767正好对应2g不会影响判断。初始化的完整代码如下#define MPU6050_ADDR (0x68 1) #define MPU6050_WHO_AM_I 0x75 #define MPU6050_PWR_MGMT_1 0x6B #define MPU6050_SMPLRT_DIV 0x19 #define MPU6050_CONFIG 0x1A #define MPU6050_GYRO_CONFIG 0x1B #define MPU6050_ACCEL_CONFIG 0x1C HAL_StatusTypeDef MPU6050_WriteReg(uint8_t reg, uint8_t data) { return HAL_I2C_Mem_Write(hi2c1, MPU6050_ADDR, reg, I2C_MEMADD_SIZE_8BIT, data, 1, 100); } HAL_StatusTypeDef MPU6050_ReadRegs(uint8_t reg, uint8_t *buf, uint8_t len) { return HAL_I2C_Mem_Read(hi2c1, MPU6050_ADDR, reg, I2C_MEMADD_SIZE_8BIT, buf, len, 100); } uint8_t MPU6050_Init(void) { uint8_t id 0; MPU6050_ReadRegs(MPU6050_WHO_AM_I, id, 1); if (id ! 0x68) return 0; // 设备异常 MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x00); MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x07); MPU6050_WriteReg(MPU6050_CONFIG, 0x04); MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x08); MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x00); return 1; }MPU6050_ReadRegs这个函数后面读取六轴原始数据时也会复用从0x3B寄存器开始连续读14个字节一次性就能拿到加速度和陀螺仪的全部数据。2.3 原始数据读取与单位换算MPU6050的输出寄存器是16位有符号数高字节在前。加速度数据从0x3B到0x40共6个字节陀螺仪从0x43到0x48共6个字节。读取后需要做单位换算加速度原始值除以16384得到单位为g的加速度陀螺仪原始值除以65.5±500°/s量程下的灵敏度得到单位为度/秒的角速度代码实现如下typedef struct { float ax, ay, az; // 单位g float gx, gy, gz; // 单位°/s } IMU_Data_t; IMU_Data_t imu; void MPU6050_ReadData(void) { uint8_t buf[14]; MPU6050_ReadRegs(0x3B, buf, 14); int16_t ax_raw (buf[0] 8) | buf[1]; int16_t ay_raw (buf[2] 8) | buf[3]; int16_t az_raw (buf[4] 8) | buf[5]; int16_t gx_raw (buf[8] 8) | buf[9]; int16_t gy_raw (buf[10] 8) | buf[11]; int16_t gz_raw (buf[12] 8) | buf[13]; imu.ax ax_raw / 16384.0f; imu.ay ay_raw / 16384.0f; imu.az az_raw / 16384.0f; imu.gx gx_raw / 65.5f; imu.gy gy_raw / 65.5f; imu.gz gz_raw / 65.5f; }这里有个细节连着0x3B到0x48一起读14个字节比单独读加速度和陀螺仪两次更高效。I2C每发起一次传输都有起始停止条件频繁切换容易出问题一次读完是更稳妥的做法。3. 姿态解算从原始数据到可信角度3.1 为什么不能直接拿原始数据判断跌倒刚开始做这个项目时我特别天真直接把合加速度超过阈值就触发报警结果实验现场惨不忍睹——走路颠一下报警弯腰捡东西报警甚至用力跺了一下脚也报警。原因在于加速度计测量的是“比力”它分不清重力分量和运动加速度分量。老人正常走路时脚落地会产生明显的冲击加速度这个分量的方向和大小都在快速变化直接拿合加速度做阈值判断本质上是在拿一个非常嘈杂的信号做判断误报是必然的。所以正确做法是把传感器数据分成两条处理线一条算合加速度抓冲击事件另一条算姿态角度判断身体是否躺倒。姿态角不能直接用加速度计的原始值算需要一个解算过程把六轴数据融合起来。3.2 加速度计与陀螺仪的互补关系姿态解算的经典方案有互补滤波、Mahony算法和EKF在STM32F103这种主频不高的芯片上一阶互补滤波是性价比最高的选择。原理说穿了很简单。加速度计在静止时能准确测出重力方向由此可以算出roll和pitch角但动态下噪声很大稍微一晃角度值就剧烈跳动。陀螺仪积分得到的角度在短时间内非常平滑准确但积分会随时间漂移时间越长误差越大。互补滤波的思想就是相信陀螺仪的短期变化用加速度计长期校正漂移两者各取所长。从加速度计算姿态角的公式float acc_roll atan2f(imu.ay, imu.az) * 180.0f / PI; float acc_pitch atan2f(-imu.ax, sqrtf(imu.ay * imu.ay imu.az * imu.az)) * 180.0f / PI;atan2f函数能自动处理象限问题比atan好用得多这算是我写姿态解算踩坑后的一个心得。3.3 一阶互补滤波的代码实现互补滤波的公式长这样float roll, pitch; float dt 0.008f; // 125Hz采样率对应周期 void Attitude_Update(void) { float acc_roll atan2f(imu.ay, imu.az) * 180.0f / PI; float acc_pitch atan2f(-imu.ax, sqrtf(imu.ay * imu.ay imu.az * imu.az)) * 180.0f / PI; roll 0.98f * (roll imu.gx * dt) 0.02f * acc_roll; pitch 0.98f * (pitch imu.gy * dt) 0.02f * acc_pitch; }这里的两个系数0.98和0.02是关键。0.98是陀螺仪权重0.02是加速度计权重两者相加等于1。权重怎么理解可以把它看成一份信任分配——你更信任短期积分的结果陀螺仪但也留了一小部分信任给长期无漂移的参考加速度计。系数0.02意味着一旦加速度计算出的角度和积分结果出现偏差大约每50个采样周期会拉回来一点既不会让动态响应变迟钝又能有效抑制陀螺仪零漂。这个dt必须和实际采样周期严格对应。我在125Hz采样率下应该用0.008秒但最开始偷懒写了个固定0.01秒结果姿态角一直缓慢朝一个方向偏找了很久才发现是采样率没对上。建议在定时器中断里推进数据读取和解算确保采样周期精确。4. 跌倒检测算法状态机设计与阈值整定4.1 合加速度的计算与物理含义有了滤波后的姿态角接下来要回到合加速度。合加速度的计算很简单float magnitude sqrtf(imu.ax * imu.ax imu.ay * imu.ay imu.az * imu.az);静止时合加速度约等于1g运动时会在1g上下波动。跌倒时身体撞击地面合加速度会在极短时间内超过2.5g甚至3g这个特征非常明显。但要强调一点合加速度对方向不敏感它只反映加速度大小的变化无法区分“身体砸向地面”和“快速抬手”这类动作——所以它只能作为触发条件不能作为唯一的判据。4.2 三阶段状态机设计我把检测过程设计成一个三状态状态机每个状态对应跌倒过程的一个阶段这样比单纯两个阈值判断要可靠得多。typedef enum { STATE_NORMAL 0, STATE_IMPACT, STATE_TILT_CHECK } Fall_State_t; Fall_State_t fall_state STATE_NORMAL; uint32_t impact_time 0;STATE_NORMAL是待机状态程序持续监控合加速度。一旦合加速度超过冲击阈值状态切换到STATE_IMPACT同时记录时间戳。进入STATE_IMPACT后系统在1.5秒内检查两个条件一是合加速度是否回落到正常范围1.3g以下说明身体已经停止运动二是滤波后的倾角是否超过50度说明身体处于接近水平的状态。两个条件同时满足判定为跌倒确认触发报警。如果1.5秒内条件不满足状态机复位回STATE_NORMAL等待下一次触发。状态机代码void Fall_Detect_Update(void) { float mag sqrtf(imu.ax * imu.ax imu.ay * imu.ay imu.az * imu.az); float tilt fabsf(roll) fabsf(pitch) ? fabsf(roll) : fabsf(pitch); switch (fall_state) { case STATE_NORMAL: if (mag 2.5f) { fall_state STATE_IMPACT; impact_time HAL_GetTick(); } break; case STATE_IMPACT: if (HAL_GetTick() - impact_time 1500) { fall_state STATE_NORMAL; // 超时回到待机 } else if (mag 1.3f tilt 50.0f) { fall_state STATE_NORMAL; Fall_Alarm_Trigger(); // 确认跌倒触发报警 } break; } }这个状态机的精髓在于把“瞬间的冲击”和“持续的姿态变化”组合在一起。单独看任何一个条件都可能误报但组合起来后必须同时满足“撞了一下”和“躺下了”两个条件才算数。我实测下来误报率能从单纯阈值判断的八成以上降到了两成以下。4.3 阈值整定的实验方法与参考值阈值不能拍脑袋定我用串口把合加速度和倾角以一定的格式打印出来让动作测试人员做一组动作记录不同动作下的数值范围再确定阈值。我整理了一份参考数据动作合加速度峰值最大倾角状态机结果正常站立约1g小于10度不报警正常行走1.2g到2.0g小于30度不报警快速坐下2.0g到2.4g约15度不报警倾角不够弯腰捡东西1.5g到1.8g约60度不报警冲击不够仰面跌倒2.8g到3.5g大于80度报警侧面跌倒2.5g到3.0g大于80度报警这几个数值是我在状态机配合下测出来的。冲击阈值设在2.5g倾角阈值设在50度超时窗口设在1.5秒。如果你的佩戴位置或者测试人群有差异这些值需要重新标定不要直接抄。4.4 报警执行与误报抑制机制报警触发后蜂鸣器以1Hz频率鸣叫同时OLED屏幕显示“FALL DETECTED”和当前的倾角数据。如果老人在摔倒后还能活动按下按键可以解除报警。如果1分钟内没人解除报警强度提升蜂鸣器切换为持续鸣叫并在扩展方案中通过GSM或WiFi模块把报警信息发送给家属手机。这里有个容易被忽略的细节报警触发后不要立刻恢复检测。老人摔倒后往往需要一段时间才能站起来或者等待救援如果传感器一直检测到“躺着”的状态就会反复触发报警造成干扰。我加了一个30秒的报警锁定期报警触发后30秒内忽略新的事件30秒后自动恢复到待机状态。5. 整机调试与常见问题排查5.1 数据漂移与随机跳变问题调试中遇到最多的问题就是MPU6050数据漂移。表现为传感器静止不动但角度一直在缓慢变化或者读数突然跳到一个离谱的值。漂移分两种。一种是陀螺仪零漂静止时陀螺仪输出不是一个固定的0而是一个很小的非零值积分后角度会缓缓朝一个方向偏。解决方法是上电后先静止采集100组陀螺仪数据取平均作为零偏值之后每次读取都减去这个零偏。这是必须做的不做的话互补滤波的长期稳定性无从谈起。另一种是尖峰跳变某一次读数突然异常高原因通常是I2C时序不稳定或者电源纹波干扰。排查先从供电入手用示波器看3.3V电源轨如果纹波超过100mV就需要在电源引脚旁边加一个10uF和0.1uF的电容。改善I2C线缆布局也很重要杜邦线越短越好尽量用屏蔽线或者干脆把传感器通过PCB排针直连。5.2 误报漏报的参数调节手法误报率高的时候先不要急着改大冲击阈值容易把真报警也滤掉了。正确思路是先用串口记录误报场景的数据比如老人弯腰捡东西时合加速度到底到了多少、倾角到了多少再去对应调节。我碰到过一种典型的误报场景是老人坐到较矮的椅子上冲击峰值到了2.4g倾角因为身体微微后仰达到了40多度两个值都接近阈值但没触顶。这时候把倾角阈值从50度提高到55度误报就消失了同时不影响真实的跌倒检测因为真跌倒是铁定会超过80度的。漏报的情况则完全不同。如果合加速度一直达不到2.5g的阈值通常是因为传感器佩戴位置太松或者身体撞到柔软物体缓冲掉了冲击。除了提醒佩戴者固定好设备外我建议把状态机的超时窗口从1.5秒放宽到2秒这样能抓住更多缓冲时间较长的跌倒类型。5.3 I2C通信卡死与复位恢复HAL库的I2C在从机无响应时可能会卡在HAL_I2C_Mem_Read里不返回表现是整个系统像死机了一样。这个问题在飞线环境下尤其常见。我的处理方法是给I2C通信加上超时和重试机制主循环里每次通信前先用HAL_I2C_IsDeviceReady探测设备如果连续5次失败就认为传感器掉线重新调用初始化函数。实测下来这个办法能解决九成以上的通信卡死问题。补充一个和本文主题无关但STM32开发必踩的坑如果你同时在用ST-Link调试配置GPIO时别把SWDIO和SWCLK这两个引脚复用到其他功能上否则调试器会直接失联处理起来非常头疼。我在CubeMX中把SYS的Debug设为Serial Wire就是为了保留调试口。5.4 低功耗与续航优化思路做可穿戴设备就绕不开功耗问题。我这版方案连续运行电流在50mA左右用500mAh锂电池大约能撑10个小时作为原型验证够用了但离真正的产品还有距离。省电可以从三方面入手。第一采样率不用全程拉满正常状态下用50Hz足够当检测到合加速度超过1.8g时再动态提升到200Hz把高采样率留给最需要的高动态阶段。第二传感器休眠MPU6050支持低功耗模式在判断老人处于静止状态超过5分钟后可以让主控进入STOP模式仅保留RTC定时唤醒每秒钟醒来一次采样几张数据确认没有异常再继续休眠。第三OLED不要常亮只在报警和调试时点亮这个屏幕在满亮度时能吃掉将近30mA电流省下来非常可观。6. 模拟测试与真实场景的差距写完代码后我搭建了一套简单的跌落测试平台来做模拟验证。方法是在地面上铺一块20厘米厚的海绵垫测试者将一个内置了这套系统的腰包绑在腰部然后从站立姿势向海绵垫倒下重复仰面倒、侧面倒、趴着倒三种姿势各10次再用正常走路、跑步、坐下、弯腰捡东西四个日常动作各做20次统计报警准确度。测试下来仰面和侧面跌倒的检出率达到100%俯面倒因为姿态角接近90度但冲击峰值稍低有一两次没触发后来把冲击阈值从2.5g降到2.3g后问题解决。日常动作的误报率大约在15%主要集中在快速坐下的动作上我针对坐姿做了倾角上限的判断最终把误报率压到了10%以内。对于一套采用M3内核加六轴传感器的原型方案来说这个指标可以接受但距离真正的商用产品还有不小的距离商用方案还需要加入气压计数据来辅助判断高度变化。这套系统后续可以扩展的方向很多。最实用的是加一个ESP8266模块通过MQTT协议把报警信息推到微信或钉钉家人无论在哪里都能第一时间收到通知也可以在报警触发后自动拨打预设电话这需要外接一个SIM800C模块。数据采集方面可以用SD卡模块记录老人的活动轨迹和姿态变化趋势长期积累后还能分析出平衡能力的衰退趋势提前发现跌倒风险。如果你也想做类似的项目我的建议是先把姿态解算和状态机跑通再一步步加功能不要一开始就把系统搞得太复杂单片机的调试复杂度是随着功能数量指数级上升的。本文还有配套的精品资源点击获取