[120] Zegar z nieba czyli wstęp do GPS-u

[120] Zegar z nieba czyli wstęp do GPS-u

Oczywiście GPS powstał do innych celów niż budowa zegarka i kiedyś takie użycie jakże wówczas drogich odbiorników było bezsensowne. Dzisiaj kompletny odbiornik kosztuje grosze. Jednak dzisiejszym bohaterem nie będzie jeden z tysięcy modułów dostępny w sieci, a taki leciwy już odbiornik Garmina, który nie wspiera nawet map. Był moim pierwszym odbiornikiem, który ćwierć wieku temu kupiłem głównie z myślą o byciu dawcą protokołu dla Palma – który już w oparciu o dane z GPS-a mógł wyświetlać zeskanowaną mapę. Było to bardzo nowatorskie, choć dziś wydaje się archaiczne.


Dla własnej wygody zbudowałem wówczas interfejs zamieniający standard RS232 na łącze optyczne. Taki moduł siedział przytwierdzony do plecaka, a już Palm IIIC odbierał wbudowanym odbiornikiem podczerwieni ramkę z danymi, uaktualniając pozycję na żądanie, a nie w trybie ciągłym. Pozwoliło to pozbyć się kabli i zmaksymalizować pracę bez wymiany baterii do kilkudziesięciu godzin. To tak w ramach sentymentalnej ciekawostki. Obecnie każdemu polecam po prostu zakup gotowego modułu, ale użycie takiego urządzenia będzie mieć dodatkową wartość edukacyjną, ponieważ będziemy musieli sobie poradzić z jeszcze jednym problemem.

Złącze – tu także przerobione przeze mnie ćwierć wieku temu – zawiera cztery sygnały: zasilanie, RX, TX i masę. Nam potrzebne będą dwa ostatnie. Problem w tym, że interfejs szeregowy spełnia standardy RS232, czyli balansuje między kilkoma woltami napięcia ujemnego i dodatniego. Fachowo bierze się kostkę MAX232 i buduje malutki układ dopasowujący napięcia do standardu TTL, czyli zera i pięciu woltów. Jednak skoro potrzebujemy transmisji tylko w jedną stronę, możemy sprawę uprościć – zdecydowanie niefachowo, ale skutecznie. Należy zbudować bardzo prosty układ, który odetnie nam napięcie ujemne i ograniczy dodatnie do pięciu woltów. Nasuwa się tu od razu pomysł na użycie diody Zenera. Ale ja proponuję użyć tak zwanej świecącej diody Zenera, zbudowanej z niebieskiego LED-a i zwyczajnej diody małej mocy podłączonej równolegle do niego. LED w kierunku przewodzenia daje spadek równy mniej więcej trzem woltom. To o wolt więcej niż potrzeba, by standard pięciowoltowy odczytał poziom wysoki, więc taka dioda wstawiona równolegle do obwodu zbije wszystko, co ma wartość większą od 3 woltów, a przy okazji będzie świecić, dzięki czemu będziemy widzieć czy pojawiają się dane.

Ale LED nie jest Zenerem i w drugą stronę nie przewodzi. Koniecznie więc w kierunku zaporowym wstawić trzeba zwyczajną diodę, która zbije nam napięcia ujemne niższe niż mniej więcej siedem dziesiątych wolta. Dopełnieniem będzie rezystor na wejściu, dobrany tak, by nie obciążać biednego Garminka. 3,3 kilooma w tych warunkach będzie wartością w sam raz.

To jeszcze nie koniec. Tak uformowany sygnał ma odwróconą fazę, gdyż jedynka w RS-ie to napięcie ujemne i Arduino nie rozpozna przekazu, w każdym razie klasycznym oprogramowaniem obsługującym port szeregowy. Najprościej w tym wypadku zamienić masę z sygnałem, czyli podłączyć Garmina na odwrót. Nieładnie, ale skutecznie. Niestety odpada wówczas wspólne zasilanie całości, jednak jak mówiłem – to tylko pomysł edukacyjny, a realizację najlepiej przeprowadzić na współczesnych modułach, które mają czyste wyjście TTL. Zresztą ten Etrex jest pierwszym modelem z serii produkowanej do dziś i ma kiepską czułość, a do tego złapanie tak zwanego fixa trwa wieki. Za to dla celów poznawczych ma przydatny tryb demo, więc testy możemy przeprowadzać z dala od okna – oczywiście z fałszywymi odczytami.

