Wielozadaniowość w Arduino?

Programowanie Arduino jest bardzo proste. Do takiego wniosku można dojść obserwując najprostsze przykłady programów. Spójrzmy chociażby na, prezentowany już wcześniej, program mrugający diodą podłączoną do wyjścia numer 13. Dla łatwiejszych dalszych rozważań został on jeszcze trochę uproszczony:

#define LED1 13

void setup() {                
  pinMode(LED1, OUTPUT); // ustawiamy pin 13 jako wyjście
}

int led1_state=0; // na początku zmienna będzie 0 (nie świeci)

void loop() {
  digitalWrite(LED1, led1_state); // ustawiamy stan wyjścia diody zgodnie 
                                  // z wartością zmiennej led1_state: 
                                  // 1 - świeci, 0 - nie
  delay(1000);             // czekamy sekundę
  led1_state=1-led1_state; // zmieniamy stan z 0 na 1 lub z 1 na 0
}

Nie ma w nim oczywiście nic wyszukanego – co sekundę zmieniamy stan wyjścia, a co za tym idzie diody świecącej. Przypominam, że 13 została wybrana nieprzypadkowo, ponieważ do tego wyjścia podłączona jest na stałe dioda znajdująca się na płytce Arduino.

Ponieważ Arduino Uno taktowane jest zegarem 16MHz, łatwo zauważyć, że większość mocy obliczeniowej zużywamy przy instrukcji delay(1000). Nie ma w tym nic złego, do czasu kiedy nasze działanie ogranicza się do wykonywania jednej czynności. Wstawienie instrukcji oczekiwania to całkiem naturalne podejście i w tym wypadku logiczne. Co jednak, gdy nasz układ się komplikuje i chcemy mrugać dwoma diodami? Proszę bardzo.

// mruganie naprzemienne
#define LED1 13
#define LED2 12

void setup() {                
  pinMode(LED1, OUTPUT);     
  pinMode(LED2, OUTPUT);     
}

int led1_state=0;
int led2_state=1;

void loop() {
  digitalWrite(LED1, led1_state);
  digitalWrite(LED2, led2_state);
  delay(1000);
  led1_state=1-led1_state;
  led2_state=1-led2_state;
}

Nie ma problemu – lekko modyfikujemy program i już dwie diody mrugają naprzemiennie. Tylko co, gdy mają mrugać w różnym tempie? Jeżeli jedna ma mrugać na przykład co sekundę a druga co pół, to poradzimy sobie z tym bez problemu, przy pomocy funkcji delay(500) i niewielkiego skomplikowania programu. Co jednak, gdy okresy mrugania są zupełnie niezależne (nie będące wielokrotnościami tej samej wartości)? Trzeba nieco zmienić podejście i sposób myślenia. Powinniśmy oprzeć się na jakimś zegarze i w funkcji loop() (wywoływanej w nieskończonej pętli) sprawdzać czy już nadszedł już czas zmiany stanu i w takim wypadku wykonać odpowiednią czynność.

// mruganie 2 diodami w różnym tempie
#define LED1 13
#define LED2 12

#define LED1delay 700
#define LED2delay 687

unsigned long now=0;
int led1_state=0;
int led2_state=0;

unsigned long next_change_1;
unsigned long next_change_2;

void setup() {                
  pinMode(LED1, OUTPUT);     
  pinMode(LED2, OUTPUT);     

  next_change_1=millis()+LED1delay;
  next_change_2=millis()+LED2delay;
}

void loop() {
  now=millis();

  if( now>=next_change_1 ) // sprawdzamy czy należy zmienić stan 1. diody
  {
    next_change_1=now+LED1delay;
    led1_state=1-led1_state;
    digitalWrite(LED1, led1_state);
  }

  if( now>=next_change_2 ) // sprawdzamy czy należy zmienić stan 2. diody
  {
    next_change_2=now+LED2delay;
    led2_state=1-led2_state;
    digitalWrite(LED2, led2_state);
  }
}

