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
- #include "leOS2.h"
- leOS2 threads;
W następnej kolejności w konstruktorze setup() wystarczy zdefiniować nasze wątki i określić ich parametry:
- void setup() {
- ...
- threads.begin();
- threads.addTask(naszaFunkcja1, threads.convertMs(1000));
- threads.addTask(naszaFunkcja2, threads.convertMs(2000));
- ...
- }
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ą
- #include <Bounce.h>
- #include "leOS2.h"
- leOS2 threads;
- int buttonStartStopPin = 2; // PIN2 - Przycisk włącz / wyłącz
- int buttonSpeedPin = 4; // PIN4 - Przycisk zmiany prędkości
- int greenLedPin = 3; // PIN3 - Dioda zielona
- int yellowLedPin = 5; // PIN5 - Dioda żółta
- int redRGBPin = 9; // PIN9 - RGB - Dioda czerwona
- int greenRGBPin = 10; // PIN10 - RGB - Dioda zielona
- int blueRGBPin = 11; // PIN11 - RGB - Dioda niebieska
- int yellowLedSpeed = 200; // Prędkość początkowa procesu mrugania
- int greenLedPWM = 255; // Wypełnienie początkowe PWM diody zielonej
- byte greenLedStatus = 1; // Status diody zielonej
- byte yellowLedStatus = 0; // Status diody żóltej
- // Ustawiamy nasze "bouncery" z interwałem czasowym 50ms
- Bounce bouncerStartStop = Bounce(buttonStartStopPin, 50);
- Bounce bouncerSpeed = Bounce(buttonSpeedPin, 50);
- // Konfiguracja pinów i wątków
- void setup()
- {
- threads.begin(); // rozpoczynamy pracę z wątkami
- // Przyciski
- pinMode(buttonStartStopPin, INPUT);
- pinMode(buttonSpeedPin, INPUT);
- // Diody
- pinMode(redRGBPin, OUTPUT);
- pinMode(greenRGBPin, OUTPUT);
- pinMode(blueRGBPin, OUTPUT);
- pinMode(yellowLedPin, OUTPUT);
- pinMode(greenLedPin, OUTPUT);
- // Dodajemy zadania uruchamiane od razu
- threads.addTask(flashGreenLed, threads.convertMs(25), SCHEDULED_IMMEDIATESTART);
- threads.addTask(onOffPush, threads.convertMs(50), SCHEDULED_IMMEDIATESTART);
- threads.addTask(speedPush, threads.convertMs(50), SCHEDULED_IMMEDIATESTART);
- // Dodajemy zadania do uruchomienia później
- threads.addTask(flashYellowLed, threads.convertMs(yellowLedSpeed), PAUSED);
- }
- // Główna pętla programu - tym razem pusta
- void loop()
- {
- }
- // Wątek obsługi przycisku zmiany prędkości
- void speedPush()
- {
- // Aktualizujemy status przycisku
- bouncerSpeed.update();
- // Reagujemy na zbocze narastające
- if (bouncerSpeed.risingEdge())
- {
- // Zmniejszamy czas oczekiwania na wątek
- yellowLedSpeed -= 40;
- // Jeśli prędkość wynosi 0, ustawiamy na 200 i sygnalizujemy niebieską diodą RGB
- // Jeśli nie, sygnalizujemy zieloną diodą RGB
- if (yellowLedSpeed == 0)
- {
- yellowLedSpeed = 200;
- digitalWrite(blueRGBPin, HIGH);
- } else
- {
- digitalWrite(greenRGBPin, HIGH);
- }
- // Zapamiętujemy status naszego wątka flashYellowLed
- byte taskStatus = threads.getTaskStatus(flashYellowLed);
- // Usuwamy wątek mrugania żółtą diodą
- threads.removeTask(flashYellowLed);
- // Dodajemy nowy wątek ze starym statusem i nowym czasem
- threads.addTask(flashYellowLed, threads.convertMs(yellowLedSpeed), taskStatus);
- } else
- {
- // Gasimy diodę RGB, zieloną i niebieską
- digitalWrite(greenRGBPin, LOW);
- digitalWrite(blueRGBPin, LOW);
- }
- }
- // Wątek obsługi przycisku wlącz/wyłącz
- void onOffPush()
- {
- // Aktualizujemy status przycisku
- bouncerStartStop.update();
- // Reagujemy na zbocze narastające
- if (bouncerStartStop.fallingEdge())
- {
- // Sygnalizujemy czerwoną diodą RGB
- digitalWrite(redRGBPin, HIGH);
- // Pobieramy status naszego wątka flashYellowLed
- byte taskStatus = threads.getTaskStatus(flashYellowLed);
- // Jeśli jest zatrzymany to wznawiamy
- // Jeśli nie jest zatrzymany to zatrzymujemy i gasimy diodę żółtą
- if (taskStatus == 0)
- {
- threads.restartTask(flashYellowLed);
- } else
- {
- threads.pauseTask(flashYellowLed);
- digitalWrite(yellowLedPin, 0);
- }
- } else
- {
- // Gasimy diodę RGB, czerwoną
- digitalWrite(redRGBPin, LOW);
- }
- }
- void flashGreenLed()
- {
- // Zmniejszamy wartość PWM dla diody zielonej
- greenLedPWM -= 5;
- // Jeśli była zapalona to wysyłamy
- if (greenLedStatus == 1)
- {
- analogWrite(greenLedPin, greenLedPWM);
- }
- // Jeśli PWM wyniosło 0 to ustawiamy pełne PWM i zmieniamy status diody
- if (greenLedPWM == 0)
- {
- greenLedPWM = 255;
- greenLedStatus ^= 1;
- }
- }
- void flashYellowLed()
- {
- // Negujemy status diody żółtej
- yellowLedStatus ^= 1;
- // Wysyłamy
- digitalWrite(yellowLedPin, yellowLedStatus);
- }
Schemat układu
Demonstracja działania
Wszystkie materiały wykorzystane w tym wpisie, możecie znaleźć tutaj.
Reklama
Komentarze
Możesz mi wytłumaczyć jaka jest różnica w SCHEDULED a SCHEDULED_IMMEDIATESTART ?
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ę.
Jest jeden minus tej biblioteki obsługuje tylko 9 wątków, dziesiąty już nie jest wywoływany
Dodatkowo nie mozna uzywac przerwan ...
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?