TP 1 — Premières tâches
Objectifs
comprendre ce qu’apporte un système temps réel par rapport à la boucle infinie de la partie 1 ;
créer un projet FreeRTOS avec CubeMX, et plusieurs tâches ;
voir la différence entre une attente qui bloque la tâche (
osDelay) et une attente qui occupe le processeur (HAL_Delay).
Durée indicative : une séance de 3 h. Prérequis : la partie 1, au moins TP 1 — Allumer une LED, lire un bouton.
Ce qu’il faut savoir
Le problème de la boucle infinie
Dans la partie 1, une seule boucle fait tout. Pour faire clignoter une LED
toutes les 500 ms et une autre toutes les 130 ms, il a fallu remplacer
HAL_Delay par des comparaisons avec HAL_GetTick() : chaque nouvelle
activité complique la boucle, et une seule opération lente retarde toutes les
autres.
Les tâches
FreeRTOS découpe le programme en tâches : chacune est une fonction avec sa propre boucle infinie, sa propre pile, et l’air de s’exécuter seule. Un ordonnanceur passe de l’une à l’autre, au rythme d’un tic d’horloge, ici 1 000 fois par seconde.
Une tâche est toujours dans l’un de ces états :
en cours (running) : elle a le processeur ;
prête (ready) : elle pourrait tourner, et attend son tour ;
bloquée (blocked) : elle attend quelque chose (un délai, un message), et ne consomme rien.

