简介:压缩包内含一套基于STM32的老人摔倒报警装置工程,面向嵌入式开发学习者与电子设计竞赛备赛者,用于实现独居老人意外跌倒时的短信即时提醒。项目以STM32为主控,结合SIM900A模块完成短信发送,代码涵盖外设初始化、跌倒检测逻辑与通信处理等部分,便于理解STM32 GPIO、定时器、串口等基础应用。压缩包共339个文件,以C源码(76个.c、68个.h)为主,并包含Keil工程配置、原理图与PCB设计文档,以及hex、axf、uvprojx等可直接编译下载的文件,整体大小约14.93MB。已有1848人学习/下载。对于希望快速上手STM32项目开发、研究GSM模块驱动或作为课程设计参考的读者而言,这套资料提供了可直接编译验证的工程和较完整的文件结构,能帮助理解软硬件协同设计,缩短从原理学习到实际调试的路径,并可基于现有结构与模块,进一步扩展远程监护、语音提醒等功能。
1. 从“跌倒检测”到“短信报警”:这个装置到底在解决什么问题
独居老人摔倒后没法按求救按钮,是这类设计最初的出发点。基于STM32老人摔倒报警装置(短信提醒)这个项目,硬件上其实就是三块:一个检测姿态和加速度的传感器、一块负责判断和控制的STM32、一个能发短信的GSM模块。真正花时间的不是把三个模块连通,而是把“摔倒”这个动作量化成算法,再把误报率压到可以接受的水平。这篇文章不按说明书讲,按我实际做这套方案时的思路走:先选型,然后读数据、定阈值、发短信,最后讲几个现场调试时才容易踩的坑。
2. 系统整体架构与硬件选型:STM32、MPU6050与SIM800C的分工
作为一类典型物联网感知终端,这个装置的信号流很清晰:MPU6050把人体运动转为数字量,STM32读取后做姿态解算和跌倒判断,一旦判定为跌倒,就通过串口向GSM模块下发AT指令,最终以短信形式把位置或求救信息送到预设手机。三个环节边界分明,选型和工作量都集中在第一、二两个环节上。
2.1 传感器选型为什么用 MPU6050 而不是单一加速度计
市面上的跌倒检测方案里,有人用ADXL345这种纯加速度计,也有人用MPU6050这种六轴传感器。我一般推荐直接用MPU6050,原因不只是它能同时输出加速度和角速度,更重要的是它内置了DMP运动处理器,可以直接输出四元数,再转成姿态角就省掉了一大段姿态解算代码。对于一块主频72MHz的STM32F103来说,CPU余量更充裕,留给短信模块和本地报警逻辑去跑。
传感器供电接3.3V,SDA和SCL上各加一颗4.7kΩ上拉电阻到3.3V。很多成品模块上已经自带,但如果是自己画板子,这两颗电阻漏了会导致I2C偶尔读不出数据。初始化时注意把MPU6050的电源管理寄存器0x6B写0,才能从睡眠模式唤醒,否则读到的加速度全是0。
2.2 GSM 模块选型与供电约束:短信提醒和本地报警不是一回事
很多第一次接触这个项目的人会忽略一个关键点:GSM模块工作时的瞬时功耗非常高。SIM800C在发短信或搜网时峰值电流可以到2A,普通AMS1117这类LDO根本扛不住,输出电压会被拉低,然后MCU复位。正确做法是给GSM模块单独一路电源,通常用一颗支持3.7V/4V输出的稳压器或DC-DC,并在模块电源脚附近并一颗2200μF电解电容和一颗100nF瓷片电容。
如果只是做功能验证,也可以直接用两节18650锂电池供电,电池正极分别走两路:一路经LDO降到3.3V给STM32和传感器,另一路直接给SIM800C供电。注意SIM800C的RXD/TXD电平是2.8V,虽然多数情况下和3.3V的STM32串口直连也能用,但稳妥起见还是加一颗电平转换芯片或者电阻分压。
2.3 最小电路的引脚分配与初始化顺序
用一块STM32F103C8T6小系统板就能跑通这个项目。I2C1固定用PB6/PB7,USART1用PA9/PA10接SIM800C,再留一个GPIO控制蜂鸣器做本地报警。引脚分配建议提前画成表格,接线时才不会乱。
| 功能 | STM32引脚 | 外设模块 | 说明 |
|---|---|---|---|
| I2C1_SCL | PB6 | MPU6050 | 接4.7kΩ上拉到3.3V |
| I2C1_SDA | PB7 | MPU6050 | 接4.7kΩ上拉到3.3V |
| USART1_TX | PA9 | SIM800C RXD | 模块端电平2.8V |
| USART1_RX | PA10 | SIM800C TXD | 模块端电平2.8V |
| 蜂鸣器控制 | PA1 | 有源蜂鸣器 | 低电平触发或高电平触发取决于模块 |
| 复位按键 | NRST | 手动复位 | 用于固件和模块恢复 |
初始化顺序会影响调试效率。我的做法是:先初始化串口,再初始化I2C并读MPU6050,然后初始化GPIO控制蜂鸣器,最后初始化GSM模块并等待网络注册。如果把GSM模块放在最前面,它开机搜网需要十几秒,这段时间里传感器数据已经准备好,反而能尽早发现问题。
3. 用 STM32 读 MPU6050:I2C 配置、数据补采与滤波
这一章要解决“数据怎么进到MCU”的问题。STM32标准库里I2C外设的配置代码比较啰嗦,但逻辑固定,照着写一次后面所有I2C传感器都能套用。这里以Keil5环境下新建的STM32标准库工程为例,时钟配置为72MHz主频。
3.1 I2C 初始化与 MPU6050 寄存器配置
先配置GPIO和I2C外设,再对MPU6050的寄存器做写入。代码如下:
void MPU6050_Init(void) { // 1. 使能GPIOB和I2C1时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); // 2. PB6=SCL, PB7=SDA,配置为开漏复用输出 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); // 3. I2C1主模式,时钟400KHz I2C_InitTypeDef I2C_InitStructure; I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; I2C_InitStructure.I2C_ClockSpeed = 400000; I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_Init(I2C1, &I2C_InitStructure); I2C_Cmd(I2C1, ENABLE); // 4. 唤醒MPU6050并配置量程 MPU6050_WriteReg(0x6B, 0x00); // PWR_MGMT_1,退出睡眠 MPU6050_WriteReg(0x19, 0x07); // SMPLRT_DIV,输出速率=1kHz/(7+1)=125Hz MPU6050_WriteReg(0x1A, 0x06); // CONFIG,数字低通滤波42Hz MPU6050_WriteReg(0x1B, 0x18); // GYRO_CONFIG,陀螺仪量程±2000°/s MPU6050_WriteReg(0x1C, 0x10); // ACCEL_CONFIG,加速度量程±8g }这里几个寄存器是关键:0x19的SMPLRT_DIV配合数字低通滤波器,决定了数据更新频率和噪声抑制能力。对本项目来说,125Hz的输出速率足够捕捉摔倒时几百毫秒内的冲击,噪声也不大。加速度量程选±8g,是为了避免摔倒瞬间的冲击把数据削顶,如果设成±2g,很多跌倒的峰值会被限幅,后续判断很容易漏报。
3.2 读取加速度与角速度并做滑动平均滤波
读取代码并不复杂,从0x3B开始连续读6个字节,得到三轴加速度原始值;再从0x43开始读6个字节,得到三轴角速度原始值。因为MPU6050的寄存器地址会自动递增,单次连续读取效率更高。
typedef struct { float ax, ay, az; // 加速度,单位g float gx, gy, gz; // 角速度,单位°/s } MPU6050_Data; void MPU6050_ReadData(MPU6050_Data *data) { uint8_t buf[6]; int16_t raw; // 读取加速度寄存器0x3B开始,共6字节 MPU6050_ReadRegs(0x3B, buf, 6); raw = (buf[0] << 8) | buf[1]; >int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }然后在主循环里打印:
printf("ax=%.2f ay=%.2f az=%.2f gx=%.2f gy=%.2f gz=%.2f\r\n", data.ax, data.ay, data.az, data.gx, data.gy, data.gz);波特率设115200,同时让被测者做站立、走动、坐下、模拟摔倒四组动作。重点看摔倒瞬间三轴加速度的合成值和角速度的峰值,下一章会用这些数据去设计阈值。
4. 摔倒判断阈值怎么定:合加速度、姿态角与状态机
跌倒检测的算法有很多,从简单的阈值到机器学习都有。作为一个依靠STM32独立运行、还带短信提醒的装置,算力有限,而且必须保证响应时间在秒级以内。实际工程里最常用、也最容易被低估的是“阈值+状态机”方案,它把算法拆成几个可量化的阶段,每个参数都能对着实测数据调。
4.1 跌倒时数据的两个特征:瞬时冲击加持续静止
人摔倒时,身体的加速度会先出现一个明显的短时冲击,随后进入相对静止状态。单纯看冲击峰值会产生大量误报,比如猛拍桌子、剧烈挥手都可能让合加速度超过2g。真正可靠的判据是两个条件的组合:短时间内合加速度超过冲击阈值,然后在一段时间内保持接近静止,同时姿态角相对初始状态发生明显翻转。
合加速度的计算公式是:
float GetAccVector(MPU6050_Data *data) { return sqrtf(data->ax *>typedef enum { STATE_IDLE, STATE_IMPACT, STATE_DOWN } FallState; float IMPACT_THRESHOLD = 2.5f; // 冲击阈值,单位g float STATIC_THRESHOLD = 0.8f; // 静止判断阈值,单位g uint16_t IMPACT_HOLD_TIME = 5; // 冲击状态保持采样次数 uint16_t STATIC_HOLD_TIME = 15; // 静止状态保持采样次数,约125Hz*0.12s FallState state = STATE_IDLE; uint16_t impact_count = 0; uint16_t static_count = 0; uint8_t CheckFall(MPU6050_Data *data) { float acc = GetAccVector(data); switch (state) { case STATE_IDLE: if (acc > IMPACT_THRESHOLD) { state = STATE_IMPACT; impact_count = 1; } break; case STATE_IMPACT: if (impact_count++ >= IMPACT_HOLD_TIME) { state = STATE_DOWN; static_count = 0; } else if (acc < 1.5f) { impact_count = 0; state = STATE_IDLE; } break; case STATE_DOWN: if (acc < STATIC_THRESHOLD) { if (++static_count >= STATIC_HOLD_TIME) { state = STATE_IDLE; return 1; // 判定摔倒,触发报警 } } else { static_count = 0; } break; } return 0; }主要逻辑是:IDLE状态下检测到超过2.5g的冲击,进入IMPACT;连续5个采样点内如果合加速度没有回落到1.5g以下,就认为确实出现了“摔倒冲击”;随后进入DOWN态,在15个采样点中持续检测到低于0.8g的静止状态,才最终判定摔倒。这两个阈值决定了装置既不会漏报,也不太会把坐下、躺下这类动作判成摔倒。
IMPACT_HOLD_TIME和STATIC_HOLD_TIME都是采样点个数,如果你把MPU6050的采样率从125Hz改成更高或更低,这两个值必须跟着改,否则时间窗会变。代码里我直接用采样点计数而不是时间,就是为了减少一把系统频率改了、算法时间也跟着变的低级错误。
4.3 误报场景的区分:用姿态角过滤弯腰和下蹲
弯腰捡东西、下蹲系鞋带这些动作,合加速度也可能短暂超过2g,但它们和摔倒有个明显区别:摔倒后身体姿态会发生不可逆的翻转,而弯腰和下蹲结束时会主动恢复直立。所以状态机里还需要加入姿态角判断。
用加速度计算出Pitch和Roll角:
float pitch = atan2f(data->ax, sqrtf(data->ay *>AT ATE0 AT+CPIN? AT+CREG? AT+CMGF=1AT用来检测模块是否应答,返回OK表示串口通信正常。ATE0关闭回显,避免后面处理串口数据时把收到的指令原文误判为模块响应。AT+CPIN?返回READY说明SIM卡没密码,返回SIM PIN说明要输入密码。AT+CREG?返回+CREG: 0,1才表示注册到了网络,这里是0,1,第一个0是上报模式,第二个1表示注册成功。最后AT+CMGF=1把短信设为文本模式,返回OK后就可以发短信了。
5.2 文本模式发送短信的完整 C 代码
短信发送的流程是:发AT+CMGS="手机号码",等待模块返回“>”提示符,然后发送短信内容,最后发0x1A作为结束符。不能用固定延时硬等,必须靠接收中断判断收到的字符。
uint8_t sim800_rx_buf[64]; uint8_t sim800_rx_len = 0; volatile uint8_t got_prompt = 0; void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE)) { uint8_t ch = USART_ReceiveData(USART1); if (sim800_rx_len < sizeof(sim800_rx_buf)) { sim800_rx_buf[sim800_rx_len++] = ch; } if (ch == '>') { got_prompt = 1; } } } uint8_t SendSMS(const char *phone, const char *msg) { char cmd[64]; sprintf(cmd, "AT+CMGS=\"%s\"\r\n", phone); UART_SendString(cmd); uint16_t wait = 0; while (!got_prompt && wait++ < 500) { DelayMs(10); // 最多等5秒 } if (!got_prompt) { return 0; } UART_SendString((char *)msg); UART_SendByte(0x1A); // Ctrl+Z结束短信内容 return 1; }代码逻辑并不复杂,真正容易踩坑的是等待“>”提示。如果模块还没返回提示符就直接发送内容,短信内容会被当成AT指令的一部分而发送失败。另外,发完0x1A后不要立刻执行其他AT指令,应该再等待模块返回OK或+CMGS: <序号>,确认运营商接受后再继续。GSM模块响应慢是常态,整个流程要给足超时时间。
5.3 提醒触发的电路联动:蜂鸣器、LED 与重发机制
检测到摔倒后,单片机的任务不只是发短信,还应该同时驱动本地报警蜂鸣器,让周围人能听到。触发逻辑是用一个GPIO拉高或拉低,驱动蜂鸣器周期性鸣叫;同时LED快速闪烁,指示短信发送状态。发送成功就常亮,失败就闪烁。
短信会存在偶尔发送失败的情况,比如网络繁忙、SIM卡欠费或者信号弱。在工程上要给重发机制,但要注意避免无限重发把电池耗尽。我的做法是:首次发送失败后隔10秒重发,最多重发3次;每次重发前都重新检查AT+CREG?,确认模块还处于注册状态。重发逻辑放到主循环里做,每次发送流程已经耗时几秒,再用DelayMs阻塞会让整个系统失去响应,不合适。
这里还要提一个设计细节:短信内容最好带上时间戳和姿态信息。如果装置配备了GPS模块,就把经纬度拼进去;没有GPS就用摔倒是的加速度值,家属收到短信后至少能知道发生了异常,而不是只收到一句意义不明确的“Help”。
6. 现场联调时的几个常见坑与验证技巧
系统联调最有用的一个手段是分层验证。先单独验证GSM模块,再单独验证MPU6050,最后组合起来跑完整流程。任何一步失败,都能立刻缩小问题范围。
- 串口回环测试:接线完成后先把SIM800C的TXD断开,短接PA9和PA10,用串口助手发什么收什么,确认STM32串口和USB转串口都正常。然后再接上模块,手动发AT,确认模块端接线和电平适配没问题。
- 用WHO_AM_I寄存器验证I2C通信:读MPU6050的0x75寄存器,正常返回0x68。如果读到0xFF,检查上拉电阻和SDA/SCL是否接反;如果返回0x00,检查电源和复位脚。
- 模拟摔倒时的实际姿势:不要把人直接硬摔到地板或者床上进行测试,容易受伤,先把传感器贴在布偶或者软垫上推进。更接近真实场景的做法是让测试者从站立状态缓慢跪倒到瑜伽垫上,这个动作的加速度数据和人摔倒很接近,但更安全。
- 独立看门狗:GSM模块初始化时卡死是常见故障,SIM800C不开机或者注册超时都会导致主循环阻塞。开启IWDG,溢出时间设为2秒,在每个状态机分支里喂狗。这样就算模块不响应,单片机也会自动复位。但发送短信过程中不能喂狗太频繁,防止复位导致短信中断。
- 阈值不是一次就能调准的:先按常态动作连续测试20次,记录所有误报和漏报场景,再回头改IMPACT_THRESHOLD。误报多就往大调,漏报多就往小调。每次只改一个参数,同时记录采样率、滤波窗口和动作类型,方便回溯。
验证短信链路时,不要用真实手机号反复测试,运营商对同一号码高频发送会触发风控,导致短信延迟甚至被拦截。用一个专门用于测试的SIM卡,发送间隔保持在30秒以上。短信发送成功后,再到AT+RST复位模块,验证整个系统断电重启后能否自动完成初始化,这是交付前最容易被跳过又最关键的一步。
本文还有配套的精品资源,点击获取