LOCODUINO

La bibliothèque ScheduleTable

.
Par : Jean-Luc

DIFFICULTÉ :

La gestion du temps a déjà fait l’objet d’un article « Comment gérer le temps dans un programme ? ». Les applications pour nos réseaux nécessitent fréquemment d’enchaîner des actions avec des temporisations entre elles, actionner un moteur d’aiguillage à solénoïde, gérer des animations lumineuses ou mécaniques, etc. Mais force est de constater que les choses restent complexes quand il s’agit d’ordonnancer dans le temps de nombreuses tâches que l’on veut multiplexer sur le même Arduino. Les programmes deviennent rapidement des usines à gaz dont la complexité devient difficilement maîtrisable.

Devant ce constat, nous vous proposons une bibliothèque originale permettant d’ordonnancer des actions dans le temps. Nous espérons qu’elle vous facilitera la tâche.

L’article a été mis à jour avec la version 1.4 de la bibliothèque. La version 1.4 apporte la compatibilité totale avec les ATTiny [1] avec l’accès aux actions sous forme d’objet. Elle permet également de retirer des actions en cours d’exécution.

Qu’est ce qu’une Schedule Table

Une Schedule Table ou table d’ordonnancement en français est une structure permettant d’ordonnancer des actions dans le temps. Elle possède une période, c’est à dire une durée, et peut recevoir une ou plusieurs actions. Par exemple on peut représenter une table d’ordonnancement de période 32 comme ceci :

PNG - 4.9 kio

On dispose ensuite des actions aux dates voulues à l’intérieur de la table d’ordonnancement. Supposons par exemple que nous voulions faire une table d’ordonnancement pour un feu routier. Le feu reste rouge pendant 16 secondes. Puis il passe au vert et le reste pendant 14 secondes. Puis il passe à l’orange et le reste pendant 2 secondes. Les actions sont allumerRouge(), allumerVert() et allumerOrange() et sont disposées comme suit dans la table d’ordonnancement.

PNG - 19.1 kio

Ensuite, pour effectuer le cycle du feu, il suffit de répéter la table d’ordonnancement à l’infini, le 0 de la répétition suivante coïncidant avec le 32 de la répétition courante. Comme ceci :

PNG - 22.8 kio

Voilà nous avons posé les bases du fonctionnement. La bibliothèque ScheduleTable permet de définir des tables d’ordonnancements, de donner leur période, d’y placer des actions, de les démarrer pour un nombre de répétitions infini, de les démarrer pour un nombre de répétitions fixé, et de les stopper.

Créer une table d’ordonnancement

Après avoir importé la bibliothèque, ce qui insère un #include <ScheduleTable.h> en tête de votre sketch, nous pouvons créer autant de tables d’ordonnancement que nous le désirons comme ceci :

SchedTable<1> cycleClignote(500);

Le <1> est le nombre maximum d’actions que peut contenir la table d’ordonnancement, ici 1, et l’argument est sa période, en millisecondes, ici 500, soit 0,5 secondes.

Ajouter des actions

Deux types d’actions sont possibles : soit l’appel d’une fonction qui ne renvoie aucune valeur et ne prend aucun argument, soit un objet instance d’une classe dérivée de ScheduleTableAction.

Voyons tout d’abord les actions qui sont des appels de fonction. Définissons une fonction pour changer l’état de la fameuse DEL de la broche 13 :

void toggleDEL13()
{
    digitalWrite(13,! digitalRead(13));
}

Nous pouvons ajouter des actions dans la table d’ordonnancement à concurrence du nombre maximum. Ceci est fait dans setup(). Par exemple, pour changer l’état de la DEL 13 après 250ms, on écrira :

cycleClignote.at(250, toggleDEL13);

Si la date d’une action est plus grande que la période de la table d’ordonnancement, l’action ne sera pas exécutée.
 

Démarrer la table d’ordonnancement

Après avoir préparé la table d’ordonnancement, il faut la démarrer, comme ceci :

cycleClignote.start();

Enfin, pour que les tables d’ordonnancement qui ont été préparées et démarrées fonctionnent, il faut les titiller dans loop() comme ceci :

ScheduleTable::update(); // met à jour toutes les tables d'ordonnancement

Et voici le code complet du programme :

