BA4RDK

欢迎来到我的博客

博客园 首页 新随笔 联系 订阅 管理

本次使用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 请看!
image

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来输出解析后的数据
image

完美呀!今天就先写到这里 我累啦:-( 谁可以V我50呀 QAQ

posted on 2026-08-08 22:22  BA4RDK  阅读(4)  评论(0)    收藏  举报