[Partage] Librairie ordonnanceur (scheduler)

Bonjour

Après le Mini scheduler, je vous propose une version nettement plus aboutie :

  • gestion intégrée du watchdog
  • possibilité d'avoir des tâches de fond
  • possibilité de surveillance de la charge CPU et de la quantité de ram disponible
  • activation / désactivation de tâches, voire de l'ordonnanceur lui-même
  • ...
    Le tout en restant sur une architecture logicielle simple et assez légère.

Bref je vous encourage vraiment à tester la bestiole, d'autant que c'est très facile : branchez une arduino au PC (aucun câblage requis), ouvrez le terminal série à 115200 bauds et testez les onze exemples fournis.
Chacun d'eux illustre une des fonctionnalités de cette bibliothèque.

Télécharger la bibliothèque ordonnanceur.h

Voici le header

//Ordonnanceur de tâches pour arduino
//Bricoleau 2016
//V1.0

#ifndef ordonnanceur_h
#define ordonnanceur_h

#include <Arduino.h>

//Définition du type de fontions associées aux tâches
typedef void (*fonctionVoid)();//correspond à toute fonction du style "void ma_fonction()"

//Une tâche est définie par :
//- une fonction à exécuter
//- une période entre deux exécutions (en millisecondes)
class tache
{
  public :
    //Constructeur
    tache(fonctionVoid fonction, uint32_t periode_ms);

    //Méthodes principales
    void changerFonction(fonctionVoid nouvelle_fonction);
    void changerPeriode(uint32_t nouvelle_periode_ms);
    void recaler(); //Voir notes explicatives plus bas

    //Méthodes secondaires
    void executer();
    uint32_t periode() const {return this->_periode;}
    uint32_t millis_prochaine_exec() const {return this->_millis_prochaine_exec;}

  private :
    fonctionVoid _fonction;
    uint32_t     _periode;
    uint32_t     _millis_prochaine_exec;
};

//L'ordonnanceur intègre la gestion du watchdog.
//Le watchdog est un dispositif hardware
//qui provoque un reset de l'arduino si une tâche a une durée anormale.
const uint8_t WATCHDOG_INACTIF    = 0;
const uint8_t WATCHDOG_1_SECONDE  = 1;
const uint8_t WATCHDOG_2_SECONDES = 2;
const uint8_t WATCHDOG_4_SECONDES = 3;
const uint8_t WATCHDOG_8_SECONDES = 4;

class ordonnanceur_c
{
  public :
    //Constructeur
    ordonnanceur_c();

    //Méthode principale, à appeler en fin de setup(), dont on ne ressort jamais sauf arrêt de l'ordonnanceur
    void lancer(const uint8_t watchdog = WATCHDOG_INACTIF);

    //Méthodes secondaires, appelables depuis une tâche
    void stopper();
    void reboot();
    bool estActif() const          {return this->_statut > 127;}

    void modifierWatchdog(const uint8_t watchdog);
    void desactiverWatchdog()      {this->modifierWatchdog(WATCHDOG_INACTIF);}
    const uint8_t watchdog() const {return this->_statut & 7;}
    bool watchdogInactif() const   {return this->watchdog() == WATCHDOG_INACTIF;}
    bool watchdogActif() const     {return this->watchdog() != WATCHDOG_INACTIF;}

    uint16_t ramDisponible() const {return this->_ramDisponible;} //Ram disponible hors exécution des tâches
    uint8_t  chargeCPU1s() const   {return this->_chargeCPU1s;}   //Pourcentage d'occupation CPU sur 1 seconde
    uint8_t  chargeCPU5s() const   {return this->_chargeCPU5s;}   //Pourcentage d'occupation CPU sur 5 secondes

  private :
    uint8_t  _statut, _chargeCPU1s, _chargeCPU5s;
    uint16_t _ramDisponible;
    uint32_t _tempo1s[2], _tempo5s[2];
    void gererWatchdog();
    void actualiserRamDisponible();
    void actualiserChargeCPU(uint32_t);
};

extern ordonnanceur_c ordonnanceur;

#endif

Ainsi que sa note explicative

