简介面向电子信息、计算机等专业学生的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 检测跌倒按键触发 SOSWIFI 模块把数据推上物联网平台手机端能看到位置和报警状态。它适合两类人一是做毕设或课设的学生需要一个完整可演示的软硬件闭环二是想快速搭一个可上报数据的定位终端、但不打算从零写协议栈的开发者。2. 选型逻辑F103C8T6 NEO-6M ESP8266 怎么配才对拿到项目先别急着看代码把硬件选型和引脚分配搞清楚后面调试能省一半时间。这套设计用的是「STM32 主控 独立 GPS 模块 独立 WIFI 模块」的经典组合理由很直接每一层都可单独验证哪一环出问题拆开来测就知道。2.1 主控选型为什么是 F103C8T6 而不是 F407 或 ArduinoSTM32F103C8T6 是 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 GPSUSART1PA9(TX) / PA10(RX)9600 波特率8N1ESP8266-01SUSART2PA2(TX) / PA3(RX)先用 AT 指令联调MPU6050I2C1PB6(SCL) / PB7(SDA)地址 0x68OLED 显示屏软件 I2CPB8(SCL) / PB9(SDA)SSD13060x3CSOS 按键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 发给 ESP8266ESP8266 走 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 ATCWMODE1 # 设为 Station 模式 ATCWJAPYourSSID,YourPass # 连接路由器返回 WIFI CONNECTED ATCIPSTARTTCP,broker.emqx.io,1883 # 建立 TCP 连接 ATCIPMODE1 # 进入透明传输模式 ATCIPSEND # 开始发送数据在透明传输模式下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 透传通道 */ }注意这套封装只适用于已经进入ATCIPSEND透传模式的情况不能直接在普通 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/RXPC 上开串口助手或 U-Center波特率设 9600能看到 NMEA 语句就说明硬件正常。演示时别把天线贴在金属外壳上馈点被遮挡会直接掉星。4.2 WIFI 供电与连接ESP8266 反复重启、MQTT 频繁掉线现象WIFI 模块一上电就打印乱码或者连上路由器后每隔几秒重启一次MQTT 连接建立后三五分钟就断开一次。原因大部分是供电不足。ESP8266 发射瞬间电流能冲到 300mASTM32 板载 3.3V LDO 通常只有 150mA 余量电压一掉模块就复位。乱码则是供电不稳导致模块以错误波特率启动或者 TX/RX 接反了。MQTT 掉线多半是没发心跳包服务器在保活周期内没收到数据就主动断开。解决给 ESP8266 单独一路 AMS1117-3.3 供电输入 5V输出并一个 100uF 电解电容和 0.1uF 瓷片电容实测能压住发射瞬间的电压跌落。接线确认ESP8266 的 TX 接 STM32 的 RXRX 接 TXGND 必须共地。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 接对方 RXRX 接对方 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, lat22.502057, lon114.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 小时原版代码是所有外设一直全速运行实测整机电流轻松超过 200mA2000mAh 电池撑不到一天。改造思路是让大部分外设「按需唤醒」GPS 模块保持常开因为它要持续接收卫星信号NEO-6M 大约 30mASTM32 进 Stop 模式用 RTC 定时 30 秒唤醒一次每次唤醒只做三件事——读取 GPS 最新坐标、唤醒 ESP8266 发送一次数据、再睡过去。ESP8266 在发送间隙用ATGSLP进睡眠。这样整机平均电流能压到 35mA 到 50mA2000mAh 锂电可以跑到 40 小时以上。这套改造的收益在毕设答辩时非常直观当你现场展示「实测待机电流 38mA、理论续航 52 小时」时评委的关注点会从「会不会做」转向「怎么做更好」。从那以后我每次做带电池的嵌入式项目硬件定型前都会先画一遍休眠路径想清楚谁保持供电、谁被关掉、谁负责唤醒再动手写应用代码。这套方法在拐杖项目上帮我省掉了后面大量的返工希望帮到你。本文还有配套的精品资源点击获取