C.A.T.H petit système multi-taches coopératif pour Arduino

Hello,

To simplify your designs, you may use C.A.T.H
Git source

Écrivez en français dans le forum francophone

Il y a FreeRTOS qui marche nickel pour les esp32 mais n'est pas compatible avec tous les modèles d'arduino.

Hello, ok désolé, je ne savais pas.

C.A.T.H est plus simple et fonctionne quasi partout :slight_smile:

Oui c’est sûr et ça n’a surtout rien à voir…

En gros votre abstraction vous permet d’appeler des fonctions de temps en temps (cf ci dessous) mais si une de vos tâches fait un delay() vous bloquez tout le monde et il n’y a pas de points de synchro (les tâches ne se connaissent pas entre elles) ou possibilité d’ajout et retrait de tâches au runtime ou de gestion du contexte d’une tâche (sa propre pile par exemple).

Ça a le mérite de montrer un exemple d’approche objet.

Quelques suggestions :

Pour le monde arduino vous auriez pu conserver la nomenclature standard et le camelCase et appeler vos fonctions membres setup() et loop()

Au lieu de tout mettre dans le .ino, vous pourriez proposer une bibliothèque.

Le nombre de tâches pourrait être dynamique au lieu d’avoir un tableau de taille statique, faites une liste chaînée et vous rajoutez la tâche en début ou fin de liste à chaque nouvelle instance.

Vous devriez revoir la gestion du temps dans votre fonction S_Loop() et de m_LoopDelay pour faire une vraie approche avec millis()

Sinon le code d’exemple pourrait être amélioré

  • avoir une bibliothèque

  • n’utilisez pas un booléen pour HIGH et LOW, ce n’est pas propre et sort de la documentation donc risque de problèmes si Arduino décidait de typer HIGH et LOW (dans un enum par exemple).

  • pourquoi un pointeur ????

Hello, merci d'avoir pris le temps de répondre.

En effet, ici delay bloque tout. mais on en a généralement pas besoins ou alors c'est que l'on sait ce que l'on fait. L'avantage d'une approche cooperative est de justement éviter la plupart des problèmes de partage de ressources qu'il faut gérer avec les mutex, volatile etc...
L'idée ici est d'avoir un outil très léger et très simple. SI vous regarder les projets que j'ai développé avec, ça simplifie énormément sans se prendre la tête :slight_smile:
Je l'ai avant tout développé pour moi mais je pense que ça peut inspirer d'autres développeurs.

Voici mes réponses à vos remarques et suggestions :

  • Perso je n'apprécie pas le camelCase, alors j'utilise la case qui me plait :slight_smile: Les goûts et les couleurs ça ne se discutent pas. Si ça dérange, rien n'empêche de renommer :slight_smile:
  • Oui, on pourrais faire une bib, c'est vrai, il faudrait que je me penche sur la question à l'occasion.
  • Faire une gestion dynamique me semble une complexité pas utile ici pour des petit projets embarqués, si j'en vois un jour l'utilité, je le ferais.
  • Je ne comprends pas bien votre remarque sur la gestion du temps
  • Oui, j'ai en effet découvert que digitalwrite n'attendait pas vraiment un boolean. Perso, je trouve que c'est une erreur de conception car ce serait hyper pratique :slight_smile: Mais bon, je fait avec.
  • J'utilise un pointeur car cette tâche est instancier plusieurs fois et doit gérer un Boolean dont le pointeur est passé en paramètre à l'init.

Merci et bonne journée.

@vedvmnkdv

J'ai supprimé votre traduction du français vers l'anglais. Le forum français est réservé à ceux qui préfèrent communiquer en français, comme son nom l'indique. Si vous souhaitez utiliser l'anglais, vous trouverez de nombreux endroits où le faire. Si vous souhaitez traduire un message du forum dans une autre langue, vous pouvez le faire vous-même. Je constate que vous avez attiré l'attention des modérateurs à plusieurs reprises, principalement pour des contributions inutiles. C'est pourquoi je vous ai suspendu du forum pour une semaine. Profitez de vos vacances pour vous familiariser avec le guide du forum, disponible en haut de chaque catégorie.

Merci

1 Like

oui, c'est juste que si vous proposez quelque chose pour le monde Arduino, autant suivre les recommandations Arduino. ça aide à l'adoption de nouvelles idées en faisant la promotion de chose "connues" (comme setup() et loop()).

