Nedávno jsme si postavili hodiny se synchronizací skrze internet. Ale co když nemáme po ruce Wi-Fi? Vyřeší to laciný přijímač GPS v ceně piva, který zjsitít přesný čas za pár sekund

Nedávno jsme si postavili hodiny se synchronizací skrze internet. Ale co když nemáme po ruce Wi-Fi? Vyřeší to laciný přijímač GPS v ceně piva, který zjsitít přesný čas za pár sekund

Displeje hodin jsou za 0,5mm vytištěnou vrstvičkou z černého PETG Galaxy Black

Displeje hodin jsou za 0,5mm vytištěnou vrstvičkou z černého PETG Galaxy Black

Čelní stěna slouží jako difuzér, která zjemňuje ostrý obraz displejů

Čelní stěna slouží jako difuzér, která zjemňuje ostrý obraz displejů

Prstenec slouží jako skeundovka, která každých 15 sekund mění barvu

Prstenec slouží jako skeundovka, která každých 15 sekund mění barvu

Sekundovka se postupně mění ze zelené a mdoré  na červenou a bílou

Sekundovka se postupně mění ze zelené a mdoré  na červenou a bílou

A máme celou minutu, prstenec je plně rozsvícený

A máme celou minutu, prstenec je plně rozsvícený

Prototypovací modul s RTC čipem DS3231 a knoflíkovou baterií

Prototypovací modul s RTC čipem DS3231 a knoflíkovou baterií

Prototypovací modul s GPS čipem NEO-6M

Prototypovací modul s GPS čipem NEO-6M

O něco drobnější přijímač ATGM336H-5N-31

O něco drobnější přijímač ATGM336H-5N-31

O něco drobnější přijímač ATGM336H-5N-31

O něco drobnější přijímač ATGM336H-5N-31

Aplikace u-blox, výpis zpráv ve formátu NMEA a jednoduchá vizualizace

Aplikace u-blox, výpis zpráv ve formátu NMEA a jednoduchá vizualizace

Časové zprávy GNZDA z čipu ATGM336H-5N-31

Časové zprávy GNZDA z čipu ATGM336H-5N-31

Původní internetové hodiny jsme doplnili o modul laciného přijímače NEO-6M s tolik typickou anténou pro satelitní příjem GNSS

Původní internetové hodiny jsme doplnili o modul laciného přijímače NEO-6M s tolik typickou anténou pro satelitní příjem GNSS

Pokud zachytíme zprávu GPRMC, vytáhneme z ní čas a datum a seřídíme softwarové hodiny běžící na čipu hlavní desky. Hodiny budou po prvotním seřízení pracovat i bez GPS

Pokud zachytíme zprávu GPRMC, vytáhneme z ní čas a datum a seřídíme softwarové hodiny běžící na čipu hlavní desky. Hodiny budou po prvotním seřízení pracovat i bez GPS

Displeje hodin jsou za 0,5mm vytištěnou vrstvičkou z černého PETG Galaxy Black
Čelní stěna slouží jako difuzér, která zjemňuje ostrý obraz displejů
Prstenec slouží jako skeundovka, která každých 15 sekund mění barvu
Sekundovka se postupně mění ze zelené a mdoré  na červenou a bílou
14
Fotogalerie

Programování elektroniky: Postavíme si hodiny, které se automaticky seřídí podle GPS

  • Nedávno jsme si postavili hodiny se synchronizací skrze internet
  • Ale co když nemáme po ruce Wi-Fi?
  • Vyřeší to laciný přijímač GPS v ceně piva

V dnešním pokračování našeho seriálu o programování elektroniky si postavíme hodiny, které už nebudete muset nikdy seřizovat. Na rozdíl od kukaček po babičce totiž budou téměř dokonale přesné. Navždy! A bez potřeby internetu!

Jak vlastně na Arduinu a podobných destičkách měřit čas? Sice máme k dispozici dvě základní funkce millis a micros, které počítají milisekundy, respektive mikrosekundy od spuštění čipu, čítače se ale přirozeně po odpojení elektřiny resetují a stejně tak po určité době přeteče jejich paměť a začnou od nuly.

