[029] Zegar cz. 2
Wróćmy do projektu naszego budzika, a na razie – zegara. Czas na napisanie obsługi klawiatury i zmiany czasu wyświetlanego na prawdziwy. Metod przestawiania zegarów czy budzików jest wiele. Niektóre są denerwujące ze względu na swoje skomplikowanie, ociężałość czy przesuwanie wartości tylko do przodu. Metodą różnych prób wybrałem interfejs, który jest w miarę szybki, ale przede wszystkim intuicyjny.
Jednak na wstępie troszkę przeorganizujemy nasz program, a to dlatego, że do pewnych jego części będziemy się odwoływać zarówno podczas wyświetlania czasu jak i jego ustawiania. Skorzystamy ze znanych już własnych funkcji, czyli podprogramów. W głównej pętli nadal będziemy przekazywać dane o czasie do zmiennej CzasOdczytany, ale już wyświetlanie tych danych na ekranie będzie realizowane podprogramami. I to dwoma, rozbitymi na wyświetlanie godzin i minut oraz osobno, sekund. Dlaczego tak? Za chwilę się wyjaśni.
void loop() {
time_t czasOdczytany = now(); // Zapisz bieżący czas do zmiennej czasOdczytany.
godzina = hour(czasOdczytany); // Nadajemy zmiennej godzina wartość godzin.
minuta = minute(czasOdczytany); // Jak wyżej, tylko dla minut.
sekunda = second(czasOdczytany); // Jak wyżej, tylko dla sekund.
wyswietlCzas(); // Wyświetlamy godziny, dwukropek i minuty...
wyswietlSekundy(); // oraz dwukropek i sekundy.
}
void wyswietlCzas() { // Podprogram wyświetla godziny, dwukropek i minuty.
lcd.home(); // Ustaw kursor na początku.
if (godzina < 10) { // Jeśli liczba godzin jest niższa od 10 to...
lcd.print(F(" ")); // Rysuj dodatkową spację.
}
lcd.print(godzina); // Rysuj liczbę godzin.
lcd.print(F(":")); // Rysuj dwukropek.
if (minuta < 10) { // Jeśli liczba minut jest niższa od 10 to...
lcd.print(F("0")); // Rysuj dodatkowe zero.
}
lcd.print(minuta); // Rysuj liczbę minut.
}
void wyswietlSekundy() { // Podprogram wyświetla dwukropek i sekundy.
lcd.print(F(":")); // Rysuj dwukropek.
if (sekunda < 10) { // Jeśli liczba sekund jest niższa od 10 to...
lcd.print(F("0")); // Rysuj dodatkowe zero.
}
lcd.print(sekunda); // Rysuj liczbę sekund.
}
Trochę teraz będzie zamieszania, ale damy radę. Co nowego doszło? Pętla warunkowa while, która trwa, dopóki nie wciśniemy przycisku pstryczekZmien. Zatem tylko ten przycisk jest nadsłuchiwany i póki nikt go nie wciśnie, program będzie wyświetlał wyłącznie czas bieżący.
void loop() {
while (digitalRead(pstryczekZmien) == HIGH) { // Powtarzamy poniższe działania, dopóki nie zostanie wciśnięty pstryczekZmien.
time_t czasOdczytany = now(); // Zapisz bieżący czas do zmiennej czasOdczytany.
godzina = hour(czasOdczytany); // Nadajemy zmiennej godzina wartość godzin.
minuta = minute(czasOdczytany); // Jak wyżej, tylko dla minut.
sekunda = second(czasOdczytany); // Jak wyżej, tylko dla sekund.
wyswietlCzas(); // Wyświetlamy godziny, dwukropek i minuty...
wyswietlSekundy(); // oraz dwukropek i sekundy.
}
Ciekawie będzie, gdy go wciśniemy.
lcd.clear(); // Wciśnięto przycisk ustawień więc czyścimy wyświetlacz i ustawiamy kursor na początku,
wyswietlCzas(); // Wyświetl ostani aktualny czas (bez sekund).
delay(opoznienieDlugie); // Wprowadź opóźnienie dla stabilnych odczytów pstryczka.
while (digitalRead(pstryczekZmien) == LOW) {} // Czekaj aż pstryczek zostanie puszczony.
while (digitalRead(pstryczekZmien) == HIGH) { // Tu zaczyna się pętla ustawiania czasu.
if (digitalRead(pstryczekPlus) == LOW) { // Jeśli pstryczekPlus jest wciśnięty...
zwiekszCzas(); // zwiększ wartość czasu,
wyswietlCzas(); // wyświetl go na ekranie,
delay(opoznienieDlugie); // wprowadź opóźnienie...
}
while (digitalRead(pstryczekPlus) == LOW) { // i powtarzaj tę procedurę, tylko szybciej.
zwiekszCzas();
wyswietlCzas();
delay(opoznienieKrotkie);
}
if (digitalRead(pstryczekMinus) == LOW) { // Jeśli pstryczekMinus jest wciśnięty...
zmniejszCzas(); // zmniejsz wartość czasu,
wyswietlCzas(); // wyświetl go na ekranie,
delay(opoznienieDlugie); // wprowadź opóźnienie...
}
while (digitalRead(pstryczekMinus) == LOW) { // i powtarzaj tę procedurę, tylko szybciej.
zmniejszCzas();
wyswietlCzas();
delay(opoznienieKrotkie);
}
}
setTime(godzina, minuta, 00, 01, 01, 2000); // Ustaw bieżący czas zgodnie z ustawionym.
delay(opoznienieDlugie); // Wprowadź opóźnienie.
}
Program przejdzie dalej i wyczyści wyświetlacz (lcd.clear). W chwilach, gdy zmienia się w sposób zasadniczy to, co ma się wyświetlić, warto wprowadzać takie polecenia. Czyszczą one ewentualne śmieci, które mogłyby się tam pojawić na skutek jakichś zakłóceń. Nie warto natomiast robić tego w głównej pętli wyświetlania czasu, gdyż jest to operacja długotrwała i w określonych warunkach może powodować migotania znaków. W głównej pętli po prostu nadpisujemy cyferki na ustalonych pozycjach.
Następnie wyświetlamy czas (wyswietlCzas) – ale tylko same godziny i minuty. Ponieważ ekran został wyczyszczony, za minutami nie ma już nic. I tak ma być, bo podczas ustawiania zegara sekundy przeszkadzałyby nam tylko.
Kolejno mamy nową instrukcję (delay(opoznienieDlugie)), która kojarzona jest z prostymi projektami i zwykle jest wyśmiewana przez zaawansowanych programistów. Delay to komenda, która nie robi nic poza blokadą działania mikrokontrolera na pewien czas. Na szczęście nie blokuje go zupełnie i większość programów działających na przerwaniach – jak nasza biblioteka zegarowa – pracuje nadal. Moje stanowisko należy do opinii środka: odradzam używanie tej instrukcji poza oczywistymi sytuacjami, gdy naprawdę potrzeba wstrzymać działanie programu i są to czasy krótkie. Po co nam to zatem? Otóż tam program znajdzie się po wciśnięciu przycisku pstryczekZmien. Po wyczyszczeniu ekranu i wyświetleniu godzin oraz minut może minąć zbyt mało czasu, by przycisk jednoznacznie się uspokoił i nie powodował drgań styków, tak zwanych „iskrzeń”, choć o żadnych iskrzeniach dosłownie nie ma tu mowy. Zwłaszcza tanie mikrostyki są znane z tego, że przez kilkadziesiąt milisekund mogą naprzemiennie włączać obwód i rozłączać go. Jeśli wprowadzimy tutaj pauzę, styki się uspokoją i przycisk jednoznacznie zostanie zakwalifikowany jako wciśnięty. A ponieważ będę z tej pauzy korzystał kilka razy, nie wprowadziłem tutaj wartości, lecz zadeklarowałem ją na początku programu, nazywając ją OpoznienieDlugie.
Równocześnie to opóźnienie pełni jeszcze druga funkcję: zapewnia nam odstęp czasowy pomiędzy zmianami pojedynczymi, precyzyjnymi, a szybkimi, tak zwaną autorepetycją. Zobaczymy to za chwilę. 250 to ¼ sekundy, bowiem argument dla instrukcji Delay podaje się w milisekundach. To nie jest wartość jedynie słuszna, każdy może sobie poeksperymentować i osadzić własną. Należy tylko pamiętać, że przy wartościach większych niż 255 trzeba zmienić typ danych z byte na int.
Gdy wspomniany czas minie, program napotka jeszcze jedną funkcję warunkową while, która każe oczekiwać na puszczenie przycisku pstryczekZmien. Teraz zaczną się schody, bo przybyło dużo, więc analizujemy. Jeśli wciśniemy przycisk pstryczekPlus (if (digitalRead(pstryczekPlus) == LOW)), wywołamy kolejną nową funkcję o nazwie zwiekszCzas.
void zwiekszCzas() { // Funkcja ta zwiększa czas o minutę.
minuta++; // Zwiększ liczbę minut.
if (minuta == 60) { // Jeśli osiągnięto wartość minut równą 60 to...
minuta = 0; // zeruj liczbę minut,
godzina++; // zwiększ liczbę godzin.
if (godzina == 24) { // Jeśli osiągnięto wartość godzin równą 24 to...
godzina = 0; // zeruj liczbę godzin.
}
}
}
minuta++ to na pierwszy rzut oka dziwna instrukcja. Jest to skrót, który każe zmiennej o nazwie minuta wzrosnąć o jeden. Można oczywiście napisać całym zdaniem: niech minuta równa będzie minucie plus jeden, ale taki skrót jest wygodniejszy i informatycy narobili ich pełno. Zatem zwiększamy wartość minut i sprawdzamy, czy aby nie osiągnęła zakazanej wartości równiej 60. Jeśli tak się stanie, liczbę minut należy wyzerować, a powiększyć liczbę godzin. Teraz jeszcze trzeba sprawdzić, czy aby liczba godzin nie osiągnęła wartości nielegalnej, czyli 24. Jeśli tak, to zerujemy ją i to wszystko.
Wracamy do głównej pętli. Wyświetlamy zmodyfikowaną wartość zegara, czekamy chwilkę i sprawdzamy czy przycisk pstryczekPlus wciąż jest wciśnięty. Jeśli tak jest, przechodzimy przez całą procedurę ponownie, ale już pauzy będą teraz szybsze, wyrażone stałą opóźnienieKrótkie. Należy ją dobrać tak, by szybkość zmian po dłuższym trzymaniu przycisków ustawiających czas była najwygodniejsza i jednocześnie zapewniała czytelność wskazań na wyświetlaczu.
Procedura zmniejszająca wartości godzin i minut jest w zasadzie identyczna, tylko symetrycznie odwrócona. Zatem manipulowanie obydwoma przyciskami umożliwi nam sprawne trafienie w wartość, którą będziemy chcieli ustawić. Analizę tych fragmentów pozostawiam czytelnikom, aczkolwiek podpowiem coś, co niekoniecznie może być zrozumiałe.
void zmniejszCzas() { // Funkcja ta zmniejsza czas o minutę.
minuta--; // Zmniejsz liczbę minut.
if (minuta == 255) { // Jeśli osiągnięto wartość minut równą 255 to...
minuta = 59; // ustaw liczbę minut równą 59,
godzina--; // zmniejsz liczbę godzin.
if (godzina == 255) { // Jeśli osiągnięto wartość godzin równą 255 to...
godzina = 23; // Ustaw liczbę godzin równą 23
}
}
}
Minuta z dwoma minusami, jak się można domyślić, to funkcja zmniejszająca wartość zmiennej o jeden. Skąd natomiast wzięła się dziwna wartość równa 255? Otóż zadeklarowaliśmy zmienną minuta jako jednobajtową. I teraz przyda nam się to bardzo. Co się stanie z taką zmienną, gdy od zera odejmiemy jeden? Nie będziemy mieć minus jeden, bo zmienna byte nie przewiduje takiej sytuacji. Ale nie będziemy mieć też błędu. Wartość przewinie się do pozycji 255, bo tak działają operacje na bajtach. Zatem wystarczy wyłapać taką sytuację i zmienić tę wartość na 59, największą legalną ilość minut. Dla godziny będzie to wartość 23. I to jest cała zagadka.
Po puszczeniu dowolnego z przycisków regulujących czas wskazania bieżące zostaną przekazane do biblioteki TimeLib. Służy to tego ta instrukcja: setTime(godzina, minuta, 00, 01, 01, 2000). Zauważmy, że mamy tutaj więcej argumentów: jest jeszcze rok, miesiąc i dzień. Nie używam ich, więc podaję jakiekolwiek sensowne wartości, w tym wypadku 1 stycznia 2000. Co do sekund, założyłem sobie, że wyjście z ustawiania zegara zeruje także sekundnik więc wpisuję tutaj zero.
Teraz jeszcze standardowe opóźnienie i powrót do głównej pętli. Kompilujemy nasz program, wysyłamy do Arduino i możemy cieszyć się zegarem. W kolejnym artykule dopiszemy sobie funkcję budzika, a ja na koniec zamieszczę raz jeszcze kod programu, tym razem w całości.
#include<LiquidCrystal.h> // To jest biblioteka obsługi wyświetlacza.
#include<TimeLib.h> // To jest biblioteka obsługi zegara.
LiquidCrystal lcd(2, 3, 4, 5, 6, 7); // Tutaj dostarcza się bibliotece obsługi wyświetlacza wiedzy, gdzie co jest podłączone.
const byte pstryczekMinus = 8; // Adres pstryczka zmniejszającego wartości.
const byte pstryczekZmien = 9; // Adres pstryczka ustawień.
const byte pstryczekPlus = 10; // Adres pstryczka zwiększającego wartości.
byte godzina; // Zmienna godzin - dla ustawień i wyświetlania.
byte minuta; // Zmienna minut - jak wyżej.
byte sekunda; // Zmienna sekund - jak wyżej.
const byte opoznienieDlugie = 250; // Czas opóźnienia klawiatury.
const byte opoznienieKrotkie = 25; // Czas autorepetycji klawiatury.
void setup() {
lcd.begin(16, 2); // Inicjujemy wyświetlacz, powiadamiając go o ilości kolumn i wierszy.
pinMode(pstryczekMinus, INPUT_PULLUP); // Deklarujemy linie pstryczków jako wejścia podciągnięte wewnętrznie do wysokiego stanu.
pinMode(pstryczekZmien, INPUT_PULLUP);
pinMode(pstryczekPlus, INPUT_PULLUP);
}
void loop() {
while (digitalRead(pstryczekZmien) == HIGH) { // Powtarzamy poniższe działania, dopóki nie zostanie wciśnięty pstryczekZmien.
time_t czasOdczytany = now(); // Zapisz bieżący czas do zmiennej czasOdczytany.
godzina = hour(czasOdczytany); // Nadajemy zmiennej godzina wartość godzin.
minuta = minute(czasOdczytany); // Jak wyżej, tylko dla minut.
sekunda = second(czasOdczytany); // Jak wyżej, tylko dla sekund.
wyswietlCzas(); // Wyświetlamy godziny, dwukropek i minuty...
wyswietlSekundy(); // oraz dwukropek i sekundy.
}
lcd.clear(); // Wciśnięto przycisk ustawień więc czyścimy wyświetlacz i ustawiamy kursor na początku,
wyswietlCzas(); // Wyświetl ostani aktualny czas (bez sekund).
delay(opoznienieDlugie); // Wprowadź opóźnienie dla stabilnych odczytów pstryczka.
while (digitalRead(pstryczekZmien) == LOW) {} // Czekaj aż pstryczek zostanie puszczony.
while (digitalRead(pstryczekZmien) == HIGH) { // Tu zaczyna się pętla ustawiania czasu.
if (digitalRead(pstryczekPlus) == LOW) { // Jeśli pstryczekPlus jest wciśnięty...
zwiekszCzas(); // zwiększ wartość czasu,
wyswietlCzas(); // wyświetl go na ekranie,
delay(opoznienieDlugie); // wprowadź opóźnienie...
}
while (digitalRead(pstryczekPlus) == LOW) { // i powtarzaj tę procedurę, tylko szybciej.
zwiekszCzas();
wyswietlCzas();
delay(opoznienieKrotkie);
}
if (digitalRead(pstryczekMinus) == LOW) { // Jeśli pstryczekMinus jest wciśnięty...
zmniejszCzas(); // zmniejsz wartość czasu,
wyswietlCzas(); // wyświetl go na ekranie,
delay(opoznienieDlugie); // wprowadź opóźnienie...
}
while (digitalRead(pstryczekMinus) == LOW) { // i powtarzaj tę procedurę, tylko szybciej.
zmniejszCzas();
wyswietlCzas();
delay(opoznienieKrotkie);
}
}
setTime(godzina, minuta, 00, 01, 01, 2000); // Ustaw bieżący czas zgodnie z ustawionym.
delay(opoznienieDlugie); // Wprowadź opóźnienie.
}
void zwiekszCzas() { // Funkcja ta zwiększa czas o minutę.
minuta++; // Zwiększ liczbę minut.
if (minuta == 60) { // Jeśli osiągnięto wartość minut równą 60 to...
minuta = 0; // zeruj liczbę minut,
godzina++; // zwiększ liczbę godzin.
if (godzina == 24) { // Jeśli osiągnięto wartość godzin równą 24 to...
godzina = 0; // zeruj liczbę godzin.
}
}
}
void zmniejszCzas() { // Funkcja ta zmniejsza czas o minutę.
minuta--; // Zmniejsz liczbę minut.
if (minuta == 255) { // Jeśli osiągnięto wartość minut równą 255 to...
minuta = 59; // ustaw liczbę minut równą 59,
godzina--; // zmniejsz liczbę godzin.
if (godzina == 255) { // Jeśli osiągnięto wartość godzin równą 255 to...
godzina = 23; // Ustaw liczbę godzin równą 23
}
}
}
void wyswietlCzas() { // Podprogram wyświetla godziny, dwukropek i minuty.
lcd.home(); // Ustaw kursor na początku.
if (godzina < 10) { // Jeśli liczba godzin jest niższa od 10 to...
lcd.print(F(" ")); // Rysuj dodatkową spację.
}
lcd.print(godzina); // Rysuj liczbę godzin.
lcd.print(F(":")); // Rysuj dwukropek.
if (minuta < 10) { // Jeśli liczba minut jest niższa od 10 to...
lcd.print(F("0")); // Rysuj dodatkowe zero.
}
lcd.print(minuta); // Rysuj liczbę minut.
}
void wyswietlSekundy() { // Podprogram wyświetla dwukropek i sekundy.
lcd.print(F(":")); // Rysuj dwukropek.
if (sekunda < 10) { // Jeśli liczba sekund jest niższa od 10 to...
lcd.print(F("0")); // Rysuj dodatkowe zero.
}
lcd.print(sekunda); // Rysuj liczbę sekund.
}