STM32的GPS数据提取程序设计说明——基于NMEA0183协议

article/2024/12/21 22:41:26

一、硬软件平台

本次程序实现效果为对GPS信号穿送来的数据进行筛选,并将筛选后的信息通过上位机显示出来,所以此次设计所需硬件包括STM32F407、RS232转TTL、CH340USB转串口模块,注意该模块在使用前,对应的系统需要安装驱动,否则串口调试助手无法识别,另外还包括JLink下载器。

本次代码设计软件为KEIL5并结合F4固件包,上位机系统为WIN7,主机系统为WIN10。

二、算法总体思路设计

由于GPS通过RS232将数据传送给板子,因此使用两个串口资源(串口1和串口2),其中一个用来接收数据,另外一个用来将筛选后的数据发送给上位机。GPS在发送信号时,相邻两次数据的发送之间有明显的时间间隔,所以该间隔可作为判断当前接收的数据是否是一个完整数据的依据。为了保证接收数据的及时性,接收数据时采用串口接收中断,发送数据则作为主程序。因此可得到以下主程序流程图。

三、具体实现步骤

3.1 串口1初始化

本次程序设计通过串口1将筛选后的数据发送给上位机,对应的硬件资源为PA9(USART1-TX)、PA10(USART1-RX)。通过将这两个IO口连接CH340,实现数据传送。串口1波特率设置为38400。

3.2 串口2初始化

串口2用来接收GPS信号,对应硬件资源为PA2(UASRT2-TX)、PA3(USART2-RX),GPS设备接口为RS232,接入单片机时,需要转为TTL电平,注意,在连接板子和RS232转TTL模块的TTL输出端时,RX和TX用反接,否则会接收不到数据。由于GPS信号发送波特率为固定的38400,所以初始化时串口2的波特率也要设置为38400。

串口2使用中断来接收数据,因为初始化时也要配置中断。具体代码如下:

 