Co je však zdaleka nejdůležitější, běžné procesory nejsou určené k přesnému počítání jakýchsi abstraktních mikrosekund a sekund reálného času, ty totiž náš firmware pouze odvozuje z pracovního taktu čipu. Není to úplně nejpřesnější, takže kdybychom se na to spoléhali a pomocí millis a při znalosti výchozího času při spuštění počítali ubíhající čas, po určité době se nám začne oproti tomu skutečnému docela výrazně rozjíždět.

Video dnešního projektu: GPS hodiny s LED prstencem

Přesný čas sice zajistí obvod RTC, ale…

Aby mohly počítače pracovat s přesným časem, jsou vybavené obvodem reálného času RTC, který má za úkol počítat přesné sekundy a je zpravidla napájený separátní baterií, takže nepřestane navyšovat sekundy, i když počítač vypneme. Nicméně i ten nejlepší RTC čip je občas třeba znovu seřídit. Co s tím?

9fa868b4-7611-417a-a056-fd8d38748f6c
Prototypovací modul s RTC čipem DS3231 a knoflíkovou baterií

Práci s dedikovanými RTC čipem DS3231 jsme si před lety vyzkoušeli i v našem seriálu.

Namísto RTC internetový čas

Naše osobní počítače a laptopy si s tím poradí už od 90. let minulého století. Tehdy jsme se totiž začali masivně připojovat k internetu, takže nebylo nic snazšího, než připojit k webu i nějaké ty atomové hodiny. No, a pomocí jednoduchého protokolu NTP (Network Time Protocol) na bázi UDP pak vytvořit systém, ve kterém se může každý klient na internetu zeptat časového serveru a dostane odpověď, kolik je přesně hodin.

Naše původní hodiny s RTC čipem jsme proto později nahradili destičkou s Wi-Fi čipem ESP8266 nebo novější generací ESP32, připojili se k místní bezdrátové síti a začali zobrazovat na segmentovém displeji čas, který program každých 300 sekund synchronizoval s předdefinovaným serverem NTP.

No jo, ale co když není po ruce internet?

Jenže máme léto, mnozí z nás vyrazí na chalupy a k moři, a byť je dnes Wi-Fi prakticky všudypřítomné, co si tak hodiny opět vylepšit a nahradit jejich internetovou synchronizaci času ještě trošku něčím jiným?

Něčím, co dnes umí každý chytrý mobilní telefon a každé sportovní hodinky. Ano, správně, jak už jsem napověděl v nadpisu dnešního článku, dalším kvalitním zdrojem přesného času – a ke všemu prakticky s globálním pokrytím – je GNSS.

Tedy globální družicové polohové systémy nehledě na to, jestli to bude americké GPS, evropské Galileo, ruský GLONASS, nebo třeba čínské BeiDou.

GPS přijímač NEO-6M v ceně lepšího piva

Stará dobrá gépéeska nemusí sloužit jen k určení přesné polohy, ale právě i času, jehož přenos je jednodušší, takže jej i starší přijímač získá mnohem dříve, než první úspěšný fix – plné určené polohy, ke kterému je třeba získat signál hned z několika družic v okolí, což nemusí být vždy nejsnazší.

40560c8d-bf73-4b71-a2b5-fb9d6f6e704c
Prototypovací modul s GPS čipem NEO-6M

Základní přijímač GNSS už zároveň dávno není žádným luxusním zbožím. Já nakonec v krabici se svými cetkami našel starý prototypovací modul s GPS čipem NEO-6M od švýcarského U-bloxu a miniaturní modul s čínským GNSS čipem ATGM336H-5N-31, který vedle GPS přijímá data také z už zmíněného BeiDou.

c6e4436d-3502-4114-a13b-585e8bc499ea24741fd8-701a-46fd-bac0-41f152dcae9e
O něco drobnější přijímač ATGM336H-5N-31

Zatímco prototypovací moduly s NEO-6M (respektive jejich klony) seženete na AliExpressu a jemu podobných už od 70 korun, ty s čipem ATGM336H-5N-31 přijdou na necelé dvě stovky.

Přijímač vypisuje textové zprávy NMEA 0183

