简介:面向电子信息、计算机等专业学生的STM32物联网智能拐杖毕设与课设案例,融合WIFI和GPS技术,实现定位、跌倒检测、异常报警等辅助功能。压缩包共237个文件,以C/C++源码、头文件为基础,搭配Android工程、APK安装包、STM32工程配置、PCB相关文件及docx/pdf文档,大小约67.97MB,目录结构清晰便于按模块学习。已有79人学习下载。案例完整覆盖原理图设计、固件开发、手机端应用调试等环节,并附有演示视频与说明文档,适合课程设计、期末大作业或毕业设计直接参考。通过研习可掌握物联网设备从硬件到软件的实现路径,理解WIFI与GPS模块的驱动方法、数据远程上报机制,也便于在此基础上二次开发健康监测或远程告警功能,提升实践能力。
1. 反直觉的开局:智能拐杖最难的其实是定位和联网
这套 STM32 智能拐杖源码,光看名字会觉得核心是硬件和算法,但真跑起来会发现:最耗时间的不是 STM32 单片机代码,而是让 GPS 模块稳定搜星、让 WIFI 模块持续在线。原因很简单,串口谁都会读,但 NMEA 协议里藏着纬度小数点移位、定位状态标志、数据帧粘连这些细节;ESP8266 那边又是供电敏感、AT 指令时序敏感,两个外设一叠加,排错时间轻松超过写代码时间。
这套资源解决的是「老人出门在外的位置可知、跌倒可知、求救可达」这组问题:GPS 拿坐标,MPU6050 检测跌倒,按键触发 SOS,WIFI 模块把数据推上物联网平台,手机端能看到位置和报警状态。它适合两类人:一是做毕设或课设的学生,需要一个完整可演示的软硬件闭环;二是想快速搭一个可上报数据的定位终端、但不打算从零写协议栈的开发者。
2. 选型逻辑:F103C8T6 + NEO-6M + ESP8266 怎么配才对
拿到项目先别急着看代码,把硬件选型和引脚分配搞清楚,后面调试能省一半时间。这套设计用的是「STM32 主控 + 独立 GPS 模块 + 独立 WIFI 模块」的经典组合,理由很直接:每一层都可单独验证,哪一环出问题,拆开来测就知道。
2.1 主控选型:为什么是 F103C8T6 而不是 F407 或 Arduino
STM32F103C8T6 是 Crotex-M3 内核,72MHz 主频,64KB Flash、20KB RAM,价格和资源都卡在「刚好够用」的位置上。这套项目需要两个串口:一个给 GPS 收 NMEA 数据,一个给 ESP8266 发 AT 指令和业务数据;还需要一个 I2C 挂 MPU6050 和 OLED。F103C8T6 有 3 个 USART、2 个 I2C、20 多个 GPIO,完全覆盖需求,而且每一路外设都有独立中断,不会互相抢资源。
用 F407 当然性能更强,但毕设答辩时会被追问「为什么用这么贵的片子」;用 Arduino 倒是上手快,可底层寄存器、时钟树、中断优先级这些考点全没了。F103C8T6 处在「代码量适中、性能有余量、原理能讲透」的最佳区间。我一般这样分配引脚:
| 外设 | 接口 | 引脚 | 说明 |
|---|---|---|---|
| NEO-6M GPS | USART1 | PA9(TX) / PA10(RX) | 9600 波特率,8N1 |
| ESP8266-01S | USART2 | PA2(TX) / PA3(RX) | 先用 AT 指令联调 |
| MPU6050 | I2C1 | PB6(SCL) / PB7(SDA) | 地址 0x68 |
| OLED 显示屏 | 软件 I2C | PB8(SCL) / PB9(SDA) | SSD1306,0x3C |
| SOS 按键 | GPIO 输入 | PA0 | 外部中断下降沿触发 |
| 蜂鸣器 | GPIO 输出 | PA1 | 跌倒报警时鸣响 |
注意:USART1 的 PA9/PA10 同时也是板载 ST-Link 的串口通道,调试时容易和 GPS 数据混在一起。建议 GPS 用 USART1,调试打印走 USART3,三个串口分工明确,避免日志和业务数据互相污染。
2.2 GPS 与 WIFI 模块选型:NEO-6M、NEO-8M、ESP8266 的取舍
GPS 模块在毕设圈里最常见的是 NEO-6M 和 NEO-8M 系列。NEO-6M 便宜、资料多、默认输出 1Hz NMEA 数据,缺点是冷启动要 30 秒左右,室内基本收不到星;NEO-8M/M8N 支持多星座联合定位,搜星速度快一些,但 VCC/GND/TX/RX 四根线的接法和 NEO-6M 完全一致,默认波特率同样是 9600,代码可以直接复用。如果你手里是 NEO-M8N,重点检查模块上的天线接口是 IPEX 还是陶瓷天线,陶瓷天线的模块要尽量让天线面朝窗外。
WIFI 模块用 ESP8266-01S 是这套设计里比较稳妥的选择。它自带 TCP/IP 协议栈,STM32 只需要通过串口发 AT 指令就能联网,不需要在单片机里跑 lwIP 协议栈。这里有个关键参数容易踩坑:ESP8266 的峰值电流能到 300mA 以上,很多板子直接从 STM32 的 3.3V 引脚取电,结果模块一发射就重启。常见做法是单独用 AMS1117-3.3 从 5V 降压给 ESP8266 供电,并且在地线上和数字电路单点汇合。
2.3 数据流架构:一条链路串起定位、上报、报警
整个系统的数据流是这样的:GPS 模块每秒通过 USART1 输出一帧 NMEA 语句,STM32 在串口空闲中断里把完整帧收进缓冲区,解析出经纬度和定位状态;跌倒检测逻辑在 IMU 数据更新时跑一次,如果判定跌倒就置报警标志;主循环把「坐标 + 报警标志 + 设备 ID」拼成 JSON 字符串,通过 USART2 发给 ESP8266,ESP8266 走 MQTT 协议推上物联网平台;平台端再分发给手机小程序或 Web 页面。
这个设计里最值得注意的一点:GPS 数据是 1Hz 更新,但 MQTT 上报不需要 1Hz 频率,持续高频上报只会让 WIFI 模块发热、掉线、流量超标。我一般会在主循环里加一个 5 秒或 10 秒的上报节流,中间几次 GPS 更新只刷新本地 OLED 显示,不触发网络发送。这样既保证位置实时性,又给 ESP8266 留出发送完成的时间窗口。
3. 核心代码落地:GPS 解析、MQTT 上报与跌倒检测亲手写一遍
这一章是整套源码里最值得逐行读的部分。三个模块互相独立,先分别调通,再合到一起,是最高效的路径。
3.1 GPS 数据解析:从 NMEA-0183 里抠出经纬度
GPS 模块输出的不是现成的经纬度,而是一行行 NMEA 协议文本。我们只需要关注$GPRMC这条语句,它包含推荐定位信息:UTC 时间、定位状态、纬度、南北半球、经度、东西半球、速度、航向、日期。典型的原始数据长这样:
$GPRMC,103855.000,A,2230.1234,N,11403.5678,E,0.23,89.0,120924,,,A*57字段按逗号分隔:第 2 段是 UTC 时间,第 3 段是定位状态(A表示有效定位,V表示无效),第 4 段是纬度,第 5 段是半球,第 6 段是经度,第 7 段是半球。解析函数的核心逻辑是:先找到$GPRMC前缀,再用sscanf按格式切出字段,最后把度分格式转成小数度。
typedef struct { float lat; /* 纬度,小数度,北正南负 */ float lon; /* 经度,小数度,东正西负 */ uint8_t valid; /* 1 表示定位有效 */ } gps_info_t; /* 解析一帧 GPRMC 语句,成功返回 1,失败返回 0 */ uint8_t gps_parse_rmc(const char *nmea, gps_info_t *info) { char utc[16], lat_str[16], lon_str[16]; char ns, ew, status; double raw; /* 按格式提取:时间,状态,纬度,半球,经度,半球 */ if (sscanf(nmea, "$GPRMC,%[^,],%c,%[^,],%c,%[^,],%c", utc, &status, lat_str, &ns, lon_str, &ew) < 6) { return 0; } /* 状态是 V 说明没锁定卫星,数据不可用 */ if (status != 'A') { return 0; } /* 纬度格式 ddmm.mmmm:整数部分前两位是度,其余是分 */ raw = atof(lat_str); int lat_deg = (int)raw / 100; float lat_min = raw - lat_deg * 100; info->lat = lat_deg + lat_min / 60.0; if (ns == 'S') { info->lat = -info->lat; } /* 经度格式 dddmm.mmmm:整数部分前三位是度 */ raw = atof(lon_str); int lon_deg = (int)raw / 100; float lon_min = raw - lon_deg * 100; info->lon = lon_deg + lon_min / 60.0; if (ew == 'W') { info->lon = -info->lon; } info->valid = 1; return 1; }这里最容易写错的是度分转换。GPS 输出的纬度2230.1234表示 22 度 30.1234 分,正确结果是22 + 30.1234/60 ≈ 22.502057度。很多新手直接除以 100,得到 22.301234,地图上直接偏出去两三公里。代码里先取整除以 100 得到度,再用原值减去整度部分得到分,这个顺序不能乱。另外,%[^,]表示读取到逗号为止,%c直接读一个字符,这两者在sscanf里配合使用时要保证输入字符串里没有空格或回车混进来,所以收串时一定要按帧截断,不能把两行 NMEA 拼在一起解析。
3.2 物联网平台接入:ESP8266 走 MQTT 上报定位数据
WIFI 模块的接入分两步:第一步用 AT 指令联网并建立 TCP 连接,第二步在 TCP 之上跑 MQTT 协议。先看 AT 指令序列,这一步用串口助手手动发就能验证:
AT # 测试模块响应,返回 OK AT+CWMODE=1 # 设为 Station 模式 AT+CWJAP="YourSSID","YourPass" # 连接路由器,返回 WIFI CONNECTED AT+CIPSTART="TCP","broker.emqx.io",1883 # 建立 TCP 连接 AT+CIPMODE=1 # 进入透明传输模式 AT+CIPSEND # 开始发送数据在透明传输模式下,STM32 往串口写什么,ESP8266 就原样发给服务器,这给 MQTT 报文构造留出了空间。MQTT PUBLISH 报文的结构是:固定报头(0x30 表示 QoS0 发布)+ 剩余长度 + 主题长度 + 主题 + 消息体。剩余长度用变长编码,小于 128 直接写一个字节,大于 128 需要拆字节并在高位置 1。封装函数如下:
static uint8_t mqtt_buf[128]; /* MQTT 剩余长度编码:每字节低 7 位存数据,最高位表示是否还有后续字节 */ static uint16_t mqtt_encode_remain_len(uint8_t *buf, uint32_t len) { uint8_t i = 0; do { uint8_t byte = len % 128; len /= 128; if (len) { byte |= 0x80; /* 还有后续字节,最高位置 1 */ } buf[i++] = byte; } while (len); return i; } /* 发送一条 QoS0 的 PUBLISH 消息 */ void mqtt_publish(const char *topic, const char *payload) { uint16_t idx = 0; uint16_t remain = strlen(topic) + 2 + strlen(payload); uint8_t len_len; mqtt_buf[idx++] = 0x30; /* PUBLISH, QoS0 */ len_len = mqtt_encode_remain_len(&mqtt_buf[idx], remain); idx += len_len; /* 主题长度:高字节在前 */ mqtt_buf[idx++] = (strlen(topic) >> 8) & 0xFF; mqtt_buf[idx++] = strlen(topic) & 0xFF; memcpy(&mqtt_buf[idx], topic, strlen(topic)); idx += strlen(topic); /* 消息体 */ memcpy(&mqtt_buf[idx], payload, strlen(payload)); idx += strlen(payload); uart2_write(mqtt_buf, idx); /* 发往 ESP8266 透传通道 */ }注意:这套封装只适用于已经进入AT+CIPSEND透传模式的情况,不能直接在普通 AT 模式下调用。上报的数据我一般拼成 JSON,例如{"dev":"cane01","lat":22.502057,"lon":114.056780,"alarm":0},平台端解析字符串取字段即可。物联网平台的选择上,常见的有阿里云 IoT、OneNET、ThingsBoard、ThingLinks,毕设阶段我建议选文档全、能开免费测试账号的,优先把 Topic 和设备 ID 对上,Token 有效期要提前确认,否则答辩前一天平台连不上就尴尬了。
注意:公共 MQTT broker(如
broker.emqx.io)只适合本地联调。正式演示前把 Topic 改成带自己设备 ID 的唯一前缀,比如cane/user01/gps,避免同一网段下别人设备的数据混进来。
3.3 跌倒检测:加速度合成阈值加姿态确认
跌倒检测不能只看某一轴加速度,因为摔倒瞬间姿态千变万化。常见做法是取三轴加速度的合成矢量长度,当它超过阈值时先记录一个「冲击事件」,然后在接下来几秒内检查设备是否处于接近水平的静止状态,两者都满足才判跌倒。这个两段式判断能过滤掉大量误报。
#define FALL_ACC_TH 2.5f /* 加速度合成阈值,单位 g */ #define FALL_CONFIRM_MS 3000 /* 冲击后确认窗口,单位 ms */ #define FALL_TILT_DEG 45.0f /* 倾斜角阈值,单位度 */ static uint8_t fall_pending = 0; static uint32_t fall_start_ms = 0; /* MPU6050 每次更新后调用一次 */ void fall_check(float ax, float ay, float az) { /* 三轴合成矢量长度 */ float acc = sqrtf(ax * ax + ay * ay + az * az); /* 第一步:检测到剧烈冲击 */ if (acc > FALL_ACC_TH) { fall_pending = 1; fall_start_ms = HAL_GetTick(); } /* 第二步:3 秒内确认倒地 */ if (fall_pending && (HAL_GetTick() - fall_start_ms > FALL_CONFIRM_MS)) { /* 合成加速度接近 1g 表示静止,倾斜角大表示躺倒 */ if (acc < 1.2f && get_tilt_angle() > FALL_TILT_DEG) { trigger_alarm(); /* 拉高蜂鸣器引脚 + 置报警标志 */ } fall_pending = 0; } }参数上,FALL_ACC_TH取 2.5g 是实测比较舒服的值:正常走路时合成加速度一般在 1g 到 1.5g 之间波动,小跑能到 2g 左右,超过 2.5g 基本只有摔倒、撞击、剧烈跳跃才会出现。FALL_CONFIRM_MS取 3 秒,是因为摔倒后人体通常不会立刻爬起来,3 秒足够区分「摔倒后静止」和「弯腰后站直」。如果你给行动迟缓的老人用,阈值可以下调到 2.2g,窗口延长到 4 秒,减少漏报。
4. 避坑记录:GPS 搜星慢、WIFI 反复重启、误报警频发的真相
这套项目调试期最容易翻车的三个问题,我按现象、原因、解决的思路整理出来。每一条都是实际跑过的血泪经验,对照排查比瞎猜快得多。
4.1 GPS 定位相关:室内收不到星、坐标漂移几十米
现象:模块上电后串口一直输出$GPRMC,.......V,........,状态位是V,几分钟都不转A;偶尔转A了,地图上标记的位置漂到隔壁街区。
原因:GPS 信号需要开阔天空,室内靠窗都只能收到零星卫星,状态自然一直是V。至于漂移,民用 GPS 本身的精度就是 2.5 米左右,再加上城市环境里高楼反射造成的多路径效应,十几米偏移是正常范围,不是模块坏了。
解决:先把模块拿到阳台或窗外,天线面朝上,等 1 到 5 分钟让冷启动完成。想确认模块本身没问题,用 USB 转 TTL 接模块的 TX/RX,PC 上开串口助手或 U-Center,波特率设 9600,能看到 NMEA 语句就说明硬件正常。演示时别把天线贴在金属外壳上,馈点被遮挡会直接掉星。
4.2 WIFI 供电与连接:ESP8266 反复重启、MQTT 频繁掉线
现象:WIFI 模块一上电就打印乱码,或者连上路由器后每隔几秒重启一次;MQTT 连接建立后,三五分钟就断开一次。
原因:大部分是供电不足。ESP8266 发射瞬间电流能冲到 300mA,STM32 板载 3.3V LDO 通常只有 150mA 余量,电压一掉模块就复位。乱码则是供电不稳导致模块以错误波特率启动,或者 TX/RX 接反了。MQTT 掉线多半是没发心跳包,服务器在保活周期内没收到数据就主动断开。
解决:给 ESP8266 单独一路 AMS1117-3.3 供电,输入 5V,输出并一个 100uF 电解电容和 0.1uF 瓷片电容,实测能压住发射瞬间的电压跌落。接线确认:ESP8266 的 TX 接 STM32 的 RX,RX 接 TX,GND 必须共地。MQTT 端把 KeepAlive 设成 60 秒,并在主循环里每 30 秒发一次 PINGREQ 包,或者干脆每 10 秒上报一次数据,让服务器看到活动。
4.3 跌倒检测与数据帧:正常走路误报警、串口收到半个包
现象:正常走路时蜂鸣器突然响;用串口调试时,GPS 解析函数偶尔返回 0,数据像是断了。
原因:误报是因为只做了加速度阈值判断,弯腰捡东西或者猛地坐下也会超过 2.5g。串口半个包是因为 GPS 是 1Hz 输出一帧,每帧约 70 字节,如果主循环轮询时恰好读到一半就开始解析,strstr找不到$GPRMC头,自然返回失败。
解决:跌倒检测必须加两段式确认,先冲击后静止,再看倾斜角,三重条件缺一不可。串口接收改成 DMA + 空闲中断,收到完整一帧后再置标志位,主循环只在标志位有效时解析。这个改动对 GPS 这类「固定频率、固定帧长」的数据源特别有效。
5. 联调与演示:从裸板到能答辩的完整清单
代码全部写完不等于项目完成,联调阶段的顺序和检查项决定了最后演示成不成功。我把整个流程压缩成三组动作,按顺序走一遍,基本能覆盖 90% 的现场问题。
5.1 上电前检查清单:五个最容易忽略的硬伤
| 检查项 | 检查内容 | 翻车后果 |
|---|---|---|
| 供电 | GPS、ESP8266 是否独立供电 | 模块反复重启 |
| 串口交叉 | TX 接对方 RX,RX 接对方 TX | 收不到任何数据 |
| 共地 | 所有模块 GND 必须连一起 | 串口乱码、电平飘 |
| GPS 天线 | 陶瓷天线面朝开阔方向 | 状态一直为 V |
| 调试器 | USB 转 TTL 与 ST-Link 不要同时供电 | 电平打架烧串口 |
5.2 串口实测:先看原始 NMEA,再谈解析
把 STM32 的调试串口(USART3)接到 USB 转 TTL,波特率 115200,复位后应该能看到类似下面的输出:
[DEBUG] GPRMC raw: $GPRMC,103855.000,A,2230.1234,N,11403.5678,E,0.23,89.0,120924,,,A*57 [INFO ] parse ok, lat=22.502057, lon=114.056780 [INFO ] mqtt publish: {"dev":"cane01","lat":22.502057,"lon":114.056780,"alarm":0}如果第一行能看到原始 NMEA,说明 GPS 硬件链路通;如果parse ok出现,说明协议解析没问题;如果mqtt publish后面跟了正常 JSON,说明 ESP8266 链路通。这三行输出分别对应三个环节,哪一行断掉就缩小到对应模块排查。实践中我发现大多数人卡在第二行:GPRMC raw能看到但一直V,这种是天线位置问题,而不是代码问题。
5.3 演示兜底:室内收不到星、现场断网怎么办
答辩现场最大的不确定性是环境,我见过不止一次演示现场 GPS 死活搜不到星、教室 WIFI 连不上。提前做三个兜底:第一,用手机开热点,热点名字和密码提前写进代码,现场直接连手机;第二,在代码里加一个「模拟坐标」开关,GPS 无效时自动填充一组预先设定的坐标,至少保证数据链路可视;第三,物联网平台端提前登录好,演示时如果网络抖动,直接切到平台的设备历史数据页面讲,也不影响效果。
6. 进阶玩法:地图打点与低功耗改造,两招让作品变产品
6.1 把 GPS 坐标实时投到网页地图
项目做到能上报数据还不够,加分项在于让评委直观看到「拐杖在地图上动」。思路是让物联网平台把 MQTT 消息转发到本地服务器,或者直接在 PC 上跑一个 MQTT 订阅脚本,把经纬度转成网页标记。本地验证时可以用 Python 订阅:
import paho.mqtt.client as mqtt def on_message(client, userdata, msg): print(msg.payload.decode()) # 拿到 JSON 后解析出 lat/lon client = mqtt.Client() client.on_message = on_message client.connect("localhost", 1883, 60) client.subscribe("smarthome/cane/gps") client.loop_forever()拿到坐标后,前端用 Leaflet 的L.marker([lat, lon]).addTo(map)就能把位置打在地图上,配合setInterval每 5 秒拉一次数据,轨迹就实时画出来了。这里有个细节:GPS 输出的是 WGS-84 坐标,直接叠加到高德或百度地图会有几百米偏差,Leaflet 默认底图用 OpenStreetMap 是同一坐标系,不用转换,选底图时注意避开国内商业地图。
6.2 低功耗改造:从全速跑 10 小时到待机 40 小时
原版代码是所有外设一直全速运行,实测整机电流轻松超过 200mA,2000mAh 电池撑不到一天。改造思路是让大部分外设「按需唤醒」:GPS 模块保持常开,因为它要持续接收卫星信号(NEO-6M 大约 30mA);STM32 进 Stop 模式,用 RTC 定时 30 秒唤醒一次;每次唤醒只做三件事——读取 GPS 最新坐标、唤醒 ESP8266 发送一次数据、再睡过去。ESP8266 在发送间隙用AT+GSLP进睡眠。这样整机平均电流能压到 35mA 到 50mA,2000mAh 锂电可以跑到 40 小时以上。
这套改造的收益在毕设答辩时非常直观:当你现场展示「实测待机电流 38mA、理论续航 52 小时」时,评委的关注点会从「会不会做」转向「怎么做更好」。从那以后我每次做带电池的嵌入式项目,硬件定型前都会先画一遍休眠路径,想清楚谁保持供电、谁被关掉、谁负责唤醒,再动手写应用代码。这套方法在拐杖项目上帮我省掉了后面大量的返工,希望帮到你。
本文还有配套的精品资源,点击获取