Update (28/05/2026): We have plenty of stock of all our products, find us also in and    How to get a quotation

NMEA protocol and message description

Table of contents

A protocol is a set of rules that defines how data is formatted, transmitted and interpreted between two or more devices so they can understand each other.

Think of it like a language with strict grammar rules — both the sender and receiver must follow the exact same rules, otherwise the message is meaningless. Without protocols, every manufacturer would invent their own format and devices from different brands would be unable to communicate with each other.

NMEA-0183 protocol (NMEA from now on) is the industry standard for GNSS technology.
We have prepared this page as a reference for NMEA protocol definition and to provide also a description of the most popular messages. If you miss any message or you find any typo, contact us and we will fix it 🙂

Message Description Availability
GGAGlobal Positioning System Fix Data — position, altitude, fix quality and number of satellitesAll receivers
GLLGeographic Position — latitude and longitude with time and statusAll receivers
GNSGNSS Fix Data — similar to GGA but supports multiple constellations (GPS, GLONASS, Galileo...)All receivers
GRSGNSS Range Residuals — residuals of ranges used in the navigation solutionAll receivers
GSAGNSS DOP and Active Satellites — fix type (2D/3D) and satellites usedAll receivers
GSTGNSS Pseudorange Error Statistics — position error estimates (RMS, latitude, longitude, altitude)All receivers
GSVGNSS Satellites in View — number, elevation, azimuth and signal strength of visible satellitesAll receivers
HDTHeading True — actual heading of the vessel relative to true northSeptentrio Mosaic-H simpleRTK3B Heading
INSPVAXASensor Fusion data — integrated position, velocity, attitude and their estimated errorsUnicore UM981 simpleRTK3B Fusion
PUBX,00Position Data — latitude, longitude, altitude and fix quality (u-blox devices)All u-blox receivers
PUBX,04Time of Day — UTC time and clock data (u-blox devices)All u-blox receivers
RMCRecommended Minimum Specific GNSS Data — position, speed, course and dateAll receivers
ROTRate of Turn — speed of rotation of the vessel in degrees per minuteSeptentrio Mosaic-H simpleRTK3B Heading
VTGCourse Over Ground and Ground Speed — track and speed in knots and km/hAll receivers
ZDATime and Date — UTC time, day, month, year and local time zoneAll receivers
No results found.

NMEA message structure

Each message starts with a $ sign followed by a short code that identifies the type of data it contains (see table on next section).
The receiver then fills in all the data fields separated by commas — latitude, longitude, altitude, time, number of satellites, etc. — and finishes the message with a checksum, which is a small number that allows the receiving device to verify the data wasn’t corrupted during transmission.
The message ends with a line break and the next message begins immediately after.

The picture below summarizes how an NMEA message is generated.

NMEA message structure – infography extracted from u-blox documentation

NMEA checksum generation

Code examples to generate the NMEA checksum based on an NMEA payload:

    
     def nmea_checksum(payload):
    checksum = 0
    for char in payload:
        checksum ^= ord(char)
    return f"{checksum:02X}"
    
# Pass only the part between $ and *
print(nmea_checksum("GNGGA,092725.00,4717.11399,N,00833.91986,E,1,08,1.01,499.6,M,48.0,M,,"))
# Returns: '4E' (or whatever the correct checksum is)
    
   

NMEA checksum validation

If you want to validate if an NMEA message is legit or not, use the example code below:

    
     def validate_nmea(sentence):
    sentence = sentence.strip()
    if not sentence.startswith('$') or '*' not in sentence:
        return False
    
    payload, claimed = sentence[1:].split('*', 1)
    
    checksum = 0
    for char in payload:
        checksum ^= ord(char)
    
    return f"{checksum:02X}" == claimed.strip()[:2].upper()
    
print(validate_nmea("$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A"))  # True
print(validate_nmea("$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*FF"))  # False
print(validate_nmea("invalid sentence"))  # False
    
   

Online NMEA checksum calculator

$ *--

Checksum (hex)

--

Checksum (decimal)

--

Payload length

--

Full sentence

Verify a sentence

    
     #include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>

bool validate_nmea(const char *sentence) {
    if (!sentence || *sentence != '$') return false;

    const char *star = strchr(sentence, '*');
    if (!star || strlen(star) < 3) return false;

    uint8_t checksum = 0;
    const char *p = sentence + 1;
    while (p != star) {
        checksum ^= (uint8_t)*p++;
    }

    uint8_t claimed;
    if (sscanf(star + 1, "%2hhX", &claimed) != 1) return false;

    return checksum == claimed;
}

int main() {
    printf("%d\n", validate_nmea("$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A")); // 1
    printf("%d\n", validate_nmea("$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*FF")); // 0
    printf("%d\n", validate_nmea(NULL));  // 0
    printf("%d\n", validate_nmea("invalid")); // 0
    return 0;
}
    
   
    
     function validateNmea(sentence) {
    sentence = sentence.trim();
    if (!sentence.startsWith('$') || !sentence.includes('*')) return false;

    const starIdx = sentence.indexOf('*');
    const payload = sentence.slice(1, starIdx);
    const claimed = sentence.slice(starIdx + 1, starIdx + 3).toUpperCase();

    if (claimed.length < 2 || !/^[0-9A-F]{2}$/.test(claimed)) return false;

    let checksum = 0;
    for (let i = 0; i < payload.length; i++) {
        checksum ^= payload.charCodeAt(i);
    }

    return checksum.toString(16).toUpperCase().padStart(2, '0') === claimed;
}

validateNmea("$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A"); // true
validateNmea("$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*FF"); // false
validateNmea("invalid"); // false
    
   
    
     #include <stdint.h>
#include <string.h>
#include <stdio.h>

uint8_t nmea_checksum(const char *sentence) {
    // Skip leading '$' if present
    if (*sentence == '$') sentence++;
    
    uint8_t checksum = 0;
    while (*sentence && *sentence != '*') {
        checksum ^= (uint8_t)*sentence++;
    }
    return checksum;
}

int main() {
    const char *sentence = "$GNGGA,092725.00,4717.11399,N,00833.91986,E,1,08,1.01,499.6,M,48.0,M,,";
    printf("Checksum: %02X\n", nmea_checksum(sentence));
    return 0;
}
    
   
    
     function nmeaChecksum(sentence) {
    // Strip leading $ and everything from * onwards
    sentence = sentence.replace(/^\$/, '').split('*')[0];
    
    let checksum = 0;
    for (let i = 0; i < sentence.length; i++) {
        checksum ^= sentence.charCodeAt(i);
    }
    return checksum.toString(16).toUpperCase().padStart(2, '0');
}

nmeaChecksum("GNGGA,092725.00,4717.11399,N,00833.91986,E,1,08,1.01,499.6,M,48.0,M,,");
// Returns: "4E"
    
   

Got any questions or requests?
Contact us! We'll answer <24 hours!

Icon
Contact ArduSimple
Close
ArduSimple – high precision RTK survey equipment and solutions made simple

Want to learn more about GPS and RTK?

If you are busy right now, our engineers can send you 3 short e-mails, explaining all you need to know to start your project.