Conditionnelles sans répétition

Bonjour,

Je suis novice dans l'univers de l'arduino et du langage C++, et j'aurais une question concernant justement le code.

Je cherche à activer et désactiver différentes parties d'un programme, qu'on pourrait rapprocher d'une sorte de machine à états, à des moments précis correspondant à la détection de certaines valeurs.

Par exemple, tant que je reçois des valeurs entre 0 et 32, le sous-programme 1 est activé. Tant que je reçois de valeurs entre 33 et 64, les sous-programmes 2 et 7 sont activés, etc.

Je peux réaliser la structure conditionnelle partant d'une cascade de "if", mais je voudrais que cette valeur d'activation ne soit "envoyée" qu'une fois : tant qu'on reste dans l'intervalle, l'instruction n'est pas répétée, jusqu'à ce que l'on en sorte pour y revenir plus tard.

J'espère que c'est assez clair. Je suppose que la question a déjà été traitée, mais je ne la trouve pas dans le forum, probablement car je ne sais pas bien la formuler.

Je vous remercie d'avance pour votre aide.

qu'on pourrait rapprocher d'une sorte de machine à états

Certainement.

Il suffit d'ajouter une variable état.

IDLE_STATE : état de repos
LOW_STATE : valeur 0 à 32 détectée
HIGH_STATE : valeur 33 à 64 détectée

Ensuite les actions doivent être effectuées en fonction de l'état :

si état == IDLE_STATE
si valeur == 0 à 32 :
traiter
état = LOW_STATE
si valeur == 33 à 64 :
traiter
état = HIGH_STATE

si état == LOW_STATE
si valeur == 33 à 64 :
traiter
état = HIGH_STATE

si état == HIGH_STATE
si valeur == 0 à 32 :
traiter
état = LOW_STATE

Donc par exemple : si état == LOW_STATE et que valeur == 0 à 32 rien ne sera fait.

Attention à l'indentation dans ce pseudo-code.
Tu peux utiliser switch/case si tu te sens à l'aise avec cette instruction.

Oui une variable d’état, je viens de terminer un exemple et de voir la réponse de hbachetti; le code que je poste n’est pas compliqué mais peut être déroutant pour un débutant avec les opérateurs logiques.

int valeur = 100;

byte etat = 0;
/*
B00000001 = 1
B00000010 = 2
B00000100 = 4
B00001000 = 8
B00010000 = 16
B00100000 = 32
B01000000 = 64
B10000000 = 128
*/

void setup() {
  Serial.begin(9600);
  while (!Serial);
  delay(2000);
  Serial.println("Lancement du programme");
  Serial.println("");
}

void loop() {
  if (Serial.available() > 0) {
    valeur = Serial.parseInt();
    Serial.print("valeur= ");
    Serial.println(valeur);
    Serial.print("etat= ");
    Serial.println(etat);
    Serial.println("");
  }

  if ((valeur > 0 and valeur < 11) && (etat & 1) == 0) { // valeur de 1 à 10
    test1();
    etat |= B00000001; // représentation binaire = 1
  }

  if ((valeur > 10 and valeur < 21) && (etat & 2) == 0) { // valeur de 11 à  20
    test2();
    etat |= B00000010; // représentation binaire = 2
  }

  if ((valeur > 20 and valeur < 31) && (etat & 4) == 0) { // valeur de 21 à 30
    test3();
    etat |= B00000100; // représentation binaire = 4
  }
}

int test1() {
  Serial.println("Fonction test1() executee");
  Serial.println("");
}

int test2() {
  Serial.println("Fonction test2() executee");
  Serial.println("");
}

int test3() {
  Serial.println("Fonction test3() executee");
  Serial.println("");
}

Je ne pense pas que ce soit une bonne chose d'utiliser un champ de bits pour l'état.
Cela laisse entendre que la variable etat peut prendre les valeurs suivantes :
0 : REPOS
1 : ETAT_1
3 : ETAT_1 + ETAT_2
7 : ETAT_1 + ETAT_2 + ETAT_3

Teste ton code avec 10, 20, 30 puis 10 ...

A la lecture de la demande, le système devrait exécuter test1, test2, test3 puis test1. Or ce ne sera pas le cas avec ton code.

hbachetti , vous avez raison, j’ai commis des erreurs au lieu de etat |= B00000001, j’aurais dû écrire etat = B00000001.

Pour la suite logique, je ne suis pas sûr du besoin à la lecture, à lui de nous le dire.

J’apporte la correction:

int valeur = 100;

byte etat = 0;

void setup() {
  Serial.begin(9600);
  while (!Serial);
  delay(2000);
  Serial.println("Lancement du programme");
  Serial.println("");
}

