Arduino odmierza czas

7 II 2022. Tutaj znajduje się źródło omawianego kodu.

W dawnych czasach, gdy na świecie pojawiły się komputery ośmiobitowe, nastał ogólnoświatowy szał nauki programowania w języku BASIC. Albowiem właściwie wszystkie te komputery po włączeniu odzywały się do ludzi w tym języku, utożsamianym z naturalnym środowiskiem łączącym komputer z człowiekiem.

O BASIC-u warto byłoby zrobić niezależnie opracowanie. Tutaj wspomnę tylko, że o ile miał on swoje zalety, a jego echa możemy znaleźć nawet w Arduino, miał też swoje wady, z których główną było przyzwyczajanie do bałaganiarstwa. I właśnie nasz program z poprzedniej historii jest ideologicznie typowym programem pisanym w BASIC-u z lat osiemdziesiątych. Bierzmy zatem miotłę i posprzątajmy nieco.

Już nie BASIC.

Największym problemem początkujących programistów jest myślenie „tu i teraz”. Rozentuzjazmowani podłączają różne elementy, by potem nimi sterować, odwołując się do konkretnych portów. Aż nagle trzeba element przesunąć, bo albo się nie mieści, albo trzeba podłączyć jeszcze coś, albo urządzenie trafiło na miesiąc do szuflady i kabelki porozłączały się. W każdym z tych wypadków należałoby prześledzić cały kod od początku do końca i zaktualizować co gdzie zostało przypięte. W przypadku takich małych programów jak „WC komputer”, nie będzie to wielki problem. Co innego, gdy linii będzie kilka tysięcy.

Zasadą numer jeden jest stworzenie odrębnego bloku deklaracji. Tam, między innymi wpisuje się konkretne adresy przyłączonych elementów i nazywa się je jakimiś przyjaznymi nazwami. Jak się to robi? Oto nasza nowa linia.

int PstryczekPierwszy = 8;

#include <LiquidCrystal.h>

LiquidCrystal lcd(2, 3, 4, 5, 6, 7);

void setup()
{
  lcd.begin(16, 2);
  lcd.setCursor(0, 1);
  lcd.print("  WC komputer   ");
  pinMode(8, INPUT_PULLUP);
  pinMode(9, INPUT_PULLUP);
  pinMode(10, INPUT_PULLUP);
}

void loop()
{
  if (digitalRead(8) == LOW) {
    lcd.home();
    lcd.print("    Zajete!     ");
  }
  if (digitalRead(9) == LOW) {
    lcd.home();
    lcd.print(" Jeszcze moment ");
  }
  if (digitalRead(10) == LOW) {
    lcd.home();
    lcd.print("Ciezka sprawa...");
  }
  if (digitalRead(8) == HIGH && digitalRead(9) == HIGH && digitalRead(10) == HIGH) {
    lcd.home();
    lcd.print("    Wolne :)    ");
  }
}

Deklaracje mają tę zaletę, że można używać „ludzkich” nazw, jakie komu pasują. Zatem pstryczek siedzący na porcie ósmym, którym będziemy wywoływać komunikat „Zajęte”, nazwę sobie PstryczekPierwszy. Trzeba tylko pamiętać, że w nazwach nie można używać spacji, a ilość znaków poza literami i cyframi jest ograniczona do kilku.

Tu jest ósemka, bo pstryczek jest podłączony do ósmego portu. A co oznacza tajemnicze int na początku? Przy deklaracjach należy określić co deklarujemy. W tym przypadku jest to numer portu, wynoszący konkretnie 8, a int oznacza liczbę całkowitą, jakich to używa się do numerowania portów na przykład. Jeszcze do tego wrócę, na razie poprzestańmy na tym, że tym zmiennej musi być podany i bez int wyskoczy nam błąd.

No to teraz można ulepszyć nasz program. Wszystkie odwołania do portu ósmego zamieniamy odwołaniami przez zmienną o miłej dla nas nazwie, czyli PstryczekPierwszy.

int PstryczekPierwszy = 8;

#include <LiquidCrystal.h>

LiquidCrystal lcd(2, 3, 4, 5, 6, 7);

void setup()
{
  lcd.begin(16, 2);
  lcd.setCursor(0, 1);
  lcd.print("  WC komputer   ");
  pinMode(PstryczekPierwszy, INPUT_PULLUP);
  pinMode(9, INPUT_PULLUP);
  pinMode(10, INPUT_PULLUP);
}

void loop()
{
  if (digitalRead(PstryczekPierwszy) == LOW) {
    lcd.home();
    lcd.print("    Zajete!     ");
  }
  if (digitalRead(9) == LOW) {
    lcd.home();
    lcd.print(" Jeszcze moment ");
  }
  if (digitalRead(10) == LOW) {
    lcd.home();
    lcd.print("Ciezka sprawa...");
  }
  if (digitalRead(PstryczekPierwszy) == HIGH && digitalRead(9) == HIGH && digitalRead(10) == HIGH) {
    lcd.home();
    lcd.print("    Wolne :)    ");
  }
}

Teraz to samo zrobimy z pstryczkami podłączonymi do portów 9 i 10. I oczywiście w treści programu zastępujemy bezpośrednie odwołania do tych portów nazwami naszych nowych zmiennych.

