为何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时间字段无法正确匹配:
- 连续数字格式符的匹配逻辑错误:
你用%2" PRIu16 "%2" PRIu16 "%2" PRIu16 ".%3" PRIu16匹配hhmmss.sss格式,但sscanf的数字格式符会自动跳过前导空白,且连续数字会被当作一个整体解析,而非按指定宽度拆分。比如010842.0会被第一个格式符读取01,但后续格式符会继续读取剩余的0842,导致匹配逻辑完全混乱,最终时间变量保留初始值0。 - 日期字段的格式符类型与匹配逻辑隐患:
你用%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
相关产品推荐
相关产品推荐

