1. Formato del mensaje
Los mensajes NMEA (National Marine Electronics Association) son cadenas de texto ASCII que comienzan con $ y terminan con un checksum precedido por *. El formato general es de campos separados por comas. El mensaje $GNGGA es uno de los más comunes y se utiliza para proporcionar datos de posicionamiento fijo, como la hora, latitud, longitud, altitud y calidad de la señal.
2. Desglose de los campos
$GNGGA: Identificador del tipo de trama. Indica que se trata de un mensaje de datos de posicionamiento fijo (Global Positioning System Fix Data).085727.80: Hora UTC en formatoHHMMSS.SS. En este ejemplo, son las 08:57:27.80.3452.56956825,N: Latitud. FormatoDDMM.MMMMMdondeDDson los grados yMM.MMMMMlos minutos.Nindica hemisferio Norte. En este caso, 34° 52.56956825' N.11401.14987635,E: Longitud. FormatoDDDMM.MMMMM.Eindica hemisferio Este. En este caso, 114° 01.14987635' E.1: Indicador de calidad de la posición.0= inválida,1= GPS fijo (SPS),2= DGPS,3= PPS.18: Número de satélites utilizados en el cálculo de la posición.0.9: HDOP (Horizontal Dilution of Precision). Un valor bajo indica una mejor precisión horizontal.67.0944,M: Altitud sobre el nivel del mar, medida en metros (M).-19.0960,M: Altura del geoide (separación entre el elipsoide de referencia y el geoide), en metros (M).\*67: Checksum. Es un valor hexadecimal de dos dígitos que resulta de aplicar la operación XOR a todos los bytes entre$y*(sin incluirlos).
3. Ejemplos de código para extraer campos
A continuación, se presentan fragmentos de código en C para extraer la hora, latitud y longitud de una cadena $GNGGA.
Extracción de la hora UTC
char tiempo_str[] = "085727.80";
// Usamos strtol para mejor control de errores
char *fin_hora;
long hora_completa = strtol(tiempo_str, &fin_hora, 10);
int horas = (int)(hora_completa / 10000);
int minutos = (int)((hora_completa % 10000) / 100);
float segundos = (float)(hora_completa % 100);
// horas = 8, minutos = 57, segundos = 27.80
Extracción de la latitud
char lat_str[] = "3452.56956825";
char *fin_lat;
float latitud_decimal = strtof(lat_str, &fin_lat) / 100.0; // Divide para separar grados y minutos
int grados_lat = (int)latitud_decimal;
float minutos_lat = (latitud_decimal - grados_lat) * 60.0; // Los minutos originales se recuperan
// grados_lat = 34, minutos_lat = 52.56956825
Extracción de la longitud
char lon_str[] = "11401.14987635";
char *fin_lon;
float longitud_decimal = strtof(lon_str, &fin_lon) / 100.0;
int grados_lon = (int)longitud_decimal;
float minutos_lon = (longitud_decimal - grados_lon) * 60.0;
// grados_lon = 114, minutos_lon = 1.14987635
4. Implementación completa para STM32 (código de ejemplo)
El siguiente código muestra una implementación completa para recibir y enalizar tramas $GNGGA mediante interrupciones UART en un microcontrolador STM32F103. Incluye la inicialización del puerto serie, el cálculo y verificación del checksum, y el parseo de los campos principales.
#include "stm32f10x.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define BUFFER_TAM 256
uint8_t buffer_rx[BUFFER_TAM];
uint16_t indice_rx = 0;
void Inicializar_USART1(void) {
GPIO_InitTypeDef gpio;
USART_InitTypeDef usart;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// TX (PA9) como salida push-pull alternada
gpio.GPIO_Pin = GPIO_Pin_9;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
gpio.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &gpio);
// RX (PA10) como entrada flotante
gpio.GPIO_Pin = GPIO_Pin_10;
gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &gpio);
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &usart);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Cmd(USART1, ENABLE);
}
uint8_t CalcularChecksum(const char *trama) {
uint8_t checksum = 0;
int i = 1; // Omitir el '$'
while (trama[i] != '\0' && trama[i] != '*') {
checksum ^= trama[i];
i++;
}
return checksum;
}
int VerificarChecksum(const char *trama) {
int len = strlen(trama);
if (len < 6) return 0; // Formato mínimo: $...*XX
char str_checksum[3];
strncpy(str_checksum, trama + len - 2, 2);
str_checksum[2] = '\0';
uint8_t checksum_recibido = (uint8_t)strtoul(str_checksum, NULL, 16);
uint8_t checksum_calculado = CalcularChecksum(trama);
return checksum_recibido == checksum_calculado;
}
void AnalizarGNGGA(const char *trama) {
char *token;
// saltar "$GNGGA"
token = strtok((char*)trama, ",");
token = strtok(NULL, ","); // Hora
if (token) {
int h = atoi(token) / 10000;
int m = (atoi(token) % 10000) / 100;
int s = atoi(token) % 100;
// Usar hora...
}
token = strtok(NULL, ","); // Latitud
if (token) {
float lat = atof(token) / 100.0;
int grados = (int)lat;
float minutos = (lat - grados) * 60.0;
// Almacenar...
}
token = strtok(NULL, ","); // Dirección latitud (N/S)
if (token) {
char dir = token[0];
// Determinar hemisferio...
}
token = strtok(NULL, ","); // Longitud
if (token) {
float lon = atof(token) / 100.0;
int grados = (int)lon;
float minutos = (lon - grados) * 60.0;
}
token = strtok(NULL, ","); // Dirección longitud (E/W)
token = strtok(NULL, ","); // Calidad
if (token) {
int calidad = atoi(token);
}
token = strtok(NULL, ","); // Satélites
if (token) {
int satelites = atoi(token);
}
token = strtok(NULL, ","); // HDOP
if (token) {
float hdop = atof(token);
}
token = strtok(NULL, ","); // Altitud
if (token) {
float altitud = atof(token);
}
// Continuar con otros campos...
}
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_RXNE)) {
buffer_rx[indice_rx++] = USART_ReceiveData(USART1);
if (indice_rx >= BUFFER_TAM) indice_rx = 0;
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
int main(void) {
SystemInit();
Inicializar_USART1();
NVIC_InitTypeDef nvic;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
nvic.NVIC_IRQChannel = USART1_IRQn;
nvic.NVIC_IRQChannelPreemptionPriority = 0;
nvic.NVIC_IRQChannelSubPriority = 0;
nvic.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic);
while (1) {
// Buscar una trama $GNGGA completa en el buffer
char *inicio = strstr((char*)buffer_rx, "$GNGGA");
char *fin = NULL;
if (inicio) {
fin = strchr(inicio, '\r');
if (!fin) fin = strchr(inicio, '\n');
if (fin) {
int longitud = fin - inicio + 1;
char trama[longitud];
strncpy(trama, inicio, longitud);
trama[longitud - 1] = '\0';
if (VerificarChecksum(trama)) {
AnalizarGNGGA(trama);
} else {
printf("Checksum incorrecto\n");
indice_rx = 0;
}
}
}
}
}
Consideraciones importantes
- Prioridad de interrupción: Ajuste la prioridad de la interrupción UART según las necesidades de su sistema para evitar conflictos.
- Tamaño del buffer: Aumente el tamaño del buffer si la tasa de datos es alta o si el procesamiento en el bucle principal es lento.
- Detección del final de trama: El código asume que la trama termina con
\ro\n. Algunos dispositivos pueden usar solo\nu otras marcas. Adapte la lógica según su fuente de datos.