Od razu GPS logger zbudowano, czyli Arduino i GPS

W poprzedniej części napisaliśmy jak podłączyć kartę microSD do Arduino i coś na niej zapisywać. Jak każdy mógł zauważyć w plątaninie kabli krył się też odbiornik GPS. Ten post miał powstać wcześniej, ale wyglądało na to, że mamy jakiś problem z dostawą z Chin, o czym kilka słów na firmowym blogu. Na szczęście wszystko się wyjaśniło, więc moduły GPS są dostępne i jeżeli planujesz podłączyć z Arduino zrobić odbiornik GPS to wiesz gdzie szukać :)

Moduł GPS VK2828U7G5FL podłączony do Arduino

Teraz o tym jak podłączymy moduł VK2828U7G5LF do  Arduino. Posłuży nam ten schemat:

Jak podłączyć GPS do Arduino
Podłączenie GPS VK2828U7G5LF do Arduino

Dla ułatwienia kolory kabli są takie same jak na złączu, które jest dodawane do każdego modułu. W podstawowym układzie wystarczą te trzy przewody: czarny – masa, czerwony – zasilanie (tak, może być 5V), niebieski – UART TTL z danymi.

Podłączyliśmy niebieski przewód do cyfrowego pinu 2 na Arduino. Dlaczego nie do 0, gdzie jest sprzętowy UART? Przez piny 0 i 1 odbywa się programowanie Arduino. Mając tam podłączony moduł GPS, który ciągle nadaje dane utrudniałoby programowanie (trzeba odłączać moduł GPS przy każdym wgraniu szkicu). Ponieważ pamięci nam na Arduino wystarczy, a GPS nadaje z niezbyt wygórowaną prędkością (9600), to SoftwareSerial, czyli programowa realizacja portu szeregowego na dowolnych pinach cyfrowych w zupełności nam wystarczy.

A czemu podłączyliśmy tylko linę TX modułu GPS? Nie będziemy nic nadawać do modułu, ustawienia fabryczne są dla nas OK. Jeśli jednak chcielibyśmy coś zmienić, to można to zrobić, wysyłając do modułu sekwencję kodów. Można je znaleźć w karcie katalogowej modułu. Co można zmienić? Np częstotliwość z jaką moduł wysyła pozycję na interfejs UART. Domyślnie jest to 1 raz na sekundę (czyli 1 Hz), maksymalnie może to być 10 razy na sekundę (czyli 10 Hz). Ryzyko uszkodzenia interfejsu UART modułu! Moduł posiada interfejs UART TTL, ale wszystko wskazuje na to, że górny poziom napięcia na wejściu to 3.3V a nie Vcc, więc podłączając TX z Arduino działające na poziomie 5V ryzykujesz uszkodzenie UART w module GPS.

Jeżeli chcesz zmienić ustawienia modułu GPS to zrób dzielnik napięcia między TX Arduino a RX modułu lub skorzystaj z konwertera poziomów logicznych. Jeżeli korzystasz z Arduino działającego w standardzie 3.3V to możesz moduł podłączyć bezpośrednio.

Program na Arduino do odbierania danych z GPS

Pierwszy krok, sprawdźmy czy w ogóle odbieramy coś z modułu. Wgrajmy minimalistyczny program:

#include <SoftwareSerial.h>
SoftwareSerial gpsBoard (2, 3, false);

void setup() {
  Serial.begin(38400);
  gpsBoard.begin(9600);
}

void loop() {
  while (gpsBoard.available()) {

    byte c = gpsBoard.read();                   // Read the GPS data
    Serial.print((char)c);
  }
}

Co w szkicu? Najpierw importujemy bibliotekę SoftwareSerial oraz definiujemy obiekt gpsBoard, który posiada interfejs (funkcje) jak Serial. Standardowo biblioteka SoftwareSerial obsługuje zarówno nadawanie i odbieranie, dlatego definicja obiektu ma format:

SoftwareSerial gpsBoard (2, 3, false);

