Au-delà de la fonction delay() : maitrises le TEMPS RÉEL avec Arduino

Au-delà de la fonction delay() : maitrises le TEMPS RÉEL avec Arduino
Do not index
Do not index
Hide CTA
Hide CTA
Video preview

Qu’est-ce qu’un système embarqué ?

Les composants matériel et logiciel de base d'un système embarqué dépendent de l'application précise pour laquelle il est conçu. Cependant, en général, un système embarqué comprend les éléments suivants :
Matériel :
  • Microcontrôleur : c'est le cerveau du système embarqué et il s'occupe de toutes les tâches de calcul et de contrôle.
  • Mémoire : elle permet de stocker les données et le code exécutable du système embarqué.
  • Des périphériques d'entrée/sortie (E/S) : tels que des capteurs, des afficheurs, des claviers, des boutons, etc., qui permettent au système embarqué de recevoir des données et de communiquer avec l'extérieur.
  • Alimentation : elle fournit de l'électricité au système embarqué.
Logiciel :
  • Système d'exploitation : il gère les ressources du système embarqué et assure la communication entre les différents composants matérielle.
  • Langage de programmation : il permet d'écrire le code qui sera exécuté par le microcontrôleur.
  • Outils de développement : ils facilitent la création et le débogage du code.
Ces composants matériel et logiciel de base sont souvent complétés par d'autres éléments en fonction des besoins spécifiques du système embarqué.

Et dans un contexte temps-réel ?

La notion de temps réel concerne la capacité d'un système à respecter des délais précis dans l'exécution de ses tâches.
Pour être en mesure de gérer le temps réel dans les systèmes embarqués, il est important de prendre en compte plusieurs facteurs :
  • Les contraintes matérielles du système : le matériel utilisé (microcontrôleur, mémoire, capteurs, etc.) doit être capable de répondre aux exigences en termes de vitesse et de précision.
  • Les contraintes logicielles du système : le système d'exploitation et le langage de programmation utilisés doivent être adaptés aux exigences en termes de temps réel et permettre de gérer efficacement les ressources du système.
  • Les exigences en termes de fiabilité et de sécurité : il est important que le système soit fiable et sécurisé afin de garantir la qualité et la sécurité des résultats obtenus.
Pour mettre en place un système embarqué capable de gérer le temps réel, il est également important de disposer d'outils de développement adaptés et de suivre les bonnes pratiques de développement en matière de temps réel.

Le problème avec la fonction delay()

delay() est une fonction dite “bloquante” et dans un environnement temps réel, cela peut causer des problèmes en empêchant le système de réagir en temps et en heure aux événements en cours. En effet, ces fonctions ont pour fâcheuse habitude d’occuper toutes les ressources de traitement et empêchent ainsi, toute autre tâche de s'exécuter.
Par exemple, si une fonction bloquante est utilisée dans un système de contrôle de véhicule autonome, elle peut ralentir la réaction du véhicule à son environnement, ce qui peut mettre en danger la sécurité des passagers et des personnes environnantes.
Pour éviter ce genre de problème, il est important de mettre en place des mesures pour gérer efficacement les conflits d'accès aux ressources de traitement.

Solution avec Arduino

Sur Arduino, les fonctions millis() et micros() permettent de mesurer le temps écoulé, respectivement, en millisecondes et microsecondes, depuis que la carte a été allumée.
En s’appuyant sur cette information, voici une première solution abordée dans la vidéo fesant usage de millis()
#define PIN_LED_1 8
#define PIN_LED_2 7

// Definie l'interval de déclenchement des taches
unsigned long interval_tache_1 = 2000; // 2 secondes
unsigned long interval_tache_2 = 3000; // 3 secondes

// Définie la duree d'une tache
unsigned long duree_tache_1 = 1000; // 1 seconde
unsigned long duree_tache_2 = 500; // 0.5 seconde

// Stock la date de fin en cours d'une tache
unsigned long fin_tache_1 = duree_tache_1;
unsigned long fin_tache_2 = duree_tache_2;

// Stock la date du prochain début d'une tache
unsigned long debut_tache_1 = 0;
unsigned long debut_tache_2 = 0;

unsigned long temps_ecoule = 0;

// Etat de la tache : en cours d'activation 
bool tache_1_activation = false;
bool tache_2_activation = false;

// Etat de la tache : active 
bool tache_1_en_cours = false;
bool tache_2_en_cours = false;

// Etat de la tache : faire un relevé des timings
bool relever_timing_1 = false;
bool relever_timing_2 = false;

char message[60];