void loop() {
  if (Serial.available() > 0) {
    valeur = Serial.parseInt();
    Serial.print("valeur= ");
    Serial.println(valeur);
    Serial.print("etat= ");
    Serial.println(etat);
    Serial.println("");
  }

  if ((valeur > 0 and valeur < 11) && (etat != 1)) { // valeur de 1 à 10
    test1();
    etat = 1; 
  }

  if ((valeur > 10 and valeur < 21) && (etat != 2)) { // valeur de 11 à  20
    test2();
    etat = 2; 
  }

  if ((valeur > 20 and valeur < 31) && (etat != 3)) { // valeur de 21 à 30
    test3();
    etat = 3; 
  }
}

int test1() {
  Serial.println("Fonction test1() executee");
  Serial.println("");
}

int test2() {
  Serial.println("Fonction test2() executee");
  Serial.println("");
}

int test3() {
  Serial.println("Fonction test3() executee");
  Serial.println("");
}

Tu n’as pas compris le conseil de hbachetti.

Ton choix de représenter l’état comme un masque de bits n’est pas bon.

Tu supposes qu’un seul bit est allumé dans le mot, que se passe-t-il si, suite à une erreur, aucun ne l’est ? ou plus d’un bit ? Tu ne sais plus dans quel etat tu te trouves.

OK, c’est suite à une erreur, mais des erreurs, tout le monde en fait, alors autant s’auto-protéger en choisissant une meilleure représentation de l’information.

Puisque le système ne peut être que dans un seul état à la fois, c’est plus sûr de garder l’état comme un entier.
En fait on utilise une énumération :

enum { ETAT_STOP, ETAT_VEILLE, ETAT_MARCHE}; // c'est un exemple avec 3 états

A partir de là, le compilateur va comprendre que
ETAT_STOP vaut 0,
ETAT_VEILLE vaut 1,
ETAT_MARCHE vaut 2
Ca part de 0 et ca ajoute 1 à chaque item.

Et ton programme fait (par exemple)

 if ( etat == ETAT_STOP && DemandeMiseEnMarche )
  etat = ETAT_MARCHE;

C’est quand même plus lisible, et tu ne te fais pas des noeuds toi même avec les manipulations de bits.

Un grand merci à vous pour vos réponses !

Pour la suite logique, je ne suis pas sûr du besoin à la lecture, à lui de nous le dire.

Pour moi c'est extrêmement clair :

tant qu'on reste dans l'intervalle, l'instruction n'est pas répétée

Donc cela veut dire : si l'on sort de l'intervalle, on envoie à nouveau.

Effectivement, il s'agit de quelque chose dans le genre :

[0 -31] -> Active état1 (un état peut consister en plusieurs combinaisons de sous-programmes dont certains sont traités aussi dans une autre zone de captation)
[32 - 63] -> Active état2
[64 - 95] -> Active état3
[96 - 127] -> Active état4

Il s'agit pour l'instant de simuler un mouvement circulaire décomposé en 128 valeurs, et déterminer un ensemble de règles de type : au bout d'un tour, réalise tel tache, au bout de quinze tours et trois quarts, réalise telle autre, tant que tu te trouves dans tel arc de cercle, garde activée telle action, etc.

A priori les valeurs arrivent de manière ordonnée, bien que certaines valeurs puissent sauter, c'est pourquoi je préfère travailler à partir de seuils qu'en détectant des valeurs fixes.

hbachetti, lorsque vous avez écrit "devrait exécuter test1, test2, test3 ", vous mettiez en avant l'ordre dans lequel les tests devait se dérouler, c'est là dessus que nos points de vue divergeaient. La réponse donnée par carinola est plus claire maintenant.

biggil, "Tu supposes qu'un seul bit est allumé dans le mot, que se passe-t-il si, suite à une erreur, aucun ne l'est ? ou plus d'un bit ?"
Oui je vous comprend, mais j'étais parti sur la possibilité de traité plus d'un cas au départ.

Voici une solution qui semble fonctionner, en partant de la proposition de Nico78 mais que j’ai modifiée pour essayer de la rendre plus lisible (pour moi!). J’évite les delays, car il y aura une multiplicité de taches en parallèle. La premier partie “Cercles” simule le mouvement d’une manivelle qui sera captée avec des valeurs midi (128), en variant la vitesse de rotation et le “grain” utilisé.

Le “Suiveur” détecte des positions spécifiques du mouvement.

////////CERCLES/////////////////////////////////
/*
  Celui-ci simule un mouvement variable et continu sur 128 valeurs, on peut l'utiliser comme modèle de la vielle
  L'idée est d'avoir un mouvement continu et rubato
*/
int metro;//Cette variable simule l'objet metro dans max
int pas;//pas d'avancement
int angle;
int date_etat_el0;// on crée une vériable qui contiendra les informations temporelles pour l'état d'ELEMENT4

