gpt4 book ai didi

来自linux串口的C Gps nmea解析器不解析读取缓冲区的最后一行

转载 作者:太空宇宙 更新时间:2023-11-04 10:11:49 25 4
gpt4 key购买 nike

我需要为我正在开发的板(Cubietruck with armbian debian jessie 8.0)创建一个 c gps nmea 解析器。根据我在互联网上找到的几个例子,我得出以下结论:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>

int fd = -1;
int end_of_loop= 0;

void sig_handler(int sig)
{
if(sig == SIGINT)
{
printf("GPS parsing stopped by SIGINT\n");
end_of_loop = 1;
close(fd);
}
}



int main(int argc, char *argv[])
{
struct termios newt;
char *nmea_line;
char *parser;
double latitude;
float longitude;
float altitude;

signal(SIGINT, sig_handler);

fd = open("/dev/ttyACM2", O_RDWR | O_NONBLOCK);
if (fd >= 0)
{
tcgetattr(fd, &newt);
newt.c_iflag &= ~IGNBRK;
newt.c_iflag &= ~(IXON | IXOFF | IXANY);
newt.c_oflag = 0;

newt.c_cflag |= (CLOCAL | CREAD);
newt.c_cflag |= CS8;
newt.c_cflag &= ~(PARENB | PARODD);
newt.c_cflag &= ~CSTOPB;

newt.c_lflag = 0;

newt.c_cc[VMIN] = 0;
newt.c_cc[VTIME] = 0;
tcsetattr(fd, TCSANOW, &newt);



usleep(100000);

while(end_of_loop == 0)
{

char read_buffer[1000];
read(fd, &read_buffer,1000);
//printf("|%s|", r_buf);

nmea_line = strtok(read_buffer, "\n");

while (nmea_line != NULL)
{

parser = strstr(nmea_line, "$GPRMC");
if (parser != NULL)
{
printf("|%s| \n", nmea_line);
char *token = strtok(nmea_line, ",");
int index = 0;
while (token != NULL)
{
if (index == 3)
{
latitude = atof(token);
printf("found latitude: %s %f\n", token, latitude);
}
if (index == 5)
{
longitude = atof(token);
printf("found longitude: %s %f\n", token, longitude);
}
token = strtok(NULL, ",");
index++;
}
}

parser = strstr(nmea_line, "$GPGGA");
if (parser != NULL)
{
printf("|%s| \n", nmea_line);
char *token = strtok(nmea_line, ",");
int index = 0;
while (token != NULL)
{
if (index == 13)
{
altitude = atof(token);
printf("found altitude: %s %f\n", token, altitude);
}
token = strtok(NULL, ",");
index++;
}

}



printf("|%s| \n", nmea_line);
nmea_line = strtok(NULL, "\n");
}

usleep(500000);

}

close(fd);

return 0;

}
else
{
printf("Port cannot be opened");
return -1;
}
}

暂时我测试了没有 GPS 定位的负面情况。这种情况下串行端口的输出是每次读取:

$GNGNS,,,,,,NNN,,,,,,*1D
$GPVTG,,T,,M,,N,,K,N*2C
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GNGSA,A,1,,,,,,,,,,,,,,,*00
$GPGGA,,,,,,0,,,,,,,,*66
$GPRMC,,V,,,,,,,,,,N*53

当我运行代码时,我得到了 GPGGA 的解析打印输出,但不是 GPRMC 的打印输出:

GNGNS,,,,,,NNN,,,,,,*1D
| GPVTG,,T,,M,,N,,K,N*2C
| GPGSA,A,1,,,,,,,,,,,,,,,*1E
| GNGSA,A,1,,,,,,,,,,,,,,,*00
| GPGGA,,,,,,0,,,,,,,,*66
|$GPGGA|
| GNGNS,,,,,,NNN,,,,,,*1D
| GNGNS,,,,,,NNN,,,,,,*1D
| GPVTG,,T,,M,,N,,K,N*2C
| GPGSA,A,1,,,,,,,,,,,,,,,*1E
| GNGSA,A,1,,,,,,,,,,,,,,,*00
| GPGGA,,,,,,0,,,,,,,,*66
|$GPGGA|

我假设这与 GPRMC 位于最后一行这一事实有关,当 nmea_line = strtok(NULL, "\n"); 被执行时,nmea_lime 变为 NULL。我使用 strcat 在 read_buffer 上添加了一个虚拟行,但没有成功。
我打印了索引,我发现对于 GPGGA,只有 index = 3 可以实现。我增加了 sleep 时间,但没有任何变化。有谁知道我可以做些什么来实现正确的解析?

最佳答案

您的解析思路似乎不错,但实现起来有一些问题。

我多年前写了一个 gps nmea 解析器,据我所知,以 "\r\n" 结尾的行,似乎也是这种情况,因为对于这一行

printf("|%s| \n", nmea_line);

你得到

| GPVTG,,T,,M,,N,,K,N*2C

如果你把它改成

printf(">%s| \n", nmea_line);

你很可能会看到

