我正在解析GNSS数据,我使用的模块QuecTel BG96具有UART串口.当试图提取与消息相关联的数据时,除了时间之外,大部分数据都可以使用sscanf来提取.

下面是一个示例消息,与下面代码中使用的相同:

"\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"

为了完整起见,以下是对这些字段的解释,如手册所示:

+QGPSLOC:<UTC>,<latitude>,<longitude>,<hdop>,<altitude>,<fix>,<cog>,<spkm>,<spkn>,<date>,<nsat> OK
  • UTC是UTC时间. 格式:hhmmss.sss(引自GPGGA句子).

问题是,第一组数字表示时间为hhmmss.sss,提取时始终为0.

Expected output:

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

My code:

#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()的格式字符串中有两种类型的错误:

  1. 你用PRN...代替SCN....前者是为printf()家庭准备的,您需要使用后者.不同之处见下文.
  2. 格式说明符的使用宽度与变量的宽度不匹配.

因此,正确的格式是:

    const char* format =
        "%s %2" SCNu16 "%2" SCNu16 "%2" SCNu16 ".%3" SCNu16
        ",%f,%f,%f,%f,%" SCNu8 ",%3" SCNu16 ".%2" SCNu16
        ",%f,%f,%2" SCNu16 "%2" SCNu16 "%2" SCNu16 ",%" SCNu8
        "%s";

100


现在,为什么你对一些时间变量取零?

为此,我们假设您正在当前系统上使用标准编译器,因此我在Windows 10上使用GCC进行调查.

下面的小测试程序显示了格式常量之间的差异:

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

int main(void) {
  printf("PRIu16 = \"%s\"\n", PRIu16);
  printf("SCNu16 = \"%s\"\n", SCNu16);
  uint16_t canary1 = 0x1122, value, canary2 = 0x5566;
  // 0x3344 = 13124
  printf("sscanf() = %d\n", sscanf("13124", "%" PRIu16, &value));
  printf("canary1 @%p = %04X\n", (void*)&canary1, canary1);
  printf("value   @%p = %04X\n", (void*)&value, value);
  printf("canary2 @%p = %04X\n", (void*)&canary2, canary2);
  return 0;
}

带有一组通用警告标志的编译已经告诉我们有些地方不对劲:

> gcc -Wall -pedantic formats.c -o formats.exe
formats.c: In function 'main':
formats.c:9:45: warning: format '%u' expects argument of type 'unsigned int *', but argument 3 has type 'uint16_t *' {aka 'short unsigned int *'} [-Wformat=]
   printf("sscanf() = %d\n", sscanf("13124", "%" PRIu16, &value));
                                             ^~~         ~~~~~~
In file included from formats.c:1:
C:/Program Files/mingw-w64/x86_64-8.1.0-posix-seh-rt_v6-rev0/mingw64/x86_64-w64-mingw32/include/inttypes.h:92:17: note: format string is defined here
 #define PRIu16 "u"
formats.c:9:45: warning: format '%u' expects argument of type 'unsigned int *', but argument 3 has type 'uint16_t *' {aka 'short unsigned int *'} [-Wformat=]
   printf("sscanf() = %d\n", sscanf("13124", "%" PRIu16, &value));
                                             ^~~         ~~~~~~
In file included from formats.c:1:
C:/Program Files/mingw-w64/x86_64-8.1.0-posix-seh-rt_v6-rev0/mingw64/x86_64-w64-mingw32/include/inttypes.h:92:17: note: format string is defined here
 #define PRIu16 "u"

结果重现了你的观察,canary1是零:

> formats.exe
PRIu16 = "u"
SCNu16 = "hu"
sscanf() = 1
canary1 @000000000061FE1E = 0000
value   @000000000061FE1C = 3344
canary2 @000000000061FE1A = 5566

我们看到PRIu16实际上是"u"sscanf()用它来写unsigned int.在我们的系统中,它通常与uint32_t具有相同的宽度.所以sscanf()很乐意写4个字节,canary1.

哪个变量会被覆盖取决于变量在内存中的顺序以及值的存储顺序.这些顺序/顺序都不是由标准定义的,所以我在两边都使用了两个金丝雀.

这解释了你观察到的零.


但是,当SCNu16unsigned short int相关联时,为什么PRIu16unsigned int相关联?

秘诀在于变量函数上的integer promotion个整型参数.而printf()就是这样的变分函数.

如果您将值uint16_t赋给printf(),它将被转换为int.由于int通常足以容纳uint16_t的所有可能值,因此转换结果不是unsigned int.但是,这两种类型的位模式在值范围uint16_t内是相同的.

所以printf() needs "u"才能正确打印这样的值.

另一方面,scanf()不能超出所提供变量的空格.所以知道宽度是needs,这就是"hu".

C++相关问答推荐

理解C中的指针定义

从C函数调用asm函数时生成错误的BLX指令(STM32H753上的gcc)

如何将字符串argv[]赋给C中的整型数组?

如何将字符串传递给函数并返回在C中更改的相同字符串?

使用scanf在C中读取和存储文件中的值

Flose()在Docker容器中抛出段错误

如何确保在C程序中将包含uft8字符的字符串正确写入MySQL?

如何按顺序将所有CSV文件数据读入 struct 数组?

一旦运行长度超过2,编译器是否会优化";strnlen(mystring,32)>;2";以停止循环?

如何在C中使数组变量的值为常量?

不同出处的指针可以相等吗?

为什么未初始化的 struct 的数组从另一个数组获取值?

处理来自浏览器的HTTP请求

不同原型的危险C函数是可能的

链表删除 node 错误

链接器脚本和C程序使用相同的头文件,这可能吗?

传递参数:C 和 C++ 中 array 与 *&array 和 &array[0] 的区别

如何正确探测平台设备?

C23 中的 [[reproducible]] 和 [[unsequenced]] 属性是什么?什么时候应该使用它们?

使用 SDL2 的 C 程序中的内存泄漏