Arduino in GPS (Global Positioning System)

Z uporabo GPS modula bomo ugotovili našo lokacijo, izdelali pa bomo tudi sledenje poti, ki jo bomo opravili. Lokacija in prikaz opravljene poti bo prikazana v Google Earth. V ta namen bomo uporabili GPS modul MT3339 BK HPR in razvojno ploščo Arduino Uno.

Slika 1: GPS modul MT3339 (vir: aliexpress.com)

GPS modul deluje po protokolu NMEA 0183, komunicira s hitrostjo 4800 do 115200 b/s, lokacijo posodablja s frekvenco 1 do 10 Hz, deluje pri napetosti 5 V in porabi cca 20 mA. Ostale karakteristične podatke najdemo na spletni strani.

Od sedmih priključkov modula GPS bomo uporabili štiri in jih povezali z razvojno ploščo Arduino Uno:

Standard NMEA 0183 je razvilo društvo National Marine Electronics Association. Namen standarda NMEA je, da pošilja vrstice podatkov, imenovane stavki. Ti so popolnoma samostojni in neodvisni od drugih stavkov. Vsak stavek se začne z znakom $, ki mu sledi še pet znakov. Prva dva znaka predstavljata tip naprave, naslednji trije pa predstavljajo namen stavka. Mi bomo uporabili dva stavka, $GPRMC in $GPGGA. Znaka GP pomenita kratico za GPS. Podatki v stavku so ločeni z vejicami, s pomočjo katerih lahko razčlenimo stavek in tako pridemo do želenega podatka v stavku. Karakteristične podatke o NMEA stavkih najdete na tej spletni strani.

Stavek $GPRMC (GPS, Recommended Minimum Specific Data). Stavek zajema podatke o poziciji in času:

Primer (signal ni pridoblen): $GPRMC,235955.800,V,,,,,0.00,0.00,050180,,,N*42

Primer (signal pridobljen): $GPRMC,094303.000,A,4623.2377,N,01534.4458,E,0.01,166.38,301215,,,A*60

ID stavka                    $GPRMC

UTC čas                     094303.000        hhmmss.sss             (h => ura, m => minuta, s => sekunda)

Status                        A                      A=Valid, V=Invalid      (A => aktiven, V => neaktiven)

Zemljepisna širina        4623.2377         ddmm.mmmm           (d => stopinja, m => minuta)

N/S indikator               N                      N=North, S=South     (N => sever, S => Jug)

Zemljepisna dolžina     01534.4458        dddmm.mmmm         (d => stopinja, m => minuta)

E/W indikator              E                      E=East, W=West      (E => vzhod, W => zahod)

Hitrost plovila              0.01                   Vozli                         (Navtične milje na uro)

Kot poti                      166.38               Kot poti v stopinjah

UTC datum                 301215              DDMMYY                  (D => dan, M => mesec, Y => leto, 30.dec.2015)

Magnetno odstopanje                           d                               (D => stopinje)

Magnetno odstopanje                           E=East, W=West

Obvezna kontrola         A*60                                                  (A => neodvisno)

Stavek $GPGGA (GPS, Global Positioning System Fix Data). Stavek zajema podatke o položaju s 3D lokacijo:

Primer (signal ni pridoblen): $GPGGA,235953.800,,,,,0,0,,,M,,*4B

Primer (signal pridobljen): $GPGGA,094304.000,4623.2377,N,01534.4458,E,1,9,1.02,276.0,M,43.3,M,,*57

ID stavla                    $GPGGA

UTC čas                    094304.000        hhmmss.sss             (h => ura, m => minuta, s => sekunda)

Zemljepisna širina       4623.2377         ddmm.mmmm           (d => stopinja, m => minuta)

N/S indikator              N                      N=North, S=South     (N => sever, S => Jug)

Zemljepisna dolžina    01534.4458        dddmm.mmmm         (d => stopinja, m => minuta)

E/W indikator             E                      E=East, W=West      (E => vzhod, W => zahod)

Položaj Fix                1                       1=Valid, 0=Invalid       (1 => položaj potrjen, 0 => položej ni potrjen)

Število satelitov          9                       (0d 0 do 12)               (9 => uporabljenih 9 satelitov)

Relativna točnost       1.02                                                   (Relativna točnost v vodoravni legi)

Nadmorska višina       276.0                                                 (Nadmorska višina)

Enota                        M                                                      (M => meter, nadmorska višina v metrih)

