Maîtriser le multitâche Arduino avec les Protothreads : Tutoriel pas à pas
Composants nécessaires
| Composant | Quantité |
|---|---|
| Arduino UNO | 1 |
| Écran LCD 16x2 Sunfounder Blue (ou équivalent compatible HD44780) | 1 |
| Plaque d'essai (breadboard) et fils de connexion | 1 |
| Potentiomètre rotatif (environ 1 kΩ) | 1 |
À propos de ce projet
Ce tutoriel montre comment faire exécuter trois tâches simultanément sur un Arduino mono-cœur :
- Faire pulser le rétroéclairage du LCD à un rythme constant
- Incrémenter un compteur chaque seconde et l'afficher
- Faire défiler plusieurs messages texte à intervalles réguliers
Qu'est-ce que le protothreading ?
Le protothreading est une technique qui simule le multitâche sur un microcontrôleur mono-cœur comme l'ATmega328P de l'Arduino Uno. Contrairement au vrai multitâche (qui nécessite plusieurs cœurs), le protothreading utilise une approche coopérative : la boucle principale (loop()) agit comme un « gestionnaire » qui vérifie périodiquement si d'autres tâches doivent s'exécuter, en fonction du temps écoulé.
Multitâche réel vs Protothreading
Un processeur multi-cœurs exécute véritablement des threads en parallèle. L'Arduino Uno, avec son unique cœur à 16 MHz, ne peut exécuter qu'une instruction à la fois. Le protothreading contourne cette limitation en découpant le travail en petites unités non-bloquantes et en utilisant millis() pour la gestion du temps, au lieu de delay() qui bloque tout le processeur.
Comment ça marche ?
Le principe repose sur la bibliothèque TimedAction (ou une implémentation maison) :
- On définit des « threads » avec leur intervalle d'exécution et leur fonction associée.
- Dans la boucle principale (la tâche la plus fréquente, ici le fading du rétroéclairage), on appelle
check()sur chaque thread à chaque itération, y compris à l'intérieur des bouclesforqui pourraient bloquer. - Si le temps écoulé depuis la dernière exécution dépasse l'intervalle, la fonction du thread s'exécute immédiatement.
Attention : les opérations bloquantes (lecture carte SD, communications lentes) perturberont la précision des autres threads. Pour des tâches légères (modification de variables, écriture LCD, PWM), c'est très efficace.
Implémentation pas à pas
- Identifier les tâches et les classer du moins bloquant (fréquent, rapide) au plus bloquant (rare, lent).
- Choisir la tâche principale (celle qui s'exécute le plus souvent) comme boucle
loop(). - Initialiser les threads avec leurs intervalles (ex: 700 ms pour le compteur, 3000 ms pour le texte).
- Appeler
check()partout : dansloop()et dans toute boucle interne.
Code complet (multithread.ino v1.1)
/* Arduino Protothreading Example v1.1
* Auteur : Drew Alden (@ReanimationXP)
* Basé sur la bibliothèque TimedAction.h
* Note : Remplacer WProgram.h par Arduino.h dans TimedAction.h pour Arduino IDE 1.6.6+
*/
#include <TimedAction.h>
#include <LiquidCrystal.h>
// --- Brochage LCD (standard Hitachi HD44780) ---
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
// --- Variables globales ---
const int backlightPin = 9; // PWM pour le rétroéclairage
int timerCounter = 0; // Compteur incrémenté
int stringNo = 0; // Index du message affiché
char* stringArray[] = {
"Check it out... ",
"I have 3 threads",
"going at once...",
"Cool, huh?! :D "
};
// --- Déclaration des threads (après les fonctions !) ---
TimedAction numberThread = TimedAction(700, incrementNumber);
TimedAction textThread = TimedAction(3000, changeText);
// --- Fonctions des threads secondaires ---
void incrementNumber() {
lcd.setCursor(0, 1);
timerCounter++;
lcd.print(timerCounter);
}
void changeText() {
lcd.setCursor(0, 0);
lcd.print(stringArray[stringNo]);
stringNo = (stringNo + 1) % (sizeof(stringArray) / sizeof(char*));
}
void setup() {
lcd.begin(16, 2);
changeText(); // Affichage initial
}
void loop() {
// Vérifier les threads secondaires
numberThread.check();
textThread.check();
// Tâche principale : fading du rétroéclairage (montée)
digitalWrite(13, HIGH);
for (int fadeValue = 0; fadeValue <= 255; fadeValue += 10) {
numberThread.check(); textThread.check(); // Indispensable !
analogWrite(backlightPin, fadeValue);
delay(20); // Court délai, non bloquant pour les threads
}
// Fading (descente)
digitalWrite(13, LOW);
for (int fadeValue = 255; fadeValue >= 0; fadeValue -= 10) {
numberThread.check(); textThread.check();
analogWrite(backlightPin, fadeValue);
delay(20);
}
}
Points clés à retenir
- Le protothreading n'est pas du vrai multitâche, mais une simulation coopérative efficace pour tâches légères.
- La bibliothèque
TimedActionest ancienne ; pour de nouveaux projets, préférez millis() natif ou des bibliothèques modernes commeTaskSchedulerouFreeRTOS(sur ESP32/ESP8266). - Toute boucle
forouwhilelongue doit contenir des appelscheck(). - Évitez
delay()long dans la boucle principale ; utilisez des délais courts (< 50 ms) si nécessaire.
Aller plus loin
Pour des applications plus complexes nécessitant une vraie préemption ou une gestion de tâches bloquantes, envisagez :
- Un microcontrôleur multi-cœurs (ESP32, Raspberry Pi Pico).
- Un RTOS comme FreeRTOS (disponible sur ESP32 via Arduino IDE).
- L'utilisation d'interruptions matérielles pour les événements critiques.
Processus de fabrication
- Mesurer la qualité de l'air avec OpenSensors : Guide Arduino et Python
- Créer un verrou RFID sécurisé avec Arduino – Guide étape par étape
- Construire une télécommande IR universelle avec Arduino : Guide étape par étape
- Construisez un Stadiomètre Numérique : Suivez la Croissance avec Arduino et Capteur Laser
- Utiliser une thermistance facilement : Guide étape par étape Arduino
- Générer des Sons Musicaux avec Arduino : Guide Pas à Pas
- Connecter Arduino à NMEA‑0183 : Guide étape par étape
- Enregistreur de Données Environnementales Arduino avec Carte SD et RTC
- Tutoriel Arduino Débutant 01 : Démarrez votre Premier Projet