NMEA Checksum Calculator

Validate and compute NMEA 0183 sentence checksums. Paste one or multiple sentences — get XOR result, match/mismatch status, and corrected sentences with one click.

NMEA Sentences

Common NMEA sentence types

Sentence IDDescriptionFields
GPRMCRecommended minimum navigation data12
GPGGAGlobal positioning fix data15
GPGLLGeographic position — latitude/longitude7
GPVTGTrack made good and ground speed9
GPZDATime and date (UTC, day, month, year)6
GPGSVSatellites in view (elevation, azimuth, SNR)4+4n

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.

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;
}
fn nmea_checksum(sentence: &[u8]) -> u8 {
    let start = if sentence.first() == Some(&b'$') { 1 } else { 0 };
    sentence[start..]
        .iter()
        .take_while(|&&b| b != b'*')
        .fold(0u8, |acc, &b| acc ^ b)
}

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;
}
fn nmea_valid(sentence: &[u8]) -> bool {
    let Some(star_pos) = sentence.iter().position(|&b| b == b'*') else {
        return false;
    };
    let suffix = &sentence[star_pos + 1..];
    if suffix.len() < 2 { return false; }
    let Ok(hex_str) = core::str::from_utf8(&suffix[..2]) else { return false; };
    let Ok(expected) = u8::from_str_radix(hex_str, 16) else { return false; };
    nmea_checksum(sentence) == expected
}

Parsing rules

  • Sentences start with $, end with *HH where HH is the two-hex-digit checksum, followed by \r\n.
  • Some talkers (proprietary sentences) use $P as 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.

Frequently asked questions

Why is my NMEA checksum wrong even though I'm XORing all the characters? +

The most common mistake is including the $ or * delimiters in the XOR computation. The NMEA checksum covers only the bytes strictly between $ (exclusive) and * (exclusive). The $ start character and the * before the hex digits are never included. Start XORing from the first byte after $ and stop when you see *.

Does the NMEA checksum include the $ sign or the * character? +

No. The checksum is computed over the payload only — everything between the $ and the * exclusive. The $ and * are protocol framing characters. The two hex digits after * are the checksum itself, also not included in the calculation.

Why does my NMEA parser fail on some sentences but not others? +

Common causes: CRLF handling (NMEA 0183 uses \r\n — if your parser includes the \r in the XOR it will get the wrong result), proprietary sentence prefixes ($P or $PUBX use the same checksum algorithm but different structure), and multi-sentence buffers where a previous \r ends up prepended to the next sentence's payload.

Newsletter

The embedded engineer's weekly cheat sheet

Register tricks, timing gotchas, and tool updates. One email per week. No fluff.

No spam. Unsubscribe anytime.