草庐IT

STM32——定位模块ATGM336H,数据解析,提取经纬度

chfens 2023-05-01 原文

模块介绍

ATGM336H定位模块支持GPS系统,BDS(北斗)系统,GLONASS(俄罗斯)系统,伽利略卫星导航系统(欧盟)。这个模块要拿到室外才能接收到信号,且初次初始化或者隔太久时间没有启用会导致获取定位信息的时间很长。

可以使用中科微电子提供的集成软件设置模块,可以设置串口输出的参数,波特率等等参数。
本文介绍用STM32串口接收定位模块的数据,并将数据进行解析,解析后得到经纬度的原始数据,把经纬度原始数据转换成精确的经纬度信息后存放到数组里,可打印或继续串口传输到其他设备,后面会介绍传输到ESP32,并保存到SPIFFS中。
上图是模块通过串口向上位机输出的数据帧样例。

通过GNSS工具可以看到模块能够获取的数据帧有如下格式。

其中RMC数据是最简定位信息,得到这串数据后,可以将GNRMC开始的前六项数据提取,分别是UTC时间,数据有效标志位,纬度,纬度方向,经度,经度方向。此时的经度和纬度只是原始数据,需要再转换为精确数据。

RMC的数据样例为

$GNRMC,123211.000,A,2295.33602,N,11326.27041,E,3.21,217.19,100722,,,A*7A

程序思路

数据转换:
2322.74250 格式:ddmm.mmmmm
11326.27041 格式:ddmm.mmmmm
转换成北纬:23 + 22.74250 / 60 = 23.37904
转换成东经:113 + 26.27041 / 60 = 113.43784
比较不准。。串口输出的数据就是这样的,估计芯片哪里出了问题。。
读取数据可以先用一个缓存数组接收所有的数据,先判断是不是$GNRMC开头的数据帧,如果是的话则逐个字符接收,因为数据帧是以换行符为结尾的,当读取到’\n’则是数据帧的结尾。
定义几个字符数组,用于分别存放UTC时间,经度和纬度的原始数组,经度和纬度的方向。
以逗号为分隔符切割字符串,这里我们需要用到strstr这个函数。

返回指向str1中第一次出现str2的位置的指针,当str1中没有str2则返回空指针。我们可以定义一个子串指针,用这个函数在数据帧中找逗号,再用memcpy函数把数据分割后存在相应的数组。

#include "ATGM336H.h"	
#include "string.h"

#define GPS_Buffer_Length 80
#define UTCTime_Length 11
#define latitude_Length 11
#define N_S_Length 2
#define longitude_Length 12
#define E_W_Length 2 
char USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
typedef struct SaveData 
{
	char GPS_Buffer[GPS_Buffer_Length];
	
	char isGetData;		//数据获取完成标志位
	char isParseData;	//解析完成标志位
	
	char UTCTime[UTCTime_Length];		//UTC时间
	char latitude[latitude_Length];		//纬度
	char N_S[N_S_Length];		//N/S
	char longitude[longitude_Length];	//经度
	char E_W[E_W_Length];		//E/W
	
	char isUsefull;		//信息有效标志位
}GNRMC;

首先定义一个结构体存放几个数组,并宏定义数组的长度。

void ATGM_StructInit()
{
	GNRMC_Info.isGetData = false;
	GNRMC_Info.isParseData = false;
	GNRMC_Info.isUsefull = false;
	memset(GNRMC_Info.GPS_Buffer, 0, GPS_Buffer_Length);    
	memset(GNRMC_Info.UTCTime, 0, UTCTime_Length);
	memset(GNRMC_Info.latitude, 0, latitude_Length);
	memset(GNRMC_Info.N_S, 0, N_S_Length);
	memset(GNRMC_Info.longitude, 0, longitude_Length);
	memset(GNRMC_Info.E_W, 0, E_W_Length);
}

首先初始化结构体,先把结构体成员内容全部置零。

void ATGM336H_Init()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	
	ATGM_StructInit();
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	
     //USART1_TX   PA.9
	
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; 
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	
    GPIO_Init(GPIOA, &GPIO_InitStructure);
   
    //USART1_RX	  PA.10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);  

	//配置中断通道
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ;//优先级最低
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			
	NVIC_Init(&NVIC_InitStructure);	
  
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	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(USART1, &USART_InitStructure); 
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    USART_Cmd(USART1, ENABLE);                 

	ClrBuf();	//初始化
}

之后配置引脚,配置串口1中断,配置串口。引脚TX我选择PA9,RX选择PA10。TX要选择复用推挽输出,RX选择浮空输入。之后开通NVIC中断通道,配置串口波特率为9600,8位数据位,1位停止位,吴娇艳,无硬件流控制,模式为收发模式,因为我既要接收定位数据,也要打印或发送数据。最后别忘记使能中断和串口。