/****************************************************************************************
//Notes :
//
//Ceci est un ordonnanceur coopératif basique : les tâches ne s'interrompent pas entre elles.
//
//La fonction associée à une tâche doit s'exécuter rapidement.
//   ==> Ne jamais utiliser l'instruction delay() !
//   Si une pause est nécessaire, terminer la fonction et gérer la suite du traitement lors de l'appel suivant.
//   Utiliser des variables statiques pour conserver l'état des données entre les appels.
//   Il est aussi possible de modifier la fonction associée à la tâche et/ou sa période, selon le contexte.
//   Voir les exemples fournis avec la bibliothèque.
//
//Utiliser des variables globales pour passer des données depuis une tâche vers une autre.
//   La communication entre tâches est asynchrone.
//
//Au démarrage, le premier appel à chaque tâche est effectué dès que possible.
//   Une fonction de type setup() peut être associée à la tâche pour sa première exécution,
//   puis être remplacée par une fonction de type loop().
//
//L'utilisation du watchdog permet de contourner les cas de bug conduisant à un blocage de l'arduino.
//   Elle est recommandée dans les programmes qui utilisent <Wire.h> ou <Ethernet.h>.
//
//Une tâche peut avoir une période à 0.
//   C'est alors une tâche de fond, exécutée aussi souvent que possible.
//   Exemple type : surveillance des entrées numériques de l'arduino (bouton poussoir, ...).
//   Une tâche de fond doit avoir la durée d'exécution la plus courte possible.
//
//Pour désactiver une tâche, lui associer la fonction NULL.
//
//La méthode tache.executer() est en principe réservée à l'ordonnanceur.
//   Mais elle peut aussi être appelée depuis une autre tâche.
//   Cette possibilité est utile lorsqu'il y a nécessité de fluidifier l'enchaînement entre les tâches,
//   par exemple entre une tâche qui lit un capteur et une tache qui exploite la valeur lue.
//
//L'ordonnanceur essaie de rattraper tout retard, afin de maintenir une période régulière.
//   ==> Le délai entre deux exécutions peut donc être inférieur à la période fixée.
//   Par exemple avec une période de 1000 ms : si la première exécution est à millis()=15,
//   la suivante reste planifiée à millis() = 1000.
//   Si besoin, recaler() permet de forcer la prochaine execution à maintenant+période.
//   Nb : recaler() est automatique si les tâches durent trop longtemps et que l'ordonnanceur est saturé.
//
/****************************************************************************************/

Modèle d'implémentation dans le programme principal arduino :

#include "ordonnanceur.h"

... // définir au moins une tâche

void setup()
{
  ...
  ordonnanceur.lancer();
}

void loop() {} //Ne sert plus à rien

ordonnanceur.zip (14.6 KB)

1 Like

:angry: Diantre : après avoir testé l'exemple "reboot" sur un "Nano", celui ci ne reboot plus et reste désespérément éteint : plus rien, mort clinique...Quelque chose a faire pour le ressusciter ?

Fausse alerte...je ne sais pas ce qui s'est vraiment passé...en tout cas ça ne reboote pas, mais le "Nano" re-fonctionne !

Bonjour

Ceci est le symptôme d'une carte arduino sur laquelle le bootloader pré-installé par le vendeur n'est pas compatible avec le watchdog.

La solution est d'installer optiboot sur la carte.

Explication :
Le bootloader est le code qui s'éxécute immédiatement après la mise sous tension de la carte.
Entre autres choses, ce code écoute sur la liaison série, et si un téléversement est en cours, il recopie ce qui est reçu vers la mémoire flash de l'arduino. C'est grâce à ce bootloader que le téléversement d'un programme vers l'arduino est facilité.
Le bootloader occupe le début de la mémoire flash, le code téléversé est récopié en suivant.

De son côté le watchdog, lorsqu'il est activé, déclenche un reboot de l'arduino s'il n'est pas rappelé régulièrement.

Là, en appelant la fonction reboot(), tu as activé le watchdog, qui a entrainé un reboot de l'arduino, mais avec un bootloader qui ne désactive pas le watchdog et dont l'exécution dure trop longtemps. Donc le watchdog provoque un nouveau reboot de l'arduino avant même la fin d'exécution du bootloader. La carte semble alors complètement bloquée, car elle boucle à l'infini sur son bootloader.
Il faut la mettre hors tension pour la réinitialiser.
Et si par malheur la fonction reboot() est déclenchée très tôt dans le programme, la reprogrammation complète de l'arduino (procédure ci-dessous) peut être la seule solution de se sortir de ce mauvais pas.

La solution : en utilisant une autre carte arduino (arduino as isp), remplacer le bootloader par la version optiboot, disposible dans l'IDE arduino. Celle-ci gère correctement le watchdog.

Perso c'est ce que je commence par faire de manière systématique lorsque je reçois une carte, comme ça au moins je suis sûr du bootloader qui y est installé.

Détail de la fonction reboot :

void ordonnanceur_c::reboot()
{
  //Activation du watchdog
  this->modifierWatchdog(WATCHDOG_1_SECONDE);

  //Boucle infinie => Reboot au bout d'une seconde
  while (1);
}