I recently picked up a BU-353 USGlobalSat USB GPS receiver for $50 off eBay. After some “troubles” with the not-well-advertised included magnet (more on that later) I hooked it up and began to search for ways to access the NMEA 0183 data stream.
It’s a fairly straightforward process, though accessing the stream using the right communication settings (8n1, 4800 baud) is somewhat confusing.
#include /* Standard input/output definitions */
#include /* String function definitions */
#include /* UNIX standard function definitions */
#include /* File control definitions */
#include /* Error number definitions */
#include /* POSIX terminal control definitions */
int fd;
struct termios options;
int streamGPSdata()
{
char b[255]; /* Input buffer */
char out[255];
char *bufptr; /* Current char in buffer */
int nbytes; /* Number of bytes read */
int tries; /* Number of tries so far */
fd = open(”/dev/cu.usbserial”, O_RDONLY | O_NOCTTY);
fcntl(fd, F_SETFL, 0);
tcgetattr(fd, &options);
cfsetispeed(&options, B4800);
cfsetospeed(&options, B4800);
options.c_cflag |= (CS8);
tcsetattr(fd, TCSANOW, &options);
for (tries = 0; tries < 20; tries ++)
{
bufptr = b;
while ((nbytes = read(fd, bufptr, b + sizeof(b) - bufptr - 1)) > 0)
{
bufptr += nbytes;
if (bufptr[-1] == ‘$’)
{
int d;
for(d = 0; d < 255; d++)
{
if(b[d]==’$'||b[d]==’A'||b[d]==’B'||b[d]==’C'||b[d]==’D’
||b[d]==’E'||b[d]==’F'||b[d]==’G'||b[d]==’H'||b[d]==’I’
||b[d]==’J'||b[d]==’K'||b[d]==’L'||b[d]==’M'||b[d]==’N’
||b[d]==’O'||b[d]==’P'||b[d]==’Q'||b[d]==’R'||b[d]==’S’
||b[d]==’T'||b[d]==’U'||b[d]==’V'||b[d]==’W'||b[d]==’X’
||b[d]==’Y'||b[d]==’Z'||b[d]==’0′||b[d]==’1′||b[d]==’2′
||b[d]==’3′||b[d]==’4′||b[d]==’5′||b[d]==’6′||b[d]==’7′
||b[d]==’8′||b[d]==’9′||b[d]==’,'||b[d]==’.'||b[d]==’*'){
if(b[d] != ‘*’){
out[d]=b[d];
}else{
out[d]=’\0′;
break;
}
// out[d]=b[d];
}
}
//printf(”\n”);
break;
}
}
if (strncmp(out, “GPGLL”, 5) == 0){
/* header, ddmm.mmmm (lat), N/S, dddmm.mmmm, E/W, hhmmss.sss, valid */
/* GPGLL, 4553.6629, N, 06422.4178, W, 142608.000, A*2B,0$9,18.7,M,022.1,M,18.8,0000*4E??$? */
printf(”We’ve got latitude/longitude!\n”);
printf(”%s\n\n”, out);
}
else if (strncmp(out, “GPRMC”, 5) == 0){
printf(”Time, date, position, course, speed.\n”);
/* header, hhmmss.sss, valid, ddmm.mmmm (lat), N/S, dddmm.mmmm (lon), E/W, speed, course, ddmmyy, magvar, */
/* GPRMC, 142607.000, A, 4553.6627, N, 06422.4175, W, 0.23, 137.29, 100407, ,*xx */
printf(”%s\n\n”, out);
}
else if (strncmp(out, “GPVTG”, 5) == 0){
/* header, course, T, course, M, speed, units, speed, units */
/* GPVTG, 284.29, T, , M, 0.20, N, 0.4, K*xx */
printf(”We’ve got a course and speed.\n”);
printf(”%s\n\n”, out);
}
else if (strncmp(out, “GPGSV”, 5) == 0){
/* header, #messages, message#, numsats, idCh1, elevation, azimuth, SNR, idCh2, elev, azim, SNR, idCh3, elev, azim, SNR, idCh4, elev, azim, SNR */
/* GPGSV, 3, 1, 12, 02, 76, 329, 32, 04, 51, 086, 31, 10, 49, 229, 30, 13, 34, 061, 20*xx */
printf(”This is overall satellite data.\n”);
printf(”%s\n\n”, out);
}
else if (strncmp(out, “GPGSA”, 5) == 0){
/* header, A, 2/3D, satUsedCh1, Ch2,Ch3,Ch4,Ch5,Ch6,Ch7,Ch8,Ch9,Ch10,Ch11,Ch12, PDOP, HDOP, VDOP */
/* GPGSA, A, 3, 12, 04, 02, 10, 05, 13, 30, 17, 23, , , , 1.5, 0.9, 1.3*xx */
printf(”This is active satellite data.\n”);
printf(”%s\n\n”, out);
}
else if (strncmp(out, “GPGGA”, 5) == 0){
/* header, hhmmss.sss, ddmm.mmmm (lat), N/S, dddmm.mmmm (lon), E/W, 1, numsats[0-12], HDOP, altitude, units, geoid sep, units, diff. corr., 0000 */
/* GPGGA, 143727.000, 4553.6654, N, 06422.4203, W, 1, 06, 2.2, 29.3, M, 022.1, M, 18.8, 0000*xx*/
printf(”This is GPS fixed data.\n”);
printf(”%s\n\n”, out);
}
}
return (-1);
}
int main()
{
streamGPSdata();
printf(”Quitting now.\n”);
close(fd);
}
The code is somewhat hackish - it scans through the NMEA stream (which consists primarily of ASCII garbage) for $GPxxx data (which only uses the shortened alphabet I scan for). It’s also still hardcoded for the Prolific-based BU-353, which installs a driver at /dev/cu.usbserial.
Compiled and tested under Mac OS X 10.4.8, gcc 4.0.0: gcc code.c -o app; ./app