Arduino Multitâche.

Bonjour à tous.

Question : Niveau débutant à la retraite.

Je m’initie à l’Arduino et fais des petits montages simples qui fonctionnent assez bien et cela encourage.
Mais, mes montages n’ont qu’une fonction de simple tâche.
A savoir par exemple ; une led s’allume quand il pleut puis s’éteint quand il ne pleut plus.

J’aimerai réaliser pour mes petits neveux un jeu de lumière à mettre dans leurs chambres.
Ce jeu de lumière et une sorte de vu-mètre géant réalisé par des bandes de ruban led de 20 cm de long sur 27 ‘’étages’’ piloté par une Arduino Méga. Il fonctionnera comme un chenillard. Pour l’instant pas de souci pour la partie électronique et programmation.
Le souci est, pour améliorer l’effet, je vais placer un deuxième vu-mètre identique, tout contre celui-ci avec des programmes identiques et d’autres différents ou en oppositions.
C'est-à-dire, par exemple quand un vu-mètre descend sur toute sa hauteur, l’autre va monter.
Deux vu-mètre, deux programmes en un seul. Mon Arduino devient multitâche et cela je ne sais pas faire.
Faire monter un vu-mètre par une boucle for pas de problème mais faire descendre l’autre en même temps………. Je sèche.

Si vous pouviez me guider, je vous en serais reconnaissant.

Cordialement.

Salut!

Rien de bien dur dans ton idée, mais il va falloir oublier la fonction "delay()" au profit des timers.

  • delay() impose un temps d'inactivité de x secondes (entre deux actions, on ne peut rien faire d'autre qu'attendre)...

  • les timers déclenchent une action toutes les x secondes (entre deux actions, on fait tourner loop() ou on ne fait rien)...

le premier multitâche sous windows 3.x était simple : toutes les 20ms, on passe la main au programme suivant (soit 50 fois par seconde, l'oeil humain n'y voit rien).

Voilà des premières pistes de réflexion, mais je sais que ce sont des notions qui font peur. Le temps passé à les découvrir sera largement récompensé! J'ai développé des systèmes entièrement gérés par les timers et interruptions. La boucle loop() est vide, et pourtant, mon arduino ne chôme pas!

Le gros avantage, c'est qu'une fois maîtrisé, on peut définir des priorités de traitement, s'offrir le luxe du "réentrant"... l'exécution de chaque tâche dépend d'un évènement externe au CPU (interruption via un capteur ou un bouton, timer, compteur d'évènements, comparateur analogique...) et cela se fait tout seul. Il faut juste savoir exactement ce qu'on veut.

Le passage au traitement par interruptions modifie un peu la programmation. Là où on avait une boucle while ou for (boucle récursive) avec des variables locales, il faut utiliser un if (ponctuel) avec des variables globales. Mais au final, ça marche tout pareil...

donc la solution : les timers (il y en a 7 dans un mega, ce qui peut permettre de gérer jusqu'à 17 tâches "timées", mais une "infinité" (plus de 10 000 à la louche) si on crée des "couches"...) Mais là comme ça, pas facile d'expliquer, mieux vaut partir de ce que tu cherches exactement à faire et en profiter pour faire un exemple... la datasheet prend une centaine de pages sur les timers, donc j'aurai du mal à les recopier ici...

ouais, t'y vas peut-être un peu fort pour un débutant même si ce que tu dis est bien ce qu'il faudrait faire pour "bien le faire".
Il y a tout de même des techniques "intermédiaires" plus simples à comprendre pour commencer à appréhender les joies du "réentrant"... :wink:
A mon humble avis, commencer par utiliser millis() au lieu de delay() serait déjà un bon début... :wink:

En approche simple, par exemple faire trois choses "simultanément" :

void setup() {
  setupA();
  setupB();
  setupC();
}

void loop() {
  loopA();
  loopB();
  loopC();
}

Charge ensuite d'écrire le contenu des fonctions setupA(), loopA(), etc. sans jamais utiliser la fonction delay(), ce qui permet d'enchaîner rapidement le différentes fonctions loop.

Et tu peux utiliser des variables globales pour qu'une donnée mise à jour par le processus A (exemple : valeur d'un capteur) soit utilisée dans un autre processus.

Ce type d'approche permet de répondre à plein de besoins simples sur arduino.
Il présente aussi l'avantage de structurer le code et de faciliter sa mise au point : on peut ajouter progressivement les processus, les valider et passer au suivant.

Et pour aller un peu plus loin, sans aller jusqu'aux problèmes inhérents aux interruptions et à la réentrance, un ordonnanceur peut être bien pratique.
Personnellement j'utilise cette bibliothèque pour tous mes développements.

Bonjour.

