Reading and Parsing USB GPS Data in C with NmeaLib

19 September 2008 in hardware

I’m currently working on a project that requires GPS data, and need a way to retrieve data from a USB-based GPS receiver I purchased (a USGlobSat BU-353 USB GPS receiver). Fortunately, most GPS receivers, particularly USB ones, support the NMEA 0183 protocol.

Background

NMEA 0183 is a serial-based protocol was developed by the National Marine Electronics Association and is used to collect data from a variety of aquatic electronic devices (such as sonar, GPS, and others). Most USB-based GPS receivers have a serial-to-USB converter built in, which allows data to be read from a device as if it were a serial device.

Raw data

The BU-353 shows up in Mac OS X as /dev/cu.usbserial. If you plug in your GPS device and boot up Minicom, you’ll see output that looks a lot like this:

Sample NMEA 0183 Strings

NmeaLib

Obviously the above strings are somewhat cryptic, but you can discern some data among them that looks like GPS data. Unfortunately the NMEA 0183 protocol is not open, and costs at least $270 to buy from the NMEA. Fortunately, most of it has been reverse engineered, and there is a great library which already exists to parse these strings into useable data. It is called NmeaLib and it is released under the LGPL. The way it works is that you first initialize it, then just feed it every string that the GPS device feeds you over the serial device, and it will interpret the data and keep a cumulative collection of all available data from the GPS receiver. In addition to the basic latitude and longitude data, it will also report the number of satellites in view, the number satellites being used, the signal strengths of both, as well as the current GPS time and many other useful pieces of information.

Reading data

The big remaining task once you have the library is to read from the GPS serial device. Reading from a serial device might seem like it would be as simple as reading from a file (and in some ways it is), but because serial port data can come at various rates and in various formats (for instance it might come at 9600 Baud with no parity bit and with 8 bits of data), there are some other calls you need to make to set the various serial settings.

To begin with, we need to get a file descriptor for the serial port. This can be done simply with:

gpsFd = open("/dev/cu.usbserial", O_RDONLY | O_NOCTTY | O_NONBLOCK);

You will also need some of the structures and functions included in termios.h, the POSIX terminal definitions, in order to set the appropriate terminal settings.

struct termios options;
struct termios origPortOptions;

// Get the existing options so they can be restored
tcgetattr(gpsFd, &origPortOptions);

bzero(&options, sizeof(options));

// Set the BAUD rate to NMEA specifications
cfsetospeed(&options, B4800);
options.c_cflag |= (CLOCAL | CREAD);

// Settings equivalent to 8N1
options.c_cflag &= ~PARENB; // Disabled parity
options.c_cflag &= ~CSTOPB; // Set 1 stop bit (| would give us 2 stop bits)
options.c_cflag |= CS8; // Set character size to 8 bits
options.c_cflag |= CRTSCTS; // Enable hardware flow control

options.c_oflag |= (OPOST | ONLCR); // Processed output and mapping of newlines to CR-LF pairs

tcsetattr(gpsFd, TCSAFLUSH, &options); // Flush buffers and apply the new settings

After that, you can read from gpsFd like any other file descriptor. If you want to get the number of bytes waiting in the buffer to be read, you can use ioctl() as follows:

int bytesInBuffer;

// Get the number of bytes waiting in the serial buffer
ioctl(gpsFd, FIONREAD, &bytesInBuffer);

Results

Once you feed the data to NmeaLib, you’ll seeing actual GPS coordinate output, which will update as new strings arrive from the GPS receiver:

GPS Test Program

Yeah so now you know where I live, but I suppose a determined individual could get that from a domain registrar anyway.

Feel free to reuse this code however you see fit, although if you do, some credit is always appreciated. Have fun building those autonomous spy blimps.