SantyagoSantyago
Avatar

Witaj!
Blog archiwalny. Już niebawem nowy serwis!

YouTube RSS Facebook GitHub

Arduino poradnik

Wstęp

Teoria

Biblioteki

Komponenty

Czujniki i sensory

Rozwiązania i algorytmy

Narzędzia

Mikrokontrolery i Arduino IDE

Arduino i klony

Poradniki wideo

Reklama na Blogu

Najnowsze poradniki

Ostatnie komentarze

Ostatnie fotografie

polskie-gorypolskie-gorypolskie-gorypolskie-gorypolskie-gorypolskie-gorypolskie-gorypolskie-gorypolskie-gorywieliczka-szyb-danilowicza

leOS 2 czyli wątki pod Arduino

Jak wiemy, program napisany pod Arduino po wcześniejszym wykonaniu konstruktora setup() ustawiając tym samym warunki początkowe, następuje cykliczne wykonywanie zawartości kodu w pętli loop(). Wszystko jest dobrze, dopóki nie będziemy mieli potrzeby wykonania kliku zadań w określonych odstępach czasu. Oczywiście nie jest to znów jakaś szkoła wyższa, bo przecież sami możemy zadbać o sterowanie wątkami, ale opracowanie sterowania takimi zadaniami, wznawiania, wstrzymywania czy modyfikacja parametrów, mogłoby nam zabrać spory kawałek czasu.

Na szczęście nie musimy tego robić. Możemy wykorzystać specjalną bibliotekę leOS 2 (little embedded Operating System), która ułatwi nam zadanie do minimum formalności. Biblioteka ta posiada obecnie dwie linie 1.x oraz 2.x - obie różnią się zasadniczo sposobem funkcjonowania. Starsza wersja opiera się o wewnętrzny licznik mikrokontrolera, natomiast nowsza licznik WatchDog (WDT).

leOS 1.x czy 2.x ?

Zatem którą wersję wybrać? Wszystko zależy od tego, do czego chcemy ją wykorzystać. Wywoływanie przerwań uruchamiających zdefiniowane zadanie w leOS 1.x może być wykonywane (teoretycznie) w odstępach 1ms, ponieważ licznik taktowany jest zegar w zegar głównym licznikiem. Problemem może być w miarę wąska gama mikrokontrolerów na których będzie poprawnie działać, a także niemożliwość korzystania z określonych z góry pinów w trybie PWM (lista pinów znajduje się w dokumentacji technicznej). Mogą również wystąpić problemy jeśli inne biblioteki w naszym projekcie będą wykorzystywać ów licznik.

leOS 2 jest już bardziej uniwersalny - działa praktycznie na wszystkich płytkach uruchomieniowych wspieranych przez Arduino IDE. Jedynym wyjątkiem są mikrokontrolery Atmega8/A, w których zmuszeni jesteśmy skorzystać ze starszej wersji. leOS 2 to również mniejsza precyzja. Wynika to ze specyfiki działania WatchDoga, gdzie licznik dzielony jest przez 2048. W przypadku oddzielnego osyclatora 128kHz dla WDT  na jeden "takt" przypada czas 16ms. Łatwo to policzyć:

128 kHz / 2048 = 62.5 Hz » 1/62.5 » 16ms

Zaletą leOS 2 jest jednak możliwość automatycznego zresetowania mikrokontrolera po zdefiniowanym czasie w przypadku, gdyby jakieś zadanie zawiesiło działanie programu.

Jak korzystać?

Po rozpakowaniu leOS do katalogu /librararies, do naszego szkicu dodajemy nagłówki oraz tworzymy instancję leOS

  1. #include "leOS2.h"
  2. leOS2 threads;

W następnej kolejności w konstruktorze setup() wystarczy zdefiniować nasze wątki i określić ich parametry:

  1. void setup() {
  2.     ...
  3.     threads.begin();
  4.     threads.addTask(naszaFunkcja1, threads.convertMs(1000));
  5.     threads.addTask(naszaFunkcja2, threads.convertMs(2000));
  6.     ...
  7. }

I właściwie tyle. Po takim zabiegu, funkcja naszaFunkcja1() będzie wykonywana co 1 sekundę, natomiast naszaFunkcja2() co 2 sekundy. W odróżnieniu do leOS 1.x czas konwertujemy za pomocą metody convertMs() zamieniającą milisekundy na wspomniane "takty".

Gdybyśmy chcieli ustawić resetowanie mikrokontrolera, gdby któryś wątek wykonywał się dłużej niż 3s, należy zmodyfikować wywołanie wywołania threads.begin() na threads.begin(threads.convertMs(3000)).

Metoda addTask() posiada jeszcze trzeci, opcjonalny parametr określający początkowy status wątka:

  • PAUSED - zadanie nie jest od razu uruchamiane,
  • SCHEDULED - domyślna opcja - zadanie wykonywane po upływie zdefiniowanego czasu,
  • ONETIME - zadanie wykonywane tylko raz,
  • SCHEDULED_IMMEDIATESTART - zadanie wykonane natychmiast po utworzeniu wąta

