资讯动态

STM32 GPS定位终端实战:NMEA解析与OLED显示全链路打通

发布时间:2026/10/20 17:31:37 来源:尧图企业网站定制
简介这是一套面向嵌入式初学者与物联网开发者的STM32GPS定位系统实战工程围绕STM32微控制器、GPS模块与OLED显示屏的协同工作展开可用于车载导航、户外运动设备等场景的原型验证。压缩包共152个文件约2.09MB以C源码与头文件为主体包含STM32外设驱动、GPS数据解析及OLED显示相关实现并附带Keil工程文件、编译中间产物与烧录脚本便于直接打开工程编译调试。资源重点演示了UART接口初始化、NMEA-0183语句接收与解析、经纬度与时间信息提取以及通过I2C或SPI驱动OLED实时刷新的完整流程同时涉及错误检测与异常处理思路。目前已有7482人学习下载适合希望快速跑通GPS定位链路、理解串口通信与显示驱动配合的开发者参考借鉴。1. 从一块 GPS 模块说起为什么 STM32 做定位终端是性价比最高的起点如果你正在找一份能直接跑通的 STM32 GPS 定位方案大概率遇到过这种情况模块买回来串口助手能收到一堆乱码换几个波特率试到怀疑人生好不容易读出经纬度OLED 上却显示成一行天书。这套方案要解决的就是把「GPS 模块出数据 → STM32 解析 → OLED 显示」这条链路完整打通并且做到可复现、可扩展、可验收。它适合正在做嵌入式毕设或课程设计的人也适合需要快速搭一个定位终端原型的工程师。整套方案围绕 STM32 主控、GPS 模块、OLED 显示三个核心件展开不依赖复杂云平台单板就能独立运行调试过程全部本地可见这对需要答辩演示的场景尤其友好。2. 需求拆解与技术选型主控、定位模块、显示方案怎么定2.1 功能需求与非功能需求分开列先把需求写清楚后面选型和验收才有依据。这个项目的功能需求通常包括实时接收 GPS 经纬度、UTC 时间、速度、卫星数把关键字段解析成可读格式在 OLED 上分页或分行显示串口输出原始 NMEA 语句供调试。非功能需求容易被忽略但答辩时老师常问定位刷新率能不能到 1Hz冷启动首次定位时间多长OLED 刷新时会不会闪供电是 3.3V 还是 5V这些指标直接决定你选哪块模块、用哪种驱动方式。我一般会把需求整理成一张表左边是功能点右边是对应的验收方式。比如「经纬度解析正确」对应「与手机地图对比误差小于 10 米」「OLED 显示不闪」对应「连续观察 5 分钟无肉眼可见抖动」。这样后面写论文或做答辩 PPT 时直接拿这张表当依据不用临时编。2.2 主控选型为什么是 STM32F103 而不是别的STM32F103C8T6 是这个场景里最常见的选型原因很实际双串口够用一个接 GPS一个接调试I2C 硬件外设直接驱动 OLED72MHz 主频解析 NMEA 语句绰绰有余Flash 64KB、RAM 20KB 对纯定位显示来说有富余。更关键的是资料多HAL 库和标准库的例程都能找到卡住的时候不至于孤立无援。如果你手头是 STM32F401 或 F411也完全可以性能更强但要注意 HAL 库版本差异导致的 I2C 时序问题。选型时不要只看主频要看外设资源够不够、社区资料多不多、你手头的下载器支不支持。我见过有人用某款冷门国产 MCU 做定位终端结果 I2C 驱动调了两周最后换回 F103 一天跑通这就是选型踩坑的典型。2.3 GPS 模块与 OLED 的搭配逻辑GPS 模块常见的是 ATGM336H、NEO-6M、NEO-M8N 这几类输出都是 NMEA 0183 协议波特率默认 9600。区别在于定位精度、冷启动时间和抗干扰能力。NEO-M8N 支持多星座冷启动约 26 秒NEO-6M 便宜但冷启动要 27 秒以上且单 GPS 星座在遮挡环境下容易丢星。如果答辩场地在室内建议提前在窗边或室外完成首次定位或者选带多星座的模块。OLED 用 0.96 寸 I2C 接口的最省事SSD1306 驱动芯片分辨率 128×64足够显示经纬度、时间、卫星数。SPI 接口的刷新更快但占引脚多I2C 两根线搞定刷新率对 1Hz 的定位数据完全够用。注意 OLED 供电是 3.3V和 STM32 共电源不要接 5V否则可能烧屏。提示GPS 模块的 TX 接 STM32 的 RXRX 接 STM32 的 TX别接反。模块的 PPS 引脚可以不接但如果你想做秒脉冲同步可以引出来接外部中断。3. 总体架构设计数据流、模块划分与引脚分配3.1 三层数据流采集层、解析层、显示层整个系统按数据流分三层。采集层负责从 GPS 模块串口读原始字节用空闲中断或 DMA 接收避免丢包。解析层把 NMEA 语句按逗号拆分提取 GPRMC 或 GNRMC 里的经纬度、时间、速度做度分格式转换。显示层把解析结果格式化后写入 OLED 显存按页刷新。这三层之间用全局结构体传递数据结构体里放经纬度浮点数、时间字符串、卫星数、定位有效标志。解析层只写结构体显示层只读结构体采集层只负责填原始缓冲区。这样解耦的好处是换 GPS 模块只改解析层换屏幕只改显示层主逻辑不动。3.2 模块划分与文件组织工程目录我一般这样分Core/放 main.c 和中断处理Drivers/GPS/放 gps.c 和 gps.h负责串口接收和 NMEA 解析Drivers/OLED/放 oled.c 和 oled.h负责 I2C 初始化和字符显示App/放 display_task.c负责把 GPS 数据映射到 OLED 布局。每个模块对外只暴露一个初始化函数和一个数据获取函数内部实现细节不跨文件引用。这种分法在答辩时很占优势老师问「如果换一个 GPS 模块要改哪里」你直接指到Drivers/GPS/目录说只改解析函数和波特率配置其他不动。模块化不是口号是让改动范围可控。3.3 引脚分配与资源占用表功能引脚外设备注GPS TX → MCU RXPA10USART1_RX9600 波特率GPS RX ← MCU TXPA9USART1_TX可不用但建议接OLED SCLPB6I2C1_SCL400kHzOLED SDAPB7I2C1_SDA400kHz调试串口 TXPA2USART2_TX115200调试串口 RXPA3USART2_RX115200引脚分配要在设计阶段定死不要边写边改。我习惯在main.h里用宏定义把引脚和端口写清楚比如#define GPS_UART huart1这样换串口只改一处。4. 关键模块实现NMEA 解析、OLED 驱动与主循环调度4.1 串口接收空闲中断加 DMA 的配置GPS 模块每秒输出多行 NMEA 语句总长度约 500 字节。用逐字节中断接收会频繁打断主循环推荐空闲中断加 DMA。配置步骤如下先初始化 USART1 为 9600 波特率开启 DMA 接收通道缓冲区设 256 字节再使能空闲中断在中断回调里计算接收长度置标志位重启 DMA。// gps.c 串口空闲中断回调 void GPS_UART_IRQHandler(void) { if (__HAL_UART_GET_FLAG(huart1, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(huart1); // 停止 DMA 以获取当前接收计数 HAL_UART_DMAStop(huart1); uint16_t len GPS_BUF_SIZE - __HAL_DMA_GET_COUNTER(huart1.hdmarx); gps_rx_len len; gps_rx_ready 1; // 通知主循环有新数据 // 重新启动 DMA 接收 HAL_UART_Receive_DMA(huart1, gps_rx_buf, GPS_BUF_SIZE); } }这段代码的关键点是空闲中断触发时DMA 可能还没填满缓冲区所以要先停 DMA 再读计数器算出实际接收长度。gps_rx_ready标志位让主循环知道什么时候去解析避免在中断里做耗时操作。缓冲区大小 256 字节是经验值NMEA 单行最长约 80 字节留足余量防止溢出。4.2 NMEA 语句解析从 GPRMC 提取经纬度NMEA 语句里最有用的字段在 GPRMC 或 GNRMC 行。格式是$GNRMC,时间,状态,纬度,南北,经度,东西,速度,航向,日期,...。解析时先找$GNRMC或$GPRMC开头再按逗号拆分检查状态位是否为A有效定位然后把度分格式转成十进制。// 解析 GNRMC 语句提取经纬度和时间 int parse_gnrmc(char *line, gps_data_t *out) { char *p line; if (strncmp(p, $GNRMC, 6) ! 0 strncmp(p, $GPRMC, 6) ! 0) return -1; // 跳过语句头按逗号分段 for (int i 0; i 2; i) { p strchr(p, ,); if (!p) return -1; p; } // 状态位A 表示有效定位 if (*p ! A) return -1; p strchr(p, ,) 1; // 纬度ddmm.mmmm 格式 double lat atof(p); p strchr(p, ,) 1; char ns *p; p strchr(p, ,) 1; double lon atof(p); p strchr(p, ,) 1; char ew *p; // 度分转十进制 out-latitude (int)(lat / 100) fmod(lat, 100) / 60.0; out-longitude (int)(lon / 100) fmod(lon, 100) / 60.0; if (ns S) out-latitude -out-latitude; if (ew W) out-longitude -out-longitude; out-valid 1; return 0; }解析函数只做一件事从一行字符串里提取有效数据。注意atof在嵌入式里可能引入较大代码体积如果 Flash 紧张可以自己写一个简单的字符串转浮点函数。状态位判断不能省否则模块还没定位成功时你会读到空经纬度OLED 上显示 0.0000看起来像坏了。4.3 OLED 显示布局四行信息怎么排128×64 的屏幕用 8×16 字体可以显示 4 行每行 16 个字符。我一般这样排第一行显示纬度第二行显示经度第三行显示 UTC 时间和卫星数第四行显示定位状态和速度。刷新时只更新变化区域不要整屏清空重写否则会闪。// display_task.c 刷新 OLED 显示 void display_update(gps_data_t *gps) { char buf[20]; if (!gps-valid) { OLED_ShowString(0, 0, Waiting GPS...); return; } snprintf(buf, sizeof(buf), Lat:%.4f, gps-latitude); OLED_ShowString(0, 0, buf); snprintf(buf, sizeof(buf), Lon:%.4f, gps-longitude); OLED_ShowString(0, 2, buf); snprintf(buf, sizeof(buf), UTC:%s S:%d, gps-utc_time, gps-satellites); OLED_ShowString(0, 4, buf); snprintf(buf, sizeof(buf), Spd:%.1fkm/h, gps-speed); OLED_ShowString(0, 6, buf); }OLED_ShowString的坐标参数是页和列页 0 到 7 对应 64 行。这里每行间隔 2 页留出空隙方便阅读。snprintf比sprintf安全能防止缓冲区溢出。如果显示内容抖动检查是不是每帧都调用了清屏函数改成只覆盖固定长度字符串即可。4.4 主循环调度解析与显示的时序安排主循环里不要用delay阻塞否则 GPS 数据来了没法及时处理。我一般这样写先检查gps_rx_ready有数据就解析再检查解析结果是否有效有效就更新显示最后留一点时间给其他任务。整个循环周期控制在 50ms 以内保证 1Hz 的定位数据不丢。// main.c 主循环 while (1) { if (gps_rx_ready) { gps_rx_ready 0; // 按行拆分缓冲区逐行解析 char *line strtok((char *)gps_rx_buf, \r\n); while (line) { parse_gnrmc(line, gps_data); line strtok(NULL, \r\n); } } if (gps_data.valid) { display_update(gps_data); } HAL_Delay(50); // 留出时间给其他任务 }strtok会修改原缓冲区所以gps_rx_buf不能是const。如果不想用strtok可以自己写一个按行分割的函数避免线程安全问题。HAL_Delay(50)不是必须的但能降低功耗对电池供电的场景有用。5. 避坑与常见问题排查从乱码到丢星的五个翻车点5.1 串口收到乱码波特率与电平不匹配现象串口助手打开全是乱码换几个波特率偶尔能读出半行。原因通常是 GPS 模块默认波特率不是 9600或者模块输出电平是 5V 而 STM32 是 3.3V。解决先查模块手册确认默认波特率用 USB 转串口直接接模块确认能读出完整 NMEA 语句再检查模块 VCC 供电是否稳定有些模块在 3.3V 下工作不正常需要 5V 供电但 TX 电平是 3.3V。5.2 定位一直无效天线与冷启动时间现象OLED 显示Waiting GPS...串口能看到 NMEA 语句但状态位是V。原因通常是天线没接好、室内信号太弱、或者冷启动时间不够。解决把天线放到窗外或室外等待至少 30 秒检查天线接口是否拧紧有源天线要确认供电正常如果用的是陶瓷天线注意方向要朝上。5.3 OLED 不亮或花屏I2C 地址与初始化时序现象OLED 完全不亮或者显示乱码方块。原因可能是 I2C 地址不对0x78 或 0x7A、初始化延时不够、或者 SCL/SDA 接反。解决用 I2C 扫描程序确认设备地址在初始化后加 100ms 延时再发显示命令检查上拉电阻是否焊接有些模块自带 4.7k 上拉有些没有。5.4 数据刷新卡顿中断里做太多事现象OLED 刷新明显卡顿GPS 数据偶尔丢包。原因是在串口中断里直接做了解析或显示导致中断时间过长。解决中断里只置标志位和拷贝数据解析和显示放到主循环如果主循环还是慢把 OLED 刷新降到 2Hz定位数据缓存最新一帧即可。5.5 经纬度偏差大度分转换算错现象显示经纬度和实际位置差几十公里。原因是度分转换公式用错把ddmm.mmmm直接当十进制用了。解决确认公式是度 整数部分 小数部分 / 60南北纬和东西经的符号不要漏。可以用手机地图查一个已知坐标对比串口输出的原始 NMEA 语句逐步核对。6. 验收与答辩怎么证明这套定位终端真的能用验收分三步走。第一步串口验证用 USB 转串口接 GPS 模块在电脑上确认能收到完整 NMEA 语句状态位为A卫星数大于 4。第二步单板验证把模块接到 STM32OLED 上能稳定显示经纬度、时间、卫星数连续观察 10 分钟不丢星、不花屏。第三步精度验证把显示坐标和手机地图对比误差在 10 米以内算合格如果偏差大检查度分转换和天线位置。答辩时老师常问三个问题为什么选这个主控定位数据怎么保证实时性如果换一个 GPS 模块要改哪里第一个问题按第 2 章的选型逻辑回答第二个问题讲空闲中断加 DMA 的接收机制强调不阻塞主循环第三个问题指到模块化目录结构说只改解析层。提前把这三个答案写进 PPT 备注现场不慌。验收项合格标准测试方法串口输出完整 NMEA 语句状态位 AUSB 转串口接电脑OLED 显示四行信息稳定无花屏连续观察 10 分钟定位精度与地图对比误差 10 米室外开阔处对比刷新率不低于 1Hz秒表计时观察时间跳变冷启动首次定位 60 秒断电重启计时我一般会在答辩前把整套流程走三遍第一遍在实验室窗边第二遍在室外空地第三遍断电重启模拟现场演示。每次记录冷启动时间和卫星数如果第三次明显变慢说明备份电池没电了换一块 CR1220 就好。从那以后我每次做定位类项目都强制在验收表里加一行「冷启动时间」不测三遍不写结论。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