基于STM32的GPS NMEA解析驱动设计与实现

引言

在嵌入式项目中,GPS模块(如ATGM336H、NEO-6M等)常被用于获取位置、速度和时间信息。这些模块通常通过串口输出符合NMEA-0183协议的ASCII语句,如$GNGGA$GNRMC等。解析这些语句并将其转换为结构化数据,是开发导航、定位功能的第一步。本文分享一个轻量级、可移植的GPS解析驱动,基于STM32 HAL库实现DMA+空闲中断接收,并包含完整的NMEA解析器。

驱动设计目标

  • 可移植性:核心解析代码不依赖特定硬件,仅使用标准C库,易于移植到其他MCU。

  • 高效性:采用DMA+空闲中断接收,减少CPU干预;解析过程无动态内存分配,适合资源受限的嵌入式环境。

  • 稳定性:具备校验和验证、环形缓冲区防溢出、行缓冲区溢出保护等机制。

  • 易用性:提供简洁的API,开发者只需初始化、在中断回调中传递数据、在主循环中处理即可获得解析后的GPS数据。

整体架构

驱动分为三层:

  1. 硬件抽象层:通过HAL库与UART外设交互,配置DMA和中断。

  2. 数据接收层:利用环形缓冲区暂存DMA收到的字节流,并在主循环中提取完整的NMEA行。

  3. 协议解析层:对每一行进行校验、字段分割,并根据语句类型(GGA/RMC)更新数据。

数据接收:DMA + 空闲中断 + 环形缓冲区

STM32的UART支持空闲中断,当总线空闲时触发,非常适合接收不定长数据。配合DMA的循环模式,可以持续接收数据而无需CPU频繁介入。

关键实现

复制代码
// 初始化时启动DMA接收
HAL_UARTEx_ReceiveToIdle_DMA(huart, rx_dma_buf, RX_BUF_SIZE);

// 空闲中断回调中,将DMA缓冲区数据搬运到环形缓冲区
void GPS_UART_RxEventCallback(GPS_Handle *gps, uint16_t Size) {
    for (uint16_t i = 0; i < Size; i++) {
        uint16_t next_head = (gps->rx_ring_head + 1) % RING_BUF_SIZE;
        if (next_head != gps->rx_ring_tail) {
            gps->rx_ring_buf[gps->rx_ring_head] = gps->rx_dma_buf[i];
            gps->rx_ring_head = next_head;
        }
    }
    // 重新启动DMA
    HAL_UARTEx_ReceiveToIdle_DMA(...);
}

环形缓冲区的读写索引分别在中断和主循环中更新,确保数据安全。

解析器设计:逐行解析,增量更新

NMEA语句以$开头,\r\n结尾,每行长度通常不超过100字节。解析器在GPS_Process中逐字符读取环形缓冲区,遇到\n时认为一行结束。

行提取与解析

复制代码
while (head != tail) {
    ch = ring_buf[tail++];
    if (ch == '$') line_len = 0;          // 新行开始
    if (line_len < LINE_BUF_SIZE-1) line_buf[line_len++] = ch;
    if (ch == '\n') {                      // 行结束
        line_buf[line_len] = '\0';
        parse_nmea_sentence((char*)line_buf, &gps_data);
        line_len = 0;
    }
}

解析核心:增量更新

为了避免局部变量未初始化导致的随机值(如之前遇到的HDOP异常),解析函数直接修改传入的gps_data_t结构体,仅更新当前语句包含的字段,保留其他字段的历史值。这样,GGA提供定位信息,RMC提供速度、日期,两者互补。

GGA解析(部分)
复制代码
static int parse_gga(char *fields[], gps_data_t *data) {
    data->hour = ...; data->minute = ...; data->second = ...;
    data->latitude = nmea_to_degrees(fields[2]);
    if (fields[3][0] == 'S') data->latitude = -data->latitude;
    data->longitude = nmea_to_degrees(fields[4]);
    if (fields[5][0] == 'W') data->longitude = -data->longitude;
    data->fix_quality = atoi(fields[6]);
    data->satellites = atoi(fields[7]);
    data->hdop = atof(fields[8]);
    data->altitude = atof(fields[9]);
    data->valid = (data->fix_quality > 0);
    return 0;
}
RMC解析(部分)
复制代码
static int parse_rmc(char *fields[], gps_data_t *data) {
    // 时间、日期、状态
    data->valid = (fields[2][0] == 'A');
    data->latitude = nmea_to_degrees(fields[3]);
    if (fields[4][0] == 'S') data->latitude = -data->latitude;
    data->longitude = nmea_to_degrees(fields[5]);
    if (fields[6][0] == 'W') data->longitude = -data->longitude;
    data->speed_knots = atof(fields[7]);
    data->speed_kmph = data->speed_knots * 1.852f;
    data->course = atof(fields[8]);
    // 日期转换...
    return 0;
}

语句类型识别

由于现代GPS模块常输出$GNGGA(北斗+GPS混合),解析器通过检查类型字段的后三个字符("GGA"或"RMC")来区分,无需硬编码前缀。

数据结构

  • gps_data_t:存储所有解析后的GPS信息,包括时间、日期、经纬度、海拔、速度、航向、卫星数、HDOP、有效性标志等。

  • GPS_Handle:驱动句柄,包含UART句柄、DMA缓冲区、环形缓冲区、读写索引、数据就绪标志以及最新的解析数据。

API使用示例

复制代码
GPS_Handle gps;

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
    if (huart == &huart1) GPS_UART_RxEventCallback(&gps, Size);
}

int main() {
    HAL_Init(); MX_USART1_UART_Init();
    GPS_Init(&gps, &huart1);

    while (1) {
        GPS_Process(&gps);
        if (gps.gps_data.valid) {
            printf("Lat: %.6f, Lon: %.6f\n", gps.gps_data.latitude, gps.gps_data.longitude);
            gps.gps_data.valid = 0;  // 清除标志,等待下一次有效数据
        }
        HAL_Delay(100);
    }
}

实际调试效果

可移植性说明

  • 硬件依赖 :仅通过HAL库的HAL_UART_Transmit(用于printf重定向)和DMA相关函数,若移植到其他平台,只需替换这些底层接口,核心解析代码无需改动。

  • 编译器 :使用标准C库(string.h, stdlib.h, ctype.h),无平台限制。

  • 内存:所有缓冲区均为静态或局部数组,无动态内存分配,适合裸机或RTOS环境。

调试与优化

  • 原始数据打印 :通过宏DEBUG_PRINT_RAW控制,方便观察原始NMEA流。

  • 环形缓冲区大小:根据GPS输出频率调整,一般512字节足够。

  • 解析性能:每秒几十条语句,解析耗时极短,不影响主循环。

总结

本文设计的GPS解析驱动充分考虑了嵌入式开发的实际需求:高效接收、稳健解析、易于移植。通过DMA+空闲中断降低CPU负载,环形缓冲区避免数据丢失,增量更新确保数据完整性,简洁的API降低使用门槛。该驱动已在实际项目中验证,可稳定运行于STM32系列MCU。

改进空间

  • 支持更多NMEA语句(如GSA、GSV)。

  • 增加经纬度格式转换(度分秒转度)。

  • 添加UTC转本地时间的函数。

希望这篇博客能为你的GPS项目开发提供参考。如有任何问题或建议,欢迎交流讨论。

参考工程

通过网盘分享的文件:GPS_NMEA_STM32F103C6T6.zip

链接: https://pan.baidu.com/s/16TK2Zk2i5J2slXHXYs19iw?pwd=9pr5 提取码: 9pr5

--来自百度网盘超级会员v8的分享

相关推荐
v先v关v住v获v取2 小时前
茶树修剪机结构设计2张cad+设计说明书+三维图
科技·单片机·51单片机
大志出奇迹2 小时前
STM32常用变量类型位数及取值范围
stm32·单片机·嵌入式硬件
LCG元3 小时前
STM32项目实战:基于STM32F103的智能循迹避障小车
stm32·单片机·嵌入式硬件
v先v关v住v获v取3 小时前
NEXUS卡丁车前悬挂控制臂的结构建模与多工况受力分析5张cad+三维图+设计说明书
科技·单片机·51单片机
luoshanxuli20103 小时前
ESP-IDF 简介
嵌入式硬件·物联网·系统架构
GodKK老神灭4 小时前
SWD读取AP寄存器完整流程
单片机·keil
羽获飞4 小时前
从零开始学嵌入式之STM32——27.基于STM32F103C8T6MCU的寄存器方式实现按键调整PWM占空比,调整输出功率
stm32·单片机·嵌入式硬件
学嵌入式的小杨同学4 小时前
STM32 进阶封神之路(十五):DHT11 单总线实战 —— 温湿度检测从时序解析到代码落地(库函数 + 寄存器)
vscode·stm32·单片机·嵌入式硬件·mcu·智能硬件·pcb工艺
QYQ_11274 小时前
嵌入式学习——51单片机
嵌入式硬件·学习·51单片机