Obě destičky zvládnou 3V a 5V napájení a mají vyvedené piny pro sériovou linku UART, na které se po spojení výchozí rychlostí 9600 b/s začnou vypisovat textové zprávy ve formátu NMEA 0183.

Začátečníkům, kteří s nimi ještě nepracovali, doporučím stažení bezplatného programu u-center od zmíněného U-bloxu, který je vypíše do svého textového logu, a pokud budou obsahovat zmínku o poloze a času, zobrazí také analogové hodiny a puntík na mapě.

2c146d3b-19a9-4fee-9864-df865ee81cc6
Aplikace u-blox, výpis zpráv ve formátu NMEA a jednoduchá vizualizace

Prototypovací modul můžete s počítačem spojit skrze USB/UART převodník, anebo v něj proměníte třeba právě destičku Arduino apod., která bude přijímat zprávy z GNSS přijímače a obratem je přeposílat skrze vlastní USB do počítače.

Zpráva GPGGA obsahuje čas

Čip NEO-6M na sériové lince periodicky zasílá hned několik zpráv, které obsahují časový údaj v základním časovém pásmu UTC (GMT+0).  Patří mezi ně GPGGA a GPRMC.

Věta ve formátu GPGGA obsahuje údaje o GPS fixu a v praxi může vypadat třeba takto:

$GPGGA,113557.00,4911.78308,N,01636.42022,E,1,04,2.71,235.0,M,42.3,M,,*54

Všimněte si, že jednotlivé hodnoty jsou oddělené čárkou a i bez dokumentace jednotlivých políček (viz odkazy výše) jistě každý rozklíčuje první tři hodnoty, obsahují totiž UTC čas, zeměpisnou šířku a zeměpisnou délku:

  • 113557.00 představuje UTC čas 11:35:37 (takže 13:35:37 SELČ)
  • 4911.78308,N představuje 49° 11,78308' severní šířky
  • 01636.42022,E představuje 16° 36,42022' východní délky

Zpráva GPRMC nabízí čas a datum

Věta GPRMC obsahuje vedle zeměpisných souřadnic a času ještě aktuální kalendářní datum a pro stejný případ výše by vypadala takto:

$GPRMC,113558.00,A,4911.78334,N,01636.41891,E,1.041,,100722,,,A*78

Na prvním, druhém a třetím políčku jsou opět čas a geografické souřadnice, v závěru si ale všimněte textového řetězce 100722, který představuje datum ve formátu DDMMRR, tedy 10. 07. 2022.

Přesný čas získáme rychleji než GPS fix

Pro náš dnešní příklad je klíčové, že zprávy GPRMC a GPGGA satelitní přijímač zasílá skrze sériovou linku nepřetržitě, i když nebudou kompletní. A jelikož k výpočtu přesného času a data není třeba tolik družic jako k vypočítání geografické polohy, políčka s časem a datem získáme mnohem dříve. V ideálním případě během několika sekund.

Nekompletní zpráva GPGGA může vypadat třeba takto:

$GPGGA,115044.00,,,,,0,00,99.99,,,,,,*63

Všimněte si ale , že už obsahuje korektní časový údaj 115044.0, tedy 11:50:44 UTC.

Nekompletní zpráva GPRMC pak bude vypadat zase takto:

$GPRMC,115045.00,V,,,,,,,100722,,,N*7F

Byť údaje o zeměpisné šířce a délce chybějí, čas (115045.00) a datum (100722) tam už jsou, takže je můžeme zobrazit na displeji, anebo použit jen k seřízení softwarových hodin.

Modul ATGM336H-5N-31 navíc podporuje zprávu ZDA, která obsahuje výhradně časové údaje. Na čipech NEO-6x je až ve vyšších verzích. Jelikož ATGM336H-5N-31 pracuje s vícero družicovými systémy, namísto GP (GPS) je zpráva uvozená znaky GN (kombinace): GNZDA. V praxi může vypadat třeba takto:

$GNZDA,174555.000,10,07,2022,00,00*4B

Tedy 17:45:55, 10.07. 2022

1dc6fe5c-5985-4676-a4d0-ee03ddb0494d
Časové zprávy GNZDA z čipu ATGM336H-5N-31

GPS čas použijeme pro seřízení hodin

