- Co to jest semafor?
- Jak używać semafora w FreeRTOS?
- Wyjaśnienie kodu semafora
- Schemat obwodu
- Co to jest Mutex?
- Jak korzystać z Mutex w FreeRTOS?
- Objaśnienie kodu mutex
W poprzednich samouczkach omówiliśmy podstawy FreeRTOS z Arduino i obiekt jądra kolejki w FreeRTOS Arduino. Teraz, w tym trzecim samouczku dotyczącym FreeRTOS, dowiemy się więcej o FreeRTOS i jego zaawansowanych interfejsach API, które pozwolą Ci lepiej zrozumieć platformę wielozadaniową.
Semaphore i Mutex (Mutual Exclusion) to obiekty jądra, które są używane do synchronizacji, zarządzania zasobami i ochrony zasobów przed uszkodzeniem. W pierwszej połowie tego samouczka zobaczymy ideę Semaphore, jak i gdzie go używać. W drugiej połowie będziemy kontynuować Mutex.
Co to jest semafor?
W poprzednich samouczkach omawialiśmy priorytety zadań, a także dowiedzieliśmy się, że zadanie o wyższym priorytecie wyprzedza zadanie o niższym priorytecie, więc podczas wykonywania zadania o wysokim priorytecie może wystąpić możliwość uszkodzenia danych w zadaniu o niższym priorytecie, ponieważ nie jest jeszcze wykonywany, a dane napływają do tego zadania w sposób ciągły z czujnika, co powoduje utratę danych i nieprawidłowe działanie całej aplikacji.
Istnieje więc potrzeba ochrony zasobów przed utratą danych i tutaj semafor odgrywa ważną rolę.
Semafor to mechanizm sygnalizacyjny, w którym zadanie w stanie oczekiwania jest sygnalizowane przez inne zadanie do wykonania. Innymi słowy, gdy zadanie 1 zakończy pracę, pokaże flagę lub zwiększy flagę o 1, a następnie tę flagę otrzyma inne zadanie (zadanie 2), pokazując, że może teraz wykonać swoją pracę. Kiedy zadanie 2 zakończy swoją pracę, flaga zostanie zmniejszona o 1.
Zasadniczo jest to mechanizm „dawania” i „przyjmowania”, a semafor jest zmienną całkowitą, która służy do synchronizacji dostępu do zasobów.
Rodzaje semaforów w FreeRTOS:
Semafor jest dwojakiego rodzaju.
- Semafor binarny
- Liczenie semaforów
1. Semafor binarny: Ma dwie wartości całkowite 0 i 1. Jest trochę podobny do kolejki o długości 1. Na przykład mamy dwa zadania, zadanie 1 i zadanie 2. Zadanie1 wysyła dane do zadania2, więc zadanie2 stale sprawdza pozycję kolejki, jeśli jest 1, a następnie może odczytać dane w przeciwnym razie musi czekać, aż stanie się 1. Po pobraniu danych, zadanie2 zmniejsz kolejkę i ustaw ją na 0 To oznacza zadanie1 ponownie może wysłać dane do task2.
Z powyższego przykładu można powiedzieć, że semafor binarny służy do synchronizacji między zadaniami lub między zadaniami a przerwaniem.
2. Semafor liczący: ma wartości większe niż 0 i można go traktować jako kolejkę o długości większej niż 1. Ten semafor służy do zliczania zdarzeń. W tym scenariuszu procedura obsługi zdarzeń `` poda '' semafor za każdym razem, gdy wystąpi zdarzenie (zwiększając wartość liczby semaforów), a zadanie procedury obsługi `` pobierze '' semafor za każdym razem, gdy przetworzy zdarzenie (zmniejszenie wartości licznika semaforów).
Wartość licznika jest zatem różnicą między liczbą zdarzeń, które miały miejsce, a liczbą przetworzonych.
Teraz zobaczmy, jak używać Semaphore w naszym kodzie FreeRTOS.
Jak używać semafora w FreeRTOS?
FreeRTOS obsługuje różne interfejsy API do tworzenia semaforów, pobierania semaforów i podawania semaforów.
Teraz mogą istnieć dwa typy API dla tego samego obiektu jądra. Jeśli musimy podać semafor z ISR, nie można użyć normalnego API semafora. Powinieneś używać interfejsów API chronionych przed przerwaniami.
W tym samouczku użyjemy semafora binarnego, ponieważ jest on łatwy do zrozumienia i wdrożenia. Ponieważ używana jest tutaj funkcja przerwań, w funkcji ISR należy używać funkcji API chronionych przed przerwaniami. Kiedy mówimy o synchronizacji zadania z przerwaniem, oznacza to wprowadzenie zadania w stan Running zaraz po ISR.
Tworzenie semafora:
Aby użyć dowolnego obiektu jądra, musimy go najpierw utworzyć. Aby utworzyć semafor binarny, użyj vSemaphoreCreateBinary ().
Ten interfejs API nie przyjmuje żadnego parametru i zwraca zmienną typu SemaphoreHandle_t. W celu przechowywania semafora tworzona jest globalna nazwa zmiennej sema_v.
SemaphoreHandle_t sema_v; sema_v = xSemaphoreCreateBinary ();
Podawanie semafora:
Istnieją dwie wersje semafora - jedna do przerywania i druga do normalnego zadania.
- xSemaphoreGive (): To API przyjmuje tylko jeden argument, który jest nazwą zmiennej semafora, taką jak sema_v, jak podano powyżej, podczas tworzenia semafora. Można go wywołać z dowolnego normalnego zadania, które chcesz zsynchronizować.
- xSemaphoreGiveFromISR (): To jest chroniona przed przerwaniami wersja API xSemaphoreGive (). Kiedy musimy zsynchronizować ISR i normalne zadanie, należy użyć xSemaphoreGiveFromISR () z funkcji ISR.
Biorąc semafor:
Aby wziąć semafor, użyj funkcji API xSemaphoreTake (). Ten interfejs API ma dwa parametry.
xSemaphoreTake (SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
xSemaphore: nazwa semafora, który ma zostać pobrany w naszym przypadku sema_v.
xTicksToWait: jest to maksymalny czas, przez jaki zadanie będzie czekało w stanie zablokowanym na udostępnienie semafora. W naszym projekcie ustawimy xTicksToWait na portMAX_DELAY, aby zadanie_1 czekało w nieskończoność w stanie Blocked, aż sema_v będzie dostępne.
Teraz użyjmy tych interfejsów API i napiszmy kod do wykonania niektórych zadań.
Tutaj jeden przycisk i dwie diody LED są połączone. Przycisk będzie działał jako przycisk przerwania, który jest podłączony do pinu 2 Arduino Uno. Po naciśnięciu tego przycisku zostanie wygenerowane przerwanie i dioda LED podłączona do pinu 8 zaświeci się, a po ponownym naciśnięciu będzie wyłączona.
Tak więc po naciśnięciu przycisku xSemaphoreGiveFromISR () zostanie wywołane z funkcji ISR, a funkcja xSemaphoreTake () z funkcji TaskLED.
Aby system wyglądał na wielozadaniowy, podłącz pozostałe diody za pomocą pinu 7, który będzie zawsze migał.
Wyjaśnienie kodu semafora
Zacznijmy pisać kod od otwarcia Arduino IDE
1. Najpierw dołącz plik nagłówkowy Arduino_FreeRTOS.h . Teraz, jeśli jakikolwiek obiekt jądra jest używany jak semafor kolejki, to należy również dołączyć do niego plik nagłówkowy.
#include #include
2. Zadeklaruj zmienną typu SemaphoreHandle_t do przechowywania wartości semafora.
SemaphoreHandle_t breakingSemaphore;
3. W void setup () utwórz dwa zadania (TaskLED i TaskBlink) za pomocą interfejsu API xTaskCreate (), a następnie utwórz semafor za pomocą xSemaphoreCreateBinary (), utwórz zadanie o równych priorytetach, a następnie spróbuj grać z tym numerem. Skonfiguruj również pin 2 jako wejście i włącz wewnętrzny rezystor podciągający i podłącz pin przerwania. Na koniec uruchom program planujący, jak pokazano poniżej.
void setup () { pinMode (2, INPUT_PULLUP); xTaskCreate (TaskLed, "Led", 128, NULL, 0, NULL); xTaskCreate (TaskBlink, "LedBlink", 128, NULL, 0, NULL); przerwaćSemaphore = xSemaphoreCreateBinary (); if ( breakSemaphore ! = NULL) { attachInterrupt (digitalPinToInterrupt (2), debounceInterrupt, LOW); } }
4. Teraz zaimplementuj funkcję ISR. Stwórz funkcję i nazwij ją taką samą, jak drugi argument funkcji attachInterrupt () . Aby przerwanie działało poprawnie, musisz usunąć problem odbicia przycisku za pomocą funkcji millis lub micros i dostosowując czas odbicia. Z tej funkcji wywołaj funkcję breakingHandler (), jak pokazano poniżej.
długi czas_odbicia = 150; volatile unsigned long last_micros; void debounceInterrupt () { if ((long) (micros () - last_micros)> = debouncing_time * 1000) {breakingHandler (); last_micros = micros (); } }
W interruptHandler () funkcji, zadzwoń xSemaphoreGiveFromISR () API.
void breakingHandler () { xSemaphoreGiveFromISR (breakingSemaphore, NULL); }
Ta funkcja poda semafor do TaskLed, aby włączyć diodę LED.
5. Tworzenie TaskLed funkcji i wewnątrz while pętli, zadzwoń xSemaphoreTake () API i sprawdzić, czy semafor jest z powodzeniem podjęte lub nie. Jeśli jest równe pdPASS (tj. 1), przełącz diodę LED, jak pokazano poniżej.
void TaskLed (void * pvParameters) { (void) pvParameters; pinMode (8, WYJŚCIE); while (1) { if (xSemaphoreTake (przerwanieSemaphore, portMAX_DELAY) == pdPASS) { digitalWrite (8,! digitalRead (8)); } } }
6. Utwórz również funkcję migania innych diod LED podłączonych do pinu 7.
void TaskLed1 (void * pvParameters) { (void) pvParameters; pinMode (7, WYJŚCIE); while (1) { digitalWrite (7, WYSOKI); vTaskDelay (200 / portTICK_PERIOD_MS); digitalWrite (7, NISKI); vTaskDelay (200 / portTICK_PERIOD_MS); } }
7. Funkcja void loop pozostanie pusta. Nie zapomnij o tym.
void loop () {}
To wszystko, cały kod można znaleźć na końcu tego samouczka. Teraz prześlij ten kod i połącz diody LED i przycisk z Arduino UNO zgodnie ze schematem obwodu.
Schemat obwodu
Po wgraniu kodu zobaczysz, że dioda LED zacznie migać po 200ms, a po naciśnięciu przycisku, natychmiast zaświeci się druga dioda LED, jak pokazano na filmie podanym na końcu.
W ten sposób semafory mogą być używane we FreeRTOS z Arduino, gdzie musi przekazywać dane z jednego zadania do drugiego bez żadnych strat.
Zobaczmy teraz, czym jest Mutex i jak z niego korzystać FreeRTOS.
Co to jest Mutex?
Jak wyjaśniono powyżej, semafor jest mechanizmem sygnalizacyjnym, podobnie Mutex jest mechanizmem blokującym w przeciwieństwie do semafora, który ma oddzielne funkcje zwiększania i zmniejszania, ale w Muteksie funkcja przyjmuje i daje sama. Jest to technika unikania korupcji wspólnych zasobów.
Aby chronić współdzielony zasób, przypisuje się kartę żetonową (mutex) do zasobu. Ktokolwiek ma tę kartę, może uzyskać dostęp do innych zasobów. Inni powinni poczekać, aż karta wróci. W ten sposób tylko jeden zasób może uzyskać dostęp do zadania, a inne czekają na swoją szansę.
Miejmy zrozumieć Mutex w FreeRTOS z pomocą przykładu.
Tutaj mamy trzy zadania, jedno do drukowania danych na LCD, drugie do wysyłania danych LDR do zadania LCD i ostatnie zadanie do wysyłania danych temperatury na LCD. Zatem tutaj dwa zadania współużytkują ten sam zasób, tj. LCD. Jeśli zadanie LDR i zadanie temperatury wysyłają dane jednocześnie, to jedno z nich może zostać uszkodzone lub utracone.
Aby zabezpieczyć się przed utratą danych, musimy zablokować zasób LCD dla zadania 1, dopóki nie zakończy zadania wyświetlania. Następnie zadanie LCD odblokuje się i task2 może wykonać swoją pracę.
Możesz obserwować działanie muteksu i semaforów na poniższym diagramie.
Jak korzystać z Mutex w FreeRTOS?
Muteksy są również używane w taki sam sposób jak semafory. Najpierw utwórz go, a następnie dawaj i bierz za pomocą odpowiednich interfejsów API.
Tworzenie muteksu:
Aby utworzyć Mutex, użyj interfejsu API xSemaphoreCreateMutex () . Jak sama nazwa wskazuje, Mutex jest rodzajem semafora binarnego. Są używane w różnych kontekstach i celach. Semafor binarny służy do synchronizacji zadań, podczas gdy Mutex służy do ochrony udostępnionego zasobu.
Ten interfejs API nie przyjmuje żadnego argumentu i zwraca zmienną typu SemaphoreHandle_t . Jeśli nie można utworzyć muteksu , xSemaphoreCreateMutex () zwraca NULL.
SemaphoreHandle_t mutex_v; mutex_v = xSemaphoreCreateMutex ();
Biorąc Mutex:
Gdy zadanie chce uzyskać dostęp do zasobu, będzie wymagało Mutex przy użyciu funkcji API xSemaphoreTake () . Działa tak samo jak semafor binarny. Przyjmuje również dwa parametry.
xSemaphore: nazwa muteksu, który ma zostać pobrany w naszym przypadku mutex_v .
xTicksToWait: Jest to maksymalny czas, przez jaki zadanie będzie czekało w stanie Zablokowane na udostępnienie muteksu. W naszym projekcie ustawimy xTicksToWait na portMAX_DELAY, aby zadanie_1 czekało w nieskończoność w stanie zablokowanym, aż mutex_v będzie dostępny.
Dawanie muteksu:
Po uzyskaniu dostępu do udostępnionego zasobu zadanie powinno zwrócić Mutex, aby inne zadania miały do niego dostęp. API xSemaphoreGive () jest używane do przywrócenia Mutexu.
Funkcja xSemaphoreGive () przyjmuje tylko jeden argument, którym jest Mutex, który ma zostać podany w naszym przypadku mutex_v.
Korzystając z powyższych API, zaimplementujmy Mutex w kodzie FreeRTOS za pomocą Arduino IDE.
Objaśnienie kodu mutex
Tutaj celem tej części jest użycie monitora szeregowego jako współdzielonego zasobu i dwóch różnych zadań, aby uzyskać dostęp do monitora szeregowego w celu wydrukowania wiadomości.
1. Pliki nagłówkowe pozostaną takie same jak semafor.
#include #include
2. Zadeklaruj zmienną typu SemaphoreHandle_t do przechowywania wartości Mutex.
SemaphoreHandle_t mutex_v;
3. W void setup (), zainicjuj monitor szeregowy z szybkością 9600 bodów i utwórz dwa zadania (Task1 i Task2) za pomocą funkcji API xTaskCreate () . Następnie utwórz Mutex za pomocą xSemaphoreCreateMutex (). Stwórz zadanie o równych priorytetach, a później spróbuj grać z tą liczbą.
void setup () { Serial.begin (9600); mutex_v = xSemaphoreCreateMutex (); if (mutex_v == NULL) { Serial.println ("Mutex nie może zostać utworzony"); } xTaskCreate (Zadanie1, "Zadanie 1", 128, NULL, 1, NULL); xTaskCreate (Zadanie2, "Zadanie 2", 128, NULL, 1, NULL); }
4. Teraz utwórz funkcje zadań dla Zadania1 i Zadania2. W while pętli funkcji zadań, przed wydrukowaniem wiadomość na monitorze szeregowego musimy wziąć Mutex korzystając xSemaphoreTake (), a następnie wydrukować wiadomość, a następnie powrócić mutex korzystając xSemaphoreGive (). Następnie daj trochę czasu.
void Task1 (void * pvParameters) { while (1) { xSemaphoreTake (mutex_v, portMAX_DELAY); Serial.println ("Cześć z Task1"); xSemaphoreGive (mutex_v); vTaskDelay (pdMS_TO_TICKS (1000)); } }
Podobnie, zaimplementuj funkcję Task2 z opóźnieniem 500 ms.
5. Void loop () pozostanie pusty.
Teraz prześlij ten kod na Arduino UNO i otwórz monitor szeregowy.
Zobaczysz komunikaty drukowane z task1 i task2.
Aby przetestować działanie Mutexa , wystarczy skomentować xSemaphoreGive (mutex_v); z dowolnego zadania. Możesz zobaczyć, że program zawiesza się na ostatniej drukowanej wiadomości .
W ten sposób można zaimplementować Semaphore i Mutex we FreeRTOS z Arduino. Aby uzyskać więcej informacji na temat Semaphore i Mutex, możesz odwiedzić oficjalną dokumentację FreeRTOS.
Pełne kody i wideo dla semafora i wyciszenia są podane poniżej.