基于STM32的GPS导航实战:从NMEA协议解析到控制算法

发布时间:2026/10/13 12:37:08
基于STM32的GPS导航实战:从NMEA协议解析到控制算法
简介基于STM32的GPS导航完整工程包面向嵌入式学习者、电子竞赛参与者及需要快速搭建导航原型的开发者内含可直接导入的Keil MDK工程。包内共有548个文件压缩后20.23MB主要包含124个.h头文件、93个.c源码文件、86个.o目标文件、81个.d依赖文件以及uv2工程文件、链接脚本、汇编启动文件等覆盖从源码到编译产物的完整Keil MDK项目结构。资料已有3059人学习下载。内容围绕GPS数据接收、NMEA协议解析、定位与地图处理、路径规划、UC/GUI界面显示、用户交互和电源管理等模块展开可具体参考其UART/SPI外设配置、GPGGA/GPGLL报文解析代码、窗口与控件创建方式以及工程文件组织方式。对于希望将STM32与图形界面结合实现导航类功能的开发者这份资料能提供清晰的模块划分、可复用的代码框架并降低从零搭建系统的时间成本。1. 基于 STM32 的 GPS 导航为什么要自己动手而不是买现成模块很多人第一次接触“基于 STM32 的 GPS 导航”这个标题脑子里浮现的第一个问题是淘宝上几十块钱的 GPS 模块插上就能用手机导航更是免费又精准为什么还有人要在 STM32 上折腾一套导航系统答案是手机导航解决的是“人在哪、往哪走”而 STM32 导航解决的是“机器在哪、该往哪走”的问题。当你做的是无人车、巡检小车、农业机器人、航模地面站或者只是想把 GPS 数据融合进自己的传感器系统里一个现成的导航终端根本满足不了需求——你需要的是能够自定义解析、自主决策、甚至和电机控制直接联动的整套方案。这个标题真正要做的事情并不复杂用一颗 STM32 单片机读取 GPS 模块输出的 NMEA 协议数据解析出经纬度、速度、航向、卫星数等信息再根据预设目标点计算出航向偏差和控制指令最终在屏上显示或驱动运动机构完成导航任务。它覆盖了嵌入式开发里最典型的三层链路传感器数据采集、数据解析与算法处理、执行与控制输出。适合正在学 STM32 的开发者、做智能车项目的学生以及想给自己的设备加上定位导航能力的工程师。接下来这篇文章会把协议、硬件连接、源码、算法、参数整定和常见坑一条条讲清楚照着做就能在桌面上跑通第一版。2. GPS 导航的基本链路从 NMEA 协议到单片机指令的完整流程2.1 理解 NMEA 0183GPS 模块其实只做一件事GPS 模块本身并不“导航”它的唯一任务就是把卫星接收到的定位信息按标准格式输出。绝大多数消费级模块使用的协议叫 NMEA 0183它是一套纯文本的串口通信协议。每条语句以$开头后跟 5 个字符的语句标识符接着是逗号分隔的数据字段最后是校验和。STM32 要做的工作不是帮模块算位置而是读字符串、拆字段、转数值再把结果用于后续计算。我们最关心的语句是$GPRMC推荐最小定位信息和$GPGGA定位信息。RMC 里包含 UTC 时间、定位状态A 表示有效、纬度、北纬/南纬标识、经度、东经/西经标识、速度节、航向角度、日期和磁偏角。GGA 则提供卫星数、海拔高度等信息。一个典型的数据帧长这样$GPRMC,063612.00,A,3110.55802,N,12124.74660,E,0.027,131.21,260224,,,D*73解析这个字符串有两个常见误区。第一个是拿字符串拆分函数硬切所有字段遇到字段缺失时数组越界第二个是纬度格式3110.55802不是 31.1055802 度而是“度分”格式——31 度 10.55802 分需要额外换算成十进制度才能参与距离计算。我在第一次做的时候直接把这个数当十进制度算了结果目标点偏离了几十公里排查了半天才发现是这个格式问题。2.2 硬件连接串口、供电、天线位置的工程要点GPS 模块大多通过 UART 和 STM32 通信默认波特率常见的是 9600 或 115200具体要看模块手册。连接上很简单模块的 TX 接 STM32 的 RX模块的 RX 接 STM32 的 TXGND 共地VCC 按模块要求接 3.3V 或 5V。这里有一个新手经常踩的坑如果模块是 5V 逻辑电平而 STM32 的 GPIO 容忍度不够最好加一个电平转换或者选择 3.3V 逻辑的模块否则串口数据会出现乱码或者偶尔丢字节的现象。天线位置是个经验活。放在室内窗边能收到部分卫星但定位精度和稳定性都得不到保证放在金属外壳内部则大概率完全没有定位。我一般调试时把天线用胶带固定在窗框上且天线陶瓷面朝上朝外。模块启动后首次定位需要冷启动时间从十几秒到几分钟不等这个时间段里串口会有输出但状态位不是 A程序里必须做好超时和无效状态的处理否则系统一开机就拿到一组无效坐标后续全部计算没有意义。2.3 最小可运行的串口接收框架中断 环形队列直接在主循环里用阻塞方式读串口会有一个问题GPS 数据是持续不断输出的每秒钟可能来好几条语句主循环一旦被别的任务占用数据就会丢失。常见的做法是把串口接收放在中断里配合一个环形缓冲区把完整的数据帧存下来由主循环去解析。环形队列的好处是接收和解析解耦中断只管往队列里写主循环按自己的节奏取数据不会互相卡死。下面给出一个最精简的串口接收框架基于 STM32 的 HAL 库编写。这个框架的作用是把串口收到的每个字节都存进一个环形缓冲区然后应用程序从缓冲区里逐字节读取并拼帧// ring_buffer.h #define RING_BUF_SIZE 512 typedef struct { uint8_t buffer[RING_BUF_SIZE]; volatile uint16_t head; volatile uint16_t tail; } RingBuffer; void RingBuffer_Init(RingBuffer *rb); uint8_t RingBuffer_Write(RingBuffer *rb, uint8_t data); uint8_t RingBuffer_Read(RingBuffer *rb, uint8_t *data); uint16_t RingBuffer_Count(RingBuffer *rb);// ring_buffer.c #include ring_buffer.h void RingBuffer_Init(RingBuffer *rb) { rb-head 0; rb-tail 0; } uint8_t RingBuffer_Write(RingBuffer *rb, uint8_t data) { uint16_t next (rb-head 1) % RING_BUF_SIZE; if (next rb-tail) { return 0; // 缓冲区满 } rb-buffer[rb-head] data; rb-head next; return 1; } uint8_t RingBuffer_Read(RingBuffer *rb, uint8_t *data) { if (rb-head rb-tail) { return 0; // 缓冲区空 } *data rb-buffer[rb-tail]; rb-tail (rb-tail 1) % RING_BUF_SIZE; return 1; } uint16_t RingBuffer_Count(RingBuffer *rb) { return (uint16_t)((rb-head RING_BUF_SIZE - rb-tail) % RING_BUF_SIZE); }// 串口中断回调 extern RingBuffer gps_rb; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { static uint8_t rx_byte; if (huart-Instance USART1) { RingBuffer_Write(gps_rb, rx_byte); HAL_UART_Receive_IT(huart1, rx_byte, 1); // 重新使能接收中断 } }这里最关键的参数是RING_BUF_SIZE。GPS 一条 NMEA 语句最长不过 100 个字节左右但模块可能连续输出多条语句所以 256 字节勉强够用512 字节更稳妥。缓冲区满了之后写入会失败这时候不会造成内存越界只会丢数据因此解析端要尽量及时消费。需要注意的是中断接收必须使用HAL_UART_Receive_IT一次接收一个字节的模式并在回调里重新开启接收否则只能收到第一字节就再也没数据进来。主循环侧的帧同步可以检查$字符作为帧头遇到\r\n作为帧尾再把这一帧字符串送入解析函数。这个方案相比直接调用HAL_UART_Receive接收定长数组最大的好处是每条 NMEA 语句长度不固定不用预先知道长度就能正确切帧。缺点是如果两个$之间数据异常比如模块复位后输出半截语句需要做异常帧丢弃和重新同步的逻辑。3. 解析与坐标换算把经纬度字符串变成可计算的距离3.1 GPRMC 解析函数的完整实现与字段边界处理解析 NMEA 最常见的方式是按逗号拆分字符串然后把需要的字段用strtof或atof转成浮点数。但这个做法在嵌入式平台上有一个隐患GPS 模块输出字段在定位无效时可能为空比如$GPRMC,,A,,,...这种情况下atof可能会返回 0而程序并不知道这个 0 是真实值还是无效数据。正确的做法是先检查定位状态位再决定是否解析经纬度。下面这个函数实现了对 GPRMC 语句的解析并做了字段缺失保护#include stdio.h #include stdlib.h #include string.h #include math.h typedef struct { uint8_t valid; // 定位是否有效 double latitude; // 十进制度纬度 double longitude; // 十进制度经度 float speed_knots; // 速度节 float course_deg; // 航向角度 uint8_t sat_count; // 卫星数量来自GGA uint8_t hour, minute, second; uint8_t day, month, year; // 日期 } GpsData; static double nmea_to_decimal(double ddmm, char direction) { int degrees (int)(ddmm / 100); double minutes ddmm - degrees * 100; double decimal degrees minutes / 60.0; if (direction S || direction W) { decimal -decimal; } return decimal; } uint8_t Gps_ParseRMC(const char *line, GpsData *gps) { char temp[128]; strncpy(temp, line, sizeof(temp) - 1); temp[sizeof(temp) - 1] \0; char *token strtok(temp, ,); int field_index 0; char lat_dir N, lon_dir E; double lat_raw 0.0, lon_raw 0.0; uint8_t status 0; gps-valid 0; while (token ! NULL) { switch (field_index) { case 1: // UTC 时间 hhmmss if (strlen(token) 6) { gps-hour (token[0] - 0) * 10 (token[1] - 0); gps-minute (token[2] - 0) * 10 (token[3] - 0); gps-second (token[4] - 0) * 10 (token[5] - 0); } break; case 2: // 定位状态 A/V status (token[0] A) ? 1 : 0; break; case 3: // 纬度 ddmm.mmmm lat_raw atof(token); break; case 4: // 北纬/南纬 lat_dir token[0]; break; case 5: // 经度 dddmm.mmmm lon_raw atof(token); break; case 6: // 东经/西经 lon_dir token[0]; break; case 7: // 地速节 gps-speed_knots atof(token); break; case 8: // 航向角 gps-course_deg atof(token); break; case 9: // UTC 日期 ddmmyy if (strlen(token) 6) { gps-day (token[0] - 0) * 10 (token[1] - 0); gps-month (token[2] - 0) * 10 (token[3] - 0); gps-year (token[4] - 0) * 10 (token[5] - 0); } break; default: break; } token strtok(NULL, ,); field_index; if (field_index 12) break; } if (status) { gps-latitude nmea_to_decimal(lat_raw, lat_dir); gps-longitude nmea_to_decimal(lon_raw, lon_dir); gps-valid 1; } return gps-valid; }这段代码的关键是用了strtok按逗号切分并在每个 case 里对字段长度做了检查。nmea_to_decimal函数把“度分”格式转换成十进制度——这个转换是 GPS 解析里最容易翻车的地方不少人直接用原始值参与距离计算导致几百米的误差。strtok会原地修改字符串所以这里把原始行先拷贝到临时数组里再操作防止破坏原始帧缓冲区。3.2 距离与航向计算Haversine 公式和方位角拿到十进制度的经纬度之后下一步是计算当前位置和目标点的距离与方位角。距离计算推荐 Haversine 公式它在几百米到几千公里的范围内都能保持较高精度而且计算量在 STM32 上完全可接受。方位角公式用于告诉控制系统“目标在哪个方向”是导航控制的关键输入。#include math.h #define PI 3.14159265358979323846 #define EARTH_RADIUS_M 6371000.0 static double deg2rad(double deg) { return deg * PI / 180.0; } double Gps_DistanceMeters(double lat1, double lon1, double lat2, double lon2) { double dlat deg2rad(lat2 - lat1); double dlon deg2rad(lon2 - lon1); double a sin(dlat / 2) * sin(dlat / 2) cos(deg2rad(lat1)) * cos(deg2rad(lat2)) * sin(dlon / 2) * sin(dlon / 2); double c 2 * atan2(sqrt(a), sqrt(1 - a)); return EARTH_RADIUS_M * c; } double Gps_BearingDeg(double lat1, double lon1, double lat2, double lon2) { double phi1 deg2rad(lat1); double phi2 deg2rad(lat2); double dlambda deg2rad(lon2 - lon1); double y sin(dlambda) * cos(phi2); double x cos(phi1) * sin(phi2) - sin(phi1) * cos(phi2) * cos(dlambda); double bearing atan2(y, x) * 180.0 / PI; if (bearing 0) bearing 360.0; return bearing; }方位角返回的是 0 到 360 度的值0 度代表正北90 度代表正东。这个角度要和当前运动航向做差才能得到转向控制量。这里有一个细节GPS 模块输出的航向角是“运动方向”静止不动时该值可能是个无效或者漂移的值。所以在控制逻辑里如果速度低于某个阈值应当忽略航向角否则原地打转时角度乱跳系统会给出错误的转向指令。海平面高度在二维导航里一般用不到但如果你做的是无人机这类需要高度信息的多旋翼平台可以从 GPGGA 语句里解析海拔值。解析方法类似只需要找到对应字段再atof这里不再赘述。4. 导航控制算法比例转向 目标点到达判断 参数整定4.1 转向控制角差计算与死区处理导航的核心控制逻辑其实不复杂算出当前点和目标点的方位角再用这个方位角和当前运动航向角做差得到角差delta bearing - course然后用 PID 或者纯比例控制去驱动转向机构。角差要归一到 -180 到 180 度之间否则会出现一个很典型的问题目标在正北当前航向是 359 度角差算出来是 1 度正确但如果直接相减得到的是 -358 度比例控制器就会给出一个大幅度的反向转向指令车会画一个奇怪的圈。归一化角差的代码非常短但价值极高float NormalizeAngleDiff(float diff) { while (diff 180.0f) diff - 360.0f; while (diff -180.0f) diff 360.0f; return diff; }死区处理是另一个关键参数。GPS 属于低更新率传感器常见模块输出频率在 1Hz 到 10Hz 之间而路径角差本身带有噪声如果转向系统对每个细微的角差都做响应车会走出一条蛇形轨迹。一般做法是设置一个死区阈值比如角差绝对值小于 5 度时不转向。这个值需要根据转向机构的响应速度来调舵机响应快死区可以小一点差速驱动的电机响应慢死区要放得宽一些。4.2 到达判断半径阈值 连续计数避免“绕圈圈”到达判断看似简单——距离小于阈值就停——但实际运行中有一个很常见的现象当车靠近目标点时GPS 定位误差可能达到两三米车会在目标点附近反复进出控制指令在“前进”和“停止”之间抖动甚至绕着目标点打转。我在地面小车上遇到过这个情况现象就是车到了目标点附近以后一直来回画圆弧始终不触发到达条件。解决方法是双条件距离小于阈值同时持续若干个控制周期。比如设定到达半径 3 米连续 5 个解析周期约 5 秒都满足条件才认为到达。这样即使某一瞬间定位误差把距离拉到 4 米只要下一时刻回到阈值内连续计数不被打断依然能正确判定到达。计数器一旦中断就重置避免瞬时抖动造成的误判。uint8_t Arrival_Check(double dist_m, float arrival_radius_m, uint8_t need_count) { static uint8_t cnt 0; if (dist_m arrival_radius_m) { cnt; if (cnt need_count) { cnt 0; // 到达成功清零等待下次任务 return 1; } } else { cnt 0; } return 0; }这个函数的三个参数都不难理解arrival_radius_m取决于 GPS 精度与控制对象的尺寸小车 25 米无人机可以更小need_count取决于控制周期时长和系统需要多大容忍度。注意每到达一个航点后要手动清零计数器否则下次判断还会带着旧的计数状态。4.3 必调参数比例系数、死区、到达半径和更新率在真实调试中最容易影响导航效果的参数是比例系数Kp。它决定了角差对应的转向量大小Kp 太小转向迟钝车在接近目标时会走一个很大的弧线甚至超过目标点然后绕回来Kp 太大转向过猛车会左右来回摆头轨迹呈现明显的锯齿状。常见的起步值是 0.51.5然后根据实际轨迹逐次调整。下面这张表列出了我调试地面小车时常用的参数范围和调整方向参数常见范围数值过大时的现象数值过小时的现象调整建议Kp转向比例0.5 ~ 2.5蛇形振荡、转向过猛转弯弧线过大、到位慢先给 1.0观察轨迹再增减死区2° ~ 8°转向不灵敏、路径呈折线高频抖动、舵机发热以轨迹不抖动为准到达半径2 ~ 5 米提前停车、未到目标点在目标点附近反复进出结合 GPS 静态漂移幅度来定控制周期200 ~ 500 ms响应跟不上动态变化CPU 占用高、舵机频繁动作数据更新率为 1Hz 时取 500ms 即可控制周期这里多说一句GPS 模块输出频率固定控制周期没有必要比数据更新率快太多。比如模块每秒输出一帧定位控制周期设到 100ms 也拿不到新数据反而让同一个位置被反复计算浪费 CPU 还容易加剧转向抖动。我一般把控制周期设为数据更新周期的一半到一倍之间用定时器中断触发控制任务避免主循环忙等。5. GPS 导航避坑指南静漂、串口乱码、首次定位失败5.1 现象定位成功后坐标仍然缓慢漂移定位状态是 A卫星数也有七八颗但屏幕上的坐标在静止状态下会缓慢移动距离值在一两米范围内波动。这个现象叫“静态漂移”是 GPS 系统的物理特性不是代码 bug。原因主要有两个卫星信号的星历误差和多路径效应——城市环境里信号经过墙面反射后到达接收机路径变长造成伪距误差。无解的玄学是同一个位置上午漂移小、下午漂移大和卫星星座几何分布有关。解决的常用手段是做坐标平滑。最简单的是一阶低通滤波把当前解析值和上一次有效值做加权平均。系数不要过大否则车在真正转弯时会明显滞后。我的做法是只对静止状态做滤波速度低于 0.1 节时启用强滤波速度上来后立刻切换到原始值这样既压住了静漂又不影响动态响应。另外可以把到达半径设得比静漂范围大 50% 左右从源头避免误判。5.2 现象串口输出乱码解析帧同步失败乱码通常有三种原因。第一种是波特率不匹配模块实际输出 115200 而代码里配置成了 9600现象是偶尔能解开一条帧但大多时候乱码。第二种是模块逻辑电平与 STM32 不兼容5V 模块直接接 3.3V 引脚导致信号畸变。第三种是共地问题模块和单片机之间没有连 GND串口参考电平不一致。排查顺序很明确先用 USB 转串口工具接模块在电脑上看数据是否正常再示波器看波形幅值是否在 0 到 3.3V 之间最后检查两个板子的地是否连在一起。还有一种隐蔽的情况是天线与模块之间的连接线过长或者接触不良导致信号衰减到恰好能锁定卫星但数据误码率高。这种情况在串口屏幕上看到的是每隔一段时间出现一条异常短帧或者空帧这时优先检查射频连接而不是改代码。5.3 现象上电后长期无法定位模块上电后串口有数据但状态位一直是 V定位状态迟迟不变成 A。室内的概率最大因为 GPS 信号经过墙壁衰减后强度不够模块无法完成定位。另一个常见原因是天线放在金属物体附近——CT 金属外壳、铝合金底盘都会屏蔽信号。还有一种是模块之前使用过内部保存的星历过期冷启动需要重新下载卫星历书这个过程在信号差的条件下可能持续 5 到 10 分钟。工程上的习惯是把天线放置位置和上电时序做成可复现的固定流程先把天线放到室外或者窗边再给模块上电如果条件允许初始化时给模块发送冷启动命令不同模块命令不同常见的是发送特定的串口配置指令清除旧星历让它重新搜星。这种做法在环境不变的情况下能显著缩短首次定位时间。调试代码时还要给定位状态加一个超时判断比如 3 分钟内没有拿到有效定位就报错并重启模块避免系统挂在等待状态。5.4 现象到达目标点附近后绕圈不止前面 4.2 节提到过连续计数方案这里展开说一下实际调试中的经验。绕圈的根因有两个一是到达半径小于 GPS 静漂幅度系统在目标点附近永远无法稳定判定到达二是控制周期太快或者 Kp 太大车带着速度冲进半径区域后冲过头又转回来再次冲入形成周期振荡。解决的组合拳是把到达半径加大到静漂幅度的两倍以上控制周期和 GPS 更新率对齐而不是比它快很多在距离目标点小于两个到达半径时给速度一个下坡函数让车提前减速而不是全速冲入。速度下坡不需要复杂算法线性映射即可——距离越近目标速度越小低于最小速度后直接停车判断。这个思路和工业上的“分段减速”本质一致在低成本小车上是性价比最高的方案。5.5 现象解析出的经纬度在跳变距离值偶尔异常大如果解析出来的经纬度偶尔跳到非常离谱的位置比如前一秒还在上海下一秒到了非洲通常是解析越界或者野指针问题。这个问题在 MCU 上比在电脑上更容易出现因为单片机没有内存保护读错数组地址不会直接崩溃而是返回一个随机值。我排查过的一个具体案例是GPRMC 语句里某个字段为空strtok返回NULL而代码继续atof(token)行为未定义在小概率情况下读到相邻内存区域的非法数值。修复方式就是在每个字段使用前判空这也是 3.1 节代码里为什么大量做了字段长度检查的原因。另外要注意浮点数在 Cortex-M 系列的兼容性。老一些的 STM32F1 系列没有硬件浮点单元用double做大量三角函数运算会占用大量 CPU 时间。如果控制周期要求高建议整篇代码用float代替double在 STM32F4 系列上可以用FPU加速但也要在编译选项中开启硬浮点支持否则性能差距能差出十倍。6. 从单点导航到路径跟踪航点队列、LCD 显示与实用调试技巧单点导航能跑通之后往上做最自然的扩展就是航点队列。所谓航点队列就是把多个目标点按顺序存进一个数组导航系统逐个到达、逐个弹出全部到达后任务完成。这个扩展不需要改动底层解析和距离计算代码只需要在到达判断的基础上增加航点状态管理。一个简单的实现思路是维护一个数组和当前索引#define WAYPOINT_MAX 16 typedef struct { double lat; double lon; float arrival_radius_m; } Waypoint; typedef struct { Waypoint points[WAYPOINT_MAX]; uint8_t count; uint8_t current_index; uint8_t is_running; } WaypointMission; uint8_t WaypointMission_Update(WaypointMission *m, double cur_lat, double cur_lon) { if (!m-is_running) return 0; if (m-current_index m-count) { m-is_running 0; // 全部完成 return 2; } Waypoint *wp m-points[m-current_index]; double dist Gps_DistanceMeters(cur_lat, cur_lon, wp-lat, wp-lon); if (Arrival_Check(dist, wp-arrival_radius_m, 5)) { m-current_index; return 1; // 到达一个航点 } return 0; }这个结构配合 LCD 显示可以很直观地看到系统状态当前航点编号、距离目标还有多少米、方位角差多少、卫星数量、定位状态。LCD 显示本身很简单但信息布局有讲究——把“是否有效定位”放在最显眼的位置因为调试时你第一眼需要知道的是当前数据能不能信而不是看具体经纬度。调试技巧上我强烈建议把解析后的数据回传一个调试串口用串口助手记录完整运行日志。日志里除了经纬度还要带上原始 NMEA 帧、解析状态、角差、控制量、判定状态。这样出现问题后翻日志能看到是解析丢了帧还是控制参数不合适而不是对着现象猜原因。有一个血泪经验之前的某次调试中车辆轨迹偏移严重查了一圈代码都没问题最后看日志才发现是 GPS 模块的波特率被自己之前的配置命令改掉了——模块配置是非易失性的改一次会一直生效。所以调试时电源共地、引脚连接确认完一定还要确认模块配置参数别跳过这一步完美复现问题。硬件和算法都稳定后可以做进一步的精度优化。比如结合陀螺仪做航向融合在 GPS 更新间隙用惯性数据推算位置或者把卡尔曼滤波引入位置估计解决静漂和丢星期间的定位中断。这些属于进阶方向但底层框架不用推倒重来——解析、航点管理、控制输出这些代码都可以原样保留只替换“位置来源”这一个数据源即可。我自己做这类项目时的一个习惯是随时保留一个“纯 GPS 模式”的编译开关融合出问题的时候能快速切回来确认基础功能没有退化。导航系统最怕的不是算错一次而是无法判断“什么时候数据不可信”。所以最后留一个建议在你的状态机上务必做出“定位无效/丢星”的显式状态而不是靠坐标值兜底。丢星时坐标会停在最后一个有效值上如果继续用这个值导航车会朝着几分钟前的位置一路狂奔。显式的超时检测加上一个简单的状态指示能省下你大量在露天场地来回跑的时间。希望这篇基于 STM32 做 GPS 导航的实战拆解能帮到你少走我绕过的那几段弯路。本文还有配套的精品资源点击获取