unsigned int DataIndex = 0;
void USART1_IRQHandler()                	
{
	unsigned char receive;
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == 1) 
	{
		receive = USART_ReceiveData(USART1);		//读取接收到的数据
	
		if(receive == '$')
			DataIndex = 0;	
			
		USART_RX_BUF[DataIndex++] = receive;

		//GNRMC\GPRMC
		if(USART_RX_BUF[0] == '$' && USART_RX_BUF[4] == 'M' && USART_RX_BUF[5] == 'C')	
		{
			if(receive == '\n')									   
			{
				memset(GNRMC_Info.GPS_Buffer, 0, GPS_Buffer_Length);      //清空
				memcpy(GNRMC_Info.GPS_Buffer, USART_RX_BUF, DataIndex); 	//保存数据
				GNRMC_Info.isGetData = true;
				DataIndex = 0;
				memset(USART_RX_BUF, 0, USART_REC_LEN);      //清空				
			}					
		}	 
   } 
}

先是定义一个DataIndex作为数组的下标,当读到了$标志表示数据帧开始,将下标置零,之后判断是不是我们要读取的这种格式,如果是则读到’\n’,即把数据转而存储到GPS_Buffer中,并把数据读取标志位置为true。

void ParseGps()
{
	char *subString;
	char *subStringNext;
	char i = 0;
	if (GNRMC_Info.isGetData)
	{
		GNRMC_Info.isGetData = false;
//		printf("\r\n");
//		printf(GNRMC_Info.GPS_Buffer);

		//截取数据帧前六部分    							 |对地航速 对地航向  日期
		//$GNRMC,112536.000,A,2322.75023,N,11326.28605,E,|  0.00,   0.00,  100722,,,A*78
		for (i = 0 ; i <= 6 ; i++)
		{
			if (i == 0)
			{
				if ((subString = strstr(GNRMC_Info.GPS_Buffer, ",")) == NULL)//如果没有找到逗号
				{
					return;
					//ERROR
				}
				
			}
			else
			{
				subString++;
				if ((subStringNext = strstr(subString, ",")) != NULL)
				{
					char usefulBuffer[2]; 
					switch(i)
					{
						case 1:memcpy(GNRMC_Info.UTCTime, subString, subStringNext - subString);break;	
						case 2:
						{
							memcpy(usefulBuffer, subString, subStringNext - subString);//有效标志位
							if(usefulBuffer[0] == 'A')
								GNRMC_Info.isUsefull = true;
							else if(usefulBuffer[0] == 'V')
								GNRMC_Info.isUsefull = false;	
							break;
						}	
						case 3:memcpy(GNRMC_Info.latitude, subString, subStringNext - subString);break;	
						case 4:memcpy(GNRMC_Info.N_S, subString, subStringNext - subString);break;	
						case 5:memcpy(GNRMC_Info.longitude, subString, subStringNext - subString);break;	
						case 6:memcpy(GNRMC_Info.E_W, subString, subStringNext - subString);break;	
						default:break;
					}
					subString = subStringNext;					
				}
			}
		}
		GNRMC_Info.isParseData = true;	
	}
}

这里进行六次循环,把数据帧分成六部分,用strstr函数找到从子串next指针开始的下一个逗号的位置,并把这个这个位置到子串指针之间的数据分别存到各自的数组中去。六次循环之后便结束,因为后面的数据是对地航速,对地航向和日期,我没有这个需求,有需要的同学可以自己修改。

