概述
最近在做Linux下的串口接收并处理GPS数据,那对于是新手的我来说,就将这个项目分成两步,第一,接收数据,第二,处理数据。本文注重第二步的代码实现,第一步只会简单提及。本文使用的嵌入式开发板龙芯智龙1C,GPS模块采用的是正点原子的ATK-S1216F8-BD模块。
第一,接收数据:在配置好开发板的串口及相关硬件后,使用cat /dev/ttyS1能将串口接收到的GPS数据打印在控制台上。模块连接图如下:
若控制台上能够显示GPS数据,则开始第二步。
第二,处理数据:
(1).串口操作:Linux下编程的过程有些固定,很多都是比如打开、配置、关闭等等
串口通信流程:打开串口ttySn—>初始化串口—>读写(read、write)—>关闭串口。
以下是usart.h代码实现:
#ifndef __USART_H
#define __USART_H
#include<stdio.h>
/*标准输入输出定义*/
#include<stdlib.h>
/*标准函数库定义*/
#include<unistd.h>
/*Unix 标准函数定义*/
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
/*文件控制定义*/
#include<termios.h>
/*PPSIX 终端控制定义*/
#include<errno.h>
/*错误号定义*/
#include<string.h>
#define SERIAL1 "/dev/ttyS1"
#define SERIAL3 "/dev/ttyS3"
int UART_Open(int fd, char *port);
void UART_Close(int fd);
int UART_Set(int fd, int speed, int flow_ctrl, int databits, int stopbits, char parity);
int UART_Init(int fd, int speed, int flow_ctrl, int databits, int stopbits, char parity) ;
int UART_Recv(int fd, char *rcv_buf, int data_len);
int UART_Send(int fd, char *send_buf, int data_len);
#endif
usart.c代码实现:
#include "usart.h"
//fd:串口文件描述符
port:串口号(/SERIAL1/SERIAL3)
int UART_Open(int fd, char *port)
{
fd = open(port, O_RDWR|O_NOCTTY|O_NDELAY);
if(fd < 0)
{
perror("Can't Open Serial Port:");
return -1;
}
//恢复串口为阻塞状态
if(fcntl(fd, F_SETFL, 0) < 0)
{
printf("fcntl failed!n");
return -1;
}
else
{
printf("fcntl=%dn",fcntl(fd, F_SETFL,0));
}
//测试是否为终端设备
if(0 == isatty(STDIN_FILENO))
{
printf("standard input is not a terminal devicen");
return -1;
}
else
{
printf("isatty success!n");
}
printf("fd->open=%dn",fd);
return fd;
}
void UART_Close(int fd)
{
close(fd);
}
/*******************************************************************
*名称:
UART_Set
*功能:
设置串口数据位,停止位和效验位
*入口参数:
fd
串口文件描述符
*
speed
串口速度
*
flow_ctrl
数据流控制
*
databits
数据位
取值为7或者8
*
stopbits
停止位
取值为1或者2
*
parity
效验类型 取值为N,E,O,S
*******************************************************************/
int UART_Set(int fd, int speed, int flow_ctrl, int databits, int stopbits, char parity)
{
int
i;
int
status;
int
speed_arr[] = { B115200, B19200, B9600, B4800, B2400, B1200, B300};
int
name_arr[] = {115200,
19200,
9600,
4800,
2400,
1200,
300};
struct termios options;
/*tcgetattr(fd,&options)得到与fd指向对象的相关参数,并将它们保存于options,该函数还可以测试配置是否正确,
该串口是否可用等。若调用成功,函数返回值为0,若调用失败,函数返回值为1.
*/
if(tcgetattr(fd,&options) != 0)
{
perror("SetupSerial 1");
return -1;
}
//设置串口输入波特率和输出波特率
for ( i= 0;
i < sizeof(speed_arr) / sizeof(int);
i++)
{
if
(speed == name_arr[i])
{
cfsetispeed(&options, speed_arr[i]);
cfsetospeed(&options, speed_arr[i]);
}
}
//修改控制模式,保证程序不会占用串口
options.c_cflag |= CLOCAL;
//修改控制模式,使得能够从串口中读取输入数据
options.c_cflag |= CREAD;
//设置数据流控制
switch(flow_ctrl)
{
case 0 ://不使用流控制
options.c_cflag &= ~CRTSCTS;
break;
case 1 ://使用硬件流控制
options.c_cflag |= CRTSCTS;
break;
case 2 ://使用软件流控制
options.c_cflag |= IXON | IXOFF | IXANY;
break;
}
//设置数据位
//屏蔽其他标志位
options.c_cflag &= ~CSIZE;
switch (databits)
{
case 5:
options.c_cflag |= CS5;
break;
case 6:
options.c_cflag |= CS6;
break;
case 7:
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag |= CS8;
break;
default:
fprintf(stderr,"Unsupported data sizen");
return -1;
}
//设置校验位
switch (parity)
{
case 'n':
case 'N': //无奇偶校验位。
options.c_cflag &= ~PARENB;
options.c_iflag &= ~INPCK;
break;
case 'o':
case 'O'://设置为奇校验
options.c_cflag |= (PARODD | PARENB);
options.c_iflag |= INPCK;
break;
case 'e':
case 'E'://设置为偶校验
options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
options.c_iflag |= INPCK;
break;
case 's':
case 'S': //设置为空格
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
break;
default:
fprintf(stderr,"Unsupported parityn");
return -1;
}
// 设置停止位
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB; break;
case 2:
options.c_cflag |= CSTOPB; break;
default:
fprintf(stderr,"Unsupported stop bitsn");
return -1;
}
//修改输出模式,原始数据输出
options.c_oflag &= ~OPOST;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
//options.c_lflag &= ~(ISIG | ICANON);
//设置等待时间和最小接收字符
options.c_cc[VTIME] = 1; /* 读取一个字符等待1*(1/10)s */
options.c_cc[VMIN] = 1; /* 读取字符的最少个数为1 */
//如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读
tcflush(fd,TCIFLUSH);
//激活配置 (将修改后的termios数据设置到串口中)
if (tcsetattr(fd,TCSANOW,&options) != 0)
{
perror("com set error!n");
return -1;
}
return 0;
}
/*******************************************************************
*名称:
UART_Init
*功能:
串口初始化
*入口参数:
fd
串口文件描述符
*
speed
串口速度
*
flow_ctrl
数据流控制
*
databits
数据位
取值为7或者8
*
stopbits
停止位
取值为1或者2
*
parity
效验类型 取值为N,E,O,S
*******************************************************************/
int UART_Init(int fd, int speed, int flow_ctrl, int databits, int stopbits, char parity)
{
if(UART_Set(fd, 115200, 0, 8, 1, 'N') == -1)
return -1;
else
return 0;
}
/*******************************************************************
* 名称:
UART0_Recv
* 功能:
接收串口数据
* 入口参数:
fd
文件描述符
*
rcv_buf
接收串口中数据存入rcv_buf缓冲区中
*
data_len
一帧数据的长度
*******************************************************************/
int UART_Recv(int fd, char *rcv_buf, int data_len)
{
int len, fs_sel;
fd_set fs_read;
struct timeval time;
FD_ZERO(&fs_read);
FD_SET(fd, &fs_read);
time.tv_sec = 10;
time.tv_usec = 0;
//使用select实现串口的多路通信
fs_sel = select(fd+1, &fs_read, NULL, NULL, &time);
printf("fs_sel = %dn",fs_sel);
if(fs_sel)
{
len = read(fd, rcv_buf, data_len);
return len;
}
else
return -1;
}
/********************************************************************
* 名称:
UART0_Send
* 功能:
发送数据
* 入口参数:
fd
文件描述符
*
send_buf
存放串口发送数据
*
data_len
一帧数据的个数
*******************************************************************/
int UART_Send(int fd, char *send_buf, int data_len)
{
int len = 0;
len = write(fd, send_buf, data_len);
if (len == data_len )
{
printf("send data is %sn", send_buf);
return len;
}
else
{
tcflush(fd, TCOFLUSH);
return -1;
}
}
其中串口设置其实就相当于串口通信的协议,
波特率:是为了两者信号流能同步,
数据位:是指又几位数据封装成一帧
结束位:是指以帧传输数据时,协定好结束位,便于提取有效数据
奇偶校验:检验数据的一种手段。
(2)GPS处理数据:
在实现处理GPS数据之前,我们先要对模块进行设置:
以上信息,一般GPS模块都会默认输出,也有的模块只输出其中几个。
我此处所使用到的仅是GNRMC,解析数据的代码实现也仅针对于GNRMC命令,详情可以参看该文档。
文档下载链接:https://download.csdn.net/download/qq_43367031/11537856
设置GPS模块输出信息,我们需要用到上位机GNSS_Viewer。
该上位机下载链接:https://download.csdn.net/download/qq_43367031/11537962
有时候,我们并不需要这么多信息,比如我们只需要 GNRMC 的信息就够了。这里,我
们将教您如何设置模块,使得模块只输出 GNRMC 定位信息。
在 GNSS_Viewer 窗口,选择 Binary->Configure NMEA Interval, 窗口下我们把不需要
输出的消息的 Interval 值设置为 0, 只是 RMC Interval 设置为 1~255, Attributes 为 Update to SRAM+FLASH, 如图 2.2.3.2.2 所示:
如上图的设置,我们将 NMEA 协议的 GNRMC 之外的输出都关闭了,设置好之后,点
击 Set 按钮,这样模块就不会再输出其他信息了。最后,模块就只会输出 GNRMC 帧了,如图 2.2.3.2.3 所示:
以及测量频率的设置,一定要设置正确,我这里设置的模块测量频率是1hz,因此在代码里的main.c里采用sleep(1),这里一定要注意,若设置有问题,则会导致GPS数据解析错误。
至此,模块设置完毕。下面开始贴代码。
gps.h代码实现:
#ifndef __GPS_H__
#define __GPS_H__
#include "usart.h"
#define GPS_LEN 512
typedef unsigned int u32;
typedef unsigned short u16;
typedef unsigned char u8;
typedef struct __gnrmc__
{
u32 time;
char pos_state;
float latitude;
float longitude;
float speed;
float direction;
u32 date;
float declination;
char dd;
char mode;
}GNRMC;
int gps_analysis(char *buff, GNRMC *gps_date);
int print_gps(GNRMC *gps_date);
#endif
gps.c代码实现:
#include "gps.h"
int gps_analyse (char *buff,GNRMC *gps_data)
{
char *ptr = NULL;
if(gps_data == NULL)
{
return -1;
}
if(strlen(buff)<10)
{
return -1;
}
//如果buff字符串中包含字符"$GNRMC"则将$GNRMC的地址赋值给ptr
if(NULL==(ptr=strstr(buff,"$GNRMC")))
{
return -1;
}
sscanf(ptr,"$GNRMC,%d.000,%c,%f,N,%f,E,%f,%f,%d,,,%c*",&(gps_data->time),&(gps_data->pos_state),&(gps_data->latitude),&(gps_data->longitude),&(gps_data->speed),&(gps_data->direction),&(gps_data->date),&(gps_data->mode));
//sscanf函数为从字符串输入,上面这句话的意思是将ptr内存单元的值作为输入分别输入到后面的结构体成员
return 0;
}
int print_gps (GNRMC *gps_data)
{
printf("===========================================================n");
printf("==
GPS状态位 : %c
[A:有效状态 V:无效状态]
n",gps_data->pos_state);
printf("==
GPS模式位 : %c
[A:自主定位 D:差分定位]
n", gps_data->mode);
printf("==
日期 : 20%02d-%02d-%02d
n",gps_data->date%100,(gps_data->date%10000)/100,gps_data->date/10000);
printf("==
时间 : %02d:%02d:%02d
n",(gps_data->time/10000+8)%24,(gps_data->time%10000)/100,gps_data->time%100);
printf("==
纬度 : 北纬:%d度%d分%d秒
n", ((int)gps_data->latitude) / 100, (int)(gps_data->latitude - ((int)gps_data->latitude / 100 * 100)), (int)(((gps_data->latitude - ((int)gps_data->latitude / 100 * 100)) - ((int)gps_data->latitude - ((int)gps_data->latitude / 100 * 100))) * 60.0));
printf("==
经度 : 东经:%d度%d分%d秒
n", ((int)gps_data->longitude) / 100, (int)(gps_data->longitude - ((int)gps_data->longitude / 100 * 100)), (int)(((gps_data->longitude - ((int)gps_data->longitude / 100 * 100)) - ((int)gps_data->longitude - ((int)gps_data->longitude / 100 * 100))) * 60.0));
printf("==
速度 : %.3f
m/s
n",gps_data->speed);
printf("============================================================n");
return 0;
}
main.c代码实现:
#include "usart.h"
#include "gps.h"
int main(void)
{
u32 fd = 0;
//文件描述符,先定义一个与程序无关的值,防止fd为任意值导致程序出bug
u32 nread = 0;
GNRMC gnrmc;
u8 gps_buff[GPS_LEN];
fd = open(SERIAL1, O_RDWR|O_NOCTTY|O_NDELAY);
if(fd<0)
{
perror("open serial1 failed");
return -1;
}
UART_Set(fd, 9600, 0, 8, 1, 'N');
while(1)
{
sleep(1);
//此处数据一定要根据GPS模块测试频率设置正确,否则数据采样会出现错误
nread = read(fd, gps_buff, sizeof(gps_buff));
if(nread<0)
{
perror("read GPS date error");
return -2;
}
printf("gps_buff: %sn", gps_buff);
memset(&gnrmc, 0 , sizeof(gnrmc));
gps_analyse(gps_buff,&gnrmc);
print_gps(&gnrmc);
}
close(fd);
return 0;
}
在ubuntu下的目录如下:
make后未报错:
将gps_test传给开发板后,在控制台运行显示:
至此,大功告成。
附:
文档下载链接:https://download.csdn.net/download/qq_43367031/11537856
上位机下载链接:https://download.csdn.net/download/qq_43367031/11537962
代码工程下载链接:https://download.csdn.net/download/qq_43367031/11537897
最后
以上就是英勇指甲油为你收集整理的Linux下实现串口接收GPS数据的全部内容,希望文章能够帮你解决Linux下实现串口接收GPS数据所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复