Desglosando la trama NMEA 0183 $GNGGA: formato, campos y ejemplos de código

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 formato HHMMSS.SS. En este ejemplo, son las 08:57:27.80.
  • 3452.56956825,N: Latitud. Formato DDMM.MMMMM donde DD son los grados y MM.MMMMM los minutos. N indica hemisferio Norte. En este caso, 34° 52.56956825' N.
  • 11401.14987635,E: Longitud. Formato DDDMM.MMMMM. E indica 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 \r o \n. Algunos dispositivos pueden usar solo \n u otras marcas. Adapte la lógica según su fuente de datos.

Etiquetas: NMEA0183 GNGGA GPS STM32 UART

Publicado el 6-4 03:33