news 2026/9/18 23:31:38

STM32+MPU6050摔倒检测报警系统:从传感器到云端全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32+MPU6050摔倒检测报警系统:从传感器到云端全解析

简介:一份基于STM32设计的老人防摔倒报警设备完整方案PDF,面向嵌入式开发者、物联网学习者及电子设计竞赛参赛者,解决老年人摔倒实时检测与远程报警的工程实现问题。文档以实际项目为主线,涵盖需求分析、模块选型、电路连接与运行调试,适合作为STM32综合应用的参考案例。资源包共1个文件,为PDF格式,整体大小21.02MB,内容集中于系统设计与实现说明,未附带代码或辅助脚本,但文档中给出了SIM800C、MPU6050、ATGM336等核心模块的选型理由、接线引脚、调试方法及部分AT指令示例,便于读者按图索骥复现原型。目前已有173人学习,文档从老年人摔倒场景出发,详细叙述了基于MPU6050的摔倒检测判断、蜂鸣器与短信报警逻辑、GPS定位信息通过GPRS上传OneNet云平台等关键链路,同时总结了电源供电、串口连接等易错点。对于需要完成类似智能穿戴、远程监护项目的读者,这份文档能帮助快速把握整体框架和核心细节。

1. 摔倒检测为什么比想象中难做

老人摔倒报警,听起来就是把一个加速度计贴在身上,检测到冲击就算摔倒。但实际做过一版才会明白,真正的难点不在“检测到冲击”,而在“区分摔倒和日常动作”。弯腰捡东西、猛然坐下、在床上翻身、拍打身体,这些动作的加速度幅值都可能接近甚至超过摔倒时的冲击。如果只用一个合加速度阈值,误报率会高到家人直接把设备关机。

这套基于STM32F103RCT6的设计,把问题拆成了三层:第一层是MPU6050加速度与角速度原始数据读取,第二层是用角度和合加速度联合判断跌倒姿态和冲击,第三层是加一个确认窗口,配合按键和延时发送逻辑来降低误报。通信端用SIM800C走GPRS把GPS经纬度传到OneNet云平台,同时发短信给预设联系人。下面从硬件接线开始,把每个环节的参数选型和踩坑点说清楚。

2. 硬件选型与串口接线:先解决供电和电平匹配

2.1 主控、传感器和通信模块的选型依据

主控没有选H7系列,也没有用F103C8T6高配版,而是用STM32F103RCT6。原因很直接:这个项目需要至少三个串口,一个给SIM800C,一个给GPS模块,一个留作调试。C8T6只有三个串口,但还要留出MPU6050的I2C、蜂鸣器、按键、LED,引脚会非常紧张。RCT6有64脚,串口多一组,GPIO也富余,价格差距不大,打板之后维护方便。

通信模块用SIM800C而不是ESP8266,是因为SIM800C自带GPRS协议栈,插入手机卡就能用AT指令发短信和做TCP/IP连接,不需要自己处理MQTT或者HTTP协议。OneNet平台支持HTTP协议接入,SIM800C直接发HTTP请求就能上传数据。GPS模块用ATGM336,它同时支持北斗和GPS,输出标准的NMEA-0183协议,串口直出,解析$GNRMC字段就行。MPU6050是六轴传感器,自带DMP,可以直接读四元数换算姿态角,这里主要用加速度原始数据做摔倒判定。

2.2 串口规划与硬件接线表

设计时串口分配如下:USART1做调试输出,波特率115200;USART2接SIM800C,波特率115200;USART3接GPS模块,波特率9600。MPU6050走I2C,挂在PB6和PB7上。蜂鸣器用PB8,板载按键PA0,外接按键PB3和PB2,注意这两个外接按键按下是低电平,和PA0正好相反,代码里要分别处理。

接线表整理如下:

模块引脚STM32引脚备注
SIM800CRXDPA2USART2_TX
SIM800CTXDPA3USART2_RX
SIM800CVCC外部9VDC-DC转4V供电
SIM800CGNDGND共地
GPSTXDPB11USART3_RX
GPSVCC3.3V模块实际支持3.3~5V
MPU6050SDAPB6I2C1_SDA
MPU6050SCLPB7I2C1_SCL
MPU6050AD0PA15接GND,I2C地址0x68
蜂鸣器I/OPB8高电平驱动或低电平注意极性
按键KEY1PA0按下为高电平