Merci à vous pour votre aide. C'est super sympa.
Super_Cinci, ta méthode est faite pour des pros, je n'ai hélas pas le niveau pour cela.
Je vais utiliser les millis () que Zorro_X me conseille gentiment.
bricoleau, je pense que la solution que tu me propose est la bonne.
Mais j'ai du mal a en appréhender la syntaxe.
Pour faire simple et pour bien comprendre, si par exemple sur la pin 10 de mon arduino, je veux faire clignoter une led deux fois par seconde et que sur la pin 11 je veux faire clignoter une led toute les secondes le tout bien entendu en même temps pourrais-tu m'aiguiller sur la syntaxe.

Cordialement.

Ben si tu installes la bibliothèque, tu verras qu'elle est livrée avec plein d'exemples d'utilisation, directement accessibles depuis l'IDE arduino.

Aller dans Fichier/Exemples/ordonnanceur/...

dont un exemple de blink multiple

Et comme je ne suis pas flemmard :slight_smile: , voici exactement ce que tu demandes, "pondu" en 5 minutes, compilé mais pas testé

La tache t1 est exécutée toutes les 500 ms
La tache t2 toutes les 1000 ms

Exécuter une tâche signifie appeler la fonction associée à la tâche.
Cette même fonction peut être modifiée entre deux exécutions de tâche.

#include "ordonnanceur.h"

void setup() {} //rien ici

void loop() {
  ordonnanceur.lancer(); //et c'est tout
}

const int pinled1 = 10;
const int delai1 = 500; //millisecondes

const int pinled2 = 11;
const int delai2 = 1000; //millisecondes


// Gestion du blink 1

void setup1(); //pré-déclaration de la fonction

tache t1(setup1, delai1); //ajoute une tâche à l'ordonnanceur

void setup1() {
  pinMode(pinled1, OUTPUT);
  digitalWrite(pinled1, HIGH);
  t1.changerFonction(loop1);
}

void loop1() {
  static int etat = HIGH; //static pour conserver la valeur entre deux appels
  if (etat == HIGH) {
    etat = LOW;
  } else {
    etat = HIGH;
  }
  digitalWrite(pinled1, etat);
}


// Gestion du blink 2 d'une autre manière pour varier les plaisirs

void setup2(); //pré-déclaration de la fonction

tache t2(setup2, delai2); //ajoute une tâche à l'ordonnanceur

void setup2() {
  pinMode(pinled2, OUTPUT);
  allumer();
}

void allumer() {
  digitalWrite(pinled2, HIGH);
  t2.changerFonction(eteindre);
}

void eteindre() {
  digitalWrite(pinled2, LOW);
  t2.changerFonction(allumer);
}

Un truc tout bête comme ça :

long tLed10 = 500;
long tLed11 = 1000;
long t10, t11;
boolean a10, a11;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  t10 = millis();
  t11 = millis();
  a10 = false;
  a11 = false;
}

void loop() {
  // put your main code here, to run repeatedly:
  if (millis() > 1.5 * tLed10 + t10 && ! a10) {
    t10 = millis();
    a10 = true;
    Serial.println("LED_10 allumé");
  } else if (millis() > tLed10 + t10 && a10) {
    a10 = false;
    Serial.println("LED_10 éteint");
  }
  if (millis() > 1.5 * tLed11 + t11 && ! a11) {
    t11 = millis();
    a11 = true;
    Serial.println("               LED_11 allumé");
  } else if (millis() > tLed11 + t11 && a11) {
    a11 = false;
    Serial.println("               LED_11 éteint");
  }
//tu peux rajouter d'autres LED avec des temps différents
}

Cordialement.

NOTA : La solution de "bricoleau" est bien plus élégante mais nécessite une compréhension un peu plus étoffée du langage.

bricoleau et ChPr, je vous remercie infiniment de votre aide et surtout de votre pédagogie.

Je vais prendre vos exemples et les étudier afin de progresser dans le langage Arduino.

Merci encore d'avoir pris de votre temps pour me guider.

Cordialement.

Voici une autre version plus basique et plus simple à comprendre

#include "ordonnanceur.h"

void setup() {
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
}

int etat1 = LOW;

void blink1() {
  etat1 = (etat1 == HIGH) ? LOW : HIGH; //inversion de l'état
  digitalWrite(10, etat1);
}

int etat2 = LOW;

void blink2() {
  etat2 = (etat2 == HIGH) ? LOW : HIGH; //inversion de l'état
  digitalWrite(11, etat2);
}

tache t1(blink1, 500); //executer blink1() toutes les 500 ms
tache t2(blink2, 1000);//executer blink2() toutes les 1000 ms

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

"bricoleau", votre dernier script est on ne peut plus simple. Pour autant, l'instrunction :

ordonnanceur.lancer();

ne devrait-elle pas plutôt être dans le setup() ?

Cordialement.

Pierre

Absolument exact.

On ne ressort jamais de l'appel ordonnanceur.lancer(), donc on peut le placer où on veut.
C'est même mieux de le mettre dans setup() car là au moins il n'y a plus d'ambiguïté sur son fonctionnement.

Certains ordonnanceurs se bornent en effet à exécuter la prochaine tâche et rendent la main, ce qui oblige à les appeler en boucle depuis le loop.
Mais ce n'est pas le cas de celui-ci.

