本次使用GPS模块为
9.9的GNSS模组! 大夏龙雀 DX-GP22 !!!
我勒个骚钢 整套模块 还带 数据线和USB转TTL模块 才9.9!NB!
咳咳 回归正题
先来介绍一下什么是NMEA‑0183 协议
NMEA‑0183 协议,是民用 GPS 最通用标准协议
由美国国家海洋电子协会制定,`串口 ASCII 文本输出`,逗号分隔
使用串口 就可以用单片机来读取数据
先来看一下数据组成
$GNGGA:定位核心数据(经纬度、卫星数量、海拔、定位状态)
$GNZDA:UTC 时间、年月日
$GNRMC:最简定位、速度日期
$GNGLL:经纬度 + 时间
$GNVTG:对地航向、速度
$GNGSA:当前选用的卫星编号、精度因子
对了 忘记展示这块模组的完整样貌了 OK 请看!

1 DX-GP22 - -
2 1PPS 灯 系统单脉冲输出指示灯 当系统搜星并定位成功后,1s 闪一次
3 TX 灯 数据输出指示灯 -
4 ON/OFF 模块关断控制引脚 低电平有效
5 VDD 电源 电源供电范围为:3.6~6V
6 GND 电源地 -
7 TXD 串口数据输出 -
8 RXD 串口数据输入 -
9 1PPS 系统单脉冲输出引脚当系统搜星并定位成功后,该引脚默认每秒发送一个脉冲,频率为 1Hz
10 陶瓷天线 - - 11 1.25-6P 贴片端子 连接器 可根据需求定制硬件
将单片机与GPS模块的串口连接起来 接收一下原始数据
//串口接收
uint8_t rx_buffer[RX_BUFFER_SIZE]; //数据缓冲区
printf("%s\r\n",rx_buffer); //输出接收到的原始数据
接收到的原始数据
[21:24:17.176]收←◆
$GNGGA,132417.000,3422.07633,N,11707.33659,E,1,06,2.1,-9.0,M,-6.3,M,,*75
$GNGLL,3422.07633,N,11707.33659,E,132417.000,A,A*49
$GNGSA,A,3,26,27,31,194,,,,,,,,,4.8,2.1,4.3,1*06
$GNGSA,A,3,36,39,,,,,,,,,,,4.8,2.1,4.3,4*33
$GPGSV,3,1,11,04,56,278,,07,06,305,,08,32,204,,09,29,310,,0*65
$GPGSV,3,2,11,16,67,004,,18,11,065,,26,45,050,34,27,64,173,26,0*6D
$GPGSV,3,3,11,28,15,115,,31,40,103,18,194,68,084,23,0*6C
$BDGSV,1,1,02,36,58,043,22,39,48,307,21,0*78
$GNRMC,132417.000,A,3422.07633,N,11707.33659,E,0.00,120.28,080826,,,A,V*09
$GNVTG,120.28,T,,M,0.00,N,0.00,K,A*2A
$GNZDA,132417.000,08,08,2026,00,00*4C
$GPTXT,01,01,01,ANTENNA OPEN*25
其中最重要的就是卫星时间和经纬度
进入代码逻辑部分啦啦啦啦!!!
**要串口解析数据(报文),我们肯定已经获得了完整的源数据到数组,**
**获取后就要对数据切割,所以我们先来介绍一下有关数组的相关函数**
切割的字符串函数
//strtok(字符串,分隔符);
//strtok(char *str, const char *delimiters);
//使用strtok来切割一句字符串 获取其中的数据
char buf[] = "$GNGGA,053437.000,3422.08444,N";//源数据
char *p;//指针 用来存储数据的地址
//第1次传入字符串
p = strtok(buf, ",");
printf("%s\n",p); //输出 $GNGGA
//后面全部填 NULL,接着往后切
p = strtok(NULL, ",");
printf("%s\n",p); //053437.000
**注意!!!**
strtok 会原地修改字符串,把分隔符改成 '\0'
所以原数组已经被拆开,不能二次复用。
一定要把元数据存储在另一个缓冲区内进行分割 `不要破坏原始数据啊!`
因此我们需要一个复制函数将数据搬运到新缓冲区
//内存原样复制,单纯搬运字节,不会管字符串、不会自动添加 \0
//memcpy(void *目标地址, const void *源地址, size_t 拷贝字节长度);
char src[] = "hello gps";
char dst[32];
memcpy(dst,src,9); //拷贝9个字节
dst[9] = '\0';
printf("%s",dst);
再来介绍一个同样重要的函数strstr
//strstr
//在主字符串 str里面查找子字符串 sub
//找到:返回子串起始位置的指针
//找不到:返回 NULL
//strstr(const char *str, const char *sub);
if(strstr(line_token,"$GNGGA") != NULL) //寻找字符串中是否有 $GNGGA 关键字
{
//这一行就是GGA定位报文
}
了解这些后开始来解析数据啦!
先来创建一些数据的结构体来存放我们的数据
//时间结构体 存放时间数据
typedef struct
{
uint8_t hour;
uint8_t min;
uint8_t sec;
uint8_t day;
uint8_t month;
uint16_t year;
}GPS_TimeTypeDef;
// GNGGA 定位结构体 存放卫星定位数据
typedef struct
{
char utc_time[16]; //UTC时间字符串
double latitude; //纬度
char lat_dir; //N / S
double longitude; //经度
char lon_dir; //E / W
uint8_t fix_state; //定位状态 0无定位 1单点定位
uint8_t satellite_num; //卫星数量
float hdop; //水平精度因子
float altitude; //海拔高度
}GPS_GGATypeDef;
//新建结构体变量
GPS_TimeTypeDef gps_time;
GPS_GGATypeDef gps_gps_gga;
再建一个源数据的缓冲区
uint8_t APP_rx_buffer[1024];
OK 开始写对应的逻辑
void GPS_Data_satellite(char *buf){
//将接收缓冲区复制到数据缓冲区
memcpy(APP_rx_buffer,buf,rx_len);
// 行切割指针
char *line_token;
// 句切割指针
char *token;
// 先进行切割第一行 也就是先保存第一行
line_token = strtok((char*)APP_rx_buffer,"\n");
//在while中循环查找需要的数据-句
while(line_token != NULL) //判断是否全部查找完 防止遗漏
{
// 筛选当前单行是不是GNZDA时间报文
if(strstr(line_token,"$GNZDA") != NULL)
{
token = strtok(line_token, ","); //切割该句子第一个逗号前面的数据
token = strtok(NULL, ","); //切割该句子下一个逗号(第二个)前面的数据
//该数据就是我们的时间数据 下面进行保存到结构体 供我们使用
// token = 053437
// token[0] = 0
// token[1] = 5
// token[2] = 3
// ...
// token[5] = 7
gps_time.hour = (token[0]-'0')*10 + (token[1]-'0');
gps_time.min = (token[2]-'0')*10 + (token[3]-'0');
gps_time.sec = (token[4]-'0')*10 + (token[5]-'0');
// 换算北京时间
gps_time.hour += 8;
if(gps_time.hour >= 24)
{
gps_time.hour -= 24;
}
//printf("BeiJingTime:%02d:%02d:%02d\r\n",gps_time.hour,gps_time.min,gps_time.sec);
memset(APP_rx_buffer,0,sizeof(APP_rx_buffer)); //清除缓冲区 (也可以不清除 看具体情况)
}
// 取下一行报文
line_token = strtok(NULL,"\n");
}
}
整体逻辑就是
将源数据保存到新的缓冲区
将数据每一行(句)拆开
判断哪一句是我所需要的数据
对该句中的“逗号”进行分割 存储逗号前的数据
注释写的也是非常详细了好吧 嘻嘻
其他的数据获取几乎一模一样
void GPS_Data_satellite(char *buf)
{
//printf("duandian_2");
memcpy(APP_rx_buffer,buf,rx_len);
// strstr(被查找字符串, 需要搜寻的关键词)
// strtok(字符串分割函数)
// 行切割指针
char *line_token;
// 句切割指针
char *token;
// 切割第一行
line_token = strtok((char*)APP_rx_buffer,"\n");
while(line_token != NULL)
{
// 筛选当前单行是不是GNGGA时间报文
if(strstr(line_token,"$GNGGA") != NULL)
{
token = strtok(line_token, ","); //1 $GNGGA
token = strtok(NULL, ","); //2 时间
token = strtok(NULL, ","); //3 纬度
gps_gps_gga.latitude = atof(token);
token = strtok(NULL, ","); //4 N
token = strtok(NULL, ","); //5 经度
gps_gps_gga.longitude = atof(token);
token = strtok(NULL, ","); //6 E
token = strtok(NULL, ","); //7 定位状态
token = strtok(NULL, ","); //8 卫星颗数
gps_gps_gga.satellite_num = atoi(token);
memset(APP_rx_buffer,0,sizeof(APP_rx_buffer));
}
// 取下一行报文
line_token = strtok(NULL,"\n");
}
}
OK 我们就先获取这么多的数据 在主程序中调用一下
//rx_done是串口中断标志位 有数据就执行里面的解析函数
if(rx_done==1)
{
//printf("%s\r\n",rx_buffer);
GPS_Data_time((char*)rx_buffer);
GPS_Data_satellite((char*)rx_buffer);
printf("北京时间:%02d:%02d:%02d 卫星数量:%d 经度:%.4f 维度:%.4f \r\n",
gps_time.hour,
gps_time.min,
gps_time.sec,
gps_gps_gga.satellite_num,
gps_gps_gga.latitude/100.0f,
gps_gps_gga.longitude/100.0f
);
rx_done=0;
}
看一下效果吧!我们用USART1来接收并解析数据 USART2来输出解析后的数据

完美呀!今天就先写到这里 我累啦:-( 谁可以V我50呀 QAQ
浙公网安备 33010602011771号