int PstryczekPierwszy = 8;
int PstryczekDrugi = 9;
int PstryczekTrzeci = 10;

#include <LiquidCrystal.h>

LiquidCrystal lcd(2, 3, 4, 5, 6, 7);

void setup()
{
  lcd.begin(16, 2);
  lcd.setCursor(0, 1);
  lcd.print("  WC komputer   ");
  pinMode(PstryczekPierwszy, INPUT_PULLUP);
  pinMode(PstryczekDrugi, INPUT_PULLUP);
  pinMode(PstryczekTrzeci, INPUT_PULLUP);
}

void loop()
{
  if (digitalRead(PstryczekPierwszy) == LOW) {
    lcd.home();
    lcd.print("    Zajete!     ");
  }
  if (digitalRead(PstryczekDrugi) == LOW) {
    lcd.home();
    lcd.print(" Jeszcze moment ");
  }
  if (digitalRead(PstryczekTrzeci) == LOW) {
    lcd.home();
    lcd.print("Ciezka sprawa...");
  }
  if (digitalRead(PstryczekPierwszy) == HIGH && digitalRead(PstryczekDrugi) == HIGH && digitalRead(PstryczekTrzeci) == HIGH) {
    lcd.home();
    lcd.print("    Wolne :)    ");
  }
}

Można śmiało skompilować program i wysłać do Arduino. Zaręczam, że będzie działać dokładnie tak samo. Teoretycznie w naszym programie przybyło linii, więc może się wydawać bardziej skomplikowany, ale przybyła też jedna wspaniała właściwość: jeśli sobie zmienimy porty, do których podłączymy nasze przełączniki, wystarczy wprowadzić zmianę tylko na samym początku programu, zostawiając całą jego resztę w spokoju. I uwierzcie, że w przypadku dużych aplikacji bez tej możliwości wszystko by się niesamowicie skomplikowało.

Teraz trzeba zrobić to samo z adresami wyprowadzeń wyświetlacza. Co prawda odwołujemy się do niego tylko raz, na początku programu, ale jednak zasada obowiązuje niezależnie od tego ile razy z niej potem skorzystamy. I co najważniejsze, tak będzie czytelniej.

int PstryczekPierwszy = 8;
int PstryczekDrugi = 9;
int PstryczekTrzeci = 10;
int lcdRS = 2;
int lcdEN = 3;
int lcdD4 = 4;
int lcdD5 = 5;
int lcdD6 = 6;
int lcdD7 = 7;
int lcdKolumny = 16;
int lcdWiersze = 2;

#include <LiquidCrystal.h>

LiquidCrystal lcd(lcdRS, lcdEN, lcdD4, lcdD5, lcdD6, lcdD7);

Ale zaraz, przecież mamy tutaj jeszcze jedną wartość, która się nie zmienia w całym programie: to ilość kolumn i wierszy wyświetlacza. Także i to możemy zadeklarować na wstępie.

int PstryczekPierwszy = 8;
int PstryczekDrugi = 9;
int PstryczekTrzeci = 10;
int lcdRS = 2;
int lcdEN = 3;
int lcdD4 = 4;
int lcdD5 = 5;
int lcdD6 = 6;
int lcdD7 = 7;
int lcdKolumny = 16;
int lcdWiersze = 2;

#include <LiquidCrystal.h>

LiquidCrystal lcd(lcdRS, lcdEN, lcdD4, lcdD5, lcdD6, lcdD7);

void setup()
{
  lcd.begin(lcdKolumny, lcdWiersze);
  lcd.setCursor(0, 1);
  lcd.print("  WC komputer   ");
  pinMode(PstryczekPierwszy, INPUT_PULLUP);
  pinMode(PstryczekDrugi, INPUT_PULLUP);
  pinMode(PstryczekTrzeci, INPUT_PULLUP);
}

Zauważmy, że wymyśliłem sobie tu pewien klucz: nazwy składają się z części związanej z obsługiwanym przedmiotem, czyli lcd oraz funkcjami wyprowadzeń albo cechami wyświetlacza. Ale to jest mój klucz, każdy może sobie wymyślić swój, byle tylko był konsekwentny.

Komentarze i pętle.

Jak widać, używałem zwykle nazw, które coś nam już mówią na wstępie. Lecz to za mało. Drugą naczelną zasadą jest nanoszenie komentarzy czy też opisów. I tak naprawdę to jest robota dla nas samych — z przyszłości, gdy będziemy wracali do własnych dzieł, nie rozumiejąc czasem zupełnie co my — z przeszłości — mieliśmy na myśli. Komentarze rozpoczynają się od dwóch ukośników i ładnie jest, gdy układa się je w rządku, choć nie ma takiego obowiązku.

int PstryczekPierwszy = 8;                                   // Używamy trzech pstryczków do wywoływania komunikatów.
int PstryczekDrugi = 9;
int PstryczekTrzeci = 10;
int lcdRS = 2;                                               // "lcd" to oczywiście wyświetlacz, którego wyprowadzenia ponazywaliśmy tutaj.
int lcdEN = 3;
int lcdD4 = 4;
int lcdD5 = 5;
int lcdD6 = 6;
int lcdD7 = 7;
int lcdKolumny = 16;                                         // "Kolumny" to ilość kolumn na naszym wyświetlaczu,
int lcdWiersze = 2;                                          // a "Wiersze" to ilość wierszy.

