[082] Układamy wiedzę - cz. 8

[082] Układamy wiedzę - cz. 8

I znowu instrukcja, której w zasadzie mogłoby nie być, ale w określonych warunkach stracilibyśmy na czytelności i szybkości działania programu. Napiszmy kolejny nieużyteczny szkic, który będzie mieć jednak znaczenie szkoleniowe, a który wykorzysta Płytkę Edukacyjną TME


const byte diodaCzerwona = 9;    // Numer pinu, do którego podłączona jest dioda czerwona.
const byte diodaZielona = 10;    // Numer pinu, do którego podłączona jest dioda zielona.
const byte diodaNiebieska = 11;  // Numer pinu, do którego podłączona jest dioda niebieska.
const byte potencjomert = A1;    // Adres potencjometru.
byte wybor;                      // Pozycja w menu switch...case

void setup() {
  pinMode(diodaCzerwona, OUTPUT);  // Zadeklaruj porty diod jako wyjścia.
  pinMode(diodaZielona, OUTPUT);
  pinMode(diodaNiebieska, OUTPUT);
}

void loop() {
  wybor = analogRead(potencjomert) / 256;  // Potencjometr wybierze jedną z czterech pozycji wyboru.

  if (wybor == 0) {                        // Pozycja zero.
    zgas();                                // Zgaś wszystkie diody.
  }
  if (wybor == 1) {                        // Pozycja pierwsza.
    zgas();                                // Zgaś wszystkie diody.
    digitalWrite(diodaCzerwona, HIGH);     // Włącz diodę czerwoną.
  }
  if (wybor == 2) {                        // Pozycja druga.
    zgas();                                // Zgaś wszystkie diody.
    digitalWrite(diodaZielona, HIGH);      // Włącz diodę zieloną.
  }
  if (wybor == 3) {                        // Pozycja trzecia.
    zgas();                                // Zgaś wszystkie diody.
    digitalWrite(diodaNiebieska, HIGH);    // Włącz diodę niebieską.
  }
}
void zgas() {                              // Zgaś wszystkie diody.
  digitalWrite(diodaCzerwona, LOW);
  digitalWrite(diodaZielona, LOW);
  digitalWrite(diodaNiebieska, LOW);
}

Niech potencjometr wybierze cztery opcje – a każdą z nich będzie świecenie jednej ze struktur diody świecącej. Jak niedawno pisałem, potencjometr może wystawiać napięcie na wejście analogowe, które zmierzy je z dokładnością 1024 poziomów. Nam wystarczą cztery, więc podzielimy wynik przez 256 i wstawimy go do zmiennej wybor.

Teraz zbudujemy cztery bloki, a w każdym będziemy sprawdzać czy wybor wynosi kolejno: 0, 1 2 czy 3. W zależności od wartości wykona się jeden z bloków. W każdym najpierw pogasimy wszystkie diody, a następnie zapalimy wybraną.

Problem w tym, że program jest trochę nieczytelny: teraz jeszcze jest jako tako, ale jak przybędzie opcji, zrobi się bałagan. Drugi, większy problem, to fakt, iż program sprawdza po kolei wszystkie warunki, nawet te, które pojawiają się już po bloku, który się wykonał, co sensu nie ma. No i gdyby z jakiegoś powodu zmienna wybor przyjęła wartość tu niewystępującą, program przeleci przez wszystkie warunki i nic nie zrobi, a wypadałoby czasem taką sytuację wyłapać i kontrolować.

Można oczywiście wstawić na końcu warunek „a jeśli nie zero, jeden, dwa i trzy, to…”, tylko że jeśli warunków będzie kilkadziesiąt, powstanie monstrum, które zwolni program znacząco. Tak czy inaczej, nie tędy droga. Dróg alternatywnych jest kilka, lecz najbardziej elegancką jest wykorzystanie specjalnej instrukcji switch case. Przyjrzyjmy się teraz zmodyfikowanemu szkicowi robiącemu to samo, ale ładniej.

void loop() {
  wybor = analogRead(potencjomert) / 256;  // Potencjometr wybierze jedną z czterech pozycji wyboru.

  switch (wybor) {                         // Utwórz listę wyboru
    case 1:                                // Pozycja pierwsza.
      zgas();                              // Zgaś wszystkie diody.
      break;                               // Opuść moduł wyboru.
    case 2:                                // Pozycja druga.
      zgas();                              // Zgaś wszystkie diody.
      digitalWrite(diodaCzerwona, HIGH);   // Włącz diodę czerwoną.
      break;                               // Opuść moduł wyboru.
    case 3:                                // Pozycja trzecia.
      zgas();                              // Zgaś wszystkie diody.
      digitalWrite(diodaZielona, HIGH);    // Włącz diodę zieloną.
      break;                               // Opuść moduł wyboru.
    case 4:                                // Pozycja czwarta.
      zgas();                              // Zgaś wszystkie diody.
      digitalWrite(diodaNiebieska, HIGH);  // Włącz diodę niebieską.
      break;                               // Opuść moduł wyboru.
    default:                               // Pozycja niepasująca do pozostałych
      zgas();                              // Zgaś wszystkie diody.
      break;                               // Opuść moduł wyboru.
  }
}
void zgas() {                              // Zgaś wszystkie diody.
  digitalWrite(diodaCzerwona, LOW);
  digitalWrite(diodaZielona, LOW);
  digitalWrite(diodaNiebieska, LOW);
}