O samotné počítání reálného času a jeho synchronizaci, pakliže dorazí NMEA zpráva s časovým údajem, se už postará některá z mnoha knihoven pro Arduino a řízení času. V našem původním příkladu s NTP serverem jsme použili Arduino Time Library od Paula Stoffregena, a tak se ji budeme držet i v tomto pokračování.

ec7c8ffb-5c69-481b-8a14-6bd9abed47b5
Původní internetové hodiny jsme doplnili o modul laciného přijímače NEO-6M s tolik typickou anténou pro satelitní příjem GNSS

Z předchozího projektu s hodinami použijeme i veškerý hardware, který se nám vejde do tištěné krabičky z 3D tiskárny. Tedy malou prototypovací řídící desku Wemos D1 Mini (respektive laciný klon), 24bodový prstenec s adresovatelnými RGB LED (WS2812B), který bude představovat sekundovou ručičku, a segmentový displej s řadičem TM1637.

Jak všechny tyto součástky oživit, se dočtete zde

Jedinou hardwarovou změnou bude připojení GPS modulu NEO-6M na sériovou linku desky D1 Mini. Jelikož budeme z přijímače pouze číst, stačí nám jednosměrná komunikace, a tak jeho pin TX propojíme s pinem RX na desce.

Ovšem pozor při nahrávání nového firmwaru z PC. V tu chvíli musí být přijímač odpojený, protože by došlo ke komunikační kolizi. Sériový pin TX na hlavní desce zůstane volný, a tak můžeme stále použít sériové spojení do PC a posílat do něj zprávy, které nám pomohou při ověřování funkce.  

Vše se nám i s novou součástkou vejde do krytu vyrobeného na 3D tiskárně:

Budeme čekat, dokud nedorazí GPRMC

Kód bude tentokrát mnohem jednodušší, nebudeme se totiž vůbec připojovat k Wi-Fi a serverům na protokolu NTP. Namísto toho budeme po nastartování všech komponent čekat, dokud z GPS přijímače nedorazí textový řádek začínající výrazem $GPRMC.

c28eb33d-b2a6-4e34-afd4-03814c41b3d9
Pokud zachytíme zprávu GPRMC, vytáhneme z ní čas a datum a seřídíme softwarové hodiny běžící na čipu hlavní desky. Hodiny budou po prvotním seřízení pracovat i bez GPS

Jakmile se tak stane, projdeme jednotlivé položky oddělené čárkou a přečteme ty, které obsahují časový údaj a datum. Dekódujeme z nich číselné hodnoty a pomocí funkce setTime z knihovny Paula Stoffregena seřídíme čas.

Pomocí druhé funkce adjustTime pak provedeme korektní posun na naše aktuální časové pásmo, protože GPS čas je ve výchozí zóně UTC (GMT+0), zatímco letní Česko se nachází v zóně GMT+2.

Pro jednoduchost příkladu bude tento posun pevnou součástí kódu, není ale nic snazšího, než měnit zónu automaticky v případě, že přijímač úspěšně vypočítá geografickou polohu, no a zapínání a vypínání letního času pak podle známého data.

Za pár sekund přesný čas bez RTC a Wi-Fi

Družicový přijímač NEO-6M stojí pár korun a je už staršího data, takže studený start může zvláště v místnosti zabrat nějaký čas, u nás v kanceláři to ale vždy trvalo nejvýše pár desítek sekund. Opět zopakuji, že k zachycení zprávy $GPRMC nepotřebujeme kompletní GPS fix, takže by to mělo být relativně rychlé.

A kdybyste měli hodiny snad kdesi ve sklepě, jednoduše je vždy seřídíte tak, že je na pár minut vynesete na zahradu. Jakmile náš program zachytí časový údaj, hodiny seřídí a opět začnou zobrazovat přesný čas z kosmu.

Kód dnešní ukázky pro Arduino