#include <LiquidCrystal.h>                                   // To jest biblioteka obsługi wyświetlacza, którą należy zaimportować poleceniem #include.

LiquidCrystal lcd(lcdRS, lcdEN, lcdD4, lcdD5, lcdD6, lcdD7); // Tutaj dostarcza się tej bibliotece wiedzy, gdzie co jest podłączone.

void setup()                                                 // Tu zaczyna się program. Ta część wykona się jednorazowo.
{
  lcd.begin(lcdKolumny, lcdWiersze);                         // Inicjujemy wyświetlacz, powiadamiając go o ilości kolumn i wierszy.
  lcd.setCursor(0, 1);                                       // Ustawiamy kursor na samym początku drugiego wiersza wyświetlacza.
  lcd.print("  WC komputer   ");                             // Wysyłamy do wyświetlacza tekst "WC komputer".
  pinMode(PstryczekPierwszy, INPUT_PULLUP);                  // Deklarujemy linie pstryczków jako wejścia podciągnięte wewnętrznie do wysokiego stanu.
  pinMode(PstryczekDrugi, INPUT_PULLUP);
  pinMode(PstryczekTrzeci, INPUT_PULLUP);
}

void loop()                                                  // Tutaj zaczyna się część programu wykonująca się bez końca.
{
  if (digitalRead(PstryczekPierwszy) == LOW) {               // Sprawdzamy czy wciśnięto pstryczek pierwszy.
    lcd.home();                                              // Ustawiamy kursor na początku wyświetlacza.
    lcd.print("    Zajete!     ");                           // i wysyłamy do niego tekst "Zajęte".
  }
  if (digitalRead(PstryczekDrugi) == LOW) {                  // To samo robimy z pstryczkiem drugim...
    lcd.home();
    lcd.print(" Jeszcze moment ");
  }
  if (digitalRead(PstryczekTrzeci) == LOW) {                 // oraz z trzecim.
    lcd.home();
    lcd.print("Ciezka sprawa...");
  }
  if (digitalRead(PstryczekPierwszy) == HIGH && digitalRead(PstryczekDrugi) == HIGH && digitalRead(PstryczekTrzeci) == HIGH) {
    lcd.home();                                              // A tutaj lądujemy, gdy żaden z pstryczków nie jest wciśnięty.
    lcd.print("    Wolne :)    ");
  }
}

A co tam należy napisać? A tego to ja nie wiem. Niech każdy sobie opisuje wszystko tak, jak mu potrzeba. Tutaj wykazałem się tak zwanym przesadyzmem, komentując także rzeczy oczywiste, ale na potrzeby tego tylko filmu.

Na chwilę zapomnijmy o optymalizacji zapisu i prześledźmy jak działa nasz program w głównej pętli. Dopóki wciskamy tylko jeden przycisk, wszystko jest w porządku. Program będzie w nieskończoność ustawiał kursor na początku i wypisywał komunikat „Zajęte”. Działa to poprawnie, ale niezgodnie ze sztuką, ponieważ wypisywanie w kółko tego samego miliony razy nie ma sensu. Wystarczyłoby to zrobić raz i zaczekać, aż przycisk zostanie puszczony albo wciśnięty zostanie inny.

A co będzie, gdy wciśniemy dwa albo trzy przyciski naraz? Katastrofa, bowiem najpierw wyświetli się napis „Zajęte” i prawie natychmiast „Jeszcze moment”.

W praktyce będzie to wyglądać tak.

Oczywiście można ostrzec użytkowników, by nie wciskali dwóch przycisków jednocześnie, lecz kolejna generalna zasada informatyków mówi, że jak coś się da zrobić, to ktoś to zrobi na pewno i trzeba się na taką sposobność zabezpieczyć. Zaradźmy temu jakoś. Poznamy teraz kolejną funkcję, o nazwie while.

void loop()                                                  // Tutaj zaczyna się część programu wykonująca się bez końca.
{
  if (digitalRead(PstryczekPierwszy) == LOW) {               // Sprawdzamy czy wciśnięto pstryczek pierwszy.
    lcd.home();                                              // Ustawiamy kursor na początku wyświetlacza.
    lcd.print("    Zajete!     ");                           // i wysyłamy do niego tekst "Zajęte".
  }
  while (digitalRead(PstryczekPierwszy) == LOW) {}           // Czekaj, dopóki przycisk nie zostanie puszczony.

  if (digitalRead(PstryczekDrugi) == LOW) {                  // To samo robimy z pstryczkiem drugim...
    lcd.home();
    lcd.print(" Jeszcze moment ");
  }
  if (digitalRead(PstryczekTrzeci) == LOW) {                 // oraz z trzecim.
    lcd.home();
    lcd.print("Ciezka sprawa...");
  }
  if (digitalRead(PstryczekPierwszy) == HIGH && digitalRead(PstryczekDrugi) == HIGH && digitalRead(PstryczekTrzeci) == HIGH) {
    lcd.home();                                              // A tutaj lądujemy, gdy żaden z pstryczków nie jest wciśnięty.
    lcd.print("    Wolne :)    ");
  }
}