Zaczynamy od słowa switch, po czym wstawiamy zmienną, która ma decydować o wyborze. Przypomnę – to pozycja potencjometru ograniczona do przedziału od zera do trzech.

Teraz zamiast if-ów będziemy mieć serię case. Przy każdym znajduje się liczba, która została osadzona w zmiennej wybor. Za nimi mamy instrukcje, które mają się wykonać w chwili, gdy warunek zachodzi, a następnie instrukcję break. Nie jest ona obowiązkowa, ale właśnie to odróżnia ten sposób od poprzedniego: po wykonaniu bloku program opuszcza całość i nie sprawdza już warunków.

A co ze wspomnianym przeze mnie stanem zmiennej wybor spoza ustalonych, które w określonych warunkach mogą się pojawić? Spójrzmy na pozycję default: Ta instrukcja ma wbudowany mechanizm pułapki takich zdarzeń. Blok zaczynający się od default: wykona się zawsze, gdy nie wykona się żaden inny. Gdy zmienimy dzielnik w linii wybor = analogRead(potencjomert) / 256 na 128, zmienna wybor będzie przyjmować wartości od 0 do 7. Kręcąc gałką możemy będziemy mogli zauważyć, że od połowy nie będzie się świecić żaden kolor. To dzieło ostatniego bloku zaczynającego się od wpisu default:, który wyłapał wartości spoza zestawienia i rozkazał wygasić wszystkie diody. Jak widać, teraz zarówno zero, czyli skręcenie potencjometru jak i pułapka na stany spoza zakresu robi to samo. Śmiało więc możemy usunąć pierwszy blok (case 1), ubędzie bajtów, a program zachowa się identycznie.

Instrukcje te są przydatne przede wszystkim do wyboru zachowań złożonych, jak na przykład budowa klawiatury wysyłających makra. Do zadań powtarzalnych, różniących się np. wartościami – jak generowanie znaków na wyświetlaczach – lepsze są tablice. Ale o tym później.

Jeśli chodzi o instrukcje, to już wszystko co istotne. Ale jest ich jeszcze kilka, więc dla porządku je przedstawię. Goto jest takim „przesuwaczem wątków”, czyli powoduje skoki bez jakichkolwiek warunków. Jest to instrukcja kontrowersyjna, lecz w rzadkich przypadkach może się przydać. Jeśli ktoś jednak myśli BASIC-iem z lat osiemdziesiątych jeszcze, niech porzuci chęć wciskania jej gdzie popadnie.

const byte diodaCzerwona = 9;    // Numer pinu, do którego podłączona jest dioda czerwona.
const byte diodaZielona = 10;    // Numer pinu, do którego podłączona jest dioda zielona.
const byte diodaNiebieska = 11;  // Numer pinu, do którego podłączona jest dioda niebieska.
const byte potencjomert = A1;    // Adres potencjometru.
byte wybor;                      // Pozycja w menu switch...case

void setup() {
  pinMode(diodaCzerwona, OUTPUT);  // Zadeklaruj porty diod jako wyjścia.
  pinMode(diodaZielona, OUTPUT);
  pinMode(diodaNiebieska, OUTPUT);
}

void loop() {
  wybor = analogRead(potencjomert) / 256;  // Potencjometr wybierze jedną z czterech pozycji wyboru.

  if (wybor == 0) {                        // Pozycja zero.
    zgas();                                // Zgaś wszystkie diody.
    goto dalej;                            // Nie sprawdzaj już kolejnych pętli.
  }
  if (wybor == 1) {                        // Pozycja pierwsza.
    zgas();                                // Zgaś wszystkie diody.
    digitalWrite(diodaCzerwona, HIGH);     // Włącz diodę czerwoną.
    goto dalej;                            // Nie sprawdzaj już kolejnych pętli.
  }
  if (wybor == 2) {                        // Pozycja druga.
    zgas();                                // Zgaś wszystkie diody.
    digitalWrite(diodaZielona, HIGH);      // Włącz diodę zieloną.
    goto dalej;                            // Nie sprawdzaj już kolejnych pętli.
  }
  if (wybor == 3) {                        // Pozycja trzecia.
    zgas();                                // Zgaś wszystkie diody.
    digitalWrite(diodaNiebieska, HIGH);    // Włącz diodę niebieską.
    goto dalej;                            // Nie sprawdzaj już kolejnych pętli.
  }
  dalej:;                                  // Ciąg dalszy programu.
}
void zgas() {                              // Zgaś wszystkie diody.
  digitalWrite(diodaCzerwona, LOW);
  digitalWrite(diodaZielona, LOW);
  digitalWrite(diodaNiebieska, LOW);
}