Les trois états d’une tâche, et ce qui la fait passer de l’un à l’autre.
osDelay(500) bloque la tâche pendant 500 tics : le processeur passe
aussitôt aux autres tâches. C’est toute la différence avec HAL_Delay, qui
attend en tournant en rond.
Étape 1 — Le projet FreeRTOS
Créez un projet
rtos_tp01_tachescomme au TP 1 de la partie 1 (STM32H750VBTx, SYS → Debug : Serial Wire, horloge par défaut), avec LED_1 à LED_4 en sortie et SW1 (PE10) en entrée sans tirage.Dans Middleware and Software Packs → FREERTOS, choisissez l’interface CMSIS_V2.
La base de temps de la HAL. FreeRTOS utilise le minuteur SysTick pour son tic. La HAL ne doit plus s’en servir pour
HAL_Delay: dans System Core → SYS, réglez Timebase Source sur TIM6. CubeMX affiche un avertissement tant que ce n’est pas fait.Dans l’onglet Tasks and Queues de FREERTOS, gardez
defaultTasket ajoutez deux tâches :Nom
Fonction
Priorité
Pile (mots)
led1TaskStartLed1osPriorityNormal
256
led2TaskStartLed2osPriorityNormal
256
Générez le code. Chaque tâche a maintenant une fonction, avec une boucle
for (;;)et unosDelay(1)de départ. Selon la version de CubeMX, ces fonctions sont dansmain.cou dansfreertos.c: cherchezStartLed1(Ctrl-H).
Important
Après osKernelStart(), la boucle while (1) de main n’est
plus jamais exécutée : c’est l’ordonnanceur qui a la main. Tout le
travail se fait désormais dans les tâches.
Étape 2 — Deux clignotements indépendants
Dans
StartLed1, faites clignoter LED_1 : bascule, puisosDelay(500).Dans
StartLed2, faites clignoter LED_2 : bascule, puisosDelay(130).Dans
StartDefaultTask, allumez LED_3 tant que SW1 est enfoncé, en lisant le bouton toutes les 20 ms.
Chaque tâche est écrite comme si elle était seule, avec une attente simple : c’est le gain d’un système temps réel.
Questions
Comparez ce code avec la boucle de la partie 1 qui faisait la même chose avec
HAL_GetTick(). Lequel est le plus facile à modifier si l’on ajoute une troisième LED à un autre rythme ?Combien de temps chaque tâche passe-t-elle réellement à travailler, sur une période de clignotement ? Que fait le processeur le reste du temps ?
Solution de l’étape 2
CubeMX a créé trois fonctions, une par tâche, chacune avec sa boucle
for (;;). On les complète :
void StartDefaultTask(void *argument)
{
(void)argument;
for (;;)
{
HAL_GPIO_WritePin(LED_3_GPIO_Port, LED_3_Pin,
HAL_GPIO_ReadPin(SW1_GPIO_Port, SW1_Pin));
osDelay(20); /* lecture toutes les 20 ms */
}
}
void StartLed1(void *argument)
{
(void)argument;
for (;;)
{
HAL_GPIO_TogglePin(LED_1_GPIO_Port, LED_1_Pin);
osDelay(500);
}
}
void StartLed2(void *argument)
{
(void)argument;
for (;;)
{
HAL_GPIO_TogglePin(LED_2_GPIO_Port, LED_2_Pin);
osDelay(130);
}
}
Ce que fait le système. osKernelStart(), à la fin de main, démarre
l”ordonnanceur : il choisit une tâche prête et lui donne le processeur.
1 000 fois par seconde, l’interruption SysTick (le tic) le réveille :
il fait avancer le temps, réveille les tâches dont le délai est écoulé, et
décide s’il faut changer de tâche. Changer de tâche, c’est sauvegarder les
registres du processeur sur la pile de la tâche qui s’arrête, et restaurer
ceux de la suivante (une interruption logicielle, PendSV, s’en charge).
Chaque tâche a donc sa propre pile : c’est ce qui lui permet de
reprendre exactement là où elle s’était arrêtée.
osDelay(500) fait passer la tâche dans l’état bloqué jusqu’au 500e tic suivant. L’ordonnanceur ne la regarde plus pendant ce temps : elle ne coûte rien.
(void)argument dit au compilateur que le paramètre est volontairement inutilisé : sans cela, il avertit.
Réponses aux questions 1 et 2
1. Comparer avec la boucle de la partie 1. Avec FreeRTOS, ajouter une
LED à un autre rythme, c’est ajouter une tâche de quatre lignes, écrite
comme si elle était seule, sans toucher aux autres. Avec la boucle et
HAL_GetTick(), il faut une variable de temps de plus, un test de plus,
et vérifier que rien d’autre dans la boucle ne bloque. Le code de chaque
activité y est mélangé à celui des autres : avec dix activités, la
boucle devient illisible. FreeRTOS sépare les activités.
2. Le temps de travail. Basculer une LED prend quelques dizaines de
cycles, soit moins d’une microseconde, par période de 130 ou 500 ms.
Les trois tâches travaillent donc environ un millionième du temps. Le reste
du temps, elles sont toutes bloquées, et l’ordonnanceur exécute la tâche
d’inactivité (idle task), créée automatiquement, de priorité minimale,
qui ne fait que tourner en rond — ou, si on le lui demande, met le cœur en
sommeil (instruction WFI) jusqu’à la prochaine interruption pour
économiser l’énergie. Le TP 9 mesure ce temps libre.
Étape 3 — osDelay contre HAL_Delay
Dans
StartLed2, remplacezosDelay(130)parHAL_Delay(130). Les deux LED clignotent-elles toujours correctement ?Dans l’onglet Tasks and Queues, passez
led2Tasken priorité osPriorityAboveNormal, régénérez, et recommencez l’essai avecHAL_Delay. Et avecosDelay?
Questions
À priorité égale, pourquoi les deux LED clignotent-elles encore avec
HAL_Delay? (Indice : l’ordonnanceur partage le temps entre tâches de même priorité à chaque tic.)Avec une priorité plus haute et
HAL_Delay, que deviennent LED_1 et LED_3 ? Pourquoi ce problème n’existe-t-il pas avecosDelay?En conclusion : dans une tâche, quelle fonction d’attente faut-il utiliser ?
Réponses aux questions 3 à 5
3. À priorité égale, avec HAL_Delay. Les deux LED clignotent encore,
car FreeRTOS partage le temps (time slicing) entre les tâches prêtes
de même priorité : à chaque tic, il passe à la suivante. led2Task, qui
tourne en rond dans HAL_Delay, reste prête en permanence ; mais quand
le délai de led1Task ou de defaultTask expire, celle-ci redevient
prête, et au tic suivant (au plus 1 ms plus tard) l’ordonnanceur lui donne
son tour. Elle bascule sa LED et se rebloque. Tout marche donc, mais
led2Task gaspille le processeur : pendant ses 130 ms d’attente, elle
consomme tout le temps que les autres ne prennent pas.
4. À priorité plus haute, avec HAL_Delay. LED_1 et LED_3 se figent.
La règle de FreeRTOS est stricte : la tâche prête la plus prioritaire
s’exécute. Or led2Task, prioritaire, n’est jamais bloquée : quand
elle ne bascule pas sa LED, elle attend en tournant dans HAL_Delay, donc
elle reste prête. Les tâches de priorité normale ne reçoivent plus jamais
le processeur : c’est la famine (TP 2). Avec osDelay, led2Task
se bloque 130 ms à chaque tour : les autres en profitent.
À noter : HAL_Delay marche encore ici parce que son compteur avance
grâce à l’interruption de TIM6, la base de temps de la HAL (étape 1).
Les interruptions, elles, passent toujours avant les tâches.
5. La conclusion. Dans une tâche, on attend toujours avec une
fonction du système, qui bloque la tâche : osDelay,
osDelayUntil (TP 2), ou l’attente d’un message ou d’un sémaphore (TP 3).
HAL_Delay n’a sa place qu’avant le démarrage de l’ordonnanceur.
Tout le code de la correction
taches.c
— compilé sans avertissement contre la HAL et le FreeRTOS 10.6.2 du
firmware RatBot.
Le fichier contient les fonctions des tâches, qui remplacent les
squelettes générés par CubeMX (dans main.c ou freertos.c selon la
version), et les extraits à placer dans les blocs USER CODE signalés en
commentaire. La configuration CubeMX attendue est rappelée en tête du
fichier, et les réponses aux questions à la fin.
Exercices supplémentaires
Ces exercices n’ont pas de correction.
Quatre LED, quatre tâches : chaque LED de façade a sa tâche et son rythme ; une seule fonction de tâche suffit, si l’on passe le numéro de la LED en argument (
argumentdeosThreadNew).La pile : réduisez la pile d’une tâche à 64 mots, ajoutez un grand tableau local, et observez. Cherchez
configCHECK_FOR_STACK_OVERFLOW.Mesurer la pile : affichez au terminal (TP 5 de la partie 1) la marge de pile restante de chaque tâche, avec
uxTaskGetStackHighWaterMark.
Après le TP
Pour rendre la carte au robot, voir Remettre la carte en état RatBot.