WGS 84                    43.3                                                   (Standard za kartografijo, geodezijo in navigacijo)

Enota                        M                                                      (M => meter)

Starost podatkov                                                                 (Starost podatkov v sekundah)

Obvezna kontrola       *57                                                     (Preverjanje napak prenosa)

Napišimo sedaj program, ki nam bo na serijski monitor izpisal zgoraj opisana stavka. Modul GPS priključimo na Arduino Uno, kot je opisano zgoraj v tabeli. Najprej bomo preizkusili delovanje v prostoru, kjer modul GPS ne bo povezan s sateliti, signal ne bo pridobljen.

Z vklučitvijo knjižnice SoftwareSerial.h bomo kreirali serijski port, preko katerega bosta komunicirala Arduino Uno in modul GPS. Pin 2 bomo uporabili za RX in pin 3 za TX.

Vključiti moramo tudi knjižnico za modul GPS. Najdemo jo spodaj v dodatkih ali na spletni strani Adafruit GPS Library.

/*

  * Vzpostavitev komunikacije med Arduino in modulom GPS. Signal ni pridobljen.

  * Avtor: Milan Ivič

  * dec. 2015 *

 */

 #include <SoftwareSerial.h>   //Vključitev knjižnice za komunikacijo Arduino - modul GPS.

 #include <Adafruit_GPS.h>      //Vključitev knjižnice za modul GPS, MT3339.

 SoftwareSerial ModulGPS(3,2);

 Adafruit_GPS GPS(&ModulGPS);

 String NMEA1;     //Spremenljivko NMEA1 tipa String uporabimo za prvi NMEA stavek.

 String NMEA2;     //Spremenljivko NMEA2 tipa String uporabimo za drugi NMEA stavek.

 char Znaki;           //Spremenljivko Znaki tipa char uporabimo za branje znakov, ki jih pridobiva modul GPS.

 

 void setup()

 {

   Serial.begin(115200);  //Vklopimo komunikacijo s serijskim monitorjem 115200 bit/s.

   GPS.begin(9600);        //Vklopimo komunikacijo z modulom GPS s hitrostjo 9600 bit/s.

   GPS.sendCommand("$PGCMD,33,0*6D");      //Z ukazom $PGCMD,33,0*6D izklopimo moteče podatke glede posodabljanja GPS antene.

   GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);     //Povemo modulu GPS, da želimo samo $GPRMC in $GPGGA NMEA stavka.

   GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);        //Posodabljaj podatke vsako sekundo (1 Hz).

   delay(1000);

 }

 

  void loop()

 {

 readGPS();        //Kličemo funkcijo za pridobivanje podatkov ($GPRMC in $GPGGA NMEA stavka).

 }

 void readGPS()    //Funkcija pridobiva in shranjuje dva NMEA stavka iz modula GPS.

 {

   clearGPS();       //Počistimo morebitne stare podatke.

   while(!GPS.newNMEAreceived())     //Zanka while se izvaja, dokler niso prejeti vsi znaki prvega stavka NMEA.

   {

     Znaki=GPS.read();               //Branje podatkov (znakov) iz modula GPS.

   }

 GPS.parse(GPS.lastNMEA());          //Po rejetju vseh podatkov (znakov) jih moramo razčleniti.

 NMEA1=GPS.lastNMEA();               //Razčlenjene podatke prvega stavka NMEA shranimo v NMEA1.

 

   while(!GPS.newNMEAreceived())     //Zanka while se izvaja, dokler niso prejeti vsi znaki drugega stavka NMEA.

   {

     Znaki=GPS.read();               //Branje podatkov (znakov) iz modula GPS.

   }

 GPS.parse(GPS.lastNMEA());          //Po rejetju vseh podatkov (znakov) jih moramo razčleniti.

 NMEA2=GPS.lastNMEA();               //Razčlenjene podatke drugega stavka NMEA shranimo v NMEA2.

 Serial.print(NMEA1);                //Izpiši prvi NMEA stavek na serijski monitor.

 Serial.println(NMEA2);              //Izpiši drugi NMEA stavek na serijski monitor.

 }

//Medtem ko GPS bere podatke ki prihajajo, moramo počistiti stare podatke (podatki se ne shranjujejo v spremenljivko Znaki).

 void clearGPS()       

 {

   while(!GPS.newNMEAreceived())

   {

    Znaki=GPS.read();

   }

 GPS.parse(GPS.lastNMEA());

   while(!GPS.newNMEAreceived())

   {

     Znaki=GPS.read();

   }

 GPS.parse(GPS.lastNMEA());

 }

