跳至正文
老丹的足迹 —— 代码写给机器,游记写给自己,感悟写给时间
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面

NMEA 0183 协议处理与 C++ 开源库选型指南

一、NMEA 0183 协议概述

1.1 协议简介

NMEA 0183 是美国国家海洋电子协会制定的航海电子设备间通信的串口数据协议标准,是目前全球定位系统(GPS)接收机最常用的数据输出格式。

1.2 串口通信参数

参数标准值备注
波特率4800 bps部分高速设备为 38400 bps
数据位8 bit固定
起始位1 bit固定
停止位1 bit固定
奇偶校验无固定
数据格式ASCII 字符可读文本

1.3 帧格式结构

每条 NMEA 语句遵循以下通用格式:

$aaacc,ddd,ddd,...,ddd*hh<CR><LF>

格式说明:

组成部分长度说明示例
$1 字符语句起始标志$
aaa3 字符谈话器 ID(设备标识)GP
cc2 字符语句类型(2-3 字符)RMC
ddd...可变数据字段,逗号分隔024813.640,A,...
*1 字符校验和前缀*
hh2 字符校验和(十六进制)50
<CR><LF>2 字符结束符(回车+换行)0x0D 0x0A

1.4 谈话器 ID(源标识)

标识含义所属系统
GPGPS全球定位系统(美国)
GLGLONASS格洛纳斯(俄罗斯)
GAGalileo伽利略(欧盟)
BDBeiDou北斗(中国)
GNGNSS多系统组合

1.5 校验和算法

校验和是对 $ 和 * 之间的所有字符进行异或(XOR)运算得到的结果:

uint8_t calculateChecksum(const char* data) {
    uint8_t checksum = 0;
    for (int i = 1; data[i] != '*' && data[i] != '\0'; i++) {
        checksum ^= (uint8_t)data[i];
    }
    return checksum;
}

二、常用 NMEA 语句详解

2.1 $GPRMC —— 推荐最小定位信息(最常用)

用途:提供最基本的时间、位置、速度、航向和日期信息。

格式:

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

字段详解:

字段名称格式/单位示例说明
0语句 ID$GPRMC$GPRMC固定
1UTC 时间hhmmss.sss024813.64002:48:13.640
2定位状态A/VAA=有效,V=无效
3纬度ddmm.mmmm3158.460831°58.4608′
4北纬/南纬N/SN
5经度dddmm.mmmm11848.3737118°48.3737′
6东经/西经E/WE
7对地速度节10.051 节 = 1.852 km/h
8对地航向度324.27真北参考
9UTC 日期ddmmyy1507062006 年 7 月 15 日
10磁偏角度可选
11磁偏角方向E/W可选
12模式指示A/D/E/NAA=自主,D=差分,E=估算,N=无效

2.2 $GPGGA —— GPS 定位信息

用途:提供详细的定位信息,包括海拔、卫星数量、精度因子等。

格式:

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

字段详解:

字段名称说明
0语句 ID$GPGGA
1UTC 时间hhmmss.sss 格式
2纬度ddmm.mmmm 格式
3纬度方向N/S
4经度dddmm.mmmm 格式
5经度方向E/W
6GPS 状态0=未定位,1=单点定位,2=差分定位
7使用卫星数00-12
8HDOP水平精度因子
9海拔高度米
10大地水准面高度米
11差分时间秒
12差分站 ID0000-1023

2.3 $GPGSA —— 精度因子与卫星信息

用途:提供使用的卫星编号和精度因子(PDOP/HDOP/VDOP)。

格式:

$GPGSA,<1>,<2>,<3>,<3>,...,<3>,<4>,<5>,<6>*hh
字段名称说明
0语句 ID$GPGSA
1模式M=手动,A=自动
2定位类型1=无,2=2D,3=3D
3PRN 卫星号12 个字段,用于定位的卫星编号
4PDOP位置精度因子
5HDOP水平精度因子
6VDOP垂直精度因子

2.4 $GPGSV —— 可见卫星信息

用途:提供天空中的可见卫星信息(仰角、方位角、信噪比)。

格式:

$GPGSV,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<4>,<5>,<6>,<7>,...*hh
字段名称说明
0语句 ID$GPGSV
1总语句数当前 GSV 语句总数
2当前语句号1-总语句数
3卫星总数00-12(可见卫星数)
4PRN 号卫星编号 01-32
5仰角00-90 度
6方位角000-359 度
7信噪比00-99 dB

注意:每条 GSV 语句最多输出 4 颗卫星,超过的卫星在后续语句中输出。

2.5 $GPVTG —— 地面速度信息

用途:提供对地速度和航向(真北/磁北两种参考)。

