linux
小编最近做了一个读取惯导的数据(每帧结构是122),然后提取其中特定的消息,在ros里以一个消息的形式发布出去的小demo。小编首先将这个任务分解成两块,先实现在linux直接.cpp就能解析的程序,然后移植到ros里。本文下面主要讲解3块内容:
1、linux里如何使用串口,读取数据;2、如何使用循环队列是需要读取的帧结构不会丢帧;3、如何使用结构体存指针。
整个代码,我会在文章最后给上我的github地址给大家参考:
一、linux下读取串口数据思路
主要就是是用open()打开文件函数,打开串口,获得句柄,譬如已可读可写方式打开插入的usb设备,ttyUSB0,代码如下:
int fd = open(\"/dev/ttyUSB0\", O_RDWR);
然后就是对这个这个串口进行初始化了,这里我也是在网上借鉴了一个简单好用的初始化,封装成了一个初始化函数:
//设置波特率,初始化串口int set_uart_baudrate(const int _fd, unsigned int baud){ int speed; switch (baud) { case 9600: speed = B9600; break; case 19200: speed = B19200; break; case 38400: speed = B38400; break; case 57600: speed = B57600; break; case 115200: speed = B115200; break; case 230400: speed = B230400; break; default: return -EINVAL; } struct termios uart_config; int termios_state; tcgetattr(_fd, &uart_config); uart_config.c_cflag |= (CLOCAL | CREAD); uart_config.c_cflag &= ~PARENB; uart_config.c_cflag &= ~CSTOPB; uart_config.c_cflag &= ~CSIZE; uart_config.c_cflag |= CS8; uart_config.c_cflag &= ~CRTSCTS; uart_config.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); uart_config.c_iflag = 0; uart_config.c_oflag = 0; uart_config.c_cc[VTIME] = 0; uart_config.c_cc[VMIN] = 1; if ((termios_state = cfsetispeed(&uart_config, speed)) < 0) { return 0; } if ((termios_state = cfsetospeed(&uart_config, speed)) < 0) { return 0; } if ((termios_state = tcsetattr(_fd, TCSANOW, &uart_config)) < 0) { return 0; } return 1;}
譬如上面我打开了ttyUSB0,设置对应的波特率,调用串口初始化这个函数,再用read()函数就能读取到串口发过来的数据了,
set_uart_baudrate(fd, 115200);int len = read(fd, buf, 1);
至此buf数组里就可以读取到串口过来的数据了,所以其实不管linux下使用串口非常简单,就是三步走战略:
1、open()函数打开串口2、串口初始化3、read()函数读取
二、构建循环队列与数据的读写
在具体构建之前,首先明白一下为什么要用循环队列。我们用read函数去读取n个字节的数据放到buf里,如果不用循环队列,那么我们直接对buf里的数据去判断帧头,校验和,那么因为我们很难保证读取出来的数据刚刚好的就是完整的一帧或几帧,那么这样就很容易造成我们直接逻辑上就存在丢帧的可能了。这里循环队列就可以很好的解决这个问题。
下面看看具体怎么构建:
typedef struct{ unsigned char Recbuf[MAXSIZE]; 缓冲数组 int tail; //尾指针(写指针) int head; //头指针(读指针)}Suqueue;
其实很简单,如何实现队列的首位可以接到一起呢,在实际的计算中是没有这样的环形内存结构的。循环队列是一种数据结构,我们通过软件的方法,来实现首尾相接。
但我们用read()函数读取到一帧数据后,就用memcpy()内存拷贝函数,将read读取的数据拷贝到缓冲数组Recbuf里,然后改变一下写指针的值即可。具体代码如下:
unsigned char buf[1];int len = read(fd, buf, 1);memcpy(queue_cycle.Recbuf + queue_cycle.tail, buf, len);queue_cycle.tail=(queue_cycle.tail+1)%MAXSIZE;
这里注意写指针的值,是采取的+1取余的方法,这样就保证了不论经过多少次循环,写指针始终在范围内。
然后读取数据也是要巧妙的结合缓冲数组的长度MAXSIZE取余的方法,如小编读取我这次的帧结构就是用,以下的代码:
for (int j = 121; j>=0; j--) { temp_buf[121-j]= queue_cycle.Recbuf[(queue_cycle.head + MAXSIZE - j+1) % MAXSIZE]; }
断断续续的看代码可能看不太明白,有需要的朋友,建议大家github上下载代码后,结合一起看,这里主要梳理一下框架。
自此循环队列也构建完成,总结起来两点需要注意:
1、构建结构体2、巧妙应用取余的方法使读写指针始终在位置内
三、用结构体存储解析帧消息
小编先举一下,我解析的帧结构是怎样的:
后面还有很多,是不是很复杂,小编一开始是读取出来的每一个数据,去根据说明书去解析,因为各种类型的都不一样,要写好多函数,真的是相当复杂。后来发现一个巧妙的方法,按照说明书定义一个一模一样的结构体,然后把我们读出来的一帧完整数据直接拷贝进去就行了。真的是方便太多了。
然后用函数将我们之前从环形数组里拷贝出来的一帧完整的数据帧,直接拷贝到我们的结构体里就行。
for (int j = 121; j>=0; j--) { temp_buf[121-j]= queue_cycle.Recbuf[(queue_cycle.head + MAXSIZE - j+1) % MAXSIZE]; } memcpy(&APM, temp_buf,122 ); //注入 结构体 /在这里访问结构体成员即可 printf(\"apm_counter:%d\\r\\n\",APM.counter);
这里要提醒大家注意的是,看到我上图数据帧结构体里的标黄的代码:
#pragma pack(1)
这句代码是将字节对齐设为1,为什么要字节对齐,这里又可以说很多,小编这篇文章就不累述了。简单来说,就是譬如你的帧结构是122字节的,各种类型都有,默认字节对齐是8,你sizeof()出来的帧结构大小往往是大于122个字节的,这就会造成你把122个字节数据拷贝到结构体里会出错。用这句代码,#pragma pack(1),相当于就是取消了自动对齐。