Guide technique FreeRTOS pour la programmation

Horloge système (SysTick)

Le noyau temps réel FreeRTOS utilise le compteur de ticks de l'horloge système pour mesurer le temps. À chaque incrémentation du tick, le noyau vérifie si des tâches bloquées doivent être réveillées. Lors d'une interruption de tick, une tâche réveillée peut avoir une priorité plus élevée que la tâche interrompue.

États des tâches

  • Exécution : La tâche est en cours d'exécution sur le processeur.
  • Prête : La tâche est prête à s'exécuter mais attend son tour.
  • Bloquée : La tâche attend un événement, comme une temporisation ou une ressource.
  • Suspendue : La tâche est volontairement suspendue par l'application.

Commutation de tâches

Préemptif

Les tâches de priorité supérieure peuvent interrompre immédiatement les tâches de priorité inférieure.

Par tranches de temps

Les tâches de même priorité s'exécutent à tour de rôle pendant des intervalles de temps définis.

Listes de tâches

Liste prête : pxReadyTasksList[priorité]

Sur une architecture 32 bits, un registre de 32 bits représente les priorités de 0 à 31, où 31 est la plus élevée.

Liste bloquée : pxDelayedTaskList

Liste suspendue : xSuspendedTaskList

Commutation de contexte

La commutation de contexte se produit lorsqu'une tâche de priorité supérieure devient prête ou lors d'une temporisation.

Fin de tâche

Auto-suppression avec vTaskDelete(NULL)

La tâche se spuprime elle-même, et la tâche idle libère la structure TCB.

Suppression externe avec vTaskDelete(handle)

Une tâche supprime une autre tâche, puis libère la structure TCB.

Tâche idle

Une tâche idle est créée automatiquement avec la priorité 0 (la plus basse). Si des tâches se créent et suppriment fréquemment, la tâche idle doit avoir le temps de s'exécuter pour libérer les ressources. Utilisez vTaskDelay() au lieu de mDelay() pour permettre la planification.

Synchronisation et exclusion mutuelle

Lorsque plusieurs tâches accèdent à une ressource partagée comme le port série, une synchronisation est nécessaire pour éviter les conflits. Les mécanismes inclus : notifications de tâches, files d'attente, groupes d'événements, sémaphores et mutex. Ils offrent des opérations comme acquisition/libération, blocage/réveil et temporisation.

Notifications de tâches

Basées sur une valeur dans le TCB de la tâche, pouvant être écrasées. La notification est envoyée à une tâche spécifique et ne peut être lue que par elle-même.

Files d'attente

Permettent de stocker plusieurs éléments de données. Les tâches et les interruptions peuvent écrire et lire des données.

Création

QueueHandle_t handleFile = xQueueCreate(5, sizeof(int32_t));

Réinitialisation

xQueueReset(handleFile);

Écriture

xQueueSend(handleFile, &valeurDonnee, delaiAttente);
xQueueSendToBack(handleFile, &valeurDonnee, delaiAttente);
xQueueSendToBackFromISR(handleFile, &valeurDonnee, &reveilPrioritaire);
xQueueSendToFront(handleFile, &valeurDonnee, delaiAttente);
xQueueSendToFrontFromISR(handleFile, &valeurDonnee, &reveilPrioritaire);

Lecture

xQueueReceive(handleFile, &bufferReception, delaiAttente);
xQueueReceiveFromISR(handleFile, &bufferReception, &tacheReveillee);

Suppression

vQueueDelete(handleFile);

Consultation

uxQueueMessagesWaiting(handleFile);
uxQueueSpacesAvailable(handleFile);

Écrasement et aperçu

Pour les files de longueur 1, xQueueOverwrite() écrase les données sans blocage. xQueuePeek() copie les données sans les supprimer.

xQueueOverwrite(handleFile, &valeurDonnee);
xQueueOverwriteFromISR(handleFile, &valeurDonnee, &reveilPrioritaire);
xQueuePeek(handleFile, &bufferReception, delaiAttente);
xQueuePeekFromISR(handleFile, &bufferReception);

Exemple d'utilisation de base

QueueHandle_t fileDonnees;

int main(void) {
    prvConfigurationMateriel();
    fileDonnees = xQueueCreate(5, sizeof(int32_t));
    if (fileDonnees != NULL) {
        xTaskCreate(fonctionEnvoyeur, "Env1", 1000, (void *)100, 1, NULL);
        xTaskCreate(fonctionEnvoyeur, "Env2", 1000, (void *)200, 1, NULL);
        xTaskCreate(fonctionRecepteur, "Recept", 1000, NULL, 2, NULL);
        vTaskStartScheduler();
    }
    return 0;
}