Na szczęście w wypadku użycia programowego portu szeregowego, można opcjonalnie odwrócić logikę i nie trzeba przestawiać połączeń. I tak właśnie zrobimy, ale za chwilę. Skoro jest edukacyjnie – poznajmy nowe urządzenie, choć raczej należałoby tu powiedzieć: aplikację. W swoich projektach często nagrywałem przebiegi za pomocą karty dźwiękowej – jeśli tylko miały częstotliwości akustyczne – i wizualizowałem je potem za pomocą programu do obróbki dźwięku Adobe Audition.

Tak wyglądają trzy sekundowe paczki danych, a tak silnie powiększony przebieg.

Widzimy tutaj zera i jedynki, czyli niskie i wysokie stany napięć. Na potrzeby takich projektów wystarczy, ale nie ma to jak stary, dobry oscyloskop.

W tym przypadku nie taki stary, bo wirtualny i także używający karty dźwiękowej. Ma to dwie zalety: nic nie kosztuje i jest jako tako dokładne – współczesne karty dysponują 80 dB dynamiki. Ma też dwie wady: brak rzetelnej informacji o poziomach rzeczywistych i brak wsparcia dla prądu stałego. W kartach dźwiękowych pasmo ograniczone jest od dołu do kilkunastu herców – stąd takie garby na przebiegach. Aplikacja jednak – z zastrzeżeniem na wspomniane ograniczenia – radzi sobie dobrze i możemy tu ustawiać wszystko, jak w dobrej klasie dwukanałowym oscyloskopie. Do tego aplikacja wygląda miło.

Obie metody w tym wypadku pozwalają znaleźć przebiegi na nieznanych wyjściach i mniej więcej zobaczyć ich treść. Skoro już możemy zakończyć tę dygresję, przejdźmy do sedna.

Gdy podłączymy GPS-a do interfejsu i odpalimy go, dioda powinna migać mniej więcej w cyklu sekundowym, bo w takim wychodzą pakiety. Masy łączymy razem, a wyjście interfejsu – do pinu czwartego. W tym momencie spotykamy się niezależnie od tego, czy wybraliśmy podobne urządzenie, czy współczesne, które łączy się bezpośrednio. W każdym wypadku protokół, który obsłużymy, nazywa się NMEA. Jest on protokołem domyślnym GPS-ów, choć nie jedynym i także domyślnie pracuje z szybkością 4800 bodów. W określonych przypadkach warto sprawdzić, czy urządzenie tak właśnie zostało skonfigurowane. Ramka – wysyłana raz na sekundę – zawiera do 82 bajtów.

Oprócz stałych elementów, rezerwowych i sumy kontrolnej, siedzą tam istotne dla nas: długość i szerokość geograficzna i bardzo dokładny zegar. Można też skorzystać z informacji o wysokości, dokładności odczytu położenia i ilości widzianych satelitów. Dziś zajmiemy się tylko zegarem.

Oczywiście jak zwykle skorzystamy z biblioteki, a jest w czym wybierać. Plotki mówią, że warto zaufać tej o nazwie TinyGPS++, która ma bogatą historię i równocześnie łatwość dostępu do danych. Cóż, bierzmy się więc do roboty, a wiele jej nie będzie.

#include <TinyGPSPlus.h>                // Biblioteka obsługująca protokół NMEA
#include <SoftwareSerial.h>             // Biblioteka oferująca dodatkowy port szeregowy.
TinyGPSPlus gps;                        // Nazwa obiektu związanego z nowymi komendami GPS-a.
SoftwareSerial portSzeregowy(4, 3, 1);  // Nazwa obiektu związanego z nowymi komendami dodatkowego poru szeregowego, deklaracja pinów: RX, TX i odwrócenie logiki.