Jest to funkcja warunkowa, podobnie jak if. Jeśli tę znaną nam już można przetłumaczyć na ludzkie: „jeśli warunek z nawiasu okrągłego jest spełniony, zrób to, co w nawiasie klamrowym albo idź sobie dalej”, tutaj tłumaczenie wygląda mniej więcej tak: „dopóki warunek z nawiasu okrągłego zachodzi, wykonuj rozkazy z nawiasu klamrowego”.

No dobrze, tylko nasz klamrowy nawias nie zawiera niczego! Ale to nie jest błąd: jeśli nie trzeba nic robić, a jedynie czekać, rozkaz nadal działa, tyle że czeka i nic więcej nie robi. Tak długo, aż warunek przestanie zachodzić, czyli dopóki pstryczek pierwszy nie zostanie puszczony. Pozostaje nam tylko dopisać takie same rozkazy w pozostałych dwóch blokach, zmieniając oczywiście numery pstryczków.

void loop()                                                  // Tutaj zaczyna się część programu wykonująca się bez końca.
{
  if (digitalRead(PstryczekPierwszy) == LOW) {               // Sprawdzamy czy wciśnięto pstryczek pierwszy.
    lcd.home();                                              // Ustawiamy kursor na początku wyświetlacza.
    lcd.print("    Zajete!     ");                           // i wysyłamy do niego tekst "Zajęte".
  }
  while (digitalRead(PstryczekPierwszy) == LOW) {}           // Czekaj, dopóki przycisk nie zostanie puszczony.

  if (digitalRead(PstryczekDrugi) == LOW) {                  // To samo robimy z pstryczkiem drugim...
    lcd.home();
    lcd.print(" Jeszcze moment ");
  }
  while (digitalRead(PstryczekDrugi) == LOW) {}

  if (digitalRead(PstryczekTrzeci) == LOW) {                 // oraz z trzecim.
    lcd.home();
    lcd.print("Ciezka sprawa...");
  }
  while (digitalRead(PstryczekTrzeci) == LOW) {}
  if (digitalRead(PstryczekPierwszy) == HIGH && digitalRead(PstryczekDrugi) == HIGH && digitalRead(PstryczekTrzeci) == HIGH) {
    lcd.home();                                              // A tutaj lądujemy, gdy żaden z pstryczków nie jest wciśnięty.
    lcd.print("    Wolne :)    ");
  }
}

Sprawdźmy jak to działa. Gdy wciśniemy przycisk pierwszy, program będzie trwał bez końca w miejscu, w którym występuje pierwszy while. Wcześniej oczywiście wypisze tekst „Zajęte”, ale jednorazowo. Potem będzie gonił bez końca w tym jednym miejscu, sprawdzając wyłącznie czy przycisk wciąż jest wciśnięty. Żadne dane nie będą już do wyświetlacza płynąć, a całość — w jakimś tam minimalnym stopniu, ale jednak — nie będzie konsumować tyle energii.

Gdybyśmy w tym czasie równocześnie wcisnęli przycisk drugi, nic się nie stanie, bo program jest zainteresowany wyłącznie puszczeniem przycisku pierwszego. Natomiast gdy to nastąpi, od razu przejdzie do obsługi przycisku drugiego i dopóki tego nie puścimy, nie będzie reagował na przycisk pierwszy ani trzeci. I tak dalej.

Teraz program jest napisany dużo ładniej, choć nadal do ideału brakuje mu sporo. Możemy od razu coś poprawić. Pierwszą rzeczą będzie dorzucenie dodatkowych przerw między blokami, żeby było przejrzyściej. Druga operacja będzie skutkiem naszej dalszej analizy programu.

Skoro zmienione przez nas wszystkie trzy bloki kontrolują wszystkie możliwe stany pstryczków wciśniętych, pozostał nam jeszcze tylko jeden stan: gdy wszystkie pstryczki są puszczone. Najważniejszy wniosek jest taki, że wcale nie musimy tego sprawdzać. Robią to za nas zmienione bloki, filtrując wszelkie inne stany.

void loop()                                                  // Tutaj zaczyna się część programu wykonująca się bez końca.
{
  if (digitalRead(PstryczekPierwszy) == LOW) {               // Sprawdzamy czy wciśnięto pstryczek pierwszy.
    lcd.home();                                              // Ustawiamy kursor na początku wyświetlacza.
    lcd.print("    Zajete!     ");                           // i wysyłamy do niego tekst "Zajęte".
  }
  while (digitalRead(PstryczekPierwszy) == LOW) {}           // Czekaj, dopóki przycisk nie zostanie puszczony.

  if (digitalRead(PstryczekDrugi) == LOW) {                  // To samo robimy z pstryczkiem drugim...
    lcd.home();
    lcd.print(" Jeszcze moment ");
  }
  while (digitalRead(PstryczekDrugi) == LOW) {}

  if (digitalRead(PstryczekTrzeci) == LOW) {                 // oraz z trzecim.
    lcd.home();
    lcd.print("Ciezka sprawa...");
  }
  while (digitalRead(PstryczekTrzeci) == LOW) {}

  lcd.home();
  lcd.print("    Wolne :)    ");                             // A tutaj lądujemy, gdy żaden z pstryczków nie jest wciśnięty.
}

