[077] Układamy wiedzę - cz. 3
Komendy, instrukcje, funkcje czy inne licho? Formalnie: instrukcje sterujące, gdyż w Arduino określenie funkcje zarezerwowano dla elementów związanych z obliczeniami, z których część już poznaliśmy. Jednak wciąż siedzi mi w głowie „lista funkcji BASIC-a”, którą pamiętam jeszcze z podstawówki i jakoś wolę to określenie. Mówi się także: komendy, acz środowisko określiło zestaw ów jako control structure. Zamiast skupiać się na tym, spójrzmy lepiej na składniki owej listy.
W zasadzie każda z instrukcji związana jest z jakimś warunkiem, a najbardziej popularna z nich to if Początkującym zawsze tłumaczy się ją w ten sposób: jeśli coś tam, to coś tam. Niezgrabne, ale działa. Czyli: jeśli zachodzi warunek w nawiasie okrągłym, to wykonaj to, co znajdziesz w znajdującym się za nim nawiasie wąsatym. A jeśli nie? To przeskocz dalej. A jak już zrobisz wszystko, co było w nawiasie wąsatym? To także przeskocz dalej.
Oczywiście teraz pokażę jakiś przykład. Do wszystkich przykładów będę używać diody świecącej, siedzącej na porcie 13 i pstryczka, który wystawia wysoki stan na porcie ósmym, bazując na płytce edukacyjnej TME, gdzie nie trzeba już niczego konfigurować. Przykład praktycznego sensu nie będzie mieć, ale jest przejrzysty i łatwo go będzie analizować.
byte pstryczek = 8; // Numer pinu, do którego podłączony jest pstryczek.
byte diodaSwiecaca = 13; // Numer pinu, do którego podłączona jest dioda świecąca.
void setup() {
pinMode(pstryczek, INPUT_PULLUP); // Zadeklaruj port pstryczka jako wejście.
pinMode(diodaSwiecaca, OUTPUT); // Zadeklaruj port diody jako wyjście.
}
void loop() {
if (digitalRead(pstryczek) == HIGH) { // Jeśli pstryczek jest wciśnięty...
digitalWrite(diodaSwiecaca, HIGH); // Włącz diodę.
}
if (digitalRead(pstryczek) == LOW) { // Jeśli pstryczek jest puszczony...
digitalWrite(diodaSwiecaca, LOW); // Wyłącz diodę.
}
}
Przejdźmy zatem do pętli loop i spójrzmy: jeśli – i teraz w nawiasie – odczytany stan pstryczka jest wysoki (później się nad tym jeszcze skupię), to – w drugim nawiasie – wystaw stan wysoki na diodę świecącą, czyli po prostu ją włącz. Gdy program znajdzie się w tamtym miejscu, będzie musiał rozważyć, czy akurat pstryczek jest wciśnięty, czy puszczony. Zauważmy, że zrobi to raz, bardzo szybko i od razu decyzję podejmie. Jeśli był wciśnięty, wykonuje się to, co mamy w nawiasie wąsatym, jeśli nie – program przechodzi do miejsca zaraz za tamtym nawiasem. A tam mamy bliźniaka, czyli taki sam zestaw instrukcji, tylko na odwrót: jeśli pstryczek jest puszczony, dioda ma zostać zgaszona. I tu jest koniec programu, a ponieważ – jak pisałem w poprzednich artykułach, część programu po wyrażeniu loop wykonuje się w nieskończoność, program wraca na początek pętli i znowu sprawdza stan pstryczka.
Słówko o składni. Organizacja Arduino co jakiś czas majstruje z zasadami. Obecnie obowiązują takie jak widzimy, ale ja tu zaraz wykombinuję coś takiego.
byte pstryczek=8; // Numer pinu, do którego podłączony jest pstryczek.
byte diodaSwiecaca=13; // Numer pinu, do którego podłączona jest dioda świecąca.
void setup() {
pinMode(pstryczek,INPUT_PULLUP); // Zadeklaruj port pstryczka jako wejście.
pinMode(diodaSwiecaca,OUTPUT); // Zadeklaruj port diody jako wyjście.
}
void loop() {
if (digitalRead(pstryczek)==1){digitalWrite(diodaSwiecaca,1);} // Jeśli pstryczek jest wciśnięty, włącz diodę.
if (digitalRead(pstryczek)==0){digitalWrite(diodaSwiecaca,0);} // Jeśli pstryczek jest puszczony, wyłącz diodę.
}
Nie tylko poprzestawiałem miejscami elementy programu, ale pozamieniałem LOW i HIGH na zera i jedynki. Logicznie jednak jest to nadal ten sam program, tylko wygląda inaczej. Dla mnie taki układ jest o wiele bardziej czytelny, ale większość protestuje, woląc kod rozwlekły. Więc zaznaczam tylko, że tak można i wracam do zasad obowiązujących.
Rozwińmy funkcję if. Dotąd było: jeśli coś tam, to coś tam. A ja proponuję dodać: a jeśli nie coś tam, to coś tam innego. Niejasne? Rozjaśni się teraz, gdy nieco przerobimy ten prymitywny program.
void loop() {
if (digitalRead(pstryczek) == HIGH) { // Jeśli pstryczek jest wciśnięty...
digitalWrite(diodaSwiecaca, HIGH); // Włącz diodę.
} else { // A jeśli nie...
digitalWrite(diodaSwiecaca, LOW); // Wyłącz diodę.
}
}
Najpierw pomyślmy co robił: jeśli przycisk był wciśnięty, włączał diodę, jeśli był puszczony – wyłączał ją. Zmieńmy to zdanie na inne w brzmieniu: jeśli przycisk jest wciśnięty, włącz diodę, a jeśli nie – wyłącz ją. Owo „a jeśli nie” sprowadza się do kolejnej komendy: else Wstawiamy ją po nawiasie wąsatym i wymaga zawsze, by gdzieś wcześniej stało if Po niej znowu mamy nawias wąsaty, w którym znajdzie się program alternatywny wobec programu wykonującego się, gdy warunek zachodził.
Zauważmy, że po else nie ma żadnych warunków i nie może być. Jedynym warunkiem jest zaprzeczenie temu, który znajdował się wcześniej. Jeśli chcielibyśmy stworzyć jakiś inny warunek, trzeba zastosować dwie funkcje if, jak w pierwszym przykładzie. Ale to nie koniec.
const byte potencjometr = A1; // Numer pinu, do którego podłączymy ślizgacz potencjometru.
byte diodaCzerwona = 9; // Numer pinu, do którego podłączona jest dioda czerwona.
byte diodaZielona = 10; // Numer pinu, do którego podłączona jest dioda zielona.
void setup() {
pinMode(diodaCzerwona, OUTPUT); // Zadeklaruj porty diod jako wyjścia.
pinMode(diodaZielona, OUTPUT);
}
void loop() {
if (analogRead(potencjometr) > 767) { // Jeśli potencjometr jest ustawiony poza 3/4 zakresu...
digitalWrite(diodaCzerwona, HIGH); // Włącz czerwoną diodę.
} else if (analogRead(potencjometr) > 511) { // A jeśli ustawiony jest poza połową zakresu...
digitalWrite(diodaZielona, HIGH); // Włącz zieloną diodę.
} else { // A jeśli nie...
digitalWrite(diodaCzerwona, LOW); // Wyłącz obie diody.
digitalWrite(diodaZielona, LOW);
}
}
Stwórzmy nowy program. Teraz będziemy sterować dwoma diodami: czerwoną i zieloną, a zamiast pstryczków wykorzystamy potencjometr. Założenie jest takie: po przekroczeniu połowy zakresu włącza się zielona dioda, po ¾ – dodatkowo czerwona. Gasną, gdy zejdziemy poniżej połowy zakresu.
Pierwszy warunek rozpoznaje stan powyżej ¾ zakresu. Kto nie wie, za chwilę się dowie, a na razie niech uwierzy: ¾ zakresu zaczyna się od wartości 768 i trwa do końca, czyli do 1023. Jeśli odczytane napięcie należy do tego przedziału, włączamy diodę czerwoną.
Jeśli tak nie jest, program przechodzi do sprawdzania następnego warunku za pomocą konstrukcji else if I tutaj także sprawdzamy zakres, ale ustawiamy go niżej, na co najmniej połowę, czyli musi być wyższy od 511. Jeśli tak będzie, włączymy diodę zieloną. Zauważmy, że nie musimy ograniczać przedziału od góry do ¾, bo to i tak będzie wyłapywać linia wcześniejsza.
Jeśli i ten warunek nie zajdzie, wykona się ostatni blok po słowie else bez if, którą to konstrukcję już znamy, a tam stoi, by wszystko pogasić. Trzeba mocno uważać przy tworzeniu takich rozbudowanych warunków. Zauważmy, że gdy mamy pierwszy warunek spełniony, wszystkie kolejne będą już ignorowane. A to znaczy, że jeśli po resecie będziemy kręcić potencjometrem od zera w prawo, najpierw będzie ciemno, potem zapali się dioda zielona, a na końcu także czerwona. A to dlatego, że przechodziliśmy po kolei przez wszystkie trzy warunki, od ostatniego. Przekręćmy potencjometr do końca i wciśnijmy reset – teraz zaskoczenie: będzie się świecić czerwona dioda solo. W miarę kręcenia potencjometrem w lewo, zaświeci się też zielona, a na końcu obie zgasną. Przyczyną jest właśnie owe zakończenie analizy całego bloku po napotkaniu pierwszej pasującej instrukcji. Dlatego tak ważna jest kolejność. Gdybyśmy zamienili wartości miejscami, nigdy by nam się nie zaświeciła zielona dioda, bo warunek przekroczenia ¾ zakresu spełnia także przekroczenie jego połowy.
Kolejnych struktur else if może być dużo, ale trzeba pamiętać o logice działań i po przekroczeniu rozsądnej ilości tych struktur wybrać inne metody złożonych wyborów, o których za jakiś czas napiszę. W zasadzie ta konstrukcja wystarczyłaby do wszystkiego, ale są jeszcze dwie inne, które mogą skrócić kod i zwiększyć czytelność. Przyjrzymy się im w kolejnym artykule.