Decoding NMEA data from a GPS module with a microcontroller

I found one of those widely distributed GPS receiver modules in my collection of electronic material. I had bought it some years ago for a school project I did with students to launch a balloon into the stratosphere (Link to text in German). Here a video of the flight phase when maximum altitude was reached:

The GPS module that was used then has still been sleeping in my junk box. When looking through the C code I had written then I found that this could be optimized. I connected the GPS receiver to an AVR ATmega32 microcontroller, added a simple 2-lines-16-characters lcd and started rewriting the software for communicating with the module.

The Hardware

OK, an ATmgea32 is somehow “overkill” for this simple circuit, but I had already installed it on my breadboard. The data lines of the ATmega32 controller that are in use for this project are pointed out here:

ATMega32_GPS-Connections

The GPS module is connected to the controller via the serial RS232 interface (TxD, RxD, PIN 15 and 14 of the micro). Note that RX of the GPS module must be connected to TxD with the controller and vice versa.

The rest of the layout is also very simple.

Microcontroller interface connections for this project

The lcd is driven in 4-bit-mode, thus only four display data lines are needed. Plus the 2 controls RS and E:

ATMega32 => LCD

  • PD4 => LCD PIN D4
  • PD5 => LCD PIN D5
  • PD6 => LCD PIN D6
  • PD7 => LCD PIN D7
  • PC2 => LCD RS
  • PC3 => LCD E

ATMega32 => GPS Module

  • PD0 (RxD) => GPS TX
  • PD1 (TxD) => GPS RX

Decoding GPS data

GPS data from the module comes coded as a so-called “NMEA”-string. “NMEA” stands for the American National Marine Electronics Association . The data is encrypted using a standardized ASCII format that appears in various patterns and is transferred by the GPS module regularly in very short intervals (about 1 second or less). The communication settings for the RS232 interface are fixed and can not be altered:

9600 baud, 8 data bits, 1 stop bit, no parity

The ATMega’s UART (universal asynchronous receiver and transmitter)  must be set with an initialization routine to the required parameters:

//Init UART
void uart_init()
{
    /* 9600 Baud */
    UBRRL = 51; UBRRH = 0;

    /* RX and TX on */
    UCSRB = (1<<RXEN)|(1<<TXEN);

    /* 8 databits, 1 stopbit, No parity */
    UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);
}

NMEA strings (example)

There are various types of data strings transferred by the module, each serving an individual purpose. They are all coded with a leading identifier marked by a $-sign and an identifier code. We will discuss the absolute basic string that delivers essential GPS information, the so-called “GPRMC”-string, where the letters “RMC” represent “recommended minimum sentence”.

This sequence contains all the necessary data for a basic GPS operation and therefore is ideal for experiments with the GPS module. Example for such a string:

$GPRMC,191410,A,4735.5634,N,00739.3538,E,0.0,0.0,181102,0.4,E*19

The various data fields are separated by a comma, please note that the data fields may have various width so just counting bytes is not reliable if you want to “land” in a specific field. Instead the comma separators must be counted.

What do the separate data fields mean?

The first entry (“191410”) after the “$GPRMC” intro is the UTC time stamp, telling when the recent GPS data set was processed, i. e. the current time in HHMMSS- format. There might be even 10th of seconds then the format is HHMMSSss.

Next is a letter (“A” for correct data, “V” (void) if the receiver was not able to get correct data), that indicates if the data is valid and can be used or not. You can use that to discard the data set when the receiver is not able to detect a valid GPS signal.

Following a number (here “4735.5634”) together with the subsequent letter (N or S) indicates the latitude of the current position. This is a 100fold decimal value formatted  by a decimal separator. Latitude can be in the range from 0 to 90 degrees North or South. Here it is about 47 degrees in the northern hemisphere.

Subsequently the same coding is used for transferring longitude data. Longitude is in the range from 0 to 180 degrees East or West with the Prime Meridian in Greenwich as separator.

Next two values (0.0 and 0.0) indicate ground speed in knots and bearing in degrees.

“181102” is the date stamp (18th Nov 2002).

“0.4” and “E” indicate a magnetic declination (degrees and direction) that might occur.

“19” is the  XOR generated checksum of the string. I found that data transfer is very reliable so I don’t calculate this.

Calculating QTH locator

