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

为何sscanf无法正确提取GNSS数据中的UTC时间字段?

排查QuecTel BG96模块+QGPSLOC消息UTC时间字段提取失败问题

正在解析QuecTel BG96模块的GNSS数据,模块通过UART串口输出+QGPSLOC消息,使用sscanf提取字段时,除UTC时间(格式hhmmss.sss)外,其余数据均可正常提取,时间字段提取后始终为0。

相关信息

示例消息

"\r\n+QGPSLOC: 010842.0,32.04415,5.31028,1.7,55.0,2,150.36,0.0,0.0,\n060224,03\r\n\r\nOK\r\n"

手册字段定义

+QGPSLOC:<UTC>,<latitude>,<longitude>,<hdop>,<altitude>,<fix>,<cog>,<spkm>,<spkn>,<date>,<nsat> OK

其中<UTC>格式为hhmmss.sss(时-分-秒.毫秒)

预期输出

240206-010842.000,       32.04415,         5.31028,      1.7,       55.0, 0002,       150.6000,         0.0000, 00000003

用户代码

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

const char* utc_datetime_format =
    "%02u%02u%02u-%02u%02u%02u.%03u";
const char* print_format =
    "%18.18s, %14.5f, %15.5f, %8.1f, %10.1f, %04u, %14.4f, "\
    "%14.4f, %08u\n";

typedef struct
{
    uint64_t year : 12;
    uint64_t month : 4;
    uint64_t day : 8;
    uint64_t hour : 8;
    uint64_t minutes : 8;
    uint64_t seconds : 8;
    uint64_t msec : 16;
} DateAndTime_Compressed;

typedef struct
{
    float latitude;
    float longitude;
} GNSS_Coordinates;

typedef struct
{
    GNSS_Coordinates       coordinates;
    float                  horizontal_precision;
    float                  altitude;
    float                  course_over_ground;
    float                  speed_over_ground_mps;  // m/s
    DateAndTime_Compressed utc_time;
    uint8_t                fix_type;
    uint8_t                num_satellites;
} GNSS_Data;

int GNSS_ParseData(char* buf, GNSS_Data* data)
{
    const char* format =
        "%s %2" PRIu16 "%2" PRIu16 "%2" PRIu16 ".%3" PRIu16
        ",%f,%f,%f,%f,%" PRIu8 ",%3" PRIu16 ".%2" PRIu16
        ",%f,%f,%2" PRIu8 "%2" PRIu8 "%2" PRIu8 ",%" PRIu32
        "%s";
    char front_padding[128] = {0}, back_padding[128] = {0};

    uint16_t year, month, day, hour, minute, second,
        millisecond, cog_deg, cog_min;
    float tmp;

    // For some reason cannot extract hhmmss.sss from
    // message directly
    int ret = sscanf(
        buf, format, front_padding, &hour, &minute, &second,
        &millisecond, &(data->coordinates.latitude),
        &(data->coordinates.longitude),
        &data->horizontal_precision, &data->altitude,
        &data->fix_type, &cog_deg, &cog_min,
        &data->speed_over_ground_mps, &tmp, &day, &month,
        &year, &data->num_satellites, back_padding);
    if (ret != 19)
    {
        // Handle Error (but no error occurs here)
        return -1;
    }
    data->utc_time = (DateAndTime_Compressed){
        .year    = year,
        .month   = month,
        .day     = day,
        .hour    = hour,
        .minutes = minute,
        .seconds = second,
        .msec    = millisecond};

    data->speed_over_ground_mps *=
        (1000.0f / 3600.0f);  // kph to mps
    data->course_over_ground =
        (float)cog_deg + (((float)cog_min) / 60.0f) /
                             100.0f;  // ddd.mm to ddd.dd

    return 0;
}

