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 🙂
Popular NMEA messages
| Message | Description | Availability |
|---|---|---|
| GGA | Global Positioning System Fix Data — position, altitude, fix quality and number of satellites | All receivers |
| GLL | Geographic Position — latitude and longitude with time and status | All receivers |
| GNS | GNSS Fix Data — similar to GGA but supports multiple constellations (GPS, GLONASS, Galileo...) | All receivers |
| GRS | GNSS Range Residuals — residuals of ranges used in the navigation solution | All receivers |
| GSA | GNSS DOP and Active Satellites — fix type (2D/3D) and satellites used | All receivers |
| GST | GNSS Pseudorange Error Statistics — position error estimates (RMS, latitude, longitude, altitude) | All receivers |
| GSV | GNSS Satellites in View — number, elevation, azimuth and signal strength of visible satellites | All receivers |
| HDT | Heading True — actual heading of the vessel relative to true north | Septentrio Mosaic-H simpleRTK3B Heading |
| INSPVAXA | Sensor Fusion data — integrated position, velocity, attitude and their estimated errors | Unicore UM981 simpleRTK3B Fusion |
| PUBX,00 | Position Data — latitude, longitude, altitude and fix quality (u-blox devices) | All u-blox receivers |
| PUBX,04 | Time of Day — UTC time and clock data (u-blox devices) | All u-blox receivers |
| RMC | Recommended Minimum Specific GNSS Data — position, speed, course and date | All receivers |
| ROT | Rate of Turn — speed of rotation of the vessel in degrees per minute | Septentrio Mosaic-H simpleRTK3B Heading |
| VTG | Course Over Ground and Ground Speed — track and speed in knots and km/h | All receivers |
| ZDA | Time and Date — UTC time, day, month, year and local time zone | All receivers |
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 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
#include
#include
#include
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
#include
#include
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"