Nb : il existe toutefois une méthode ordonnanceur.stopper() appelable depuis n'importe quelle tâche, pour arrêter l'ordonnanceur et sortir de la méthode lancer(). Ainsi qu'une méthode estActif() pour savoir si l'ordonnanceur est en cours d'exécution ou non.
Je les ai prévues pour être propre, mais avoue n'en n'avoir pas eu l'usage jusqu'à présent.

Bonjour.

bricoleau, tu es un véritable champion ! :wink:

C'est une version plus simple et tout aussi efficace.

Sois en remercié jusqu’à la vingtième génération.

Encore merci pour ta précieuse aide.

Cordialement.

balisto56:
C'est-à-dire, par exemple quand un vu-mètre descend sur toute sa hauteur, l’autre va monter.
Deux vu-mètre, deux programmes en un seul. Mon Arduino devient multitâche et cela je ne sais pas faire.
Faire monter un vu-mètre par une boucle for pas de problème mais faire descendre l’autre en même temps………. Je sèche.

Le plus simple est de gérer les 2 vu-mètres dans la même boucle.
Il ne s'agit pas de multi-tâche mais de 2 actions décalées du temps (imperceptible) d'exécuter une instruction.

balisto56:
Pour faire simple et pour bien comprendre, si par exemple sur la pin 10 de mon arduino, je veux faire clignoter une led deux fois par seconde et que sur la pin 11 je veux faire clignoter une led toute les secondes le tout bien entendu en même temps pourrais-tu m'aiguiller sur la syntaxe.

Voici un code illlustrant ce que tu suggères avec les sorties 10 et 11 :

// date pour la gestion du temps de la LED1
int dateLed1;

// etat de la LED 1
bool led1Allumee = false;

void led1_setup()
{
    pinMode(10, OUTPUT);

    // initialisation de la date de la led 1
    dateLed1 = millis();
}

// date pour la gestion du temps de la LED2
int dateLed2;

// etat de la LED 2
bool led2Allumee = false;

void led2_setup()
{
    pinMode(11, OUTPUT);

    // initialisation de la date de la led 2
    dateLed2 = millis();
}


void led1_loop()
{
    // date de maintenant 
    int date = millis();
    
    // si 250ms se sont écoulées 
    // Le clignottement 2 fois par seconde, ca fait 2 x 250ms / seconde :
    //   250ms éteint -> 250ms allumé -> 250ms étient -> 250ms allumé => 1 seconde
    if ( (date - dateLed1) >= 250 )
    {
        // changer l'état de la led
        led1Allumee = !led1Allumee;  // inversion de l'état

        // changer l'état de la sortie
        digitalWrite(10, (led1Allumee ? HIGH : LOW));

        // mettre à jour la date de la LED 
        dateLed1 = date;
    }
}

void led2_loop()
{
    // date de maintenant 
    int date = millis();
    
    // si 1000ms se sont écoulées 
    // Le clignottement toutes les secondes, ca fait  :
    //   1s éteint -> 1s allumé
    if ( (date - dateLed2) >= 1000 )
    {
        // changer l'état de la led
        led2Allumee = !led2Allumee;  // inversion de l'état

        // changer l'état de la sortie
        digitalWrite(11, (led2Allumee ? HIGH : LOW));

        // mettre à jour la date de la LED 
        dateLed2 = date;
    }
}

void setup() 
{
    led1_setup();
    led2_setup();
}

void loop() 
{
    led1_loop();
    led2_loop();
}

Dans ce paragraphe tu trouveras un résumé de ce qui se fait avec millis(). Sinon, le tuto d'eskimon cité plus haut le détaille vraiment très bien.

En pratique tu peux utiliser autant de variables "dateQuelquechose" que t'as besoin de compter le temps.

Je n'avais pas eu le temps de répondre avant, désolé. Les autres réponses, notamment l'ordonnanceur de bricoleau ont l'avantage de "masquer" ce genre de choses et souvent de "bien le faire" (je ne sais pas comment c'est codé dans la librairie de bricoleau) en utilisant les timers du processeur comme le suggérait Super_Cinci.

Ce qu'il faut retenir aussi, c'est le coté "réentrant" auquel faisait référence Super_Cinci : la mécanique et la logique changent un peu maintenant. Il faut garder l'état de ta LED quelque part pour pouvoir l'utiliser lorsque "t'y reviens", c'est ce que je fais avec les variables "ledXAllumee". Mais ca peut être toute autre chose comme un tableau, pour gérer un chenillard par exemple... :wink:

En espérant que c'est au moins plus clair maintenant ! :wink:

Zorro_X:
Voici un code illlustrant ce que tu suggères avec les sorties 10 et 11 :

En espérant que c'est au moins plus clair maintenant ! :wink:

Bonjour.

Merci de ton aide Zorro_X.

Pour moi c'est encore un peu nébuleux mais je vais me pencher sur ton code et en étudier la structure et les fonctions.

En tous cas c'est sympa de votre part de m'aider.

Codialement.