Du scheduler coopératif au RTOS — comment FreeRTOS gère plusieurs tâches indépendantes, les priorités, et la préemption automatique sur simulateur Windows.
Le scheduler coopératif vu en Phase 1 est simple mais limité : une tâche qui prend du temps retarde toutes les autres. FreeRTOS est préemptif — il interrompt une tâche à tout moment pour en exécuter une de priorité plus haute.
// Prototype d'une fonction tâche — toujours void, toujours boucle infinie void vTaskAcquisition(void *pvParams) { AppContext_t *ctx = (AppContext_t *)pvParams; // récupérer le contexte for (;;) { // Travail de la tâche console_print(ctx, "[ACQ] lecture capteur\n"); vTaskDelay(pdMS_TO_TICKS(500)); // dormir 500ms — libère le CPU } // Ne jamais retourner — si la tâche se termine, appeler vTaskDelete(NULL) } int main(void) { AppContext_t ctx = { .mutex = xSemaphoreCreateMutex() }; // Créer 3 tâches avec des priorités différentes xTaskCreate( vTaskAcquisition, // fonction "Acquisition", // nom (debug) 1024, // stack en words (pas en octets !) &ctx, // pvParams → passé à la tâche 3, // priorité (plus haut = plus urgent) NULL // handle optionnel ); xTaskCreate(vTaskDisplay, "Display", 1024, &ctx, 2, NULL); xTaskCreate(vTaskLogger, "Logger", 1024, &ctx, 1, NULL); vTaskStartScheduler(); // démarre FreeRTOS — ne retourne jamais for (;;); }
| Priorité | Tâche | Comportement | Typiquement |
|---|---|---|---|
| Haute (3+) | Acquisition, ISR handler | Préempte toutes les autres immédiatement | Temps-réel strict |
| Moyenne (2) | Traitement, affichage | Tourne quand haute prio est bloquée | Logique applicative |
| Basse (1) | Logging, monitoring | Tourne seulement si tout le reste est bloqué | Tâches de fond |
Plutôt que des variables globales, toutes les ressources partagées (queue, mutex, flags) sont regroupées dans une struct AppContext passée à chaque tâche via pvParams.
typedef struct { QueueHandle_t cmd_queue; // file de commandes inter-tâches SemaphoreHandle_t mutex; // protection stdout partagé volatile bool shutdown_req; // signal d'arrêt propre } AppContext_t; // Dans chaque tâche : void vMyTask(void *pvParams) { AppContext_t *ctx = (AppContext_t *)pvParams; // Accès via ctx->mutex, ctx->cmd_queue, etc. // Zéro variable globale dans le code applicatif }
Zéro globale applicative · console_print protège stdout via le mutex du contexte · La queue est partagée proprement · On pourrait instancier deux AppContext indépendants sans toucher au code des tâches.
Chaque tâche a sa propre pile allouée statiquement. Trop petite → stack overflow silencieux sur STM32. FreeRTOS fournit un outil de diagnostic :
// Dans une tâche de monitoring (basse priorité) void vTaskMonitor(void *pvParams) { for (;;) { UBaseType_t watermark = uxTaskGetStackHighWaterMark(NULL); printf("[MON] Stack libre min: %lu words\n", watermark); // Si watermark < 20 → stack trop petite, risque d'overflow vTaskDelay(pdMS_TO_TICKS(2000)); } }
Le 4ème argument de xTaskCreate est en words (4 octets sur ARM Cortex-M). Passer 128 donne 512 octets de pile — souvent insuffisant. Commencer à 256 et ajuster selon le watermark.
vTaskDelay() ou attendre sur une queue/sémaphore — sinon elle monopolise le CPU et affame les tâches de priorité inférieure.vTaskDelete(NULL) pour se supprimer proprement.pvParams plutôt que des globales — code plus testable, plus modulaire, et thread-safe par construction.xTaskCreate retourne pdPASS ou errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY. Ne jamais ignorer silencieusement.