- Qu'est-ce que le sémaphore?
- Comment utiliser Semaphore dans FreeRTOS?
- Explication du code du sémaphore
- Schéma
- Qu'est-ce que Mutex?
- Comment utiliser Mutex dans FreeRTOS?
- Explication du code Mutex
Dans les tutoriels précédents, nous avons couvert les bases de FreeRTOS avec Arduino et de l'objet noyau Queue dans FreeRTOS Arduino. Maintenant, dans ce troisième didacticiel FreeRTOS, nous en apprendrons plus sur FreeRTOS et ses API avancées, qui peuvent vous permettre de mieux comprendre la plate-forme multitâche.
Le sémaphore et le mutex (exclusion mutuelle) sont les objets du noyau qui sont utilisés pour la synchronisation, la gestion des ressources et la protection des ressources contre la corruption. Dans la première moitié de ce tutoriel, nous verrons l'idée derrière Semaphore, comment et où l'utiliser. En seconde période, nous continuerons avec Mutex.
Qu'est-ce que le sémaphore?
Dans les didacticiels précédents, nous avons discuté des priorités des tâches et nous avons également appris qu'une tâche de priorité plus élevée préempte une tâche de priorité inférieure.Ainsi, pendant l'exécution d'une tâche de priorité élevée, il peut y avoir une possibilité que la corruption des données se produise dans une tâche de priorité inférieure car n'est pas encore exécutée et les données arrivent continuellement à cette tâche à partir d'un capteur, ce qui entraîne une perte de données et un dysfonctionnement de l'ensemble de l'application.
Il est donc nécessaire de protéger les ressources contre la perte de données et le sémaphore joue ici un rôle important.
Le sémaphore est un mécanisme de signalisation dans lequel une tâche dans un état d'attente est signalée par une autre tâche pour exécution. En d'autres termes, lorsqu'une tâche1 a terminé son travail, elle affichera un indicateur ou incrémentera un indicateur de 1, puis cet indicateur est reçu par une autre tâche (tâche2) montrant qu'elle peut effectuer son travail maintenant. Lorsque la tâche 2 a terminé son travail, l'indicateur sera diminué de 1.
Donc, fondamentalement, il s'agit d'un mécanisme «Give» et «Take» et le sémaphore est une variable entière qui est utilisée pour synchroniser l'accès aux ressources.
Types de sémaphore dans FreeRTOS:
Le sémaphore est de deux types.
- Sémaphore binaire
- Compter le sémaphore
1. Sémaphore binaire: Il a deux valeurs entières 0 et 1. Il est quelque peu similaire à la file d'attente de longueur 1. Par exemple, nous avons deux tâches, task1 et task2. Task1 envoie des données à task2 afin que task2 vérifie en permanence l'élément de file d'attente s'il y en a 1, puis il peut lire les données sinon il doit attendre jusqu'à ce qu'il devienne 1. Après avoir pris les données, task2 décrémente la file d'attente et le rend 0 Cela signifie à nouveau task1 peut envoyer les données à task2.
D'après l'exemple ci-dessus, on peut dire que le sémaphore binaire est utilisé pour la synchronisation entre les tâches ou entre les tâches et l'interruption.
2. Comptage du sémaphore: Il a des valeurs supérieures à 0 et peut être considéré comme une file d'attente d'une longueur supérieure à 1. Ce sémaphore est utilisé pour compter les événements. Dans ce scénario d'utilisation, un gestionnaire d'événements `` donnera '' un sémaphore à chaque fois qu'un événement se produit (incrémentation de la valeur du nombre de sémaphores), et une tâche de gestionnaire `` prendra '' un sémaphore à chaque fois qu'il traite un événement (décrémentant la valeur du nombre de sémaphores).
La valeur de comptage est, par conséquent, la différence entre le nombre d'événements qui se sont produits et le nombre qui a été traité.
Voyons maintenant comment utiliser Semaphore dans notre code FreeRTOS.
Comment utiliser Semaphore dans FreeRTOS?
FreeRTOS prend en charge différentes API pour créer un sémaphore, prendre un sémaphore et donner un sémaphore.
Désormais, il peut y avoir deux types d'API pour le même objet noyau. Si nous devons donner un sémaphore à partir d'un ISR, l'API de sémaphore normale ne peut pas être utilisée. Vous devez utiliser des API protégées par interruption.
Dans ce tutoriel, nous utiliserons un sémaphore binaire car il est facile à comprendre et à implémenter. Comme la fonctionnalité d'interruption est utilisée ici, vous devez utiliser des API protégées contre les interruptions dans la fonction ISR. Lorsque nous disons synchroniser une tâche avec une interruption, cela signifie mettre la tâche en état d'exécution juste après l'ISR.
Créer un sémaphore:
Pour utiliser n'importe quel objet du noyau, nous devons d'abord le créer. Pour créer un sémaphore binaire, utilisez vSemaphoreCreateBinary ().
Cette API ne prend aucun paramètre et renvoie une variable de type SemaphoreHandle_t. Un nom de variable globale sema_v est créé pour stocker le sémaphore.
SemaphoreHandle_t sema_v; sema_v = xSemaphoreCreateBinary ();
Donner un sémaphore:
Pour donner un sémaphore, il existe deux versions: une pour l'interruption et une autre pour la tâche normale.
- xSemaphoreGive (): Cette API ne prend qu'un seul argument qui est le nom de variable du sémaphore comme sema_v comme indiqué ci-dessus lors de la création d'un sémaphore. Il peut être appelé à partir de n'importe quelle tâche normale que vous souhaitez synchroniser.
- xSemaphoreGiveFromISR (): Il s'agit de la version API protégée contre les interruptions de xSemaphoreGive (). Lorsque nous devons synchroniser un ISR et une tâche normale, alors xSemaphoreGiveFromISR () doit être utilisé à partir de la fonction ISR.
Prendre un sémaphore:
Pour prendre un sémaphore, utilisez la fonction API xSemaphoreTake (). Cette API prend deux paramètres.
xSemaphoreTake (SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
xSemaphore: Nom du sémaphore à prendre dans notre cas sema_v.
xTicksToWait: Il s'agit de la durée maximale pendant laquelle la tâche attendra dans l'état Bloqué pour que le sémaphore devienne disponible. Dans notre projet, nous allons définir xTicksToWait sur portMAX_DELAY pour que la tâche_1 attende indéfiniment dans l'état Bloqué jusqu'à ce que sema_v soit disponible.
Maintenant, utilisons ces API et écrivons un code pour effectuer certaines tâches.
Ici, un bouton poussoir et deux LED sont interfacés. Le bouton-poussoir agira comme un bouton d'interruption qui est attaché à la broche 2 d'Arduino Uno. Lorsque ce bouton est enfoncé, une interruption sera générée et une LED qui est connectée à la broche 8 sera allumée et lorsque vous appuyez à nouveau, elle sera éteinte.
Ainsi, lorsque le bouton est enfoncé, xSemaphoreGiveFromISR () sera appelé depuis la fonction ISR et la fonction xSemaphoreTake () sera appelée depuis la fonction TaskLED.
Pour rendre le système multitâche, connectez d'autres LED avec la broche 7 qui sera toujours en état de clignotement.
Explication du code du sémaphore
Commençons à écrire du code pour en ouvrant l'IDE Arduino
1. Tout d'abord, incluez le fichier d'en-tête Arduino_FreeRTOS.h . Maintenant, si un objet noyau est utilisé comme sémaphore de file d'attente, un fichier d'en-tête doit également être inclus pour lui.
#include #include
2. Déclarez une variable de type SemaphoreHandle_t pour stocker les valeurs de sémaphore.
SemaphoreHandle_t interruptSemaphore;
3. Dans void setup (), créez deux tâches (TaskLED et TaskBlink) à l'aide de l'API xTaskCreate (), puis créez un sémaphore à l'aide de xSemaphoreCreateBinary (). Créez une tâche avec des priorités égales et essayez plus tard de jouer avec ce numéro. Configurez également la broche 2 en tant qu'entrée et activez la résistance de rappel interne et connectez la broche d'interruption. Enfin, démarrez le planificateur comme indiqué ci-dessous.
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. Maintenant, implémentez la fonction ISR. Créez une fonction et nommez-la comme le deuxième argument de la fonction attachInterrupt () . Pour que l'interruption fonctionne correctement, vous devez supprimer le problème anti-rebond du bouton poussoir en utilisant la fonction millis ou micros et en ajustant le temps de anti-rebond. À partir de cette fonction, appelez la fonction interruptHandler () comme indiqué ci-dessous.
long debouncing_time = 150; volatile unsigned long last_micros; void debounceInterrupt () { if ((long) (micros () - last_micros)> = debouncing_time * 1000) { interruptHandler (); dernier_micros = micros (); } }
Dans la fonction interruptHandler () , appelez l' API xSemaphoreGiveFromISR () .
void interruptHandler () { xSemaphoreGiveFromISR (interruptSemaphore, NULL); }
Cette fonction donnera un sémaphore à TaskLed pour allumer la LED.
5. Créer une TaskLed fonction et à l' intérieur du tout en boucle, appelez xSemaphoreTake () API et vérifier si la sémaphores est prise ou non avec succès. S'il est égal à pdPASS (c'est-à-dire 1), faites basculer la LED comme indiqué ci-dessous.
void TaskLed (void * pvParameters) { (void) pvParameters; pinMode (8, SORTIE); while (1) { if (xSemaphoreTake (interruptionSemaphore, portMAX_DELAY) == pdPASS) { digitalWrite (8,! digitalRead (8)); } } }
6. Créez également une fonction pour faire clignoter d'autres LED connectées à la broche 7.
void TaskLed1 (void * pvParameters) { (void) pvParameters; pinMode (7, SORTIE); while (1) { digitalWrite (7, HIGH); vTaskDelay (200 / portTICK_PERIOD_MS); digitalWrite (7, FAIBLE); vTaskDelay (200 / portTICK_PERIOD_MS); } }
7. La fonction de boucle vide restera vide. N'oubliez pas.
boucle void () {}
Voilà, le code complet se trouve à la fin de ce tutoriel. Maintenant, téléchargez ce code et connectez les LED et le bouton-poussoir à l'Arduino UNO selon le schéma de circuit.
Schéma
Après avoir téléchargé le code, vous verrez qu'une LED clignote après 200 ms et lorsque le bouton est enfoncé, immédiatement la deuxième LED s'allume comme indiqué dans la vidéo donnée à la fin.
De cette façon, les sémaphores peuvent être utilisés dans FreeRTOS avec Arduino où il a besoin de transmettre les données d'une tâche à une autre sans aucune perte.
Voyons maintenant ce qu'est Mutex et comment l'utiliser FreeRTOS.
Qu'est-ce que Mutex?
Comme expliqué ci-dessus, le sémaphore est un mécanisme de signalisation, de même, Mutex est un mécanisme de verrouillage contrairement au sémaphore qui a des fonctions séparées pour l'incrémentation et la décrémentation, mais dans Mutex, la fonction prend et se donne en elle-même. C'est une technique pour éviter la corruption des ressources partagées.
Pour protéger la ressource partagée, on attribue une carte à jeton (mutex) à la ressource. Celui qui possède cette carte peut accéder à l'autre ressource. Les autres doivent attendre le retour de la carte. De cette manière, une seule ressource peut accéder à la tâche et les autres attendent leur chance.
Comprenons Mutex dans FreeRTOS avec l'aide d'un exemple.
Ici, nous avons trois tâches, une pour l'impression des données sur l'écran LCD, la seconde pour l'envoi de données LDR à la tâche LCD et la dernière tâche pour l'envoi des données de température sur l'écran LCD. Donc, ici, deux tâches partagent la même ressource à savoir LCD. Si la tâche LDR et la tâche de température envoient des données simultanément, l'une des données peut être corrompue ou perdue.
Donc, pour protéger la perte de données, nous devons verrouiller la ressource LCD pour la tâche 1 jusqu'à ce qu'elle ait terminé la tâche d'affichage. Ensuite, la tâche LCD se déverrouille et la tâche 2 peut alors effectuer son travail.
Vous pouvez observer le fonctionnement du Mutex et des sémaphores dans le diagramme ci-dessous.
Comment utiliser Mutex dans FreeRTOS?
Les mutex sont également utilisés de la même manière que les sémaphores. Tout d'abord, créez-le, puis donnez et prenez en utilisant les API respectives.
Créer un Mutex:
Pour créer un Mutex, utilisez l' API xSemaphoreCreateMutex () . Comme son nom l'indique, Mutex est un type de sémaphore binaire. Ils sont utilisés dans différents contextes et objectifs. Un sémaphore binaire sert à synchroniser les tâches tandis que Mutex est utilisé pour protéger une ressource partagée.
Cette API ne prend aucun argument et renvoie une variable de type SemaphoreHandle_t . Si le mutex ne peut pas être créé, xSemaphoreCreateMutex () renvoie NULL.
SemaphoreHandle_t mutex_v; mutex_v = xSemaphoreCreateMutex ();
Prendre un Mutex:
Lorsqu'une tâche souhaite accéder à une ressource, elle prend un Mutex en utilisant l' API xSemaphoreTake () . C'est la même chose qu'un sémaphore binaire. Cela prend également deux paramètres.
xSemaphore: Nom du Mutex à prendre dans notre cas mutex_v .
xTicksToWait: Il s'agit de la durée maximale pendant laquelle la tâche attendra dans l'état Bloqué pour que le Mutex devienne disponible. Dans notre projet, nous définirons xTicksToWait sur portMAX_DELAY pour que la tâche_1 attende indéfiniment dans l'état Bloqué jusqu'à ce que mutex_v soit disponible.
Donner un Mutex:
Après avoir accédé à la ressource partagée, la tâche doit renvoyer le Mutex afin que d'autres tâches puissent y accéder. L' API xSemaphoreGive () est utilisée pour rendre le Mutex.
La fonction xSemaphoreGive () ne prend qu'un seul argument qui est le Mutex à donner dans notre cas mutex_v.
En utilisant les API ci-dessus, implémentons Mutex dans le code FreeRTOS à l'aide de l'IDE Arduino.
Explication du code Mutex
Ici, le but de cette partie est d'utiliser un moniteur série comme ressource partagée et deux tâches différentes pour accéder au moniteur série afin d'imprimer un message.
1. Les fichiers d'en-tête resteront les mêmes qu'un sémaphore.
#include #include
2. Déclarez une variable de type SemaphoreHandle_t pour stocker les valeurs de Mutex.
SemaphoreHandle_t mutex_v;
3. Dans void setup (), initialisez le moniteur série avec un débit de 9600 bauds et créez deux tâches (Task1 et Task2) à l'aide de l' API xTaskCreate () . Créez ensuite un Mutex en utilisant xSemaphoreCreateMutex (). Créez une tâche avec des priorités égales et essayez plus tard de jouer avec ce numéro.
void setup () { Serial.begin (9600); mutex_v = xSemaphoreCreateMutex (); if (mutex_v == NULL) { Serial.println ("Le mutex ne peut pas être créé"); } xTaskCreate (Tâche1, "Tâche 1", 128, NULL, 1, NULL); xTaskCreate (Tâche2, "Tâche 2", 128, NULL, 1, NULL); }
4. Maintenant, créez des fonctions de tâche pour Task1 et Task2. Dans un tout boucle de fonction de tâche, avant d' imprimer un message sur le moniteur de série, nous devons prendre un Mutex en utilisant xSemaphoreTake () puis imprimez le message, puis retourner le Mutex en utilisant xSemaphoreGive (). Alors donnez un peu de retard.
void Task1 (void * pvParameters) { while (1) { xSemaphoreTake (mutex_v, portMAX_DELAY); Serial.println ("Salut de Task1"); xSemaphoreGive (mutex_v); vTaskDelay (pdMS_TO_TICKS (1000)); } }
De même, implémentez la fonction Task2 avec un retard de 500 ms.
5. Void loop () restera vide.
Maintenant, téléchargez ce code sur Arduino UNO et ouvrez le moniteur série.
Vous verrez que les messages s'impriment à partir de la tâche1 et de la tâche2.
Pour tester le fonctionnement de Mutex, il suffit de commenter xSemaphoreGive (mutex_v); de n'importe quelle tâche. Vous pouvez voir que le programme se bloque sur le dernier message d'impression .
C'est ainsi que Semaphore et Mutex peuvent être implémentés dans FreeRTOS avec Arduino. Pour plus d'informations sur Semaphore et Mutex, vous pouvez visiter la documentation officielle de FreeRTOS.
Les codes complets et la vidéo pour Semaphore et Mutes sont donnés ci-dessous.