模块略览——GPS 与 NMEA 0183 协议

JiaoKan 发布于 2024-05-12 89 次阅读


💬 前言

手头很多电子模块啥的,但多数都不会时常使用,每次要用到的时候再寻找一边文档什么的略麻烦了些,故作此系列简记之。

本文略览的是 GPS 传感器。GPS 传感器一般采用串口通信与上位机进行通信,而且不论是什么型号的传感器,基本都符合一个标准——NMEA 0183,所以说不论你买到的是什么模块,基本都可以适用本文的方法。

📡 NMEA 0183 协议关于 GPS 的部分简述

格式

$[语句],[数据]*[校验和]<CR><LF>

而语句的部分,由两部分组成:

[数据来源][句子类型]

数据来源而言有GPBDGN等,分别代表 GPS,北斗,多系统组合。
语句类型将在下方分别介绍。

GGA——GPS定位信息

$GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>*hh<CR><LF>

  1. UTC 时间,hhmmss(时分秒)格式
  2. 纬度 ddmm.mmmm(度分)格式(前面的 0 也将被传输)
  3. 纬度半球 N(北半球)或 S(南半球)
  4. 经度 dddmm.mmmm(度分)格式(前面的 0 也将被传输)
  5. 经度半球 E(东经)或 W(西经)
  6. GPS 状态:0=未定位,1=非差分定位,2=差分定位,6=正在估算
  7. 正在使用解算位置的卫星数量(00~12)(前面的 0 也将被传输)
  8. HDOP 水平精度因子(0.5~99.9)
  9. 相对于 WGS84 椭球面的高度(HAE, Height Above Ellipsoid)(-9999.9~99999.9)
  10. 地球椭球面(前者的基准)相对大地水准面(地球重力场等势面,近似的)的高度(N, Geoid Heigh)
  11. 差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空
  12. 差分站 ID 号 0000~1023(前面的 0 也将被传输,如果不是差分定位将为空)

比如这么一句语句:

$GPGGA,183457.00,4545.34205,N,00450.60258,E,1,05,2.13,164.1,M,47.4,M,,*58

意思就是 18:34:57 (UTC) 的时候,在北纬 45°45.34205′,东经 4°50.60258′,定位方式是非差分定位,使用到 5 颗卫星,水平精度因子为 2.13,HAE为 164.1 米,大地水准面高度为 47.4 米(模块根据内置超高阶重力场模型代入经纬度计算得出),经过计算可以得知本地的正常高(就8848代表的那个高度)为 $ H = HAE - N $,为 116.7 米,因为是非差分式定位,所以没有差分数据。收起你的好奇心,我直接告诉你这个地理位置是法国里昂第三区(ゝ∀・),下同。

GLL——定位地理信息

$GPGLL,<1>,<2>,<3>,<4>,<5>,<6>,<7>*hh<CR><LF>

  1. 纬度 ddmm.mmmm(度分)格式(前面的 0 也将被传输)
  2. 纬度半球 N(北半球)或 S(南半球)
  3. 经度 dddmm.mmmm(度分)格式(前面的 0 也将被传输)
  4. 经度半球 E(东经)或 W(西经)
  5. UTC 时间,hhmmss(时分秒)格式
  6. 定位状态,A=有效定位,V=无效定位
  7. 模式指示(仅 NMEA0183 3.00 版本以上输出,A=自主定位,D=差分,E=估算,N=数据无效,3.01 版本及以上新增了 M=手动输入,S=模拟模式)

比如这么一句语句:

$GPGLL,4545.34205,N,00450.60258,E,183457.00,A,A*6D

重复部分省略,这句话还告诉我们这是一个有效的定位数据,是通过自主定位得出的,说人话就是没用地面参考站修正误差进行差分定位。

RMC——推荐定位信息

$GPRMC,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,<10>,<11>,<12>*hh<CR><LF>

  1. UTC 时间,hhmmss(时分秒)格式
  2. 定位状态,A=有效定位,V=无效定位
  3. 纬度 ddmm.mmmm(度分)格式(前面的 0 也将被传输)
  4. 纬度半球 N(北半球)或 S(南半球)
  5. 经度 dddmm.mmmm(度分)格式(前面的 0 也将被传输)
  6. 经度半球 E(东经)或 W(西经)
  7. 地面速率(000.0~999.9 节,前面的 0 也将被传输)
  8. 地面航向(000.0~359.9 度,以真北为参考基准,前面的 0 也将被传输)
  9. UTC 日期,ddmmyy(日月年)格式
  10. 磁偏角(000.0~180.0 度,前面的 0 也将被传输)
  11. 磁偏角方向,E(东)或 W(西)
  12. 模式指示

比如这么一句语句:

$GPRMC,183457.00,A,4545.34205,N,00450.60258,E,1.159,,110524,,,A*7B

重复部分省略,换算过后地面速率约为 2.15 km/h(0.60 m/s),但其实我没动,所以这个属于误差。

VTG——地面速度信息

$GPVTG,<1>,T,<2>,M,<3>,N,<4>,K,<5>*hh<CR><LF>

  1. 以真北为参考基准的地面航向(000~359 度,前面的 0 也将被传输)
  2. 以磁北为参考基准的地面航向(000~359 度,前面的 0 也将被传输)
  3. 地面速率(000.0~999.9 节,前面的 0 也将被传输)
  4. 地面速率(0000.0~1851.8 公里/小时,前面的 0 也将被传输)
  5. 模式指示

比如这么一句语句:

$GPVTG,,T,,M,1.159,N,2.147,K,A*2F

可以发现,这里相较于上面一类语句还贴心地给你换算了一下。

GSA——当前卫星信息

