ESP32 avec Freertos (Arduino IDE) – Créer des tâches

ESP32 avec Freertos (Arduino IDE) - Créer des tâches

Dans ce tutoriel, nous présenterons les concepts de base de Freertos et vous montrerons comment l’utiliser avec l’ESP32 et l’ide Arduino. Vous apprendrez à créer des tâches uniques et multiples, à suspendre et à reprendre les tâches, à exécuter du code sur les deux cœurs ESP32 et à calculer la taille de pile appropriée nécessaire (mémoire) pour chaque tâche.

Freertos est un système d’exploitation en temps réel qui permet à l’ESP32 de gérer et d’exécuter plusieurs tâches simultanément de manière fluide et efficace. Il est intégré à l’ESP32 et entièrement intégré à la fois au Core Arduino et au Framework de développement ESPRESSIF IoT (IDF).

ESP32 avec Freertos - Guide de démarrage Créer des tâches

Dans ce tutoriel, nous couvrirons les sujets suivants:

Condition préalable

Ce tutoriel se concentre sur la programmation de l’ESP32 à l’aide du noyau Arduino. Avant de continuer, vous devriez avoir le noyau Arduino ESP32 installé dans votre IDE Arduino. Suivez le prochain tutoriel pour installer l’ESP32 sur l’ide Arduino, si vous ne l’avez pas déjà fait.


Qu’est-ce que Freertos?

Freertos est un système d’exploitation en temps réel léger et open-source (RTOS). Il fournit un cadre pour exécuter plusieurs tâches simultanément, chacune avec sa propre priorité et son programme d’exécution. Au lieu d’exécuter du code ligne par ligne, Freertos vous permet de créer des tâches indépendantes que l’ESP32 peut basculer entre rapidement en fonction de la priorité de la tâche.

Logo Freertos

Il fournit également des outils tels que les files d’attente et les sémaphores afin que les tâches puissent communiquer de manière transparente entre elles.

Cela rend votre code plus organisé et réactif, surtout lorsque votre ESP32 gère plusieurs tâches en même temps, comme les capteurs de lecture, la gestion des demandes HTTP et l’affichage d’informations sur un écran, tout en écoutant les interruptions.

Pourquoi Freertos est-il utile avec l’ESP32?

Le système d’exploitation en temps réel de Freertos est intégré à l’ESP32 et intégré dans l’IDF ESPRESSIF et le noyau Arduino. Il prend en charge:

  • Gestion des tâches: créer, suspendre, reprendre ou supprimer les tâches (couvertes dans ce tutoriel).
  • Planification: vous permet de donner la priorité à vos tâches, afin qu’ils fonctionnent dans un ordre spécifique (couvert dans ce tutoriel).
  • Communication inter-tâches: en utilisant des choses comme les files d’attente, les sémaphores et les mutex, nous pouvons assurer une communication transparente entre les tâches sans écraser l’ESP32.
  • Support double-core: il vous permet d’exécuter vos tâches sur Core 0 ou Core 1 de l’ESP32 (également couverte dans ce tutoriel, mais pour un guide plus approfondi sur l’utilisation du double-core avec l’ESP32, vérifiez ce guide: comment utiliser ESP32 Dual Core avec Arduino IDE).

Donc, en résumé…

Freertos est utile avec l’ESP32 car il permet un multitâche, permettant à plusieurs tâches comme la lecture du capteur, le Wi-Fi et afficher des mises à jour de s’exécuter sans se bloquer.

Il vous permet également de profiter du processeur Dual-Core ESP32 en vous permettant d’attribuer des tâches à des cœurs spécifiques pour de meilleures performances.

De plus, avec la planification basée sur la priorité, les tâches critiques peuvent fonctionner immédiatement, ce qui le rend idéal pour les applications en temps réel lorsque vous devez réagir rapidement aux événements externes détectés par les GPIOS ESP32.

Concepts de base de Freertos

