Быстрый парсер NMEA

GPS навигацией не занимается только ленивый. Вот и мне пришлось…
Задача — парсерить ответы от модуля. В результате получился вот такой алгоритм
Я имел доступ к большому числу разных модулей (EB500 EB800, вся линейка Ublox,Quectel L76, ML8088)
Парсер работает со всеми этими модулями без изменений
Сам парсер вызывается из прерывания по приему каждого байта,
в результате работы — необходимые данные разложены по полочка
понимает та же глонас модули.

Обрабатывать данные можно либо после последнего принятого байта ( флаг -конец приема), но это не надежно
так как модули иногда подтормаживают, и между байтами есть задержка
У меня реализован таймер на 20 милисекунд, который сбрасывается каждым приходящим байтом, если данных нет, то вырабатывается низкоприоритетное прерывание, где идет вся обработка)
этот же таймер следит за жизнью модуля. Иногда они просто виснут. при отсутсвии данных в течении 1 секунды, модуль перегружается.


char Time[12]=""; //время
char Status[2]=""; //валидность
char SLatitude[16]="";  //Латитуда
char NS[3]="";				// 
char SLongitude[12]="";		//Лонгитуда 
char EW[3]="";				// 
char CourseTrue[10]="";			// курс
char Data[12]="";				//Дата
char SatCount[4]="";                    //используемых спутников
char AltitudaMSL[12]="";            //высота
char ViewSat[4];   //
char COG[8]="";			//	
char COGstat[4]="";		//
char Speed[8]="";			//скорость
char SpeedAlt[8]="";	//
char UNUSED[32]="";			//мусорка, тут все данные, которые не нужны
char Knot[8]="";
char *const RMC[]={Time,Status,SLatitude,NS,SLongitude,EW,UNUSED,CourseTrue,Data,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED};
char *const GGA[]={UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,SatCount,UNUSED,AltitudaMSL,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED};
char *const GSV[]={UNUSED,UNUSED,ViewSat,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED,UNUSED};
char *const VTG[]={COG,COGstat, UNUSED,UNUSED,Knot,UNUSED,Speed,UNUSED,UNUSED,UNUSED};
unsigned char GLONAS_COUNT=0;
unsigned char GPS_COUNT=0;
volatile char DataDone=0;
unsigned char DataValid=0;
void Parser(unsigned char data)
{
static unsigned char ByteCount=0xff;
static unsigned int MsgType;					
static char *MsgTxt=(char*)&MsgType;	 
static  unsigned char ComaPoint=0xff;
static unsigned char CharPoint=0;
if(data=='$'){ByteCount=0;ComaPoint=0xff;MsgTxt=(char*)&MsgType; return;} //ждем начала стрки
if(ByteCount==0xff) return;									//
ByteCount++;
if(ByteCount<=1)	return;								//
if(ByteCount<6&&ByteCount>1)		//берем 4 символа заголовка
	{
	*MsgTxt=data;	//и делаем из него число			  		
	MsgTxt++;
	return;
	}  
//
switch(MsgType)
	{
	case 	0x434D5250:				//GPRMC		
	case	0x434D524E:				//GNRMC 
		if(data==',') {ComaPoint++;	CharPoint=0;RMC[ComaPoint][0]=0;return;}
		if(data=='*') {MsgType=0;return;}
	  	RMC[ComaPoint][CharPoint++]=data;
		RMC[ComaPoint][CharPoint]=0;
		return;
	case	0x41474750:				//PGGA
	case	0x4147474e:				//NGGA
		if(data==',')  {ComaPoint++;	CharPoint=0;GGA[ComaPoint][0]=0;return;}
		if(data=='*') {MsgType=0;return;}
		GGA[ComaPoint][CharPoint++]=data;
		GGA[ComaPoint][CharPoint]=0;
		return;
	case	0x47545650:		//PVTG
		if(data==',')  {ComaPoint++;	CharPoint=0;VTG[ComaPoint][0]=0;return;}
		if(data=='*') {return;}
	  	VTG[ComaPoint][CharPoint++]=data;
		VTG[ComaPoint][CharPoint]=0;
		return;
	case	0x4754564e:		//NVTG
		if(data==',')  {ComaPoint++;	CharPoint=0;VTG[ComaPoint][0]=0;return;}
		if(data=='*') {return;}
	  	VTG[ComaPoint][CharPoint++]=data;
		VTG[ComaPoint][CharPoint]=0;
		return;
	case	0x56534750:		//PGSV		
		if(data==',')  {ComaPoint++;	CharPoint=0;GSV[ComaPoint][0]=0;return;}
		if(data=='*')  {GPS_COUNT=AsciiToInt(ViewSat);MsgType=0;return;}
		GSV[ComaPoint][CharPoint++]=data;
		GSV[ComaPoint][CharPoint]=0;
		return;
	case	0x5653474c:		//LGSV		
		if(data==',')  {ComaPoint++;	CharPoint=0;GSV[ComaPoint][0]=0;return;}
		if(data=='*') {GLONAS_COUNT=AsciiToInt(ViewSat);MsgType=0;return;}
	  	GSV[ComaPoint][CharPoint++]=data;
		GSV[ComaPoint][CharPoint]=0;
		return;
	default:	ByteCount=0xff;break;
	}	
ByteCount=0xff;
}