Pin 2 to odbieranie danych, 3 nadawanie. Trzeci parametr to czy sygnał jest odwrócony. Zdarzają się urządzenia, które mają odwrócony poziom sygnałów, i bit 0 jest reprezentowany przez stan wysoki napięcia a bit 1 przez zero. Domyślnie wartość tego argumentu to false, ale używam go tutaj, byście byli świadomi jego istnienia – przydaje się gdy spotkasz takie urządzenie (np sensory Maxbotix używają odwróconego seriala). Niestety, pin 3 nie może być użyty do innych celów. Jeśli zależy Ci na wolnych pinach i/lub na zmniejszeniu rozmiaru szkicu możesz skorzystać z okrojonej biblioteki SoftwareSerial, którą możesz znaleźć w tym wątku na forum Arduino (ale sam z nie nie korzystałem). Jest tam link do wersji TX-only oraz RX-only.

W setup ustawiamy port UART Arduino, oraz programowy serial  na odpowiednie prędkości. W loop każdy odebrany znak z modułu GPS wysyłamy do komputera.

Po wgraniu na Arduino na monitorze portu szeregowego powinny zacząć się pokazywać takie wpisy:

 $GPRMC,103512.00,A,5217.08229,N,02053.34368,E,0.067,,240316,,,A*79
 $GPVTG,,T,,M,0.067,N,0.124,K,A*25
 $GPGGA,103512.00,5217.08229,N,02053.34368,E,1,06,1.69,84.5,M,34.2,M,,*62
 $GPGSA,A,3,05,13,30,20,09,28,,,,,,,2.53,1.69,1.89*0D
 $GPGSV,3,1,11,05,52,246,16,07,48,068,,08,16,062,26,09,14,118,23*71
 $GPGSV,3,2,11,13,42,292,22,15,06,297,18,20,28,307,23,21,06,337,*7D
 $GPGSV,3,3,11,27,12,031,08,28,40,165,27,30,80,078,13*4A
 $GPGLL,5217.08229,N,02053.34368,E,103512.00,A,A*63

Tak wygląda to, co podaje moduł GPS. Powyższy zrzut jest dla sytuacji, w której moduł już uzyskał pozycję (sygnalizują to migające zielone diody z obu stron płytki modułu). Gdy moduł startuje podaje informacje o sobie:

$GPTXT,01,01,02,u-blox ag - www.u-blox.com*50 
$GPTXT,01,01,02,HW  UBX-G70xx   00070000 FF7FFFFFo*69 
$GPTXT,01,01,02,ROM CORE 1.00 (59842) Jun 27 2012 17:43:52*59 
$GPTXT,01,01,02,PROTVER 14.00*1E 
$GPTXT,01,01,02,ANTSUPERV=AC SD PDoS SR*20 
$GPTXT,01,01,02,ANTSTATUS=DONTKNOW*33

A potem w formacie jak wyżej pozycję co jedną sekundę, tylko będzie mniej liczb. Dane te są nieczytelne dla człowieka (format ten nazywa się NMEA, składa się komunikatów aka sentencji), trzeba je przekształcić, nie będziemy jednak tutaj robić ręcznie konwersji formatu. Skorzystamy z gotowej biblioteki TinyGPS. Ściągnij i rozpakuj w folderze z bibliotekami. Po restarcie Arduino będzie dostępny nowy przykład (Examples/TinyGPS/test_with_gps_device). Otwórz go, popraw piny w definicji obiektuy SoftwareSerial oraz w setup zmień jego prędkość na 9600 z przykładowego 4800. Po wgraniu, powinieneś zobaczyć mniej więcej taki obraz:

Testing TinyGPS library v. 13 
by Mikal Hart 
 
Sats HDOP Latitude  Longitude  Fix  Date       Time     Date Alt    Course Speed Card  Distance Course Card  Chars Sentences Checksum 
          (deg)     (deg)      Age                      Age  (m)    --- from GPS ----  ---- to London  ----  RX    RX        Fail 
------------------------------------------------------------------------------------------------------------------------------------- 
**** **** ********* ********** **** ********** ******** **** ****** ****** ***** ***   *******  ****** ***   0     0         0         
8    129  52.284843 20.889328  602  03/24/2016 11:03:40 619  113.30 0.00   0.41  N     1440     274.92 W     481   2         0         
8    129  52.284843 20.889335  627  03/24/2016 11:03:41 644  113.40 0.00   1.11  N     1440     274.92 W     962   4         0         
8    129  52.284851 20.889335  639  03/24/2016 11:03:42 658  113.60 0.00   0.26  N     1440     274.92 W     1443  6         0         
8    129  52.284851 20.889333  659  03/24/2016 11:03:43 676  113.90 0.00   0.26  N     1440     274.92 W     1926  8         0         
8    129  52.284854 20.889329  675  03/24/2016 11:03:44 694  114.10 0.00   0.15  N     1440     274.92 W     2409  10        0         
8    129  52.284858 20.889328  702  03/24/2016 11:03:45 719  114.60 0.00   0.33  N     1440     274.92 W     2890  12        0

