> Home > Français > Arduino > Librairies > jm_Scheduler
Cette librairie est le fruit d'un questionnement lors d'un concours de robotique: Comment gérer et contrôler des processus parallèles et indépendants avec Arduino?
Sans librairie additionnelle, la réponse est assez complexe et nécessite une programmation délicate
et coûteuse en effort de programmation: Chaque processus doit effectuer sa propre gestion cyclique
dans la fonction loop().
La réponse professionnelle serait sans doute l'usage de RTOS avec le modèle multitâche préemptif.
Cela induit une complexité de programmation avec les librairies non-réentrantes d'Arduino
ou avec la taille de stack très petite des Arduino Uno, Leonardo, etc....
Au final, le mode multitâche coopératif est bien plus approprié,
à la manière de nombreux logiciels de programmation d'applications modernes
comme NodeJS, Python, Lua...
En plus d'offrir une gestion multitâche coopératif presque native,
ces logiciels offrent également une gestion d'événements.
Générer des événements, on peut le faire également avec la librairie jm_Scheduler.
jm_Scheduler gère des coroutines
activées et réactivées à la manière de la fonction JavaScript setInterval(),
avec des fonctionnalités supplémentaires:
jm_Scheduler démarre immédiatement la coroutine et la répète périodiquement.coroutine peut être changée dynamiquement.coroutine peut être stoppée et puis redémarrée ultérieurement.coroutine peut être réveillée par une routine d'interruption.coroutine est associé un scheduler.schedulers n'est limité que par la mémoire à disposition.
La librairie officielle Scheduler
ne fonctionne que pour les processeurs SAM et SAMD (Arduino Due et Zero) et
planifie les tâches avec le modèle préemptif.
jm_Scheduler planifie les tâches avec le modèle coopératif
POUR TOUS LES MODÈLES D'ARDUINO, avec plus de fonctionnalités et
avec une gestion centralisée du temps des processus.
La fonction yield() qui suspend une tâche est implémentée.
La fonction startLoop() de la librairie officielle Arduino n'est pas implémentée
car elle n'est pas appropriée au principe du multitâche coopératif.
jm_Scheduler exécute les coroutines séquentiellement sur le stack du processeur.
Les règles de yield et resume sont les suivantes:
yield intervient immédiatement lorsque une coroutine prend fin (return final).coroutine sera redémarrée cycliquement selon la valeur de répétition.
yield peut être déclenché par l'appel de la fonction yield().coroutine sera résumée dès que plus aucune coroutine
ne demande une exécution immédiate.
coroutines doivent être implémentées globalement
ou à l'intérieur des coroutines en accolant le mot-clé static à la déclaration de la variable.
// This example schedules a coroutine every second
#include <jm_Scheduler.h>
jm_Scheduler scheduler;
void coroutine()
{
Serial.print('.');
}
void setup(void)
{
Serial.begin(9600);
scheduler.start(coroutine, TIMESTAMP_1SEC); // Start coroutine() immediately
// and repeat it every second
}
void loop(void)
{
yield();
}
Clock1.ino.
Cet exemple démontre les avantages de démarrer immédiatement une coroutine
d'affichage du temps avec une répétition périodique.
Clock2.ino et Clock3.ino qui présentent d'autres idées de comptage du temps.
Clock4.ino présente une autre technique utile de jm_Scheduler:
Changer dynamiquement de fonction de coroutine.
Beat1.ino et Beat2.ino présentent les interactions possibles entre 2 coroutines.
Wakeup1.ino démontre l'interaction possible entre une routine d'interruption
et une coroutine, et en implémentant un timeout dans une gestion d'événement.
timestamp (référence de temps) est généré par la fonction Arduino micros().
micros() d'un UNO ou Leonardo fonctionnant à 16MHz
retourne un timestamp avec une granularité de [4us].
jm_Scheduler ne requière aucun hardware spécifique, ni n'utilise aucune ressource dédiée.
La déclaration Arduino de la fonction micros() est la suivante:
unsigned long micros();
Regardez dans les références Arduino micros() pour les détails de la fonction.
timestamp est un compteur 32-bit en [us]
et redémarre à 0 environ toutes les 70 minutes (précisément 1h+11m+34s+967ms+296us).
typedef uint32_t timestamp_t;
#define timestamp_read() ((timestamp_t)micros())
#define TIMESTAMP_DEAD (0x01CA0000) // coroutine dead time [30s + 15ms + 488us]
#define TIMESTAMP_TMAX (0xFE35FFFF) // [1h + 11m + 4s + 951ms + 808us - 1]
#define TIMESTAMP_1US (1UL) // [1us]
#define TIMESTAMP_1MS (1000*TIMESTAMP_1US) // [1ms]
#define TIMESTAMP_1SEC (1000*TIMESTAMP_1MS) // [1s]
#define TIMESTAMP_1MIN (60*TIMESTAMP_1SEC) // [1 minute]
#define TIMESTAMP_1HOUR (60*TIMESTAMP_1MIN) // [1 hour]
timestamp_t est le type de toutes les valeurs timestamp.
timestamp_read() retourne la valeur instantanée timestamp.
Cette fonction peut aussi être utilisée dans les routines d'interruption pour dater leurs données.
TIMESTAMP_DEAD est le temps d'exécution maximum d'une coroutine
pour garantir un fonctionnement correct de jm_Scheduler.
Si la coroutine ne prend pas fin avant cette valeur, alors jm_Scheduler peut perdre la synchronisation des tâches (voir ci-dessous).
TIMESTAMP_TMAX est le temps maximum de planification d'une coroutine.
En pratique, n'utilisez pas de valeur de timestamp plus grande qu'une heure.
// start coroutine immediately
void start(voidfuncptr_t func);
// start coroutine immediately and repeat it at fixed interval
void start(voidfuncptr_t func, timestamp_t ival);
// start coroutine on time and repeat it at fixed interval
void start(voidfuncptr_t func, timestamp_t time, timestamp_t ival);
// stop coroutine, current or scheduled, remove it from chain
void stop();
// rearm current coroutine and set or reset interval
void rearm(timestamp_t ival);
// rearm current coroutine, change coroutine function and set or reset interval
void rearm(voidfuncptr_t func, timestamp_t ival);
// rearm coroutine asynchronously and set interval
void rearm_async(timestamp_t ival);
// rearm coroutine asynchronously, change coroutine function and set interval
void rearm_async(voidfuncptr_t func, timestamp_t ival);
// wakeup a scheduled coroutine (maybe repeated)
void wakeup();
// wakeup a scheduled coroutine (maybe repeated but only 1st wakeup_time is recorded)
void wakeup(uint32_t wakeup_time);
// read wakeup count, reset it and remove coroutine from wakeup chain
int wakeup_read();
start() démarre une coroutine,
immédiatement ou après une attente time,
avec ou sans répétition ival.
start() est en principe invoqué une fois ou
lorsque la coroutine est stoppée.
rearm() permet de changer les données du scheduler lorsque la coroutine est en exécution.
stop() interrompt toutes nouvelles exécutions d'une coroutine.
stop() peut être invoqué par la coroutine en exécution ou par d'autres processus.
Lorsque stop() est invoqué par la coroutine en exécution,
stop() ne provoque pas l'arrêt immédiat de la coroutine,
stop() suspend simplement les exécutions suivantes.
Pour stopper immédiatement une coroutine,
la coroutine doit invoquer stop() puis effectuer un return.
rearm() change les consignes de la variable scheduler.
Les nouvelles valeurs pendront effet à la fin d'exécution de la coroutine.
rearm() permet de changer l'intervalle d'exécution
ou de changer la fonction coroutine.
L'exécution de la prochaine instance de la coroutine sera déclenchée
de manière synchrone en additionnant ival au temps de déclenchement de l'instance précédente.
rearm_async() change les consignes de la variable scheduler.
Les nouvelles valeurs pendront effet à la fin d'exécution de la coroutine.
rearm_async() permet de changer l'intervalle de déclenchement de la prochaine instance,
et en option de changer la fonction coroutine.
L'exécution de la prochaine instance de la coroutine sera déclenchée
de manière asynchrone en additionnant ival au temps de fin d'exécution de l'instance précédente.
wakeup() réveille une coroutine en attente d'exécution.
Le nombre de réveils est comptabilisé.
wakeup_read() lit le compteur puis le réinitialise.
wakeup() peut être invoqué depuis une routine d'interruption.
wakeup_read() ne doit être invoqué que depuis la coroutine réveillée.
Le contrôle du scheduler implique au minimum la mise en place de la fonction yield()
dans la fonction Arduino loop().
Déclaration de la fonction yield():
static void yield();
yield() est la pierre angulaire du scheduler. Elle doit être invoquée aussi souvent que possible.
Prenez note que yield() est une méthode statique de la librairie jm_Scheduler.
Elle remplace la fonction weak native d'Arduino.
Le bon endroit pour invoquer yield() est dans la fonction Arduino loop().
Exemple:
void setup()
{
// ...
}
void loop()
{
yield();
// ...
}
yield() peut aussi être invoqué dans la fonction Arduino setup().
Exemple:
void setup(void)
{
// here, some jm_Scheduler variables initialized...
Serial.begin(9600);
while (!Serial)
{
// wait for USB Serial ready...
yield(); // split loop while()...
}
yield(); // split long setup()...
// continue setup()...
}
Remarques:
yield() peut aussi être invoqué depuis une coroutine.
yield() est automatiquement invoqué dans la fonction Arduino delay().
Pour garantir la bonne exécution de toutes les tâches coopératives,
le temps d'exécution des coroutines doit être le plus court possible.
Cette pratique est identique à celle d'une routine d'interruption.
Utilisez la fonction Arduino delay() pour mettre en pause une coroutine
ou la fonction yield() pour fractionner l'exécution d'une coroutine.
Une meilleure pratique est de fractionner le code d'une coroutine en plusieurs coroutines.
Les méthodes jm_Scheduler rearm() et rearm_async()
permettent de sérialiser l'exécution de plusieurs coroutines.
Ci-dessous sont listés quelques hacks qui peuvent être implémentés en modifiant le fichier jm_Scheduler.h:
timestamp peut être la fonction Arduino millis() en remplacement de micros().
timestamp à 16-bit.
De cette manière, toutes les comparaisons de timestamp seront accélérées avec un Arduino UNO ou Leonardo.
timestamp de 64-bit.