ANSI C Basic Lightweight NMEA Parser for GPS

For a recent OBD2 (vehicle/fleet data-logging) project running on a Raspberry PI Zero, I needed access to GPS data – both position/speed and time.

The Raspberry PI doesn’t include a Real Time Clock (RTC). Upon boot, it doesn’t know what the actual time it is, unless it has access to a network and can retrieve the time via Network Time Protocol (NTP). With the logger fitted to a vehicle, a network connection wasn’t always available for NTP.

Robust, feature rich software packages currently exist such as gpsd – a GPS service daemon. gpsd can also be used used in conjunction with NTPd to set the time.

One advantage of using gpsd would have been compatibility with various GPS. While NMEA 0183 is a standard, there are subtle differences, interpretations or even bugs between vendors that can throw the spanner in the works.

However, as I was targeting my own PCB containing a ublox MAX-M8W, a lack of compatibility didn’t rate high in my list of project risks. I also believed the PI Zero was an overkill for the project, and was considering a lower power microcontroller that didn’t have all the overhead of Linux.

NMEA-0183

NMEA-0183 is a National Marine Electronics Association standard describing the electrical specification and data protocol for communication with common marine electronics such as echo sounders, sonars, anemometers and of course, GPS.

The u-blox 8 receiver description & protocol specification, section 31: NMEA Protocol describes all you need to know about NMEA messages.

NMEA 0183 takes the form of a series of ASCII comma delimited string, for example:

$GPVTG,333.93,T,,M,0.343,N,0.635,K,A30
$GPGGA,043025.00,3510.97111,S,13512.82950,E,1,11,0.79,37.5,M,-4.4,M,,6D
$GPGSA,A,3,26,03,04,02,06,30,08,07,09,27,16,,1.35,0.79,1.10,0B
$GPGSV,3,1,12,02,06,248,22,03,19,018,31,04,52,098,44,05,00,215,73

Each line begins with a start character. This is always ‘$’.

It is followed by an ‘address’ field consisting only of digits and uppercase letters. This address field is divided into two fields.

The first two characters define the ‘talker identifier’ or the source of the message. For example messages from a GPS receiver will begin with GP, from a GLONASS receiver – GL, from Galileo – GA and from any combination of GNSS receiver – GN.

The remaining three characters define the ‘sentence formatter’ or the contents of the message. The ublox 8 Receiver Description will list all the messages supported by the module, but some of the common ones are:

  • GGA – Global positioning system fix data.
  • GSA – GNSS DOP and active satellites.
  • GSV – GNSS satellites in view.
  • VTG – Course over ground and ground speed.
  • RMC – Recommended minimum data.

The ublox 8 Receiver Description will also list and explain the contents each field of the message.

The last field of each message is always the checksum.

Parsing

The comma delimited format of the NMEA messages makes it extremely easy to parse. The code can iterate through the string and change each comma into a null to terminate the C string. It can then store the address of the next location into an array so we can later reference to the start of each field.

int parse_comma_delimited_str(char *string, char **fields, int max_fields)
{
	int i = 0;
	fields[i++] = string;

	while ((i < max_fields) && NULL != (string = strchr(string, ','))) {
		*string = '\0';
		fields[i++] = ++string;
	}

	return --i;
}

Once the messages have been parsed, they can be simply referenced by field number:

printf("UTC Time  :%s\r\n",field[1]);
printf("Latitude  :%s\r\n",field[2]);
printf("Longitude :%s\r\n",field[4]);
printf("Satellites:%s\r\n",field[7]);

Field numbers can be obtained from the ublox 8 Receiver Description. If you want, you could even replace the magic numbers with defines!

Extract from ublox 8 Receiver Description manual showing field numbers for the RMC message.

Linux

For ease of development, the code was initially developed on a desktop computer running Ubuntu, before being transferred to the Raspberry PI target. In these initial stages, a FTDI USB to asynchronous serial cable was used.

The terminal interface on Linux supports canonical input processing mode. In this mode, characters are grouped into lines terminated by a new line character. Hence, we don’t need to read bytes from the serial port into a circular buffer and wait until we receive a new line.

Setting the RealTime Clock

For the purposes of the logger, the code didn’t have to set the real time clock on the host. As long as the logger code used the GPS time for the timestamp of data points, the Linux system time was irrelevant.

However, why not set the system time? It could aid debugging as the file timestamps reflected the correct time. It would also allow other possibilities such as using cron jobs.

The SetTime() function takes the UTC (Coordinated Universal Time) date and time from the GPS receiver in the format of ddmmyy and hhmmss.ss.

After a quick type check, the date and time is converted into integers and used to populate a time structure tm. It is then converted into time (number of seconds) since epoch, corrected for the current timezone and used to set the clock using the clock_settime() function. This requires superuser privileges to function, otherwise an operation not permitted error may result.

Raspberry PI

The GPS module was connected to the Raspberry PI’s UART pins (pins 8 & 10). To enable this UART port add enable_uart=1 to the /boot/config.txt file.

The Linux kernel is also likely to use this UART (console) for kernel messages during boot. This will result in your GPS being flooded with characters. To avoid this, remove console=tty* from your /boot/cmdline.txt file.

The Linux device for the UART will vary between platforms. For more information and the correct device, visit: https://www.raspberrypi.org/documentation/configuration/uart.md

Source Code

The source code for this project can be obtained from Github:




Be the first to comment

Leave a Reply

Your email address will not be published.


*