leOS udostępnia jeszcze szereg metod pozwalających na sterowanie zadaniami w trakcie działania programu:

  • resetTask(naszaFunkcja) - pozwala na zresetowanie zadania
  • removeTask(naszaFunkcja) - usuwa zadanie z kolejki
  • modifyTask(naszaFunkcja, nowyCzas [, nowyStatus]) - modyfikuje wcześniej utworzone zadanie
  • getTaskStatus(naszaFunkcja) - pobiera aktualny status zadania

Jak to działa w praktyce?

Wykorzystując układ z poprzedniego wpisu, rozbudujemy go o dodatkowy przycisk i wspomnianą  bibliotekę leOS. Założenie projektu jest następujące. Chcemy utworzyć cztery wątki:

  • flashGreenLed() - płyna zmiana jasności diody LED z wykorzystaniem modulacji PWM.
  • flashYellowLed() - mruganie diodą LED o określonej prędkości.
  • onOffPush() - przycisk uruchamiania i zatrzymywania zadania flashYellowLed() + sygnalizacja diodą
  • speedPush() - przycisk zmiany czasu wykonywania zadania flashYellowLed() + dwukolorowa sygnalizacja diodą
  1. #include <Bounce.h>
  2. #include "leOS2.h"
  3.  
  4. leOS2 threads;
  5.  
  6. int buttonStartStopPin = 2;    // PIN2 - Przycisk włącz / wyłącz
  7. int buttonSpeedPin = 4;        // PIN4 - Przycisk zmiany prędkości
  8.  
  9. int greenLedPin = 3;           // PIN3  - Dioda zielona
  10. int yellowLedPin = 5;          // PIN5  - Dioda żółta
  11. int redRGBPin = 9;             // PIN9  - RGB - Dioda czerwona
  12. int greenRGBPin = 10;          // PIN10 - RGB - Dioda zielona
  13. int blueRGBPin = 11;           // PIN11 - RGB - Dioda niebieska
  14.  
  15. int yellowLedSpeed = 200;       // Prędkość początkowa procesu mrugania
  16. int greenLedPWM = 255;         // Wypełnienie początkowe PWM diody zielonej
  17.  
  18. byte greenLedStatus = 1;       // Status diody zielonej
  19. byte yellowLedStatus = 0;      // Status diody żóltej
  20.  
  21. // Ustawiamy nasze "bouncery" z interwałem czasowym 50ms
  22. Bounce bouncerStartStop = Bounce(buttonStartStopPin, 50);
  23. Bounce bouncerSpeed = Bounce(buttonSpeedPin, 50);
  24.  
  25. // Konfiguracja pinów i wątków
  26. void setup()
  27. {
  28.   threads.begin(); // rozpoczynamy pracę z wątkami
  29.  
  30.   // Przyciski
  31.   pinMode(buttonStartStopPin, INPUT);
  32.   pinMode(buttonSpeedPin, INPUT);
  33.  
  34.   // Diody
  35.   pinMode(redRGBPin, OUTPUT);
  36.   pinMode(greenRGBPin, OUTPUT);
  37.   pinMode(blueRGBPin, OUTPUT);
  38.   pinMode(yellowLedPin, OUTPUT);
  39.   pinMode(greenLedPin, OUTPUT);
  40.  
  41.   // Dodajemy zadania uruchamiane od razu
  42.   threads.addTask(flashGreenLed, threads.convertMs(25), SCHEDULED_IMMEDIATESTART);
  43.   threads.addTask(onOffPush, threads.convertMs(50), SCHEDULED_IMMEDIATESTART);
  44.   threads.addTask(speedPush, threads.convertMs(50), SCHEDULED_IMMEDIATESTART);
  45.  
  46.   // Dodajemy zadania do uruchomienia później  
  47.   threads.addTask(flashYellowLed, threads.convertMs(yellowLedSpeed), PAUSED);
  48. }
  49.  
  50. // Główna pętla programu - tym razem pusta
  51. void loop()
  52. {
  53. }
  54.  
  55. // Wątek obsługi przycisku zmiany prędkości
  56. void speedPush()
  57. {
  58.   // Aktualizujemy status przycisku
  59.   bouncerSpeed.update();
  60.  
  61.   // Reagujemy na zbocze narastające
  62.   if (bouncerSpeed.risingEdge())
  63.   {
  64.     // Zmniejszamy czas oczekiwania na wątek
  65.     yellowLedSpeed -= 40;
  66.     
  67.     // Jeśli prędkość wynosi 0, ustawiamy na 200 i sygnalizujemy niebieską diodą RGB
  68.     // Jeśli nie, sygnalizujemy zieloną diodą RGB
  69.     if (yellowLedSpeed == 0)
  70.     {
  71.         yellowLedSpeed = 200;
  72.         digitalWrite(blueRGBPin, HIGH);
  73.     } else
  74.     {
  75.       digitalWrite(greenRGBPin, HIGH);
  76.     }
  77.     
  78.     // Zapamiętujemy status naszego wątka flashYellowLed  
  79.     byte taskStatus = threads.getTaskStatus(flashYellowLed);
  80.     
  81.     // Usuwamy wątek mrugania żółtą diodą
  82.     threads.removeTask(flashYellowLed);
  83.     
  84.     // Dodajemy nowy wątek ze starym statusem i nowym czasem
  85.     threads.addTask(flashYellowLed, threads.convertMs(yellowLedSpeed), taskStatus);
  86.  } else
  87.  {
  88.     // Gasimy diodę RGB, zieloną i niebieską
  89.     digitalWrite(greenRGBPin, LOW);
  90.     digitalWrite(blueRGBPin, LOW);
  91.  }  
  92. }
  93.  
  94. // Wątek obsługi przycisku wlącz/wyłącz
  95. void onOffPush()
  96. {
  97.   // Aktualizujemy status przycisku
  98.   bouncerStartStop.update();
  99.  
  100.   // Reagujemy na zbocze narastające
  101.   if (bouncerStartStop.fallingEdge())
  102.   {
  103.     // Sygnalizujemy czerwoną diodą RGB
  104.     digitalWrite(redRGBPin, HIGH);
  105.     
  106.     // Pobieramy status naszego wątka flashYellowLed
  107.     byte taskStatus = threads.getTaskStatus(flashYellowLed);
  108.     
  109.     // Jeśli jest zatrzymany to wznawiamy
  110.     // Jeśli nie jest zatrzymany to zatrzymujemy i gasimy diodę żółtą
  111.     if (taskStatus == 0)
  112.     {
  113.       threads.restartTask(flashYellowLed);
  114.     } else
  115.     {
  116.       threads.pauseTask(flashYellowLed);
  117.       digitalWrite(yellowLedPin, 0);      
  118.     }
  119.   } else
  120.   {
  121.     // Gasimy diodę RGB, czerwoną
  122.     digitalWrite(redRGBPin, LOW);
  123.   }
  124.  
  125. }
  126.  
  127. void flashGreenLed()
  128. {
  129.   // Zmniejszamy wartość PWM dla diody zielonej
  130.   greenLedPWM -= 5;
  131.  
  132.   // Jeśli była zapalona to wysyłamy
  133.   if (greenLedStatus == 1)
  134.   {
  135.     analogWrite(greenLedPin, greenLedPWM);
  136.   }
  137.  
  138.   // Jeśli PWM wyniosło 0 to ustawiamy pełne PWM i zmieniamy status diody
  139.   if (greenLedPWM == 0)
  140.   {
  141.     greenLedPWM = 255;
  142.     greenLedStatus ^= 1;
  143.   }  
  144. }
  145.  
  146. void flashYellowLed()
  147. {
  148.   // Negujemy status diody żółtej
  149.   yellowLedStatus ^= 1;
  150.  
  151.   // Wysyłamy
  152.   digitalWrite(yellowLedPin, yellowLedStatus);
  153. }

