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.