void cercles_setup()
{
  date_etat_el0 = millis();
  pas = random(1, 10);
  angle = 0;
  metro = 100;
}

void cercles_loop()
{
  int date_0 = millis();// date de maintenant
  if ((date_0 - date_etat_el0) >= metro)
  {
    angle = angle + pas;
    date_etat_el0 = date_0;
    Serial.print("angle = "); Serial.println (angle);
    {
      if (angle >= 127) {
        angle = 0;
        pas = random(1, 10);
        metro = random(10, 200);
      }
    }
  }
}

//////////////////////SUIVEUR////////////////

int etat;
int valeur;
int test1() {
  Serial.println("");
  Serial.println("---------------Il est minuit");
  Serial.println("");
}

int test2() {
  Serial.println("");
  Serial.println("---------------Il est trois heures");
  Serial.println("");
}

int test3() {
  Serial.println("");
  Serial.println("---------------Il est six heures");
  Serial.println("");
}
int test4() {
  Serial.println("");
  Serial.println("---------------Il est neuf heures");
  Serial.println("");
}

void suiveur_setup() {
  etat = 0;
}

void suiveur_loop() {
  if ((angle >= 0 and angle < 32) && (etat != 1)) { // valeur de 0 à 31
    test1();
    etat = 1;
  }
  else if ((angle >= 32 and angle < 64) && (etat != 2)) { // valeur de 32 à  63
    test2();
    etat = 2;
  }
  else if ((angle >= 64 and angle < 96) && (etat != 3)) { // valeur de 64 à 95
    test3();
    etat = 3;
  }
  else if ((angle >= 96 and angle < 128) && (etat != 4)) { // valeur de 96 à 127
    test4();
    etat = 4;
  }
}

////////////////////// MACHINE /////////////////

void setup()
{
  Serial.begin(9600);
  cercles_setup();
  suiveur_setup();
}

void loop()
{
  cercles_loop();
  suiveur_loop();
}

J’ai essayé d’exprimer sous la forme de code la proposition de hbachetti, mais je ne suis pas arrivé. J’ai l’impression qu’en multipliant les états (en fait, je pense qu’il faudrait les appeler “événements”) cela devient complexe à programmer, mais si vous avez des conseils pour éclaircir le code, je vous en serais bien reconnaissant…

Si hbachetti vous avait donné un code à tester, vous n'auriez pas eu de mal à le comprendre, c'est un fonctionnement par étape, les évènements se suivent les uns à la suite des autres tels qu'on les a décidés ou tels qui sont attendus, ils suivent un ordre précis, sa formulation et son algorithme était très claire cependant.

Vous pouvez encore améliorer le code avec un tableau de valeurs (ce qui sera plus facile pour modifier les valeurs que parcourir le code) et utiliser une énumération pour l'état.

J'essaierais de fournir un exemple demain si d'autres ne l'auront pas fait avant.

Merci, ça m'intéresserait bien de connaitre les variantes, et de pouvoir apprécier les avantages.