Oui bien sûr, c'est hors du champ de ce que vous faites. C'était plus une réponse pour @axelmaux et FreeRTOS.

vos commentaires parlent de temps en ms

  CathCnt               m_CurCounter;               // Curent number of ms before next Loop call
  CathCnt               m_LoopDelay;                // Default period of Loop call (in ms)

mais ce n'est pas ce que fait le code il me semble.

ah ok - oui j'avais raté les variables globales.


en gros (tapé ici) votre classe pourrait ressembler à cela

class Cath {
public:
  virtual void setup() = 0;
  virtual void loop() = 0;

  static void registerTask(Cath* task, unsigned long period) {
    task->period = period;
    task->lastRunTime = 0;
    task->next = taskList;
    taskList = task;
  }

  static void setupTask() {
    for (Cath* task = taskList; task != nullptr; task = task->next)  task->setup();
  }

  static void loopTask() {
    unsigned long currentMillis = millis();
    for (Cath* task = taskList; task != nullptr; task = task->next) {
      if (currentMillis - task->lastRunTime >= task->period) {
        task->loop();
        task->lastRunTime = currentMillis;
      }
    }
  }

private:
  unsigned long period;
  unsigned long lastRunTime;
  Cath* next;

  static Cath* taskList;
};

Cath* Cath::taskList = nullptr;

et le .ino ne ferait comme vous que

void setup() {
  Cath::setupTask();
}

void loop() {
  Cath::loopTask();
}