For a radio amateur it is clear that GPS data must be converted to the Maidenhead system to determine the current QTH locator (JN49AB in my case). Thus I have programmed a short function that can convert decimal data for latitude and longitude to the Maidenhead system:

//Calc QTH locator from decimal value for latitude and
//longitude. o1 and o2 are signifier for 'E or 'W' (longitude) resp.
//'N' or 'S' for latitude
//example for calling: calc_maidenhead(8.002, 'E', 49.1002, 'N', buf);
void calc_maidenhead(double longitude, char o1, double latitude, char o2, char *buffer)
{

    //Longitude
    int deg_lo, lo1, lo2, lo3;
    double deg10_lo;

    //Lattitude
    int deg_la, la1, la2, la3;
    double deg10_la;

    deg_lo = longitude; //Truncate values for longitude and latitude
    deg_la = latitude;

    deg10_lo = (longitude - deg_lo) * 12; //Calculate fraction behind decimal separator
    deg10_la = (latitude - deg_la) * 24;

    //Longitude
    if(o1 == 'E')
    {
        lo1 = 180 + deg_lo;
    }
    else
    {
        lo1 = 180 - deg_lo;
    }
    lo2 = lo1 / 20;
    lo3 = lo1 - lo2 * 20;

    //Lattitude
    if(o2 == 'N')
    {
        la1 = 90 + deg_la;
    }
    else
    {
        la1 = 90 - deg_la;
    }

    la2 = la1 / 10;
    la3 = la1 - la2 * 10;

    *(buffer + 0) = lo2 + 65;
    *(buffer + 1) = la2 + 65;
    *(buffer + 2) = lo3 / 2 + '0';
    *(buffer + 3) = la3 + '0';
    *(buffer + 4) = (int) deg10_lo + 'A';
    *(buffer + 5) = (int) deg10_la + 'A';
}

Parameters handed over to the function are

  • Longitude in decimals (double),
  • ‘E’ or ‘W’ for orientation,
  • Latitude in decimals (double),
  • ‘N’ or ‘S’ for orientation,
  • a 6 byte buffer (+ 1 byte for “\0” termination) referenced by address for the locator data.

The program rolls through the data permanently giving relevant information on the display with changes every second. Due to the fact that I haven’t implemented error handling there might be more or less useles indications when data from the GPS receiver is corrupted or invalid. But mostly the routine works out very fine without any complaint. You can use the ‘A’ or ‘V’ indicator transferred to change the display routine if unvalid data is detected.

By the end of this article you can get the full  C-code for the project. Thanks for reading!

73 de Peter (DK7IH)

/*****************************************************************/
/*                    GPS with ATMega32                          */
/*  ************************************************************ */
/*  Mikrocontroller:  ATMEL AVR ATmega32, 8 MHz                  */
/*                                                               */
/*  Compiler:         GCC (GNU AVR C-Compiler)                   */
/*  Autor:            Peter Rachow  2018                         */
/*  Fuses: -U lfuse:w:0xe4:m -U hfuse:w:0xd9:m                   */
/*****************************************************************/
// This code reads data from a GPS module via RS232 interface and
// decodes the basic NMEA string of $GPRMC type, the so-called
// "RMC" (Recommended Minimum Specific GNSS Data) string.
// Displayed are Date, Time, QTH-Locator (Maidenhead), Lattitude and
// Longitude and Ground speed
/*   PORTS */
// O U T P U T
// LCD
// RS      = PC2
// E       = PC3
// D4...D7 = PD4..PD7

// PD0  TX at GPS module
// PD1  RX at GPS module

#define F_CPU 8000000

#include
#include
#include
#include 

#include
#include
#include
#include
#include 

int main(void);

/***************/
/* LCD-Display */
/***************/
#define LCD_INST 0x00
#define LCD_DATA 0x01

void lcd_write(char, unsigned char);
void lcd_write(char, unsigned char);
void set_rs(char);
void set_e(char);
void lcd_init(void);
void lcd_cls(void);
void lcd_line_cls(int);
void lcd_putchar(int, int, unsigned char);
void lcd_putstring(int, int, char*);
void lcd_putnumber(int, int, long, int);
void lcd_display_test(void);
void setcustomcharacters(void);

/*******************/
/* Stringhandling */
/*******************/
int strlen(char *s);
int instring(char*, char*);
int strcomp(char*, char*);
void int2asc(long, int, char*, int);

