瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。
【公众号】迅为电子
【粉丝群】258811263(加群获取驱动文档+例程)
【视频观看】嵌入式学习之Linux驱动(第十七篇 串口_全新升级)_基于RK3568
【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板
第203章 GPS模块编程
203.1 GPS模块简介
全球卫星导航系统是一种利用卫星技术为全球用户提供精确时间和位置信息的系统。目前全球主要有两大卫星系统:美国的GPS(全球定位系统)和俄罗斯的GLONASS(全球导航卫星系统)。除此之外,欧盟的伽利略卫星导航系统、中国的北斗卫星导航系统和印度的纳维克卫星导航系统也在逐步建设和发展中。定位模块往往和CPU通过串口相连,然后CPU通过wifi蓝牙等其他方式将坐标进行上传。
本次实验使用的定位模块是迅为的GPS模块。如下图所示:
GPS模块的天线如下图所示:
203.2 GPS模块硬件连接
GPS模块接口原理图如下图所示:
其中BUF_GPS_RST接口悬空不接,以下为20pin底座连接到RK3568开发板GPIO接口连接表如下所示:
GPS模块引脚编号 | GPS模块引脚名称 | 连接到的开发板的引脚编号 | 连接到的开发板的引脚名称 |
2 | BUF_GPS_TXD | 8 | UART9_RX_M1 |
3 | BUF_GPS_RXD | 6 | UART9_TX_M1 |
11 | GND | 19/20 | GND |
19 | VDD33_A31 | 2/4 | VCC3V3_SYS |
注意,GPS模块连接好之后,天线要放到户外!
203.2 GPS数据帧介绍
连接好GPS模块后,我们能够编写GPS应用程序以获取位置信息,或者使用microcom直接获取。输入以下命令:
microcom -s 9600 /dev/ttyS9
我们来分析一下输出的数据信息,
这里列举的是 GPS NMEA 格式中常见的数据类型,其中包括:
- GPRMC (Recommended Minimum Specific GPS/Transit data):GPRMC 数据类型提供了位置、速度、航向等最基本的GPS定位信息。这个数据类型常用于导航系统和船舶自动导航系统。
- GPVTG (Track Made Good and Ground Speed):GPVTG 数据类型提供了航向角和地面速度信息,用于显示船、车等运动物体的导航信息。 它提供了地面航向角和相对地面的速度。
- GPGGA (Global Positioning System Fix Data):GPGGA 数据类型包括了定位解、时间、位置准确度、海拔等位置信息,通常被用于接收机实时显示当前位置。
- GPGSA (GPS DOP and Active Satellites):GPGSA 数据类型包括了DOP值和当前卫星定位状态。它用于提供GPS卫星测量数据中可用卫星数量、解算出的位置几何因子DOP等信息。
- GPGSV (GPS Satellites in View):GPGSV 数据类型提供了现时可视卫星的信息,包括卫星的PRN、仰角、方位角、信号强度等信息。这些信息有助于接收器找到更多卫星并提高位置定位的准确性。
- GPGLL (Geographic Position - Latitude/Longitude):GPGLL 数据类型提供了经度和纬度信息,用于描述接收机的当前经纬度位置。
我们只需要关注GPRMC这条信息即可,如下图所示:
GPRMC(Recommended Minimum Specific GNSS Data)是一种常见的GPS数据帧格式,用于在GPS设备之间或者GPS设备与其他设备之间传输GPS位置信息。以下是GPRMC数据帧中包含的信息:
字段 | 含义 | 详细解释 |
字段0:$GPRMC | 消息ID | 是一种GPS数据帧格式 |
字段1: 083634.00 | UTC时间 | 格式为hhmmss.sss,表示数据记录时的UTC时间(格林威治时间),格林威治时间换成北京时间需要叫8小时。 |
字段2:A | 状态指示 | 表示数据的有效性,通常是'A'表示有效,'V'表示无效 |
字段3: 3854.62194 | 纬度 | 格式为ddmm.mmmm,表示纬度信息。 |
字段4:N | 纬度半球 | 表示纬度的半球,可能是北半球(N)或南半球(S) |
字段5:11526.10876 | 经度 | 格式为dddmm.mmmm,表示经度信息 |
字段6:E | 经度半球 | 表示经度的半球,可能是东经(E)或西经(W) |
字段7: 0.932 | 地面速率(节) | 表示设备当前的地面速率,以节为单位 |
字段8:55.00 | 地面航向 | 表示设备当前的地面航向,以真北为参考,以度为单位 |
字段9:200624 | UTC日期 | 格式为ddmmyy,表示UTC日期。 |
字段10: 空 | 磁偏角 | 可选项,表示磁北与真北之间的偏角。 |
字段11:空 | 磁偏角方向 | 可选项,表示磁偏角的方向,可能是东(E)或西(W) |
字段12:空 | 模式指示 | 表示定位模式,通常是'A'表示自主定位,'D'表示差分定位。 |
字段13:D*50 | 校验值 |
这些信息一起构成了GPRMC数据帧,用于描述GPS设备的位置、速度和时间信息。
203.3 GPS应用编程
当我们已经理解了GPS的基础知识,现在让我们进一步探讨如何将这些概念应用到实际的GPS应用编程中去。编写好的测试程序存放位置为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\124_gps
首先新建下图中的文件,如下所示:
uart.c文件的内容为126_uart_app中编写的set_uart函数,uart.c文件内容如下所示:
#include <stdio.h>
#include <termios.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
/* 设置串口参数的函数 */
int set_uart(int fd, int speed, int bits, char check, int stop) {
struct termios newtio, oldtio;
// 步骤一:保存原来的串口配置
if(tcgetattr(fd, &oldtio) != 0) {
printf("tcgetattr oldtio error\n");
return -1;
}
bzero(&newtio, sizeof(newtio));
// 步骤二:设置控制模式标志
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
// 步骤三:设置数据位
switch(bits) {
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
// 步骤四:设置奇偶校验位
switch(check) {
case 'O': // 偶校验位
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E': // 奇校验位
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'N': // 无校验
newtio.c_cflag &= ~PARENB;
break;
}
// 步骤五:设置波特率
switch(speed) {
case 9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
}
// 步骤六:设置停止位
switch(stop) {
case 1:
newtio.c_cflag &= ~CSTOPB; // 1位停止位
break;
case 2:
newtio.c_cflag |= CSTOPB; // 2位停止位
break;
}
// 步骤七:刷新输入队列
tcflush(fd, TCIFLUSH);
// 步骤八:设置配置立刻生效
if (tcsetattr(fd, TCSANOW, &newtio) != 0) {
printf("tcsetattr newtio error\n");
return -2;
}
return 0;
}
新建gps.c,内容如下所示:
#include <stdio.h>
#include <termios.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "gps.h"
// 函数:get_gps_data
// 描述:从数据缓冲区解析GPS数据,并填充到结构体中
// 参数:
// - buff: 指向包含GPS数据的缓冲区的指针
// - gps_data: 指向结构体 gprmc_data 的指针,用于存储解析后的GPS数据
// 返回值:无
void get_gps_data(char *buff, struct gprmc_data *gps_data) {
char *p = NULL;
// 在缓冲区中查找以 "$GPRMC" 开头的位置
p = strstr(buff, "$GPRMC");
// 使用 sscanf 函数从字符串 p 开始按指定格式解析数据,并存储到 gps_data 结构体中对应的成员变量中
sscanf(p, "$GPRMC,%d.00,%c,%f,%c,%f,%c,%c,%f, ,%d,,,%c,%*c",
&(gps_data->time), // UTC时间(hhmmss)
&(gps_data->state), // 状态(A=有效,V=无效)
&(gps_data->latitude), // 纬度(ddmm.mmmm格式)
&(gps_data->NS), // 纬度半球(N=北半球,S=南半球)
&(gps_data->longitude), // 经度(dddmm.mmmm格式)
&(gps_data->EW), // 经度半球(E=东经,W=西经)
&(gps_data->speed), // 地面速率(节)
&(gps_data->date), // UTC日期(ddmmyy)
&(gps_data->mode)); // 模式指示(A=自主定位,D=差分定位)
// 打印解析后的部分 GPS 数据,用于验证解析是否正确(可选)
printf("state:%c, %c:%f, %c:%f\n", gps_data->state, gps_data->NS,
gps_data->latitude, gps_data->EW, gps_data->longitude);
}
新建gps.h文件,文件内容如下所示:
#ifndef __GPS_H__
#define __GPS_H__
// 定义结构体 gprmc_data,用于存储解析后的 GPS 数据
struct gprmc_data {
char id; // 数据标识(未使用)
int time; // UTC 时间(hhmmss.sss 格式)
char state; // 状态指示(A=有效,V=无效)
float latitude; // 纬度(ddmm.mmmm 格式)
char NS; // 纬度半球(N=北半球,S=南半球)
float longitude; // 经度(dddmm.mmmm 格式)
char EW; // 经度半球(E=东经,W=西经)
float speed; // 地面速率(节)
int date; // UTC 日期(ddmmyy 格式)
char mode; // 模式指示(A=自主定位,D=差分定位)
char check; // 校验位(未使用)
};
// 声明函数原型:设置串口参数
extern int set_uart(int fd, int speed, int bits, char check, int stop);
// 声明函数原型:解析 GPS 数据
extern void get_gps_data(char *buff, struct gprmc_data *gps_data);
#endif
新建main.c函数,内容如下所示:
#include <stdio.h>
#include <termios.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "gps.h"
int main() {
int fd; // 文件描述符
char buf[128]; // 读取数据的缓冲区
int count; // 读取数据的字节数
struct gprmc_data data; // 存储解析后的GPS数据的结构体
// 步骤一:打开串口设备
fd = open("/dev/ttyS9", O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0) {
printf("open error \n"); // 打开失败提示
return -1; // 返回错误码
}
// 步骤二:设置串口参数
set_uart(fd, 9600, 8, 'N', 1);
sleep(2); // 等待设备初始化
// 步骤三:循环读取GPS数据
while (1) {
count = read(fd, buf, sizeof(buf)); // 从串口读取数据
memset(&data, 0, sizeof(data)); // 清空存储数据的结构体
get_gps_data(buf, &data); // 解析GPS数据
sleep(2); // 等待下一次读取
}
// 步骤四:关闭串口设备
close(fd);
return 0;
}
203.4 运行测试
203.4.1 编译应用程序
首先进行应用程序的编译,因为测试APP是要在开发板上运行的,所以需要aarch64-linux-gnu-gcc来编译。我们先来编写Makefile文件,Makefile文件内容如下所示:
# 定义交叉编译器路径
CC=/home/topeet/Linux/linux_sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc
# 生成可执行文件 app
app: gps.o uart.o main.o
$(CC) -o app gps.o uart.o main.o
# 编译 gps 模块
gps.o: gps.c gps.h
$(CC) -c gps.c
# 编译 uart 模块
uart.o: uart.c
$(CC) -c uart.c
# 编译主程序入口 main 模块
main.o: uart.o gps.o gps.h
$(CC) -c gps.c uart.c main.c
# 清除编译生成的目标文件和可执行文件
clean:
rm -f app *.o
然后输入make命令编译应用程序,如下图所示:
编译完成的可执行程序如下图所示:
然后将编译完成的可执行程序app拷贝到开发板上。
203.4.2 运行测试
将GPS模块连接到开发板背面的串口9上,连接方法请看本章GPS模块硬件连接小节。
然后将上个小节编译完成的可执行程序app拷贝到开发板的根目录下,如下图所示:
输入以下命令运行应用程序
./app
如上图所示,打印GPS模块获取的位置信息,接收到的纬度为 N3854.616211,经度为 E11526.110352。实际的纬度和经度应该缩小 100 倍,纬度为 N38.54616211,经度为 E115.26110352。我们可以通过网址“https://www.toolnb.com/tools/getbaidupoint.html”查询下自己的实际位置,实际经纬度和我们所测经纬度偏差不大。
至此GPS模块实验就完成了。