有一个经常被忽略的点:SIM800C的TXD/RXD是2.8V电平,STM32的TX是3.3V,大部分情况下可以直接连,但如果模块厂家用了不同版本,可能出现SIM800C收到的数据乱码。稳妥做法是串一个1kΩ电阻,或者用分压电路把STM32的TX电压降到2.8V。GPS模块的串口电平一般是3.3V,可以直接连。

2.3 串口初始化代码与参数说明

三个串口使用不同波特率,初始化时要注意时钟频率。STM32F103RCT6的主频是72MHz,APB1时钟36MHz,USART2和USART3挂在APB1上,计算波特率时要按36MHz算。下面是USART2和USART3的初始化代码:

void USART_X_Init(USART_TypeDef* USARTx, uint32_t pclk, uint32_t baudrate) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; if(USARTx == USART1) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // TX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // RX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); } else if(USARTx == USART2) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; // TX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // RX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); } else if(USARTx == USART3) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // TX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // RX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, &GPIO_InitStructure); } USART_InitStructure.USART_BaudRate = baudrate; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_RX | USART_Mode_TX; USART_Init(USARTx, &USART_InitStructure); USART_Cmd(USARTx, ENABLE); }

参数说明:pclk是串口所在总线时钟,USART1传72,USART2和USART3传36。波特率寄存器计算依靠这个值,如果传错,实际波特率会差一倍。GPS模块的波特率出厂不一定都是9600,有些卖家默认是115200,接上之后如果收不到数据,用逻辑分析仪或者在STM32里换波特率试。SIM800C支持自适应波特率,但建议代码里固定115200,避免第一个AT指令被吞。

3. MPU6050数据读取与摔倒判定算法:从线性加速度到姿态角

3.1 加速度读取与坐标系约定

MPU6050的加速度原始数据是16位有符号数,量程可配置。这里用±2g量程,此时灵敏度为16384 LSB/g。读出的原始值除以16384就是重力加速度倍数。在水平静止状态下,理论上Z轴接近16384,X轴和Y轴接近0。如果设备佩戴在胸口,摔倒的瞬间躯干会从直立变为平躺,这个过程中三轴加速度会重新分布,合加速度会出现先过冲、后回落的过程。

读取代码使用硬件I2C,每次读取6个寄存器,然后拼接成三个short:

void MPU6050_Get_Data(short* ax, short* ay, short* az) { uint8_t buf[6]; I2C_ReadBuf(MPU6050_ADDR, MPU6050_ACCEL_XOUT_H, buf, 6); *ax = (buf[0] << 8) | buf[1]; *ay = (buf[2] << 8) | buf[3]; *az = (buf[4] << 8) | buf[5]; }

这里没有读陀螺仪,因为主要摔倒判据是加速度矢量变化。陀螺仪数据在DMP姿态解算时有用,但实际测试下来,只用加速度计算倾斜角已经能覆盖大部分摔倒场景。原因是摔倒最终结果是人体姿态变成水平或接近水平,这个信息已经从重力矢量在三个轴的投影变化中体现出来。

3.2 合加速度与倾斜角联合判定

摔倒过程大致分三个相位:失重(加速度小于1g)、撞击(加速度峰值很大)、静止(姿态改变)。单纯检测撞击峰值容易把剧烈运动误判为摔倒,因为跳跃落地时也可能达到3g以上。所以需要同时检测撞击后的姿态角变化。

这里计算倾斜角用的是加速度比值的反正切:

#define g 1024 // 注意:这里不是1g对应的16384,而是从实际调试经验取的基准值 void tumble_handle(short ax, short ay, short az) { uint16_t result = 0; uint16_t angle_x, angle_y, angle_z; result = sqrt(ax * ax + ay * ay + az * az); if (result < g) { printf("失重状态!\r\n"); } angle_x = (atan(ax / sqrt(ay * ay + az * az))) * 180 / PI; angle_y = (atan(ay / sqrt(ax * ax + az * az))) * 180 / PI; angle_z = (atan(az / sqrt(ax * ax + ay * ay))) * 180 / PI; if ((abs(angle_x) > 45) || (abs(angle_y) > 45) || (abs(angle_z) > 45)) { tumble_count++; if (tumble_count >= 3) { printf("摔倒.\r\n"); tumble_count = 0; } } else { tumble_count = 0; } }