/*******************/
/*   U  A  R  T    */
/*******************/
void uart_init(void);
void uart_putc(char);
void uart_send_string(char*);
void init_rx_buffer(void);
char make_crc(int, int);

/**********************/
/* V A R I A B L E S  */
/**********************/
//USART defines &amp; variables
#define RX_BUF_SIZE 64
char rx_buf[RX_BUF_SIZE];
int rx_buf_cnt = 0;

/*****************************/
/* Result string formatting  */
/*****************************/
void get_time(char*, char*);
int get_receiver_status(char*);
void get_latitude(char*, char*);
void get_longitude(char*, char*);
void get_latitude_ns(char*, char*);
void get_longitude_ew(char*, char*);
double get_gps_coordinate_decimal(char*, int, char*);
void get_ground_speed(char*, char*);
void get_ground_speed(char*, char*);
void get_date(char*, char*);
void calc_maidenhead(double, char, double, char, char*);

/**************************************/
/*            L  C  D                 */
/**************************************/
/* Send one byte to LCD */
void lcd_write(char lcdmode, unsigned char value)
{
    int x = 16, t1;

    set_e(0); 

    if(!lcdmode)
	{
        set_rs(0);
	}
    else
	{
        set_rs(1);
	}	

    _delay_ms(1);

    set_e(1);

    /* Hi */
	for(t1 = 0; t1 &lt; 4; t1++)
	{
	    if(value &amp; x)
	    {
	       PORTD |= x;
	    }
        else
	    {
           PORTD &amp;= ~(x);
	    }  

		x *= 2;
	}
	set_e(0);

	x = 16;

	set_e(1);
	/* Lo */
	for(t1 = 0; t1 &lt; 4; t1++)
	{
	    if((value &amp; 0x0F) * 16 &amp; x)
	    {
	       PORTD |= x;
	    }
        else
	    {
           PORTD &amp;= ~(x);
	    }  

		x *= 2;
	}

    set_e(0);
}

/* RS */
void set_rs(char status)
{
    if(status)
	{
        PORTC |= (1 &lt;&lt; PC2);
	}
    else
	{
	    PORTC &amp;= ~(1 &lt;&lt; PC2);
	}
}

/* E */
void set_e(char status)
{
    if(status)
	{
        PORTC |= (1 &lt;&lt; PC3);
	}
    else
	{
	    PORTC &amp;= ~(1 &lt;&lt; PC3);
	}
}

void lcd_putchar(int row, int col, unsigned char ch)
{
    lcd_write(LCD_INST, col + 128 + row * 0x40);
    lcd_write(LCD_DATA, ch);
}

void lcd_putstring(int row, int col, char *s)
{
    unsigned char t1;

    for(t1 = col; *(s); t1++)
	{
        lcd_putchar(row, t1, *(s++));
	}
}

void lcd_putnumber(int y, int x, long number, int dec)
{
	char *buf;

	buf = malloc(32);
	int2asc(number, dec, buf, 31);
	lcd_putstring(y, x, buf);
	free(buf);
}	

void lcd_cls(void)
{
    lcd_write(LCD_INST, 1);
}

void lcd_init(void)
{

    lcd_write(LCD_INST, 40);

    //Matrix 5*7
    lcd_write(LCD_INST, 8);

    /* Display on, Cursor off, Blink off */
    /* Entrymode !cursoincrease + !displayshifted */
    lcd_write(LCD_INST, 12);

	//4-Bit-Mode
    lcd_write(LCD_INST, 2);	

	lcd_cls();
}

void lcd_line_cls(int ln)
{
    int t1;

	for(t1 = 0; t1 &lt; 15; t1++)
	{
	    lcd_putchar(1, t1, 32);
	}
}	

//Define own chars
void setcustomcharacters(void)
{
    int i1;
    unsigned char adr=64;

    unsigned char customchar[]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,     // \0
		                         0x04, 0x0A, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00};    //°-sign
    lcd_write(LCD_INST, 0);
    lcd_write(LCD_DATA, 0);

    //Send data to CGRAM in lcd
    for (i1 = 0; i1 &lt; 16; i1++)
    {
        lcd_write(LCD_INST, adr++);
        lcd_write(LCD_DATA, customchar[i1]);
    }
}

