[124] Zegar z sieci cz. 2
![[124] Zegar z sieci cz. 2](/_next/image?url=https%3A%2F%2Farduino.pl%2Fimgproxy%2FDxIuHYsxb6-zvqCPtJ2ov1ceElynKpoHzMGWi7kLSBg%2Ff%3Awebp%2Fw%3A1200%2FbG9jYWw6Ly8vaW1hZ2VzLzAtNjhhL2VlNzMxL2M3MzViLzAtNjhhZWU3MzFjNzM1YjExNjc0MTAxOS5wbmc%3D.webp&w=3840&q=75)
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 < 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.