`$GPGSA,<1>,<2>,<3>,<3>,<3>,<3>,<3>,<3>,<3>,<3>,<3>,<3>,<3>,<3>,<4>,<5>,<6>*hh

  1. 模式,M=手动,A=自动
  2. 定位类型,1=没有定位,2=2D 定位,3=3D 定位
  3. PRN 码(伪随机噪声码),即正在用于解算位置的卫星号(01~32,前面的 0 也将被传输)。
  4. PDOP 位置精度因子(0.5~99.9)
  5. HDOP 水平精度因子(0.5~99.9)
  6. VDOP 垂直精度因子(0.5~99.9)

比如这么一句语句:

$GPGSA,A,3,04,02,09,03,31,,,,,,,,3.14,2.13,2.31*0A

表示处于自动模式,自动选择了 3D 定位(在经纬度地基础上,还获取了高度,至少需要四颗卫星),正在用于定位地卫星编号为 04,02,09,03,31,水平精度略好于垂直精度,整体精度因子为 3.14。

GSV——可见卫星信息

$GPGSV,<1>,<2>,<3>,<4>,<5>,<6>,<7>,…<4>,<5>,<6>,<7>*hh<CR><LF>

  1. GSV 语句的总数
  2. 本句 GSV 的编号
  3. 可见卫星的总数(00~12,前面的 0 也将被传输)
  4. PRN 码(伪随机噪声码)(01~32,前面的 0 也将被传输)
  5. 卫星仰角(00~90 度,前面的 0 也将被传输)
  6. 卫星方位角(000~359 度,前面的 0 也将被传输)
  7. 信噪比(00~99dB,没有跟踪到卫星时为空,前面的 0 也将被传输)

注:4~7将按照每颗卫星进行循环显示,每条 GSV 语句最多可以显示 4 颗卫星的信息。其他卫星信息将在下一序列的 NMEA0183 语句中输出。

比如这么一组语句:

$GPGSV,3,1,12,02,18,148,34,03,52,073,18,04,79,096,13,06,44,309,09*72
$GPGSV,3,2,12,07,01,177,,09,52,218,32,11,06,313,,17,30,231,*7A
$GPGSV,3,3,12,19,37,261,20,21,05,145,,28,04,028,07,31,22,050,11*73

总共有三条语句,总共12颗卫星可见,以语句中第一颗卫星为例,卫星编号为 02,高度角为 18°,方位角为 148°,信噪比为 34 dB。

精度因子(DOP)

这里的精度因子是对卫星相对于接收机地几何分布的一个定量描述,这个几何分布如何影响定位精度呢?如图:

file
file

《GPS原理与接收机设计》

根据这个值可以区分几个等级:

DOP值 等级 含义
1 理想 置信度水平高
2-4 优秀 置信度水平满足所有的应用需求
4-6 良好 置信度水平满足高精度应用需求
6-8 中等 置信度水平满足大部分应用需求
8-20 一般 置信度水平较低,应评估应用风险
20-50 很差 置信度水平很差,基本无法满足应用需求

那么如何通过精度因子推算出精度呢?我们有:$$
误差=\text{DOP}\times基准误差
$$问题就出在这个基准误差,我也不造啊,没查到啊◑﹏◐。不过万能的 ChatGPT 4 告诉我民用设备一般取值 5~10 米。

🧑‍💻 程序设计

上面说了那么多,是要从零开始造轮子吗?那指定不是,Introducing TinyGPS++

这是一个可以用来解析 GPS 数据的库,我们只需要调用它就可以了。具体的文档可以参考 GitHub 仓库里贴的链接

这里简述一下通用的流程,程序启动串口(或者 SoftwareSerial),然后定期喂数据进TinyGPSPlus类的对象,然后从这个对象里面取数据。也可以自己定义TinyGPSCustom类,来读取并没有被这个库覆盖带的数据(比如卫星数据),这个可以参考官方给出的 Examples。此外,这个库还提供了计算两地大圆距离以及航向的函数。

一个聪明的喂数据的方法是重载(或者新定义)delay 函数。

现在给出一个极简版例程:

#include <TinyGPSPlus.h>
#include <SoftwareSerial.h>

static const int RXPin = 13, TXPin = 14;
static const uint32_t GPSBaud = 9600;

static const double ORIENTALPEARLTOWER_LAT = 121.50566572291831;
static const double ORIENTALPEARLTOWER_LON = 31.244890217260576;

SoftwareSerial ss(RXPin, TXPin);
TinyGPSPlus gps;

static void smartDelay(unsigned long ms);

void setup()
{
    Serial.begin(115200);
    ss.begin(GPSBaud);
}

void loop()
{
    Serial.println("----------------------------------------");
    Serial.println(String(gps.date.year()) + '/' +
                   String(gps.date.month()) + '/' +
                   String(gps.date.day()) + ' ' +
                   String(gps.time.hour()) + ':' +
                   String(gps.time.minute()) + ':' +
                   String(gps.time.second()) + " UTC");
    Serial.println("Latitude: " + String(gps.location.lat()));
    Serial.println("Longitude: " + String(gps.location.lng()));
    Serial.println("To Oriental Pearl Tower (km): " +
                   String(TinyGPSPlus::distanceBetween(
                              gps.location.lat(),
                              gps.location.lng(),
                              ORIENTALPEARLTOWER_LAT,
                              ORIENTALPEARLTOWER_LON) /
                          1000.0));

    smartDelay(1000);

    if (millis() > 5000 && gps.charsProcessed() < 10)
        Serial.println(F("No GPS data received: check wiring"));
}

static void smartDelay(unsigned long ms)
{
    unsigned long start = millis();
    do
    {
        while (ss.available())
            gps.encode(ss.read());
    } while (millis() - start < ms);
}
此作者没有提供个人介绍。
最后更新于 2024-05-12