【ESP32S3 + ATGM332D GPS模块实战:从NMEA解析到本地速度计算】
·
【ESP32S3 + ATGM332D GPS模块实战:从NMEA解析到本地速度计算】
项目概述
本文详细介绍如何使用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 定位无效
现象:定位状态: 无效
排查步骤:
- 检查天线连接是否牢固
- 确保天线在室外开阔处
- 等待至少30秒冷启动时间
- 检查串口波特率是否正确(ATGM332D默认9600)
5.2 速度为0
现象:计算航速: 0.00 km/h
排查步骤:
- 检查是否有连续的位置变化
- 确认参考点已设置(首次定位后)
- 检查时间差计算是否正确
5.3 航向跳变
现象:航向角度突变
原因:
- GPS定位存在微小抖动
- 低速运动时位移量小,噪声影响大
解决方案:
- 增加滤波算法(如卡尔曼滤波)
- 对连续多个采样点进行平滑处理
六、总结
本文实现了从GPS数据采集到速度航向计算的完整流程:
- 硬件层面:使用ESP32硬件串口与ATGM332D模块通信
- 数据解析:利用TinyGPSPlus库高效解析NMEA语句
- 坐标转换:通过本地墨卡托投影建立ENU坐标系
- 速度计算:基于位移差分计算真实运动参数
该方案适用于无人机、机器人、车辆等需要实时定位和运动状态监测的应用场景。
参考资料:
更多推荐



所有评论(0)