Schemat układu

Demonstracja działania

Wszystkie materiały wykorzystane w tym wpisie, możecie znaleźć tutaj.

Reklama

Komentarze Komentarze
Avatar 1
Pytanie Windows 7 / Mozilla Firefox 44.0
08 February 2016 - 12:40 Pątnów

Możesz mi wytłumaczyć jaka jest różnica w SCHEDULED a SCHEDULED_IMMEDIATESTART ?

Avatar 2
Korneliusz Linux Ubuntu / Mozilla Firefox 43.0
09 February 2016 - 07:16 Bytom

SCHEDULED_IMMEDIATESTART wykona się od razu po zdefiniowaniu wątka, czyli jakby wymuszony pierwszy start od razu, niezależnie jak go ustawisz. Chyba :)

Ale odkopałeś dinozaura :) Gratuluję.

Avatar 1
rafal Windows 8 / Mozilla Firefox 44.0
12 February 2016 - 19:09 Pątnów

Jest jeden minus tej biblioteki obsługuje tylko 9 wątków, dziesiąty już nie jest wywoływany

Avatar 1
uzi18 Linux / Mozilla Firefox 45.0
19 April 2016 - 20:16 Kraków

Dodatkowo nie mozna uzywac przerwan ...

Avatar 1
Ben Windows / Safari 537.36
03 January 2017 - 22:23 Warszawa

Mam pewien problem. Uprościłem maksymalnie użycie wątków i biblioteki aby sprowadzić to wszystko do jak najprostszej postaci. Wydaje się, że wątki działają poprawnie ale tylko przez 200 milisekund po czym zawieszają się.
Jeśli ustawię:

threads.addTask(flashGreenLed, threads.convertMs(100) SCHEDULED_IMMEDIATESTART);

zielona dioda mrugnie 2x, natomiast gdy ustawię

threads.addTask(flashGreenLed, threads.convertMs(50) SCHEDULED_IMMEDIATESTART);

zielona dioda mrugnie 4x.

Po czym obydwie diody zawieszają się. Obie gasną lub obie się zaświecają lub jedna zostaje zapalona a druga zgaszona.
Czy jakiś pomysł na rozwiązanie?