< GNGNS,,,,,,NNN,,,,,,*1D

第二个问题是您正在以一种可重入的方式使用 strtok。在在循环的开始,您执行 nmea_line = strtok(read_buffer, "\n");。然后你去解析该行并进入一个新的循环。然后你做东西线char *token = strtok(nmea_line, ","); 通过这样做 strtok 忘记了有关第一次通话的信息​​。

在所有操作结束时再次nmea_line = strtok(NULL, "\n");,但是这个NULL适用于哪个strtok?根据输出你永远不会知道,但它肯定不会与 nmea_line = strtok(read_buffer, "\n"); 匹配。

幸运的是 strtok 有一个可重入版本:strtok_r

man strtok

#include <string.h>

char *strtok(char *str, const char *delim);

char *strtok_r(char *str, const char *delim, char **saveptr);

DESCRIPTION

The strtok() function breaks a string into a sequence of zero or more nonempty tokens. On the first call to strtok(), the string to be parsed should be specified in str. In each subsequent call that should parse the same string, str must be NULL.

[...]

The strtok_r() function is a reentrant version strtok(). The saveptr argument is a pointer to a char* variable that is used internally by strtok_r() in order to maintain context between successive calls that parse the same string.

例子:

char line[] = "a:b:c,d:e:f,x:y:z";

char *s1, *s2, *token1, *token2, *in1, *in2;

in1 = line;

while(token1 = strtok_r(in1, ",", &s1))
{
in1 = NULL; // for subsequent calls

in2 = token1;

printf("First block: %s\n", token1);

while(token2 = strtok_r(in2, ":", &s2))
{
in2 = NULL; // for subsequent calls

printf(" val: %s\n", token2);
}
}

输出:

First block: a:b:c
val: a
val: b
val: c
First block: d:e:f
val: d
val: e
val: f
First block: x:y:z
val: x
val: y
val: z

我看到的另一个问题是:

while(...)
{
read(fd, &read_buffer,1000);

nmea_line = strtok(read_buffer, "\n");
}

read 函数与fgets 不同,它不读取字符串,而是读取字节。那意味着 read 不关心它在读什么。如果顺序恰好是与 ASCII 表的值匹配的值序列,它不会添加'\0' - 读取缓冲区中的终止字节。这是一个问题,因为你正在使用需要有效字符串的函数。如果读取输入不包含换行符,strtok 将继续读取,直到找到 '\0' 并且如果该字节不存在,它将读取超出限制。这是未定义的行为。

这样做的第二个问题是 read 再次不关心你准备好的字节,你不是在读行,你准备好了 1000 字节可能包含也可能不包含字符串的内存块。很有可能该 block 不包含字符串,因为 /dev/ttyACM2 将生成一个无尽的流,永远不会向用户发送 '\0'

我会使用 fgets 获取一行并解析它,然后再获取另一行,然后很快。因为您只有文件描述符,所以您应该使用:

fdopen

#include <stdio.h>

FILE *fdopen(int fd, const char *mode);

The fdopen() function associates a stream with the existing file descriptor, fd. The mode of the stream (one of the values "r", "r+", "w", "w+", "a", "a+") must be compatible with the mode of the file descriptor. The file position indicator of the new stream is set to that belonging to fd, and the error and end-of-file indicators are cleared. Modes "w" or "w+" do not cause truncation of the file. The file descriptor is not dup'ed, and will be closed when the stream created by fdopen() is closed. The result of applying fdopen() to a shared memory object is undefined.

所以我会这样做:

FILE *f = fopen(fd, "r");


// the gps nmea lines are never that long
char line[64];

char *t1_save;

while(fgets(line, sizeof line, f))
{
// get rid of \r\n
line[strcspn(line, "\r\n")] = 0;

parser = strstr(line, "$GPRMC");
if(parser)
{
// do the parsing
}

...
}

在这个版本中你甚至不需要 strtok_r 因为你不需要嵌套 strtok 调用。


编辑

我之前错过了一件事:

int end_of_loop= 0;

void sig_handler(int sig)
{
if(sig == SIGINT)
{
printf("GPS parsing stopped by SIGINT\n");
end_of_loop = 1;
close(fd);
}
}

int main(int argc, char *argv[])
{
...
while(end_of_loop == 0)
{
}
}

根据你的编译器的优化,你最终会陷入无穷无尽的循环,即使在按下 Ctrl+C 之后。编译器可能将 while 循环优化为 while(1),因为在 mainend_of_loop变量永远不会改变,因此没有必要总是检查该值。

当试图用捕获信号停止循环时,最好至少声明变量为 volatile,这样编译器就不会优化该变量离开。大多数情况下(参见 12)最好的方法是:

volatile sig_atomic_t end_of_loop= 0;

void sig_handler(int sig)
{
if(sig == SIGINT)
{
printf("GPS parsing stopped by SIGINT\n");
end_of_loop = 1;
close(fd);
}
}

关于来自linux串口的C Gps nmea解析器不解析读取缓冲区的最后一行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48608805/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com