Zatem możemy śmiało usunąć tę długą linię, która znajdowała się na dole. Jednak nie jest dobrze. Gdy pstryczki były wciśnięte, ekran rysował się jednorazowo. Napis „wolne” natomiast będzie się rysował w kółko i bez końca, jak to było na początku. Brakuje funkcji nakazującej „nicnierobienie” w oczekiwaniu na wciśnięcie przycisków. Myślę, że wielu już wie, jak ją stworzyć: rozbudowując warunek while na analizę wszystkich trzech pstryczków jednocześnie.

void loop()                                                  // Tutaj zaczyna się część programu wykonująca się bez końca.
{
  if (digitalRead(PstryczekPierwszy) == LOW) {               // Sprawdzamy czy wciśnięto pstryczek pierwszy.
    lcd.home();                                              // Ustawiamy kursor na początku wyświetlacza.
    lcd.print("    Zajete!     ");                           // i wysyłamy do niego tekst "Zajęte".
  }
  while (digitalRead(PstryczekPierwszy) == LOW) {}           // Czekaj, dopóki przycisk nie zostanie puszczony.

  if (digitalRead(PstryczekDrugi) == LOW) {                  // To samo robimy z pstryczkiem drugim...
    lcd.home();
    lcd.print(" Jeszcze moment ");
  }
  while (digitalRead(PstryczekDrugi) == LOW) {}

  if (digitalRead(PstryczekTrzeci) == LOW) {                 // oraz z trzecim.
    lcd.home();
    lcd.print("Ciezka sprawa...");
  }
  while (digitalRead(PstryczekTrzeci) == LOW) {}

  lcd.home();
  lcd.print("    Wolne :)    ");                             // A tutaj lądujemy, gdy żaden z pstryczków nie jest wciśnięty.
  while (digitalRead(PstryczekPierwszy) == HIGH && digitalRead(PstryczekDrugi) == HIGH && digitalRead(PstryczekTrzeci) == HIGH) {}
}

Jest już smacznie, przynajmniej na płaszczyźnie optymalizacji działań. Odłóżmy na chwilę naprawianie świata, poznamy nową, ciekawą funkcję.

Długo będziesz tam jeszcze siedział?

O bibliotekach już było, gdy potrzebowaliśmy nawiązać współpracę z wyświetlaczem. Użyliśmy wtedy biblioteki LiquidCrystal, którą twórcy Arduino IDE ściągnęli domyślnie. Dziś przyjrzymy się kolejnej bibliotece, która zajmuje się sprawami związanymi z funkcjami zegarowymi. Na pokładzie nie znajdziemy żadnej, więc tym razem należy kliknąć w Narzędzia, następnie Zarządzaj bibliotekami i wyszukać taką o nazwie Time. Tak na marginesie, bibliotek zegarowych znajdziemy kilka, ale ja dziś wybrałem tą.

Po kliknięciu w Instaluj będziemy mogli dopisać kolejny wiersz #include, który dołączy co dopiero ściągniętą bibliotekę. Przy okazji, klikając w More info, możemy poczytać sobie informację o bibliotece, jak jej używać i co ona potrafi. Zróbmy sobie zatem użytek z pustych przebiegów, gdy to nasze urządzenie nic nie robi, tylko czeka. Na początek dopiszemy takie linie w miejscu programu, gdy nic się nie dzieje i żaden pstryczek nie jest wciśnięty.

int PstryczekPierwszy = 8;                                   // Używamy trzech pstryczków do wywoływania komunikatów.
int PstryczekDrugi = 9;
int PstryczekTrzeci = 10;
int lcdRS = 2;                                               // "lcd" to oczywiście wyświetlacz, którego wyprowadzenia ponazywaliśmy tutaj.
int lcdEN = 3;
int lcdD4 = 4;
int lcdD5 = 5;
int lcdD6 = 6;
int lcdD7 = 7;
int lcdKolumny = 16;                                         // "Kolumny" to ilość kolumn na naszym wyświetlaczu,
int lcdWiersze = 2;                                          // a "Wiersze" to ilość wierszy.

#include <LiquidCrystal.h>                                   // To jest biblioteka obsługi wyświetlacza, którą należy zaimportować poleceniem #include.
#include <TimeLib.h>                                         // To jest biblioteka obsługi zegara, którą należy zaimportować poleceniem #include.

LiquidCrystal lcd(lcdRS, lcdEN, lcdD4, lcdD5, lcdD6, lcdD7); // Tutaj dostarcza się tej bibliotece wiedzy, gdzie co jest podłączone.