static void fonctionEnvoyeur(void *parametres) {
    int32_t valeurEnvoyee = (int32_t)parametres;
    for (;;) {
        if (xQueueSendToBack(fileDonnees, &valeurEnvoyee, 0) != pdPASS) {
            printf("Échec d'envoi à la file.\r\n");
        }
    }
}

static void fonctionRecepteur(void *parametres) {
    int32_t valeurRecue;
    const TickType_t attente = pdMS_TO_TICKS(100UL);
    for (;;) {
        if (xQueueReceive(fileDonnees, &valeurRecue, attente) == pdPASS) {
            printf("Reçu = %d\r\n", valeurRecue);
        } else {
            printf("Échec de réception de la file.\r\n");
        }
    }
}

Exemple avec identification de source

typedef enum { idVitesseMoteur, idConsigneVitesse } SourceID_t;
typedef struct { SourceID_t idDonnee; int32_t valeurDonnee; } Donnee_t;

static const Donnee_t donneesEnvoi[2] = {
    { idVitesseMoteur, 10 },
    { idConsigneVitesse, 5 }
};

QueueHandle_t filePartagee;

int main(void) {
    prvConfigurationMateriel();
    filePartagee = xQueueCreate(5, sizeof(Donnee_t));
    if (filePartagee != NULL) {
        xTaskCreate(envoyeurDonnees, "TacheCAN", 1000, (void *)&donneesEnvoi[0], 2, NULL);
        xTaskCreate(envoyeurDonnees, "TacheHMI", 1000, (void *)&donneesEnvoi[1], 2, NULL);
        xTaskCreate(recepteurDonnees, "Recept", 1000, NULL, 1, NULL);
        vTaskStartScheduler();
    }
    return 0;
}

static void envoyeurDonnees(void *parametres) {
    for (;;) {
        if (xQueueSendToBack(filePartagee, parametres, pdMS_TO_TICKS(100UL)) != pdPASS) {
            printf("Échec d'envoi.\r\n");
        }
    }
}

static void recepteurDonnees(void *parametres) {
    Donnee_t donneeRecue;
    for (;;) {
        if (xQueueReceive(filePartagee, &donneeRecue, 0) == pdPASS) {
            if (donneeRecue.idDonnee == idVitesseMoteur) {
                printf("CAN, Vitesse = %d\r\n", donneeRecue.valeurDonnee);
            } else {
                printf("HMI, Consigne = %d\r\n", donneeRecue.valeurDonnee);
            }
        }
    }
}

La planification des tâches montre que les tâches de haute priorité remplissent la file, puis la tâche de lecture consomme les données, permettant la réécriture.

Groupes d'événements

Chaque événement est représenté par un bit (1 pour actif, 0 pour inactif). Ils permettent de signaler des combinaisons d'événements sans transférer de données, avec un effet de diffusion réveillant toutes les tâches en attente.

Sémaphores

Basés sur une valeur de comptage. L'acquisition décrémente le compteur, la libération l'incrémente.

Création d'un sémaphore binaire

SemaphoreHandle_t handleSemBinaire = xSemaphoreCreateBinary();
SemaphoreHandle_t handleSemBinaireStatique = xSemaphoreCreateBinaryStatic(&tamponStatique);

Création d'un sémaphore à comptage

SemaphoreHandle_t handleSemCompteur = xSemaphoreCreateCounting(10, 0);
SemaphoreHandle_t handleSemCompteurStatique = xSemaphoreCreateCountingStatic(10, 0, &tamponStatique);

Suppression

vSemaphoreDelete(handleSemaphore);

Acquisition et libération

xSemaphoreGive(handleSemaphore);
xSemaphoreGiveFromISR(handleSemaphore, &reveilPrioritaire);
xSemaphoreTake(handleSemaphore, delaiAttente);

Mutex

Un mutex (exclusion mutuelle) est un verrou binaire où seul le possesseur peut le libérer. Il protège les ressources critiques contre les accès non atomiques et garantit la réentrance des fonctions. L'initialisation est à 1, l'acquisition bloque les autres tâches, et la libération réveille les tâches en attente.

Portage de FreeRTOS

Obtention du code source

Le code source officiel est disponible sur le site de FreeRTOS pour la portabilité vers différentes architectures matérielles.

Étiquettes: FreeRTOS C RTOS systèmes embarqués files d'attente

Publié le 27 juin à 23h06