ESP32 avec FreeRTOS : Démarrage des sémaphores (Arduino)

ESP32 avec FreeRTOS : Démarrage des sémaphores (Arduino)

Dans ce guide, nous allons vous présenter et vous expliquer comment utiliser les sémaphores FreeRTOS avec l’ESP32, à l’aide de l’IDE Arduino. Les sémaphores sont comme des signaux (ou drapeaux) qui permettent de synchroniser des tâches et de gérer des événements. Ils peuvent être utilisés pour indiquer qu’un événement s’est produit ou qu’une ressource est disponible. Contrairement aux files d’attente, les sémaphores ne transportent pas de données.

Guide de démarrage de l'IDE Arduino pour sémaphores FreeRTOS ESP32

Il existe deux types de sémaphores : les sémaphores binaires et les sémaphores de comptage. Dans ce didacticiel, nous allons créer et explorer deux exemples différents pour vous montrer comment fonctionnent ces deux types de sémaphores.

Vous êtes nouveau sur FreeRTOS ? Commencez par ce didacticiel : ESP32 avec FreeRTOS (Arduino IDE) – Guide de démarrage : Création de tâches.

Présentation des sémaphores

Les sémaphores sont des outils de signalisation dans FreeRTOS utilisés pour coordonner les tâches. Ils sont couramment utilisés pour indiquer qu’un événement s’est produit ou qu’une ressource est disponible. Contrairement aux files d’attente, les sémaphores ne transportent pas de données, seulement un « compte », ou un « drapeau », ou un « signal » (ou peu importe comment vous voulez l’appeler) utilisé pour déclencher des actions lorsque quelque chose se produit.

Exemple simple du fonctionnement d'un sémaphore binaire
Exemple simple du fonctionnement d’un sémaphore binaire

Ils permettent aux tâches d’attendre des événements, tels qu’une pression sur un bouton ou une détection de mouvement, ou de signaler qu’un événement s’est produit. Cela les rend particulièrement utiles dans les scénarios impliquant des interruptions et la synchronisation des tâches.

Étant donné que les sémaphores ne stockent pas de données, ils consomment moins de mémoire que les files d’attente et sont idéaux pour une signalisation légère d’événements entre les tâches.

Il existe deux types de sémaphores :

  • Sémaphore binaire : signale un seul événement. C’est un outil de synchronisation qui peut être vide (0) ou plein (1). C’est comme un signal qu’une tâche attend avant de pouvoir se poursuivre.
  • Sémaphore de comptage : suit plusieurs événements (il peut s’agir du même événement plusieurs fois). C’est comme une file d’attente d’événements jusqu’à un nombre maximum que vous définissez. Contrairement aux files d’attente FreeRTOS, celles-ci ne transportent pas de données. Seulement un signal de synchronisation. Vous comprendrez mieux comment cela fonctionne plus loin dans l’exemple.

Fonctions de base des sémaphores

Voici quelques fonctions de base des sémaphores binaires et de comptage lors de l’utilisation de l’ESP32 avec l’IDE Arduino. Nous explorerons ensuite ces fonctions dans des exemples pratiques.

Création d’un sémaphore binaire

Pour créer un sémaphore binaire, utilisez la fonction xSemaphoreCreateBinary(). Il renvoie un handle SemaphoreHandle_t en cas de succès, ou NULL si la création échoue.

Création d’un sémaphore de comptage

Pour créer un sémaphore de comptage, utilisez la fonction xSemaphoreCreateCounting(). Il renvoie un handle SemaphoreHandle_t en cas de succès, ou NULL si la création échoue. Passez en argument le nombre maximum.

Prendre un sémaphore (obtenir du sémaphore)

Utilisez la fonction xSemaphoreTake(semaphore, timeout) dans une tâche pour attendre ou prendre un sémaphore. Pour un sémaphore binaire, il bloque jusqu’à ce que le sémaphore soit disponible (état 1), le mettant à 0 lorsqu’il est pris.