void setup()                                                 // Tu zaczyna się program. Ta część wykona się jednorazowo.
{
  lcd.begin(lcdKolumny, lcdWiersze);                         // Inicjujemy wyświetlacz, powiadamiając go o ilości kolumn i wierszy.
  lcd.setCursor(0, 1);                                       // Ustawiamy kursor na samym początku drugiego wiersza wyświetlacza.
  lcd.print("  WC komputer   ");                             // Wysyłamy do wyświetlacza tekst "WC komputer".
  pinMode(PstryczekPierwszy, INPUT_PULLUP);                  // Deklarujemy linie pstryczków jako wejścia podciągnięte wewnętrznie do wysokiego stanu.
  pinMode(PstryczekDrugi, INPUT_PULLUP);
  pinMode(PstryczekTrzeci, INPUT_PULLUP);
}

void loop()                                                  // Tutaj zaczyna się część programu wykonująca się bez końca.
{
  if (digitalRead(PstryczekPierwszy) == LOW) {               // Sprawdzamy czy wciśnięto pstryczek pierwszy.
    lcd.home();                                              // Ustawiamy kursor na początku wyświetlacza.
    lcd.print("    Zajete!     ");                           // i wysyłamy do niego tekst "Zajęte".
  }
  while (digitalRead(PstryczekPierwszy) == LOW) {            // Czekaj, dopóki przycisk nie zostanie puszczony.
    lcd.setCursor(0, 1);                                     // Ustawiamy kursor na samym początku drugiego wiersza wyświetlacza.
    lcd.print(" Juz ");                                      // Napiszemy "Już...
    lcd.print(now());                                        // Wyciągniemy ilość sekund od zresetowania timera
    lcd.print(" sekund   ");                                 // I dopiszemy "... sekund".
  }
  if (digitalRead(PstryczekDrugi) == LOW) {                  // To samo robimy z pstryczkiem drugim...
    lcd.home();
    lcd.print(" Jeszcze moment ");
  }
  while (digitalRead(PstryczekDrugi) == LOW) {
    lcd.setCursor(0, 1);
    lcd.print(" Juz ");
    lcd.print(now());
    lcd.print(" sekund   ");
  }
  if (digitalRead(PstryczekTrzeci) == LOW) {                 // oraz z trzecim.
    lcd.home();
    lcd.print("Ciezka sprawa...");
  }
  while (digitalRead(PstryczekTrzeci) == LOW) {
    lcd.setCursor(0, 1);
    lcd.print(" Juz ");
    lcd.print(now());
    lcd.print(" sekund   ");
  }
  lcd.home();
  lcd.print("    Wolne :)    ");                             // A tutaj lądujemy, gdy żaden z pstryczków nie jest wciśnięty.
  while (digitalRead(PstryczekPierwszy) == HIGH && digitalRead(PstryczekDrugi) == HIGH && digitalRead(PstryczekTrzeci) == HIGH) {
    lcd.setCursor(0, 1);
    lcd.print(" Juz ");
    lcd.print(now());
    lcd.print(" sekund   ");
  }
}

Lcd.setCursor — to już znamy, ustawia kursor w konkretnym miejscu. Lcd.print także — rysuje teksty na wyświetlaczu. Pojawiła się nowa funkcja o nazwie now() Dokumentacja biblioteki Time wspomina, iż zwraca ona ilość sekund od zainicjowania zegara.

Teraz na dole wyświetlacza będzie się nam wyświetlał tekst informujący ile sekund minęło od włączenia urządzenia.

Linie te powielimy w każdym miejscu, gdzie wystąpiła funkcja while, żeby sekundnik wyświetlał się nam w każdej pozycji przełączników. Ale żeby ta nowa właściwość naszego komputera toaletowego miała sens, dodamy jeszcze jedną funkcję. Brzmi ona setTime (0) i zeruje wartość zegara.

void loop()                                                  // Tutaj zaczyna się część programu wykonująca się bez końca.
{
  if (digitalRead(PstryczekPierwszy) == LOW) {               // Sprawdzamy czy wciśnięto pstryczek pierwszy.
    lcd.home();                                              // Ustawiamy kursor na początku wyświetlacza.
    lcd.print("    Zajete!     ");                           // i wysyłamy do niego tekst "Zajęte".
    setTime (0);                                             // Zerujemy czas.
  }
  while (digitalRead(PstryczekPierwszy) == LOW) {            // Czekaj, dopóki przycisk nie zostanie puszczony.
    lcd.setCursor(0, 1);                                     // Ustawiamy kursor na samym początku drugiego wiersza wyświetlacza.
    lcd.print(" Juz ");                                      // Napiszemy "Już...
    lcd.print(now());                                        // Wyciągniemy ilość sekund od zresetowania timera
    lcd.print(" sekund   ");                                 // I dopiszemy "... sekund".
  }
  if (digitalRead(PstryczekDrugi) == LOW) {                  // To samo robimy z pstryczkiem drugim...
    lcd.home();
    lcd.print(" Jeszcze moment ");
    setTime (0);
  }
  while (digitalRead(PstryczekDrugi) == LOW) {
    lcd.setCursor(0, 1);
    lcd.print(" Juz ");
    lcd.print(now());
    lcd.print(" sekund   ");
  }
  if (digitalRead(PstryczekTrzeci) == LOW) {                 // oraz z trzecim.
    lcd.home();
    lcd.print("Ciezka sprawa...");
    setTime (0);
  }
  while (digitalRead(PstryczekTrzeci) == LOW) {
    lcd.setCursor(0, 1);
    lcd.print(" Juz ");
    lcd.print(now());
    lcd.print(" sekund   ");
  }
  lcd.home();
  lcd.print("    Wolne :)    ");
  setTime (0);                                               // A tutaj lądujemy, gdy żaden z pstryczków nie jest wciśnięty.
  while (digitalRead(PstryczekPierwszy) == HIGH && digitalRead(PstryczekDrugi) == HIGH && digitalRead(PstryczekTrzeci) == HIGH) {
    lcd.setCursor(0, 1);
    lcd.print(" Juz ");
    lcd.print(now());
    lcd.print(" sekund   ");
  }
}