Комментарии (11)

RSS свернуть / развернуть
char SLatitude[16]=""; //Латитуда
char SLongitude[12]=""; //Лонгитуда

Лонгтисюда, лонгтихотькуда. По-русски это широта и долгота, в школе на географии учат.
+3
Еще есть всякие Мультитраны в помощь.
0
Для МК, ИМХО, адекватнее работать с бинарными протоколами, если такая возможность есть (не уверен насчет всего многообразия GNSS-приёмников)
0
некоторые модули потдерживают подобное по SPI или I2c, там уже идет структуированные данные,
Но они всегда работают в режиме подчиненного( был проект на убоксах ) отказались, так как нужно еще и IRQ аппаратное заводить для инициации чтения
0
На u-blox сейчас у нас данные идут через уарт, но в виде пакетов ubx. Это ничем не отличается от работы с nmea, кроме начальной инициализации. Зато вытаскиваиь полезную информацию из них намного менее затратно по ресурсам.
0
автор, зачем вам RMC? распарсите gga и vtg получите ровно тоже самое или больше.

если из GSV вам надо только количество спутников по системам то логичнее парсить GSA. сейчас вы считаете все спутники которые потенциально могут быть использованы(в реальности многие не будут даже отслеживаться), а не только те что реально используются, в GSA лежат только спутки используемые в позиции + оно короче и проще.

работать с бинарными протоколами
эти протоколы разные для каждого производителя железа и, как правило, проприетарные.
+1
уже не помню, почему так, но какие то древние модули, не все отдавали. а код один на все
0
char UNUSED[32]="";                     //мусорка, тут все данные, которые не нужны


А зачем вам эта мусорка?
Если уж решили так реализовать парсинг, пишите в массивы (RMC и т д) NULL там, где нужно игнорировать данные, и добавьте проверки, чтобы обеспечить пропускание записи, если адрес массива равен NULL, типа:

if(RMC[ComaPoint]) RMC[ComaPoint][CharPoint++]=data;

и т. д.

Кстати, а вы случайно не экспериментировали с дифференциальными поправками для GPS?
0
лишние проверки, ветвления по коду. а так — все единообразно. понадобится какое то поле — просто подставлю новый масив
0
А как данный парсер будет себя вести, принимая следующие пакеты:

$GPGGA,065543.069,,,,,0,00,,,M,0.0,M,,0000*58
$GPRMC,065543.069,V,,,,,,,071213,,,N*45

Корректно будет парсить? При подаче питания (при холодном старте) у большинства приёмников идут вот такие урезанные пакеты
0
Отлично будет считать. количество запятых не меняется, а он ориентируется по ним. в переменных будут пустые строки
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.