Pour un sémaphore de comptage, il décrémente le compte s’il est supérieur à 0, ou se bloque si le compte est 0. Le paramètre timeout spécifie la durée d’attente (en ticks) ; portMAX_DELAY signifie attendre indéfiniment. Cela signifie que la tâche sera bloquée jusqu’à ce qu’il y ait une valeur de sémaphore à prendre.

Donner un sémaphore

Pour donner un sémaphore, utilisez la fonction xSemaphoreGive() si elle est à l’intérieur d’une tâche, ou xSemaphoreGiveFromISR() si elle est utilisée dans les ISR (fonctions de routine de service d’interruption).

Pour un sémaphore binaire, il met l’état à 1, débloquant une tâche en attente (ou ignorée si le sémaphore est déjà à 1). Pour un sémaphore de comptage, il incrémente le compte jusqu’à maxCount, débloquant une tâche en attente (ignorée si à maxCount).

Exemple 1 : Sémaphore binaire – Basculer une LED en appuyant sur un bouton

Dans cette section, nous allons construire un exemple simple pour démontrer comment fonctionne un sémaphore binaire et comment l’implémenter dans une application pratique. Cet exemple fera basculer une LED une fois lorsque le bouton-poussoir est enfoncé. Pour signaler la pression sur le bouton, nous utiliserons un sémaphore. Simultanément, nous aurons une autre tâche faisant clignoter une LED pour démontrer que nous pouvons exécuter plusieurs tâches simultanément.

ESP32 avec un bouton-poussoir et deux LED - exemple pour démontrer des sémaphores binaires

Voici donc un aperçu de l’exemple :

  • Nous allons ajouter une interruption à un bouton-poussoir. En appuyant sur le bouton poussoir, l’ISR correspondant donnera le sémaphore (le mettra à 1).
  • Il existe une autre tâche, appelée LEDToggleTask(), qui attendra que le sémaphore change l’état de la LED. Lorsque le sémaphore est donné par l’ISR, cette tâche s’exécutera et le sémaphore sera réinitialisé à 0. Ce n’est que lorsque le sémaphore sera mis à 1, lorsque le bouton sera enfoncé, que cette tâche sera réexécutée.
  • Simultanément, nous avons une autre tâche appelée LEDBlinkTask() qui va augmenter et diminuer la luminosité d’une autre LED.

Pièces requises

Voici une liste des pièces requises pour cet exemple :

Vous pouvez utiliser les liens précédents ou vous rendre directement sur MakerAdvisor.com/tools pour trouver toutes les pièces pour vos projets au meilleur prix !

1760036408 698 ESP32 avec FreeRTOS Demarrage des semaphores Arduino

Câblage du circuit

Câblez le circuit suivant :

  • LED rouge connectée au GPIO 2
  • LED bleue connectée au GPIO 4
  • Bouton poussoir connecté au GPIO 23

Vous pouvez suivre le diagramme schématique suivant.

ESP32 avec deux LED et un schéma de principe d'un bouton-poussoir

Code

Téléchargez le code suivant sur l’IDE Arduino.

