Come utilizzare un ricevitore gps con Arduino
Il ricevitore GPS (Global Positioning System) è oggi un dispositivo alla portata di tutti, dal costo contenuto e dal semplice utilizzo. Questo articolo vuole essere una guida introduttiva all’interpretazione dei dati di posizione forniti dal ricevitore gps, senza utilizzare librerie esterne. Ho utilizzato una scheda Arduino/Genuino UNO e il modulo Adafruit Ultimate GPS Breakout v3 (vedi l’articolo su Amazon).
Il modulo ha una sensibilità di ben -165dBm (in tracking), questo significa che riesce a rilevare segnali di 1.25nVolt, ha un refresh di 10 Hz e permette di ‘inseguire’ 22 satelliti.
Questo dispositivo rende semplice la gestione del tracking dei satelliti ed del calcolo della posizione. I dati vengono restituiti sul canale seriale TTL, alla frequenza di 1Hz. E’ sufficiente collegarsi alla seriale per poter leggere i dati. I dati principali sono quelli di posizione (espressa in Latitudine e Longitudine) la quota e l’orario zulu ma sono presenti anche altre informazioni relative ai satelliti utilizzati per il fix.
Lo schema elettrico seguente mostra i collegamenti da effettuare tra una UNO e la Adafruit Ultimate GPS Breakout v3:
Questo schema serve per inviare direttamente sul Serial Monitor i dati restituiti dal modulo gps. Il segnale trasmesso dal dispositivo viene convogliato sulla linea di trasmissione seriale della UNO affinché possa raggiungere il pc attraverso la porta usb. Carichiamo sulla UNO uno sketch vuoto:
void setup() { // put your setup code here, to run once: } void loop() { // put your main code here, to run repeatedly: }
Terminato l’upload del programma lanciamo il Serial Monitor e settiamo il bps a 9600. La schermata seguente illustra le stringhe generate dal ricevitore GPS.
Nel mio caso il sensore gps non aveva ancora agganciato i satelliti, i dati ricevuti indicano una latitudine e una longitudine pari a zero. Il led posto sul pcb indica quando avviene il fix dei satelliti, se il led lampeggia ogni secondo il gps sta cercando di agganciare i segnali dei satelliti, quando vengono agganciati il led lampeggia ogni 15 secondi. Il fix dei satelliti è funzione della posizione dell’antenna del sensore, in campo aperto, senza nessun ostacolo, avviene entro il minuto mentre se ci troviamo in posizioni a bassa copertura (ad esempio dentro casa) il sensore impiegherà molto più tempo e nei casi peggiori non riuscirà ad elaborare il segnale.
Per interpretare i dati è necessario conoscere quali informazioni vengono prodotte dal ricevitore gps. Sul Serial Monitor notiamo quattro righe con informazioni differenti che si ripetono ogni secondo. Queste stringhe iniziano tutte con il simbolo del dollaro e con un codice di cinque caratteri. Il codice specifica quali dati sono presenti nella relativa stringa, in ogni stringa i dati vengono separati dalle virgole (campi), ulteriori informazioni possono essere apprese in seguendo questo link.
Le quattro stringhe sono riassunte nel box seguente:
$GPGGA,235951.800,,,,,0,00,,,M,,M,,*79 $GPGSA,A,1,,,,,,,,,,,,,,,*1E $GPRMC,235951.800,V,,,,,0.00,0.00,050180,,,N*40 $GPVTG,0.00,T,,M,0.00,N,0.00,K,N*32
La stringa che inizia con $GPGGA (Global Positioning System Fix Data), contiene le informazioni della latitudine, della longitudine, della quota e del tempo.
La stringa che inizia con $GPGSA, contiene dati relativi al DOP (Diluizione della precisione) e al prn (pseudo random noise) dei satelliti utilizzati per il fix.
La stringa che inizia con $GPRMC indica il valore minimo raccomandato dei dati di tempo, velocità e posizione.
La stringa che inizia con $GPVTG indica il Track made good e il valore di Groud Speed.
La stringa che ci interessa maggiormente è quella che inizia con $GPGGA perché contiene i dati di posizione ed il tempo. Il codice che andremo a scrivere recupererà i dati separandoli dalle virgole.
Modifichiamo il circuito precedente perché ci serve sia la seriale hardware sia quella software. La seriale software la useremmo per ricevere i dati dal sensore gps mentre la seriale hardware la useremmo per inviare i dati al pc:
Verifichiamo con questo codice che i dati ricevuti dalla seriale software (attivata sul pi 2 e 3) vengano ritrasmessi al pc impiegando la seriale hardware:
#include <SoftwareSerial.h> //pin 2 RX e pin 3 TX SoftwareSerial mySerial(2, 3); void setup() { //init seriale hardware Serial.begin(9600); //init seriale software mySerial.begin(9600); } void loop() { //controllo se nella seriale software ci sono dati if (mySerial.available()) { //se sono presenti dati leggili ed inviali //alla seriale hardware Serial.write(mySerial.read()); } }
Con questo codice otterremmo sul Serial Monitor gli stessi dati ottenuti col primo esempio.
Naturalmente se è avvenuto il fix del segnale, i campi delle stringhe viste in precedenza conterranno i dati di posizione e orario, inoltre ne otterremmo anche una quinta che inizierà per $GPGSV (indica nel dettaglio i parametri dei satelliti agganciati), quindi il risultato sarà qualcosa del genere:
$GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74
$GPGSV,3,2,11,14,25,170,00,16,57,208,39,18,67,296,40,19,40,246,00*74
$GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D
$GPGGA,235317.000,4003.9039,N,10512.5793,W,1,08,1.6,1577.9,M,-20.7,M,,0000*5F
$GPGSA,A,3,19,28,14,18,27,22,31,39,,,,,1.7,1.0,1.3*35
$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68
$GPVTG,360.0,T,348.7,M,000.0,N,000.0,K*43
Ora cercherò di isolare la stringa che inizia con $GPGGA. Per prima cosa ho modificato il codice per concatenare i caratteri ricevuti sulla seriale software in un oggetto stringa in questo modo potrò elaborarla usando i metodi della classe String:
#include <SoftwareSerial.h> //pin 2 RX e pin 3 TX SoftwareSerial mySerial(2, 3); String NMEA; void setup() { //init seriale hardware Serial.begin(9600); //init seriale software mySerial.begin(9600); } void loop() { if (mySerial.available()) { //leggo la prima riga char c = mySerial.read(); if (c != '\n') { NMEA += c; } else { Serial.println(NMEA); //resetto la stringa //altrimenti accoderei tutti i caratteri ricevuti! NMEA = ""; } } }
Ottenute solo le stringhe ho la possibilità di capire come inizia la stringa, ad esempio il metodo subString restituisce una sotto stringa in funzione degli argomenti passati nei parametri. Il codice seguente invia al Serial Monitor solo le stringhe che iniziano con $GPGGA:
#include <SoftwareSerial.h> //pin 2 RX e pin 3 TX SoftwareSerial mySerial(2, 3); String NMEA; void setup() { //init seriale hardware Serial.begin(9600); //init seriale software mySerial.begin(9600); } void loop() { if (mySerial.available()) { //leggo la prima riga char c = mySerial.read(); if (c != '\n') { NMEA += c; } else { //printo solo la stringa che mi interessa //quella che inizia con GPGGA //substring estrapola dalla stringa usando come parametri //la posizione iniziale e finale in questo caso //parte dalla posizione 0 alla posizone 6 if(NMEA.substring(0,6) == "$GPGGA") Serial.println(NMEA); //resetto la stringa NMEA = ""; } } }
Ho così isolato la stringa che mi interessa maggiormente. Continuo la scrittura del codice con alcune istruzioni che mi permettono di estrapolare i dati separati dalle virgole, quindi utilizzerò il metodo indexOf per passare i parametri al metodo subString:
#include <SoftwareSerial.h> //pin 2 RX e pin 3 TX SoftwareSerial mySerial(2, 3); String NMEA; int posizione_delimitatore = 0; int index = 0; void setup() { //init seriale hardware Serial.begin(9600); //init seriale software mySerial.begin(9600); } void loop() { if (mySerial.available()) { //leggo la prima riga char c = mySerial.read(); if (c != '\n') { NMEA += c; } else { posizione_delimitatore = 0; //indice di partenza zero index = 0; //printo solo la stringa che mi interessa if (NMEA.substring(0, 6) == "$GPGGA") { //Serial.println(NMEA); //fino a che il metodo indexOf non restituisce -1 //elabora la stringa while (posizione_delimitatore != -1) { //ottieni la posizione della prima virgola posizione_delimitatore = NMEA.indexOf(",", index); //invia al Serial Monitor la sotto stringa usando come indice //il dato ottenuto da indexOf Serial.println(NMEA.substring(index, posizione_delimitatore)); //memorizza il nuovo indice di partenza index = posizione_delimitatore; //incrementa l'indice di 1 per escludere la //virgola index++; } } //resetta la stringa NMEA = ""; } } }
Il risultato sarà qualcosa del genere:
$GPGGA,152454.000,4003.1968,N,10512.5793,E,1,05,1.56,890.9,M,48.0,M,,*6A $GPGGA 152454.000 4003.1968 N 10512.5793 E 1 05 1.56 890.9 M 48.0 M *6A
Il codice suddivide correttamente tutti i campi. Molti di questi dati possono essere ulteriormente filtrati, ad esempio a me interessa ottenere il dato di tempo, la latitudine, la longitudine e la quota. Il codice seguente invia al Serial Monitor solo questi campi:
#include <SoftwareSerial.h> //pin 2 RX e pin 3 TX SoftwareSerial mySerial(2, 3); String NMEA; int posizione_delimitatore = 0; int index = 0; int campo = 0; void setup() { //init seriale hardware Serial.begin(9600); //init seriale software mySerial.begin(9600); } void loop() { if (mySerial.available()) { //leggo la prima riga char c = mySerial.read(); if (c != '\n') { NMEA += c; } else { posizione_delimitatore = 0; index = 0; //questa variabile viene usata per contare tutti i campi trovati campo = 0; //printo solo la stringa che mi interessa if (NMEA.substring(0, 6) == "$GPGGA") { //Serial.println(NMEA); while (posizione_delimitatore != -1) { posizione_delimitatore = NMEA.indexOf(",", index); //in funzione del campo invio al serial monitor //solo i dati che mi interessano switch (campo) { case 2: case 4: case 9: Serial.println(NMEA.substring(index, posizione_delimitatore)); break; } index = posizione_delimitatore; index++; //incrementa il campo trovato campo++; } } NMEA = ""; } } }
Abbiamo cosi ottenuto il recupero dei dati di posizione e tempo senza utilizzare una libreria esterna. Le librerie rendono tutto più semplice e veloce ma credo che sia altrettanto importante capire cosa avviene dietro le quinte.
I dati ottenuti sono pronti per l’utilizzo in tutti quei progetti cui è necessario recuperare la posizione geografica e il tempo corrente.
Related posts
3 Comments
Lascia un commento Annulla risposta
Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.
Articoli popolari
Sorry. No data so far.
.Net micro framework Arduino Arduino Webserver Domotica Flyport I2C IOT Netduino OpenPicus raspberry RTC Speed Test
Finalmente una spiegazione chiara e completa, per leggere ed interpretare correttamente e stringhe, senza usare librerie esterne! Grazie!
Buongiorno
Ottimo lavoro. Grazie
Ma se quei parametri li volessi salvare su variabili per poi elaborarli altrove nel programma come posso fare.
Grazie ancora
Ciao Luciano,
invece che spedirli sulla seriale basta assegnarli ad una stringa. Ad esempio questa istruzione:
Serial.println(NMEA.substring(index, posizione_delimitatore));
diventa:
String str_rx;
str_rx=NMEA.substring(index, posizione_delimitatore);
Usando la variabile str_rx puoi elaborare i dati come ti pare.
Saluti