void setup() {
  Serial.begin(115200);       // Inicjacja sprzętowego portu szeregowego.
  portSzeregowy.begin(4800);  // Inicjacja dodatkowego portu szeregowego.
}

void loop() {
  while (portSzeregowy.available() > 0) {    // Czekaj aż załaduje się ramka.
    if (gps.encode(portSzeregowy.read())) {  // Dekoduj ramkę.
      Serial.print(gps.date.year());         // Rok.
      Serial.print(F("-"));
      Serial.print(gps.date.month());  // Miesiąc.
      Serial.print(F("-"));
      Serial.print(gps.date.day());  // Dzień.
      Serial.print(F(" "));
      Serial.print(gps.time.hour());  // Godzina.
      Serial.print(F(":"));
      Serial.print(gps.time.minute());  // Minuta.
      Serial.print(F(":"));
      Serial.println(gps.time.second());  // Sekunda.
      delay(700);
    }
  }
}

Zaczynamy od zaczytania bibliotek: obsługującej protokół oraz emulującej port szeregowy. Co prawda można by tutaj skorzystać ze sprzętowego portu na pinie zerowym i pierwszym, ale należałoby się odłączać podczas każdorazowego programowania Arduino, a poza tym port ten przyda nam się do podglądu parametrów w programie monitora. Poza tym software’owy port można odwrócić logicznie i tak też zrobimy podczas definiowania, wstawiając na końcu jedynkę. Nie wstawiamy jej w przypadku użycia współczesnych odbiorników, przystosowanych do poziomów TTL.

Trzeba jeszcze jak zwykle nazwać drugi obiekt, czyli źródło danych z nieba i można zainicjować oba porty. W pętli głównej skromnie: czekamy na ramkę, dekodujemy ją i wyciągamy kolejnymi komendami składniki daty i czasu. Wszystkie wysyłamy na port szeregowy, więc ukażą się nam w okienku monitora.

Jak widać, mamy tutaj jakąś hipotetyczną datę demo, bo – jak mówiłem – odpalam urządzenie w pomieszczeniu, gdzie nie ma dostępu do satelitów. Przydałoby się jednak przetestować układ w terenie, więc skorzystajmy z wyświetlacza, który przecież siedzi gratis na płytce edukacyjnej TME. Dołóżmy więc jego obsługę.

#include <TinyGPSPlus.h>                // Biblioteka obsługująca protokół NMEA
#include <SoftwareSerial.h>             // Biblioteka oferująca dodatkowy port szeregowy.
TinyGPSPlus gps;                        // Nazwa obiektu związanego z nowymi komendami GPS-a.
SoftwareSerial portSzeregowy(4, 3, 1);  // Nazwa obiektu związanego z nowymi komendami dodatkowego poru szeregowego, deklaracja pinów: RX, TX i odwrócenie logiki.

#include                                                       // Biblioteka obsługująca magistralę I2C
#include                                                    // Biblioteka obsługująca wyświetlacze HD44780
#include                              // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD

void setup() {
  portSzeregowy.begin(4800);  // Inicjacja dodatkowego portu szeregowego.
  lcd.begin(16, 2);           // Inicjalizacja wyświetlacza LCD
}

void loop() {
  while (portSzeregowy.available() > 0) {    // Czekaj aż załaduje się ramka.
    if (gps.encode(portSzeregowy.read())) {  // Dekoduj ramkę.
      lcd.setCursor(3, 0);                   // Ustaw kursor.
      lcd.print(gps.date.year());            // Rok.
      lcd.print(F("-"));                     // Separator (kreska).
      lcd.print(gps.date.month());
      lcd.print(F("-"));
      lcd.print(gps.date.day());  // Dzień.
      lcd.setCursor(4, 1);
      lcd.print(gps.time.hour());  // Godzina.
      lcd.print(F(":"));
      lcd.print(gps.time.minute());  // Minuta.
      lcd.print(F(":"));
      lcd.print(gps.time.second());  // Sekunda.
    }
  }
}

Wymaga ona bibliotek: związanej z I2C, samego wyświetlacza, ekspandera, do którego został podłączony i konfiguracji tegoż. Usuwamy inicjację monitora, a wszystkie odwołania do niego zamieniamy komendami wyświetlającymi wyniki na wyświetlaczu.