Avant de plonger dans les exemples pratiques, couvrons quelques concepts de base liés à Freertos:

  • Tâches: les tâches sont des fonctions indépendantes fonctionnant simultanément, chacune avec sa propre pile (utilisation de la mémoire allouée) et la priorité. Les tâches peuvent être dans des États comme la course, prête, bloquée ou suspendue.
  • Scheduler: Le planificateur décide quelles tâches à exécuter en fonction de leurs priorités. Il s’agit d’un planificateur préventif, ce qui signifie qu’il peut interrompre une tâche de priorité inférieure à tout moment pour exécuter une tâche plus élevée, garantissant que les tâches critiques sont exécutées dès qu’elles sont prêtes.
  • Priorités: des nombres plus élevés indiquent une priorité plus élevée (par exemple: 1 = faible, 5 = haut).

1) Création de tâches

Ceci est l’exemple le plus simple dans lequel nous vous montrerons comment créer une tâche Freertos pour clignoter une LED à chaque seconde. Nous connecterons la LED à GPIO 2. Au lieu de cela, vous pouvez ignorer la LED et vérifier les résultats sur la LED intégrée ESP32.

Pièces requises

Pour cet exemple, vous avez besoin des parties suivantes:

Vous pouvez utiliser les liens précédents ou aller directement à makeradvisor.com/tools pour trouver toutes les pièces de vos projets au meilleur prix!

1755279066 397 ESP32 avec Freertos Arduino IDE Creer des taches

Câblage du circuit

Câblez une conduite à GPIO 2 comme indiqué dans le diagramme schématique suivant.

ESP32 avec une LED connectée à GPIO2 via une résistance de 220Ohm

ESP32: Création de tâches Freertos – Code Arduino

Le code suivant crée une tâche qui clignote une LED connectée à GPIO 2 à chaque seconde.