/***********************/
//
// STRING-FUNCTIONS
//
/**********************/
//Convert int number to string
void int2asc(long num, int dec, char *buf, int buflen)
{
    int i, c, xp = 0, neg = 0;
    long n, dd = 1E09;

    //Write 0 to buffer in case value == 0
    if(!num)
    {
		buf[0] = &#039;0&#039;;
		buf[1] = 0;
		return;
	}	

    if(num &lt; 0)
    {
     	neg = 1;
	    n = num * -1;
    }
    else
    {
	    n = num;
    }

    //Fill buffer with \0
    for(i = 0; i &lt; 12; i++)
    {
	    *(buf + i) = 0;
    }

    c = 9; //Max. number of displayable digits
    while(dd)
    {
	    i = n / dd;
	    n = n - i * dd;

	    *(buf + 9 - c + xp) = i + 48;
	    dd /= 10;
	    if(c == dec &amp;&amp; dec)
	    {
	        *(buf + 9 - c + ++xp) = &#039;,&#039;;
	    }
	    c--;
    }

    //Search for 1st char different from &#039;0&#039;
    i = 0;
    while(*(buf + i) == 48)
    {
	    *(buf + i++) = 32;
    }

    //Add minus-sign if neccessary
    if(neg)
    {
	    *(buf + --i) = &#039;-&#039;;
    }

    //Eleminate leading spaces
    c = 0;
    while(*(buf + i))
    {
	    *(buf + c++) = *(buf + i++);
    }
    *(buf + c) = 0;
}

//Compare 2 strings
int strcomp(char *s1, char *s2)
{
    int t1;
    for(t1 = 0; t1 &lt; strlen(s2); t1++)
    {
        if(*(s1 + t1) != *(s2 + t1))
	    {
	       return 0;
		}
    }

    return 1;
}

//Get length of string
int strlen(char *s)
{
   int t1 = 0;

   while(*(s + t1++));

   return (t1 - 1);
}

//Find s2 in s1 if present, hand back position in string if yes
int instring(char *s1, char *s2)
{
   int t1, t2, ok;

   for(t1 = 0; *(s1 + t1) ; t1++)
   {
	    ok = 1;
	    for(t2 = 0; t2 &lt; strlen(s2); t2++)
	    {

	        if(*(s1 + t1 + t2) != *(s2 + t2))
	        {
	            ok = 0;
	        }
	    }

		if(ok)
	    {
	        return t1;
	    }
    }

    return 0;
}

//************/
//    UART
//************/
//Init UART
void uart_init()
{
    /* 9600 Baud */
    UBRRL = 51;
    UBRRH = 0;

    /* RX and TX on */
    UCSRB = (1&lt;&lt;RXEN)|(1&lt;&lt;TXEN);    

    /* 8 Datenbits, 1 Stopbit, keine Paritaet */
    UCSRC = (1&lt;&lt;URSEL)|(1&lt;&lt;UCSZ1)|(1&lt;&lt;UCSZ0);
}

//Send 1 char to UART
void uart_putc(char tx_char)
{
    while(!(UCSRA &amp; (1&lt;&lt;UDRE)));

    UDR = tx_char;
}

//Send one string to UART
void uart_send_string(char *s)
{
    int t1 = 0;
	while(*(s + t1))
	{
	    uart_putc(*(s + t1++));
	}
	uart_putc(13);
	uart_putc(10);

}

uint8_t uart_getc(void)
{
    while(!(UCSRA &amp; (1&lt;&lt;RXC)));   // warten bis Zeichen verfuegbar
    return UDR;                   // Zeichen aus UDR an Aufrufer zurueckgeben
}

//Init RX buffer
void init_rx_buffer(void)
{
	int t1;

    for(t1 = 0; t1 &lt; RX_BUF_SIZE - 1; t1++)
    {
		rx_buf[t1] = 0;
	}
	rx_buf_cnt = 0;
}		

/* CRC-calculation */
char make_crc(int buflen, int addchar)
{
    int t1, x = 0;

    for(t1 = 0; t1 &lt; buflen; t1++) /* Puffer bis dato */
	{
        x = x ^ rx_buf[t1];
    }
	x = x ^ addchar;                /* Sendebyte */

    return x;
}

///////////////////// END UART ///////////////

