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.
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.
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:
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.
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
the POSIX terminal definitions, in order to set the appropriate terminal
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);
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:
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.