/*
   Hodiny s prstencem a GPS casem

   Pouzite knihovny:
   Adafuit NeoPixel, LED prstenec: https://github.com/adafruit/Adafruit_NeoPixel
   TimeLib, softwarove hodiny: https://github.com/PaulStoffregen/Time
   TM1637, segmentovy displej: https://github.com/Seeed-Studio/Grove_4Digital_Display

   POUZITE KNIHOVNY VYSE STAHUJTE JAKO ZIP PRIMO Z GITHUBU
   NEKTERE DOHLEDATE I VE SPRAVCI KNIHOVEN ARDUINO, 
   ALE OBCAS SE JEDNA O STARE A NEKOMPATIBILNI VERZE

   Stazene knihovny v Arduinu nainstalujete z listy:
   Projekt-Pridat knihovnu-Pridat ZIP knihovnu

   Pouzita deska:
   Wemos D1 Mini a kompatibilni klony

   Kryt pro 3D tisk:
   www.tinkercad.com/things/9lU8NQrdpXy
*/

#include <Adafruit_NeoPixel.h> // Prstenec
#include <TM1637.h> // Segmentovy displej
#include <TimeLib.h> // Pocitani casu
#include <Ticker.h> // ESP8266 asynchronni casovac

int casovaZona = 2; // Casova zona, mame letni cas, takze GMT+2

// Pomocne textove retezce pro gps hodnoty
String gpsradek;
String gpscas;
String gpsdatum;

Adafruit_NeoPixel prstenec(24, D5, NEO_GRB + NEO_KHZ800); // Prstenec je pripojeny na GPIO D5
TM1637 displej(D1, D2); // Segmentovy displej je pripojeny na GPIO D1 (CLK) a D2 (DIO)
Ticker ticker; // Obvjekt asynchronniho casovace

// Pole s RGB barvami 24 jednotek LED prstence
uint8_t pixely[24][3] = {
  {0, 50, 0},
  {0, 50, 0},
  {0, 50, 0},
  {0, 50, 0},
  {0, 50, 0},
  {0, 50, 0},
  {0, 0, 50},
  {0, 0, 50},
  {0, 0, 50},
  {0, 0, 50},
  {0, 0, 50},
  {0, 0, 50},
  {50, 0, 0},
  {50, 0, 0},
  {50, 0, 0},
  {50, 0, 0},
  {50, 0, 0},
  {50, 0, 0},
  {50, 50, 50},
  {50, 50, 50},
  {50, 50, 50},
  {50, 50, 50},
  {50, 50, 50},
  {50, 50, 50}
};

uint8_t dvojtecka = 1; // Promenna ridici blikani dvojtecky segmentoveho displeje
time_t staryCas = 0; // Pomocna promenna pro porovnani, jeslti uz se zmenil cas ve smycce loop

// Funkce, jejimz periodickym volanim postupne spiname jednotlive LED prstence
// Pouzijeme pro efekt uvodni animace pri hledani GPS signalu
void cekani() {
  static uint8_t pozice = 0;
  prstenec.clear();
  prstenec.setPixelColor(pozice, prstenec.Color(0x00, 0xFF, 0x00));
  prstenec.show();
  pozice++;
  if (pozice == 24) pozice = 0;
}

// Funkce pro zjisteni GPS casu
// Funkce neblouje, je tedy treba ji volat stale dokola
bool zjistiGPSCas() {
  // Pokud mame seriova data
  while (Serial.available()) {
    // Precti cely radek
    gpsradek = Serial.readStringUntil('\n');
    // Pokud radek zacina vyrazem $GPRMC, je to veta obsahujici cas a datum
    if (gpsradek.startsWith("$GPRMC")) {
      // Pro kontrolu jej vypis zpet do seriove linky
      Serial.println(gpsradek);
      // Vynulujeme pomocne tetxove retzece pro cas a datum
      gpsdatum = "";
      gpscas = "";
      // Hodnoty na radku jsou oddelene carkou
      // Projdeme tedy znak po znaku a budeme pocitat carky
      // Po prvni carce zacina cas, po devate datum (cislujeme od nuly)
      uint8_t index = 0;
      for (uint8_t i = 0; i < gpsradek.length(); i++) {
        if (gpsradek[i] == ',') index++;
        if (index == 0) gpscas = gpsradek.substring(i + 2, i + 8);
        if (index == 8) gpsdatum = gpsradek.substring(i + 2, i + 8);
      }

      // Zkusime textove hodnoty casu a dala prevest na cisla
      int hodina = gpscas.substring(0, 2).toInt();
      int minuta = gpscas.substring(2, 4).toInt();
      int sekunda = gpscas.substring(4, 6).toInt();
      int den = gpsdatum.substring(0, 2).toInt();
      int mesic = gpsdatum.substring(2, 4).toInt();
      int rok = gpsdatum.substring(4, 6).toInt() + 2000;

      // Pokud se to podarilo a radek obsahoval validni udaje
      if (den > 0 && mesic > 0) {
        // Pro kontrolu vypiseme aktualni cas a datum do seriove linky
        Serial.printf("%d:%d:%d, %d.%d. %d\r\n", hodina, minuta, sekunda, den, mesic, rok);
        // Seridime softwarove hodiny
        setTime(hodina, minuta, sekunda, den, mesic, rok);
        // Nastavime korektni casove pasmo (GMT+2 pro SELC, GMT+1 pro SEC)
        adjustTime(casovaZona * SECS_PER_HOUR);
        // Vratime true
        return true;
      }
    }
  }
  // Pokud jsme cas a datum nezjistili, vratime false
  return false;
}



