You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32低内存占用下优雅解析NMEA语句的技术咨询

NMEA语句逐行解析优化方案(基于STM32 HAL)

问题背景

正在开发NMEA语句解析器,目标是逐行解析每秒重复发送的NMEA语句组,示例语句如下:

$GPBWC,220516,5130.02,N,00046.34,W,213.8,T,218.0,M,0004.6,N,EGLM*11
$GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
$GPVTG,360.0,T,348.7,M,000.0,N,000.0,K*43

最初使用HAL_UART_Receive_IT中断接收,仅能解析首条$GPBWC语句,后续$GPRMC和$GPVTG因UART持续接收导致丢失。若一次性接收整组语句会占用过多内存,且新增语句类型时需扩展内存,因此需要一种低内存占用、能完整逐行解析所有语句的优雅方案。

当前实现代码(可运行但非最优)

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    update(rx_byte[0]);
    HAL_UART_Receive_IT(&hlpuart1, rx_byte, 1);
}

void update(char c) {

    if (c == '$') {
        sen_index = 0;
        msgValid = 1;
    }
    
    if (msgValid == 1) {
        sentense[sen_index] = c;
        sen_index++;

        if (c == '\n') {
    
            msgValid = 0;
            strncpy(prefix, sentense + 1, 5);
    
            if (strcmp(prefix, "GPRMC") == 0) {
                // 解析GPRMC逻辑
            }
    
            if (strcmp(prefix, "GPBWC") == 0) {
                // 解析GPBWC逻辑
            }
    
            if (strcmp(prefix, "GPVTG") == 0) {
                // 解析GPVTG逻辑
            }
        }
        // 省略其他代码
    }
}

// 主循环中
while (1) {
    HAL_UART_Receive_IT(&hlpuart1, rx_byte, 1);
}

优化方案

1. 内存安全与边界检查

当前代码未对sen_index做边界限制,若串口收到异常数据(如无换行的超长串),会导致sentense数组越界,引发硬件故障。需添加数组长度判断:

#define MAX_NMEA_LEN 80  // NMEA单条语句最大长度一般不超80字节

void update(char c) {
    if (c == '$') {
        sen_index = 0;
        msgValid = 1;
        memset(sentense, 0, MAX_NMEA_LEN); // 重置缓冲区,避免残留数据干扰
    }
    
    if (msgValid == 1) {
        // 检查数组边界,防止越界
        if (sen_index >= MAX_NMEA_LEN - 1) {
            msgValid = 0; // 丢弃异常超长数据
            return;
        }
        sentense[sen_index++] = c;

        if (c == '\n') {
            msgValid = 0;
            // 处理NMEA标准的CRLF结尾,统一字符串结束符
            if (sen_index >= 6 && sentense[sen_index-2] == '\r') {
                sentense[sen_index-2] = '\0';
            } else {
                sentense[sen_index-1] = '\0';
            }
            // 提取前缀并手动添加结束符,避免strncpy未终止问题
            strncpy(prefix, sentense + 1, 5);
            prefix[5] = '\0';
            
            // 分支判断解析逻辑
            if (strcmp(prefix, "GPRMC") == 0) {
                parse_gprmc(sentense);
            } else if (strcmp(prefix, "GPBWC") == 0) {
                parse_gpbwc(sentense);
            } else if (strcmp(prefix, "GPVTG") == 0) {
                parse_gpvtg(sentense);
            }
            // 新增语句类型只需添加else if分支,无需修改内存分配
        }
    }
}

2. 模块化解析逻辑

将不同类型语句的解析逻辑拆分为独立函数,降低update函数复杂度,便于后续扩展:

void parse_gprmc(char *nmea_str) {
    // 拆分逗号分隔字段,提取时间、位置、状态等信息
    char *token = strtok(nmea_str, ",");
    int field_idx = 0;
    while (token != NULL) {
        switch(field_idx) {
            case 1: // 时间字段
                // 处理时间字符串逻辑
                break;
            case 2: // 有效性状态(A=有效,V=无效)
                // 处理状态逻辑
                break;
            // 其他字段依次处理
        }
        token = strtok(NULL, ",");
        field_idx++;
    }
}

// 同理实现parse_gpbwc、parse_gpvtg等函数

3. 中断回调优化

主循环中无需反复调用HAL_UART_Receive_IT,仅需在初始化和回调中启动下一次中断即可,避免冗余操作:

// 初始化时启动第一次中断
HAL_UART_Receive_IT(&hlpuart1, rx_byte, 1);

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == LPUART1) { // 确认串口实例,避免多串口干扰
        update(rx_byte[0]);
        // 启动下一次中断接收
        HAL_UART_Receive_IT(&hlpuart1, rx_byte, 1);
    }
}

// 主循环
while (1) {
    // 处理解析后的数据,如更新显示、存储等
    // 无需再调用HAL_UART_Receive_IT
}

4. 校验和验证

NMEA语句末尾的*XX是校验和,添加校验逻辑确保数据完整性:

uint8_t calculate_nmea_checksum(char *nmea_str) {
    uint8_t checksum = 0;
    int i = 1; // 跳过$符号
    while (nmea_str[i] != '*' && nmea_str[i] != '\0') {
        checksum ^= nmea_str[i];
        i++;
    }
    return checksum;
}

// 在解析前验证校验和
if (c == '\n') {
    char *checksum_str = strrchr(sentense, '*');
    if (checksum_str != NULL) {
        uint8_t received_checksum = strtol(checksum_str + 1, NULL, 16);
        uint8_t calculated_checksum = calculate_nmea_checksum(sentense);
        if (received_checksum != calculated_checksum) {
            msgValid = 0;
            return; // 校验失败,丢弃该语句
        }
    }
    // 后续解析逻辑
}

优化后优势

  • 低内存占用:仅需单条NMEA语句最大长度的缓冲区(约80字节),无需存储整组语句
  • 可扩展性:新增NMEA语句类型只需添加对应的解析函数和前缀判断分支
  • 鲁棒性:添加边界检查、校验和验证,避免异常数据导致的程序崩溃
  • 简洁性:模块化设计降低代码耦合,便于维护

内容的提问来源于stack exchange,提问作者Yann

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 05:22:53