void uart2_init(u32 bound){//GPIO端口设置GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART1时钟//串口1对应引脚复用映射GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2); //GPIOA2复用为USART2GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2); //GPIOA3复用为USART2//USART1端口配置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; //GPIOA9与GPIOA10GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//速度50MHzGPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10//USART1 初始化设置USART_InitStructure.USART_BaudRate = bound;//波特率设置USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式USART_Init(USART2, &USART_InitStructure); //初始化串口2USART_Cmd(USART2, ENABLE);  //使能串口2USART_ClearFlag(USART2, USART_FLAG_TC);USART_ITConfig(USART2, USART_IT_RXNE,ENABLE);//开启相关中断//Usart2 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;//串口2中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//抢占优先级1NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;		//子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器TIM7_Int_Init(99,7199);USART2_RX_STA=0;TIM_Cmd(TIM7,DISABLE);			//关闭定时器7}

 

3.3 串口2接收中断函数

该中断函数是整个程序的重点所在,主要目的是将接收的字符存入数组内,这里数组名及大小定义为USART2_RX_BUF[NMEA_COUNT_MAX];其中将NMEA_COUNT_MAX设置为600,代表数组最大容量。当有数据发过来时,存入数组,并对该数组进行处理,若没有处理完毕,则不再接收其他数据,这里定义数据接收状态变量vu16 USART2_RX_STA。另外借助10ms定时器(TIM7)中断判断是不是一次连续的数据,如果接收连续2个字符之间的时间差不大于10ms则认为是,如果大于10ms则中断触发,强制标记数据接收完成。

void USART2_IRQHandler(void)                	//串口2中断服务程序
{GPIO_ResetBits(GPIOA,GPIO_Pin_6);  //LED0灯亮  char Buffer;if(SET==USART_GetITStatus(USART2,USART_IT_RXNE)){USART_ClearFlag(USART2, USART_FLAG_RXNE);Buffer = USART_ReceiveData(USART2);//接收数据if((USART2_RX_STA&(1<<15))==0)//接收完的一批数据,还没有被处理,则不再接收其他数据{if(USART2_RX_STA<NMEA_COUNT_MAX)//还可以接收数据{TIM_SetCounter(TIM7,0);//计数器清空if(USART2_RX_STA==0){TIM_Cmd(TIM7,ENABLE);//使能定时器7}USART2_RX_BUF[USART2_RX_STA++]=Buffer;//记录接收到的值	 }else{USART2_RX_STA|=1<<15;//强制标记接收完成}}}GPIO_SetBits(GPIOA,GPIO_Pin_6);//LED0灯灭
} 

 

/*定时器7中断服务函数*/
void TIM7_IRQHandler(void)
{ 	if (TIM_GetITStatus(TIM7, TIM_IT_Update) != RESET)//是更新中断{	 			   USART2_RX_STA|=1<<15;	//标记接收完成TIM_ClearITPendingBit(TIM7, TIM_IT_Update  );  //清除TIM7更新中断标志    TIM_Cmd(TIM7, DISABLE);  //关闭TIM7 }	    
}

 

 

3.4数据解析

每次接收的数据都存放在数组里面,每个信息都有属于自己的标识符,例如$GNRMC、!AIVDM等,所以要找到目标信息的位置,直接对数组进行字符串搜索即可。搜索算法如下图所示,返回字符串首字符在数组中的位置。

 

/*查找字符串*/
u16 FindStr(char *str,char *ptr)
{u16 index=0;char *STemp=NULL;char *PTemp=NULL;char *MTemp=NULL;if(0==str||0==ptr)return 0;for(STemp=str;*STemp!='\0';STemp++)	 //依次查找字符串{index++;  //当前偏移量加1MTemp=STemp; //指向当前字符串//比较for(PTemp=ptr;*PTemp!='\0';PTemp++){	if(*PTemp!=*MTemp)break;MTemp++;}if(*PTemp=='\0')  //出现了所要查找的字符,退出break;}return index;
}

得到目标信息的位置后,就可以提取信息中的数据了,NMEA数据的特点是信息中的数据之间都是用逗号隔开,所以逗号的数量就代表了该条信息含有多少个数据,通过数逗号的方法就可以得到每个数据。

以提取!AIVDM信息为例,其他信息提取方法相同。代码中通过宏定义的方式来选择解析和发送哪条信息。

 

/*定义AIVDM数据结构体*/
typedef struct AIVDM_data
{char first[2];char second[2];char third[2];char four[5];char five[50];char six[12];}AIVDM_data;

 

/*数据解析*/
#if PRINT_AIVDM //发送数据开关u8 CommaNum_AIVDM=0;//逗号数量u8 BufIndex_AIVDM=0;char Sbuf_AIVDM;char *Pstr_AIVDM;u16 index_AIVDM=0;memset(&AIVDM_data_ais,0x00,sizeof(AIVDM_data_ais));index_AIVDM=FindStr(GPS_REC_data,"!AIVDM");if(index_AIVDM){CommaNum_AIVDM=0;Pstr_AIVDM=GPS_REC_data+index_AIVDM+6;do{Sbuf_AIVDM=*Pstr_AIVDM++ ;switch(Sbuf_AIVDM){case ',':CommaNum_AIVDM++;BufIndex_AIVDM=0;break;default:switch(CommaNum_AIVDM){case 0:AIVDM_data_ais.first[BufIndex_AIVDM]=Sbuf_AIVDM;break;//逗号数量为0表示第一个数据case 1:AIVDM_data_ais.second[BufIndex_AIVDM]=Sbuf_AIVDM;break;//逗号数量为1表示第二个数据case 2:AIVDM_data_ais.third[BufIndex_AIVDM]=Sbuf_AIVDM;break;//逗号数量为2表示第三个数据case 3:AIVDM_data_ais.four[BufIndex_AIVDM]=Sbuf_AIVDM;break;//逗号数量为3表示第四个数据case 4:AIVDM_data_ais.five[BufIndex_AIVDM]=Sbuf_AIVDM;break;//逗号数量为4表示第五个数据case 5:AIVDM_data_ais.six[BufIndex_AIVDM]=Sbuf_AIVDM;break;//逗号数量为5表示第六个数据default:break;}BufIndex_AIVDM++;break;}}while(Sbuf_AIVDM!='\r');//每条信息以'\r'字符结尾}//memset(GPS_Buffer,0,sizeof(GPS_Buffer));#endif

3.5 信息发送

信息发送就用普通的printf函数,不过要使用串口1函数,所以要重写一个fputs()函数,并在头文件中stdlib.h头文件。在发送之前,需要对数据进行一些简单的过滤,比较最后一个数据是信息的校验码,校验码的第二位一定是符号*等等,如果不符合过滤的条件就不发送,保证数据的正确性。

 

/*数据过滤及打印*/
#if PRINT_AIVDMif(AIVDM_data_ais.six[1]=='*'&&AIVDM_data_ais.five[0]!='0'&&AIVDM_data_ais.five[1]!='0'&&AIVDM_data_ais.five[2]!='0'&&AIVDM_data_ais.five[0]!='8'&&AIVDM_data_ais.five[1]!='8'&&AIVDM_data_ais.five[2]!='8'){printf("!AIVDM");printf(",");printf("%s",AIVDM_data_ais.first);printf(",");printf("%s",AIVDM_data_ais.second);printf(",");printf("%s",AIVDM_data_ais.third);printf(",");printf("%s",AIVDM_data_ais.four);printf(",");printf("%s",AIVDM_data_ais.five);printf(",");printf("%s\n",AIVDM_data_ais.six);}#endif

3.6 主函数

各个模块的功能已经实现了,接下来就可以根据图2.1编写主函数的程序,如下图所示。

 

/*主函数大循环*/
while(1){delay_ms(1);if(USART2_RX_STA&0X8000)//接收到一次数据了{rxlen=USART2_RX_STA&0X7FFF;//得到数据长度for(i=0;i<rxlen;i++)GPS_REC_data[i]=USART2_RX_BUF[i];//将缓冲数据写入数组中USART2_RX_STA=0;//启动下一次接收GPSParse();//解析字符串send_data();//发送字符串}}

 

四、遇到的问题及解决方案

4.1 数据打印前一半打印正常,后一半没有数据或者是不正确的数据

原因:在前一次数据没有解析发送完,就来了第二次数据,由于接收是采用中断方式,所以会暂停解析发送转而去接收数据,这就导致了上一次的数据被新的数据冲刷掉了,冲刷后的结果无法预测,可能没有了,可能是别的。

解决方法:参考正点原子代码。定义接收状态标志变量USART3_RX_STA,将该变量看作一个16位的寄存器,其中0-14位代表串口接收数据的长度,第15位为1时代表不接收当前数据,为0代表接受当前数据,只有当定时器中断触发时即GPS一次连续的数据已经发送完毕时,手动给该位置1,此时不再接收下一次数据,当当前数据取出后即写给另外一个数组时该位再重新置1。具体代码见图3.2和3.7。

4.2  GPS接收没数据

原因:RS232转TTL的TTL输出端与PA2,PA3反接了。

解决方法:不用反接,RX接RX,TX接TX。

五、结果

六、工程下载连接

https://download.csdn.net/download/weixin_39954922/13218297

  • 七、参考文献
  1. http://training.eeworld.com.cn/video/18411
  2. https://blog.csdn.net/jickjiang/article/details/79086202
  3. https://blog.csdn.net/qq_33559992/article/details/52051689
  4. 感谢正点原子例程~

http://chatgpt.dhexx.cn/article/3kX5M6jM.shtml

相关文章

GPS模块编程之NMEA0183协议

原文地址&#xff1a;https://blog.csdn.net/northcan/article/details/7261310 NMEA 0183是美国国家海洋电子协会&#xff08;National Marine Electronics Association&#xff09;为海用电子设备制定的标准格式。现在已经成为GPS导航设备统一的RTCM&#xff08;Radio Techni…

NMEA 0183 校验计算方法

每一行末尾的“星号”后面都有两位校验&#xff0c;是本行字符串中将“美元符号”和“星号”之间的每一个字符从左至右依次进行异或等于运算而得到的 $GPGSV,2,2,07,24,71,118,51,27,02,300,37,32,08,258,4141 $GLGSV,2,1,07,66,46,058,48,82,29,260,44,76,43,059,47,77,38,150…

获取原始NMEA 0183语句的方法

MEA0183语句是从卫星上广播的含有丰富信息的原始GPS语句&#xff0c;它的语法为海用电子设备的标准格式。它所含的信息量远远不止位置坐标&#xff0c;开发和利用这些信息可能会带来意想不到的商机。比如&#xff0c;它发出的时间信息已经被很多钟表厂商利用&#xff0c;开发出…

从NMEA0183到GNSS定位数据获取(二)软件篇

总述 GPS我们都知道&#xff0c;一种用来全球定位的系统&#xff0c;后来俄罗斯推出了格洛纳斯定位系统&#xff0c;中国推出了北斗定位&#xff0c;欧盟有伽利略&#xff0c;印度与日本也有有发展。所以后来把覆盖全球的自主地利空间定位的卫星系统成为GNSS。 现在卫星定位那么…

c++中拷贝构造函数被调用的时机

1 c中拷贝构造函数被调用的时机 拷贝构造函数被调用的几种情况&#xff1a; &#xff08;1&#xff09;当用类的一个对象去初始化该类的另一个对象时&#xff0c;系统会自动调用拷贝构造函数&#xff1b; &#xff08;2&#xff09;将一个对象作为实参传递给一个非引用类型的…

二说 拷贝构造函数 拷贝赋值函数

文章目录 什么是拷贝构造函数拷贝构造函数的调用时机2.1 当函数的参数为类的对象时2.2 函数的返回值是类的对象2.3 对象需要通过另外一个对象进行初始化 浅拷贝与深拷贝3.1 默认拷贝构造函数3.2 浅拷贝3.3 深拷贝3.4 防止默认拷贝发生 拷贝构造函数的几个细节4.1 为什么拷贝构造…

C++拷贝构造函数、构造函数和析构函数

一、拷贝构造函数 转载自&#xff1a;http://www.cnblogs.com/BlueTzar/articles/1223313.html 1、类对象的拷贝 对于普通类型的对象来说&#xff0c;它们之间的复制是很简单的&#xff0c;例如&#xff1a; int a88; int ba; 而类对象与普通对象不同&#xff0c;类对象内部…

拷贝构造函数起作用的三种情况

拷贝构造函数起作用的三种情况&#xff1a; 1.当用类的对象去初始化同类的另一个对象时。 Date d2(d1); Date d2 d1; //初始化语句&#xff0c;并非赋值语句。 2.当函数的形参是类的对象&#xff0c;调用函数进行形参和实参结合时。 void Func(A a1) //形参是类Date的对象…

【拷贝构造函数】c++类拷贝构造函数详解

【拷贝构造函数】c类拷贝构造函数详解 目录 【拷贝构造函数】c类拷贝构造函数详解一、什么是拷贝构造函数二、拷贝构造函数的几种调用时机1. 当函数的参数为类的对象时2. 函数的返回值是类的对象3. 当成员变量为类类型时4. 普通派生类构造函数的写法 三、浅拷贝与深拷贝1. 默认…

拷贝(复制)构造函数定义及3种调用情况举例

一、拷贝构造函数是一种特殊的构造函数&#xff0c;其形参为本类的对 象引用。 class 类名 { public : 类名&#xff08;形参&#xff09;&#xff1b;//构造函数 类名&#xff08;类名 &对象名&#xff09;&#xff1b;//拷贝构造函数 ... }&#xff1b; //拷贝构造函…

C++——拷贝构造函数详解

C——拷贝构造函数详解 1.拷贝构造函数的特点&#xff1a;2.通过例子引入拷贝构造&#xff1a;3构造对象的时候使用引用返回与不使用引用返回的问题&#xff1a;3.1不使用引用返回&#xff1a;3.2引用返回——从已经死亡的地址接收值不牢靠&#xff1a; 4.缺省的拷贝构造和等号…

C++ 拷贝构造函数详解

C 拷贝构造函数详解 下面的讲解将以C标准库的string类作为讲解对象&#xff0c;string类&#xff1a;class with pointer member(s) 1、拷贝构造函数和拷贝赋值函数 1.1引入 下面是给出的测试函数&#xff0c;也是我们要能在自己设计的myString类中实现的功能&#xff1a; …

详解析构函数、拷贝构造函数

目录 一.析构函数&#xff08;析构器&#xff09; &#xff08;一&#xff09;.使用方式及注意事项 1.使用方式 2.注意事项 &#xff08;二&#xff09;.默认析构函数 二.拷贝构造函数 &#xff08;一&#xff09;.使用方式及注意事项 1.使用方式 2.注意事项 &#xff0…

【深入理解C++】拷贝构造函数

文章目录 1.拷贝构造函数2.默认的拷贝操作3.默认拷贝构造函数4.何时调用拷贝构造函数 1.拷贝构造函数 拷贝构造函数是构造函数的一种。当利用已存在的对象创建一个新对象时&#xff0c;就会调用新对象的拷贝构造函数进行初始化。 拷贝构造函数的格式是固定的&#xff0c;即接…

C++拷贝构造函数详解

一. 什么是拷贝构造函数 首先对于普通类型的对象来说&#xff0c;它们之间的复制是很简单的&#xff0c;例如&#xff1a; int a 100; int b a; 而类对象与普通对象不同&#xff0c;类对象内部结构一般较为复杂&#xff0c;存在各种成员变量。 下面看一个类对象拷贝的简…

c++拷贝构造函数(深拷贝,浅拷贝)详解

一、什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=100; int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。 下面看一个类对象拷贝的简单例子。 #include<iostream> using n…

YOLO 裂缝检测

环境 python3.5 yolov3 opencv keras

基于OpenCV的混凝土裂纹检测

基于OpenCV的混凝土裂纹检测 前言 这是我发的第一次博客&#xff0c;有什么建议大家可以给我留言&#xff0c;感激不尽! 接下来&#xff0c;我们进入正题。 一、使用函数库 numpy, opencv, heapq, skimage.morphology 二、使用步骤 1.初步预处理 初步预处理包括&#xf…

【图像识别】基于计算机视觉实现路面裂缝检测识别系统matlab代码

1 简介 随着公路与铁路事业的飞速发展,各类车辆和里程的增加,铁路的一次次提速,都对路面产生了巨大的压力。不论是公路路面还是铁路路面,路面裂纹都能随处可见,由路面裂纹造成的交通事故时有发生。研究路面裂纹检测方法对于路面维护、交通安全具有极其重大意义。近年来,路面裂…

基于计算机视觉的裂纹检测方案

点击上方“小白学视觉”&#xff0c;选择加"星标"或“置顶” 重磅干货&#xff0c;第一时间送达01. 数据集 我们首先需要从互联网上获取包含墙壁裂缝的图像&#xff08;URL格式&#xff09;数据。总共包含1428张图像&#xff1a;其中一半是新的且未损坏的墙壁&#x…