格式:

$GPVTG,<1>,T,<2>,M,<3>,N,<4>,K,<5>*hh
字段名称说明
0语句 ID$GPVTG
1真北航向000-359 度
2磁北航向000-359 度
3速度节
4速度公里/小时
5模式指示A/D/E/N

2.6 $GPGLL —— 地理经纬度信息

用途:仅提供地理位置信息(无速度/航向)。

格式:

$GPGLL,<1>,<2>,<3>,<4>,<5>,<6>,<7>*hh
字段名称说明
0语句 ID$GPGLL
1纬度ddmm.mmmm
2纬度方向N/S
3经度dddmm.mmmm
4经度方向E/W
5UTC 时间hhmmss.sss
6状态A/V
7模式指示A/D/E/N

三、坐标转换方法

NMEA 0183 协议中的经纬度采用度分(DDMM.MMMM)格式,使用时通常需要转换为十进制度(DD.DDDDDD)格式。

3.1 度分格式转十进制度

十进制度 = 度 + 分 / 60

示例:

  • 输入纬度:3158.4608(表示 31 度 58.4608 分)
  • 转换过程:31 + 58.4608 / 60 = 31 + 0.9743467 = 31.9743467°

3.2 C++ 实现

double DM2DD(double dm) {
    int degrees = static_cast<int>(dm / 100);
    double minutes = dm - degrees * 100;
    return degrees + minutes / 60.0;
}

// 带方向的处理(南纬/西经为负数)
double DM2DDWithDirection(double dm, char direction) {
    double result = DM2DD(dm);
    if (direction == 'S' || direction == 'W') {
        result = -result;
    }
    return result;
}

四、C++ 开源库选型对比

4.1 总体对比表

特性libnmeaminmeamarnav
语言C (C99)C (C99)C++11/17
代码体积中等(完整库)极小(2 个文件)较大(完整框架)
内存管理传统手动管理无动态分配RAII 现代 C++
支持语句数中等(~15 种)较少(~10 种)丰富(80+ 种)
是否支持生成语句仅解析仅解析支持
特殊功能ESP32 官方组件无浮点运算版本航海计算、AIS
许可证MITMITBSD 3-Clause
最佳环境嵌入式 Linux/ESP32单片机/STM32/Arduino桌面/服务器

4.2 libnmea 详解

GitHub 仓库:https://github.com/janantes/libnmea(推荐)或相关衍生项目

核心特点:

  • 纯 C 实现,跨平台支持好
  • 模块化架构,可动态加载语句插件
  • Espressif(ESP32)官方提供组件支持
  • 语句结构体设计清晰,易于使用

使用示例:

#include <libnmea/nmea.h>

// 创建解析器
nmea_s *parser = nmea_new();

// 解析语句
char sentence[] = "$GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,,,A*50";
nmea_info_s info;

if (nmea_parse(parser, sentence, &info)) {
    printf("纬度: %f, 经度: %f\n", info.latitude, info.longitude);
    printf("速度: %f 节\n", info.speed);
    printf("航向: %f 度\n", info.course);
}

// 清理
nmea_free(parser);

适用场景:

  • 嵌入式 Linux 设备
  • ESP32/ESP8266 物联网项目
  • 需要支持多种 NMEA 语句的 C 语言项目

4.3 minmea 详解

GitHub 仓库:https://github.com/kosma/minmea

核心特点:

  • 极致轻量:仅两个文件(minmea.h / minmea.c)
  • 零动态内存分配:所有内存栈上静态分配
  • 可选无浮点模式:适合无 FPU 的低端 MCU
  • 高可定制性:编译时宏配置

使用示例:

#include "minmea.h"

char sentence[] = "$GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,,,A*50";
struct minmea_sentence_rmc rmc;

if (minmea_parse_rmc(&rmc, sentence)) {
    printf("纬度: %d°%d'%d\" %c\n", 
           rmc.latitude.degrees, 
           rmc.latitude.minutes, 
           rmc.latitude.seconds,
           rmc.latitude_direction == MINMEA_LAT_NORTH ? 'N' : 'S');
    printf("速度: %d 节\n", rmc.speed.value);
}

适用场景:

  • 低端单片机(STM32F1、AVR、PIC 等)
  • 内存受限的 RTOS 环境
  • 只需要核心 NMEA 语句的项目

4.4 marnav 详解

GitHub 仓库:https://github.com/mariokonrad/marnav

核心特点:

  • 现代 C++ 接口:充分利用 C++11/17 特性
  • 功能最全面:支持 80+ 种 NMEA 语句
  • 双向支持:既解析也生成 NMEA 语句
  • 内置航海计算:CPA、TCPA、恒向线计算等
  • AIS 支持:可解析船舶自动识别系统数据