Jeśli dodamy ją we wszystkich miejscach po wykryciu wciśnięcia bądź puszczenia któregoś z przycisków, dostaniemy interesujący stoper, który będzie odmierzał poszczególne etapy przesiadywania w miejscu odosobnienia. „Samotność muszli” także będzie rejestrowana, gdy żaden z przycisków nie będzie wciśnięty.

I w ten sposób dorobiliśmy się wersji dwa zero ustępowego komputera. We wszelkich sporach terytorialno — czasowych wyświetlany stoper z pewnością pozwoli rozstrzygnąć je i ustalić toaletowy konsensus. Na koniec dzisiejszej przygody popracujmy raz jeszcze miotłą.

Miotła turbo plus plus.

Spójrzmy na część kodu — jest powtarzalna. A jeśli coś jest powtarzalne, to kolejna naczelna zasada informatyków mówi, że koniecznie trzeba coś z tym zrobić. Wszystko, co się powiela, winno być wydzielone do osobnego bloku, żeby istniało tylko w jednej kopii. A w tych wszystkich dziurach po usunięciu powielonych fragmentów należy wstawić instrukcję, która będzie się do owego bloku odwoływać. Zatem do roboty!

void loop()                                                  // Tutaj zaczyna się część programu wykonująca się bez końca.
{
  if (digitalRead(PstryczekPierwszy) == LOW) {               // Sprawdzamy czy wciśnięto pstryczek pierwszy.
    lcd.home();                                              // Ustawiamy kursor na początku wyświetlacza.
    lcd.print("    Zajete!     ");                           // i wysyłamy do niego tekst "Zajęte".
    setTime (0);                                             // Zerujemy czas.
  }
  while (digitalRead(PstryczekPierwszy) == LOW) {            // Czekaj, dopóki przycisk nie zostanie puszczony.
    lcd.setCursor(0, 1);                                     // Ustawiamy kursor na samym początku drugiego wiersza wyświetlacza.
    lcd.print(" Juz ");                                      // Napiszemy "Już...
    lcd.print(now());                                        // Wyciągniemy ilość sekund od zresetowania timera
    lcd.print(" sekund   ");                                 // I dopiszemy "... sekund".
  }
  if (digitalRead(PstryczekDrugi) == LOW) {                  // To samo robimy z pstryczkiem drugim...
    lcd.home();
    lcd.print(" Jeszcze moment ");
    setTime (0);
  }
  while (digitalRead(PstryczekDrugi) == LOW) {
    lcd.setCursor(0, 1);
    lcd.print(" Juz ");
    lcd.print(now());
    lcd.print(" sekund   ");
  }
  if (digitalRead(PstryczekTrzeci) == LOW) {                 // oraz z trzecim.
    lcd.home();
    lcd.print("Ciezka sprawa...");
    setTime (0);
  }
  while (digitalRead(PstryczekTrzeci) == LOW) {
    lcd.setCursor(0, 1);
    lcd.print(" Juz ");
    lcd.print(now());
    lcd.print(" sekund   ");
  }
  lcd.home();
  lcd.print("    Wolne :)    ");
  setTime (0);                                               // A tutaj lądujemy, gdy żaden z pstryczków nie jest wciśnięty.
  while (digitalRead(PstryczekPierwszy) == HIGH && digitalRead(PstryczekDrugi) == HIGH && digitalRead(PstryczekTrzeci) == HIGH) {
    lcd.setCursor(0, 1);
    lcd.print(" Juz ");
    lcd.print(now());
    lcd.print(" sekund   ");
  }
}

Zapis void już nam się obił o uszy. Dwa takie były obowiązkowe i nawet narzekałem, że stworzono je trochę na siłę. Tym razem void będzie przydatny i pozwoli stworzyć niezależny blok programu, do którego z głównej pętli będzie można przeskakiwać na życzenie.

Void sekundnik() to nazwa naszego bloku, bo żeby był on identyfikowalny, musi posiadać nazwę. Sami ją możemy wymyślić, jaka nam pasuje. Teraz pomiędzy klamry nawiasu wpiszemy instrukcje, które się powtarzają, a z oryginalnych miejsc powtarzające się instrukcje należy usunąć.

Tak, właśnie napisaliśmy własną funkcję w Arduino i nazwaliśmy ją sekundnik(). Gdziekolwiek teraz w programie umieścimy ją, program przeskoczy do tego miejsca, zrobi wszystko, co tam jest napisane, po czym powróci z powrotem.

