Les outils de communication inter-tâches (IPC) de FreeRTOS — pattern producteur/consommateur, protection de ressources partagées, et les pièges classiques du multithreading.
| Outil | Usage | Bloquant ? | Données ? |
|---|---|---|---|
| xQueueSend | Envoyer une donnée | Optionnel (timeout) | Oui — copie par valeur |
| xQueueReceive | Recevoir une donnée | Oui (bloque si vide) | Oui — copie vers buffer |
| xQueuePeek | Inspecter sans retirer | Optionnel | Oui — non consommé |
| xSemaphoreTake/Give | Synchro ou mutex | Oui (bloque) | Non |
| xTaskNotify | Signal léger inter-tâches | Non (fire & forget) | 32 bits optionnels |
La queue est la primitive de communication de base. Elle transmet des données par valeur d'une tâche à une autre de manière thread-safe — pas de mutex nécessaire pour accéder à la queue elle-même.
// Dans main — créer la queue avant les tâches QueueHandle_t cmd_queue = xQueueCreate(5, sizeof(Command_t)); // Producteur — envoie des commandes void vTaskProducer(void *pvParams) { AppContext_t *ctx = (AppContext_t *)pvParams; Command_t cmd; uint32_t seq = 0; for (;;) { cmd.id = seq++; cmd.value = sensor_read(); // Envoyer — bloquer 100ms max si queue pleine if (xQueueSend(ctx->cmd_queue, &cmd, pdMS_TO_TICKS(100)) != pdTRUE) { console_print(ctx, "[PROD] Queue pleine!\n"); } vTaskDelay(pdMS_TO_TICKS(200)); } } // Consommateur — traite les commandes void vTaskConsumer(void *pvParams) { AppContext_t *ctx = (AppContext_t *)pvParams; Command_t cmd; for (;;) { // Bloquer indéfiniment jusqu'à réception if (xQueueReceive(ctx->cmd_queue, &cmd, portMAX_DELAY) == pdTRUE) { process_command(&cmd); } } }
Quand plusieurs tâches accèdent à une même ressource (stdout, bus I2C, buffer partagé), il faut garantir l'accès exclusif. Le mutex est un sémaphore binaire avec gestion de la priorité intégrée.
// console_print — impression thread-safe via mutex void console_print(AppContext_t *ctx, const char *msg) { // Prendre le mutex — bloquer si une autre tâche l'a if (xSemaphoreTake(ctx->mutex, pdMS_TO_TICKS(100)) == pdTRUE) { printf("%s", msg); // section critique — un seul thread à la fois xSemaphoreGive(ctx->mutex); // libérer immédiatement } // Si timeout : on perd le message — logique de dégradation gracieuse }
Si une tâche haute prio attend un mutex tenu par une tâche basse prio, et qu'une tâche moyenne prio préempte la tâche basse — la tâche haute est indirectement bloquée par la moyenne. Solution : utiliser xSemaphoreCreateMutex() (pas Binary) — FreeRTOS applique alors la Priority Inheritance automatiquement.
Le sémaphore binaire sert à la synchronisation entre une ISR et une tâche. L'ISR donne le sémaphore (signal rapide), la tâche le prend (traitement lourd hors interruption).
// Watchdog logiciel via sémaphore binaire SemaphoreHandle_t wdg_sem; void vTaskWorker(void *pvParams) { for (;;) { do_work(); xSemaphoreGive(wdg_sem); // signaler "je suis vivant" vTaskDelay(pdMS_TO_TICKS(500)); } } void vTaskWatchdog(void *pvParams) { const TickType_t timeout = pdMS_TO_TICKS(1500); // 3x la période for (;;) { // Attendre le signal — timeout si worker bloqué if (xSemaphoreTake(wdg_sem, timeout) != pdTRUE) { handle_watchdog_timeout(); // reset, log, alarm... } } }
// Inspecter le prochain élément sans le retirer de la queue // Utile pour décider si on veut le consommer ou laisser une autre tâche le faire Command_t next_cmd; if (xQueuePeek(ctx->cmd_queue, &next_cmd, 0) == pdTRUE) { if (next_cmd.priority == HIGH) { xQueueReceive(ctx->cmd_queue, &next_cmd, 0); // consommer handle_high_priority(&next_cmd); } // sinon laisser pour une autre tâche } // uxQueueMessagesWaiting — nombre d'éléments en attente uint32_t pending = uxQueueMessagesWaiting(ctx->cmd_queue); printf("Queue: %lu éléments en attente\n", pending);
xSemaphoreCreateMutex() pour protéger des ressources — il implémente la priority inheritance. Réserver CreateBinary() pour la synchronisation ISR/tâche.portMAX_DELAY sauf si on est certain que l'événement arrivera. Préférer un timeout explicite avec gestion de l'échec.