Nie wygląda to najlepiej w przypadku wartości mniejszych od 10: pozycje przesuwają się w lewo, ale – co gorsza – śmieci z poprzednich odczytów pozostają i całość jest nieczytelna. Dołożyłem więc dodatkowe linie, które wykrywające zbyt krótkie ciągi i dopisują zera wiodące. Teraz wygląda to już dobrze.

#include <TinyGPSPlus.h>                // Biblioteka obsługująca protokół NMEA
#include <SoftwareSerial.h>             // Biblioteka oferująca dodatkowy port szeregowy.
TinyGPSPlus gps;                        // Nazwa obiektu związanego z nowymi komendami GPS-a.
SoftwareSerial portSzeregowy(4, 3, 1);  // Nazwa obiektu związanego z nowymi komendami dodatkowego poru szeregowego, deklaracja pinów: RX, TX i odwrócenie logiki.

#include <Wire.h>                                                      // Biblioteka obsługująca magistralę I2C
#include <hd44780.h>                                                   // Biblioteka obsługująca wyświetlacze HD44780
#include <hd44780ioClass/hd44780_I2Cexp.h>                             // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD

void setup() {
  portSzeregowy.begin(4800);  // Inicjacja dodatkowego portu szeregowego.
  lcd.begin(16, 2);           // Inicjalizacja wyświetlacza LCD
}

void loop() {
  while (portSzeregowy.available() > 0) {                // Czekaj aż załaduje się ramka.
    if (gps.encode(portSzeregowy.read())) {              // Dekoduj ramkę.
      lcd.setCursor(3, 0);                               // Ustaw kursor.
      lcd.print(gps.date.year());                        // Rok.
      lcd.print(F("-"));                                 // Separator (kreska).
      if (gps.date.month() < 10) { lcd.print(F("0")); }  // Dodaj zero wiodące dla wartości mniejszej od 10
      lcd.print(gps.date.month());
      lcd.print(F("-"));
      if (gps.date.day() < 10) { lcd.print(F("0")); }
      lcd.print(gps.date.day());  // Dzień.
      lcd.setCursor(4, 1);
      if (gps.time.hour() < 10) { lcd.print(F("0")); }
      lcd.print(gps.time.hour());  // Godzina.
      lcd.print(F(":"));
      if (gps.time.minute() < 10) { lcd.print(F("0")); }
      lcd.print(gps.time.minute());  // Minuta.
      lcd.print(F(":"));
      if (gps.time.second() < 10) { lcd.print(F("0")); }
      lcd.print(gps.time.second());  // Sekunda.
    }
  }
}

Na koniec wyłączmy tryb demo, przenieśmy się na balkon i zobaczmy czy czas się uaktualni.

Po chwili – możemy się cieszyć zegarkiem, ale nie do końca. NMEA podaje czas w formacie UTC, czyli dla południka zerowego, bez uwzględnienia czasu letniego. Cóż, najprościej będzie po prostu dołożyć poprawkę i uwzględnić ewentualny przełącznik czasu letniego albo zorganizować go programowo, co jest skomplikowane, a pisałem o tym przy okazji omawiania zegara pracującego w oparciu o pingi. Jeśli poprzestaniemy na przełączniku, także nie będzie to takie proste: samo dodanie godziny zadziała do 22. Potem należy jeszcze zwiększyć wartość dnia, a w razie potrzeby także miesiąca i roku. Elementy te należałoby przenieść do zmiennych i przeprowadzać już na nich rachunki uwzględniające możliwość zwiększenia tych liczb względem wartości odczytanych. Ale coś takiego już kiedyś prezentowałem przy okazji omawiania zegarów, więc odsyłam do moich poprzednich artykułów.

Powiązane tematy

Płytka edukacyjna TME-EDU-ARD-2Płytka edukacyjna TME-EDU-ARD-2Sprawdź tutaj

Przeczytaj również

Nasi partnerzy

TMETech Master EventTME EducationPoweredby
Copyright © 2025 arduino.pl