使用示例:

#include <marnav/nmea/io.hpp>
#include <marnav/nmea/rmc.hpp>

int main() {
    std::string raw = "$GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,,,A*50";

    // 解析
    auto sentence = marnav::nmea::make_sentence(raw);
    auto rmc = marnav::nmea::sentence_cast<marnav::nmea::rmc>(sentence);

    if (rmc) {
        std::cout << "时间: " << rmc->get_time_utc() << std::endl;
        std::cout << "纬度: " << rmc->get_latitude() << std::endl;
        std::cout << "经度: " << rmc->get_longitude() << std::endl;
        std::cout << "速度: " << rmc->get_sog_knots() << " kn" << std::endl;
    }

    return 0;
}

CMake 集成:

include(FetchContent)
FetchContent_Declare(
    marnav
    GIT_REPOSITORY https://github.com/mariokonrad/marnav.git
)
FetchContent_MakeAvailable(marnav)
target_link_libraries(your_project PRIVATE marnav::marnav)

适用场景:

  • 桌面/服务器端应用程序(Windows/Linux/macOS)
  • 船舶导航系统
  • 电子海图显示与信息系统(ECDIS)模拟器
  • 需要 AIS 解析的专业项目

五、快速选型建议

你的情况推荐库理由
使用 C++11/17 开发桌面/服务器应用marnav现代 C++ 接口,功能最全,开发效率高
使用 C 语言,运行在嵌入式 Linuxlibnmea轻量、模块化、生态好
开发 ESP32 项目libnmea官方组件支持,安装方便
在低端单片机(STM32F1、Arduino)上开发minmea无动态内存分配,代码极小
需要生成 NMEA 语句marnav唯一原生支持双向的 C++ 库
需要 AIS 解析或航海专业计算marnav内置相关功能,无需额外开发

六、总结与建议

6.1 关于协议处理

NMEA 0183 协议本身并不复杂,核心要点包括:

  1. 正确配置串口参数(4800-8-N-1)
  2. 校验和验证确保数据完整性
  3. 度分坐标到十进制度的转换
  4. 根据语句类型字段分发给对应的解析器

6.2 关于开源库选择

对于实际项目,强烈建议使用成熟的开源库而非自己实现。原因如下:

  • 节省大量开发与调试时间
  • 避免隐藏的边界条件 bug
  • 获得社区持续维护和更新

最终建议:

  • 如果你正在开发 C++ 桌面/服务器 项目 → 选择 marnav
  • 如果你在 嵌入式 Linux 上开发(C 语言为主) → 选择 libnmea
  • 如果你在 低端单片机 上开发 → 选择 minmea

七、参考资料

资源链接/说明
NMEA 0183 官方规范NMEA 官网(需购买)
marnav 文档https://marnav.io
minmea GitHubhttps://github.com/kosma/minmea
libnmea(ESP32 组件)https://components.espressif.com/components/janantala/libnmea
各大 GNSS 厂商文档u-blox、Trimble、NovAtel 等厂商协议手册

作者

老丹

关注我
其他文章
上一个

C/C++ 开源JSON库详解:cJSON、json-c、jsoncpp

下一个

UFW 详解:Linux 防火墙的简洁之道

关于博主

    老丹是一名C/C++后台开发工程师,信奉“无抽象不设计,无性能不生产”。

  • 技术栈:Modern C++、Linux环境编程、多线程/并发、网络编程等。
  • 信条:能用constexpr解决的问题绝不拖到运行时,能靠RAII避免的泄漏绝不写析构。
  • 正在填坑:从解封装到渲染的C++全链路实现,正在驯服FFmpeg与H.264/H.265。
  • 输出原则:这里的每一段代码都经过-Wall -Wextra -Werror -O2的洗礼。

搜索

近期文章

  • 正向代理与反向代理:一篇搞懂两者的区别 2026年6月6日
  • WP Super Cache 完全指南:为你的 WordPress 博客加速 2026年6月6日
  • WordPress 中 Redis 对象缓存完全指南 2026年6月6日
  • UFW 详解:Linux 防火墙的简洁之道 2026年6月6日
  • NMEA 0183 协议处理与 C++ 开源库选型指南 2026年6月3日

文章分类

  • C/C++开发 (5)
  • Linux服务配置 (3)
  • 计算机理论 (1)
联系我们:📍 地址:中国·广东省深圳市   |   ✉️ 邮箱:support@tanglinux.com   |   💬 QQ:870866607
版权所有:老丹的足迹粤ICP备2026061170号-1       公安备案图标 粤公网安备44030002013274号