/*
  Rui Santos & Sara Santos - Raspberryme.com
  Complete project details at https://Raspberryme.com/esp32-freertos-arduino-tasks/
  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 LED_PIN 2

// Declare task handle
TaskHandle_t BlinkTaskHandle = NULL;

void BlinkTask(void *parameter) {
  for (;;) { // Infinite loop
    digitalWrite(LED_PIN, HIGH);
    Serial.println("BlinkTask: LED ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS); // 1000ms
    digitalWrite(LED_PIN, LOW);
    Serial.println("BlinkTask: LED OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    Serial.print("BlinkTask running on core ");
    Serial.println(xPortGetCoreID());
  }
}

void setup() {
  Serial.begin(115200);
  
  pinMode(LED_PIN, OUTPUT);

  xTaskCreatePinnedToCore(
    BlinkTask,         // Task function
    "BlinkTask",       // Task name
    10000,             // Stack size (bytes)
    NULL,              // Parameters
    1,                 // Priority
    &BlinkTaskHandle,  // Task handle
    1                  // Core 1
  );
}

void loop() {
  // Empty because FreeRTOS scheduler runs the task
}

Afficher le code brut

Comment fonctionne le code?

Nous commençons par définir le GPIO qui sera connecté à la LED.

#define LED_PIN 2

Poignée de tâche

Ensuite, déclarez la poignée de la tâche. Un taskhandle_t est une variable qui pointe vers une tâche Freertos, vous permettant de le contrôler, comme le reprendre, l’arrêter ou le supprimer. Dans cet exemple, nous n’aurons pas besoin de la poignée de la tâche, mais nous le créons néanmoins pour vous montrer comment cela se fait.

TaskHandle_t BlinkTaskHandle = NULL;

Fonction de tâche

Ensuite, nous créons une tâche. Une tâche n’est rien d’autre qu’une fonction qui exécute les commandes que vous souhaitez. Voici la fonction BlinkTask utilisée dans cet exemple.

void BlinkTask(void *parameter) {
  for (;;) { // Infinite loop
    digitalWrite(LED_PIN, HIGH);
    Serial.println("BlinkTask: LED ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS); // 1000ms
    digitalWrite(LED_PIN, LOW);
    Serial.println("BlinkTask: LED OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    Serial.print("BlinkTask running on core ");
    Serial.println(xPortGetCoreID());
  }
}

Cette fonction est une tâche Freertos, qui est un type spécial de fonction qui s’exécute indépendamment sous le planificateur de Freertos, permettant le multitâche sur l’ESP32.

Les tâches Freertos doivent retourner vide et doivent accepter un seul argument, qui peut être utilisé pour transmettre des données à la fonction (non utilisée dans notre cas).

void BlinkTask(void *parameter) {

Le for (;;) crée une boucle infinie pour s’assurer que la tâche s’exécute indéfiniment jusqu’à ce qu’elle soit explicitement arrêtée. Ceci est similaire à la fonction Loop () utilisée dans le code Arduino.

for (;;) { // Infinite loop

Ensuite, nous utilisons la fonction numériquewrite () pour activer et désactiver la LED. Notez qu’au lieu d’utiliser la fonction de retard () typique, nous utilisons VTaskDelay ().

digitalWrite(LED_PIN, HIGH);
Serial.println("BlinkTask: LED ON");
vTaskDelay(1000 / portTICK_PERIOD_MS); // 1000ms
digitalWrite(LED_PIN, LOW);
Serial.println("BlinkTask: LED OFF");
vTaskDelay(1000 / portTICK_PERIOD_MS);

vtaskdelay ()

VTaskDelay () est une fonction Freertos qui fait une tâche pour un nombre spécifié de tiques, permettant à d’autres tâches d’exécuter pendant cette période. Il ne bloque pas votre code comme delage (). La fonction vtaskdelay () accepte les tiques. Sur l’ESP32, chaque tick est généralement de 1 ms (défini par Porttick_period_ms), donc vtaskdelay (1000 / Porttick_period_ms) pause la tâche pour 1000 ms (1 seconde).

vTaskDelay(1000 / portTICK_PERIOD_MS);

Obtenez un IDE Core

À des fins de démonstration, nous imprimons également dans lequel le noyau est en cours d’exécution. Nous pouvons obtenir ces informations en appelant la fonction xportGetCoreId ().

Serial.print("BlinkTask running on core ");
Serial.println(xPortGetCoreID());

installation()

Dans la configuration (), nous initialisons le moniteur série et définissons la LED en tant que sortie.

void setup() {
  Serial.begin(115200);
  
  pinMode(LED_PIN, OUTPUT);

Créer une tâche

Maintenant, pour créer une tâche Freertos et l’attribuer à un noyau spécifique, nous devons utiliser la fonction XTaskCreatePinnedToCore (). Cette fonction spécifie également la fonction de tâche, le nom, la taille de la pile, les paramètres, la priorité et la poignée de tâche.

xTaskCreatePinnedToCore(
  BlinkTask,      // Task function
  "BlinkTask",   // Task name
  10000,           // Stack size (bytes)
  NULL,            // Parameters
  1,                   // Priority
  &BlinkTaskHandle,  // Task handle
  1                  // Core 1
);

La fonction de tâche est BlinkTask que nous avons définie plus tôt. Nous pouvons également donner un nom à la tâche. Dans ce cas, «blinktask».

BlinkTask,     // Task function
"BlinkTask",   // Task name

Nous définissons la taille de la pile de tâches sur 10000 octets. La taille de la pile des tâches est la quantité de mémoire allouée à la tâche pour stocker ses variables, ses appels de fonction et ses données temporaires pendant qu’il s’exécute, garantissant qu’il a suffisamment d’espace pour fonctionner sans écraser l’ESP32. Il est défini en octets. Dans un exemple ultérieur, nous verrons comment obtenir la taille de la pile d’une tâche.

10000,    // Stack size (bytes)

Dans ce cas, notre tâche n’a pas de paramètres, nous avons donc défini ce paramètre sur NULL.

NULL,     // Parameters

Nous donnons la priorité de la tâche 1. Plus le nombre est élevé, plus la priorité est élevée. Dans ce cas, cela n’a pas beaucoup d’importance parce que nous n’avons qu’une tâche.

1,    // Priority

Nous définissons également la poignée de la tâche pour la tâche que nous avons créée au début du code.

&BlinkTaskHandle,  // Task handle

Et enfin, nous définissons dans quel noyau nous voulons exécuter la tâche. L’ESP32 a deux cœurs, le Core 0 et le Core 1 désigné. Dans cet exemple, nous utilisons Core 1.

1   // Core 1

boucle()

Le Loop () est vide car le planificateur FreertOS exécutera la tâche. Cependant, il est possible d’ajouter du code au Loop () pour exécuter toutes les autres commandes que vous souhaitez.

void loop() {
  // Empty because FreeRTOS scheduler runs the task
}

Démonstration

Téléchargez le code sur votre carte ESP32. Après le téléchargement, ouvrez le moniteur en série à un taux en bauds de 115200. Vous devriez obtenir un résultat similaire.

ESP32 exécutant une seule tâche à l'aide de Freertos - Démonstration du moniteur en série

En même temps, la LED doit clignoter chaque seconde.


2) suspendre et reprendre les tâches

Dans cet exemple, vous apprendrez à suspendre et à reprendre une tâche Freertos sur l’ESP32. Nous allons créer une tâche qui clignote une LED et utiliserons un bouton-poussoir pour le contrôler. Lorsque le bouton est enfoncé, la tâche sera suspendue pour arrêter le clignotement, et l’appuyer à nouveau reprendra la tâche.

Pièces requises

Pour cet exemple, vous avez besoin des parties suivantes:

Câblage du circuit

Ajoutez un bouton-poussoir à votre circuit précédent. Nous connectons le bouton-poussoir à GPIO 23, mais vous pouvez utiliser tout autre GPIO approprié.

ESP32 connecté à une LED sur GPIO 2 et à un bouton-poussoir sur GPIO 23

Lecture recommandée: ESP32 PINOOUT RÉFÉRENCE: Quelles broches GPIO devriez-vous utiliser?

ESP32: Suspendre et reprendre les tâches Freertos – Code Arduino

Le code suivant écoute la presse d’un bouton-poussoir pour suspendre ou reprendre la tâche Freertos clignotante.

/*
  Rui Santos & Sara Santos - Raspberryme.com
  Complete project details at https://Raspberryme.com/esp32-freertos-arduino-tasks/
  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 LED1_PIN 2
#define BUTTON_PIN 23

// Task handle
TaskHandle_t BlinkTaskHandle = NULL;

// Volatile variables for ISR
volatile bool taskSuspended = false;
volatile uint32_t lastInterruptTime = 0;
const uint32_t debounceDelay = 100; // debounce period

void IRAM_ATTR buttonISR() {
  // Debounce
  uint32_t currentTime = millis();
  if (currentTime - lastInterruptTime < debounceDelay) {
    return;
  }
  lastInterruptTime = currentTime;

  // Toggle task state
  taskSuspended = !taskSuspended;
  if (taskSuspended) {
    vTaskSuspend(BlinkTaskHandle);
    Serial.println("BlinkTask Suspended");
  } else {
    vTaskResume(BlinkTaskHandle);
    Serial.println("BlinkTask Resumed");
  }
}

void BlinkTask(void *parameter) {
  for (;;) { // Infinite loop
    digitalWrite(LED1_PIN, HIGH);
    Serial.println("BlinkTask: LED ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS); // 1000ms
    digitalWrite(LED1_PIN, LOW);
    Serial.println("BlinkTask: LED OFF");
    Serial.print("BlinkTask running on core ");
    Serial.println(xPortGetCoreID());
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

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


  // Initialize pins
  pinMode(LED1_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP); // Internal pull-up resistor

  // Attach interrupt to button
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);

  // Create task
  xTaskCreatePinnedToCore(
    BlinkTask,         // Task function
    "BlinkTask",       // Task name
    10000,             // Stack size (bytes)
    NULL,              // Parameters
    1,                 // Priority
    &BlinkTaskHandle,  // Task handle
    1                  // Core 1
  );
}

void loop() {
  // Empty because FreeRTOS scheduler runs the task
}

Afficher le code brut

Comment fonctionne le code?

Nous avons déjà couvert comment créer des tâches dans l’exemple précédent. Nous allons donc couvrir les pièces pertinentes de cette section.

Nous définissons les GPIO pour la LED et le bouton-poussoir.

#define LED1_PIN 2
#define BUTTON_PIN 23

Nous créons des variables volatiles qui seront utilisées dans l’ISR (routine de service d’interruption pour le bouton-poussoir). La variable tasksuspeded est utilisée pour déterminer si la tâche est suspendue ou non, et le temps de dette et le deuil de DebountElay sont nécessaires pour déboucher le bouton-poussoir.

// Volatile variables for ISR
volatile bool taskSuspended = false;
volatile uint32_t lastInterruptTime = 0;
const uint32_t debounceDelay = 100; // debounce period

Pour détecter les presses à bouton-poussoir, nous utilisons des interruptions. Lorsque nous utilisons les interruptions, nous devons définir une routine de service d’interruption (une fonction qui s’exécute sur la RAM ESP32). Dans ce cas, nous créons la fonction boutonisr (). Nous devons ajouter IRAM_ATTR à la définition de la fonction pour l’exécuter sur RAM.

void IRAM_ATTR buttonISR() {

Lecture recommandée: ESP32 avec capteur de mouvement PIR en utilisant des interruptions et des minuteries.

Tout d’abord, nous déboudons le bouton-poussoir, et si une appuye sur un bouton est détectée, nous basculons l’état de la variable de drapeau à tâches tâches.

// Debounce
uint32_t currentTime = millis();
if (currentTime - lastInterruptTime < debounceDelay) {
  return;
}
lastInterruptTime = currentTime;

// Toggle task state
taskSuspended = !taskSuspended;

Suspendre et reprendre les tâches

Ensuite, si la variable tasksuspeded est vraie, nous appelons la fonction VTaskSusPend () et passons la poignée de la tâche comme argument. Ici, vous pouvez voir l’une des utilisations de la poignée de la tâche. C’est un moyen de se référer à la tâche pour le contrôler.

if (taskSuspended) {
  vTaskSuspend(BlinkTaskHandle);

Si la variable tasksuspeded est fausse, nous appelons la fonction VTaskResume () pour reprendre l’exécution de la tâche.

} else {
  vTaskResume(BlinkTaskHandle);

installation()

Dans la configuration (), nous devons déclarer notre bouton-poussoir comme une interruption comme suit:

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

Le reste du code reste similaire à l’exemple précédent.

Démonstration

Téléchargez le code sur votre carte ESP32.

La LED commencera à clignoter. Si vous appuyez sur le bouton-poussoir, la LED arrête de clignoter. Appuyez à nouveau et la LED recommencera à clignoter.

ESP32 avec une LED clignotant avec un bouton-poussoir pour suspendre et reprendre la tâche

Vous pouvez vérifier comment cela fonctionne en regardant la vidéo suivante.

Dans le même temps, vous devriez obtenir toutes les informations sur le moniteur en série.

ESP32 Suspendre et reprendre les tâches Freertos - Démonstration du moniteur en série

3) Créer et exécuter plusieurs tâches

Dans cette section, vous apprendrez à créer et à exécuter plusieurs tâches Freertos simultanément. Par exemple, nous allons créer deux tâches pour clignoter deux LED différentes.

Pièces requises

Pour cet exemple, vous avez besoin des parties suivantes:

Câblage du circuit

Nous allons câbler deux LED à l’ESP32. Nous utiliserons GPIOS 2 et 4. Vous pouvez utiliser tout autre GPIOS approprié tant que vous modifiez le code en conséquence.

Deux LED connectées à l'ESP32 - une LED connectée à GPIO2 et une autre connectée à GPIO4

ESP32: Création et exécution de plusieurs tâches – Code Arduino

Le code suivant crée deux tâches Freertos distinctes, chacune clignotant une LED à un rythme différent. Avec Freertos, il est facile d’exécuter les deux tâches indépendamment sans se bloquer. Cette approche est beaucoup plus propre et plus efficace que l’utilisation de retards ou de logique de synchronisation complexe dans le code arduino traditionnel.

/*
  Rui Santos & Sara Santos - Raspberryme.com
  Complete project details at https://Raspberryme.com/esp32-freertos-arduino-tasks/
  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 LED1_PIN 2
#define LED2_PIN 4

TaskHandle_t Task1Handle = NULL;
TaskHandle_t Task2Handle = NULL;

void Task1(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED1_PIN, HIGH);
    Serial.println("Task1: LED1 ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    digitalWrite(LED1_PIN, LOW);
    Serial.println("Task1: LED1 OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

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

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

  xTaskCreatePinnedToCore(
    Task1,             // Task function
    "Task1",           // Task name
    10000,             // Stack size (bytes)
    NULL,              // Parameters
    1,                 // Priority
    &Task1Handle,      // Task handle
    1                  // Core 1
  );

  xTaskCreatePinnedToCore(
    Task2,            // Task function
    "Task2",          // Task name
    10000,            // Stack size (bytes)
    NULL,             // Parameters     
    1,                // Priority
    &Task2Handle,     // Task handle
    1                 // Core 1
  );
}

void loop() {
  // Empty because FreeRTOS scheduler runs the task
}

Afficher le code brut

Comment fonctionne le code?

La création et l’exécution de plusieurs tâches sont aussi simples que la création et l’exécution d’une seule tâche.

Tout d’abord, vous devez définir vos tâches. Dans notre cas, nous avons deux tâches différentes pour clignoter deux LED différentes à des taux différents. Nous les appelons Task1 et Task2.

void Task1(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED1_PIN, HIGH);
    Serial.println("Task1: LED1 ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    digitalWrite(LED1_PIN, LOW);
    Serial.println("Task1: LED1 OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

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

Ensuite, dans la configuration (), nous avons juste besoin de créer les tâches à l’aide du XTaskCreatePinnedTocore. Dans cet exemple, nous exécutons les deux tâches sur Core 1 de l’ESP32 et les deux ont la même priorité.

xTaskCreatePinnedToCore(
  Task1,             // Task function
  "Task1",           // Task name
  10000,             // Stack size (bytes)
  NULL,              // Parameters
  1,                 // Priority
  &Task1Handle,      // Task handle
  1                  // Core 1
);

xTaskCreatePinnedToCore(
  Task2,            // Task function
  "Task2",          // Task name
  10000,            // Stack size (bytes)
  NULL,             // Parameters     
  1,                // Priority
  &Task2Handle,     // Task handle
  1                 // Core 1
);

Démonstration

Téléchargez le code sur votre carte. Après le téléchargement, appuyez sur le bouton ESP32 pour qu’il commence à exécuter le code. Vous aurez deux LED différentes clignotant à des tarifs différents.

ESP32 exécutant deux tâches Freertos - clignotant deux LED à des taux différents

Dans la vidéo suivante, vous pouvez mieux comprendre comment cela fonctionne.

Comme vous pouvez le voir, il est super simple d’y parvenir en créant des tâches Freertos au lieu d’utiliser des retards ou d’autres calculs de synchronisation complexes.


4) Création et exécution de plusieurs tâches sur différents noyaux (ESP32 Dual-Core)

La plupart des modèles ESP32 sont livrés avec deux cœurs, le Core 0 et le Core 1 désigné.

Câblage du circuit

Maintenez le circuit à partir de l’exemple précédent avec deux LED.

Tâches sur plusieurs cœurs

Le code suivant est similaire à l’exemple précédent, mais chaque tâche s’exécute sur un noyau différent. De plus, nous ajoutons également une ligne à chaque tâche pour revérifier dans quel noyau il s’exécute.

/*
  Rui Santos & Sara Santos - Raspberryme.com
  Complete project details at https://Raspberryme.com/esp32-freertos-arduino-tasks/
  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 LED1_PIN 2
#define LED2_PIN 4

TaskHandle_t Task1Handle = NULL;
TaskHandle_t Task2Handle = NULL;

void Task1(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED1_PIN, HIGH);
    Serial.println("Task1: LED1 ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    digitalWrite(LED1_PIN, LOW);
    Serial.println("Task1: LED1 OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    Serial.print("Task 1 running on core ");
    Serial.println(xPortGetCoreID());
  }
}

void Task2(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED2_PIN, HIGH);
    Serial.println("Task2: LED2 ON");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    digitalWrite(LED2_PIN, LOW);
    Serial.println("Task2: LED2 OFF");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    Serial.print("Task 2 running on core ");
    Serial.println(xPortGetCoreID());
  }
}

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

  xTaskCreatePinnedToCore(
    Task1,             // Task function
    "Task1",           // Task name
    10000,             // Stack size (bytes)
    NULL,              // Parameters
    1,                 // Priority
    &Task1Handle,      // Task handle
    1                  // Core 1
  );

  xTaskCreatePinnedToCore(
    Task2,            // Task function
    "Task2",          // Task name
    10000,            // Stack size (bytes)
    NULL,             // Parameters     
    1,                // Priority
    &Task2Handle,     // Task handle
    0                 // Core 0
  );
}

void loop() {
  // Empty because FreeRTOS scheduler runs the task
}

Afficher le code brut

Comment fonctionne le code?

La seule différence dans cet exemple est que nous définissons Task2 pour s’exécuter sur Core 0 comme indiqué ci-dessous.

xTaskCreatePinnedToCore(
  Task2,            // Task function
  "Task2",          // Task name
  10000,            // Stack size (bytes)
  NULL,             // Parameters     
  1,                // Priority
  &Task2Handle,     // Task handle
  0                 // Core 0
);

Démonstration

Si vous téléchargez le code sur votre ESP32, le résultat sera le même que l’exemple précédent. Deux LED clignotent à des taux différents.

ESP32 exécutant deux tâches Freertos - clignotant deux LED à des taux différents

Dans le moniteur série, vous pouvez réellement vérifier que chaque tâche s’exécute dans un autre noyau.

ESP32 Tâches d'exécution sur deux cœurs - Démonstration du moniteur en série

5) Utilisation de la mémoire des tâches

Dans cette section, nous couvrirons comment mesurer l’utilisation de la pile et du tas pour vos tâches afin que vous puissiez optimiser l’allocation de mémoire.

La pile et le tas sont deux types de mémoire utilisés par les tâches Freertos sur l’ESP32, chacune servant un rôle unique dans la gestion des besoins de mémoire d’un programme.

Quelle est exactement l’utilisation de la pile? La pile est une zone de mémoire dédiée pour chaque tâche Freertos, utilisée pour stocker des données temporaires comme les variables locales, les informations sur les appels de fonction et l’état de tâche pendant l’exécution. Chaque tâche a sa propre pile, allouée lorsque la tâche est créée. Vous avez vu que dans les exemples précédents, nous allouons une pile de 10000 octets à chaque tâche.

Il existe une fonction que vous pouvez appeler à l’intérieur de votre tâche pour déterminer l’utilisation de la pile: la fonction uxtaskgetstackhighwatermark (). Cette fonction détermine la taille de pile allouée qui n’est pas utilisée.

Qu’est-ce que l’utilisation du tas? Le tas est un pool de mémoire partagé dans le SRAM de l’ESP32, utilisé pour l’allocation de mémoire dynamique, y compris les piles de tâches, les tampons et autres données d’exécution allouées par Freertos ou le noyau Arduino. Nous pouvons appeler la fonction xportGetFreeHeapSize () dans notre code pour déterminer le tas libre.

Taille de pile de tâche et tas gratuit – code

Le code suivant est similaire à celui des projets précédents, avec deux fonctions pour clignoter deux LED différentes. Nous déterminons la pile libre et le tas libre.

Téléchargez le code suivant sur votre carte.

/*
  Rui Santos & Sara Santos - Raspberryme.com
  Complete project details at https://Raspberryme.com/esp32-freertos-arduino-tasks/
  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 LED1_PIN 2
#define LED2_PIN 4

TaskHandle_t Task1Handle = NULL;
TaskHandle_t Task2Handle = NULL;

void Task1(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED1_PIN, HIGH);
    Serial.println("Task1: LED1 ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    digitalWrite(LED1_PIN, LOW);
    Serial.println("Task1: LED1 OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    Serial.printf("Task1 Stack Free: %u bytes\n", uxTaskGetStackHighWaterMark(NULL));
  }
}

void Task2(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED2_PIN, HIGH);
    Serial.println("Task2: LED2 ON");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    digitalWrite(LED2_PIN, LOW);
    Serial.println("Task2: LED2 OFF");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    Serial.printf("Task2 Stack Free: %u bytes\n", uxTaskGetStackHighWaterMark(NULL));
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.printf("Starting FreeRTOS: Memory Usage\nInitial Free Heap: %u bytes\n", xPortGetFreeHeapSize());

  xTaskCreatePinnedToCore(
    Task1,
    "Task1",
    10000,
    NULL,
    1,
    &Task1Handle,
    1
  );

  xTaskCreatePinnedToCore(
    Task2,
    "Task2",
    10000,
    NULL,
    1,
    &Task2Handle,
    1
  );
}

void loop() {
  static uint32_t lastCheck = 0;
  if (millis() - lastCheck > 5000) {
    Serial.printf("Free Heap: %u bytes\n", xPortGetFreeHeapSize());
    lastCheck = millis();
  }
}

Afficher le code brut

Démonstration

Après avoir téléchargé le code sur votre carte, vous devriez obtenir quelque chose de similaire sur votre moniteur série.

ESP32 avec Freertos Déterminez la tâche pile de filigrane élevé

L’UXTaskGetStackHighwaterMark rapporte 8556 octets gratuits pour les tâches 1 et 8552 octets gratuits pour la tâche2, ce qui signifie que chaque tâche utilise 1444–1448 octets de sa pile de 10000 octets au sommet. Ainsi, nous pouvons réduire considérablement la taille de la pile allouée à chaque tâche.

Une bonne taille de pile devrait couvrir l’utilisation de pointe de la tâche (1444 octets) plus une marge de sécurité de 500 à 1000 octets pour gérer les augmentations inattendues

Dans le cas d’un tas gratuit, dans mon cas, le xportGetFreeHeapSize () rapporte 247616 octets gratuits (non illustré dans la capture d’écran), indiquant le tas restant après l’allocation de piles (20000 octets pour deux tâches) et d’autres ressources système.

Emballage

Ce tutoriel était une introduction détaillée à Freertos avec l’ESP32. Vous avez appris à créer des tâches uniques et multiples, attribuer un noyau à chaque tâche, suspendre et reprendre des tâches et même calculer la pile de tâches.

L’utilisation de Freertos avec l’ESP32 est un excellent choix car il vous permet d’effectuer plusieurs tâches simultanément de manière simple, avec des priorités pour exécuter les tâches les plus critiques en premier.

Dans les futurs tutoriels, nous couvrirons la communication entre les tâches à l’aide de sémaphores et de files d’attente.

Nous espérons que vous avez trouvé ce guide utile.

En savoir plus sur l’ESP32 avec nos ressources:

Merci d’avoir lu.

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

Youtube video

  • ELEGOO Carte Starter Kit de Démarrage ESP-32 Compatible avec Arduino IDE
  • Arduino Nano ESP32 with headers [ABX00083] - Carte compacte avec microcontrôleur ESP32, WiFi et Bluetooth intégrés, idéale pour projets IoT, automatisation et applications connectées avec Arduino IDE.