/*
  Rui Santos & Sara Santos - Raspberryme.com
  Complete project details at https://Raspberryme.com/esp32-freertos-semaphores-arduino/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define BUTTON_PIN 23
#define LED1_PIN 2   // Toggled LED
#define LED2_PIN 4   // Blinking LED

#define DEBOUNCE_DELAY 200

SemaphoreHandle_t buttonSemaphore = NULL;

volatile uint32_t lastInterruptTime = 0;

void IRAM_ATTR buttonISR() {
  uint32_t currentTime = millis();
  if (currentTime - lastInterruptTime > DEBOUNCE_DELAY) {
    BaseType_t higherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(buttonSemaphore, &higherPriorityTaskWoken);
    lastInterruptTime = currentTime;
    if (higherPriorityTaskWoken) {
      portYIELD_FROM_ISR();
    }
  }
}

void LEDToggleTask(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  bool ledState = false;
  for (;;) {
    if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {
      ledState = !ledState;
      digitalWrite(LED1_PIN, ledState ? HIGH : LOW);
      Serial.print("LEDToggleTask: LED1 ");
      Serial.println(ledState ? "ON" : "OFF");
    }
  }
}

void LEDBlinkTask(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED2_PIN, HIGH);
    Serial.println("LEDBlinkTask: LED2 ON");
    vTaskDelay(250 / portTICK_PERIOD_MS);
    digitalWrite(LED2_PIN, LOW);
    Serial.println("LEDBlinkTask: LED2 OFF");
    vTaskDelay(250 / portTICK_PERIOD_MS);
  }
}

void setup() {
  Serial.begin(115200);

  // Defining the button as an interrupt
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);

  buttonSemaphore = xSemaphoreCreateBinary();
  if (buttonSemaphore == NULL) {
    Serial.println("Failed to create semaphore!");
    while (1);
  }

  xTaskCreatePinnedToCore(
    LEDToggleTask,          // Task function
    "LEDToggleTask",        // Task name
    3000,                   // Stack size
    NULL,                   // Task parameters
    2,                      // Higher priority
    NULL,                   // Task handle
    1                       // Core ID
  );

  xTaskCreatePinnedToCore(
    LEDBlinkTask,           // Task function
    "LEDBlinkTask",         // Task name
    3000,                   // Stack size
    NULL,                   // Task parameters
    1,                      // Medium priority
    NULL,                   // Task handle
    1                       // Core ID
  );
}

void loop() {
  
}

Afficher le code brut

Comment fonctionne le code ?

Commencez par définir les broches du bouton-poussoir, de la LED basculée et de la LED clignotante.

#define BUTTON_PIN 23
#define LED1_PIN 2    // Toggled LED
#define LED2_PIN 4    // Blinking LED

Définissez le délai anti-rebond du bouton-poussoir en millisecondes.

#define DEBOUNCE_DELAY 200

Créez un handle pour le sémaphore appelé boutonSemaphore.

SemaphoreHandle_t buttonSemaphore = NULL;

installation()

Expliquons d’abord setup(), puis analysons les tâches.

Tout d’abord, définissez le bouton-poussoir comme interruption et définissez sa fonction de rappel (ISR). Dans ce cas, cela s’appelle boutonISR.

pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);

Nous créons un sémaphore binaire en utilisant la fonction xSemaphoreCreateBinary() sur le handle ButtonSemaphore que nous avons créé précédemment.

buttonSemaphore = xSemaphoreCreateBinary();
if (buttonSemaphore == NULL) {
  Serial.println("Failed to create semaphore!");
  while (1);
}

Ensuite, nous créons la LedToggleTask et la LEDBlinkTask avec des priorités différentes.

xTaskCreatePinnedToCore(
  LEDToggleTask,          // Task function
  "LEDToggleTask",        // Task name
  3000,                   // Stack size
  NULL,                   // Task parameters
  2,                      // Higher priority
  NULL,                   // Task handle
  1                       // Core ID
);

xTaskCreatePinnedToCore(
  LEDBlinkTask,           // Task function
  "LEDBlinkTask",         // Task name
  3000,                   // Stack size
  NULL,                   // Task parameters
  1,                      // Medium priority
  NULL,                   // Task handle
  1                       // Core ID
);

boutonISR()

Les lignes suivantes créent la fonction buttonISR().

void IRAM_ATTR buttonISR() {
  uint32_t currentTime = millis();
  if (currentTime - lastInterruptTime > DEBOUNCE_DELAY) {
    BaseType_t higherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(buttonSemaphore, &higherPriorityTaskWoken);
    lastInterruptTime = currentTime;
    if (higherPriorityTaskWoken) {
      portYIELD_FROM_ISR();
    }
  }
}

Lorsque le bouton est enfoncé, la fonction buttonISR() s’exécutera. Si nous avons une pression valide, il utilise les fonctions de sémaphore FreeRTOS pour signaler à une autre tâche que l’événement du bouton s’est produit.

Donner le sémaphore

La ligne suivante est l’action clé du sémaphore. Il « donne » le sémaphore. Ce sémaphore est comme un signal qui indique à une autre partie du programme (généralement une tâche FreeRTOS – dans notre cas, il s’agit de la LEDToggleTask) que le bouton a été enfoncé.

xSemaphoreGiveFromISR(buttonSemaphore, &higherPriorityTaskWoken);
upperPriorityTaskWoken et portYIELD_FROM_ISR

La variable upperPriorityTaskWoken est utilisée pour vérifier si le fait de donner le sémaphore réveillera une tâche qui a une priorité plus élevée que la tâche en cours d’exécution. Si c’est le cas, nous appelons portYIELD_FROM_ISR() pour permettre au système de passer immédiatement à cette tâche de priorité plus élevée juste après la fin de l’interruption. Dans notre cas, nous souhaitons passer immédiatement à la LEDToggleTask.

Autrement dit:

  • Fondamentalement, upperPriorityTaskWoken est utilisé pour vérifier si donner au sémaphore débloqué une tâche plus importante.
  • Nous le transmettons à xSemaphoreGiveFromISR() afin qu’il puisse mettre à jour la valeur.
  • Si c’est pdTRUE, nous appelons portYIELD_FROM_ISR() pour permettre à FreeRTOS de passer immédiatement à cette tâche.

C’est ainsi que FreeRTOS permet aux interruptions de déclencher en toute sécurité des tâches hautement prioritaires sans causer de problèmes de planification des tâches.

LEDToggleTâche

LEDToggleTask() basculera l’état de LED1 lorsqu’il y aura une valeur sur le sémaphore.

void LEDToggleTask(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  bool ledState = false;
  for (;;) {
    if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {
      ledState = !ledState;
      digitalWrite(LED1_PIN, ledState ? HIGH : LOW);
      Serial.print("LEDToggleTask: LED1 ");
      Serial.println(ledState ? "ON" : "OFF");
    }
  }
}

Lorsque la tâche LEDToggleTask() s’exécute, elle configure la broche LED comme sortie et démarre une boucle infinie. A l’intérieur de la boucle, il attend le sémaphore en utilisant xSemaphoreTake(buttonSemaphore, portMAX_DELAY). Le portMAX_DELAY signifie que la tâche attendra indéfiniment jusqu’à ce qu’il y ait une valeur sur le sémaphore (jusqu’à ce que le bouton soit enfoncé).

if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {

Lorsque le sémaphore est reçu, la tâche basculera l’état de la LED et l’imprimera sur le moniteur série.

if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {
  ledState = !ledState;
  digitalWrite(LED1_PIN, ledState ? HIGH : LOW);
  Serial.print("LEDToggleTask: LED1 ");
  Serial.println(ledState ? "ON" : "OFF");

LEDBlinkTâche

Outre l’autre tâche, nous avons la LEDBlinkTask qui s’exécute indépendamment et simultanément, faisant clignoter une LED indéfiniment.

void LEDBlinkTask(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED2_PIN, HIGH);
    Serial.println("LEDBlinkTask: LED2 ON");
    vTaskDelay(250 / portTICK_PERIOD_MS);
    digitalWrite(LED2_PIN, LOW);
    Serial.println("LEDBlinkTask: LED2 OFF");
    vTaskDelay(250 / portTICK_PERIOD_MS);
  }
}

Démonstration

Téléchargez le code sur votre tableau. Après le téléchargement, ouvrez le moniteur série à une vitesse de transmission de 115 200 bauds. Appuyez sur le bouton ESP32 RST pour qu’il commence à exécuter le code.

ESP32 avec deux LED et un bouton-poussoir - bouton-poussoir enfoncé pour basculer l'état d'une LED

La LED connectée au GPIO 4 clignotera toutes les 250 millisecondes. Appuyez sur le bouton poussoir pour basculer l’état de la LED connectée au GPIO 2. Vous pouvez voir une petite démonstration dans la courte vidéo ci-dessous.

Dans le moniteur série, vous devriez obtenir quelque chose de similaire.

Exemple de sémaphore binaire ESP32 - Démonstration du moniteur série

Exemple 2 : compter le sémaphore

Dans cette section, nous allons créer un exemple simple pour démontrer le fonctionnement du comptage des sémaphores. Nous allons créer un sémaphore de comptage avec un nombre maximum de 5. Ce sémaphore prendra jusqu’à 5 pressions sur des boutons. Il existe une autre tâche qui consommera ce sémaphore pour faire clignoter une LED autant de fois que les valeurs du sémaphore. Lorsqu’une valeur est consommée depuis le sémaphore, une autre valeur peut être ajoutée.

En résumé, voici un aperçu du fonctionnement du projet :

  • Nous allons attacher une interruption à un bouton-poussoir. Lorsque le bouton-poussoir est enfoncé, la routine de service d’interruption (ISR) donnera un sémaphore, jusqu’à un nombre maximum de 5.
  • La LEDBlinkTask attendra le sémaphore. Chaque fois qu’il en reçoit un, la LED clignote. La LED clignotera une fois pour chaque compte actuellement disponible dans le sémaphore.
  • Lorsque la LEDBlinkTask consomme une valeur du sémaphore, il y a un « espace » pour un nouveau compte ajouté en appuyant sur un bouton-poussoir.
  • Simultanément, nous aurons une autre tâche appelée LEDFadeTask qui fera disparaître une autre LED. Cette tâche est utilisée pour démontrer la puissance de FreeRTOS pour gérer le multitâche.

Pièces requises et schéma de câblage

Identique à l’exemple précédent.

Code

Copiez le code suivant dans l’IDE Arduino.

/*
  Rui Santos & Sara Santos - Raspberryme.com
  Complete project details at https://Raspberryme.com/esp32-freertos-semaphores-arduino/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define BUTTON_PIN 23
#define LED1_PIN 2    // Blinking LED
#define LED2_PIN 4    // Fading LED

#define DEBOUNCE_DELAY 200 // debounce for the pushbutton in milliseconds

#define SEMAPHORE_MAX_COUNT 5

SemaphoreHandle_t buttonSemaphore = NULL;
volatile uint32_t lastInterruptTime = 0;

void IRAM_ATTR buttonISR() {
  uint32_t currentTime = millis();
  if (currentTime - lastInterruptTime > DEBOUNCE_DELAY) {
    BaseType_t higherPriorityTaskWoken = pdFALSE;
    if (xSemaphoreGiveFromISR(buttonSemaphore, &higherPriorityTaskWoken) == pdTRUE) {
      Serial.println("buttonISR: Gave semaphore token");
    } else {
      Serial.println("buttonISR: Semaphore full");
    }
    lastInterruptTime = currentTime;
    if (higherPriorityTaskWoken) {
      portYIELD_FROM_ISR();
    }
  }
}

void LEDBlinkTask(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  for (;;) {
    
    // Get and print the current semaphore count
    UBaseType_t count = uxSemaphoreGetCount(buttonSemaphore);
    Serial.print("LEDBlinkTask: Current semaphore count = ");
    Serial.println(count);

    if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {
      Serial.println("LEDBlinkTask: Blinking LED1 for button press");
      vTaskDelay(500 / portTICK_PERIOD_MS);
      digitalWrite(LED1_PIN, HIGH);
      vTaskDelay(1000 / portTICK_PERIOD_MS);
      digitalWrite(LED1_PIN, LOW);
      vTaskDelay(500 / portTICK_PERIOD_MS);
    }
  }
}

void LEDFadeTask(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    // Fade up (0 to 255)
    for (int duty = 0; duty <= 255; duty += 5) {
      analogWrite(LED2_PIN, duty);
      if (duty % 50 == 0) {  // Print every 10th step
        Serial.print("LEDFadeTask: Fading LED2, duty=");
        Serial.println(duty);
      }
      vTaskDelay(50 / portTICK_PERIOD_MS);
    }
    // Fade down (255 to 0)
    for (int duty = 255; duty >= 0; duty -= 5) {
      analogWrite(LED2_PIN, duty);
      if (duty % 50 == 0 || duty == 255) {
        Serial.print("LEDFadeTask: Fading LED2, duty=");
        Serial.println(duty);
      }
      vTaskDelay(50 / portTICK_PERIOD_MS);
    }
  }
}

void setup() {
  Serial.begin(115200);  // Higher baud rate
  delay(1000);
  Serial.println("Starting FreeRTOS: Counting Semaphore");

  pinMode(BUTTON_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);

  buttonSemaphore = xSemaphoreCreateCounting(SEMAPHORE_MAX_COUNT, 0);
  if (buttonSemaphore == NULL) {
    Serial.println("Failed to create semaphore!");
    while (1);
  }

  xTaskCreatePinnedToCore(
    LEDBlinkTask,           // Task function
    "LEDBlinkTask",         // Task name
    3000,                   // Stack size
    NULL,                   // Task parameters
    2,                      // Higher priority
    NULL,                   // Task handle
    1                       // Core ID
  );

  xTaskCreatePinnedToCore(
    LEDFadeTask,            // Task function
    "LEDFadeTask",          // Task name
    3000,                   // Stack size
    NULL,                   // Task parameters
    1,                      // Lower priority
    NULL,                   // Task handle
    1                       // Core ID
  );
}
void loop() {}

Afficher le code brut

Comment fonctionne le code ?

Ce code est assez similaire au précédent. Nous allons juste jeter un œil aux sections importantes liées au sémaphore de comptage.

Création du sémaphore de comptage

Dans setup(), nous créons un sémaphore de comptage avec un nombre maximum de 5 (SEMAPHORE_MAX_COUNT) commençant à 0. Nous faisons cela en utilisant la fonction xSemaphoreCreateCounting().

buttonSemaphore = xSemaphoreCreateCounting(SEMAPHORE_MAX_COUNT, 0);
if (buttonSemaphore == NULL) {
  Serial.println("Failed to create semaphore!");
  while (1);
}

Création de tâches

Toujours dans le setup(), nous créons nos tâches et les assignons à un core. La LEDBlinkTask a une priorité plus élevée que la LEDFadeTask.

xTaskCreatePinnedToCore(
  LEDBlinkTask,           // Task function
  "LEDBlinkTask",         // Task name
  3000,                   // Stack size
  NULL,                   // Task parameters
  2,                      // Higher priority
  NULL,                   // Task handle
  1                       // Core ID
);

xTaskCreatePinnedToCore(
  LEDFadeTask,            // Task function
  "LEDFadeTask",          // Task name
  3000,                   // Stack size
  NULL,                   // Task parameters
  1,                      // Lower priority
  NULL,                   // Task handle
  1                       // Core ID
);

Bouton ISR et sémaphore de comptage

Lorsque vous appuyez sur le bouton-poussoir, la fonction buttonISR() s’exécutera. Si nous avons une pression valide sur un bouton, nous la donnons au sémaphore de comptage. Le sémaphore prendra jusqu’à cinq comptes. Nous utilisons la même fonction que celle utilisée dans l’exemple précédent xSemaphoreGiveFromISR().

if (xSemaphoreGiveFromISR(buttonSemaphore, &higherPriorityTaskWoken) == pdTRUE) {
  Serial.println("buttonISR: Gave semaphore token");
} else {
  Serial.println("buttonISR: Semaphore full");
}

LEDBlinkTask et prise du sémaphore

La LEDBlinkTask attend indéfiniment jusqu’à ce que nous ayons un décompte sur le sémaphore. Quand il y a un décompte sur le sémaphore, on le prend et on fait clignoter la LED.

if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {
  Serial.println("LEDBlinkTask: Blinking LED1 for button press");
  vTaskDelay(500 / portTICK_PERIOD_MS);
  digitalWrite(LED1_PIN, HIGH);
  vTaskDelay(1000 / portTICK_PERIOD_MS);
  digitalWrite(LED1_PIN, LOW);
  vTaskDelay(500 / portTICK_PERIOD_MS);
}

Puisque nous avons un sémaphore de comptage, la LED clignotera autant de fois que les comptes actuellement disponibles sur le sémaphore.

Chaque fois qu’il extrait du sémaphore, un nouvel espace est disponible pour ajouter un nouveau compte (via une pression sur un bouton-poussoir).

Dans cette tâche, nous imprimons également le nombre de sémaphores actuel en appelant la fonction uxSemaphoreGetCount() et en passant le handle du sémaphore comme argument.

// Get and print the current semaphore count
UBaseType_t count = uxSemaphoreGetCount(buttonSemaphore);
Serial.print("LEDBlinkTask: Current semaphore count = ");
Serial.println(count);

Tâche de fondu LED

Simultanément, nous avons une autre tâche indépendante appelée LEDFadeTask qui atténue simplement l’autre LED.

void LEDFadeTask(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    // Fade up (0 to 255)
    for (int duty = 0; duty <= 255; duty += 5) {
      analogWrite(LED2_PIN, duty);
      if (duty % 50 == 0) {  // Print every 10th step
        Serial.print("LEDFadeTask: Fading LED2, duty=");
        Serial.println(duty);
      }
      vTaskDelay(50 / portTICK_PERIOD_MS);
    }
    // Fade down (255 to 0)
    for (int duty = 255; duty >= 0; duty -= 5) {
      analogWrite(LED2_PIN, duty);
      if (duty % 50 == 0 || duty == 255) {
        Serial.print("LEDFadeTask: Fading LED2, duty=");
        Serial.println(duty);
      }
      vTaskDelay(50 / portTICK_PERIOD_MS);
    }
  }
}

Démonstration

Téléchargez le code sur votre tableau. Après le téléchargement, ouvrez le moniteur série à une vitesse de transmission de 115 200 bauds. Appuyez sur le bouton ESP32 RST pour qu’il commence à exécuter le code.

ESP32 avec deux LED et un bouton-poussoir - bouton-poussoir enfoncé pour basculer l'état d'une LED

La LED connectée au GPIO 4 s’estompera constamment.

Appuyez plusieurs fois sur le bouton poussoir pour faire clignoter la LED connectée au GPIO 2 autant de fois que d’appuis sur le bouton de la file d’attente du sémaphore.

Vous pouvez voir une petite démonstration dans la courte vidéo ci-dessous.

Dans le moniteur série, vous devriez obtenir quelque chose de similaire. Le comptage du sémaphore diminuera au fur et à mesure que la LED clignote (si vous ne continuez pas à appuyer sur le bouton poussoir).

Sémaphore de comptage ESP32 - Démonstration d'un exemple de moniteur série

Conclusion

Dans ce guide, vous avez découvert les sémaphores binaires et de comptage FreeRTOS et comment les implémenter avec l’ESP32 programmé avec l’IDE Arduino.

Les sémaphores nous permettent de synchroniser des tâches pour signaler quand une ressource est disponible, quand un événement s’est produit ou un point d’une tâche où l’autre doit s’exécuter.

Nous vous avons montré deux exemples simples pour démontrer le fonctionnement des sémaphores. Cela peut être appliqué à des applications beaucoup plus complexes avec plusieurs tâches donnant et prenant du sémaphore.

Nous espérons que vous avez trouvé ce didacticiel utile pour commencer à implémenter la programmation FreeRTOS sur vos croquis ESP32. Nous avons d’autres tutoriels sur cette série FreeRTOS qui pourraient vous plaire :

Pour en savoir plus sur l’ESP32, assurez-vous de consulter nos ressources :

Cette vidéo vous emmène dans l’histoire de Raspberry Pi :

YouTube video

  • ELEGOO Carte Starter Kit de Démarrage ESP-32 avec Tutoriel et Carte de Développement Microcontrôleur Double Cœur USB-C Prise en Charge AP/STA/AP+STA, Compatible avec Arduino IDE
  • Kit de démarrage Basique pour ESP32, kit de démarrage électronique Compatible avec Arduino, kit de démarrage Basique ESP32 ESP-32S, pour débutants et passionnés d'électronique