Funkcja millis() zwraca ilość milisekund, która upłynęła od uruchomienia Arduino. W funkcji setup() (wykonywanej raz, przy starcie) ustalamy kiedy ma nastąpić zmiana stanu obu diod. Następnie w nieskończonej pętli sprawdzamy dla każdej z nich czy właściwy czas już nadszedł. Gdy nadejdzie wyliczamy kiedy ma nastąpić następne mrugnięcie, a następnie zmieniamy stan diody. Dzięki temu obie diody mrugają zupełnie niezależnie.

W powyższym programie nie jest przewidziana jedna rzecz – przekręcenie licznika czasu. Ponieważ millis() zwraca wartość typu long, można się spodziewać, że zakres tego typu kiedyś zostanie przkroczony (po kilku tygodniach). Możemy wtedy trafić na sytuację, kiedy nasz zakładany czas zmiany przypadnie na sam koniec zakresu, a gdy dojdziemy do momentu sprawdzania czasu, będzie on miał już wartość bliską zeru (przekręcenie licznika). Wtedy jedna dioda (albo obie) mogą przestać mrugać.

Ktoś może stwierdzić, że to czysto hipotetyczne, mało prawdopodobne a sprzyjające warunki będą się zdarzać rzadko. I pewnie będzie miał rację, do czasu gdy mrugamy diodami. Biorąc jednak pod uwagę, że Arduino może zostać wykorzystane do różnych zadań – na przykład do podlewania ogrodu i może działać nieprzerwanie przez całe miesiące lub lata, takie sytuacje trzeba przewidzieć. Nie ma z tym jednak większego problemu, wystarczy dodać sprawdzanie jednego warunku.

// mruganie 2 diodami w różnym tempie
// z zabezpieczeniem przed przekręceniem licznika millis()
#define LED1 13
#define LED2 12

#define LED1delay 700
#define LED2delay 687

unsigned long now=0;
int led1_state=0;
int led2_state=0;

unsigned long next_change_1;
unsigned long next_change_2;

void setup() {                
  pinMode(LED1, OUTPUT);     
  pinMode(LED2, OUTPUT);     

  next_change_1=millis()+LED1delay;
  next_change_2=millis()+LED2delay;
}

void loop() {
  last=now;
  now=millis();

  if( now>=next_change_1 || now<last ) // sprawdzamy czy należy zmienić stan 1. diody
  {
    next_change_1=now+LED1delay;
    led1_state=1-led1_state;
    digitalWrite(LED1, led1_state);
  }

  if( now>=next_change_2 || now<last ) // sprawdzamy czy należy zmienić stan 2. diody
  {
    next_change_2=now+LED2delay;
    led2_state=1-led2_state;
    digitalWrite(LED2, led2_state);
  }
}

Oczywiście docelowy program do sterowania wieloma urządzeniami zapewne będzie wyglądał nieco inaczej. Być może warto będzie napisać bibliotekę do obsługi wielu zegarów zdarzeń. Ważny jest jednak tak naprawdę sposób myślenia i pozbycie się nawyku stosowania funkcji delay().

Warto też wiedzieć, że nie jest to jedyne możliwe podejście do „wielozadaniowości”. Arduino obsługuje także przerwania (idea zapewne znana jest wielu użytkownikom komputerów, którzy pamiętają jeszcze starsze komputery). Mogą być one w pewnych sytuacjach przydatne, szczególnie gdy projektowany układ ma się opierać na obsłudze wejść. Ba, czasami ich użycie może być nawet konieczne do właściwego działania układu. Nie jest to jednak tematem aktualnego wpisu.

Jeżeli chcesz kupić Arduino – polecam sklep Nettigo.

Ten wpis został opublikowany w kategorii Programowanie i oznaczony tagami , . Dodaj zakładkę do bezpośredniego odnośnika.

0 odpowiedzi na Wielozadaniowość w Arduino?

  1. Nettigo pisze:

    Do obsługi takiej wielozadaniowości szczerze polecam: https://github.com/sprae/Timers

    • techniczny pisze:

      Jak najbardziej, można, a nawet pewnie warto użyć gotowej biblioteki. Moim celem było raczej przedstawienie sposobu myślenia i podejścia do programowania Arduino. Gdy się użyje biblioteki, warto wiedzieć na jakiej zasadzie mniej więcej działa, choćby po to, żeby całość działała sprawnie.

  2. Domino pisze:

    Zmienna last jest nie zdeklarowana w kodzie,

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *