
简介这是一套面向嵌入式初学者与物联网开发者的STM32GPS定位系统学习资料围绕STM32微控制器、GPS模块与OLED显示屏的协同工作展开可用于车载导航、户外运动设备等场景的原型开发。资源包共152个文件约2.09MB以C源码与头文件为主体包含37个.h、34个.c另有Keil工程相关的.uvprojx、.uvoptx、.sct、.map、.axf、.hex等编译与烧录文件以及若干备份与批处理脚本便于直接打开工程编译验证。压缩包中的示例程序演示了GPS数据的接收与解析并通过串口输出结果读者可据此学习UART通信配置、NMEA-0183语句解析、经纬度与时间信息提取以及OLED显示刷新等关键环节。目前已有7482人学习下载适合希望快速搭建定位设备、理解GPS数据流处理流程的开发者参考借鉴。1. 从一块 GPS 模块说起为什么 STM32 方案值得认真做一遍很多人第一次接触定位项目脑子里想的是「买个模块插上就能用」。真动手才发现串口读出来的不是经纬度而是一堆以$GNGGA开头的乱码好不容易解析出坐标OLED 上显示的却是0.000000。STM32 加 GPS 加 OLED 这套组合看着简单实际上把嵌入式开发里最典型的几件事全串起来了串口通信、协议解析、定时刷新、显示驱动、电源管理。它适合正在做毕设或课设的学生也适合想快速搭一个定位终端原型的工程师。这套方案要解决的就是让定位数据从天线进来、经过 MCU 处理、稳定地显示在屏幕上并且能说清楚每一步为什么这么做。2. 需求拆解与技术选型定位终端到底要跑通哪几件事2.1 功能需求与非功能需求分开列做方案第一步不是画图是把需求写清楚。定位终端的功能需求通常包括接收卫星定位数据、解析出经纬度和时间、在 OLED 上实时显示、支持按键切换显示页面、可选地把数据通过串口转发给上位机。非功能需求容易被忽略但它决定方案能不能验收定位冷启动时间、坐标刷新频率、OLED 刷新是否闪烁、整机功耗、断电后能否恢复默认状态。我一般会把需求整理成一张表左边是功能点右边是对应的验收指标。比如「实时显示」这种模糊说法要落到「定位成功后 1 秒内刷新一次屏幕」「低功耗」要落到「待机电流小于多少毫安」。没有指标的方案答辩时老师一问就露馅。需求类型具体项验收指标功能卫星数据接收串口稳定接收无丢帧功能坐标解析经纬度误差在模块标称范围内功能OLED 显示刷新周期 1 秒无肉眼可见闪烁非功能冷启动定位开阔环境下 60 秒内定位成功非功能供电3.3V 供电整机电流可控2.2 主控与模块选型为什么是 STM32 加串口 GPS主控选 STM32 的理由很实际串口资源多、定时器够用、HAL 库成熟、资料好找。具体型号上F103 系列是课设里最常见的资源足够跑一个定位终端如果要做低功耗版本L 系列更合适。GPS 模块这边常见的是支持 NMEA 协议的串口模块默认波特率 9600输出$GNGGA、$GNRMC等语句。OLED 选 0.96 寸 I2C 接口的最省事两根线就能驱动分辨率 128×64显示经纬度和时间绰绰有余。这里有个选型细节I2C 地址通常是 0x78 或 0x3C买回来先用扫描程序确认不然初始化失败会让人怀疑人生。提示GPS 模块的供电和天线摆放对定位影响很大模块尽量远离 MCU 晶振和电源电感天线朝上并保持开阔。2.3 通信协议与数据格式确认NMEA 协议是这套方案的通信基础。$GNGGA语句里包含时间、纬度、经度、定位质量、卫星数、海拔等信息字段用逗号分隔。纬度格式是ddmm.mmmm经度是dddmm.mmmm需要转换成十进制度数才能用。定位质量字段为 0 表示未定位为 1 表示单点定位这个字段是判断数据是否可用的关键。// NMEA GGA 语句示例 // $GNGGA,123519.00,3958.12345,N,11618.54321,E,1,08,0.9,545.4,M,46.9,M,,*47 // 字段含义时间,纬度,南北,经度,东西,定位质量,卫星数,HDOP,海拔,...解析时不能直接按固定位置截取因为卫星数变化会导致字段长度不同。稳妥的做法是按逗号分割逐字段取值。这个细节后面在解析模块里会展开。3. 总体架构设计三层结构与四个模块的职责划分3.1 硬件连接与资源分配硬件上GPS 模块通过 USART2 与 STM32 相连OLED 走 I2C1按键接在普通 GPIO 上并开启外部中断。电源部分用 3.3V 稳压GPS 模块单独加滤波电容。资源分配要在方案里写清楚因为答辩时老师常问「你用了哪些外设、引脚怎么分的」。外设引脚用途USART2PA2/PA3GPS 数据接收I2C1PB6/PB7OLED 显示EXTIPC13按键切换页面TIM2内部定时刷新与超时判断3.2 软件分层驱动层、解析层、应用层软件分三层。驱动层负责串口和 I2C 的初始化与读写解析层负责从 NMEA 语句里提取有效数据应用层负责调度显示、处理按键、管理状态。分层的意义在于换一个 GPS 模块只需要改解析层换一块屏幕只需要改驱动层应用逻辑不动。// 分层结构示意 // driver_uart.c - 串口收发 // driver_oled.c - 屏幕驱动 // nmea_parser.c - 协议解析 // app_main.c - 业务调度3.3 数据流与状态机设计数据流是串口中断收到一个字节存入环形缓冲区主循环检测到完整语句后交给解析层解析层返回结构体应用层判断定位状态并刷新 OLED。状态机分三个状态等待定位、定位成功、定位丢失。定位丢失超过一定时间要清空屏幕上的坐标避免显示过期数据误导使用者。typedef enum { STATE_WAIT_FIX, // 等待定位 STATE_FIXED, // 定位成功 STATE_LOST // 定位丢失 } gps_state_t; typedef struct { double latitude; // 纬度十进制度 double longitude; // 经度十进制度 uint8_t fix_quality; // 定位质量 uint8_t satellites; // 卫星数 char utc_time[10]; // UTC 时间 } gps_data_t;这个结构体是整份方案的数据核心解析层填它应用层读它。字段类型要选对经纬度用 double 保证精度时间和卫星数用整型或字符数组。4. 关键模块实现串口接收、NMEA 解析与 OLED 刷新4.1 串口环形缓冲区与中断接收GPS 模块会持续输出数据不能在主循环里阻塞读取。做法是开串口接收中断每收到一个字节就存入环形缓冲区主循环再从缓冲区里找完整语句。环形缓冲区的大小要能装下至少两条完整语句通常 256 字节够用。#define RX_BUF_SIZE 256 typedef struct { uint8_t buffer[RX_BUF_SIZE]; volatile uint16_t head; volatile uint16_t tail; } ring_buf_t; ring_buf_t gps_rx; // 串口中断回调每收到一个字节调用一次 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART2) { uint16_t next (gps_rx.head 1) % RX_BUF_SIZE; if (next ! gps_rx.tail) { // 缓冲区未满 gps_rx.buffer[gps_rx.head] rx_byte; gps_rx.head next; } HAL_UART_Receive_IT(huart2, rx_byte, 1); // 重新开启接收 } }这里的关键点是head和tail用volatile修饰因为它们在中断和主循环里都会被访问。缓冲区满时丢弃新数据而不是覆盖旧数据保证语句完整性。中断里重新开启接收是 HAL 库的固定套路漏掉这一句就只收得到一个字节。4.2 NMEA 语句解析按逗号分割而不是按位置截取解析$GNGGA时先判断语句头再按逗号分割字段。下面是一个精简的解析函数重点看字段索引和校验处理。#include string.h #include stdlib.h // 解析 GGA 语句成功返回 0 int parse_gga(const char *line, gps_data_t *out) { if (strncmp(line, $GNGGA, 6) ! 0 strncmp(line, $GPGGA, 6) ! 0) { return -1; // 不是 GGA 语句 } char buf[128]; strncpy(buf, line, sizeof(buf) - 1); buf[sizeof(buf) - 1] \0; char *fields[15] {0}; int idx 0; char *token strtok(buf, ,); while (token ! NULL idx 15) { fields[idx] token; token strtok(NULL, ,); } if (idx 10) return -1; // 字段不足 // 定位质量0 表示未定位 out-fix_quality atoi(fields[6]); if (out-fix_quality 0) return -1; out-satellites atoi(fields[7]); strncpy(out-utc_time, fields[1], 9); out-utc_time[9] \0; // 纬度 ddmm.mmmm 转十进制度 double raw_lat atof(fields[2]); int lat_deg (int)(raw_lat / 100); out-latitude lat_deg (raw_lat - lat_deg * 100) / 60.0; if (fields[3][0] S) out-latitude -out-latitude; // 经度 dddmm.mmmm 转十进制度 double raw_lon atof(fields[4]); int lon_deg (int)(raw_lon / 100); out-longitude lon_deg (raw_lon - lon_deg * 100) / 60.0; if (fields[5][0] W) out-longitude -out-longitude; return 0; }这段代码有几个容易翻车的点。第一strtok会修改原字符串所以要先拷贝一份。第二纬度除 100 取整得到度余下部分除以 60 得到分这是 NMEA 的固定换算。第三南纬和西经要取负值漏掉这一步坐标就跑到另一个半球了。第四定位质量为 0 时直接返回失败不要把无效坐标显示出去。4.3 OLED 显示驱动与刷新策略OLED 用 I2C 驱动初始化后按页写入显存。显示内容分两页第一页显示经纬度第二页显示时间和卫星数。刷新策略上不要每次收到数据就刷屏那样会闪。做法是用定时器每 1 秒置一个标志位主循环检测到标志位再刷新。void oled_show_gps(const gps_data_t *data) { char line[24]; OLED_Clear(); snprintf(line, sizeof(line), Lat:%.6f,>volatile uint8_t page_flag 0; uint8_t current_page 0; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin GPIO_PIN_13) { page_flag 1; // 只置标志不处理 } } // 主循环中 if (page_flag) { page_flag 0; current_page (current_page 1) % 2; oled_refresh_page(current_page); }5. 避坑与常见问题排查那些让定位终端翻车的细节5.1 串口只收到一个字节就停了现象是串口中断进了一次就再也不进。原因通常是 HAL 库的接收中断是一次性的回调里没有重新调用HAL_UART_Receive_IT。解决办法是在回调末尾重新开启接收或者改用 DMA 加空闲中断的方式后者更适合高速数据流。5.2 坐标显示为 0 或乱码现象是 OLED 上经纬度一直是 0。原因可能是解析时字段索引错了或者定位质量字段为 0 时没有正确返回。排查方法是把原始 NMEA 语句通过串口打印出来对照字段逐个核对。另外要确认波特率GPS 模块默认 9600如果 MCU 设成 115200 就会收到乱码。5.3 冷启动定位慢或定不上现象是上电后很久定不上位。原因可能是天线摆放不好、供电不稳、或者模块处于室内。解决办法是把天线放到窗边或室外检查供电电压是否稳定必要时给模块加一个大电容。冷启动本身就需要时间方案里要写清楚这是正常现象不是代码问题。5.4 OLED 闪烁或显示残影现象是屏幕刷新时闪烁。原因是刷新频率太高或者每次全屏清除。解决办法是降低刷新频率到 1 秒一次或者用局部刷新代替全屏清除。I2C 速率也可以适当提高但要注意走线长度。5.5 数据更新了但屏幕不变现象是串口能收到新数据屏幕却停在旧值。原因通常是解析结果没有传给显示函数或者显示函数用的是局部变量副本。排查时在解析成功后加一个标志位主循环检测到标志位再刷新确保数据流是通的。6. 验收与答辩怎么证明这套定位终端真的跑通了验收这件事我一般分三步走。第一步是功能验收把设备拿到开阔环境上电后观察 OLED 是否在 60 秒内显示坐标按键是否能切换页面串口是否能输出原始语句。第二步是稳定性验收连续运行 30 分钟记录定位丢失次数和恢复时间坐标漂移是否在合理范围。第三步是边界验收把天线拔掉看状态机是否正确进入定位丢失把设备拿到室内看是否如实显示未定位而不是假坐标。答辩时老师最爱问的几个问题提前准备好答案。比如「你怎么判断定位数据有效」答案是看 GGA 语句的定位质量字段为 0 就丢弃。「坐标怎么从 NMEA 转成十进制度」答案是度分格式换算纬度和经度分别处理。「为什么用环形缓冲区」答案是防止中断和主循环竞争导致数据丢失。这些问题在方案里都有对应实现答起来不虚。验收项方法通过标准定位功能开阔环境上电60 秒内显示有效坐标显示刷新观察 OLED1 秒刷新无闪烁按键切换连续按键页面正常切换无卡死定位丢失拔掉天线状态机进入丢失清空坐标长时间运行连续 30 分钟无死机坐标漂移可接受有个习惯我每次做这类项目都会强制走一遍把原始 NMEA 语句和解析后的坐标同时打印出来逐条比对。有一次坐标总是差一点点查了半天发现是纬度换算时把度分格式当成了十进制度少除了一次 60。从那以后我每次写完解析函数都先用几条已知语句做单元测试确认换算正确再上硬件。希望这套方案能帮你少走点弯路把定位终端稳稳跑起来。本文还有配套的精品资源点击获取