Razlaga programa je opisane že v komentarjih. Pomembno je določiti ustrezno zakasnitev, izpisovanje obeh NMEA stavkov ($GPRMC in $GPGGA) mora biti v enakem zaporedju. Če se prvi izpisuje stavek $GPRMC, drugi pa $GPGGA, se mora v tem zaporedju izpisovati, dokler imamo vklopljen serijski monitor.

Slika 2: Podatki na serijskem monitorju, komunikacija med Arduino in modulom GPS je vzpostavljena, signal ni pridobljen.

Vzpostavili smo komunikacijo Arduino Uno z modulom GPS, nismo pa pridobili vseh podatkov, saj smo bili v prostoru, kjer se modul GPS ni mogel povezati s sateliti, signal ni bil pridobljen. Podatek GPS fix v stavki $GPGGA je 0. Če bi bil signal pridobljen, bi bil podatek GPS fix enak 1. Tudi podatek o številu satelitov v tem stavku je enak 0.

Preden se bomo s prenosnikom in priključenim modulom GPS z Arduino Uno prestavili na vidno polje satelitov, dopolnimo program tako, da se nam bodo na serijski monitor poleg obeh NMEA stavkov izpisovali še tisti podatki, ki jih potrebujemo za prikaz našega položaja v Google Earth. To so zemljepisna širina, indikator N/S, zemljepisna dolžina, indikator E/W ter za nas zanimiva nadmorska višina. Za vzpostavitev modula GPS s sateliti potrebuje modul GPS, po podatkih proizvajalca cca 30 s časa.

/*

  * Vzpostavitev komunikacije med Arduino in modulom GPS. Signal je pridobljen.

  * Avtor: Milan Ivič

  * dec. 2015 *

 */

 #include <SoftwareSerial.h>   //Vključitev knjižnice za komunikacijo Arduino - modul GPS.

 #include <Adafruit_GPS.h>      //Vključitev knjižnice za modul GPS, MT3339.

 SoftwareSerial ModulGPS(3,2);

 Adafruit_GPS GPS(&ModulGPS);

 String NMEA1;     //Spremenljivko NMEA1 tipa String uporabimo za prvi NMEA stavek.

 String NMEA2;     //Spremenljivko NMEA2 tipa String uporabimo za drugi NMEA stavek.

 char Znaki;           //Spremenljivko Znaki tipa char uporabimo za branje znakov, ki jih pridobiva modul GPS.

 

 void setup()

 {

   Serial.begin(115200);                     //Vklopimo komunikacijo s serijskim monitorjem 115200 bit/s.

   GPS.begin(9600);                          //Vklopimo komunikacijo z modulom GPS s hitrostjo 9600 bit/s.

   GPS.sendCommand("$PGCMD,33,0*6D");        //Z ukazom $PGCMD,33,0*6D izklopimo moteče podatke glede posodabljanja GPS antene.

   GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);     //Povemo modulu GPS, da želimo samo $GPRMC in $GPGGA NMEA stavka.

   GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);        //Posodabljaj podatke vsako sekundo (1 Hz).

   delay(1000);

 }

 

 void loop()

 {

 readGPS();           //Kličemo funkcijo za pridobivanje podatkov ($GPRMC in $GPGGA NMEA stavka).

   if(GPS.fix==1)     //Ali je povezava modula GPS s sateliti vzpostavljena?

   {

     Serial.print(GPS.latitude);      //Izpiši zemljepisno širino.

     Serial.print(GPS.lat);           //Izpiši indikator N/S (sever, jug).

     Serial.print(", ");

     Serial.print(GPS.longitude);     //Izpiši zemljepisno dolžino.

     Serial.print(GPS.lon);           //Izpiši indikator E/W (vzhod, zahod).

     Serial.print(", ");

     Serial.println(GPS.altitude);    //Izpiši nadmorsko višino.

   }

 }

 void readGPS()    //Funkcija pridobiva in shranjuje dva NMEA stavka iz modula GPS.

 {

   clearGPS();       //Počistimo morebitne stare podatke.

   while(!GPS.newNMEAreceived())     //Zanka while se izvaja, dokler niso prejeti vsi znaki prvega stavka NMEA.

   {

     Znaki=GPS.read();               //Branje podatkov (znakov) iz modula GPS.

   }

 GPS.parse(GPS.lastNMEA());          //Po rejetju vseh podatkov (znakov) jih moramo razčleniti.

 NMEA1=GPS.lastNMEA();               //Razčlenjene podatke prvega stavka NMEA shranimo v NMEA1.

 

   while(!GPS.newNMEAreceived())     //Zanka while se izvaja, dokler niso prejeti vsi znaki drugega stavka NMEA.

   {

     Znaki=GPS.read();               //Branje podatkov (znakov) iz modula GPS.

   }

 GPS.parse(GPS.lastNMEA());          //Po rejetju vseh podatkov (znakov) jih moramo razčleniti.

 NMEA2=GPS.lastNMEA();               //Razčlenjene podatke drugega stavka NMEA shranimo v NMEA2.

 Serial.println(NMEA1);              //Izpiši prvi NMEA stavek na serijski monitor.

 Serial.println(NMEA2);              //Izpiši drugi NMEA stavek na serijski monitor.

 Serial.println("");

 }

 //Medtem ko GPS bere podatke ki prihajajo, moramo počistiti stare podatke (podatki se ne shranjujejo v spremenljivko Znaki).

