How it works
NMEA 0183 uses a simple XOR checksum over the sentence payload. The payload is everything between $ (exclusive) and * (exclusive). The result is formatted as two uppercase hex digits and appended after the *.
For $GPRMC,092751.000,A,5321.6802,N,00630.3371,W,0.06,31.66,280511,,,A*43:
payload = "GPRMC,092751.000,A,5321.6802,N,00630.3371,W,0.06,31.66,280511,,,A"
checksum = 'G' ^ 'P' ^ 'R' ^ 'M' ^ 'C' ^ ',' ^ ... = 0x43
The $ and * themselves are never included. Everything in between is — commas, decimal points, all of it.
C implementation
This is the entire algorithm. No library needed:
uint8_t nmea_checksum(const char *sentence) {
uint8_t cksum = 0;
/* skip leading $ if present */
if (*sentence == '$') sentence++;
while (*sentence && *sentence != '*') {
cksum ^= (uint8_t)*sentence++;
}
return cksum;
}
To validate an incoming sentence:
bool nmea_valid(const char *sentence) {
const char *star = strchr(sentence, '*');
if (!star || strlen(star) < 3) return false;
uint8_t expected = (uint8_t)strtol(star + 1, NULL, 16);
return nmea_checksum(sentence) == expected;
}
Parsing rules
- Sentences start with
$, end with*HHwhereHHis the two-hex-digit checksum, followed by\r\n. - Some talkers (proprietary sentences) use
$Pas prefix — checksum algorithm is identical. - The checksum field is optional in NMEA 0183 v1.5 but mandatory in v2.0+. In practice, all modern GPS modules include it.
- The maximum sentence length is 82 characters including
$and\r\n.
Common mistakes
Including $ or * in the XOR. The checksum covers only the data between them. This is the most common off-by-one.
Case sensitivity. The hex checksum must be uppercase (*4B not *4b). Most parsers accept lowercase anyway, but the spec says uppercase.
CRLF vs LF. NMEA 0183 specifies \r\n. If your UART strips the \r, that’s fine — the checksum is computed before the line terminator.
Multi-sentence batches. If your parser buffers incoming data and processes line-by-line, ensure the \r doesn’t end up in the sentence string before you compute the checksum.
PUBX sentences. u-blox uses $PUBX for proprietary messages. Same checksum algorithm, but the parser must not confuse PUBX with standard talker+sentence-ID format.
Firmware considerations
For interrupt-driven UART with a DMA ring buffer, compute the checksum incrementally as bytes arrive — don’t wait for the full sentence. Start XORing from the first byte after $, stop when you see *, then compare the next two hex bytes to your running XOR. This avoids storing the entire sentence and works within tight RAM budgets on Cortex-M0 targets.