Sensors / GNSS
Examples
Extracting latitude, longitude, and timestamp from a real
$GPGGA sentence read over UART.
A sample sentence
$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
Fields, in order: UTC time (123519 = 12:35:19), latitude
(4807.038,N), longitude (01131.000,E), fix
quality (1), satellites used (08), HDOP
(0.9), altitude (545.4,M), geoid separation,
then an empty DGPS field, and finally the checksum after *.
Checksum validation
The checksum is the XOR of every byte between $ and
*, compared against the two hex digits that follow.
bool nmea_checksum_ok(const char *sentence) {
if (sentence[0] != '$') return false;
const char *star = strchr(sentence, '*');
if (!star) return false;
uint8_t checksum = 0;
for (const char *p = sentence + 1; p < star; p++)
checksum ^= (uint8_t)*p;
uint8_t expected = (uint8_t)strtol(star + 1, NULL, 16);
return checksum == expected;
}
Parsing latitude/longitude and time
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int hour, minute, second;
double latitude; // decimal degrees, +north / -south
double longitude; // decimal degrees, +east / -west
int fix_quality;
} gnss_fix_t;
static double dm_to_decimal(const char *dm, char hemisphere) {
double raw = atof(dm); // e.g. 4807.038
int degrees = (int)(raw / 100); // 48
double minutes = raw - (degrees * 100); // 07.038
double decimal = degrees + minutes / 60.0;
if (hemisphere == 'S' || hemisphere == 'W')
decimal = -decimal;
return decimal;
}
bool parse_gpgga(const char *sentence, gnss_fix_t *out) {
if (!nmea_checksum_ok(sentence)) return false;
if (strncmp(sentence, "$GPGGA", 6) != 0 &&
strncmp(sentence, "$GNGGA", 6) != 0) return false;
char time_field[16], lat_field[16], lat_hemi[2];
char lon_field[16], lon_hemi[2];
int fix_quality;
// field order: time, lat, N/S, lon, E/W, fix quality, ...
int n = sscanf(sentence,
"$%*3s,%15[^,],%15[^,],%1[^,],%15[^,],%1[^,],%d",
time_field, lat_field, lat_hemi, lon_field, lon_hemi, &fix_quality);
if (n < 6) return false;
out->fix_quality = fix_quality;
if (fix_quality == 0) return false; // fields below aren't meaningful yet
out->hour = (time_field[0] - '0') * 10 + (time_field[1] - '0');
out->minute = (time_field[2] - '0') * 10 + (time_field[3] - '0');
out->second = (time_field[4] - '0') * 10 + (time_field[5] - '0');
out->latitude = dm_to_decimal(lat_field, lat_hemi[0]);
out->longitude = dm_to_decimal(lon_field, lon_hemi[0]);
return true;
}
Checking fix_quality == 0 and bailing out early matters
more than it looks like it should — a receiver with no fix yet
still happily sends well-formed $GPGGA sentences with
empty or stale latitude/longitude fields, and a parser that doesn't
check fix quality first will silently hand back garbage coordinates
that look plausible.