[124] Zegar z sieci cz. 2

[124] Zegar z sieci cz. 2

Zegar z sieci należy traktować wyłącznie jako źródło czasu dla zegara lokalnego – ale o tym za chwilę, bo do poprzedniego projektu przydałoby się jeszcze dołożyć kalendarz. To takie proste nie będzie, z jednym wyjątkiem: dniem tygodnia, bo biblioteka go zwraca. W praktycznych rozwiązaniach wystarczy ten element – w ten sposób możemy zbudować na przykład tygodniowy budzik. Dobranie się do dnia, miesiąca i roku wymaga wykorzystania czasu uniksowego, o którym niedawno pisałem, a który tutaj także jest dostępny. Za pomocą rozbudowanych algorytmów można otrzymać te dane, lecz tutaj to zagadnienie pominę.


#include <WiFi.h>       // Biblioteka obsługująca WiFi
#include <WiFiUdp.h>    // Biblioteka obsługująca protokół UDP
#include <NTPClient.h>  // Biblioteka pobierająca czas z sieci.

const char* nazwaSieciWiFi = "nazwaSieciWiFi";                         // Nazwa sieci WiFi
const char* hasloSieciWiFi = "hasloSieciWiFi";                         // Hasło sieci WiFi
const int przesuniecieCzasu = 7200;                                    // Różnica w sekundach między czasem UTC (dla południka zerowego i czasu zimowego).
WiFiUDP zrodloCzasu;                                                   // Nazwa obiektu dla serwera czasu.
NTPClient timeClient(zrodloCzasu, "pool.ntp.org", przesuniecieCzasu);  // Adres serwera czasu.

#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

const char dniTygodnia[] = { "NiPnWtSrCzPtSo" };  // Tablica z dniami tygodnia.

void setup() {
  lcd.begin(16, 2);                            // Inicjuj wyświetlacz LCD
  lcd.setCursor(0, 0);                         // Ustaw kursor.
  lcd.print("Lacze sie z WiFi");               // Wyślij tekst.
  lcd.setCursor(0, 1);                         // Ustaw kursor.
  lcd.print(nazwaSieciWiFi);                   // Wyślij nazwę sieci WiFi.
  WiFi.begin(nazwaSieciWiFi, hasloSieciWiFi);  // Łącz się z siecią WiFi.
  while (WiFi.status() != WL_CONNECTED) {      // Czekaj na połączenie.
    delay(250);                                // Opóźnienie dla stabilizacji procesu łączenia.
  }
  timeClient.begin();  // Inicjuj klienta serwera czasu.
  delay(2000);         // Opóźnienie dla ustabilizowania się połączenia.
  lcd.clear();         // Wyczyść ekran.
}
void loop() {
  timeClient.update();                   // Pobierz czas z serwera czasu.
  byte godzina = timeClient.getHours();  // Pobierz elementy czasu.
  byte minuta = timeClient.getMinutes();
  byte sekunda = timeClient.getSeconds();
  byte tydzien = timeClient.getDay();

  lcd.setCursor(2, 0);                      // Ustaw kursor.
  lcd.print(dniTygodnia[tydzien * 2]);      // Wzorzec dnia tygodnia (pierwsza litera).
  lcd.print(dniTygodnia[tydzien * 2 + 1]);  // Wzorzec dnia tygodnia (druga litera).
  lcd.print(" ");                           // Wyświetl spację.
  if (godzina < 10) lcd.print("0");         // Wstaw zero dla wartości mniejszych od 10
  lcd.print(godzina);                       // Wyświetl godzinę.
  lcd.print(":");                           // Wyświetl dwukropek.
  if (minuta < 10) lcd.print("0");
  lcd.print(minuta);
  lcd.print(":");
  if (sekunda < 10) lcd.print("0");
  lcd.print(sekunda);
  delay(1000);
}

Numer dnia tygodnia zwraca instrukcja getDay. Nieszczęśliwie zerowym dniem jest niedziela, pierwszym – poniedziałek i tak dalej. Zadeklarowałem więc tablicę dniTygodnia z dwuliterowymi skrótami w takiej właśnie kolejności. W sekcji wyłuskiwania danych pojawiła się nowa linia, analogiczna do trzech pozostałych. Nazwę dni tygodnia umieścimy przed datą, wyciągając z tablicy obie litery za pomocą prostego wzoru. Po skompilowaniu możemy cieszyć się zegarem z dniem tygodnia.

Idea tu prezentowana jest jednak w założeniach nieprawidłowa. Podobnie jak to było z GPS-em i DCF77, dane o czasie powinniśmy traktować jako element synchronizujący lokalny zegar, a nie napędzający go w nieskończonej pętli. Powód jest prosty: mogą nastąpić przerwy w dostępie do sieci czy przekłamania, uniemożliwiające prawidłowy odczyt i taki zegar po prostu się zatrzyma. Co prawda biblioteka potrafi sama nawiązać połączenie w przypadku jego utraty, ale w tym czasie zegar powinien pracować. Połączymy więc dwa projekty, wykorzystując opisany przeze mnie niedawno zegar RTC, który znajdziemy w Uno R4

#include <WiFi.h>       // Biblioteka obsługująca WiFi
#include <WiFiUdp.h>    // Biblioteka obsługująca protokół UDP
#include <NTPClient.h>  // Biblioteka pobierająca czas z sieci.

const char* nazwaSieciWiFi = "Syntezator";                             // Nazwa sieci WiFi
const char* hasloSieciWiFi = "Tramwaj1";                               // Hasło sieci WiFi
const int przesuniecieCzasu = 7200;                                    // Różnica w sekundach między czasem UTC (dla południka zerowego i czasu zimowego).
WiFiUDP zrodloCzasu;                                                   // Nazwa obiektu dla serwera czasu.
NTPClient timeClient(zrodloCzasu, "pool.ntp.org", przesuniecieCzasu);  // Adres serwera czasu.

#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

const char dniTygodnia[] = { "NiPnWtSrCzPtSo" };  // Tablica z dniami tygodnia.
#include "RTC.h"                                  // Biblioteka obsługująca zegar RTC
byte godzina;
byte minuta;
byte sekunda;
byte tydzien;

void setup() {
  lcd.begin(16, 2);                            // Inicjuj wyświetlacz LCD
  lcd.setCursor(0, 0);                         // Ustaw kursor.
  lcd.print("Lacze sie z WiFi");               // Wyślij tekst.
  lcd.setCursor(0, 1);                         // Ustaw kursor.
  lcd.print(nazwaSieciWiFi);                   // Wyślij nazwę sieci WiFi.
  WiFi.begin(nazwaSieciWiFi, hasloSieciWiFi);  // Łącz się z siecią WiFi.
  while (WiFi.status() != WL_CONNECTED) {      // Czekaj na połączenie.
    delay(250);                                // Opóźnienie dla stabilizaczji procesu łączenia.
  }
  RTC.begin();         // Inicjuj sensor zegara.
  timeClient.begin();  // Inicjuj klienta serwera czasu.
  delay(2000);         // Opóźnienie dla ustabilizowania się połączenia.
  lcd.clear();         // Wyczyść ekran.
}
void loop() {
  timeClient.update();                                                                                                                                                       // Pobierz czas z serwera czasu.
  RTCTime zapiszCzas(1, Month::JANUARY, 2025, timeClient.getHours(), timeClient.getMinutes(), timeClient.getSeconds(), DayOfWeek::SUNDAY, SaveLight::SAVING_TIME_INACTIVE);  // Ustaw: dzień miesiąca, miesiąc - słownie, rok, godziny, minuty, sekundy, dzień tygodnia - słownie, aktywność czasu letniego.
  RTC.setTime(zapiszCzas);                                                                                                                                                   // Wyśli dane do RTC

  tydzien = timeClient.getDay();            // Pozyskaj dzień tygodnia do bezpośredniego wyświetlenia.
  lcd.setCursor(2, 0);                      // Ustaw kursor.
  lcd.print(dniTygodnia[tydzien * 2]);      // Wzorzec dnia tygodnia (pierwsza litera).
  lcd.print(dniTygodnia[tydzien * 2 + 1]);  // Wzorzec dnia tygodnia (druga litera).

  do {                                 // Nie wychodź z pętli przed 59 sekundą.
    RTCTime odczytajCzas;              // Powołaj bufor na czas odczytany z układu RTC.
    RTC.getTime(odczytajCzas);         // Załaduj go wartościami.
    godzina = odczytajCzas.getHour();  // Pobierz elementy czasu.
    minuta = odczytajCzas.getMinutes();
    sekunda = odczytajCzas.getSeconds();

    lcd.setCursor(5, 0);               // Ustaw kursor.
    if (godzina < 10) lcd.print("0");  // Wstaw zero dla wartości mniejszych od 10
    lcd.print(godzina);                // Wyświetl godzinę.
    lcd.print(":");                    // Wyświetl dwukropek.
    if (minuta < 10) lcd.print("0");
    lcd.print(minuta);
    lcd.print(":");
    if (sekunda < 10) lcd.print("0");
    lcd.print(sekunda);
    delay(950);            // Wyświetlanie wystarczy ograniczyć do niecałej sekundy.
  } while (sekunda &lt; 59);  // Nie wychodź z pętli przed 59 sekundą.
}

Zaczniemy więc od dołożenia biblioteki obsługującej zegar RTC.h oraz przekształcenia zmiennych czasu w globalne, gdyż zaraz nam się to przyda. We wstępnej części programu musimy zainicjować RTC siedzące w Arduino. Problem z tą biblioteką jest taki, że dni tygodnia, a także – tutaj niewykorzystywane – nazwy miesięcy są reprezentowane przez łańcuchy tekstów, niekompatybilnych z naszą tablicą. Oczywiście można napisać konwerter, ale nie ma potrzeby – zaraz napiszę dlaczego.

Ideą tego szkicu jest wyświetlanie czasu w pętli z odświeżaniem sekundowym w oparciu o dane z RTC, ale już odświeżanie tych danych w pętli minutowej. Innymi słowy, raz na minutę program będzie opuszczał pętlę główną i zaciągał czas z internetu – nie częściej. Do tego celu użyłem rzadko przydającej się instrukcji do… while. Wygląda to tak, że najpierw wykonuje się wszystko, co znajdziemy w nawiasie wąsatym i na końcu sprawdza się warunek w nawiasie normalnym – tym po instrukcji while. W tym wypadku jest to warunek nieprzekroczenia 59 sekundy. Gdy wartość sekund będzie wynosiła właśnie tyle, pętla zostanie opuszczona i program przejdzie na początek pętli głównej. Tam sobie zaciągnie dane z sieci i zapisze je w rejestrach RTC.

Wspomniany problem niekompatybilnych zapisów dnia tygodnia rozwiązać najłatwiej, wprost przepisując na ekran jego wartość wedle sposobu z poprzednich szkiców. Co prawda przez minutę musimy uważać, by nie uszkodzić tej pozycji na wyświetlaczu, bo nie jest ona rysowana w cyklu sekundowym. Dzień tygodnia zmienia się raz na dobę, więc wystarczy, że będzie odświeżany co minutę.

Ostatnia uwaga: skąd tu opóźnienie nieco niższe niż sekunda? Całość trochę trwa, zwłaszcza operacje sieciowe. Jeśli ustawimy dokładnie sekundę opóźnienia, może się trafić, że po wyjściu z pętli zgubi nam się na chwilę sekundnik. Program, z racji współpracy z siecią, jest dość ciężki, potrzebuje 70 kB Flasha i 12 kB RAM-u, ale przeznaczony jest na układy, które tego dobra mają sporo.

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