Z obowiązku jednak przerobiłem przykład z potencjometrem i funkcjami if tak, by przypominał bardziej ten ze switch. Po wykonaniu warunku opuszczamy blok właśnie za pomocą funkcji goto, która przenosi nas na koniec całej serii. Na pewno przyspiesza to działanie programu, ale nie tędy droga. Ciekawostka: jeśli przy pozycji dalej: nie będzie żadnej instrukcji więcej, będziemy musieli wstawić średnik. Inaczej wyskoczy błąd.

Break wystąpił przed chwilą, ale można go umieszczać także w innych pętlach, jak for czy while. Robi to samo: zarzuca wykonywanie pętli. Zmodyfikujmy nasz przykład ze światłami.

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.
byte kasujMiganie = 8;   // Numer pinu, do którego podłączony jest pstryczek kasujący alarm.
byte liczbaPowtorzen;    // Licznik powtórzeń migania.

void setup() {
  pinMode(diodaCzerwona, OUTPUT);       // Zadeklaruj porty diod jako wyjścia.
  pinMode(diodaZielona, OUTPUT);
  pinMode(kasujMiganie, INPUT_PULLUP);  // Zadeklaruj port pstryczka jako wejścia.
}

void loop() {
                                      // Czerwone światło.
  digitalWrite(diodaZielona, LOW);    // Wyłącz diodę zieloną.
  digitalWrite(diodaCzerwona, HIGH);  // Włącz diodę czerwoną.
  delay(3000);                        // Zaczekaj trzy sekundy.

                                      // Zielone światło.
  digitalWrite(diodaCzerwona, LOW);   // Wyłącz diodę czerwoną.
  digitalWrite(diodaZielona, HIGH);   // Włącz diodę zieloną.
  delay(3000);                        // Zaczekaj trzy sekundy.

                                              // Migamy zielonym światłem.
  liczbaPowtorzen = 5;                        // Liczba mrugnięć.
  while (liczbaPowtorzen > 0) {               // Powtarzaj dopóki liczba mrugnięć nie osiągnie zera.
    if (digitalRead(kasujMiganie) == HIGH) {  // Jeśli wciśnięto przycisk...
      break;}                                 // Porzuć ten blok.
    digitalWrite(diodaZielona, LOW);          // Wyłącz diodę zieloną.
    delay(250);                               // Zaczekaj ćwierć sekundy.
    digitalWrite(diodaZielona, HIGH);         // Włącz diodę zieloną.
    delay(250);                               // Zaczekaj ćwierć sekundy.
    liczbaPowtorzen--;                        // Zmniejsz liczbę powtórzeń o jeden.
  }
}

Wstawiłem przycisk kasujMiganie, którego wciśnięcie podczas migania przerywa ów proceder. Trzeba jednak pamiętać, że tak brutalne przerywanie pętli nie jest eleganckie i nie należy nadużywać tej metody, stosując delikatniejsze sposoby. W tym wypadku wystarczyłoby wyzerowanie zmiennej liczbaPowtorzen i program opuściłby pętlę w kolejnym przebiegu.

Na koniec pozostawię dwie ostatnie instrukcje, stosowane rzadko, ale mogące być użytecznymi w określonych warunkach. Zamiast tworzyć abstrakcyjne przykłady, posilę się oficjalnymi. Return działa podobnie trochę jak break – przerywa działanie programu, ale może być zestawiany z opcjonalną zmienną, którą można wykorzystać w bloku nadrzędnym.

int checkSensor() {
  if (analogRead(0) > 400) {
    return 1;
  } else {
    return 0;
  }
}

Z tego co widzę, oficjalnie Arduino nie promuje takiego sposobu na rzecz podprogramów czy zmiennych globalnych. Natomiast continue ma pewien potencjał. Spójrzmy na przykład.

for (int x = 0; x <= 255; x++) {
  if (x > 40 && x < 120) {  // create jump in values
    continue;
  }
  analogWrite(PWMpin, x);
  delay(50);
}

Oto krótka procedura, wpisująca kolejne wartości z pętli na port analogowy, lecz w przypadku, gdy zmienna będzie należeć do zakresu (44...120), nie będzie tego robić, a będzie przeskakiwać aż warunek przestanie obowiązywać. Można to oczywiście napisać bez tej instrukcji, ale dobrze wiedzieć, że da się też tak.

Płytka edukacyjna TME-EDU-ARD-2Płytka edukacyjna TME-EDU-ARD-2Sprawdź tutaj

Przeczytaj również

Nasi partnerzy

TMETech Master EventTME EducationPoweredby
Copyright © 2025 arduino.pl