IoT-For-Beginners 实战:Wio Terminal 上使用 TinyGPSPlus 解码 GPS NMEA 数据
【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners
本篇指南对应 IoT-For-Beginners 课程「运输场景 · 第 1 课:位置跟踪(Location tracking)」的第二阶段:在前一课通过 UART 读取出 GPS 模块的原始 NMEA 报文之后,如何使用开源库 TinyGPSPlus 逐字符解码这些报文,最终提取出十进制度表示的纬度、经度与参与定位的卫星数量。读完后你将掌握在 Wio Terminal 上配置 PlatformIO 库依赖、编写 NMEA 解码循环,并通过串口监视器验证 GPS 定位结果的完整流程。
Wio Terminal 左侧 Grove 接口连接的 Grove GPS 传感器
前提:你已经能从 Wio Terminal 读到原始 NMEA 数据
本节是 位置跟踪课程 的延续。其前置步骤记录在 Read GPS data - Wio Terminal 中:Grove GPS Air530 传感器通过 Grove 线接到 Wio Terminal 屏幕朝你时左侧的 Grove 接口,代码侧通过自定义的Serial3UART 端口(9600 波特率)读取数据。
运行前置课程代码后,串口监视器里会看到如下看似乱码的输出——它们其实是标准 GPS 数据:
$GNGGA,020604.001,4738.538654,N,12208.341758,W,1,3,,164.7,M,-17.1,M,,*67 $GPGSA,A,1,,,,,,,,,,,,,,,*1E $BDGSA,A,1,,,,,,,,,,,,,,,*0F $GPGSV,1,1,00*79 $BDGSV,1,1,00*68理解这些报文的关键点(来自课程 Location tracking 主文档):
- GPS 传感器按NMEA 0183 标准输出文本报文。每条报文(sentence)以
$开头,后跟 2 个字符表示消息来源(GP为美国 GPS 系统,GN为综合导航系统,BD为北斗等),再跟 3 个字符表示消息类型,其余为逗号分隔的字段,以换行符结尾。 - 常见的消息类型包括:
GGA(GPS 定位数据,含纬度、经度、海拔与参与定位的卫星数)、ZDA(当前日期与时间)、GSV(可见卫星详情)。 GGA中的经纬度采用(dd)dmm.mmmm格式并附带方向字符:d是度,m是分,秒以小数分钟表示。例如$GNGGA报文中的纬度部分4738.538654,N表示北纬 47°38.538654',换算成十进制度即47.6423109;经度部分12208.341758,W表示西经 122°08.341758',换算为-122.1390293(N/S表示南北纬,E/W表示东西经,西经取负值)。- 坐标的书写顺序始终是
latitude, longitude(纬度在前,经度在后)。
手工按上述规则解析字段固然可行,但更稳健的做法是使用现成的 NMEA 解码库——这正是本篇要做的事。
第 1 步:为项目添加 TinyGPSPlus 库依赖
打开gps-sensor应用工程(如果尚未打开)。TinyGPSPlus 是一个专门用于解码 NMEA 报文的开源库,需要把它声明为 PlatformIO 的库依赖。在工程的 platformio.ini 中添加lib_deps配置:
lib_deps = mikalhart/TinyGPSPlus @ 1.0.2仓库中该工程的完整 platformio.ini 如下,可以看到lib_deps是在 Wio Terminal 目标环境声明下追加的:
; PlatformIO Project Configuration File ; ; Build options: build flags, source filter ; Upload options: custom upload port, speed and extra flags ; Library options: dependencies, extra library storages ; Advanced options: extra scripting ; ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html [env:seeed_wio_terminal] platform = atmelsam board = seeed_wio_terminal framework = arduino lib_deps = mikalhart/TinyGPSPlus @ 1.0.2各配置项含义:platform = atmelsam指定 Atmel SAM 芯片平台(Wio Terminal 搭载 SAMD21),board = seeed_wio_terminal指定具体板卡,framework = arduino使用 Arduino 框架编译;lib_deps固定引用 TinyGPSPlus 的1.0.2版本,保证不同人编译时依赖版本一致。执行构建时 PlatformIO 会自动下载该库并参与编译。
第 2 步:在 main.cpp 中引入 TinyGPSPlus 解码器
在 src/main.cpp 顶部为 TinyGPSPlus 库添加 include 指令:
#include <TinyGPS++.h>然后在Serial3声明的下方声明一个 TinyGPSPlus 对象,用它来逐字符处理 NMEA 语句:
TinyGPSPlus gps;对照仓库中解码版工程的 src/main.cpp,文件头部的完整声明区为:
#include <Arduino.h> #include <wiring_private.h> #include <TinyGPS++.h> static Uart Serial3(&sercom3, PIN_WIRE_SCL, PIN_WIRE_SDA, SERCOM_RX_PAD_1, UART_TX_PAD_0); TinyGPSPlus gps;其中Serial3是前置课程已建立的 UART 端口(复用 Grove 口上的PIN_WIRE_SCL/PIN_WIRE_SDA引脚为 SERCOM3),TinyGPSPlus gps;即为本次新增的 NMEA 解码器实例。从源码结构看,gps是一个纯软件状态机对象:不直接绑定任何硬件,只负责接收字符流并维护「是否收到完整语句」「位置是否有效」等内部状态,因此它可以被喂入来自任意串口、文件甚至虚拟设备模拟的数据源。
第 3 步:改写 printGPSData 函数实现逐字符解码
将printGPSData函数的内容修改为:
if (gps.encode(Serial3.read())) { if (gps.location.isValid()) { Serial.print(gps.location.lat(), 6); Serial.print(F(",")); Serial.print(gps.location.lng(), 6); Serial.print(" - from "); Serial.print(gps.satellites.value()); Serial.println(" satellites"); } }仓库中 src/main.cpp 的printGPSData与此一致。这段代码的工作方式是逐字符驱动的:
Serial3.read()从 UART 串口读出下一个字符(-1 表示无数据);gps.encode(...)把该字符送入 TinyGPSPlus 的 NMEA 解码器。解码器内部按 NMEA 0183 规则累积字符,只有当它成功解析出一条完整且校验通过的语句时才返回true;- 每当
encode返回true(即刚读完一条有效语句),再调用gps.location.isValid()判断解码器是否已从GGA等语句中确认出一个有效位置; - 位置有效时,把
gps.location.lat()(纬度)与gps.location.lng()(经度)以 6 位小数打印到串口监视器,并附上gps.satellites.value()报告的参与本次定位的卫星数量。Serial.print(value, 6)的第二个参数 6 即小数位数,正好对应 GGA 报文「以小数分表示秒」精度换算到十进制度后的小数位;F(",")使用 PROGMEM 字符串宏,把字面量放进程序存储器以节省 RAM。
也就是说,每读取一个字符就检查一次解码器是否凑齐了一条有效语句,若凑齐且位置有效,就把「纬度,经度 - 由 N 颗卫星定位」的输出推送到串口监视器。
第 4 步:构建、上传并验证
- 构建并把代码上传到 Wio Terminal(PlatformIO 的 Upload 命令,或命令面板中的相应任务)。
- 代码上传完成后,打开串口监视器观察 GPS 定位数据。预期输出形如:
> Executing task: platformio device monitor < --- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time --- More details at http://bit.ly/pio-monitor-filters --- Miniterm on /dev/cu.usbmodem1201 9600,8,N,1 --- --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- 47.6423109,-122.1390293 - from 3 satellites示例中输出的47.6423109,-122.1390293正是前置章节用$GNGGA,020604.001,4738.538654,N,12208.341758,W,...手工换算得到的坐标,可用来交叉验证解码结果的正确性。监视器头部显示9600,8,N,1,说明串口监视器需与Serial.begin(9600)保持一致的波特率。
💁 该工程的完整代码位于 code-gps-decode/wio-terminal 目录(工程根目录为
3-transport/lessons/1-location-tracking/code-gps-decode/wio-terminal/gps-sensor/)。
至此,你的 GPS 传感器程序就成功实现了 NMEA 数据解码。
源码细节:解码版工程与原始读取版的差异
将解码版工程 code-gps-decode/wio-terminal/.../src/main.cpp 与前一步原始读取版工程 code-gps/wio-terminal/.../src/main.cpp 对比,可以清楚看到本步骤的最小改动面:
| 关注点 | 原始读取版(code-gps) | 解码版(code-gps-decode) |
|---|---|---|
| 头文件 | Arduino.h、wiring_private.h | 额外新增#include <TinyGPS++.h> |
| 全局对象 | 仅static Uart Serial3(...) | 新增TinyGPSPlus gps; |
printGPSData实现 | Serial.println(Serial3.readStringUntil('\n'));整行打印原始报文 | gps.encode(Serial3.read())逐字符解码后打印结构化坐标 |
setup/loop | 9600 初始化、pinPeripheral(PIN_WIRE_SCL, PIO_SERCOM_ALT)、while (Serial3.available() > 0)循环 | 完全相同 |
| SERCOM3 中断处理 | SERCOM3_0_Handler~SERCOM3_3_Handler四个弱中断入口转调Serial3.IrqHandler() | 完全相同 |
两个值得注意的实现细节:
- 读取粒度的变化:原始版用
readStringUntil('\n')一次读完一整条 NMEA 语句(NMEA 语句以换行符结尾,这与 wio-terminal-gps-sensor.md 中的解释一致),整行转发到监视器;解码版改为每次只读一个字符交给gps.encode(),把「按行切分 + 字段解析」的责任全部交给库的状态机,这也是 NMEA 解码的推荐方式。 - 中断与引脚复用:src/main.cpp 底部保留的
SERCOM3_0_Handler至SERCOM3_3_Handler四个函数是 Atmel SAMD 系列 SERCOM3 外设的中断入口,负责把 UART 中断转发给Serial3.IrqHandler();setup中的pinPeripheral(PIN_WIRE_SCL, PIO_SERCOM_ALT)则把 Grove 引脚复用为 SERCOM 功能。这部分硬件层配置由前置课程负责,解码库完全不关心数据来源,二者职责分离清晰。
延伸:其他设备的解码方式与课程挑战
本课还面向树莓派/虚拟设备提供了平行的解码指南,见 single-board-computer-gps-decode.md;虚拟设备则可用 Counterfit 虚拟 GPS 传感器 通过预设经纬度或 NMEA 语句流来模拟定位数据,方便在无实体硬件时验证解码逻辑。
课程主文档 Location tracking 中还有一个进阶挑战:不依赖第三方库,自己编写一个 NMEA 解码器,从GGA语句中提取经纬度。结合本文介绍的(dd)dmm.mmmm字段格式、方向字符符号规则(N/S、E/W)与十进制度换算公式(十进制度 = 度 + 分 / 60),你可以尝试仅用字符串切分与算术运算复现4738.538654,N -> 47.6423109这类换算,作为对 TinyGPSPlus 内部逻辑的一次「白盒验证」。
【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考