#include <ScheduleTable.h>
 
SchedTable<1> cycleClignote(500);
 
void toggleDEL13()
{
  digitalWrite(13, !digitalRead(13));
}
 
void setup() {
  pinMode(13, OUTPUT);
  cycleClignote.at(250,toggleDEL13);
  cycleClignote.start();
}
 
void loop() {
  ScheduleTable::update();
}

Gérer plusieurs tables simultanément

Beaucoup de choses pour faire clignoter une DEL, me direz vous. Nous allons compliquer les choses pour montrer l’intérêt de la bibliothèque. Supposons maintenant que vous vouliez faire un feu d’avertissement de chantier, du genre de ceux qui émettent une paire de flashs toutes les secondes en plus de la DEL 13. Connectons cette DEL sur la broche 8 et écrivons une fonction permettant de changer son état :

void toggleDEL8()
{
  digitalWrite(8, !digitalRead(8));
}

Fixons la période à 2 secondes. 500 ms après le démarrage de la table d’ordonnancement, la DEL est allumée, éteinte 30 ms après puis rallumée 200 ms après et enfin éteinte 30 ms après. Les instants sont donc : 500, 530, 730 et 760. Construisons notre table d’ordonnancement, nous avons besoins de 4 actions :

SchedTable<4> cycleFlash(2000); // 4 actions, période de 2000 ms

Les actions sont ensuite positionnées :

cycleFlash.at(500, toggleDEL8);
cycleFlash.at(530, toggleDEL8);
cycleFlash.at(730, toggleDEL8);
cycleFlash.at(760, toggleDEL8);

puis la table d’ordonnancement démarrée :

cycleFlash.start();

Voici le programme complet :

#include <ScheduleTable.h>
 
SchedTable<4> cycleFlash(2000);
SchedTable<1> cycleClignote(300);
 
void toggleDEL13()
{
  digitalWrite(13, !digitalRead(13));
}
 
void toggleDEL8()
{
  digitalWrite(8, !digitalRead(8));
}
 
void setup() {
  pinMode(8, OUTPUT);
  pinMode(13, OUTPUT);
  
  cycleFlash.at(500, toggleDEL8);
  cycleFlash.at(530, toggleDEL8);
  cycleFlash.at(730, toggleDEL8);
  cycleFlash.at(760, toggleDEL8);
  
  cycleClignote.at(150, toggleDEL13);
  
  cycleFlash.start();
  cycleClignote.start();
}

void loop() {
  ScheduleTable::update();
}

Il est donc possible de préparer et de démarrer de multiples tables d’ordonnancement de manière indépendante les unes des autres.

Utilisation d’un objet au lieu d’une fonction

Nous venons de voir des actions qui sont des appels de fonction. Il est également possible de rendre un objet cible de l’action. Dans ce cas, la table d’ordonnancement appelle la méthode action de l’objet. La classe de l’objet doit hériter de la classe ScheduleTableAction comme ceci :

class Led : public ScheduleTableAction
{
private:
  byte mPin;
 
public:
  Led(byte pin) : mPin(pin) {}
   
  void begin()
  {
    pinMode(mPin, OUTPUT);
    digitalWrite(mPin, LOW);
  }
 
  virtual void action()
  {
    digitalWrite(mPin, !digitalRead(mPin));
  }
};

Vous pouvez vous référer à la série d’article de Thierry : « Le monde des objets (1) », « Le monde des objets (2) » et « Le monde des objets (3) » sur la programmation objet.

Nous pouvons ensuite instancier un objet de type Led et une table d’ordonnancement pour la faire clignoter :

Led led13(13);
SchedTable<1> clignote(500);

puis démarrer la DEL, ajouter l’action et démarrer la table dans setup()

void setup() {
  led13.begin();
  clignote.at(250, led13);
  clignote.start();
}
Notez bien que l’action est un objet, cet objet doit continuer d’exister après qu’il ait été ajouté à la Schedule Table. En effet, la Schedule Table ne mémorise qu’un pointeur vers cet objet. Si l’objet est détruit par votre programme parce qu’il s’agissait par exemple d’une variable locale à une fonction qui s’est terminée, la ScheduleTable tentera d’exécuter l’action d’un objet qui n’existe plus et ceci conduira à un plantage du programme.

Les fonctions de la bibliothèque

Déclaration d’une table d’ordonnancement

Une table d’ordonnancement est une instance de la classe SchedTable. Une table d’ordonnancement est déclarée de la manière suivante :

SchedTable<10> maTable(10000);

ou 10 est le nombre d’actions que peut contenir la table au maximum et 10000 la période de la table en millisecondes. On peut également changer l’unité de temps ce qui peut être pratique si la table gère des durées importante. Dans ce cas un 3e argument donne le nombre de millisecondes que dure l’unité de temps. Ainsi la déclaration suivante :

SchedTable<3> cycleFeuTricolore(32, 1000);

crée une table d’ordonnancement de 3 actions, les changements d’état du feu, dont la période est de 32 unités de temps, une unité de temps valant 1000 millisecondes soit 1 seconde. Les dates des actions seront données en secondes.

la période est limité à 231-1 divisé par l’unité de temps, soit un peu plus de 2 milliards de millisecondes, soit 24 jours.

Positionnement des actions

Une action est positionnée dans le temps à une date relative au début de la table, et en utilisant l’unité de temps de la table. Pour cela, on utilise la méthode at. Le premier argument est la date où l’action est positionnée.

la date d’une action ne peut pas excéder 231-1 divisé par l’unité de temps, soit un peu plus de 2 milliards de millisecondes, soit 24 jours.

Le second argument est l’action qui peut être soit une fonction, soit un objet dérivé de la classe ScheduleTableAction. Si maFonction est une fonction de la forme void maFonction(), on peut écrire :

maTable.at(4765,maFonction); // pas de parenthèses après maFonction !

maFonction sera appelée quand la date 4765 sera arrivée.

De même si monObjet est une instance d’une classe dérivée de la classe ScheduleTableAction, on peut écrire :

maTable.at(4765,monObjet);

la méthode action() de cet objet sera appelée quand la date 4765 sera arrivée.

Démarrage et arrêt d’une table d’ordonnancement

La méthode start(...) permet de démarrer une table d’ordonnancement. Deux formes sont possibles :

maTable.start();

démarre maTable pour un nombre de périodes infini.

maTable.start(n);

démarre maTable pour n périodes.

Il est également possible de stopper une table d’ordonnancement :

maTable.stop();

Changement de la période

La période d’une table d’ordonnancement peut être changée au moyen de la méthode setPeriod(...) qui prend comme unique argument la nouvelle période de la table :

maTable.setPeriod(15000);

Remise à 0

Les actions peuvent être toutes retirées d’une table d’ordonnancement en utilisant la méthode empty() :

maTable.empty();

On peut ensuite mettre d’autres actions à des dates différentes dans la table.

Suppression d’une ou plusieurs actions

Une action ou plusieurs action peuvent être supprimées de la table en utilisant la méthode removeAt(...). Cette méthode prend pour argument la date où se trouve(nt) la ou les actions à supprimer.

maTable.removeAt(10);

supprime toutes les actions situées à la date 10.

La bibliothèque ScheduleTable est maintenant disponible via le gestionnaire de bibliothèques de l’IDE Arduino. Dans le menu Croquis, Inclure une bibliothèque, choisissez Gérer les bibliothèques. Dans la case Filtrez votre recherche, tapez ScheduleTable.

[1Testé sur ATTiny 45 et ATTiny 84.

14 Messages

  • La bibliothèque ScheduleTable 7 avril 2015 13:21, par Savignyexpress

    Merci Jean-Luc pour la présentation de cette bibliothèque fort intéressante !

    Cette librairie permet le multi-tâches coopératif non pré-emptif. En conséquence, il faut donc que les fonctions appelées rendent la main très rapidement. Si l’une d’elles part en boucle, les timings ne seront plus respectés.

    Répondre

    • La bibliothèque ScheduleTable 8 avril 2015 09:35, par Jean-Luc

      Remarque pertinente SavignyExpress. Effectivement si l’une des actions a une durée d’execution plus grande que le temps qui sépare son début de l’action suivante, l’action suivante démarrera tardivement.

      Donc pas de delay(...) Et surtout pas de boucle infinie.

      Répondre

  • Bibliothèque ScheduleTable 18 septembre 2016 07:19, par Jean-Louis

    Bonjour,

    Je découvre votre site web. Un seul mot : bravo.

    Cdlt
    Jean-Louis

    Répondre

  • Tout est dans le titre, je suis en train de faire un programme qui gère des lampe brancher sur relais . Les adresses temporel peuvent être modifier via le terminal sans reset l’arduino. Le problème c’est que ça marche très bien lors d’une première passe mais impossible de modifier la table ou une deuxième fois ou de la suprimer pour mettre de nouvelles adresses.
    Repasser par la création de la table une deuxième fois lors du programme fonctionne qu’une seule fois, la deuxième fait bugé le update. Mon programme rest bloqué dessus. C’est très bizare.

    J’ai retourner le truc dans tout les sens, impossible de faire ce que je veux, (stopper la table avant de remettre start, l’enfermer dans une boucle qui ne joue qu’une fois...)

    soit c’est pas possible soit le update bug et reste bloqué.

    Aurais-tu une solution pour pour suprimé ou modifier les adresses temporel d’une table sans reset l’arduino ? merci :) Sinon super bibliothèque !
    _

    Répondre

    • Bonjour,

      Il n’est pas prévu d’enlever des actions d’une Schedule Table ni de la détruire. En fait je n’avais pas prévu l’usage que vous en faites. Mais par contre je peux ajouter ce qu’il faut pour pouvoir le faire :

      • vider une Schedule Table de ses actions
      • Enlever une action en donnant sa date
      • supprimer et recréer une Schedule Table

      Répondre

      • Oh merci, ce serait super !
        _Au début je cherchais à coder en utilisant la technique du Millis(), mais c’était rapidement compliquer à s’organiser avec 4 relais tous modifiables. En fouillant sur le net, j’ai pus trouver votre librarie qui simplifiais beaucoup le programme. Tout marchais jusqu’à ce que je rencontre ces petit problème cités. On va dire que je suis niveau débutant avancée en programtion et éléctronique arduino. J’ai même cherché à l’intérieur de votre bibliothèque pour comprendre le programme ^^.
        _Donc oui ce super cool de rajouter des fonctions à votre librairie mais ça risque de vous prendre beaucoup de temps non ? Peux êtree pouvez-vous vous épargner cet peine si vous connaissez une autre libraire de gestion de temps adapté à ce que je souhaite faire ? Ou une autre méthode ? :)

        Cordialement
        _

        Répondre

        • Beaucoup de temps, non. deux heures maximum je pense. Disons que ce soir c’est fait si j’arrive à trouver 2h :)
          Non je ne connais pas d’autres bibliothèques de ce genre. J’ai cherché avant de la faire, sans succès.

          (Édit : et je n’ai pas trouvé les 2h, demain soir ça sera fait)

          Répondre

  • La bibliothèque ScheduleTable 7 janvier 2019 22:46, par Jean-Luc

    Bonsoir,

    C’est fait pour removeAt et empty. Je n’ai pas fait de release car je souhaite améliorer mais l’API ne changera pas. Vous pouvez récupérer cette préversion sur GitHub.

    Répondre

    • La bibliothèque ScheduleTable 9 janvier 2019 15:14, par alexis

      Bonjour,
      Écoutez merci beaucoup, je viens d’incorporer ça dans mon code et ça marche parfaitement. C’est vraiment pratique et génial ! Je pense que ça pourra intéresser beaucoup d’autres personnes que moi, car la gestion du temps en C est vraiment pénible. On en arrive rapidement à devoir oublier delay() et on se perd rapidement avec des boucles Milli(). Donc pouvoir changer à volonté en cours de programmes cet sheduleTable déjà très ergonomique à utiliser. Ça la rend indispensable de mon point de vue.
      Donc encore une fois merci beaucoup, en attendant la nouvelle version officiel.
      Cordialement.

      Répondre

      • La bibliothèque ScheduleTable 11 janvier 2019 15:43, par Jean-Luc

        Bonjour,

        Merci pour les compliments :-)

        J’ai fait la release 1.4. Elle devrait apparaître dans le gestionnaire de bibliothèques. On peut maintenant détruire et allouer dynamiquement des Schedule Table.

        Répondre

  • La bibliothèque ScheduleTable 5 janvier 2020 04:19, par Michel

    Bonjour et merci pour le partage. Je suis un néophyte de 67 ans. J’ai un projet en cours d’automatisme de porte cochère. La fonction ouvrir() utilise beaucoup delay parce que j’alimente avec une batterie de 2 Ah et je démarre chaque moteur séparément et surtout le contrôle des moteurs(blocage) doit être permanent.
    Je ne veux pas abuser mais j’ai beau relire, je ne vois pas comment intégrer votre librairie, si c’est compatible. Votre exemple "closure" semble m’indiquer que oui ?
    La fonction :

    void ouvrir() {
      action = true;
      servoB.attach(7);  //servo bas
      servoH.attach(8);  //servo haut
      delay(100); servoB.write(0); // déverrouille le bas;
      delay(100); servoH.write(0); // déverrouille le haut
      delay(100); digitalWrite (pinO1, 1); //démarre moteur1
      delay(100); moteur1 = true; //retarde la surveillance courant moteur
      delay(1900); digitalWrite (pinO2, 1); //démarre moteur2
      delay(300); moteur2 = true;//délai 300 important
      delay(100); servoH.write(170); // relache le haut
      delay(2000); servoB.detach();  //servo bas
      servoH.detach();  //servo haut
    } // fin ouvrir» 

    Voir en ligne : La bibliothèque ScheduleTable

    Répondre

    • La bibliothèque ScheduleTable 5 janvier 2020 10:19, par Jean-Luc

      Bonjour,

      Voici :

      #include <Servo.h>
      #include <ScheduleTable.h>
      
      SchedTable<9> porte(5000);
      Servo servoB;
      Servo servoH;
      
      bool moteur1 = false;
      bool moteur2 = false;
      bool action = false;
      
      const byte pinO1 = 3;
      const byte pinO2 = 4;
      
      void setup()
      {
        porte.at(1,    [] { action = true; servoB.attach(7); servoH.attach(8); } );
        porte.at(100,  [] { servoB.write(0); } );
        porte.at(200,  [] { servoH.write(0); } );
        porte.at(300,  [] { digitalWrite(pinO1, HIGH); } );
        porte.at(400,  [] { moteur1 = true; } );
        porte.at(2300, [] { digitalWrite(pinO2, HIGH); } );
        porte.at(2600, [] { moteur2 = true; } );
        porte.at(2700, [] { servoH.write(170); } );
        porte.at(4700, [] { servoB.detach(); servoH.detach(); } );
      }
      
      void ouvrir()
      {
        porte.start(1);
      }
      
      bool boutonPresse()
      {
        return true;
      }
      
      void loop()
      {
        if (boutonPresse()) ouvrir();
        
        ScheduleTable::update();
      }

      Répondre

  • La bibliothèque ScheduleTable 5 janvier 2020 15:48, par Michel

    Un grand merci. Y a plus qu’à...
    Après relecture, j’avais compris start(1) qui permet de jouer la période une seule fois et à la demande.
    J’ai fait aussi le test avec les closures... ça fonctionne aussi très bien sans se casser la tête avec millis().
    Super cette lib et pas seulement pour un débutant. ☺

    Voir en ligne : https://www.locoduino.org/spip.php?...

    Répondre

  • La bibliothèque ScheduleTable 6 juin 2021 18:10, par Jean

    Bonjour,
    Merci pour cette bibliothèque qui est grandement utile quand on ne manie que très peu le millis, cela simplifie vachement les choses.

    Cependant, il n’est pas très facile de jongler avec le schedule table, si l’on veut des durée de plusieurs heures par tranche de 30 minutes, y aurait-il la possibilité de déclarer :
    const unsigned long SECOND = 1000 ;
    const unsigned long MINUTE = 60 * SECOND ;
    const unsigned long HOUR = 3600 * SECOND ;
    const unsigned long TRENTEMINUTES = 30 * 60 * SECOND ;

    ...Et ensuite se servir des valeurs "SECOND ; MINUTE"..ect ? Car j’ai essayé mais cela ne semble pas possible.

    Cordialement.

    Répondre

Réagissez à « La bibliothèque ScheduleTable »

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.

Lien hypertexte

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)

Rubrique « Bibliothèques »

Les derniers articles

Les articles les plus lus