Popularny post ethanak Napisano Kwiecień 10, 2021 Popularny post Udostępnij Napisano Kwiecień 10, 2021 Ten artykuł jest częścią serii "Arduino dla początkujących: instrukcje sterujące w C/C++" #1 - if, bloki #2 - while i coś jeszcze #3 - for, break, switch Czytając posty zamieszczane przez początkujących użytkowników można zauważyc pewną regułę: otóż większość ma problem ze zrozumieniem, jak działają instrukcje sterujące. O ile z prostą instrukcją if prawie każdy radzi sobie bez problemu, o tyle instrukcje pętli (while, for) czy wyboru (switch) nader często stosowane są nieprawidłowo. Spróbujmy temu zaradzić. Podstawa wszelkich podstaw, czyli instrukcja "if" Na początek zajmijmy się instrukcja if. Jej zadanie jest proste: jakaś inna instrukcja zostanie wykonana tylko wtedy, jeśli jakiś warunek jest spełniony. Zapiszmy to w taki sposób: if (WARUNEK) INSTRUKCJA; Warunkiem może być tu dowolne wyrażenie zwracające wartość. Możemy to pokazać na przykładzie. Załóżmy, że do naszego Arduino do pinu 2 podłączymy przycisk: Skorzystamy tu z wbudowanej diody LED, aby nie dodawać dodatkowych elementów. No i zadanie pierwsze - niech dioda zapala się gdy naciśniemy przycisk. Nasz program będzie wyglądać tak: #define BUTTON_PIN 2 #define LED_PIN 13 void setup() { pinMode(BUTTON_PIN, INPUT_PULLUP); // ustawiamy pin jako wejście z podciągnięciem do Vcc pinMode(LED_PIN, OUTPUT); //ustawiamy pin jako wyjście digitalWrite(LED_PIN, LOW); // i ustawiamy stan niski na pinie } void loop() { if (digitalRead(BUTTON_PIN) == LOW) digitalWrite(LED_PIN, HIGH); // zapalamy ledę po wciśnięciu } Po uruchomieniu widzimy, że po naciśnięciu przycisku dioda się zapala. Zaraz... a czemu po puszczeniu nie gaśnie? Aha, bo w programie nie ma przecież nic o gaszeniu, prawda? Trzeba to natychmiast naprawić! void loop() { if (digitalRead(BUTTON_PIN) == LOW) digitalWrite(LED_PIN, HIGH); // zapalamy ledę po wciśnięciu if (digitalRead(BUTTON_PIN) == HIGH) digitalWrite(LED_PIN, LOW); // gasimy ledę po puszczeniu } No, teraz działa. Ale czy to naprawdę ma wyglądać? Dwa razy wywołujemy funcję digitalRead, nie da się prościej? Oczywiście, że się da! Po prostu instrukcja if ma jeszcze drugą postać: if (WARUNEK) INSTRUKCJA1; else INSTRUKCJA2; Jeśli użyjemy tej formy, INSTRUKCJA1 zostanie wykonana jeśli warunek jest spełniony, w przeciwnym razie zostanie wykonana INSTRUKCJA2. Poprawmy więc nasz program (przy okazji trochę go uczytelniając): void loop() { if (digitalRead(BUTTON_PIN) == LOW) digitalWrite(LED_PIN, HIGH); // zapalamy ledę po wciśnięciu else digitalWrite(LED_PIN, LOW); // gasimy ledę po puszczeniu } Jakoś ładniej, prawda? I działa tak samo! Ponieważ w dalszej części będziemy posługiwać się diagramami, zobaczmy jak na takim diagramie będzie wyglądać instrukcja if: Zajmijmy się teraz warunkiem. W językach C i C++ warunkiem - jak wspomniano - może być dowolne wyrażenie zwracające wartość. Ta wartość zostaje przekształcona na typ int. Warunek jest spełniony, jeśli po owym przekształceniu wartość jest różna od zera. Tak więc wynikiem porównania (jak w naszym programie) jest wartość logiczna "prawda" lub "fałsz". Po przekształceniu na typ int "fałsz" jest zerem, natpomiast "prawda" to jakaś wartość różna od zera (nie interesuje nas na razie jaka konkretnie, ważne że to nie zero). Możemy również sprawdzić, że Arduinowe LOW to zero, a HIGH to jeden. Tak więc możemy jeszcze bardziej uprościć nasz program: void loop() { if (digitalRead(BUTTON_PIN)) // przycisk puszczony digitalWrite(LED_PIN, LOW); // a więc gasimy ledę else digitalWrite(LED_PIN, HIGH); // a jak wciśnięty to zapalamy } Zapis z porównaniem jest jednak - przynajmniej dla początkujących - bardziej czytelny, i takim będziemy się dalej posługiwać. Spróbujemy teraz czegoś trudniejszego. Ponieważ instrukcja if zawiera inną instrukcję, nic nie stoi na przeszkodzie aby tą inną instrukcją był również if. W ten sposób możemy sobie zagnieżdżać warunki i tworzyć całkiem skomplikowane programy. Podłączmy teraz drugi przycisk, przykładowo do pinu 3. Niech ten przycisk będzie przyciskiem zezwalającym na zapalanie ledy: czyli będzie ona reagować na wciśnięcie pierwszego przycisku tylko wtedy, gdy drugi jest wciśnięty; puszczenie pierwszego przycisku nie powinno wywołać żadnej reakcji, natomiast puszczenie drugiego powoduje zgaśnięcie ledy. Spróbujmy więc napisać program: #define BUTTON_PIN 2 #define ENABLE_PIN 3 #define LED_PIN 13 void setup() { pinMode(BUTTON_PIN, INPUT_PULLUP); // ustawiamy pin jako wejście z podciągnięciem do Vcc pinMode(ENABLE_PIN, INPUT_PULLUP); // drugi pin tak samo pinMode(LED_PIN, OUTPUT); //ustawiamy pin jako wyjście digitalWrite(LED_PIN, LOW); // i ustawiamy stan niski na pinie } void loop() { if (digitalRead(ENABLE_PIN) == LOW) // jeśli przycisk ENABLE jest wciśnięty if (digitalRead(BUTTON_PIN) == LOW) // teraz możemy zapalić ledę digitalWrite(LED_PIN, HIGH); else // przycisk ENABLE jest puszczony digitalWrite(LED_PIN, LOW); } Uruchamiamy nasz program, sprawdzamy i... oj, coś tu jest nie tak! Ano jest. Kompilator po prostu przypisał "else" do wewnętrznego ifa, i dlatego leda gaśnie od razu po puszczeniu pierwszego przycisku jeśli drugi pozostał wciśnięty. Trochę nie o to nam chodziło... jak to naprawić? Spróbujmy: E pluribus unum, czyli blok instrukcji Otóż w C/C++ istnieje właśnie pojęcie bloku. Taki blok może w większości przypadków zastąpić pojedynczą instrukcję - przede wszystkim właśnie w instrukcjach sterujących. Zapisujemy go w ten sposób: { // początek bloku instrukcja pierwsza; instrukcja druga; instrukcja trzecia; // i kolejne } // koniec bloku Spróbujmy więc poprawić nasz program: void loop() { if (digitalRead(ENABLE_PIN) == LOW) // jeśli przycisk ENABLE jest wciśnięty { // rozpoczynamy blok if (digitalRead(BUTTON_PIN) == LOW) // teraz możemy zapalić ledę digitalWrite(LED_PIN, HIGH); } // kończymy blok else // przycisk ENABLE jest puszczony digitalWrite(LED_PIN, LOW); } No, teraz działa! Blok instrukcji ma kilka dodatkowych cech. Najważniejszą z nich jest ta, że może zawierać zmienne lokalne, które będą widoczne tylko wewnątrz bloku i będą niszczone po wyjściu z niego. Ta cecha jest bardzo ważna szczególnie w przypadku małych procesorków (jak Arduinowa ATmega). Wyobraźmy sobie taką sytuację: void loop() { int a[500]; char b[1000]; // tu fragment programu operujący na tablicy a // a tu fragment programu operujący na tablicy b } Niestety - próba wykonania takiego programu skończy się smętnie: Arduino nie ma tyle pamięci, aby pomieścić obie tablice. Możemy jednak temu zaradzić właśnie używając bloków: void loop() { { // rozpoczynamy blok int a[500]; // tu fragment operujący na tablicy a } // zamykamy blok niszcząc przy okazji tablicę a { // rozpoczynamy drugi blok int b[1000]; // tu fragment operujący na tablicy b } // zamykamy blok niszcząc przy okazji tablicę b } Tak więc w dalszej części artykułu wszędzie gdzie pojawi się INSTRUKCJA (czy też INSTRUKCJA1, INSTRUKCJA2 itd) będziemy mogli wstawić blok instrukcji. Tyle na razie, w następnej części spróbujemy rozpracować pętlę oraz poznamy pewną nieznaną instrukcję o której się nie wspomina. 9
Treker (Damian Szymański) Kwiecień 11, 2021 Udostępnij Kwiecień 11, 2021 @ethanak artykuł został właśnie zaakceptowany i jest już dostępny publicznie
Gieneq Kwiecień 12, 2021 Udostępnij Kwiecień 12, 2021 @ethanak Przydatna sprawa, będę linkował w często pojawiających się pytaniach. Szkoda tylko że nie ma else if - jest to dość kłopotliwe dla początkujących, którzy uznają, że warunki czasem nie działają (w tym dla mnie z 10 lat temu jak zaczynałem). Dnia 10.04.2021 o 18:09, ethanak napisał: Spróbujmy więc napisać program: #define BUTTON_PIN 2 #define ENABLE_PIN 3 #define LED_PIN 13 void setup() { pinMode(BUTTON_PIN, INPUT_PULLUP); // ustawiamy pin jako wejście z podciągnięciem do Vcc pinMode(ENABLE_PIN, INPUT_PULLUP); // drugi pin tak samo pinMode(LED_PIN, OUTPUT); //ustawiamy pin jako wyjście digitalWrite(LED_PIN, LOW); // i ustawiamy stan niski na pinie } void loop() { if (digitalRead(ENABLE_PIN) == LOW) // jeśli przycisk ENABLE jest wciśnięty if (digitalRead(BUTTON_PIN) == LOW) // teraz możemy zapalić ledę digitalWrite(LED_PIN, HIGH); else // przycisk ENABLE jest puszczony digitalWrite(LED_PIN, LOW); } Uruchamiamy nasz program, sprawdzamy i... oj, coś tu jest nie tak! Pierwsze to gdy ktoś robi takiego stworka z ilomaś ifami i jednym elsem i dziwi się, że else nie tyczy się wszystkich innych ifów tylko tego ostatniego. Druga pułapka to warunek bez klamerek, który z jakiegoś powodu nic nie robi - sam spędziłem kiedyś nad czymś takim kilkanaście minut debugowania, a to tylko dlatego że ktoś wstawił na końcu ifa średnik. Dnia 10.04.2021 o 18:09, ethanak napisał: Ano jest. Kompilator po prostu przypisał "else" do wewnętrznego ifa, Tu ciekawostka, autoformat Arduino IDE (Ctrl-T) tak też ułoży kod: void loop() { if (digitalRead(ENABLE_PIN) == LOW) // jeśli przycisk ENABLE jest wciśnięty if (digitalRead(BUTTON_PIN) == LOW) // teraz możemy zapalić ledę digitalWrite(LED_PIN, HIGH); else // przycisk ENABLE jest puszczony digitalWrite(LED_PIN, LOW); } 1
Popularny post ethanak Kwiecień 12, 2021 Autor tematu Popularny post Udostępnij Kwiecień 12, 2021 @Gieneq Myślę że kontynuacja jest dobrym pomysłem - pomyślałem teraz o czymś w stylu "najczęstsze błędy" jako aneks do tej serii. Tu można by było omówić wszelkie pułapki (choćby właśnie "else if" czy użycie pustej instrukcji). Co o tym sądzisz? 4
Gieneq Kwiecień 13, 2021 Udostępnij Kwiecień 13, 2021 @ethanak czemu nie, taka treść będzie potrzebna. Takich wpisów w kawałkach jest sporo, to ciężko je znaleźć. A zebrane w jednym miejscu i otagowane już łatwiej ponownie wykorzystać.
Pomocna odpowiedź
Bądź aktywny - zaloguj się lub utwórz konto!
Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony
Utwórz konto w ~20 sekund!
Zarejestruj nowe konto, to proste!
Zarejestruj się »Zaloguj się
Posiadasz własne konto? Użyj go!
Zaloguj się »