简介:本资源是一套完整的基于STM32的老人摔倒报警装置毕业设计项目,面向电子信息、自动化、嵌入式系统等专业的本科生,解决居家养老场景下老年人突发摔倒后及时报警与定位的核心需求,适用于毕业设计、课程设计及期末大作业等实践环节。压缩包共338个文件,涵盖76个C源码文件(含传感器数据采集、姿态判断、GSM通信等核心逻辑)、68个头文件(定义硬件接口与算法参数)、48个编译中间文件(.o/.d/.crf)及Keil工程配置文件(.uvprojx/.uvoptx)、原理图(.schdoc)与PCB设计(.prjpcb)、Hex可执行镜像、PDF论文及Word文档等,整体大小为14.91MB。已有162人学习下载,项目经助教审定、本地实机编译验证通过,评审得分98分,配套论文结构规范、代码模块清晰、注释完整,并包含SIM900A通信调试、MPU6050姿态解算、阈值自适应判断等关键实现细节,便于快速理解与二次开发。 去年带的一位学弟把“基于STM32的老人摔倒报警装置”做成了一款带短信通知和GPS定位的完整作品,答辩时直接拿了优。这类项目属于嵌入式领域里比较典型的“传感器采集+算法判断+多模报警”组合,工作量适中、技术点覆盖广,而且实用价值一眼就能看懂,所以一直是毕业设计里的热门选题。这篇就把我拆解这个项目时的完整思路、硬件选型逻辑、核心代码实现和踩坑记录一起整理出来,给准备做类似系统或者正在为毕设发愁的同学一个可参考的底稿。
1. 项目整体设计与思路拆解
1.1 原始需求到底在解决什么问题
做这个项目之前,先别急着打开Keil写代码。要把需求拆透:老人摔倒报警装置,核心不是“报警”本身,而是“摔倒”这件事能不能被准确、及时地识别出来。如果整天误报,家人会把设备关掉;如果漏报,那这个设备就失去了存在意义。所以这个项目的灵魂在检测算法,而不在LED闪烁或者蜂鸣器响不响。
拆开看,项目包含几个核心环节:
- 姿态数据采集:用什么传感器、什么通信接口把老人的身体姿态变成数字量。
- 摔倒识别算法:如何从加速度、角速度数据里判断“这是摔倒”而不是“弯腰捡东西”或者“坐下”。
- 报警响应机制:检测到摔倒后,怎么让本地和远程的人知道,包括现场声光报警、短信通知、电话呼叫、GPS位置上传。
- 系统可靠性与续航:老人设备不能一天一充,也不能关键时刻死机。
从功能模块看,这就是一个典型的嵌入式数据采集与处理系统,同时带无线通信外设。把它作为毕设,恰好覆盖了嵌入式开发的核心技能树——GPIO、定时器、中断、I2C/SPI/UART、ADC、低功耗管理、状态机编程,这些都是面试时能拿出来讲的东西。
1.2 为什么主控选了STM32而不是51、Arduino或ESP32
选型这部分我在帮学弟改方案时讨论得最多。很多人上来就选STM32F103C8T6,理由是“教程多”“大家都用”,这个理由没错,但不够充分。真正的原因是:这个项目需要同时挂传感器、GSM模块、GPS模块、按键、OLED屏,还要跑一个不算太简单的检测算法,51单片机资源太紧张,Arduino虽然开发快但不利于体现“工程能力”,ESP32虽然自带WiFi但它的优势在这个项目里用不上,反而增加了功耗和复杂度。
STM32F103C8T6经典得不能再经典了,72MHz主频、64KB Flash、20KB RAM,挂在它身上的外设资源对这个项目来说刚刚好。而且无论是标准库还是HAL库,资料都极其丰富,后期调试遇到问题一搜就有答案。另外还有一个隐藏优势:本科毕设的评分标准里很看重“系统设计合理性”和“工作量”,用STM32可以自然地展开复位电路、晶振电路、下载调试接口、电源树设计这些硬件细节,这些都是加分项。
1.3 传感器与报警方案选型背后的取舍
摔倒检测最常用的传感器是MPU6050,六轴(三轴加速度+三轴陀螺仪),I2C接口,内置DMP运动处理引擎,可以直接输出四元数或者欧拉角,省去自己在MCU里做姿态解算的功夫。有人问能不能用只测加速度的ADXL345,可以,但摔倒过程中伴随明显的旋转,光有加速度判断不出来姿态翻转,误报率会高很多。所以既然做这个项目,MPU6050是性价比最高的选择。
报警通路怎么设计也要想清楚。本地报警很简单,蜂鸣器加LED就够。远程报警就有讲究了:
- GSM短信报警:用SIM800C或者SIM900A模块,跌倒后发短信到预设号码。这个方案最直接,适合没有WiFi覆盖的居家和户外场景。
- GPS定位:用NEO-6M或者ATGM336H模块,摔倒后把经纬度一起发出去。如果作品定位是“居家养老”,GPS不是必须的;但如果想让作品“走出去”,还是加上好。
- WiFi/4G网络上报:如果用ESP8266把数据推送到云平台或者微信小程序,那就是另一个量级的作品了,工作量也大不少。
我当时的建议是基础版做“本地蜂鸣器+GSM短信+GPS定位”三件套,如果时间充裕再加ESP8266做云端上报。这样既有实用性,又有技术延展空间,论文里还可以多写一章“系统扩展设计”。
2. 硬件设计核心细节与实操要点
2.1 最小系统与电源树设计
STM32最小系统听起来简单,但很多新人焊完板子发现“下载不了程序”或者“跑起来不稳定”,多半是细节出了问题。
- 供电:推荐AMS1117-3.3,输入5V,输出3.3V。注意输入输出都要加10uF和100nF滤波电容,且电容要尽量靠近芯片引脚。老人在用的时候可能插着充电宝,充电宝输出电压纹波大,不加滤波电容MCU很容易复位。
- 晶振:8MHz主晶振加两个20pF负载电容。有些人为了省事直接用内部RC振荡器,省了两个电容但换来的是串口波特率可能不准的隐患,GSM模块通信对时序要求不低,不建议省。
- 复位电路:10K上拉电阻加0.1uF电容到地,这是标准接法。如果做的是产品级设计,还可以加MAX809复位芯片,但毕设没这个必要。
- BOOT引脚:BOOT0和BOOT1都要接10K下拉到地,否则上电可能进入ISP模式,程序没跑起来还不知道怎么回事。
- 下载调试:推荐用SWD,只要PA13( SWDIO)、PA14(SWCLK)和GND三根线,比JTAG省IO。初次接触的同学容易在STM32的PA13、PA14上外接别的功能导致无法下载,这是后话,后面专门写。
电源树方面,整个系统的电流大头在GSM模块,发送短信时峰值电流能到2A,这是很多人容易忽视的。GSM模块的供电不能和MCU共用AMS1117直接拉,会瞬间掉电导致整个系统复位。正确做法是GSM模块单独用一颗MP1584或者LM2596降压模块从电池/适配器供电,或者至少加大容量电容和粗线。这块如果不注意,调试GSM时系统不断重启,会把人折磨到怀疑人生。
2.2 MPU6050传感器布局与接线
MPU6050的布局有两个坑:一是安装位置尽量在人体躯干中心,比如胸前或者腰部。如果装在手腕上,手臂的摆动会被当成躯干运动,误报率直线上升。二是传感器的方向要固定。因为算法里要判断“人体相对于地面的角度”,如果传感器装反了,原始数据的正负号就反了,角度计算出来的结果就会完全错误。
接线部分很简单,I2C只需要两根线:
- VCC -> 3.3V
- GND -> GND
- SCL -> PB6(I2C1_SCL,如果用硬件I2C)
- SDA -> PB7(I2C1_SDA)
- AD0 -> GND(从机地址为0x68)
关于I2C这里多说一句,STM32的硬件I2C在标准库时代被吐槽得厉害,很多老工程师都“心有余悸”。但其实在新版HAL库里已经稳定很多了。新手如果不想折腾,用GPIO模拟I2C时序也完全没问题,代码量不大,稳定性自己可控。我自己习惯用模拟I2C,因为万一传感器异常,逻辑分析仪一抓就能看出是MCU时序问题还是传感器没响应。
2.3 远程报警模块接线与SIM卡问题
GSM模块(以SIM800C为例)非常容易踩坑,接线时需要特别关注:
- RXD接STM32的TX,TXD接STM32的RX,需要交叉连接。
- 模块是TTL电平,但有些老模块是RS232电平,买的时候要确认清楚。以SIM800C为代表的模块是TTL串口,可以直连。
- VCC必须接5V或者4.2V(模块不同有差异),不能接3.3V。很多同学把模块VCC接到3.3V上导致模块完全不启动或者经常重启。
- 开机方式:SIM800C是PWRKEY引脚拉低一秒开机,不是上电就开机。检测方法是用串口发送"AT",返回"OK"就说明模块活着。
SIM卡推荐用移动卡,信号覆盖好,有些物联网卡需要APN设置,反而增加调试难度。发送短信前要确认SIM卡没欠费,这个看起来低级但真有人到了答辩前一天才发现短信发不出去是因为卡欠费了。
GPS模块的TXD接STM32的RX就行,NMEA协议每秒输出一帧数据,$GPRMC字段里有经纬度信息。GPS模块冷启动搜星可能需要一两分钟,测试时一定要在窗边或者室外,室内基本搜不到星,这也是很多人说“GPS怎么没数据”的原因。
3. 软件工程搭建与核心算法实现
3.1 用标准库还是HAL库——我推荐你这么做
这个问题几乎每个初学者都要问一遍。我的观点很明确:如果目标是快速出成品、把精力放在算法和应用逻辑上,用HAL库;如果目标是深入理解寄存器操作、享受“一切尽在掌握”的感觉,用标准库。
用HAL库的好处是CubeMX可以图形化配置时钟树、串口、I2C、GPIO,几分钟就把工程基础搭好了,不用反复查寄存器手册。缺点是有些底层封装得太厚,出了问题不好排查。而标准库的代码读起来直白,跟寄存器一一对应,出了问题能一眼看到底。
个人建议:做这个项目用标准库完全够用,代码量不大;但如果后续打算扩展到RTOS或者复杂外设管理,HAL库+CubeMX会省心很多。无论选哪个,建工程时把外设分层——底层驱动(delay、uart、i2c)、模块驱动(mpu6050、gsm、gps、oled)、应用层(摔倒检测算法、报警状态机)——这个习惯一定要养成。
3.2 MPU6050数据读取与DMP姿态解算
MPU6050的原始输出是加速度计的3轴值和陀螺仪的3轴角速度值。直接用原始值判断摔倒也可以,但会有个问题:传感器固定在人体上,如果安装时有倾斜,静止时三轴加速度值就不是标准的(0,0,1g),这会干扰阈值判断。
更稳定的做法是开启DMP,直接读四元数,然后转换成欧拉角(pitch、roll、yaw)。DMP是MPU6050内部的运动处理引擎,它会做传感器融合、滤波、姿态解算,MCU只需要通过I2C把四元数读出来换算就行。这比自己在MCU上跑互补滤波或者卡尔曼滤波省力得多,而且精度更好。
四元数转欧拉角的公式:
pitch = asin(-2q1q3 + 2q0q2) * 57.3 roll = atan2(2q2q3 + 2q0q1, -2q1q1 - 2q2q2 + 1) * 57.3 yaw = atan2(2*(q1q2 + q0q3), q0q0 + q1q1 - q2q2 - q3q3) * 57.3核心判断逻辑可以这样设计:
- 计算合加速度 magnitude = sqrt(ax² + ay² + az²)
- 当 magnitude 超过阈值(比如3g)时,认为发生了剧烈冲击
- 继续检测角度:静止站立时pitch接近0°或90°(取决于安装方向),倒地后身体接近水平,检测pitch是否发生大于50°的突变
- 连续检测1~2秒,如果角度没有恢复,确认摔倒,触发报警
角度突变和加速度冲击同时满足才算摔倒。如果只按加速度阈值判断,老人大力关门产生的震动就可能误报;如果只按角度判断,老人弯腰捡东西也会触发。
3.3 摔倒检测算法代码实现
下面给出一段核心的摔倒判断逻辑,用标准库实现。代码思路是:周期性读取MPU6050数据,计算合加速度,先判断冲击,再判断姿态变化,最后加一个持续确认。
#define IMPACT_THRESHOLD 3.0f // 合加速度冲击阈值,单位g #define ANGLE_THRESHOLD 50.0f // 角度变化阈值,单位° #define CONFIRM_TIME_MS 1500 // 确认时间,单位ms uint8_t Fall_Detect(float ax, float ay, float az, float pitch, float roll) { static uint8_t impact_flag = 0; static uint32_t impact_time = 0; static float pre_pitch = 0, pre_roll = 0; // 1. 计算合加速度 float magnitude = sqrt(ax*ax + ay*ay + az*az); // 2. 检测剧烈冲击 if (magnitude > IMPACT_THRESHOLD) { // 记录冲击时刻的姿态角 if (!impact_flag) { impact_flag = 1; impact_time = HAL_GetTick(); pre_pitch = pitch; pre_roll = roll; } } // 3. 冲击发生后,等待姿态变化确认 if (impact_flag) { // 超过确认窗口还没满足姿态变化,复位状态 if (HAL_GetTick() - impact_time > CONFIRM_TIME_MS) { impact_flag = 0; return 0; } float pitch_diff = fabs(pitch - pre_pitch); float roll_diff = fabs(roll - pre_roll); // 姿态角变化超过阈值,确认摔倒 if (pitch_diff > ANGLE_THRESHOLD || roll_diff > ANGLE_THRESHOLD) { impact_flag = 0; return 1; // 确认为摔倒 } } return 0; }这段代码的思路是“先撞后倒”。老人摔倒的过程通常分两段:先是身体撞击地面产生瞬间冲击(合加速度突变),然后身体躺平不再起来(姿态角变化且保持)。这两段都满足才触发报警,能过滤掉大部分误报场景。
在实际测试中,我建议把三个阈值单独做成宏定义,方便反复测试调节。比如合加速度阈值调低到2.5g,灵敏度会高一些但误报也多;调高到4g,误报少了但可能漏报。这是在论文里可以写数据、画曲线的部分,也是答辩时老师比较感兴趣的内容。
需要注意:HAL_GetTick()的基准是系统上电后的毫秒数,默认频率是1kHz,只要系统时钟配置没问题,这个时间基准是准的。另外还有一个很容易被忽略的点:fabs是浮点绝对值,需要包含math.h,并且编译时链接libm库(可以在Keil的Options for Target里面勾选Use MicroLIB,或者链接时加-lm)。
3.4 报警状态机与GSM短信发送
检测到摔倒之后,系统进入报警状态机。这个状态机设计的核心思路是:报警一旦触发,不能因为一次误动作就自动消失,需要人工干预才能复位,但也要给老人一个“误触按掉”的机会。
IDLE → FALL_DETECTED → WAIT_USER_CANCEL → ALARMING → RESET- IDLE:正常待机,持续监测传感器数据。
- FALL_DETECTED:算法判定摔倒,此时先不立刻发短信,蜂鸣器短鸣三次,LED快闪,等待5秒。
- WAIT_USER_CANCEL:如果老人在5秒内按键,说明是误报,回到IDLE。如果没按键,进入ALARMING。
- ALARMING:蜂鸣器持续鸣叫,发送短信到预存号码,附上GPS坐标。每隔30秒发一次,直到有人按下复位键。
这个设计借鉴了“二次确认”机制。我见过很多版本是检测到摔倒立刻发短信,结果做测试时自己从沙发上蹦一下都发一条,10分钟内把家人的手机短信塞满了。加一个5秒的取消窗口,误报率能降一半以上。
GSM发短信的核心代码如下,用的是AT指令中的文本模式:
void GSM_SendSMS(char* phone, char* message) { char cmd[64]; // 1. 发送AT指令确认模块在线 UART_SendString("AT\r\n"); Delay_ms(500); // 2. 设置短信为文本模式 UART_SendString("AT+CMGF=1\r\n"); Delay_ms(500); // 3. 指定接收号码 sprintf(cmd, "AT+CMGS=\"%s\"\r\n", phone); UART_SendString(cmd); Delay_ms(500); // 4. 发送短信内容,0x1A是Ctrl+Z,表示发送 UART_SendString(message); Delay_ms(200); UART_SendByte(0x1A); Delay_ms(3000); }理论上AT指令返回“OK”就算成功,但实际调试时经常出现返回“ERROR”。最常见的几个原因:SIM卡没插好、模块开机时序不对(PWRKEY没拉够时间)、串口波特率不匹配(模块默认一般是9600或者115200)。排查顺序建议是:先用USB转TTL接模块单独调试,用串口助手直接发送AT指令,确认模块能回复OK,再接STM32联调。
3.5 OLED显示与按键交互
OLED用0.96寸I2C接口的就够,128x64分辨率,能显示系统状态、传感器数值、经纬度坐标。驱动IC一般是SSD1306,底层驱动代码网上有很多成熟版本,直接移植即可。如果要用中文字库,还要处理取模的问题,这个工作量不大但很繁琐,我建议显示界面用英文加数字就够了,论文里配图一样清晰。
按键建议用一个独立按键接在PA0上,配一个10K下拉电阻,按下为高电平。初始化时开GPIO输入模式,在主循环里做消抖判断:检测到低->高沿跳变后延时20ms再读一次,确认还是高才有效。不要用中断方式做这个按键,因为主循环本身对实时性要求不高,轮询完全够用,用中断反而要处理各种临界区问题。
4. 常见问题与排查技巧实录
4.1 串口打印数据正常,但程序总在延时函数卡死
这个问题几乎每个用HAL库的同学都会遇到。HAL_Delay()是依赖SysTick中断的,如果代码里某个地方临时关掉了中断,或者中断优先级配置不当,HAL_Delay()就永远等不到tick递增,表现为程序卡死。标准的排查流程是:
首先检查Init函数里HAL_Init()是否被调用,这是SysTick初始化的前提。然后检查有没有在中断回调里调用HAL_Delay()。只要在中断回调里调用HAL_Delay(),程序基本必卡死。最后检查是否误关了中断,比如__disable_irq()用完之后没开回来。
如果实在查不出来,还有一个“笨办法”:自己写一个基于定时器的delay函数,不依赖SysTick。用TIM2做1ms中断,在中断里对全局变量加一,delay就查这个变量。
4.2 MPU6050读取数据全零或者只有第一个字节正常
这个问题一般出在I2C时序上。MPU6050的I2C从机地址是0x68(AD0接地)或者0x69(AD0接VCC),很多驱动代码里默认0x68,如果你的模块AD0不小心拉高了,就需要改成0x69。
如果用的是硬件I2C,配置完I2C时钟后建议先用I2C_IsDeviceReady(&hi2c1, 0x68 << 1, 100)做一个设备探测,返回HAL_OK说明通路上没问题。如果超时,大概率是SCL/SDA引脚配置错误,或者上拉电阻没焊(模块上一般都有,如果自己画板子就必须加4.7K上拉)。
如果数据读出来是0xFF或者0x00,还有一种可能是电源问题:MPU6050的VCC对纹波比较敏感,用万用表量一下3.3V是不是稳定。用稳压芯片供电,不要直接从某个模块上飞线取电。
4.3 GSM发短信偶尔成功偶尔失败
GSM模块最让人头疼的就是“不稳定”。调试时经常发现:第一次发短信成功,第二次失败,第三次又成功。排插思路如下:
- 确认电源足够:可以用示波器看模块VCC在发送瞬间的跌落幅度。如果发送时波形掉到4V以下,说明电源没扛住,需要单独供电。
- 确认串口波特率稳定:GSM模块对波特率误差要求比较高,如果用内部RC振荡器,波特率可能偏差到3%以上,偶尔出现乱码。解决方法是改用外部晶振,或者把波特率降到9600(误差容忍度稍高)。
- 发送指令之间间隔时间要够长:AT+CMGS发送后要等模块返回“>”字符再发内容,写完内容后发送0x1A。中间的延时不能省。调试的时候可以把延时加到1秒,代价是速度变慢但成功率会明显提高。
4.4 误报率居高不下怎么调
如果做好了本地测试,发现走路、坐下、弯腰都不会误报,但老人正常躺下睡觉却误报了,问题出在算法对“摔倒”和“躺下”的区分上。躺下其实也是一个剧烈的姿态变化,和摔倒的区别在于:躺下是一个主动的、缓慢的过程,合加速度不会出现瞬间冲击;而摔倒的冲击阶段加速度会瞬间超过3g甚至4g。检测代码里先判冲击再判角度,就是为了把“躺下”过滤掉。
如果实际测试中躺床上的动作太快导致误报,可以适当提高冲击阈值。如果走路震动太大导致误报,检查一下传感器的安装松紧程度,固定不牢固会导致震动被放大。还可以考虑加一个“合加速度持续高于阈值持续N毫秒”的条件,避免瞬间尖峰触发。
4.5 常见问题速查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 程序下载失败 | BOOT引脚配置错误、接线松 | 检查BOOT0/BOOT1下拉,确认SWD三根线导通 |
| 上电后OLED无显示 | I2C地址错误、SCL/SDA接反 | 扫描I2C设备地址,确认模块供电3.3V |
| MPU6050数据不更新 | 传感器未初始化成功、I2C死锁 | 复位传感器,读WHO_AM_I寄存器(应返回0x68) |
| GSM不开机 | VCC电压不足、PWRKEY未拉低 | 万用表测量VCC,确认开机时序 |
| 短信发不出去 | SIM卡没插好、欠费、信号弱 | 单独用串口助手AT测试,换SIM卡排除 |
| GPS无定位数据 | 室内搜星慢或失败 | 拿到窗边或室外,冷启动等2分钟 |
| 蜂鸣器不响 | 驱动引脚配置错误、三极管驱动电流不足 | 先直接用GPIO输出高低电平测试蜂鸣器 |
5. 论文与资料整理的经验
毕设评分不只看了演示效果,论文占的比重很大。一篇好的毕设论文要能讲清楚三件事:为什么做、怎么做、结果怎么验证。项目本身做了很多,论文里一定要把工作量体现出来。
建议论文结构这样组织:
- 第一章绪论写背景意义、国内外研究现状,引入老龄化趋势和智慧养老概念。
- 第二章系统总体方案设计,画总体框图,对比方案选型,给出设计指标。
- 第三章硬件设计,分模块画原理图和PCB截图,讲清楚每个模块的电路原理和设计理由。
- 第四章软件设计,先画主程序流程图,再分模块讲初始化流程、检测算法、报警逻辑。
- 第五章系统测试,写测试方案、测试数据、测试结果分析。注意要有对比数据,比如不同阈值下的误报率统计。
资料整理方面,源码要写清楚注释,命名规范,关键函数的功能说明放在文件头。学长学姐找你要源码的时候直接能看得懂,这才算整理到位。附带的原理图最好转成PDF格式,PCB也导出高清图,别交个源文件就算完事。
整个项目做完,回头看会发现这个课题最值钱的部分不是硬件本身,而是“如何通过多源信息综合判断一个事件”的工程思维。STM32只是一个载体,放到别的MCU上同样成立。答辩的时候、写简历的时候,把这段思路讲清楚比罗列一堆外设名称有用得多。最后再分享一个我个人的小习惯:项目调试的每一天都用手机拍一段短视频,记录当前跑通了哪个功能、现象是什么。到写论文和做答辩PPT的时候,这些素材直接就是“系统调试与测试环节”最扎实的一手证据。
本文还有配套的精品资源,点击获取