La bibliothèque ScheduleTable

. Par : Jean-Luc. URL : https://www.locoduino.org/spip.php?article116

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.