void loop()                                                  // Tutaj zaczyna się część programu wykonująca się bez końca.
{
  if (digitalRead(PstryczekPierwszy) == LOW) {               // Sprawdzamy czy wciśnięto pstryczek pierwszy.
    lcd.home();                                              // Ustawiamy kursor na początku wyświetlacza.
    lcd.print("    Zajete!     ");                           // i wysyłamy do niego tekst "Zajęte".
    setTime (0);                                             // Zerujemy czas.
  }
  while (digitalRead(PstryczekPierwszy) == LOW) {            // Czekaj, dopóki przycisk nie zostanie puszczony.
    sekundnik();                                             // Rysujemy sekundnik.
  }
  if (digitalRead(PstryczekDrugi) == LOW) {                  // To samo robimy z pstryczkiem drugim...
    lcd.home();
    lcd.print(" Jeszcze moment ");
    setTime (0);
  }
  while (digitalRead(PstryczekDrugi) == LOW) {
    sekundnik();
  }
  if (digitalRead(PstryczekTrzeci) == LOW) {                 // oraz z trzecim.
    lcd.home();
    lcd.print("Ciezka sprawa...");
    setTime (0);
  }
  while (digitalRead(PstryczekTrzeci) == LOW) {
    sekundnik();
  }
  lcd.home();
  lcd.print("    Wolne :)    ");
  setTime (0);                                               // A tutaj lądujemy, gdy żaden z pstryczków nie jest wciśnięty.
  while (digitalRead(PstryczekPierwszy) == HIGH && digitalRead(PstryczekDrugi) == HIGH && digitalRead(PstryczekTrzeci) == HIGH) {
    sekundnik();
  }
}

void sekundnik()                                             // Tu się rysuje sekundnik.
{
  lcd.setCursor(0, 1);                                       // Ustawiamy kursor na samym początku drugiego wiersza wyświetlacza.
  lcd.print(" Juz ");                                        // Napiszemy "Już...
  lcd.print(now());                                          // Wyciągniemy ilość sekund od zresetowania timera
  lcd.print(" sekund   ");                                   // I dopiszemy "... sekund".
}

Spójrzmy, jak nasz program ślicznie zyskał na czytelności. Ale to nie wszystko. Sprawne oko dojrzy jeszcze jeden fragment powtarzający się czterokrotnie. No to do roboty. Tworzymy blok podprogramu o nazwie void wyzeruj()

void loop()                                                  // Tutaj zaczyna się część programu wykonująca się bez końca.
{
  if (digitalRead(PstryczekPierwszy) == LOW) {               // Sprawdzamy czy wciśnięto pstryczek pierwszy.
    wyzeruj();                                               // Ustawiamy kursor i zerujemuy zegar.
    lcd.print("    Zajete!     ");                           // i wysyłamy do niego tekst "Zajęte".
  }
  while (digitalRead(PstryczekPierwszy) == LOW) {            // Czekaj, dopóki przycisk nie zostanie puszczony.
    sekundnik();                                             // Rysujemy sekundnik.
  }
  if (digitalRead(PstryczekDrugi) == LOW) {                  // To samo robimy z pstryczkiem drugim...
    wyzeruj();
    lcd.print(" Jeszcze moment ");
  }
  while (digitalRead(PstryczekDrugi) == LOW) {
    sekundnik();
  }
  if (digitalRead(PstryczekTrzeci) == LOW) {                 // oraz z trzecim.
    wyzeruj();
    lcd.print("Ciezka sprawa...");
  }
  while (digitalRead(PstryczekTrzeci) == LOW) {
    sekundnik();
  }
  wyzeruj();
  lcd.print("    Wolne :)    ");                             // A tutaj lądujemy, gdy żaden z pstryczków nie jest wciśnięty.
  while (digitalRead(PstryczekPierwszy) == HIGH && digitalRead(PstryczekDrugi) == HIGH && digitalRead(PstryczekTrzeci) == HIGH) {
    sekundnik();
  }
}

void sekundnik()                                             // Tu się rysuje sekundnik.
{
  lcd.setCursor(0, 1);                                       // Ustawiamy kursor na samym początku drugiego wiersza wyświetlacza.
  lcd.print(" Juz ");                                        // Napiszemy "Już...
  lcd.print(now());                                          // Wyciągniemy ilość sekund od zresetowania timera
  lcd.print(" sekund   ");                                   // I dopiszemy "... sekund".
}

void wyzeruj()                                               // A tu się ustawia kursor i zeruje zegar.
{
  lcd.home();                                                // Ustawiamy kursor na początku wyświetlacza.
  setTime (0);                                               // Zerujemy czas.
}

Wrzucamy tam dwie powtarzające się instrukcje, a w miejscach, w których mieściły się oryginalnie, stawiamy naszą drugą nową funkcję o wybranej przez nas nazwie, czyli wyzeruj()

I tak to wygląda kod, któremu bliżej do klasyki niż amatorstwa. Nie rozpędzajmy się jednak, nadal jest to nie do końca zgodne ze sztuką programistyczną. Lecz nie wszystko naraz.

W kolejnej historii obiecuję stworzyć coś wreszcie naprawdę użytecznego, bo powiedzmy sobie szczerze: komputer wspomagający optymalne korzystanie z toalety do takich rzeczy nie należy. Zbudujemy sobie prawdziwy budzik.