O protocolo NMEA 0183 (nmea.org) é o protocolo utilizado pela maioria dos aparelhos de GPS tanto os de uso comercial (ex. os da Garmin) como os módulos utilizados para desenvolvimento. O protocolo é baseando em ASCII e transmitido serialmente, normalmente via RS232. A comunicação é feita através de mensagens, e estas mensagens seguem as normas estabelecidas pela NMEA são algumas delas:

  • O caractere inicial de cada mensagem deve ser um cifrão “$”
  • As sentenças da mensagem são separadas por virgulas “,”
  • Logo após o cifrão 2 letras identificam o dispositivo que está enviando a mensagem e 3 o tipo de mensagem
  • Apos o ultimo capo deve ter um asterisco “*” seguido do checksum de 2 caracteres em hexadecimal
  • O checksum consiste em um XOR (exclusive OR – OU exclusivo) de toda a mensagem desde o “$” até o “*”

Exemplo:

  $GPGLL,4916.45,N,12311.12,W,225444,A,*1D

Onde:
     GP           Mensagem enviada por um dispositivo GPS
     GLL          Posição geográfica, latitude e longitude
     4916.46,N    Latitude 49 graus 16,45 minutos Norte
     12311.12,W   Longitude 123 graus 11.12 minutos Oeste
     225444       Fixo as 22:54:44 UTC
     A            Dados Active(ativo) ou Void(sem nada)
     *1D          checksum

Como estes dados estão em formato ASCII e eles não tem um significado útil para cálculos até serem interpretados, separados e convertidos tendo em vista isto proponho aqui um algumas funções independentes de bibliotecas, isto é pode ser usada em qualquer plataforma, para interpretar as mensagens separá-las e converter as mesmas para valores operáveis. O código pode ser alimentado com mais definições, possibilitando a interpretação de mais tipos de mensagens. Inicialmente escrevi o suporte para as mensagens do tipo GGA que contém informações da posição e dos satélites. Como nem todos os microcontroladores suportam variáveis do tipo float, o código foi escrito usando somente variáveis inteiras, porem como visto acima na mensagem de exemplo, podemos receber dados com decimais; para contornar este problema o código utiliza uma matriz de 30 por 3 onde a primeira coluna guarda o valor inteiro, a segunda coluna o valor decimal e a terceira coluna o expoente em módulo do valor decimal. (É possível utilizar notação cientifica para os números, porem, armazená-los em uma única variável pode se tornar um problema para processadores de 8 e 15 bits)

Exemplo:

30,00124 seria,

Parte inteira

Parte decimal

Expoente

30

124

5

transformando isso para float seria

x = 30+\left ( 124E^{-5} \right ) ou

x = 30+\left (\frac{124}{100^{5}} \right )

Resumindo a funcionalidade do programa: Passando a sentença ele interpreta os valores numéricos e os armazena sequencialmente em uma matriz como explicado acima, caso o “valor” seja uma letra, como por exemplo N, S, E, W, ele mantem o valor da letra. Como a função adiciona os valores sequencialmente na matriz basta criar definições para ler as informações como no exemplo abaixo que acompanha o código:

Observação: O primeiro valor da matriz é o tipo de sentença(GGA, GSV, GLL…) indicada pela soma do valor ASCII das três letras.

#define USEFLOAT 1 // Como a plataforma onde estou rodando o código pode ou não trabalhar com float, criei um define para o compilador compilar o trecho de código compatível com a plataforma.
 
#define GPRMC 'R'+'M'+'C'
#define GPRMB 'R'+'M'+'B'
#define GPGGA 'G'+'G'+'A'
#define GPVTG 'V'+'T'+'G'
#define GPRMA 'R'+'M'+'A'
#define GPGSV 'G'+'S'+'V'
#define GPGSA 'G'+'S'+'A'
 
#define GGA_HORA (int)gps_data[1][0]/10000 // Hora UTC informada pelo sistema de GPS
#define GGA_MIN (int)(gps_data[1][0]/100)%100 // Minuto UTC informada pelo sistema de GPS
#define GGA_SEG (int)gps_data[1][0]%100 // Segundo UTC informada pelo sistema de GPS
#define GGA_LATG (int)gps_data[2][0]/100    // Graus da latitude
#define GGA_LATM (int)(gps_data[2][0]%100)  // Minutos da latitude
#if USEFLOAT == 1
#define GGA_LATS ((float)gps_data[2][1]/pot(gps_data[2][2]))*60.00 // Segundos da latitude(COM ponto flutuante)
#else
#define GGA_LATS ((int)gps_data[2][1]*60)/pot(gps_data[2][2]) // Segundos da latitude(SEM ponto flutuante)
#endif
#define GGA_LATD (char)gps_data[3][0]   // Direção da latitude (N, S)
#define GGA_LONG (int)gps_data[4][0]/100 // Graus da longitude
#define GGA_LONM (int)gps_data[4][0]%100 // Minutos da longitude
#if USEFLOAT == 1
#define GGA_LONS ((float)gps_data[4][1]/pot(gps_data[4][2]))*60.00 // Segundos da longitude(COM ponto flutuante)
#else
#define GGA_LONS ((int)gps_data[4][1]*60)/pot(gps_data[4][2]) // Segundos da longitude(SEM ponto flutuante)
#endif
#define GGA_LOND (char)gps_data[5][0] // Direção da latitude (E, W)
#define GGA_QLTY gps_data[6][0] // Qualidade da aquisição (0=Inválido, 1=GPS fixo, 2=DGPS fixo)
#define GGA_NSAT gps_data[7][0] // Número de satélites em vista pelo GPS
#if USEFLOAT == 1
#define GGA_HDOP (float)gps_data[8][0]+((float)gps_data[8][1]/pot(gps_data[8][2]))  // Precisão relativa da posição horizontal
#define GGA_ALT (float)gps_data[9][0]+((float)gps_data[9][1]/pot(gps_data[9][2]))   // Altitude em relação ao mar
#define GGA_ALTU (char)gps_data[10][0]  // Unidade de medida da altitude
#define GGA_EG  (float)gps_data[11][0]+((float)gps_data[11][1]/pot(gps_data[11][2])) // Altura da geoide acima do WGS84
#define GGA_EGU  (char)gps_data[12][0]  // Unidade de medida
#endif

Download do código:

NMEA 0183