Intuitivement, je vois bien l'intérêt de l'utilisation de enum ou d'un tableau, ainsi que d'un "switch/case" qui viendrait remplacer la cascade de "if", comme proposé par hbachetti et biggil, mais je ne suis pas encore à l'aise avec le code. Il me manque une étape, qui consiste à définir des variables à parti d'intervalles (par exemple : premier_quart = n'importe quelle valeur entre 0 et 31).

Un enum permet d’y voir plus clair.

Exemple: ETAT_REPOS est plus parlant que 0

“switch/case” qui viendrait remplacer la cascade de “if”

Aucun avantage. Un switch/case n’est qu’une cascade de if / else.

Un switch / case ne saura pas remplacer ceci :

if ((valeur > 0 and valeur < 11) …

Bon, voilà un exemple de ce que ça peut donner, vérifier bien que cela fonctionne comme attendu.
J’ai conservé vos procédures de setup, il y a une petite modification, j’ai mis angle = -1 et le programme ne commence seulement que si la valeur devient positive donc le programme peut commencer à partir de n’importe quel quart et pas forcément du premier.

Je ne suis pas sûr que vous allez trouver ça plus simple au final, mais ça vous donne une idée de ce qu’on peut faire.

////////CERCLES/////////////////////////////////
/*
  Celui-ci simule un mouvement variable et continu sur 128 valeurs, on peut l'utiliser comme modèle de la vielle
  L'idée est d'avoir un mouvement continu et rubato
*/
int metro;//Cette variable simule l'objet metro dans max
int pas;//pas d'avancement
int angle;
int date_etat_el0;// on crée une vériable qui contiendra les informations temporelles pour l'état d'ELEMENT4

enum Quart : byte {
  nonDefini,     // 0
  premierQuart,   // 1
  deuxiemeQuart,  // 2
  troisiemeQuart, // 3
  quatriemeQuart, // 4
};

Quart actuelQuart;

// tableau de byte (0 à 255) à deux dimensions: 4 lignes et 2 colonnes ce qui donne 8 valeurs
// le premier indice sera mis à jour à la compilation
byte tableau[][3] = {
  {0, 32, premierQuart},
  {32, 64, deuxiemeQuart},
  {64, 96, troisiemeQuart},
  {96, 128, quatriemeQuart}
  // imaginons que vous souhaitiez rajouter une valeur ici,
  // vous n'aurez pas besoin de modifier le reste du code
};

int NbIndicetableau = (sizeof(tableau) / sizeof(byte)) / 3; // 3=nb de colonnes

void cercles_setup()
{
  date_etat_el0 = millis();
  pas = random(1, 10);
  angle = -1; // j'ai changé la valeur ici pour le mettre dans une valeur indéterminée
  metro = 100;
}

void cercles_loop()
{
  int date_0 = millis();// date de maintenant
  if ((date_0 - date_etat_el0) >= metro)
  {
    angle = angle + pas;
    date_etat_el0 = date_0;
    Serial.print("angle = "); Serial.println (angle);
    {
      if (angle >= 127) {
        angle = 0;
        pas = random(1, 10);
        metro = random(10, 200);
      }
    }
  }
}

void suiveur_setup() {
  actuelQuart = nonDefini;
}

//////////////////////SUIVEUR////////////////

int traitementQuart(byte actuelQuart) {
  byte ret = 1;
  switch (actuelQuart) {
    case premierQuart:
      Serial.println("");
      Serial.println("---------------Il est minuit");
      Serial.println("");
      break;
    case deuxiemeQuart:
      Serial.println("");
      Serial.println("---------------Il est trois heures");
      Serial.println("");
      break;
    case troisiemeQuart:
      Serial.println("");
      Serial.println("---------------Il est six heures");
      Serial.println("");
      break;
    case quatriemeQuart:
      Serial.println("");
      Serial.println("---------------Il est neuf heures");
      Serial.println("");
      break;
    default:
      Serial.println("");
      Serial.println("- Une erreur s'est produite -");
      Serial.println("");
      ret = 0;
  }
  return ret;
}

void suiveur_loop() {
  if (angle > -1) {
    for (unsigned int i = 0; i < NbIndicetableau; ++i) {
      //            valeur basse du quart,    valeur haute du quart,            nom du quart
      if ((angle >= tableau[i][0] and angle < tableau[i][1]) && (actuelQuart != (tableau[i][2]))) {
        actuelQuart = (tableau[i][2]);
        Serial.println("");
        Serial.print("Passage au Quart: ");
        Serial.println(actuelQuart);
        if (!traitementQuart(actuelQuart)) {
          Serial.println("!!!----ERREUR---!!!");
        }
        break;
      }
    }
  }
}
////////////////////// MACHINE /////////////////

void setup()
{
  Serial.begin(9600);
  // Affichage de la valeur du nombre d'éléments x du tableau
  Serial.print("Nombre d'éléments: ");
  Serial.println(NbIndicetableau);
  Serial.print("La valeur initiale du quart vaut: ");
  Serial.println(actuelQuart);

  cercles_setup();
}

void loop()
{
  cercles_loop();
  delay(2000);
  suiveur_loop();
}

C'est génial, encore une fois, merci pour la leçon ! J'ai de quoi travailler, mais vous me faites avancer à vitesse grand v.

Excusez-moi de vous solliciter encore, mais je suis parti pour deux semaines sans ma carte arduino. Alors, pour voir le fonctionnement, j'ai lancé le simmulateur de tinkercard, mais il m'envoie une erreur sur la ligne :

actuelQuart = (tableau[ i ][2]);

In function 'void suiveur_loop()':
104:36: error: invalid conversion from 'byte {aka unsigned char}' to 'Quart' [-fpermissive]

C'est peut-être juste un problème lié au tinkercard ? La vérification dans l'IDE n'envoie aucune erreur.

Oui, l'IDE d'Arduino le prend sans erreur, il faut ajouter une conversion de type en spécifiant (Quart) parce que l'un ,actuelQuart, est déclaré comme étant un type Quant et l'autre est un type byte, le tableau. Bien entendu les deux sont de type primaire byte mais le langage oblige à une certaine rigueur. Je ne suis pas débutant en programmation mais j'apprend ce langage (c et c++) depuis que je me suis mis à l'Arduino.

remplacer la ligne par:

actuelQuart = (Quart)(tableau[i][2]);