- Was ist Semaphor?
- Wie verwende ich Semaphore in FreeRTOS?
- Erklärung des Semaphorcodes
- Schaltplan
- Was ist Mutex?
- Wie verwende ich Mutex in FreeRTOS?
- Erklärung des Mutex-Codes
In früheren Tutorials haben wir die Grundlagen von FreeRTOS mit Arduino und dem Queue-Kernel-Objekt in FreeRTOS Arduino behandelt. In diesem dritten FreeRTOS-Tutorial erfahren Sie mehr über FreeRTOS und seine erweiterten APIs, mit denen Sie die Multitasking-Plattform besser verstehen können.
Semaphor und Mutex (gegenseitiger Ausschluss) sind die Kernelobjekte, die zur Synchronisierung, Ressourcenverwaltung und zum Schutz von Ressourcen vor Korruption verwendet werden. In der ersten Hälfte dieses Tutorials werden wir die Idee hinter Semaphore sehen, wie und wo es verwendet werden soll. In der zweiten Hälfte werden wir mit Mutex fortfahren.
Was ist Semaphor?
In früheren Tutorials haben wir uns mit Aufgabenprioritäten befasst und auch erfahren, dass eine Aufgabe mit höherer Priorität eine Aufgabe mit niedrigerer Priorität vorbelegt. Während der Ausführung einer Aufgabe mit hoher Priorität besteht daher die Möglichkeit, dass bei Aufgaben mit niedrigerer Priorität Daten beschädigt werden wird noch nicht ausgeführt und Daten kommen kontinuierlich von einem Sensor zu dieser Aufgabe, was zu Datenverlust und Fehlfunktionen der gesamten Anwendung führt.
Es besteht also die Notwendigkeit , Ressourcen vor Datenverlust zu schützen, und hier spielt Semaphore eine wichtige Rolle.
Semaphor ist ein Signalisierungsmechanismus, bei dem eine Aufgabe in einem Wartezustand von einer anderen Aufgabe zur Ausführung signalisiert wird. Mit anderen Worten, wenn eine Aufgabe1 ihre Arbeit beendet hat, zeigt sie ein Flag an oder erhöht ein Flag um 1, und dann wird dieses Flag von einer anderen Aufgabe (Aufgabe2) empfangen, die zeigt, dass sie ihre Arbeit jetzt ausführen kann. Wenn Task2 seine Arbeit beendet hat, wird das Flag um 1 verringert.
Im Grunde handelt es sich also um einen "Geben" - und "Nehmen" -Mechanismus, und das Semaphor ist eine ganzzahlige Variable, die zum Synchronisieren des Zugriffs auf Ressourcen verwendet wird.
Arten von Semaphoren in FreeRTOS:
Es gibt zwei Arten von Semaphoren.
- Binäres Semaphor
- Semaphor zählen
1. Binäres Semaphor: Es hat zwei ganzzahlige Werte 0 und 1. Es ähnelt etwas der Warteschlange der Länge 1. Zum Beispiel haben wir zwei Aufgaben, Aufgabe1 und Aufgabe2. Task1 sendet Daten an Task2, sodass Task2 das Warteschlangenelement kontinuierlich überprüft, wenn es 1 gibt. Dann kann es die Daten lesen, die es warten muss, bis es 1 wird. Nach dem Aufnehmen der Daten dekrementiert Task2 die Warteschlange und macht sie 0 Das bedeutet erneut Task1 kann die Daten an task2 senden.
Aus dem obigen Beispiel kann gesagt werden, dass ein binäres Semaphor zur Synchronisation zwischen Aufgaben oder zwischen Aufgaben und Interrupt verwendet wird.
2. Zählsemaphor: Es hat Werte größer als 0 und kann als Warteschlange mit einer Länge von mehr als 1 betrachtet werden. Dieses Semaphor wird zum Zählen von Ereignissen verwendet. In diesem Verwendungsszenario "gibt" ein Ereignishandler jedes Mal, wenn ein Ereignis auftritt, ein Semaphor (erhöht den Wert der Semaphoranzahl), und eine Handleraufgabe "nimmt" jedes Mal ein Semaphor, wenn sie ein Ereignis verarbeitet (dekrementiert den Wert der Semaphoranzahl)..
Der Zählwert ist daher die Differenz zwischen der Anzahl der aufgetretenen Ereignisse und der Anzahl der verarbeiteten Ereignisse.
Nun wollen wir sehen, wie Semaphore in unserem FreeRTOS-Code verwendet wird.
Wie verwende ich Semaphore in FreeRTOS?
FreeRTOS unterstützt verschiedene APIs zum Erstellen eines Semaphors, zum Aufnehmen eines Semaphors und zum Geben eines Semaphors.
Jetzt kann es zwei Arten von APIs für dasselbe Kernelobjekt geben. Wenn wir ein Semaphor von einem ISR angeben müssen, kann die normale Semaphor-API nicht verwendet werden. Sie sollten Interrupt-geschützte APIs verwenden.
In diesem Tutorial verwenden wir binäres Semaphor, da es leicht zu verstehen und zu implementieren ist. Da hier die Interrupt-Funktionalität verwendet wird, müssen Sie Interrupt-geschützte APIs in der ISR-Funktion verwenden. Wenn wir sagen, dass eine Aufgabe mit einem Interrupt synchronisiert wird, bedeutet dies, dass die Aufgabe direkt nach dem ISR in den Status "Ausführen" versetzt wird.
Erstellen eines Semaphors:
Um ein Kernel-Objekt zu verwenden, müssen wir es zuerst erstellen. Verwenden Sie zum Erstellen eines binären Semaphors vSemaphoreCreateBinary ().
Diese API akzeptiert keine Parameter und gibt eine Variable vom Typ SemaphoreHandle_t zurück. Ein globaler Variablenname sema_v wird erstellt, um das Semaphor zu speichern.
SemaphoreHandle_t sema_v; sema_v = xSemaphoreCreateBinary ();
Ein Semaphor geben:
Für die Angabe eines Semaphors gibt es zwei Versionen - eine für Interrupt und eine für die normale Aufgabe.
- xSemaphoreGive (): Diese API verwendet nur ein Argument, nämlich den Variablennamen des Semaphors wie sema_v, wie oben angegeben, während ein Semaphor erstellt wird. Es kann von jeder normalen Aufgabe aufgerufen werden, die Sie synchronisieren möchten.
- xSemaphoreGiveFromISR (): Dies ist die Interrupt-geschützte API-Version von xSemaphoreGive (). Wenn wir einen ISR und eine normale Aufgabe synchronisieren müssen, sollte xSemaphoreGiveFromISR () über die ISR-Funktion verwendet werden.
Ein Semaphor nehmen:
Verwenden Sie zum Aufnehmen eines Semaphors die API-Funktion xSemaphoreTake (). Diese API akzeptiert zwei Parameter.
xSemaphoreTake (SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
xSemaphore: Name des Semaphors, das in unserem Fall sema_v verwendet werden soll.
xTicksToWait: Dies ist die maximale Zeit, die die Aufgabe im Status "Blockiert" wartet, bis das Semaphor verfügbar ist. In unserem Projekt setzen wir xTicksToWait auf portMAX_DELAY , damit task_1 im blockierten Zustand unbegrenzt wartet, bis sema_v verfügbar ist.
Verwenden wir nun diese APIs und schreiben einen Code, um einige Aufgaben auszuführen.
Hier sind ein Druckknopf und zwei LEDs miteinander verbunden. Der Druckknopf fungiert als Interrupt-Knopf, der an Pin 2 von Arduino Uno angebracht ist. Wenn diese Taste gedrückt wird, wird ein Interrupt generiert und eine LED, die an Pin 8 angeschlossen ist, wird eingeschaltet. Wenn Sie sie erneut drücken, wird sie ausgeschaltet.
Wenn die Taste gedrückt wird, wird xSemaphoreGiveFromISR () von der ISR-Funktion und die Funktion xSemaphoreTake () von der TaskLED-Funktion aufgerufen.
Um das System multitaskingartig aussehen zu lassen, schließen Sie andere LEDs an Pin 7 an, der immer blinkt.
Erklärung des Semaphorcodes
Beginnen wir mit dem Schreiben von Code, indem wir die Arduino IDE öffnen
1. Fügen Sie zunächst die Header-Datei Arduino_FreeRTOS.h hinzu . Wenn nun ein Kernelobjekt wie ein Warteschlangensemaphor verwendet wird, muss auch eine Headerdatei dafür enthalten sein.
#include #include
2. Deklarieren Sie eine Variable vom Typ SemaphoreHandle_t, um die Werte des Semaphors zu speichern.
SemaphoreHandle_t interruptSemaphore;
3. Erstellen Sie in void setup () zwei Aufgaben (TaskLED und TaskBlink) mit der xTaskCreate () - API und erstellen Sie dann ein Semaphor mit xSemaphoreCreateBinary (). Erstellen Sie eine Aufgabe mit gleichen Prioritäten und versuchen Sie später, mit dieser Nummer zu spielen. Konfigurieren Sie außerdem Pin 2 als Eingang, aktivieren Sie den internen Pull-up-Widerstand und schließen Sie den Interrupt-Pin an. Starten Sie abschließend den Scheduler wie unten gezeigt.
void setup () { pinMode (2, INPUT_PULLUP); xTaskCreate (TaskLed, "Led", 128, NULL, 0, NULL); xTaskCreate (TaskBlink, "LedBlink", 128, NULL, 0, NULL); InterruptSemaphore = xSemaphoreCreateBinary (); if (interruptSemaphore! = NULL) { attachInterrupt (digitalPinToInterrupt (2), debounceInterrupt, LOW); } }
4. Implementieren Sie nun die ISR-Funktion. Erstellen Sie eine Funktion und benennen Sie sie wie das zweite Argument der Funktion attachInterrupt () . Damit der Interrupt ordnungsgemäß funktioniert, müssen Sie das Entprellungsproblem des Druckknopfs mithilfe der Millis- oder Mikros-Funktion und durch Anpassen der Entprellzeit beseitigen. Rufen Sie von dieser Funktion aus die Funktion interruptHandler () auf (siehe unten).
lange Entprellzeit = 150; volatile unsigned long last_micros; void debounceInterrupt () { if ((long) (micros () - last_micros)> = debouncing_time * 1000) { interruptHandler (); last_micros = micros (); } }
In unterbrechungsroutine () Funktion ruft xSemaphoreGiveFromISR () API.
void InterruptHandler () { xSemaphoreGiveFromISR (InterruptSemaphore, NULL); }}
Diese Funktion gibt TaskLed ein Semaphor zum Einschalten der LED.
5. Erstellen Sie eine TaskLed- Funktion und rufen Sie innerhalb der while- Schleife die xSemaphoreTake () -API auf und prüfen Sie, ob das Semaphor erfolgreich erstellt wurde oder nicht. Wenn es gleich pdPASS (dh 1) ist, schalten Sie die LED wie unten gezeigt um.
void TaskLed (void * pvParameters) { (void) pvParameters; PinMode (8, OUTPUT); while (1) { if (xSemaphoreTake (InterruptSemaphore, portMAX_DELAY) == pdPASS) { digitalWrite (8,! digitalRead (8)); } } }
6. Erstellen Sie außerdem eine Funktion zum Blinken anderer LEDs, die an Pin 7 angeschlossen sind.
void TaskLed1 (void * pvParameters) { (void) pvParameters; PinMode (7, OUTPUT); während (1) { digitalWrite (7, HIGH); vTaskDelay (200 / portTICK_PERIOD_MS); digitalWrite (7, LOW); vTaskDelay (200 / portTICK_PERIOD_MS); } }
7. Die Void-Loop-Funktion bleibt leer. Vergiss es nicht.
void loop () {}
Den vollständigen Code finden Sie am Ende dieses Tutorials. Laden Sie nun diesen Code hoch und verbinden Sie die LEDs und den Druckknopf gemäß Schaltplan mit dem Arduino UNO.
Schaltplan
Nach dem Hochladen des Codes blinkt nach 200 ms eine LED. Wenn die Taste gedrückt wird, leuchtet sofort die zweite LED, wie im Video am Ende gezeigt.
Auf diese Weise können Semaphoren in FreeRTOS mit Arduino verwendet werden, wo die Daten verlustfrei von einer Aufgabe zur anderen weitergegeben werden müssen.
Nun wollen wir sehen, was Mutex ist und wie man es FreeRTOS verwendet.
Was ist Mutex?
Wie oben erläutert, ist Semaphor ein Signalisierungsmechanismus. In ähnlicher Weise ist Mutex ein Sperrmechanismus im Gegensatz zum Semaphor, das separate Funktionen zum Inkrementieren und Dekrementieren hat, aber in Mutex nimmt und gibt die Funktion an sich. Dies ist eine Technik, um die Beschädigung gemeinsam genutzter Ressourcen zu vermeiden.
Um die gemeinsam genutzte Ressource zu schützen, weist man der Ressource eine Token-Karte (Mutex) zu. Wer diese Karte hat, kann auf die andere Ressource zugreifen. Andere sollten warten, bis die Karte zurückgegeben wird. Auf diese Weise kann nur eine Ressource auf die Aufgabe zugreifen und andere warten auf ihre Chance.
Lassen Sie uns Mutex in FreeRTOS anhand eines Beispiels verstehen.
Hier haben wir drei Aufgaben: Eine zum Drucken von Daten auf dem LCD, eine zweite zum Senden von LDR-Daten an die LCD-Aufgabe und die letzte Aufgabe zum Senden von Temperaturdaten auf dem LCD. Hier teilen sich also zwei Aufgaben dieselbe Ressource, dh LCD. Wenn die LDR-Task und die Temperatur-Task gleichzeitig Daten senden, ist möglicherweise eine der Daten beschädigt oder geht verloren.
Um den Datenverlust zu schützen, müssen wir die LCD-Ressource für Aufgabe1 sperren, bis die Anzeigeaufgabe abgeschlossen ist. Dann wird die LCD-Aufgabe entsperrt und dann kann Aufgabe 2 ihre Arbeit ausführen.
Sie können die Funktionsweise von Mutex und Semaphoren im folgenden Diagramm beobachten.
Wie verwende ich Mutex in FreeRTOS?
Mutexe werden ebenso wie Semaphoren verwendet. Erst erstellen, dann geben und nehmen mit den entsprechenden APIs.
Erstellen eines Mutex:
Verwenden Sie zum Erstellen eines Mutex die xSemaphoreCreateMutex () - API . Wie der Name schon sagt, handelt es sich bei Mutex um eine Art binäres Semaphor. Sie werden in verschiedenen Kontexten und Zwecken verwendet. Ein binäres Semaphor dient zum Synchronisieren von Aufgaben, während Mutex zum Schutz einer gemeinsam genutzten Ressource verwendet wird.
Diese API akzeptiert kein Argument und gibt eine Variable vom Typ SemaphoreHandle_t zurück . Wenn der Mutex nicht erstellt werden kann, gibt xSemaphoreCreateMutex () NULL zurück.
SemaphoreHandle_t mutex_v; mutex_v = xSemaphoreCreateMutex ();
Einen Mutex nehmen:
Wenn eine Aufgabe auf eine Ressource zugreifen möchte, benötigt sie mithilfe der xSemaphoreTake () -API einen Mutex. Es ist dasselbe wie ein binäres Semaphor. Es werden auch zwei Parameter benötigt.
xSemaphore: Name des Mutex, der in unserem Fall verwendet werden soll mutex_v .
xTicksToWait: Dies ist die maximale Zeit, die die Aufgabe im blockierten Zustand wartet, bis der Mutex verfügbar ist. In unserem Projekt setzen wir xTicksToWait auf portMAX_DELAY , damit task_1 im blockierten Zustand unbegrenzt wartet, bis mutex_v verfügbar ist.
Einen Mutex geben:
Nach dem Zugriff auf die freigegebene Ressource sollte die Aufgabe den Mutex zurückgeben, damit andere Aufgaben darauf zugreifen können. Die xSemaphoreGive () - API wird verwendet, um den Mutex zurückzugeben.
Die Funktion xSemaphoreGive () verwendet nur ein Argument, nämlich den Mutex, der in unserem Fall mutex_v angegeben werden soll.
Verwenden Sie die oben genannten APIs, um Mutex mithilfe der Arduino IDE im FreeRTOS-Code zu implementieren.
Erklärung des Mutex-Codes
In diesem Teil besteht das Ziel darin, einen seriellen Monitor als gemeinsam genutzte Ressource und zwei verschiedene Aufgaben zu verwenden, um auf den seriellen Monitor zuzugreifen und eine Nachricht zu drucken.
1. Die Header-Dateien bleiben dieselben wie ein Semaphor.
#include #include
2. Deklarieren Sie eine Variable vom Typ SemaphoreHandle_t , um die Werte von Mutex zu speichern.
SemaphoreHandle_t mutex_v;
3. Initialisieren Sie in void setup () den seriellen Monitor mit einer Baudrate von 9600 und erstellen Sie zwei Tasks (Task1 und Task2) mithilfe der xTaskCreate () -API . Erstellen Sie dann einen Mutex mit xSemaphoreCreateMutex (). Erstellen Sie eine Aufgabe mit gleichen Prioritäten und versuchen Sie später, mit dieser Nummer zu spielen.
void setup () { Serial.begin (9600); mutex_v = xSemaphoreCreateMutex (); if (mutex_v == NULL) { Serial.println ("Mutex kann nicht erstellt werden"); } xTaskCreate (Task1, "Task 1", 128, NULL, 1, NULL); xTaskCreate (Task2, "Task 2", 128, NULL, 1, NULL); }}
4. Erstellen Sie nun Aufgabenfunktionen für Aufgabe1 und Aufgabe2. In einer while- Schleife der Task-Funktion müssen wir vor dem Drucken einer Nachricht auf dem seriellen Monitor einen Mutex mit xSemaphoreTake () erstellen, dann die Nachricht drucken und den Mutex mit xSemaphoreGive () zurückgeben. Dann gib etwas Verzögerung.
void Task1 (void * pvParameters) { while (1) { xSemaphoreTake (mutex_v, portMAX_DELAY); Serial.println ("Hi from Task1"); xSemaphoreGive (mutex_v); vTaskDelay (pdMS_TO_TICKS (1000)); } }
Implementieren Sie in ähnlicher Weise die Task2-Funktion mit einer Verzögerung von 500 ms.
5. Die Leerschleife () bleibt leer.
Laden Sie nun diesen Code auf Arduino UNO hoch und öffnen Sie den seriellen Monitor.
Sie werden sehen, dass Nachrichten von Task1 und Task2 gedruckt werden.
Um die Funktionsweise von Mutex zu testen, kommentieren Sie einfach xSemaphoreGive (mutex_v). von jeder Aufgabe. Sie können sehen, dass das Programm an der letzten Drucknachricht hängt .
So können Semaphore und Mutex in FreeRTOS mit Arduino implementiert werden. Weitere Informationen zu Semaphore und Mutex finden Sie in der offiziellen Dokumentation von FreeRTOS.
Vollständige Codes und Videos für Semaphore und Mutes sind unten angegeben.