十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

IoT-For-Beginners 实战:Wio Terminal 用 TinyGPSPlus 解码 NMEA GPS 定位数据

IoT-For-Beginners 实战:Wio Terminal 用 TinyGPSPlus 解码 NMEA GPS 定位数据 IoT-For-Beginners 实战Wio Terminal 用 TinyGPSPlus 解码 NMEA GPS 定位数据【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners本篇文章聚焦 IoT-For-Beginners 课程项目三《Transport》第一课《Location tracking》中的 Wio Terminal 实战环节在完成 GPS 传感器原始数据读取之后如何利用开源库 TinyGPSPlus 对 UART 串口收到的 NMEA 0183 报文逐字符解码从中提取纬度、经度和参与定位的卫星数量并在串口监视器上输出可读的十进制坐标。读完本文你将掌握NMEA 句子的构成与 GGA 报文格式、在 PlatformIO 工程中声明库依赖的写法、TinyGPSPlus 的encode()/location/satellites核心 API 用法以及一套可直接复制到真实 Wio Terminal 上运行、基于仓库源码的完整解码程序。为什么原始 GPS 数据需要“解码”在上一环节 wio-terminal-gps-sensor.md 中Wio Terminal 通过 Grove 左侧接口复用 SERCOM3 引脚以 9600 baud 的 UART 波特率从 Grove GPS Air530 传感器读取数据并在串口监视器中打印出类似这样的“天书”$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这其实是 GPS 传感器遵循NMEA 0183 标准输出的文本报文。每一条“句子”以$开头随后 2 个字符表示数据来源如GP表示美国 GPS 系统、GN表示 GLONASS 等多系统组合接着 3 个字符表示消息类型其余部分是以逗号分隔的字段整句以换行符结束。其中与定位直接相关的是GGAGPS Fix Data报文它包含纬度、经度、海拔、参与解算的卫星数量等信息此外常见的还有 ZDA日期时间与 GSV视野内卫星详情等报文类型。更完整的报文类型对照可参阅本课 README.md 中的“NMEA GPS data”一节。GGA 报文中的坐标采用 NMEA 的(d)ddmm.mmmm格式度分制秒为分的十进制小数并附一个方向字符纬度用N/S经度用E/W。例如上述报文中的4738.538654,N表示 47 度 38.538654 分换算成十进制小数度即47.6423109方向N表示北纬为正值而12208.341758,W对应-122.1390293西经为负。计算机系统普遍使用十进制度因此必须把这种度分制报文“解码”成latitude, longitude形式的坐标对——这正是本环节要解决的问题。方案选型用 TinyGPSPlus 替代手写解析与其手工做字符串切分与度分换算更稳妥的做法是引入开源 NMEA 解析库。在 Wio TerminalArduino/PlatformIO 生态中本课程选用的是TinyGPSPlus它在仓库中作为lib_deps声明为mikalhart/TinyGPSPlus 1.0.2职责是接收 NMEA 原始字节流内部完成句子切分、字段解析与格式转换并把经度、纬度、卫星数、海拔、速度、日期时间等结果以对象属性的形式暴露给应用层。作为对照同一课面向 Raspberry Pi / 虚拟 IoT 设备的分支采用 Python 生态的pynmea2库通过pynmea2.parse()与pynmea2.dm_to_sd()完成同样的度分转十进制度工作详见 single-board-computer-gps-decode.md。步骤一在 platformio.ini 中添加库依赖打开上一环节创建的gps-sensor工程若尚未创建请先按 wio-terminal-gps-sensor.md 完成硬件接线与原始读取程序。编辑工程根目录下的platformio.ini在[env:seeed_wio_terminal]环境中追加lib_deps[env:seeed_wio_terminal] platform atmelsam board seeed_wio_terminal framework arduino lib_deps mikalhart/TinyGPSPlus 1.0.2这一写法是 PlatformIO 的库管理器语法mikalhart/TinyGPSPlus定位到 GitHub 上 Mikal Hart 的 TinyGPSPlus 仓库 1.0.2锁定具体版本号保证构建的可复现性。保存后PlatformIO 在首次编译时会自动下载并缓存该库。仓库中本环节的完整工程配置见 code-gps-decode/wio-terminal/gps-sensor/platformio.ini。步骤二引入头文件并声明解码器对象在src/main.cpp顶部添加 TinyGPSPlus 的头文件#include TinyGPS.h随后在Serial3串口对象声明的下方实例化一个全局的 TinyGPSPlus 解码器对象static Uart Serial3(sercom3, PIN_WIRE_SCL, PIN_WIRE_SDA, SERCOM_RX_PAD_1, UART_TX_PAD_0); TinyGPSPlus gps;gps对象是整个解码流程的核心状态机它会缓存收到的字节累积成句子逐条解析并把最新解析结果保存在自身的location、satellites、time、date、altitude、speed等子对象中。对象声明为全局变量是为了让它在setup()与loop()之间持续存活、累积跨多次循环调用才收齐的报文数据。步骤三改写 printGPSData 完成逐字节喂入将上一环节中“整行字符串直接转发”的printGPSData函数替换为如下实现void 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); } } }这段代码的工作逻辑可以拆解为四步Serial3.read()从 UART 串口读入下一个字符每次一个字节gps.encode(字符)把该字符送入 TinyGPSPlus 解码器。解码器边接收边判断只有当它凑齐并成功解析出一条完整的 NMEA 句子时encode()才返回true返回true后再通过gps.location.isValid()检查本次解析出的位置是否有效例如卫星尚未锁定、坐标为 0 时通常为无效状态若位置有效则用gps.location.lat()与gps.location.lng()取出十进制小数度坐标并打印参数6表示保留 6 位小数同时用gps.satellites.value()打印参与本次定位解算的卫星数量。F(,)中的F()宏将字符串常量存放在 Flash程序存储区而非 RAM以节省 Wio Terminal 宝贵的 SRAM。loop()中维持“只要有数据就持续喂入”的读取循环void loop() { while (Serial3.available() 0) { printGPSData(); } delay(1000); }注意gps.encode()是逐字符调用的因此while循环必须把串口缓冲区中的每个字节都喂给解码器不能只处理一次。完整源码仓库中的真实实现仓库 code-gps-decode/wio-terminal/gps-sensor/src/main.cpp 中保存了本环节的完整可运行代码除了上述解码逻辑外还包含 Wio Terminal 使用 Grove 左侧 UART 口所必需的 SERCOM 配置。整体结构如下#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; void setup() { Serial.begin(9600); while (!Serial) ; // Wait for Serial to be ready delay(1000); Serial3.begin(9600); while (!Serial3) ; // Wait for Serial3 to be ready delay(1000); pinPeripheral(PIN_WIRE_SCL, PIO_SERCOM_ALT); } void 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); } } } void loop() { while (Serial3.available() 0) { printGPSData(); } delay(1000); } void SERCOM3_0_Handler() { Serial3.IrqHandler(); } void SERCOM3_1_Handler() { Serial3.IrqHandler(); } void SERCOM3_2_Handler() { Serial3.IrqHandler(); } void SERCOM3_3_Handler() { Serial3.IrqHandler(); }对照 code-gps/wio-terminal/gps-sensor/src/main.cpp解码前的原始读取版本可以看出两次迭代的差异点仅有三处新增#include TinyGPS.h、新增TinyGPSPlus gps;全局对象、以及重写printGPSData()的实现setup()、loop()、SERCOM 中断处理函数均保持不变。这体现了“先读原始数据、再叠加解码层”的渐进式教学思路也说明解码逻辑可以被平滑地接入既有工程。其中几个关键配置需要结合上一环节理解Serial3通过wiring_private.h提供的Uart类与sercom3绑定把 Wio Terminal Grove 左侧接口PIN_WIRE_SCL/PIN_WIRE_SDA引脚重映射为 UART RX/TXSerial3.begin(9600)与 GPS 传感器的默认波特率一致8 数据位、无校验、1 停止位即9600,8,N,1pinPeripheral(PIN_WIRE_SCL, PIO_SERCOM_ALT)把该引脚配置为 SERCOM 复用功能末尾四个SERCOM3_x_Handler()中断服务函数把 SERCOM3 的中断路由到Serial3.IrqHandler()保证 UART 接收中断被正确处理这是字符能够持续进入gps.encode()的底层保障。步骤四编译上传并用串口监视器验证在 VSCode 的 PlatformIO 工具栏中执行 Build 并 Upload将固件烧写到 Wio Terminal。随后打开串口监视器PlatformIO 的platformio device monitor任务当传感器完成卫星锁定后即可看到类似如下的输出 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: CtrlC | Menu: CtrlT | Help: CtrlT followed by CtrlH --- 47.6423109,-122.1390293 - from 3 satellites与原始 NMEA 输出相比此刻显示的是真正可用的十进制坐标对47.6423109,-122.1390293对应微软西雅图园区附近以及from 3 satellites的卫星计数。注意串口监视器须使用与Serial一致的波特率9600坐标显示为 6 位小数意味着理论上可达约 0.1 米量级的精度展示粒度但实际定位精度仍取决于信号环境与参与解算的卫星数。解码结果的深层原理与实用提示从源码层面可以进一步理解 TinyGPSPlus 的几个关键行为encode()的返回语义它返回true仅表示“刚刚完成了一条句子的解析”并不代表位置已更新因此官方推荐与gps.location.isValid()组合判断避免把首次encode触发误当作有效定位lat()/lng()的精度参数Serial.print(value, 6)的第二个参数是浮点打印的小数位TinyGPSPlus 内部以百万分之一度TinyGPSPlus::GPS_INVALID_F_ANGLE等常量定义在库内部的分辨率保存坐标6 位小数打印即可完整呈现其内部精度isValid()与定位状态在刚上电、卫星尚未锁定绿色 LED 未开始每秒闪烁时isValid()通常返回false代码会静默跳过打印等待数分钟至信号稳定后输出才会出现。若长期无输出应像 wio-terminal-gps-sensor.md 所述重新摆放外置天线使其正对天空卫星数量gps.satellites.value()取自 GGA 报文中的“参与解算卫星数”字段。通常至少需要 3 颗卫星才能完成二维定位更多卫星可提高精度与稳定性不依赖 NTP 的时间源NMEA 报文中还携带 UTC 时间戳TinyGPSPlus 的gps.time、gps.date子对象可解析出当前时间IoT 设备可借此校时而无需联网。延伸挑战与课后作业本环节作为解码的起点还有两个值得继续深挖的方向自研 NMEA 解码器本课 README.md 的挑战要求不依赖第三方库自己实现一个能从$GNGGA句子中提取纬度/经度并完成度分转十进制度的解析器——这有助于彻底吃透 NMEA 字段布局挖掘更多 GPS 数据assignment.md 要求继续探索 GGA/ZDA/GSV 等报文中的其他字段例如海拔、当前速度、日期时间并考虑用 GPS 时间替代 NTP 为 Wio Terminal 校时——TinyGPSPlus 的altitude、speed、time、date等子对象正是完成这些扩展的现成接口。至此你的 Wio Terminal GPS 程序已完成了从“原始 NMEA 报文”到“可读经纬度坐标”的完整解码闭环后续即可将47.6423109,-122.1390293这类坐标作为遥测数据上报到 IoT 云平台支撑车辆定位、地理围栏Geofence等上层应用——这正是本课 README.md 中“连接车辆定位跟踪”的最终目标。【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表