Neste artigo vou demonstrar como coletar coordenadas de GPS direto da página NMEA (National Marine Electronics Association). Esse formato é um padrão utilizado para a comunicação de dados de navegação e posição de sistemas GPS.

Os dados enviados pelo GPS para a porta serial geralmente consistem em várias sentenças (strings de texto) no formato NMEA, com informações sobre a posição, horário, velocidade, direção e outros parâmetros. Cada sentença NMEA começa com um caractere “$” e é seguida por informações que são separadas por vírgulas.
Aqui está um exemplo de uma sentença NMEA típica enviada por um módulo GPS:
$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
Com o hardware configurado, este simples código e um módulo GPS conectado ao ESP32, você verá no terminal o mesmo conteúdo mostrado na foto acima:
void loop() {
while (Serial2.available()) {
char c = Serial2.read();
Serial.print(c);
}
}
Neste tutorial, vamos criar uma Task em FreeRTOS que realiza a leitura contínua de dados selecionados nesta página, permitindo seu uso em qualquer aplicação.
Para isso, vamos utilizar um módulo de desenvolvimento ESP32 S3 WROOM -1 e um módulo GPS da Ublox Neo M7. No entanto funciona com qualquer ESP32 e módulo de GPS:

- VCC do GPS → 3.3V do ESP32-S3
- GND do GPS → GND do ESP32-S3
- TX do GPS → RX2 (GPIO16) do ESP32-S3
- RX do GPS → TX2 (GPIO17) do ESP32-S3
Conectando o ESP ao Note e Criando o Projeto no VS Code
Após conectar o ESP32 ao GPS, você pode agora conectá-lo ao seu computador usando um cabo USB.
📌 Criando o Projeto no VS Code com PlatformIO
1️⃣ Abra o VS Code e vá até o PlatformIO.
2️⃣ Clique em Criar Novo Projeto.
3️⃣ Durante a configuração, selecione:
- Modelo do ESP que está utilizando.
- Framework: Arduino.
Se estiver seguindo este tutorial e usando o mesmo modelo de ESP, seu arquivo platformio.ini ficará assim:
[env:esp32-s3-devkitc-1]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
monitor_speed = 115200
upload_speed = 921600
Conforme mencionado acima o objetivo deste projeto é receber as sentenças NMEA de um receptor GPS e extrair as coordenadas de latitude, longitude, altitude e outros dados relevantes. As coordenadas são armazenadas em uma estrutura de dados e constantemente atualizadas, com a possibilidade de usá-las em outras funções do sistema.
Estrutura do Projeto
- Coleta das sentenças GPS NMEA.
- Processamento de cada sentença para extrair dados como latitude, longitude, velocidade, etc.
- Validação das sentenças usando a verificação de checksum.
- Uso de FreeRTOS para garantir que a coleta de dados seja feita continuamente, sem bloquear a execução do código.
1. Inicialmente vamos declarar as funções que serão utilizadas no projeto
#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. Declaração de Variáveis e Estruturas
Na sequência, declaramos uma estrutura Datagps
que armazenará os dados coletados, como latitude, longitude, número de satélites, e outros parâmetros do GPS. Abaixo está a definição dessa estrutura:
// --- 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. Variáveis Globais
Aqui, temos variáveis que armazenam os dados das sentenças GPS e da estrutura Datagps
que mantém os dados atualizados. Observe que é na variável “sentenca” que declaramos de quais sentenças da página NMEA vamos retirar as informações.
// --- 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. Configuração Inicial do Setup()
A função setup()
é responsável pela inicialização do sistema, configuração da comunicação serial e pela criação da tarefa FreeRTOS para ler continuamente as sentenças GPS.
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;
}
Aqui, configuramos a comunicação serial tanto para o debug do ESP32 quanto para o receptor GPS conectado ao pino Serial2.
5. Loop Principal
Dentro da função loop()
, chamamos a função gpsSetData()
que é responsável por verificar se há novos dados e armazená-los na estrutura Datagps
.
void loop() {
// Get GPS data from NMEA sentences
gpsSetData();
delay(1000);
}
6. Tarefa FreeRTOS
A função gpsTask()
é uma tarefa criada pelo FreeRTOS para continuamente coletar os dados do GPS sem bloquear o fluxo principal do programa. Nela, monitoramos a comunicação serial, lemos e isolamos os dados enviados pelo GPS, e verificamos se a sentença recebida é válida utilizando a função de checksum.
Lembrando que lá no início do código selecionamos as sentenças que queremos isolar na página NMEA através da declaração da seguinte variável:
String sentenca[NUM_SENTENCAS] = {"$GPGGA", "$GPVTG", "$GPRMC"};
Função da Task FreeRtos:
// --- 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 ---
Explicação da Lógica
- O buffer armazena os dados do GPS recebidos.
- Cada vez que uma sentença termina (detectada por
'\n'
), ela é validada pelo checksum. - Se a sentença for válida, ela é armazenada na variável
sentData
.
7. Verificação de Checksum, validade da sentença coletada
Antes de processar qualquer dado, garantimos que a sentença recebida é válida verificando o checksum. A função nmea0183_checksum()
calcula o checksum e o compara com o valor enviado pela sentença para garantir a integridade dos dados.
// --- 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. Processamento de Dados Recebidos
A função gpsSetData()
processa as sentenças recebidas, extraindo dados de latitude, longitude, altitude, entre outros. A função seqRetData()
é utilizada para isolar os campos desejados de cada sentença.
/ --- 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 ---
Aqui, verificamos e extraímos os dados das sentenças, realizando conversões de coordenadas e valores.
9. Função para isolar os dados de uma sentença
A função seqRetData()
é responsável por isolar os dados de uma sentença NMEA com base na posição do dado desejado.
// --- 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 ---
Esta função percorre a sentença, contando as vírgulas para encontrar o valor desejado na posição correta.
10. Conclusão
Este projeto utiliza o protocolo NMEA0183 para coletar dados GPS, processá-los e validá-los em tempo real usando FreeRTOS. Com a estrutura Datagps
, conseguimos armazenar os dados de forma organizada e acessá-los para outras funcionalidades.
Você pode expandir esse projeto para armazenar as coordenadas em um cartão SD, enviá-las por Bluetooth ou até exibi-las em um display.