//////////////////////////////
//                          //
// NMEA decode functions    //
//                          //
//////////////////////////////

//Format date string
void get_date(char *buffer2, char *buffer3)
{

    int t1, t2 = 0;
    int p1, p2;

    //Zero buffer3
    for(t1 = 0; t1 &lt; RX_BUF_SIZE; t1++)
    {
		*(buffer3 + t1) = 0;
	}

	//Find 9th datafield
	t1 = 0;
	t2 = 0;
	while(t2 != 9 &amp;&amp; t1 &lt; RX_BUF_SIZE)
	{
		if(*(buffer2 + t1) == &#039;,&#039;)
		{
			t2++;
		}
		t1++;
	}
	p1 = t1;

	//Find end of datafield
	t1 = 0;
	t2 = 0;
	while(t2 != 10 &amp;&amp; t1 &lt; RX_BUF_SIZE)
	{
		if(*(buffer2 + t1) == &#039;,&#039;)
		{
			t2++;
		}
		t1++;
	}
	p2 = t1;

	//Copy relevant prt of string to buffer3
	t2 = 0;
    for(t1 = p1; t1 &lt; p2 - 1; t1++)
    {
		if((t1 - p1) == 2 || (t1 - p1) == 4)
		{
			*(buffer3 + t2++) = &#039;.&#039;;
		}
		*(buffer3 + t2++) = *(buffer2 + t1);
	}
}	

//Format time string
void get_time(char *buffer2, char *buffer3)
{

    int t1, t2 = 0;
    int p1, p2;

    //Zero buffer3
    for(t1 = 0; t1 &lt; RX_BUF_SIZE; t1++)
    {
		*(buffer3 + t1) = 0;
	}

	//Find first datafield
	t1 = 0;
	while(*(buffer2 + t1) != &#039;,&#039; &amp;&amp; t1 &lt; RX_BUF_SIZE)
	{
		t1++;
	}
	p1 = t1 + 1;

	//Find dot in timestring
	t1 = p1 + 1;
	while(*(buffer2 + t1) != &#039;.&#039; &amp;&amp; t1 &lt; RX_BUF_SIZE)
	{
		t1++;
	}
	p2 = t1;

	//Copy relevant prt of string to buffer3
    for(t1 = p1; t1 &lt; p2; t1++)
    {
		if(t1 == 9 || t1 == 11)
		{
			*(buffer3 + t2++) = &#039;:&#039;;
		}
		*(buffer3 + t2++) = *(buffer2 + t1);
	}
}	

//Format receiver status string
int get_receiver_status(char *buffer)
{
	int t1, t2 = 0;
    int p1;

	//Find second datafield
	t1 = 0;
	while(t2 != 2 &amp;&amp; t1 &lt; RX_BUF_SIZE)
	{
		if(*(buffer + t1) == &#039;,&#039;)
		{
			t2++;
		}
		t1++;
	}
	p1 = t1;

	if(*(buffer + p1) == &#039;A&#039;)
    {
		return 0;
	}
	else
	{
		return 1;
	}
}	

//Format latitude to degrees
void get_latitude(char *buffer2, char *buffer3)
{

    int t1, t2;
    int p1, p2;

    //Zero buffer3
    for(t1 = 0; t1 &lt; RX_BUF_SIZE; t1++)
    {
		*(buffer3 + t1) = 0;
	}

	//Find 3rd datafield
	t1 = 0;
	t2 = 0;
	while(t2 != 3 &amp;&amp; t1 &lt; RX_BUF_SIZE)
	{
		if(*(buffer2 + t1) == &#039;,&#039;)
		{
			t2++;
		}
		t1++;
	}
	p1 = t1;

	//Find end of datafield
	t1 = 0;
	t2 = 0;
	while(t2 != 4 &amp;&amp; t1 &lt; RX_BUF_SIZE)
	{
		if(*(buffer2 + t1) == &#039;,&#039;)
		{
			t2++;
		}
		t1++;
	}
	p2 = t1;

	//No data available
	if(p2 - p1 == 1)
	{
		return;
	}	

	//Copy relevant prt of string to buffer3
	t2 = 0;
    for(t1 = p1; t1 &lt; p2 - 1; t1++)
    {
		if(t1 == p1 + 2)
		{
			*(buffer3 + t2++) = 1; //°
		}	

		*(buffer3 + t2++) = *(buffer2 + t1);
	}
}	

//Format latitude orientation
void get_latitude_ns(char *buffer2, char *buffer3)
{

    int t1, t2;
    int p1, p2;

    //Zero buffer3
    for(t1 = 0; t1 &lt; RX_BUF_SIZE; t1++)
    {
		*(buffer3 + t1) = 0;
	}

	//Find 4th datafield
	t1 = 0;
	t2 = 0;
	while(t2 != 4 &amp;&amp; t1 &lt; RX_BUF_SIZE)
	{
		if(*(buffer2 + t1) == &#039;,&#039;)
		{
			t2++;
		}
		t1++;
	}
	p1 = t1;

	//Find end of datafield
	t1 = 0;
	t2 = 0;
	while(t2 != 5 &amp;&amp; t1 &lt; RX_BUF_SIZE)
	{
		if(*(buffer2 + t1) == &#039;,&#039;)
		{
			t2++;
		}
		t1++;
	}
	p2 = t1;

	//No data available
	if(p2 - p1 == 1)
	{
		return;
	}	

	//Copy relevant prt of string to buffer3
	t2 = 0;
    for(t1 = p1; t1 &lt; p2 - 1; t1++)
    {
		*(buffer3 + t2++) = *(buffer2 + t1);
	}	

}	

//Format longitude
void get_longitude(char *buffer2, char *buffer3)
{

    int t1, t2;
    int p1, p2;

    //Zero buffer3
    for(t1 = 0; t1 &lt; RX_BUF_SIZE; t1++)
    {
		*(buffer3 + t1) = 0;
	}

	//Find 5th datafield
	t1 = 0;
	t2 = 0;
	while(t2 != 5 &amp;&amp; t1 &lt; RX_BUF_SIZE)
	{
		if(*(buffer2 + t1) == &#039;,&#039;)
		{
			t2++;
		}
		t1++;
	}
	p1 = t1;

	//Find end of datafield
	t1 = 0;
	t2 = 0;
	while(t2 != 6 &amp;&amp; t1 &lt; RX_BUF_SIZE)
	{
		if(*(buffer2 + t1) == &#039;,&#039;)
		{
			t2++;
		}
		t1++;
	}
	p2 = t1;

	//No data available
	if(p2 - p1 == 1)
	{
		return;
	}	

	//Copy relevant prt of string to buffer3
	t2 = 0;
    for(t1 = p1; t1 &lt; p2 - 1; t1++)
    {
		if(t1 == p1 + 3)
		{
			*(buffer3 + t2++) = 1; //°
		}	

		*(buffer3 + t2++) = *(buffer2 + t1);
	}	

}	

//Format longitude orientation
void get_longitude_ew(char *buffer2, char *buffer3)
{

    int t1, t2;
    int p1, p2;

    //Zero buffer3
    for(t1 = 0; t1 &lt; RX_BUF_SIZE; t1++)
    {
		*(buffer3 + t1) = 0;
	}

	//Find 5th datafield
	t1 = 0;
	t2 = 0;
	while(t2 != 6 &amp;&amp; t1 &lt; RX_BUF_SIZE)
	{
		if(*(buffer2 + t1) == &#039;,&#039;)
		{
			t2++;
		}
		t1++;
	}
	p1 = t1;

	//Find end of datafield
	t1 = 0;
	t2 = 0;
	while(t2 != 7 &amp;&amp; t1 &lt; RX_BUF_SIZE)
	{
		if(*(buffer2 + t1) == &#039;,&#039;)
		{
			t2++;
		}
		t1++;
	}
	p2 = t1;

	//No data available
	if(p2 - p1 == 1)
	{
		return;
	}	

	//Copy relevant prt of string to buffer3
	t2 = 0;
    for(t1 = p1; t1  lattiude
double get_gps_coordinate_decimal(char *buf, int ctype, char *retbuf)
{
	int t1, t2;
	int sp; //Position of 1st comma of relevant data field
	double rval = 0;
	double x;

	char *l_str = malloc(20);

	if(!ctype)
	{
		sp = 5;  //Longitude
	}
	else
	{
		sp = 3; //Lattitude
	}	

	//Init temporary string
	for(t1 = 0; t1 &lt; 20; t1++)
	{
		*(l_str + t1) = 0;
	}

	//Search start position of value (3rd or 5th &#039;,&#039;)
	t1 = 0; t2 = 0;
	while(buf[t1] != 0 &amp;&amp; t2 != sp)
	{
		if(buf[t1] == &#039;,&#039;)
		{
			t2++;
		}
		t1++;
	}
	sp = t1;	

	//Load relevant part of original string to new bufferstring
	t2 = 0;
	for(t1 = sp; *(buf + t1) != &#039;,&#039;; t1++)
	{
		*(l_str + t2++) = buf[t1];
	}

    //Check multiplier
	if(!ctype)
	{
		x = 100;
	}
	else
	{
		x = 10;
	}

	//Convert string to FP number
	for(t1 = 0; l_str[t1] != 0; t1++)
	{
		if(l_str[t1] != &#039;.&#039;)
		{
		    rval += (double)(l_str[t1] - 48) * x;
		    x /= 10;
		}
	}

	free(l_str);

	//Get orientation indicator (N/S or W/E)
	if(!ctype)
	{
		sp = 6; //Longitude
	}
	else
	{
		sp = 4; //Lattitude
	}	

	//Search start position (4th or 6th &#039;,&#039;)
	//where letter is expected
	t1 = 0; t2 = 0;
	while(buf[t1] != 0 &amp;&amp; t2 != sp)
	{
		if(buf[t1] == &#039;,&#039;)
		{
			t2++;
		}
		t1++;
	}
	sp = t1;	

	retbuf[0] = buf[sp];

	return rval;
}	

//Ground speed
void get_ground_speed(char *buffer2, char *buffer3)
{

    int t1, t2;
    int p1, p2;

    //Zero buffer3
    for(t1 = 0; t1 &lt; RX_BUF_SIZE; t1++)
    {
		*(buffer3 + t1) = 0;
	}

	//Find 5th datafield
	t1 = 0;
	t2 = 0;
	while(t2 != 7 &amp;&amp; t1 &lt; RX_BUF_SIZE)
	{
		if(*(buffer2 + t1) == &#039;,&#039;)
		{
			t2++;
		}
		t1++;
	}
	p1 = t1;

	//Find end of datafield
	t1 = 0;
	t2 = 0;
	while(t2 != 8 &amp;&amp; t1 &lt; RX_BUF_SIZE)
	{
		if(*(buffer2 + t1) == &#039;,&#039;)
		{
			t2++;
		}
		t1++;
	}
	p2 = t1;

	//No data available
	if(p2 - p1 == 1)
	{
		return;
	}	

	//Copy relevant prt of string to buffer3
	t2 = 0;
    for(t1 = p1; t1 = RX_BUF_SIZE)
        {
			/*
			for(t1 = 0; t1 = 0; t1--)
		    {
			    //Scan for specific identifier of NMEA message RMC Example &quot;$GPRMC&quot;
		        valid = 1;
		        for(t2 = 0; t2 &lt; 5; t2++)
		        {
				    if(rx_buf[t1 + t2] != msg_code[t2])
				    {
					    valid = 0;
				    }
			    }		

		        if(valid) //Infotype OK, data found
		        {
					//Copy rx-buffer to buffer2

					//Init buffer2
					for(t2 = 0; t2 &lt; RX_BUF_SIZE; t2++)
					{
						*(buf2 + t2) = 0;
					}	

					//Copy relevant part of rx-buffer to buffer2
					t3 = 0;
					for(t2 = t1; t2 &lt; rx_buf_cnt &amp;&amp; rx_buf[t2] != &#039;*&#039;; t2++)
			        {
					    *(buf2 + t3) = rx_buf[t2];
					    t3++;
					}   

					lcd_cls();

					//Display information
					lcd_putstring(0, 0, &quot;  GPS Receiver&quot;);
                    lcd_putstring(1, 0, &quot;   DK7IH 2018&quot;);
                    _delay_ms(dispdelay);
					lcd_cls();

					//Message string
					lcd_putstring(0, 2, &quot;Message type&quot;);
					lcd_putstring(1, 4, msg_code);
					_delay_ms(dispdelay);
					lcd_cls();

					//QTH locator (Maidenhead)
					lcd_putstring(0, 2, &quot;QTH Locator&quot;);
					rbuf0 = malloc(4);
					rbuf1 = malloc(4);
					for(t1 = 0; t1 &lt; 4; t1++)
					{
						*(rbuf0 + t1) = 0;
						*(rbuf1 + t1) = 0;
					}	

					lon = get_gps_coordinate_decimal(buf2, 0, rbuf0); //Calc longitude
					lat = get_gps_coordinate_decimal(buf2, 1, rbuf1); //Calc latitude

					rstr = malloc(7);
					for(t1 = 0; t1 <span data-mce-type="bookmark" id="mce_SELREST_start" data-mce-style="overflow:hidden;line-height:0" style="overflow:hidden;line-height:0;"></span>&lt; 7; t1++)
					{
						*(rstr + t1) = 0;
					}
					calc_maidenhead(lon, rbuf0[0], lat, rbuf1[0], rstr);
					//calc_maidenhead(8.3362, &#039;E&#039;, 49.0145, &#039;N&#039;, rstr); //Test for &quot;JN49EA&quot;
					lcd_putstring(1, 4, rstr);
					_delay_ms(dispdelay);
				    free(rbuf0);
   			        free(rbuf1);
   			        free(rstr);
					lcd_cls(); 

					//DATE
					lcd_putstring(0, 6, &quot;Date&quot;);
					get_date(buf2, buf3);
					lcd_putstring(1, 4, buf3);
					_delay_ms(dispdelay);
					lcd_cls();

					//TIME
					lcd_putstring(0, 3, &quot;Time (UTC)&quot;);
					get_time(buf2, buf3);
					lcd_putstring(1, 4, buf3);
					_delay_ms(dispdelay);
					lcd_cls();

					//Receiver status
					lcd_putstring(0, 0, &quot;Receiver status&quot;);
					if(get_receiver_status(buf2) == 0)
					{
						lcd_putstring(1, 7, &quot;OK&quot;);
					}
					else
					{
						lcd_putstring(1, 7, &quot;ERR&quot;);
					}
					_delay_ms(dispdelay);
					lcd_cls();

					//Lattitude
					lcd_putstring(0, 4, &quot;Lattitude&quot;);

					get_latitude(buf2, buf3);
					lcd_putstring(1, 2, buf3);
					get_latitude_ns(buf2, buf3); //N or S
					lcd_putstring(1, 14, buf3);
					_delay_ms(dispdelay);
					lcd_cls();

					//Longitude
					lcd_putstring(0, 4, &quot;Longitude&quot;);
					get_longitude(buf2, buf3);
					lcd_putstring(1, 2, buf3);
					lcd_putstring(0, 4, &quot;Longitude&quot;); //E or W
					get_longitude_ew(buf2, buf3);
					lcd_putstring(1, 15, buf3);
					_delay_ms(dispdelay);
					lcd_cls();

                    //Ground speed
					lcd_putstring(0, 2, &quot;Ground speed&quot;);
					get_ground_speed(buf2, buf3);
					lcd_putstring(1, 5, buf3);
					lcd_putstring(1, 12, &quot;[kn]&quot;);
					_delay_ms(dispdelay);
					lcd_cls();
		        }
		    }
	        rx_buf_cnt = 0;
	    }
	    else
	    {
		    rx_buf[rx_buf_cnt++] = ch;
	    }
    }
	return 0;
}

Advertisements

2 thoughts on “Decoding NMEA data from a GPS module with a microcontroller”

  1. Some module dependent code is also required to select which NEMA sentences you want the module to spit out. Usually this is a one time thing as the module stores the settings in non-volatile memory, but not always. Some cheaper GPS receivers have to be programmed upon each power up / reboot. Another configuration item is the rate at which the update occurs. Usually once a second, but cheap modules might only update at a 1/10 hz rate (YUCK). The bearing data usually wont be present unless the receiver is moving, IE: the ground speed isn’t zero. It IS possible to combine TWO GPS receivers, or have a receiver with TWO antennas separated by a known distance to get a bearing fix without movement. This is cool, you could then mount the GPS receiver and the two antennas on your HF beam and get an accurate beam heading. Such devices are used in the cell phone industry to align cell tower antennas. (As you might guess, I’ve played around with GPS before).

  2. Interesting is that my module never says ground speed = 0. But I don”t move, I swear” (except from the earth’s rotation, its way round the sun and the circulation of our solar system round the center of the galaxy. ;-)) 73 de Peter

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.