(pas testé, juste pour l'idée de la liste chaînée et la gestion de la periode par millis)

PS/ si vous pouvez changer le titre de votre post en français, ce serait vraiment mieux pour le forum...

Si j'avais ce programme à faire sans chercher à économiser le code, j'écrirais:

// voir https://github.com/Phildem/Cath/blob/main/Cat-exemple_Schematic.GIF


// *********************** SlowBlink ***********************
// Allumée 1s, éteinte 1s

const uint8_t LED_SLOW_BLINK = 4;

#include "MTobjects.h" // Voir http://arduino.dansetrad.fr/MTobjects

MTsoftPWM SlowBlink(LED_SLOW_BLINK, impulsions_de 1000000L micro_secondes, periodes_de 2000000L micro_secondes);



// *********************** FastBlink ***********************
// Allumée 0,11s, éteinte 0,1s

const uint8_t LED_FAST_BLINK = 5;

#include "MTobjects.h" // Voir http://arduino.dansetrad.fr/MTobjects

MTsoftPWM FastBlink(LED_FAST_BLINK, impulsions_de 110000L micro_secondes, periodes_de 210000L micro_secondes);



// *********************** Assymetric ***********************
// Allumée 50ms, période 1,11s

const uint8_t LED_ASSYMETRIC = 6;

#include "MTobjects.h" // Voir http://arduino.dansetrad.fr/MTobjects

MTsoftPWM Assymetric(LED_ASSYMETRIC, impulsions_de 50000L micro_secondes, periodes_de 1110000L micro_secondes);



// *********************** A ou B ***********************

const uint8_t LED_A_OU_B = 7;
const uint8_t BOUTON_A_OU = 2;
const uint8_t BOUTON_B_OU = 3;

#include "MTobjects.h" // Voir http://arduino.dansetrad.fr/MTobjects

void ou(void);

MTbutton BoutonAou(BOUTON_A_OU, ou, ou);

MTbutton BoutonBou(BOUTON_B_OU, ou, ou);

void ou(void)
{
  pinMode(LED_A_OU_B, OUTPUT);
  digitalWrite(LED_A_OU_B, BoutonAou.getSelect() | BoutonBou.getSelect() ? HIGH : LOW);
}


// *********************** A ET B ***********************

const uint8_t LED_A_ET_B = 8;
const uint8_t BOUTON_A_ET = 2;
const uint8_t BOUTON_B_ET = 3;

#include "MTobjects.h" // Voir http://arduino.dansetrad.fr/MTobjects

void et(void);

MTbutton BoutonAet(BOUTON_A_ET, et, et);

MTbutton BoutonBet(BOUTON_B_ET, et, et);

void et(void)
{
  pinMode(LED_A_ET_B, OUTPUT);
  digitalWrite(LED_A_ET_B, BoutonAet.getSelect() & BoutonBet.getSelect() ? HIGH : LOW);
}



// *********************** A ouexclusif B ***********************

const uint8_t LED_A_OUEXCLUSIF_B = 9;
const uint8_t BOUTON_A_OUEXCLUSIF = 2;
const uint8_t BOUTON_B_OUEXCLUSIF = 3;

#include "MTobjects.h" // Voir http://arduino.dansetrad.fr/MTobjects

void ouexclusif(void);

MTbutton BoutonAouexclusif(BOUTON_A_OUEXCLUSIF, ouexclusif, ouexclusif);

MTbutton BoutonBouexclusif(BOUTON_B_OUEXCLUSIF, ouexclusif, ouexclusif);

void ouexclusif(void)
{
  pinMode(LED_A_OUEXCLUSIF_B, OUTPUT);
  digitalWrite(LED_A_OUEXCLUSIF_B, BoutonAouexclusif.getSelect() ^ BoutonBouexclusif.getSelect() ? HIGH : LOW);
}



// *********************** Pour faire autre chose ***********************

void setup(){}

void loop(){}

Et j'aurais le message:

Le croquis utilise 3384 octets (10%) de l'espace de stockage de programmes. Le maximum est de 32256 octets.
Les variables globales utilisent 283 octets (13%) de mémoire dynamique, ce qui laisse 1765 octets pour les variables locales. Le maximum est de 2048 octets.

Et le jour ou j'utiliserai Arduino pour faire du traitement de texte et de la vidéo, je me pencherais sur le multitask

Mais pour l'instant la bibliothèque utilisée est en maintenance.

Maintenant pour ce qui est de delay(), je l'aime bien et elle ne m'empêche pas de faire tourner un moteur pas à pas tout en l'utilisant comme le montre cet exemple:

/ Ce programme montre que l'on peut faire autre chose pendant un delay,
// et même suspendre un delay par un autre.

// Ce programme comporte un "blink with delay" en toile de fond.
// Quand on appuie sur un bouton, un pas à pas part pour faire un tour et
// après 1s une ligne est envoyée à la console série. Cette temporisation
// utilise un delay() qui va suspendre le blink. Les deux delay() simultanés
// ne vont pas gêner la lecture du bouton ni la rotation du moteur. Si on
// appuie sur le bouton juste après un changement dʼétat de la led, les deux
// delay comptent en même temps et le clignotement nʼest pas affecté. Si on
// appuie sur le bouton longtemps après le changement dʼétat de la led,
// cette dernière ne peut évidemment pas changer d'état car elle n'a pas la
// main. La durée du blink est alors modifiée.

#include "MTobjects.h" // V1.0.3 Voir http://arduino.dansetrad.fr/MTobjects

const uint8_t PIN_BOUTON = A0;

MTulnStepper Stepper(pin_A1 2, pin_B1 3, pin_A2 4, pin_B2 5);

void bouge(void)
{
  Stepper.move(Stepper.getStepsPerTurn()); // Parti pour un tour de rotation
  delay(2000 milli_secondes); // Qui va donc suspendre le delay du blink
  Serial.println(F("C'est parti"));
}

MTbutton Bouton(PIN_BOUTON, bouge);

void setup()
{
  digitalWrite(LED_BUILTIN, LOW); // Led en sortie, éteinte 
  pinMode(LED_BUILTIN, OUTPUT);

  Serial.begin(115200); // Pour envoyer les messages
}

void loop()
{
  // Programme classique du "blink with delay"
  digitalWrite(LED_BUILTIN, HIGH);
  delay(5000 milli_secondes);
  digitalWrite(LED_BUILTIN, LOW);
  delay(5000 milli_secondes);
}

Hello,

Il y a toujours plein de manière de faire les choses.
Les leds, c'était juste pour montrer le concept mais on peut faire des application plus complexes facilement comme :

Testeur de servos numérique

ou

Commande pompe temporisée

Hi,

J'ai changé le titre.

Oui, utiliser une liste chainée est en effet plus simple mais ce qui me gêne un peu dans ce cas ,c'est que dans certains cas, une tâche peut appeler une méthode d'une autres instance de tâche et se sera parfois moins pratique.

Merci !


Ok - compris (en surchargeant l’opérateur [] vous pourriez retourner la N-ieme tâche). Il y a effectivement plusieurs approches possibles vous pourriez aussi faire un template par exemple pour la taille max.

Si vous faites une bibliothèque c’est mieux si l’utilisateur n’a pas à modifier le code de la bibliothèque pour changer cette taille statique