// Hlavni funkce setup se spousti hned po startu cipu
void setup() {
   // Nastartujeme seriovou linku do GPS prijimace rychlosti 9 600 b/s
  Serial.begin(9600);
  delay(1000); // Chvili pockame

  Serial.println("Startuji prstenec");
  prstenec.begin(); // Pripravime prstenec
  prstenec.setBrightness(20); // Nastavime relativne nizky jas, aby krabicka nezarila prilis
  prstenec.clear(); // Vypneme vsechny LED
  prstenec.show(); // Zobrazime prstenec

  Serial.println("Startuji segmentovy displej");
  displej.init(); // Nastartujeme segmentovy displej
  displej.set(7); // Nastavime nejvyssi jas (0-7)
  displej.point(dvojtecka); // Vykreslime dvojtecku mezi hodinami a minutami

  Serial.println("Cekam na GPS data");

  // Vyhradime si pamet pro textove retezce, ktere pouzivame v dekoderu GPS zprav
  // Zajistime tim, aby se nam nefragmentovala RAM jejich castym vytvarenim
  gpsradek.reserve(250);
  gpscas.reserve(20);
  gpsdatum.reserve(20);

  // Spustime asynchronni casovac rychlosti 10 Hz, ktery bude volat stale dokola funkci cekani
  // To je nase uvodni animace
  ticker.attach(0.1, cekani);
  // Stale dokola budeme volat funkci zjistiGPSCas, dokud  neziska cas a nevrati true
  while (!zjistiGPSCas()) delay(1);
  // Ukoncime casovac s animaci
  ticker.detach();
}

// Nekonecna smycka loop se spusti po zpracovani funkce setup a jeji obsah se opakuje stale dokola
void loop() {
  // Zkusime zjistit GPS cas
  zjistiGPSCas();
  // Zna uz casova knihovna cas?
  if (timeStatus() != timeNotSet) {
    /* Smycku loop nechceme zbytecne blokovat prikazem delay,
       a tak porovname aktualni cas s tim starym ulozenym v pomocne knihovne,
       a pokud se uz zmenil (pribyla sekunda), tprve tehdy
       vypiseme zpravu do seriove linky s aktualnim casem
    */
    if (now() != staryCas) {
      staryCas = now();

      // Prohod nastaveni dvojtecky v segmentovem displeji, takze bude blikat
      displej.point(dvojtecka ^= 1);

      // Vypiseme aktualni cas na segmentovy displej
      char txtHodiny[5];
      sprintf(txtHodiny, "%02d%02d", hour(), minute());
      displej.displayStr(txtHodiny, 0);

      // Na prstenci zobrazime aktualni pocet sekund
      // Prstenec ma jen 24 LED, prepocitame proto rozsah 0-59 na 0-23
      prstenec.clear();
      for (uint8_t i = 0; i < map(second(), 0, 59, 0, 23); i++)
        prstenec.setPixelColor(i, prstenec.Color(pixely[i][0], pixely[i][1], pixely[i][2]));
      prstenec.show();
    }
  }
}

Určitě si přečtěte

Články odjinud