void setup() {
    //Communication série
    Serial.begin(9600);

    //LEDs en mode OUTPUT
    pinMode(PIN_LED_1, OUTPUT);
    pinMode(PIN_LED_2, OUTPUT);
  
}


void loop() {

    //Collecte le temps ecoulé en début de boucle
    temps_ecoule = millis();

    //Observe si la date de début à été atteinte
    if(debut_tache_1 <= temps_ecoule){

        //Calcule la prochaine date de debut
        debut_tache_1 += interval_tache_1;

        //Active la log
        relever_timing_1 = true;

        //Met la tache "en cours d'activation"
        tache_1_activation = true;
    }

    if(debut_tache_2 <= temps_ecoule){
        debut_tache_2 += interval_tache_2;
        relever_timing_2 = true;
        tache_2_activation = true;
    }

    // Observe si la tache est "en cours d'activation"
    if(tache_1_activation){
        // Desactive le mode "en cours d'activation"
        tache_1_activation = false;

        //Calcule la date de fin de la tache
        fin_tache_1 = temps_ecoule + duree_tache_1;

        //Met la tache "en cours"
        tache_1_en_cours = true;

        //Allume la LED
        digitalWrite(PIN_LED_1, HIGH);
    }

    if(tache_2_activation){
        tache_2_activation = false;
        fin_tache_2 = temps_ecoule + duree_tache_2;
        tache_2_en_cours = true;

        digitalWrite(PIN_LED_2, HIGH);
    }

    // Observe si la tache est "en cours"
    if(tache_1_en_cours){

        // Observe si la tache est périmée
        if(fin_tache_1 <= temps_ecoule){
            //Eteint la LED
            digitalWrite(PIN_LED_1, LOW);

            //Désactive la tache
            tache_1_en_cours = false;
        }
    }

    if(tache_2_en_cours){
        if(fin_tache_2 <= temps_ecoule){
            digitalWrite(PIN_LED_2, LOW);
            tache_2_en_cours = false;
        }
    }

    // Observe si la log est active
    if(relever_timing_1){
        // Désactive la log
        relever_timing_1 = false;

        // Construit une chaine de caractere dynamic
        sprintf(message, "debut_tache_1 : %lu", debut_tache_1);

        // Envoie la log sur le canal série
        Serial.println(message);
    }

    if(relever_timing_2){
        relever_timing_2 = false;
        sprintf(message, "debut_tache_2 : %lu", debut_tache_2);
        Serial.println(message);
    }

}

Aller plus loin : librairie externe

Je vous recommande d’installer la librairie directement installable depuis l’IDE Arduino arduino-timer. Celle-ci vous facilitera la vie quant à la manipulation d’intervalles et timming dans vos projets.
#include <arduino-timer.h>
#define PIN_LED_1 8
#define PIN_LED_2 7

// Structure en vue de stocker tout les parametres propre a une Tache
struct Tache{
    int pin;
    int duree;
};

// Déclaration des 2 taches
Tache tache_1;
Tache tache_2;

// Déclaration du gestionnaire des taches : timer
// 3 constructeurs existent, celui ci est l'un d'eux.
// int : nombre de taches
// micros | millis : précision du timer
// type argument attendu lors de l'appel d'un callback.
Timer<4, millis, Tache> timer;

// Callback appellé afin d'eteindre la LED définie sur tache.pin
bool extinction_led(Tache tache){
    //Extinction de la LED
    digitalWrite(tache.pin, LOW);
    
    //Auto destruction de la tache en memoire.
    return false;
}

// Callback appellé afin d'allumer la LED définie sur tache.pin
bool allumage_led(Tache tache) {
    //Allumage de la LED
    digitalWrite(tache.pin, HIGH);

    //Instanciation d'une tache a executer dans tache.duree
    timer.in(tache.duree, extinction_led, tache);

    //Réinstanciation automatique de la tache
    return true;
}

void setup() {
    //LED setting mode OUTPUT
    pinMode(PIN_LED_1, OUTPUT);
    pinMode(PIN_LED_2, OUTPUT);

    //Initialisation des deux taches avec leur pin de raccordement et la durée de la tache
    tache_1 = {PIN_LED_1, 1000};
    tache_2 = {PIN_LED_2, 500};

    //Déclaration aupres du gestionnaire de taches de deux taches a executer a interval régulier
    timer.every(3000, allumage_led, tache_1);
    timer.every(2000, allumage_led, tache_2);

}

void loop() {
    //Appel constant de "l'horloge interne" du gestionnaire des taches pour la mise à jour et lancement des taches.
    timer.tick();
}
Cyril

Written by

Cyril

Hobbyiste en électronique, programmation et IA