Interpretare le stringhe NMEA-0183 dei navigatori GPS. TEA: un semplice ma efficace algoritmo di encryption. Un orologio/datario con Atmel AT89C4051.
Navigazione GPS: interpretare le stringe NMEA-0183
Un ricevitore GPS può essere facilmente interfacciato con qualsiasi microcontrollore attraverso l’UART. La lettura delle stringhe inviate dal ricevitore e basate sul protocollo NMEA 0183 è invece una cosa leggermente più complicata soprattutto se non è noto il formato dei dati. Il listato 1 riporta alcune routine per PIC (i sorgenti sono in C per il compilatore Hitech) utili per la lettura delle stringhe NMEA 0183 ed il calcolo di distanze e molti altri parametri di navigazione. Il listato 2 riporta integralmente il file .h contenente le dichiarazioni delle funzioni.
#include <pic18.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "nmea.h"
#ifndef M_PI
#define M_PI ((double) 3.14159265358979)
#endif
void nmea_gprmc(_const char *line);
void nmea_gpgga(_const char *line);
struct nmea_gps_data gps_data;
char nmea_handle_line(_const char *line)
{
if(line[0] == '$' && line[1] == 'G' && line[2] == 'P')
{
if(line[3] == 'R' && line[4] == 'M' && line[5] == 'C') // GPRMC
{
nmea_gprmc(line);
return NMEA_GPRMC;
}
else
;
}
return 0;
}
//esempio:
// $GPRMC,142455,V,3900.0000,N,09500,0000,W,000.0,000.0,010105,003.8,E*78
// 0000000000111111111122222222223333333333444444444455555555556666666666
// 0123456789012345678901234567890123456789012345678901234567890123456789
extern bit active;
void nmea_gprmc(_const char *line)
{ memcpy(gps_data.time, line+7, sizeof(gps_data.time));
memcpy(gps_data.date, line+53, sizeof(gps_data.date));
memcpy(gps_data.latitude, line+16, sizeof(gps_data.latitude));
memcpy(gps_data.longitude, line+28, sizeof(gps_data.longitude));
memcpy(gps_data.velocity, line+41, sizeof(gps_data.velocity));
memcpy(gps_data.heading, line+47, sizeof(gps_data.heading));
active = (*(line+14)=='A'?1:0);
return;
}
void nmea_copy_to_logdata(struct nmea_gps_logdata *to)
{
memcpy(to->date, gps_data.date, sizeof(to->date));
memcpy(to->time, gps_data.time, sizeof(to->time));
memcpy(to->latitude, gps_data.latitude, sizeof(to->latitude));
memcpy(to->longitude, gps_data.longitude, sizeof(to->longitude));
return;
}
//conversione delle coordinate:
double nmea_coordinate_to_double(_const char *str)
{
unsigned char intpart;
char tmpstr[sizeof(gps_data.longitude) + 1];
if(str[5] == '.') // longitudine
{
memcpy(tmpstr, str+3, sizeof(gps_data.longitude)-3);
tmpstr[sizeof(gps_data.longitude)-3] = '\0';
intpart = (str[0]-'0')*100 + (str[1]-'0')*10 + (str[2]-'0');
}
Else //latitudine
{
memcpy(tmpstr, str+2, sizeof(gps_data.latitude)-2);
tmpstr[sizeof(gps_data.latitude)-2] = '\0';
intpart = (str[0]-'0')*10 + (str[1]-'0');
}
return intpart + atof(tmpstr)/60;
}
double nmea_distance(struct nmea_position *one, struct nmea_position *two)
{
// da longitudine (in gradi) a km:
// cos(latitudine/360 * 2*M_PI) * 40070 / 360 =
// cos((double) 2*M_PI/360 * latitudine) * ((double) 40070/360)
double difflat, difflong;
difflat = (two->latitude - one->latitude) * (double) 111000;
difflong = (two->longitude - one->longitude) * (double) 49142;
return sqrt(difflat*difflat + difflong*difflong);
}
signed short nmea_bearing(struct nmea_position *one, struct nmea_position *two)
{
double difflat, difflong;
double rads;
signed short degs;
difflat = (two->latitude - one->latitude) * (double) 111000; // y
difflong = (two->longitude - one->longitude) * (double) 49142; // x
rads = atan2(difflat, difflong);
rads = -(rads - M_PI/2); // twist and mirror to fit GPS coordinates
degs = (double) 360 * rads / (M_PI*2) -
((gps_data.heading[0]-'0') * 100 + (gps_data.heading[1]-'0') * 10 + (gps_data.heading[2]-'0'));
if(degs > 180)
degs-=360;
else if(degs < -180)
degs+=360;
return degs;
}
| Listato 1 |
#ifndef _NMEA_H
#define _NMEA_H
#define NMEA_GPRMC 1
char nmea_handle_line(_const char *line);
void nmea_copy_to_logdata(struct nmea_gps_logdata *to);
double nmea_coordinate_to_double(_const char *str);
double nmea_distance(struct nmea_position *one, struct nmea_position *two);
signed short nmea_bearing(struct nmea_position *one, struct nmea_position *two);
struct nmea_gps_data
{
char time[6];
char date[6];
char latitude[9];
char longitude[10];
char heading[3];
char velocity[5];
};
struct nmea_gps_logdata
{
char time[6];
char date[6];
char latitude[9];
char longitude[10];
};
struct nmea_position
{
double latitude;
double longitude;
};
#define NMEA_OFFSET 20
#define NMEA_POS_OFFSET(x) ((sizeof(struct nmea_gps_logdata)*x) + NMEA_OFFSET)
extern struct nmea_gps_data gps_data;
#endif
| Listato 2 |





Dalla mia esperienza si possono trovare GPS che ritornano il time con tre valori decimali (GPZ > 1 HZ), è quindi più corretto utilizzare un parser che divida i campi con l’opportuno separatore ‘,’ . Non ho idea se poi questo rientri nello standard o no, ma è comunque una buona prassi.
Anche un bel controllo del CRC finale è una feature da non sottovalutare.