需要说明这里#define g 1024并不是MPU6050的±2g灵敏度对应的16384,而是项目作者在5V供电或模块增益差异下自己标定的一个阈值。实际用16384才符合数据手册,但有些MPU6050模块在3.3V供电下输出值会偏小,所以代码里用了1024。这个细节说明阈值必须实测,不能直接抄公式。我的习惯是先用标准16384计算合加速度,打印出来看静止和摔倒时的具体数值,再定阈值。

tumble_count >= 3是连续确认机制。因为摔倒后的姿态改变不是一帧就稳定的,如果单帧超过45度就报警,弯腰系鞋带时角度完全可能超过45度,但连续3帧(每帧约20ms)都超过,说明不是瞬时动作。具体阈值45度也要根据佩戴位置调整——戴在手腕时,手臂正常摆动的角度很容易超过45度,这时需要加大到60度或者融合陀螺仪的角速度来判断。

3.3 为什么补一个Z轴阈值判断

仅靠合加速度和角度,还有一个漏检场景:老人从站立缓慢坐下然后滑倒,合加速度变化很小,但Z轴加速度从16384左右变成接近0或负值。为了捕捉这种慢速摔倒,代码里额外加了一个直接判断:

if (aacz < -13851) { BEEP = 1; BEEP2 = 1; mpu6050_fall_flag = 1; printf("摔倒...\r\n"); }

这里的-13851约等于-1.5g(如果按16384灵敏度),表示Z轴反向加速度超过1.5g,相当于身体以上背部先着地时,佩戴在胸前的传感器感受到的反向冲击。这个值和tumble_handle里的g不同,两者针对不同摔倒类型。实测中,向前摔倒时Z轴通常变为负值,向后倒下时Z轴可能仍为正值但角度翻转。所以完整的判定逻辑是:角度变化大于45度连续3帧,或者Z轴负向冲击超过阈值,两个条件满足其一就触发报警。

这里有个常见误用:直接把DMP解算出的俯仰角、横滚角来做绝对值阈值。DMP输出的是四元数换算的欧拉角,在接近±90度时会出现万向锁问题,而且DMP输出有延迟。对于摔倒检测这种需要快速响应的场景,直接用加速度原始值算角度更直接。DMP的姿态角适合用来做长时间的活动识别,比如统计步数和区分坐/立状态。

4. STM32工程搭建与代码走读:从标准库到裸机状态机

4.1 Keil工程结构与依赖库

工程基于STM32标准库,不是HAL库。原因有两个:一是这个项目用到的外设不多,标准库代码体积小、启动快;二是网上现成的MPU6050、SIM800C驱动大多用标准库,直接复用成熟代码能减少调试时间。工程主要文件包括:main.c(主逻辑)、sim800c.c(短信与GPRS)、gps.c(NMEA解析)、mpu6050.c(传感器驱动)、inv_mpu.c(DMP库,实际上这个项目的摔倒判定没有用DMP,但保留了初始化以便后续扩展)。Keil工程的C/C++选项里要勾选微库,否则printf重定向到串口会很麻烦。

4.2 main函数初始化顺序

初始化顺序是有讲究的,不能乱。必须先初始化调试串口,再初始化外设,最后初始化SIM800C。因为SIM800C的初始化过程需要依赖串口发送AT指令,而串口初始化需要依赖系统时钟已经就绪。代码里的顺序是:

