项目概述

本文详细介绍如何使用ESP32通过硬件串口读取ATGM332D GPS模块数据,使用TinyGPSPlus库解析NMEA语句,并通过本地墨卡托投影(ENU坐标系)计算真实速度和航向。


源码地址https://download.csdn.net/download/VOR234/93024101

一、硬件准备

先采用GnssToolKit3配置GPS使用模式为航海模式
在这里插入图片描述
更新速率为10Hz
在这里插入图片描述
配置波特率为9600
在这里插入图片描述
查看原始NEMA数据
在这里插入图片描述
查看收星情况
在这里插入图片描述

1.1 核心部件

部件 型号 说明
主控板 ESP32-S3 开发板
GPS模块 ATGM332D 支持GPS/北斗双模定位
天线 GPS有源天线 SMA接口

1.2 硬件连接

ATGM332D模块      ESP32-S3
     TX  ----------> GPIO17 (UART1_RX)
     RX  ----------> GPIO18 (UART1_TX)
    VCC  ----------> 3.3V
    GND  ----------> GND

在这里插入图片描述

注意事项:

  • ATGM332D默认波特率为9600
  • 使用硬件串口Serial1,避免占用调试串口
  • GPS模块需外接有源天线并放置在开阔处

二、软件配置

2.1 PlatformIO环境配置

platformio.ini中添加依赖库:

[env:freenove_esp32_s3_wroom]
platform = espressif32
board = freenove_esp32_s3_wroom
framework = arduino
monitor_speed = 115200
upload_speed = 921600

lib_deps =
    mikalhart/TinyGPSPlus@^1.0.3  ; GPS NMEA解析库

2.2 核心库介绍

TinyGPSPlus是一个轻量级、高效的NMEA解析库,特点:

  • 支持所有标准NMEA语句
  • 自动解析经纬度、时间、速度、航向等信息
  • 内存占用小,适合嵌入式设备

三、代码实现

3.1 完整代码

#include <Arduino.h>
#include <TinyGPSPlus.h>
#include <math.h>

// GPS模块配置
#define GPS_UART Serial1       // 使用硬件串口1
#define GPS_BAUD_RATE 9600    // ATGM332D默认波特率
#define GPS_TX_PIN 18         // UART1_TX
#define GPS_RX_PIN 17         // UART1_RX
#define PRINT_INTERVAL_MS 500 // 2Hz输出频率

// TinyGPSPlus对象
TinyGPSPlus gps;

// 参考点(初始位置作为本地坐标系原点)
double refLat = 0.0;
double refLng = 0.0;
bool refSet = false;

// 上一时刻的位置和时间
double lastEast = 0.0;
double lastNorth = 0.0;
unsigned long lastTimeMs = 0;

// 计算得到的速度和航向
float calcSpeed = 0.0;
float calcHeading = 0.0;

// 将经纬度转换为本地ENU坐标
void latLngToENU(double lat, double lng, double refLat, double refLng, 
                 double& east, double& north);

void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println("ATGM332D GPS模块测试 - 本地墨卡托投影计算");
  
  // 初始化GPS串口
  GPS_UART.begin(GPS_BAUD_RATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
  delay(1000);
  
  Serial.printf("GPS UART: Serial1, TX=%d, RX=%d, Baud=%d\n", 
                GPS_TX_PIN, GPS_RX_PIN, GPS_BAUD_RATE);
  Serial.printf("输出频率: %d Hz\n", 1000 / PRINT_INTERVAL_MS);
  Serial.println("========================================");
}

void loop() {
  // 持续读取并解析GPS数据
  while (GPS_UART.available() > 0) {
    gps.encode(GPS_UART.read());
  }
  
  // 定时输出(2Hz)
  static unsigned long lastPrintTime = 0;
  if (millis() - lastPrintTime >= PRINT_INTERVAL_MS) {
    lastPrintTime = millis();
    
    Serial.println("========================================");
    
    if (gps.location.isValid()) {
      double lat = gps.location.lat();
      double lng = gps.location.lng();
      
      // 设置参考点
      if (!refSet) {
        refLat = lat;
        refLng = lng;
        refSet = true;
        Serial.println("[INFO] 参考点已设置");
      }
      
      // 转换为ENU坐标
      double east, north;
      latLngToENU(lat, lng, refLat, refLng, east, north);
      
      // 计算速度和航向
      unsigned long currentTimeMs = millis();
      if (lastTimeMs > 0 && refSet) {
        double dt = (currentTimeMs - lastTimeMs) / 1000.0;
        if (dt > 0.01) {
          double dx = east - lastEast;
          double dy = north - lastNorth;
          
          // 速度: m/s -> km/h
          calcSpeed = sqrt(dx*dx + dy*dy) / dt * 3.6;
          
          // 航向: 弧度转角度,0-360度
          calcHeading = atan2(dx, dy) * 180.0 / M_PI;
          if (calcHeading < 0) calcHeading += 360.0;
        }
      }
      
      // 更新状态
      lastEast = east;
      lastNorth = north;
      lastTimeMs = currentTimeMs;
      
      // 输出数据
      Serial.println("定位状态: 有效");
      
      if (gps.time.isValid()) {
        Serial.printf("UTC时间: %02d:%02d:%06.3f\n", 
                      gps.time.hour(), gps.time.minute(), 
                      gps.time.second() + gps.time.centisecond() / 100.0);
      }
      
      if (gps.date.isValid()) {
        Serial.printf("日期: %04d-%02d-%02d\n", 
                      gps.date.year(), gps.date.month(), gps.date.day());
      }
      
      Serial.printf("纬度: %.6f°\n", lat);
      Serial.printf("经度: %.6f°\n", lng);
      Serial.printf("东向偏移: %.3f m\n", east);
      Serial.printf("北向偏移: %.3f m\n", north);
      Serial.printf("计算航速: %.6f km/h\n", calcSpeed);
      Serial.printf("计算航向: %.6f°\n", calcHeading);
      Serial.printf("原始航速: %.6f km/h\n", gps.speed.kmph());
      Serial.printf("原始航向: %.6f°\n", gps.course.deg());
      Serial.printf("卫星数量: %d\n", gps.satellites.value());
      
      if (gps.hdop.isValid()) {
        Serial.printf("HDOP: %.2f\n", gps.hdop.hdop());
      }
      
      if (gps.altitude.isValid()) {
        Serial.printf("海拔高度: %.2f m\n", gps.altitude.meters());
      }
    } else {
      Serial.println("定位状态: 无效");
      Serial.println("请将天线放置在开阔地带...");
      Serial.printf("已处理字符数: %lu\n", gps.charsProcessed());
    }
    
    Serial.println("========================================");
    Serial.println();
  }
  
  if (gps.charsProcessed() < 10) {
    Serial.println("等待GPS数据...");
    delay(500);
  }
}