void clearGPS()         

 {

   while(!GPS.newNMEAreceived())

   {

    Znaki=GPS.read();

   }

 GPS.parse(GPS.lastNMEA());

   while(!GPS.newNMEAreceived())

   {

     Znaki=GPS.read();

   }

 GPS.parse(GPS.lastNMEA());

 }

Slika 2: Podatki na serijskem monitorju, komunikacija med Arduino in modulom GPS je vzpostavljena, signal je pridobljen.

S prenosnikom in priključenim modulom GPS z Arduino Uno smo se prestavili na parkirišče trgovskega centra Tuš v Slovenski Bistrici.

Ko je signal pridobljen, se nam na serijski monitor izpisujeta stavka $GPRMC in $GPGGA s podatki, za njima pa še vrstica s podatki o zemljepisni širini, indikatorjem N (sever), zemljepisni dolžini, indikatorjem E (vzhod) ter nadmorsko višino v metrih.

Iz podatkov stavka $GPRMC lahko med drugim razberemo UTC čas (094303.000 => 9 ura, 43 min, 03 sek), status A (aktiven), podatek o zemljepisni širini (4623.2377 => 46 stopinj, 23,2377 minut), S (severna polobla), podatek o zemljepisni dolžini (01534.4458 => 15 stopinj, 34,4458 minut), E (vzhod), in datum (301215 => 30. dec. 2015).

Iz stavka $GPGGA pa lahko med drugimi podatki razberemo položaj fix (1 => položaj potrjen - signal pridobljen), uporabljenih 9 satelitov in podatek o nadmorski višini (276,0 metrov).

Modulu GPS z Arduino Uno dodajmo še modul s pomnilniško mikro SD kartico, na katero se bosta shranjevala stavka $GPGGA in $GPRMC s podatki. Modul vsebuje 6 priključkov (slika 3), GND, Vcc, MISO, MOSI, SCK in CS in komunicira z Arduino Uno preko SPI vodila.

Slika 3: Modul za pomnilniško micro SD kartico (vir: aliexpress)

Priključitev modula za pomnilniško kartico micro SD na razvojno ploščo Arduino Uno:

Podatke stavkov $GPGGA in $GPRMC, ki se bodo shranjevali na SD kartico potrebujemo zato, ker želimo na Google Earth prikazati naš položaj oziroma naše gibanje (pot), ki smo jo opravili z vključeno napravo (Arduino Uno z GPS modulom in SD modulom). Napetostno napajanje izvedemo z  baterijo napetosti 9 V, kot prikazuje slika 4.

Slika 4: Priključitev baterije 9 V na Arduino Uno (vir: onemansanthology)

Napišimo oziroma dopolnimo program, ki bo omogočal shranjevanje podatkov stavka $GPGGA in stavka $GPRMC na SD kartico:

/*

 * Vzpostavitev komunikacije med Arduino in modulom GPS.

 * Shranjevanje podatkov stavka $GPGGA in stavka $GPRMC na SD kartico.

 * Avtor: Milan Ivič

 * junij. 2016 *

*/

 #include <SD.h>                    //Vključitev knjižnice za SD kartico.

 #include <SPI.h>                   //Vključitev knjižnice za SPI komunikacijo.

 #include <SoftwareSerial.h>        //Vključitev knjižnice za komunikacijo Arduino - modul GPS.

 #include <Adafruit_GPS.h>          //Vključitev knjižnice za modul GPS, MT3339.

 SoftwareSerial ModulGPS(3,2);

 Adafruit_GPS GPS(&ModulGPS);

 String NMEA1;             //Spremenljivko NMEA1 tipa String uporabimo za prvi NMEA stavek.

 String NMEA2;             //Spremenljivko NMEA2 tipa String uporabimo za drugi NMEA stavek.

 char Znaki;               //Spremenljivko Znaki tipa char uporabimo za branje znakov, ki jih pridobiva modul GPS.

 int ChipSelect = 4;       //Priključek CS (Chip Select) modula SD kartice priključimo na pin 4.

 File PodatkiGPS;          //Objekt, kamor se bodo zapisovali podatki na SD kartico.

 

 void setup()

 {  

   GPS.begin(9600);                        //Vklopimo komunikacijo z modulom GPS s hitrostjo 9600 bit/s.

   GPS.sendCommand("$PGCMD,33,0*6D");      //Z ukazom $PGCMD,33,0*6D izklopimo moteče podatke glede posodabljanja GPS antene.

   GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);       //Povemo modulu GPS, da želimo samo $GPRMC in $GPGGA NMEA stavka.

   GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);          //Posodabljaj podatke vsako sekundo (1 Hz).

   delay(1000);

   pinMode(10, OUTPUT);

   SD.begin(ChipSelect);         //Inicializacija čitalca SD kartice.  

 }

 

  void loop()

 {

 readGPS();                  //Kličemo funkcijo za pridobivanje podatkov ($GPRMC in $GPGGA NMEA stavka).

   if(GPS.fix==1)            //Ali je povezava modula GPS s sateliti vzpostavljena?

   {

     PodatkiGPS = SD.open("NMEAc.txt", FILE_WRITE);          //Odpremo mapo za zapisovanje na SD kartici.

     PodatkiGPS.println(NMEA1);                              //Zapiši prvi NMEA stavek na SD kartico.   

     PodatkiGPS.println(NMEA2);                              //Zapiši drugi NMEA stavek na SD kartico.

     PodatkiGPS.close();                                     //Zapri mapo.         

   }

 }

 void readGPS()                    //Funkcija pridobiva in shranjuje dva NMEA stavka iz modula GPS.

 {

   clearGPS();                          //Počistimo morebitne stare podatke.

   while(!GPS.newNMEAreceived())       //Zanka while se izvaja, dokler niso prejeti vsi znaki prvega stavka NMEA.

   {

     Znaki=GPS.read();                 //Branje podatkov (znakov) iz modula GPS.

   }

 GPS.parse(GPS.lastNMEA());            //Po rejetju vseh podatkov (znakov) jih moramo razčleniti.

 NMEA1=GPS.lastNMEA();                 //Razčlenjene podatke prvega stavka NMEA shranimo v NMEA1.

 

   while(!GPS.newNMEAreceived())      //Zanka while se izvaja, dokler niso prejeti vsi znaki drugega stavka NMEA.

   {

     Znaki=GPS.read();                  //Branje podatkov (znakov) iz modula GPS.

   }

 GPS.parse(GPS.lastNMEA());            //Po rejetju vseh podatkov (znakov) jih moramo razčleniti.

 NMEA2=GPS.lastNMEA();                 //Razčlenjene podatke drugega stavka NMEA shranimo v NMEA2.

 }

 //Medtem ko GPS bere podatke ki prihajajo, moramo počistiti stare podatke (podatki se ne shranjujejo v spremenljivko Znaki).

void clearGPS()                             

 {

   while(!GPS.newNMEAreceived())

   {

    Znaki=GPS.read();

   }

 GPS.parse(GPS.lastNMEA());

   while(!GPS.newNMEAreceived())

   {

     Znaki=GPS.read();

   }

 GPS.parse(GPS.lastNMEA());

 }

Ko je povezava modula GPS s sateliti vzpostavljena (GPS.fix ==1), se na SD kartici odpre tekstovna datoteka z imenom NMEAc (lahko je drugačno ime) v katero se zapisujejo podatki stavka $GPGGA in $GPRMC. Po vsakem zapisu obeh stavkov, se mapa zapre.