TP 5 — Timers logiciels et chien de garde
Objectifs
déclencher une action périodique ou différée sans créer de tâche, avec un timer logiciel ;
comprendre le chien de garde (watchdog) : un compte à rebours matériel qui redémarre la carte si le programme cesse de le relancer ;
surveiller que chaque tâche tourne encore, et pas seulement une.
Durée indicative : une séance de 3 h. Prérequis : TP 3 — Communiquer entre tâches.
Ce qu’il faut savoir
Les timers logiciels
Un timer logiciel appelle une fonction (callback) après un délai, une
fois (one-shot) ou périodiquement. Tous les timers sont servis par une seule
tâche du système, la timer service task (Tmr Svc) : leurs fonctions
doivent donc être courtes, et ne jamais attendre — une attente dans
l’une bloquerait tous les autres timers.
Le chien de garde indépendant
L”IWDG (Independent WatchDoG) est un compteur matériel qui décompte au rythme de son propre oscillateur, le LSI (environ 32 kHz), indépendant de l’horloge principale. S’il atteint zéro, il redémarre le microcontrôleur. Le programme doit donc le relancer (refresh) régulièrement.
Une fois démarré, rien ne peut l’arrêter, sauf un redémarrage. C’est ce qui le rend sûr : un programme planté ne peut pas le désactiver par erreur.
Avec FreeRTOS, relancer le chien de garde dans une tâche ne prouve qu’une chose : que cette tâche tourne. Une bonne surveillance vérifie que toutes les tâches importantes ont donné signe de vie avant de le relancer.
Le firmware du robot fonctionne ainsi : si sa boucle de traction se fige, le chien de garde redémarre la carte, et la cause est notée dans son journal de bord.
Étape 1 — Un timer périodique
Créez un projet
rtos_tp05_timerscomme au TP 3 (FreeRTOS CMSIS_V2, Timebase TIM6, UART5 etprintf, LED_1 à LED_4, SW1 à SW4).Dans l’onglet Timers and Semaphores, créez un timer
blinkTimer, de type osTimerPeriodic, avec la fonctionBlinkCallback.Démarrez-le avant le lancement de l’ordonnanceur (
USER CODE BEGIN RTOS_TIMERS) :osTimerStart(blinkTimerHandle, 500);Dans
BlinkCallback, faites basculer LED_1.
Étape 2 — Un timer à usage unique
Une minuterie d’escalier : un appui sur SW2 allume LED_2 pendant 3 s ; un nouvel appui pendant ce temps repart pour 3 s.
Créez un timer
lightTimerde type osTimerOnce, fonctionLightCallback, qui éteint LED_2.Dans une tâche qui scrute SW2 (avec anti-rebond), à chaque appui : allumez LED_2 et appelez
osTimerStart(lightTimerHandle, 3000).
Questions
Que fait
osTimerStartsur un timer déjà en marche ?Ajoutez un
osDelay(2000)dansBlinkCallback. Que devient la minuterie d’escalier ? Pourquoi ?
Solution des étapes 1 et 2
Le démarrage du timer périodique, dans USER CODE BEGIN RTOS_TIMERS :
osTimerStart(blinkTimerHandle, 500);
Les deux fonctions de rappel :
void BlinkCallback(void *argument) /* toutes les 500 ms */
{
(void)argument;
HAL_GPIO_TogglePin(LED_1_GPIO_Port, LED_1_Pin);
}
void LightCallback(void *argument) /* 3 s après le dernier appui */
{
(void)argument;
HAL_GPIO_WritePin(LED_2_GPIO_Port, LED_2_Pin, GPIO_PIN_RESET);
}
et, dans la tâche des boutons, à chaque appui sur SW2 (front montant, lu toutes les 30 ms, ce qui suffit à filtrer les rebonds) :
HAL_GPIO_WritePin(LED_2_GPIO_Port, LED_2_Pin, GPIO_PIN_SET);
osTimerStart(lightTimerHandle, 3000);
Qui exécute les rappels. Ni une interruption, ni la tâche qui a démarré le timer : la tâche des timers de FreeRTOS (Tmr Svc), créée au démarrage. Elle garde la liste des timers triée par échéance, dort jusqu’à la plus proche, et appelle le rappel. Démarrer ou arrêter un timer, c’est lui envoyer un message dans sa file de commandes. Avantage : un timer ne coûte presque rien (pas de pile à lui). Limite : tous les rappels partagent une tâche.
Réponses aux questions 1 et 2
1. osTimerStart sur un timer en marche. Il le relance : le délai
repart de zéro à partir de maintenant. C’est exactement le comportement
d’une minuterie d’escalier : chaque appui prolonge l’éclairage de 3 s. Pour
ne pas relancer, il faudrait tester osTimerIsRunning avant.
2. Un osDelay(2000) dans BlinkCallback. La minuterie d’escalier
déraille : LED_2 s’éteint jusqu’à 2 s trop tard. Pendant que
BlinkCallback attend, la tâche des timers est bloquée dans ce
rappel : elle ne peut exécuter aucun autre rappel, et LightCallback
attend son tour. Le clignotement lui-même devient faux (2,5 s au lieu de
0,5 s). Un rappel de timer doit être court et ne jamais attendre —
la même règle qu’une interruption, pour une raison voisine.
Étape 3 — Le chien de garde
Dans System Core → IWDG1, cochez Activated. Réglez IWDG counter clock prescaler sur 64 et IWDG down-counter reload value sur 1000.
Dans
USER CODE BEGIN 2, une fois l’UART initialisée, regardez si le dernier redémarrage vient du chien de garde :if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDG1RST)) { printf("redemarrage par le chien de garde\r\n"); HAL_GPIO_WritePin(LED_4_GPIO_Port, LED_4_Pin, GPIO_PIN_SET); } __HAL_RCC_CLEAR_RESET_FLAGS();
Lancez le programme sans relancer le chien de garde.
Créez une tâche
watchdogTaskqui appelleHAL_IWDG_Refresh(&hiwdg1)toutes les 500 ms.
Questions
Calculez le délai du chien de garde avec ces réglages. Correspond-il à ce que vous observez à l’étape 3 ?
Mettez le programme en pause dans le débogueur, quelques secondes. Que se passe-t-il à la reprise ? Cherchez
__HAL_DBGMCU_FREEZE_IWDG1.
Solution de l’étape 3
Dans USER CODE BEGIN 2 :
if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDG1RST))
{
printf("redemarrage par le chien de garde\r\n");
HAL_GPIO_WritePin(LED_4_GPIO_Port, LED_4_Pin, GPIO_PIN_SET);
}
__HAL_RCC_CLEAR_RESET_FLAGS();
__HAL_DBGMCU_FREEZE_IWDG1(); /* question 4 */
et la tâche de surveillance, première version :
void StartWatchdog(void *argument)
{
(void)argument;
for (;;)
{
osDelay(500);
HAL_IWDG_Refresh(&hiwdg1);
}
}
La cause du redémarrage. Le registre RSR du RCC garde des drapeaux
qui indiquent pourquoi la carte a redémarré : mise sous tension,
bouton reset, redémarrage logiciel, chien de garde… Ils restent levés
jusqu’à ce qu’on les efface (RMVF) : il faut donc les lire, puis les
effacer, à chaque démarrage. Le firmware du robot range cette cause dans
son journal de bord.
Où est démarré le chien de garde. MX_IWDG1_Init, généré par CubeMX,
le démarre dès l’initialisation, bien avant l’ordonnanceur. Les tâches
doivent donc commencer à le relancer dans les 2 s.
Réponses aux questions 3 et 4
3. Le délai. Le compteur de l’IWDG avance au rythme du LSI divisé par le prédiviseur, et part de la valeur de rechargement :
32 000 Hz / 64 = 500 Hz (2 ms par pas)
1 000 pas × 2 ms = 2,0 s
Sans relance, la carte redémarre toutes les 2 s environ : LED_4 s’allume et le message apparaît à chaque démarrage. Le délai mesuré peut s’écarter de quelques pourcents : le LSI est un oscillateur RC interne, peu précis (il varie avec la température et d’une puce à l’autre). C’est voulu : le chien de garde a sa propre horloge, indépendante de celle du processeur, pour fonctionner même si celle-ci tombe en panne.
4. En pause dans le débogueur. Le cœur est arrêté, mais le chien de
garde, sur son horloge à lui, continue de compter : au bout de 2 s, il
redémarre la carte, et la session de débogage est perdue. C’est déroutant
la première fois. __HAL_DBGMCU_FREEZE_IWDG1() règle le bloc de débogage
(DBGMCU) pour figer le chien de garde tant que le cœur est arrêté
par le débogueur. Sans effet en dehors du débogage.
Étape 4 — Surveiller toutes les tâches
Le chien de garde est maintenant relancé… même si une autre tâche est figée. Améliorons la surveillance :
Chaque tâche surveillée (ici la tâche des boutons, et une tâche
workqui fait clignoter LED_3) lève son bit dans une variable commune à chaque tour de boucle : bit 0 pour l’une, bit 1 pour l’autre.watchdogTaskrelance le chien de garde seulement si tous les bits sont levés, puis les remet à zéro.Pour simuler une panne, un appui sur SW3 fait entrer la tâche
workdans une boucle infinie :for (;;) { }.
Questions
Avec la relance sans condition de l’étape 3, la carte redémarre-t-elle quand
workse fige ? Pourquoi ?La variable commune est modifiée par plusieurs tâches. Montrez qu’un simple
alive |= 1upeut perdre un bit, et protégez-la (section critique, ouosThreadFlags, ou un octet par tâche).La tâche
watchdogTaskest-elle elle-même surveillée ?
Solution de l’étape 4
static volatile uint8_t g_alive_button; /* un octet par tâche surveillée */
static volatile uint8_t g_alive_work;
volatile uint8_t g_freeze_work; /* SW3 : panne simulée */
void StartWork(void *argument)
{
(void)argument;
for (;;)
{
if (g_freeze_work) { for (;;) { } } /* la tâche se fige */
HAL_GPIO_TogglePin(LED_3_GPIO_Port, LED_3_Pin);
g_alive_work = 1u; /* signe de vie */
osDelay(200);
}
}
void StartWatchdog(void *argument)
{
(void)argument;
for (;;)
{
osDelay(500);
if (g_alive_button && g_alive_work) /* tout le monde a répondu */
{
g_alive_button = 0u;
g_alive_work = 0u;
HAL_IWDG_Refresh(&hiwdg1);
}
}
}
et g_alive_button = 1u; à chaque tour de la tâche des boutons, qui met
aussi g_freeze_work à 1 à l’appui sur SW3.
Le principe. Chaque tâche surveillée prouve qu’elle tourne, en levant son drapeau à chaque tour de boucle. La tâche de surveillance vérifie, toutes les 500 ms, que tous les drapeaux ont été levés depuis sa dernière visite, et seulement alors relance le chien de garde. Si une tâche se fige, son drapeau reste à 0, le chien de garde n’est plus relancé, et la carte redémarre dans les 2 s.
La période de surveillance (500 ms) doit être plus longue que la période de chaque tâche surveillée (200 et 30 ms), sinon une tâche saine serait jugée morte ; et assez courte pour relancer le chien de garde à temps (2 s), même si la surveillance prend du retard.
Réponses aux questions 5 à 7
5. Avec la relance sans condition. Non, la carte ne redémarre pas. work est figée
dans une boucle infinie, mais elle a la même priorité que watchdogTask :
l’ordonnanceur partage le temps entre elles, et watchdogTask continue de
relancer le chien de garde toutes les 500 ms. La panne passe inaperçue.
Relancer le chien de garde depuis une seule tâche ne prouve qu’une chose :
que cette tâche-là tourne.
6. alive |= 1u n’est pas sûr. L’instruction se compile en trois
opérations : lire la variable dans un registre, faire le OU,
écrire le résultat. Si la tâche A lit alive (0b00), puis est
préemptée ; si B lève son bit et écrit 0b10 ; A reprend, calcule
0b00 | 0b01 = 0b01 et l’écrit : le bit de B est effacé. C’est une
condition de course (race condition). Trois remèdes :
un octet par tâche (la solution ci-dessus) : chaque tâche n’écrit que son octet, en une seule écriture, qui ne peut pas être coupée en deux ;
une section critique :
taskENTER_CRITICAL(); alive |= 1u; taskEXIT_CRITICAL();empêche toute préemption pendant l’opération ;les drapeaux de tâche (
osThreadFlagsSet), que le système modifie de façon atomique.
7. watchdogTask est-elle surveillée ? Oui, indirectement : si elle se
fige, plus personne ne relance le chien de garde, et la carte redémarre.
C’est le chien de garde matériel qui la surveille. Mais attention : si une
tâche plus prioritaire s’emballe, watchdogTask est affamée, et le
résultat est le même — redémarrage. C’est d’ailleurs souhaitable : un
système où une tâche monopolise le processeur est en panne.
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.
Journal de redémarrage : comptez les redémarrages par chien de garde dans un registre de sauvegarde de la RTC (TP 9 de la partie 1), et affichez le compteur au démarrage.
Quelle tâche ? Au moment où la surveillance renonce à relancer le chien de garde, notez dans un registre de sauvegarde quelle tâche manquait, et affichez-la après le redémarrage.
Le chien de garde fenêtré (WWDG) : il redémarre aussi la carte si on le relance trop tôt. Dans quel cas est-ce utile ?
Après le TP
Pour rendre la carte au robot, voir Remettre la carte en état RatBot. Un programme avec
chien de garde actif redémarre la carte en boucle s’il ne le relance pas :
pour reprendre la main, maintenez la carte en reset pendant la connexion du
programmateur (mode=UR dans STM32CubeProgrammer).