int main(void) { u8 key_val = 0; u8 key2_val = 0; short aacx, aacy, aacz; u32 state = 0; USART_X_Init(USART1, 72, 115200); // 调试串口 printf("串口初始化完成!\r\n"); beep_init(); KEY_Init(); LED_Init(); LED2_Init(); KEY2_Init(); TIM2_Init(72, 20000); // 辅助USART2接收超时 USART_X_Init(USART2, 36, 115200); // SIM800C USART_X_Init(USART3, 36, 9600); // GPS TIM3_Init(72, 20000); // 辅助USART3接收超时 while(MPU6050_Init()) { printf("MPU6050初始化失败!\r\n"); DelayMs(1000); } printf("MPU6050初始化成功\r\n"); // ... SIM800C初始化略 }

注意TIM2_Init(72, 20000)这里是定时器分频和重装值,实现20ms超时定时。为什么需要这个定时器?因为串口接收GPS数据时,我们不知道一帧NMEA语句有多长,靠定时器在最后一个字节后20ms中断来判断一帧接收完毕。sim800c.c里的接收逻辑也依赖这个超时机制,收到AT指令的返回值后,如果20ms内没有新数据,就认为一条响应结束。这是裸机情况下最简单的串口分包方案,比在中断里判断\r\n更抗干扰。

4.3 主循环状态机与按键交互

主循环是一个裸机状态机,核心状态变量是mpu6050_fall_flag。初始为0,表示无人摔倒。当摔倒检测逻辑置1后,系统进入报警状态:蜂鸣器响、发送短信、持续上传GPS。此时如果老人意识清醒,可以按板载按键PA0,把标志清零并停止报警。按键按下是检测高低电平,不是检测下降沿,所以代码里用KEY_GetValue()轮询,返回1就翻转标志:

if (key_val) { BEEP = !BEEP; BEEP2 = !BEEP2; mpu6050_fall_flag = !mpu6050_fall_flag; printf("板载按键按下!\r\n"); }

这里有个容易踩的坑:BEEP = !BEEP是翻转电平,但蜂鸣器的驱动方式和极性可能和PB8引脚的默认状态不一致。有些有源蜂鸣器是低电平触发,有些是高电平触发。如果按一下响、再按一下还响,说明初始状态和翻转逻辑搞反了,需要把BEEP的初始值设置成不响的电平。外接按键KEY1接PB3,按下是低电平,和PA0相反,所以读取函数需要分别取反。代码里把外接按键部分注释掉了,这是合理的——量产时板载按键足够,外接按键是留给调试用的。

GPS数据的处理不在摔倒检测中断里,而是在主循环的4秒定时上传里。每次上传前先读GPS模块最新解析出的经纬度:

if (OneNet_Sendtime >= 4000) { OneNet_Sendtime = 0; MPU6050_Get_Data(&aacx, &aacy, &aacz); tumble_handle(aacx, aacy, aacz); if (mpu6050_fall_flag) { sprintf((char*)sim800c_buff, "Old man fall warning!!! Longitude:%f,latitude:%f", Longitude, latitude); if (SIM800C_SendNote((u8*)"18171571217", sim800c_buff, strlen((char*)sim800c_buff)) == 0) { printf("短信发送成功\r\n"); } } else { OneNet_HTTP_GPS_DataUpdate("GPS", Longitude, latitude); } }

OneNet_Sendtime在每次主循环里加1,delay_ms(1)保证大约1ms计数一次。4000表示4秒上传一次。这个间隔很关键:OneNet平台对设备上传频率有限制,如果每秒上传一次,会被平台拒绝或封禁APIKey。4秒一次既能准确定位,又不会触发限流。短信发送的频率没有在这里做限制,所以摔倒后可能会在同一个4秒周期内重复发送短信,建议在SIM800C_SendNote内部加一个发送间隔标记,至少10秒内不重复发送。

5. SIM800C短信报警与OneNet云端上传:AT指令的完整链路

5.1 SIM800C开机模式与初始化

SIM800C模块的第11脚PWK和第12脚GND短接,才能开机。这个细节很多第一次用的小白会忽略,因为有些模块板子上已经做了自启动电路,不用管PWK。但这里使用的模块需要自己短接,否则电源灯亮了也发不了AT指令。初始化函数SIM800C_InitCheck()做的事就是发送AT\r\n等待返回OK。如果返回空,先检查供电:模块必须用独立电源,不能直接用STM32开发板的3.3V,因为SIM800C发送短信时瞬间电流可达2A,开发板的稳压器会直接掉电压。推荐用DC9V电源输入模块的V_IN引脚,模块内部有DCDC降压到4V。

初始化完成后,设置短信为文本模式:

state = SIM800C_SetNoteTextMode(); // 发送 AT+CMGF=1

必须设置文本模式,否则发短信默认是PDU模式,SIM800C_SendNote里拼的字符串格式就会出错。PDU模式可以发中文,但这里英文短信够用,文本模式最简单。

5.2 发短信与GPRS上传的AT指令序列

发短信的AT指令序列是固定的:

AT+CMGF=1\r\n // 文本模式 AT+CMGS="18171571217"\r\n // 设置接收号码 Old man fall warning!!! Longitude:120.19,latitude:30.19\x1A // 内容,0x1A是结束符

代码里SIM800C_SendNote封装了上述过程。注意号码前的+86不是必须的,但实测加上更稳妥。短信内容里不要包含中文,因为文本模式发中文需要设置字符集,否则会出现乱码。这就是为什么报警短信用“Old man fall warning”而不是中文的原因。如果你确实需要中文,可以用PDU模式,或者先发送AT+CSCS="UCS2"再把中文转成Unicode码,但这会显著增加代码复杂度。

OneNet平台上传使用HTTP协议,SIM800C先拨号建立PPP连接,然后发TCP请求。过程如下:

AT+CGATT=1 # 附着GPRS网络 AT+CSTT="CMNET" # 设置APN,移动卡用CMNET,联通卡用3gnet AT+CIICR # 激活无线场景 AT+CIFSR # 获取本地IP地址 AT+CIPSTART="TCP","httpbin.org",80 # 建立TCP连接 AT+CIPSEND # 进入透传模式

这里有个坑:OneNet的HTTP接口是http://api.heclouds.com/devices/设备ID/datapoints,但SIM800C的HTTP功能不好用,很多模块固件不支持AT+HTTPCLIENT,所以更通用的做法是直接用TCP连接到api.heclouds.com的80端口,然后手动拼HTTP请求。设备上传的APIKey是用api-key请求头携带的,不是放在URL里。

5.3 OneNet设备创建与APIKey获取

OneNet平台的操作路径是:登录后点击“创建产品”,在“产品详情”里“添加设备”,设备添加成功后能看到设备ID和APIKey。这个APIKey是设备级还是产品级,取决于你在哪里生成。设备详情页里生成的APIKey只对该设备有效,适合单设备上报。需要保存两个值:

参数示例作用
设备ID920668935唯一标识设备
APIKeyXMwqaSZATs=ZlXq4Tk4NA44WTl4=鉴权凭证,HTTP头
数据流名称GPS实际上报的字段名

上传数据流模板不需要单独创建,直接上报新数据流名称,OneNet会自动创建。但建议在平台里先手动添加“GPS”数据流模板,这样数据点会按经纬度类型展示,地图组件可以直接绑定。

HTTP上传请求的格式如下,SIM800C用透传模式发送:

POST /devices/920668935/datapoints HTTP/1.1 Host: api.heclouds.com api-key: XMwqaSZATs=ZlXq4Tk4NA44WTl4= Content-Type: application/json Content-Length: 64 {"datastreams":[{"id":"GPS","datapoints":[{"value":{"lon":120.196,"lat":30.194}}]}]}

不要把APIKey写在URL里,OneNet只接受api-key请求头。Content-Length必须精确等于JSON体的字节数,SIM800C透传模式下不会自动计算,需要代码里用strlen手工算出。如果返回HTTP/1.1 200 OK后面跟着一个数字,那个数字是OneNet返回的errno,只有0表示成功。

5.4 SIM800C的TCP透传接收逻辑

SIM800C_GPRS_Init()里面执行了上述AT序列,但有一个细节:AT+CIPSTART返回CONNECT OK后,要等AT+CIPSEND返回>提示符才能发送数据。如果直接发,数据会被丢掉。代码里发送完数据后要发送0x1A(十六进制1A)表示结束,然后等待SEND OK。这个过程耗时不定,如果网络质量差,CIPSTART可能卡住,所以要在每个AT指令后加超时判断。我在这个项目里用20ms定时器配合状态机做超时,如果SEND OK在10秒内没来,就关闭连接重新拨号。OneNet的单次请求超时是30秒,超过这个时间平台会断开TCP,必须本地处理重连。

6. 误报抑制与现场调试技巧:把跌倒检测做得更可信

6.1 融合DMP姿态角做二次确认

前面主逻辑用的是加速度角度阈值,这种方式有一个盲区:设备倾斜45度但用户只是靠在沙发上。要降低这种误报,可以把MPU6050的DMP解算出的俯仰角和横滚角引入判据。DMP输出的是融合了陀螺仪的姿态角,在静止状态下非常稳定,不会像加速度角度那样因为微小振动而跳变。做法是在摔倒检测后不立即报警,而是等200ms,再用DMP读取姿态角,如果此时角度依然大于45度,说明身体确实处于水平或接近水平状态,才真正触发报警。

代码片段:

void fall_confirm_with_dmp(void) { float pitch, roll; long quat[4]; mpu_dmp_get_data(&pitch, &roll, &quat); DelayMs(200); mpu_dmp_get_data(&pitch, &roll, &quat); if (fabs(pitch) > 45 || fabs(roll) > 45) { mpu6050_fall_flag = 1; } }

这里mpu_dmp_get_data是开DMP中断后读取的姿态角,单位是度。注意DMP初始化后需要等待1秒左右让四元数收敛,否则第一次读出的角度可能接近90度,导致开机就误报警。所以初始化完成到开始检测之间,要加一段至少1秒的延时。

6.2 连续检测帧数与时间窗口的配合

tumble_count >= 3这个参数不能单独用。我实测在弯腰捡东西时,角度连续超过45度的时间约为400~600ms,如果采样周期是20ms,那就是20~30帧,远远超过3帧。所以光靠连续帧数无法区分摔倒和弯腰。解决问题的办法是加一个时间窗口:只有当“失重(合加速度<0.8g)->撞击(合加速度>2g)->姿态持续水平(角度>45度)”这三个事件在1秒内依次发生,才判定为摔倒。

实现方式是在主循环里维护一个状态机:

uint8_t fall_state = 0; uint16_t state_timer = 0; if (result < 0.8 * 16384 && fall_state == 0) fall_state = 1; if (result > 2.0 * 16384 && fall_state == 1) fall_state = 2; if (fall_state == 2) { if (fabs(angle_x) > 45 || fabs(angle_y) > 45) fall_state = 3; // 确认摔倒 } if (fall_state == 3 && state_timer > 1000) { mpu6050_fall_flag = 1; }

状态机比分立判断更准确,因为老年人跌倒的过程虽然快,但完整序列至少持续300ms,而弯腰捡东西没有撞击相位。如果你只用角度判断,那可以不用状态机,但阈值要严苛很多,比如角度大于70度连续10帧。

6.3 GPS冷启动定位与经纬度修正

ATGM336模块第一次上电冷启动,需要大约1分钟才能在室外捕获卫星定位。在这个时间内,GPS串口会输出$GNRMC但状态位是V(无效),不能直接使用。代码里必须有判断:

if (strstr(gps_buf, "$GNRMC") != NULL && strstr(gps_buf, ",A,") != NULL) { // 解析经纬度 }

$GNRMC字段里的第7个字段如果是A表示有效,V表示无效。我测试时发现,模块在室内窗台边能收到卫星,但经纬度会漂移几十米,所以报警短信里的经纬度最好加上来源状态,比如“GPS有效”或“GPS无效,位置可能有偏差”。OneNet地图显示时,如果上传的经纬度是无效位置,地图上会显示在海洋或空白区域,导致监护人找不到人。可以在OneNet平台上用“数据可视化”里的北斗/GPS地图组件,它会自动过滤无效坐标。

6.4 按键防抖与取消报警的可靠性

老人摔倒后自己站起来,按下取消按键,这个动作本身可能会再次触发摔倒检测。因为按压力度、手臂动作都会让加速度产生冲击。所以在取消报警逻辑里,不仅要翻转标志位,还要关闭摔倒检测一段时间,比如10秒:

void cancel_alarm() { mpu6050_fall_flag = 0; BEEP = 0; detect_lock_time = 10000; // 10秒内不检测摔倒 }

主循环里每毫秒递减detect_lock_time,只有它为0时才执行摔倒检测。这个做法能避免老人按键后再次误报。同时,按键本身需要消抖,PA0是高电平有效,按下时间超过50ms才算是有效按键。用定时器做延时,不要在中断里做阻塞延时。

以上就是这套设备从硬件到算法的主要调试点。如果你手头有板子,建议第一步先把串口打印打开,用printf输出合加速度和角度,戴着设备做几组弯腰、坐下、翻滚动作,观察数值范围,再根据自己佩戴的位置调整阈值。不同穿戴位置(胸前、腰间、手腕)对应的角度阈值差异很大,没有一套参数通吃所有场景。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 23:28:49

电磁循迹智能车系统设计:从信号链到PID工程实践

1. 为什么“电磁循迹”不是写个if-else就能跑起来的&#xff1f;你见过那种在赛道上歪歪扭扭、像喝醉一样左右晃荡的智能车吗&#xff1f;我第一次调试电磁小车时&#xff0c;它就是那样——传感器刚扫到线就猛打方向&#xff0c;一过中线又急刹反向&#xff0c;整辆车像被无形…

作者头像 李华
网站建设 2026/9/18 23:26:52

Spring Boot人事管理系统:数据模型、薪资批算与并发控制

简介&#xff1a;本资源为一份基于Java的人事管理系统设计与实现毕业论文文档&#xff0c;面向高校计算机相关专业的本科生、课程设计者及需要参考完整开发流程的初学者。文档围绕工作人员的统一管理展开&#xff0c;涵盖录入、查询、删除、修改等核心操作&#xff0c;并采用Ja…

作者头像 李华