extern float Lat;
extern float Lon;
extern char dest[23];
void printGpsBuffer()
{
	//$GNRMC,123211.000,A,2322.74250,N,11326.27041,E,3.21,217.19,100722,,,A*7A
	if (GNRMC_Info.isParseData)
	{
		int i = 0;
		GNRMC_Info.isParseData = false;		
		if(GNRMC_Info.isUsefull)
		{
			float tmp = 0;		int j = 0;	
			GNRMC_Info.isUsefull = false;
			for (i = 0; GNRMC_Info.latitude[i] != '\0'; i++)
			{

				if (GNRMC_Info.latitude[i] == '.')
				{
					continue;
				}
				if (i <= 1)
				{
					Lat = (GNRMC_Info.latitude[0] - 48) * 10 + (GNRMC_Info.latitude[1] - 48);
					//取出个位和十位
				}
				else
				{
					tmp += (GNRMC_Info.latitude[i] - 48);
					tmp *= 10;
				}
			}
			for (j = 0; j <= 5; j++)
			{
				tmp /= 10;
			}
			Lat += tmp / 60;
			//23 22.74250
			//23.xxxxx
			int iLat = 0;			
			iLat = (int)Lat;
			GNRMC_Info.latitude[0] = iLat / 10 + '0';
			GNRMC_Info.latitude[1] = iLat % 10 + '0';
			GNRMC_Info.latitude[2] = '.';
			Lat -= iLat;
			for (j = 3; j < 10; j++)
			{
				Lat *= 10;
				iLat = (int)Lat;
				GNRMC_Info.latitude[j] = iLat + '0';
				Lat -= iLat;
			}							
			tmp = 0;
			//113.27041
			for (i = 0; GNRMC_Info.longitude[i] != '\0'; i++)
			{

				if (GNRMC_Info.longitude[i] == '.')
				{
					continue;
				}
				if (i <= 2)
				{
					Lon = (((GNRMC_Info.longitude[0] - 48) * 10 + (GNRMC_Info.longitude[1] - 48)) * 10) + (GNRMC_Info.longitude[2] - 48);
					//取出个位和十位和百位
				}
				else
				{
					tmp += (GNRMC_Info.longitude[i] - 48);
					tmp *= 10;
				}
			}
			for (j = 0; j <= 5; j++)
			{
				tmp /= 10;
			}
			int iLon = 0;
			//113.43784
			Lon += tmp / 60;
			iLon = (int)Lon;
			GNRMC_Info.longitude[0] = iLon / 100 + '0';
			GNRMC_Info.longitude[1] = (iLon % 100) / 10 + '0';
			GNRMC_Info.longitude[2] = iLon % 10 + '0';
			GNRMC_Info.longitude[3] = '.';
			Lon -= iLon;
			for (j = 4; j < 11; j++)
			{
				Lon *= 10;
				iLon = (int)Lon;
				GNRMC_Info.longitude[j] = iLon + '0';
				Lon -= iLon;
			}
			
			dest[8] = dest[10] = dest[20] = ',';
			dest[9] = 'N'; dest[21] = 'E'; dest[22] = '\0';
			for(i = 0; i < 22; i++)
			{
				if(i <= 7)
					dest[i] = GNRMC_Info.latitude[i];
				if(i >= 11 && i <= 19)
					dest[i] = GNRMC_Info.longitude[i - 11];
			}
			
			//printf("\r\ndest = ");
			printf(dest);
			//printf("\r\n");
		}
		else
		{
			printf("GPS DATA Is Not Useful!");
		}	
	}
}

最后就是数据的转换,要把经纬度数组中的字符提取出来组合成数字。对于纬度,先提取数组前两位的数据作为个位和十位,从第三位开始后面的数据提取出来组成一个浮点数,之后把这个浮点数除以60,最后加上个位和十位的整数,便得到了转换后的纬度。经度同理,区别在于经度有百位。
这里只是简单的没有啥依据的不合理的提取。
提取得到两个浮点数后,把经纬度方向一起存储在dest数组中,组合成完整的包括经纬度方向的数据。

结语

数据解析的步骤很繁琐,且模块输出的原始数据不是很准确。后面再试试。

有关STM32——定位模块ATGM336H,数据解析,提取经纬度的更多相关文章

  1. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  2. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  3. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  4. ruby - 当使用::指定模块时,为什么 Ruby 不在更高范围内查找类? - 2

    我刚刚被困在这个问题上一段时间了。以这个基地为例:moduleTopclassTestendmoduleFooendend稍后,我可以通过这样做在Foo中定义扩展Test的类:moduleTopmoduleFooclassSomeTest但是,如果我尝试通过使用::指定模块来最小化缩进:moduleTop::FooclassFailure这失败了:NameError:uninitializedconstantTop::Foo::Test这是一个错误,还是仅仅是Ruby解析变量名的方式的逻辑结果? 最佳答案 Isthisabug,or

  5. ruby - 获取模块中定义的所有常量的值 - 2

    我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c

  6. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  7. ruby-on-rails - 使用 config.threadsafe 时从 lib/加载模块/类的正确方法是什么!选项? - 2

    我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co

  8. 「Python|Selenium|场景案例」如何定位iframe中的元素? - 2

    本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决

  9. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  10. ruby-on-rails - Controller 中的 Rails 辅助模块 - 2

    我有一个Controller,我想为这个Controller创建一个助手,我可以在不包含它的情况下使用它。我尝试像这样创建一个与Controller同名的助手classCars::EnginesController我创建的助手是moduleCars::EnginesHelperdefcheck_fuellogger.debug("chekingfuel")endend我得到的错误是undefinedlocalvariableormethod`check_fuel'for#有没有我遗漏的约定? 最佳答案 如果你真的想在Controll

随机推荐