In this article I will demonstrate how to collect GPS coordinates directly from the page NMEA (National Marine Electronics Association). This format is a standard used for communicating navigation and position data from GPS systems.

The data sent by the GPS to the serial port usually consists of several sentences (text strings) in NMEA format, with information about the position, time, speed, direction and other parameters. Each NMEA sentence begins with a character “$” and is followed by information that is separated by commas.
Here is an example of a typical NMEA sentence sent by a GPS module:
$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
With the hardware configured, this simple code and a GPS module connected to the ESP32, you will see in the terminal the same content shown in the photo above:
void loop() {
while (Serial2.available()) {
char c = Serial2.read();
Serial.print(c);
}
}
In this tutorial, we will create a Task in FreeRTOS that performs the continuous reading of selected data on this page, allowing its use in any application.
For this, we will use an ESP32 S3 WROOM -1 development module and a Ublox Neo M7 GPS module. However, it works with any ESP32 and GPS module:

- GPS VCC → 3.3V of ESP32-S3
- GPS GND → GND of ESP32-S3
- GPS TX → RX2 (GPIO16) of ESP32-S3
- GPS RX → TX2 (GPIO17) of ESP32-S3
Connecting ESP to Note and Creating the Project in VS Code
After connecting the ESP32 to GPS, you can now connect it to your computer using a cable USB.
📌 Creating the Project in VS Code with PlatformIO
1️⃣ Open VS Code and go to the PlatformIO.
2️⃣ Click on Create New Project.
3️⃣ During setup, select:
- ESP Model you are using.
- Framework: Arduino.
If you are following this tutorial and using the same ESP model, your file platformio.ini it will look like this:
[env:esp32-s3-devkitc-1]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
monitor_speed = 115200
upload_speed = 921600
As mentioned above, the goal of this project is to receive NMEA sentences from a GPS receiver and extract the latitude, longitude, altitude and other relevant data. The coordinates are stored in a data structure and constantly updated, with the possibility of using them in other functions of the system.
Project Structure
- Collection of GPS NMEA sentences.
- Processing each sentence to extract data such as latitude, longitude, speed, etc.
- Validation of sentences using checksum verification.
- Using FreeRTOS to ensure that data collection is done continuously, without blocking code execution.
1. Initially we will declare the functions that will be used in the project.
#include <Arduino.h>
#define NUM_SENTENCAS 3
// --- Function Declarations ---
String seqRetData(String *SEQ, uint8_t pos); // Returns the isolated data from the sequence based on the requested position
bool gpsSetData(); // GPS Sets GPS data with processed sentences
void gpsTask(void *pvParameters); // Task function to continuously collect GPS sentences
bool nmea0183_checksum(const char *nmea_data); // Verifies if the checksum of an NMEA0183 sentence is correct
// -----------------------------
2. Declaring Variables and Structures
Next, we declare a structure Datagps
which will store the collected data, such as latitude, longitude, number of satellites, and other GPS parameters. Below is the definition of this structure:
// --- GPS Data Structure ---
typedef struct
{
float lat;
float lon;
uint8_t fix;
uint8_t sat;
float hdop;
float alt;
float vel;
float bea;
char hor[7];
char date[7];
char isfix[2];
bool isnew = false;
bool isvalid = false;
} Datagps;
3. Global Variables
Here we have variables that store the data of the GPS sentences and the structure Datagps
which keeps the data updated. Note that it is in the “sentence” variable that we declare which sentences on the NMEA page we are going to extract the information from.
// --- Global Variables ---
Datagps gps; // GPS Data
String sentenca[NUM_SENTENCAS] = {"$GPGGA", "$GPVTG", "$GPRMC"};
String sentData[NUM_SENTENCAS]; // Data for each sentence
bool sentencaValida[NUM_SENTENCAS] = {false, false, false}; // Flag to validate if the sentence was received and is correct
// --------------------------------------------------------------
4. Initial Setup Configuration do Setup()
The function setup()
is responsible for initializing the system, configuring serial communication and creating the task FreeRTOS to continuously read GPS sentences.
void setup() {
Serial.begin(115200);
delay(100);
Serial2.begin(9600, SERIAL_8N1, 16, 17);
delay(100);
Serial.println("NMEA GPS Sentence Example v1.0");
xTaskCreate(gpsTask, "GPS Task", 2048, NULL, 1, NULL); // Adjust stack size as needed
// Set to get new data
gps.isnew = false;
gps.isvalid = false;
}
Here, we configure serial communication for both the ESP32 debug and the GPS receiver connected to the Serial2 pin.
5. Main Loop
Inside the function loop()
, we call the function gpsSetData()
which is responsible for checking for new data and storing it in the structure Datagps
.
void loop() {
// Get GPS data from NMEA sentences
gpsSetData();
delay(1000);
}
6. FreeRTOS Task
The function gpsTask()
is a task created by FreeRTOS to continuously collect GPS data without blocking the main program flow. In it, we monitor the serial communication, read and isolate the data sent by the GPS, and check if the received sentence is valid using the function checksum.
Remembering that at the beginning of the code we selected the sentences that we want to isolate on the NMEA page by declaring the following variable:
String sentenca[NUM_SENTENCAS] = {"$GPGGA", "$GPVTG", "$GPRMC"};
FreeRtos Task Function:
// --- Task function to continuously collect GPS sentences ---
void gpsTask(void *pvParameters) {
char buffer[512]; // Buffer for serial reading
int bufferIndex = 0; // Buffer index
String tempBuffer = ""; // Accumulator for serial data
String currentSentence = ""; // Current sentence being read
// Initialize with sentences not validated
for (int i = 0; i < 3; i++) {
sentencaValida[i] = false;
}
while (true) {
// If there is data available on the serial
if (Serial2.available() > 0) {
// Continuous reading of serial data
while (Serial2.available()) {
char incomingByte = Serial2.read(); // Reads the next byte
tempBuffer += incomingByte; // Accumulates the read bytes
// If we find the end of a sentence, like '\n'
if (incomingByte == '\n') {
// Serial.println(tempBuffer);
// For each requested sentence, check if it was found in the buffer
for (int i = 0; i < 3; i++) {
// Check if the sentence was found
if (tempBuffer.startsWith(sentenca[i]) && !sentencaValida[i]) {
// Stores the validated sentence
if (nmea0183_checksum(tempBuffer.c_str())) {
sentData[i] = tempBuffer; // Stores the complete sentence
sentencaValida[i] = true; // Marks as valid
Serial.print("Sentença " + sentenca[i] + " válida recebida: " + tempBuffer);
} else {
Serial.println("Erro de checksum na sentença: " + sentenca[i]);
}
}
}
tempBuffer = ""; // Clears the buffer after processing
}
}
}
// Check if all sentences were received
bool allValid = true;
for (int i = 0; i < 3; i++) {
if (!sentencaValida[i]) {
allValid = false;
break;
}
}
// If all sentences are valid, restart reading
if (allValid) {
// Serial.println("Todas as sentenças válidas foram recebidas!");
gps.isnew = true;
// Reset variables if necessary
for (int i = 0; i < 3; i++) {
sentencaValida[i] = false;
}
}
vTaskDelay(100 / portTICK_PERIOD_MS); // Delay to avoid processor overload
}
} // --- END Task function to continuously collect GPS sentences ---
Explanation of Logic
- The buffer stores the received GPS data.
- Each time a sentence ends (detected by
'\n'
), it is validated by checksum. - If the sentence is valid, it is stored in the variable
sentData
.
7. Checksum verification, validity of the collected sentence
Before processing any data, we ensure that the received sentence is valid by checking the checksum. The function nmea0183_checksum()
calculates the checksum and compares it with the value sent by the sentence to ensure data integrity.
// --- Verifies if the checksum of an NMEA0183 sentence is correct ---
bool nmea0183_checksum(const char *nmea_data) {
uint8_t crc = 0;
// Ignores the '$' at the beginning and calculates the checksum
int i = 1;
for (i = 1; nmea_data[i] != '*' && nmea_data[i] != '\0'; i++) {
crc ^= (uint8_t)nmea_data[i];
}
// Gets the checksum value of the sentence after the '*' character
if (nmea_data[i] != '*') {return false;}
int checksum = strtol(&nmea_data[i + 1], NULL, 16);
// Verifies if the calculated checksum matches the sentence checksum
return crc == checksum;
} // --- END Verifies if the checksum of an NMEA0183 sentence is correct ---
8. Processing of Received Data
The function gpsSetData()
processes the received sentences, extracting data on latitude, longitude, altitude, among others. The function seqRetData()
is used to isolate the desired fields of each sentence.
/ --- Sets GPS data with processed sentences ---
bool gpsSetData()
{
// Verify if new GPS data is available
if (!gps.isnew) {return false;}
gps.isnew = false;
String dat;
float val;
/// GGA Sequence-------------------------------------
if (sentData[0] == "") {return false;}
// Checks if it is fixed (6th position)
dat = seqRetData(&sentData[0], 6);
gps.fix = dat.toInt();
// Gets Latitude
dat = seqRetData(&sentData[0], 2);
val = dat.toFloat();
dat = seqRetData(&sentData[0], 3);
if(dat=="S") {val = val*(-1);}
gps.lat = val*0.01;
// Gets Longitude
dat = seqRetData(&sentData[0], 4);
val = dat.toFloat();
dat = seqRetData(&sentData[0], 5);
if(dat=="W") {val = val*(-1);}
gps.lon = val*0.01;
// Gets Altitude
dat = seqRetData(&sentData[0], 9);
gps.alt = dat.toFloat();
// Gets number of satellites
dat = seqRetData(&sentData[0], 7);
gps.sat = dat.toInt();
// Gets HDOP
dat = seqRetData(&sentData[0], 8);
gps.hdop = dat.toFloat();
// VTG Sequence-------------------------------------
// Gets speed in km/h
dat = seqRetData(&sentData[1], 7);
gps.vel = dat.toFloat();
// RMC Sequence-------------------------------------
// Gets UTC time
dat = seqRetData(&sentData[2], 1);
dat.toCharArray(gps.hor, sizeof(gps.hor));
// Gets date
dat = seqRetData(&sentData[2], 9);
dat.toCharArray(gps.date, sizeof(gps.date));
// Gets if it is fixed
gps.isfix[0] = (seqRetData(&sentData[2], 2) == "A") ? 'S' : 'N';
// Gets bearing (direction in degrees)
dat = seqRetData(&sentData[2], 8);
gps.bea = dat.toFloat();
// Verifies if the coordinate is valid based on fix, HDOP, and number of satellites
if (gps.isfix[0] == 'S' && gps.hdop <= 3 && gps.sat >= 4) {gps.isvalid = true;} else {gps.isvalid = false;}
// Prints collected data
Serial.println("------------------------------");
Serial.print("Lat: ");
Serial.print(gps.lat,6);
Serial.print(" Lon: ");
Serial.println(gps.lon,6);
Serial.print("Alt: ");
Serial.println(gps.alt,1);
Serial.print("Fix: ");
Serial.println(gps.fix);
Serial.print("Sat: ");
Serial.println(gps.sat);
Serial.print("Hdop: ");
Serial.println(gps.hdop);
Serial.print("isFix: ");
Serial.println(gps.isfix);
Serial.print("isValid: ");
Serial.println(gps.isvalid ? 'S' : 'N');
Serial.print("Bea: ");
Serial.println(gps.bea);
Serial.print("Vel: ");
Serial.println(gps.vel);
Serial.print("Hor: ");
Serial.println(gps.hor);
Serial.print("Date: ");
Serial.println(gps.date);
Serial.println("------------------------------");
return true;
} // --- END Sets GPS data with processed sentences ---
Here, we check and extract data from sentences, performing coordinate and value conversions.
9. Function to isolate data from a sentence
The function seqRetData()
is responsible for isolating data from a NMEA sentence based on the position of the desired data.
// --- Returns the isolated data from the sequence based on the requested position ---
String seqRetData(String *SEQ, uint8_t pos)
{
String res = "", str = *SEQ;
uint8_t i, ini = 0;
uint8_t fim = str.length();
// Counts the commas in the sequence until the desired position is found
for (i = 0;i < pos;i++)
{
ini = str.indexOf(',', ini+1);
}
// Positioned, isolates the byte
ini++; // Advances one position to get the data
while (ini <= fim)
{
if (str[ini] == ',') {break;}
res += str[ini];
ini++;
}
return res;
} // --- END Returns the isolated data from the sequence based on the requested position ---
This function iterates through the sentence, counting commas to find the desired value in the correct position.
10. Conclusion
This project uses the protocol NMEA0183 to collect GPS data, process it and validate it in real time using FreeRTOS. With the structure Datagps
, we can store data in an organized way and access it for other functions.
You can expand this project to store the coordinates on an SD card, send them via Bluetooth, or even display them on a display.