int main(int argc, char** argv)
{
    char msg[] =
        "\r\n+QGPSLOC: "\
        "010842.0,32.04415,5.31028,1.7,55.0,2,150.36,0.0,0."\
        "0,060224,03\r\n\r\nOK\r\n";

    GNSS_Data gnss_data = {0};

    int ret = GNSS_ParseData(
        msg,
        &gnss_data);  // ret = 0 but data.utc_time wrong
                      // (dates are correct, times wrong)
    if (ret != 0)
    {
        printf("Failed to parse data\n");
        exit(-1);
    }
    char utc_date_string[64] = {0};
    snprintf(
        utc_date_string, 32, utc_datetime_format,
        gnss_data.utc_time.year, gnss_data.utc_time.month,
        gnss_data.utc_time.day, gnss_data.utc_time.hour,
        gnss_data.utc_time.minutes,
        gnss_data.utc_time.seconds,
        gnss_data.utc_time.msec);

    printf(
        print_format, utc_date_string,
        gnss_data.coordinates.latitude,
        gnss_data.coordinates.longitude,
        gnss_data.horizontal_precision, gnss_data.altitude,
        gnss_data.fix_type, gnss_data.course_over_ground,
        gnss_data.speed_over_ground_mps,
        gnss_data.num_satellites);

    return 0;
}

问题根源

你的sscanf格式字符串存在两个关键错误,导致UTC时间字段无法正确匹配:

  1. 连续数字格式符的匹配逻辑错误:
    你用%2" PRIu16 "%2" PRIu16 "%2" PRIu16 ".%3" PRIu16匹配hhmmss.sss格式,但sscanf的数字格式符会自动跳过前导空白,且连续数字会被当作一个整体解析,而非按指定宽度拆分。比如010842.0会被第一个格式符读取01,但后续格式符会继续读取剩余的0842,导致匹配逻辑完全混乱,最终时间变量保留初始值0。
  2. 日期字段的格式符类型与匹配逻辑隐患:
    你用%2"PRIu8"匹配060224(ddmmyy格式),虽然日期字段看似正确,但本质是后续字段匹配“巧合”对齐,连续无分隔的数字依然会导致匹配错位风险。

修复方案

推荐使用字符串截取+单独解析的方式,避免连续数字格式符的匹配混乱:

int GNSS_ParseData(char* buf, GNSS_Data* data)
{
    char utc_str[16] = {0};
    char date_str[8] = {0};
    const char* format =
        "%s %6s.%3" PRIu16 ","
        "%f,%f,%f,%f,%" PRIu8 ",%f,%f,%f,"
        "%6s,%" PRIu32 "%s";
    char front_padding[128] = {0}, back_padding[128] = {0};

    uint16_t millisecond;
    float cog, tmp;
    uint32_t nsat;

    int ret = sscanf(
        buf, format, front_padding, utc_str, &millisecond,
        &(data->coordinates.latitude),
        &(data->coordinates.longitude),
        &data->horizontal_precision, &data->altitude,
        &data->fix_type, &cog,
        &data->speed_over_ground_mps, &tmp,
        date_str, &nsat, back_padding);
    if (ret != 13)
    {
        return -1;
    }

    // 拆分UTC字符串为时分秒
    sscanf(utc_str, "%2" PRIu16 "%2" PRIu16 "%2" PRIu16,
           &data->utc_time.hour, &data->utc_time.minutes, &data->utc_time.seconds);
    // 原消息中是.0,转换为000毫秒
    data->utc_time.msec = millisecond * 100;

    // 拆分日期字符串为日月年,并转换为完整年份
    sscanf(date_str, "%2" PRIu8 "%2" PRIu8 "%2" PRIu8,
           &data->utc_time.day, &data->utc_time.month, &data->utc_time.year);
    data->utc_time.year += 2000;

    data->num_satellites = (uint8_t)nsat;
    data->speed_over_ground_mps *= (1000.0f / 3600.0f);
    // 转换航向角:ddd.mm → ddd.dd
    data->course_over_ground = (float)((int)cog) + ((cog - (int)cog) * 100 / 60);

    return 0;
}

额外优化点

  • 年份字段:原消息中的年份是两位(如24),需手动加2000转换为完整年份(2024),否则输出的日期会出现错误。
  • 航向角:直接读取浮点数后计算比手动拆分更简洁可靠。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 18:37:04