Możesz zajrzeć do przykładu by sprawdzić jak uzyskać niektóre z informacji, jak np liczbę widocznych satelitów.

Jak odczytać pozycję?

Jeśli chodzi o odczyt najbardziej nas interesującego parametru, czyli położenia, to najpierw, zamiast wysyłać znak do komputera, przykażemy go do obiektu gps. Metoda encode z kolejnych znaków składa całość komunikatu i na podstawie całości dokonuje obliczeń:

while (gpsBoard.available()) {
    byte c = gpsBoard.read();                   // Read the GPS data
    //Serial.print((char)c);
    gps.encode(c)); 
  }

gps jest obiektem typu TinyGPS. Teraz pozostaje uzyskać dane w łatwiejszym do użycia formacie:

long lat, lon;
unsigned long fix_age;

gps.get_position(&lat, &lon, &fix_age);

Zwróć uwagę, na znaki & przed nazwami zmiennych. Dzięki temu zapisując jakieś wartości do tych zmiennych wewnątrz funkcji, wartość ta widoczna jest poza funkcją. Po wywołaniu get_position zmienne lat, lon i fix_age będą nadal miały taką wartość jaka została ustawiona.

W lat i lon dostaniesz odpowiednio szerokość i długość geograficzną. Podane są jako liczba całkowita, w milionowych częściach stopnia. Czyli 9 stopni to będzie wartość 9 milionów, -15 stopni – minus 15 milionów. Trzeba pamiętać też, że wartość ta podana jest w systemie dziesiętnym, czyli lat równe 9 i pół miliona oznacza 9 stopni i 30 minut szerokości geograficznej.

Trzeci argument fix_age zawiera wiek (w milisekundach) od ostatniego odczytu z modułu GPS. Przed pierwszym odczytem będzie zawierał wartość równą stałej TinyGPS::GPS_INVALID_AGE. Jeśli porównasz fix_age z tą wartością i będą równe, to znaczy, że jeszcze nie odczytał pozycji.

Sprawę zgubienia sygnału z satelity można wykryć inaczej.

  unsigned long chars = 0;
  unsigned short sentences = 0, failed = 0;
  gps.stats(&chars, &sentences, &failed);

Po wywołaniu tej funkcji mamy trzy wartości: chars – ile znaków odebrał obiekt, failed – ile komunikatów z moduł GPS nie zostało poprawnie odczytane  i to co nas interesuje sentences – czyli liczba poprawnych komunikatów. Z każdym poprawnym odczytem pozycji ten licznik będzie się zwiększał (bodajże o 2 na każdy poprawny odczyt). Jeżeli przez kilka sekund pozostaje bez zmian – na pewno nie ma sygnału. Pamiętaj, TinyGPS będzie zwracał zawsze ostatnią odczytaną pozycję i inne dane, więc to jedyna metoda by programowo to sprawdzić.

Więcej o bibliotece TinyGPS można poczytać na stronie autora biblioteki.

Co jeszcze warto wiedzieć?

Moduł ma dwie zielone diody, które rytmicznie pulsują co 1 sekundę gdy moduł uzyskał sygnał na tyle dobry, by wyznaczyć pozycję. Impuls ten jest dostępny również w postaci sygnału PPS na złączu (przewód biały). Podłączyć go można do Arduino – impulsy co 1 sekundę na danym pinie oznaczają że moduł podaje poprawną pozycję. Ale nie tylko. GPS jest uznawany za referencyjny wzorzec czasu. Odbiornik jest wzorcem czasu klasy STRATUM 0 (można poczytać więcej o tym we wpisie o NTP na WIkipedii),  więc impuls jest dokładnie co 1 sekundę i może stanowić odniesienie do dokładnych pomiarów czasu.