// 经纬度转ENU坐标
void latLngToENU(double lat, double lng, double refLat, double refLng, 
                 double& east, double& north) {
  const double R = 6378137.0; // 地球半径(米)
  
  double latRad = lat * M_PI / 180.0;
  double lngRad = lng * M_PI / 180.0;
  double refLatRad = refLat * M_PI / 180.0;
  double refLngRad = refLng * M_PI / 180.0;
  
  double cosLat = cos(refLatRad);
  
  east = R * (lngRad - refLngRad) * cosLat;
  north = R * (latRad - refLatRad);
}

3.2 关键技术解析

3.2.1 NMEA数据解析

TinyGPSPlus通过gps.encode()方法逐字符解析NMEA数据流:

while (GPS_UART.available() > 0) {
  gps.encode(GPS_UART.read());
}

解析后的关键数据:

  • gps.location.lat() - 纬度(十进制度)
  • gps.location.lng() - 经度(十进制度)
  • gps.speed.kmph() - 对地速度(km/h)
  • gps.course.deg() - 航向角度(度)
3.2.2 本地墨卡托投影(ENU坐标系)

原理:将地球表面近似为平面,以首次定位点为原点建立东-北-天坐标系。

核心公式

east = R * (lng - refLng) * cos(refLat)
north = R * (lat - refLat)

其中:

  • R = 地球半径(6378137米)
  • refLat, refLng = 参考点经纬度
  • east, north = 相对于参考点的东向、北向偏移(米)
3.2.3 速度与航向计算
// 速度 = 位移 / 时间
calcSpeed = sqrt(dx*dx + dy*dy) / dt * 3.6;

// 航向 = arctan2(东向位移, 北向位移)
calcHeading = atan2(dx, dy) * 180.0 / M_PI;

说明

  • 速度单位转换:m/s × 3.6 = km/h
  • 航向范围:0°(正北)→ 90°(正东)→ 180°(正南)→ 270°(正西)

四、测试结果

串口接收数据
在这里插入图片描述

4.1 输出示例

========================================
定位状态: 有效
UTC时间: 13:01:11.600
日期: 2026-06-24
纬度: 38.869444°
经度: 121.532086°
东向偏移: -1.430 m
北向偏移: 2.727 m
计算航速: 3.079846 km/h
计算航向: 317.520691°
原始航速: 2.277960 km/h
原始航向: 323.150000°
卫星数量: 21
HDOP: 0.80
海拔高度: 82.00 m
========================================

4.2 数据对比分析

参数 原始GPS数据 计算值(ENU) 说明
航速 2.28 km/h 3.08 km/h 计算值更接近实际运动状态
航向 323.15° 317.52° 计算值基于实际位移方向

4.3 关键指标

  • 定位精度:水平精度优于1米(HDOP < 1.0)
  • 卫星数量:21颗(GPS+北斗双模)
  • 更新频率:约10Hz(模块输出)
  • 计算频率:2Hz(程序设定)

五、常见问题排查

5.1 定位无效

现象定位状态: 无效

排查步骤

  1. 检查天线连接是否牢固
  2. 确保天线在室外开阔处
  3. 等待至少30秒冷启动时间
  4. 检查串口波特率是否正确(ATGM332D默认9600)

5.2 速度为0

现象计算航速: 0.00 km/h

排查步骤

  1. 检查是否有连续的位置变化
  2. 确认参考点已设置(首次定位后)
  3. 检查时间差计算是否正确

5.3 航向跳变

现象:航向角度突变

原因

  • GPS定位存在微小抖动
  • 低速运动时位移量小,噪声影响大

解决方案

  • 增加滤波算法(如卡尔曼滤波)
  • 对连续多个采样点进行平滑处理

六、总结

本文实现了从GPS数据采集到速度航向计算的完整流程:

  1. 硬件层面:使用ESP32硬件串口与ATGM332D模块通信
  2. 数据解析:利用TinyGPSPlus库高效解析NMEA语句
  3. 坐标转换:通过本地墨卡托投影建立ENU坐标系
  4. 速度计算:基于位移差分计算真实运动参数

该方案适用于无人机、机器人、车辆等需要实时定位和运动状态监测的应用场景。


参考资料

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