Projet de boite à dons/tirelire Arduino : plusieurs actions à la fois

Bonjour à tous,

Débutant dans le monde Arduino, j'ai un projet pour une association, il s'agit d'une boite à dons/tirelire qui s'apparente à une machine de Rube Goldberg (réaction en chaîne). En gros, ce sera une boite avec une façade en plexiglas qui permettra à l'utilisateur de voir le trajet de la pièce.

Le principe : L'utilisateur met une pièce dans la boîte, et passe à travers un photo interrupteur à fourche, ce qui déclenche du son (buzzer passif), des lumières (bande de Led programmable), et un moteur à courant continu qui fait tourner un tapis roulant.

La pièce suit son chemin dans la boîte, arrive sur le tapis roulant, et tombe ensuite sur une rampe, qui l'emmène soit dans une boîte 1 ou dans une boîte 2, chacune équipée d'un photo interrupteur qui détecte où est tombée la pièce. Cela déclenche un son, affiche un message sur un écran lcd et déclenche des ventilateurs selon la boîte où la pièce est tombée.

Le problème : J'arrive à peu près à maîtriser les composants indépendamment les uns des autres. Or, quand il s'agit d'écrire le programme en entier, je suis très rapidement bloqué. En effet, mon programme arrive à reconnaître la pièce dans le photo interrupteur et à effectuer une action à la fois, mais je ne suis pas bien sûr de comment arriver à lancer le son, la lumière et le moteur en même temps, et que ces actions s'arrêtent quand la pièce tombe dans la boîte 1 ou 2.

J'ai cherché des solutions, et suis tombé sur des sujets sur le multitasking à l'aide de "state machine", ce qui dépasse encore mon niveau en programmation. Dois-je m'orienter par là, ou existe-t-il des solutions plus simple pour lancer des actions au même moment et les faire s'arrêter quand un interrupteur est déclenché ?

Merci beaucoup

Léo

Salut

Je pense que ton projet ressemble fort à un automate.
Pour chaque action à réaliser il faut que tu te poses la question : quelle est l'événement qui la déclenche. Ensuite chaque action réalisée fait basculer ton automate dans un état. Cet état dans ton cas peut être carrément l'endroit où la pièce se trouve.
Le premier état s'appelle IDLE en anglais ou REPOS. Les autres états s'appelleront TAPIS, RAMPE, etc.
Exemple :

Etat IDLE -> événement "pièce introduite" -> action "start" -> état "STARTED"

L'action start met en route le buzzer, la lumière et le tapis roulant.
A ce moment là on voit déjà que ta description manque de précision : quand arrête t-on tout ça ?

  • quand la pièce entre dans la chambre 1 ou 2 ?

La majeure partie du temps, quand le problème est simple, on peut l'écrire avec un switch / case :

EXEMPLE

Sinon, avec "Finite State Machine arduino" sous google tu trouveras certainement une implémentation qui te conviendra.

@+

Bonjour,

hbachetti:
La majeure partie du temps, quand le problème est simple, on peut l'écrire avec un switch / case :

EXEMPLE

Sinon, avec "Finite State Machine arduino" sous google tu trouveras certainement une implémentation qui te conviendra.

Ou bien le très bon Tuto de JML

Bonsoir,

Merci à vous pour vos réponses, je vais aller voir dans cette direction.

Effectivement, les actions moteurs/lumière/son s'arrêtent (ou changent) quand la pièce tombe dans la boite 1 ou dans la boite 2.

A ce propos, une petite question encore : si j'ai bien compris, je vais devoir utiliser les interruptions pour lancer une partie de programme spécifique (nouveaux sons/nouvelles lumières/arrêt moteur) quand la pièce tombe dans une des boîtes, ou il existe un autre moyen ?

Merci encore

Bonne soirée à vous

Léo

Re-bonsoir,

J'ai un peu affiné le déroulement du programme tel que je l'envisage :

Allumage de la machine

SI aucune pièce détectée

  • la bande de LED éclaire en continu
  • Un message indique sur un écran LCD "Insérer pièce"

SI une pièce est détectée à l'entrée

  • Mettre le moteur en marche (tapis roulant)
  • Allumer et éteindre les LED de la bande les unes après les autres
  • Jouer une mélodie

SI la pièce tombe dans la boite 1

  • Arrêter le moteur
  • les LED de la bande clignotent pendant 5 secondes
  • Jouer une autre mélodie pendant 5 secondes
  • Afficher un message sur l'écran LCD pendant 5 secondes
  • Allumer les ventilateurs pendant 5 secondes

SI la pièce tombe dans la boîte 2

  • IDEM que boîte 1 mais avec une mélodie et un affichage sur l'écran différents.

Au vu de ces précisions, vos réponses précédentes s'appliquent elles quand même ?

Pensez-vous que ceci est réalisable avec un seul Arduino Uno ? J'ai tendance à me dire, instinctivement, qu'il serait plus simple d'utiliser une carte pour la phase "détecter une pièce => allumer moteurs et LED " et une carte qui gère l'arrivée des pièces et les actions associées.

Il reste une question (en plus de tout le programme à écrire :slight_smile: ) : comment gérer si l'utilisateur insère une autre pièce alors que le programme n'est pas fini ?

Merci à vous !!

Léo

Il serait étonnant d'avoir besoin de plusieurs processeurs. Tu as besoin apparemment de trois entrées optos fourche, et d'une huitaine de sorties. Les entrées analogiques sont utilisables en tant qu'entrées / sorties classiques.
Suivant les modèles, les cartes ARDUINO possèdent plus ou moins d'entrées avec interruptions :

Mais cela reste parfaitement faisable sans interruptions, le tout étant de ne pas passer trop de temps dans les actions pour ne pas louper le passage de la pièce dans les optos.

comment gérer si l'utilisateur insère une autre pièce alors que le programme n'est pas fini ?

Soit tu interdis la chose (fermeture de l'entrée pièces à l'aide d'un actionneur). Un solénoïde actionnant un clapet (une petite plaque de métal ou de plastique pivotant sur un axe) peut faire l'affaire. La réalisation mécanique peut être intéressante. Il faut que l'entrée pièce soit une fente dans la face avant, et le clapet vient obturer cette fente.

Soit tu autorise la chose et c'est l'état de l'automate qui dira si le programme doit traiter la nouvelle pièce ou attendre que la précédente soit traitée. Il te faudra mémoriser chaque pièce insérée. Le risque est d'en introduire plusieurs pendant que la précédent est en cours de traitement. Elles seront donc ensuite transportées ensemble.

Je bosse dans le développement logiciel sur des horodateurs ou des machine de distribution de titres de transport. Dans nos machines nous utilisons la première solution.

@+

Bonjour,

Merci beaucoup, je m'y colle !

Je vous tiens au courant de la suite du projet.

Bonne fin de journée

Léo

Bonjour,

Après avoir pas mal bidouillé et trouvé de l'aide auprès d'un ami, le projet est maintenant opérationnel, merci beaucoup pour votre aide qui m'a permis de m'orienter dans la bonne direction !

Il me reste 2 petites questions/améliorations en suspens :

  • Lorsque une pièce est insérée, que la lumière clignote, l'écran "grésille" et s'allume et s'éteint en même temps que la bande de led, et le message n'apparaît pas clairement. Je pense que c'est lié à une instruction "lcd.clear" qui n'a pas sa place, mais je ne trouve pas laquelle.

  • La fréquence de clignotement diffère selon la boîte dans laquelle tombe la pièce : elle clignote très vite quand elle est insérée dans la boîte "clous" et beaucoup moins dans la case "bière". Je pense que c'est lié au nombre incrémenté associé à chaque objet, mais je ne comprends pas pourquoi : c'est pour moi uniquement une info textuelle (50 clous) et non une variable.

Pouvez vous m'éclairer ?

Je joins le fichier .ino !

Merci !

Sketch_boite_a_dons_2_actuelle.ino (7.16 KB)

  lcd.clear();
  afficherMessage (typeMateriel);
  lcd.clear();

Pourquoi appeler lcd.clear() après avoir affiché un message ?
Il est forcément effacé.

Il y a d'autres appels à lcd.clear() pourquoi ? ? ?

Il serait bien d'insérer le code dans le message en utilisant les balises.
https://forum.arduino.cc/index.php?topic=315372.0

Merci de ton retour !

Voilà mon code :

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);

int numLed = 1;
int oldLedPort = 9;

unsigned long ledStartupTime = 0;
const unsigned long ledDuration = 1000;

unsigned long blinkStartupTime = 0;
const unsigned long blinkDuration = 2000;
bool blinkOn = false;

int blinkingLed = 0;
bool isBlinking = false;
int typeMateriel = 0;

int nombreVis = 0;
int nombreClous = 0;
int nombreBieres = 0;
int nombreAbrasifs = 0;
const unsigned long dureeAffichage = 3000;
unsigned long departAffichage = 0;

void setup() {
  // put your setup code here, to run once:
  lcd.init(); // initialisation de l'afficheur
  pinMode (A0, INPUT);
  pinMode (A1, INPUT);
  pinMode (A2, INPUT);
  pinMode (A3, INPUT);
  pinMode (6, OUTPUT);
  pinMode (7, OUTPUT);
  pinMode (8, OUTPUT);
  pinMode (9, OUTPUT);
  pinMode (10, OUTPUT);
  pinMode (11, INPUT_PULLUP);
  pinMode (12, OUTPUT);
  digitalWrite (10, HIGH);
  shutDownLeds();
  Serial.begin (9600);
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("BOITE A DONS 1.0");
  delay (500);
lcd.clear();

}

void loop() {
  for (int i = 1; i < 5; i ++) {

    if (check(i)) //check chaque boite l'une après l'autre. Exemple : si check (1) est true, c'est que c'est la boite 1 qui est active
    {
      Serial.println (i); //indique quelle boite est active
      blinkingLed = i; //indique quelle Led doit blinker
      typeMateriel = i;
      isBlinking = true; //indique qu'on passe en mode blink
      blinkStartupTime = millis(); //initialise le temps de départ du blink
      departAffichage = millis();
      shutDownLeds(); //eteint les Leds
      digitalWrite(10, LOW);
      lcd.clear();

    }
  }

  if (!isBlinking) { //si le mode blink n'est pas actif
    boucleLed(); //faire la boucle led standard
    affichageStandard();
    digitalWrite(10, LOW);

  } else {

    blinkLed(blinkingLed); //sinon, blinker la Led grâce à la fonction blinkLed
    afficherMessage (typeMateriel);


  }


  delay (20);//attendre x millisecondes



}

bool check(int boite) //fonction qui verifie si une piece est inseree dans une boite donnée et renvoie true ou false
{
  int port; //initalise la variable port

  switch (boite) { //si boite = 1, donne la valeur 2 à la variable port, etc...
    case 1:
      port = A0;
      break;
    case 2:
      port = A1;
      break;
    case 3:
      port = A2;
      break;
    case 4:
      port = A3;
      break;
    default:
      return false;
  }

  if (analogRead(port) > 300) //si l'interrupteur qui a le numero de Pin de la variable port précédemment changée est à 0 (donc est appuyé)
  {
    return true; //envoyer true pour la boite correspondante

  } else {
    return false; //sinon, renvoyer false
  }

}

void boucleLed () { //fonction qui fait tourner les leds quand la boite ne détecte pas de pièce
  int port; //initialise variable port
  unsigned long currentTime = millis(); //assigne à la variable currenttime la valeur millis (cad temps écoulé depuis le début)

  if (currentTime - ledStartupTime > ledDuration) { //si l'intervalle entre le temps présent et le temps de départ de la led est supérieur à l'intervall défini
    ledStartupTime = currentTime; //le temps de départ de la led est maintenant le temps présent
    port = getLedPort(numLed); //le port est celui de la led active

    if (port != 0) { //si une led est bien active
      digitalWrite(oldLedPort, LOW); //le port de la dernière led active s'éteint
      digitalWrite(port, HIGH); //le port de la led présente s'allume


      oldLedPort = port; //le port de la led présente devient l'ancien port
      numLed++; //le numero de la led augmente

      if (numLed > 4) { //au bout de la 4ème Led, on recommence à 1
        numLed = 1;
      }
    }
  }
}


void blinkLed(int led) { //fonction qui fait clignoter la led quand la pièce est détectée
  int port = getLedPort(led); //on prend le port de la led
  unsigned long currentTime = millis(); //on définit le temps présent

  if (port != 0) { //si un port est actif
    if (currentTime - blinkStartupTime < blinkDuration) { //et si l'intervalle de blink n'est pas dépassé
      if (blinkOn) { //et si le mode Blinkon est actif
        digitalWrite(port, LOW);//éteindre la led
        digitalWrite(10, HIGH);
        blinkOn = false; //mode blinkon désactivé
      } else { //sinon, allumer la led et mode blinkon est activé
        digitalWrite(port, HIGH);
        digitalWrite(10, LOW);
        blinkOn = true;
      }
    } else {//si l'intervalle de blink est dépassé
      isBlinking = false; //mode isblinking désactivé
      shutDownLeds(); //on éteint les leds
      incrementerCompteur (typeMateriel);
    }
  }
}




int getLedPort(int boite) { //fonction qui prend le port de la led selon la boite activée
  int port;

  switch (boite) {
    case 1 :
      port = 6;
      break;
    case 2 :
      port = 7;
      break;
    case 3 :
      port = 8;
      break;
    case 4:
      port = 9;
      break;
    default:
      return 0;
  }

  return port;
}

void affichageStandard () {
  lcd.setCursor(0, 0);
  lcd.print (nombreVis);
  lcd.setCursor(12, 0);
  lcd.print ("VIS");
  lcd.setCursor(0, 1);
  lcd.print (nombreClous);
  lcd.setCursor(12, 1);
  lcd.print ("CLOUS");
  lcd.setCursor(0, 2);
  lcd.print (nombreBieres);
  lcd.setCursor(12, 2);
  lcd.print ("BIERES");
  lcd.setCursor(0, 3);
  lcd.print (nombreAbrasifs);
  lcd.setCursor(12, 3);
  lcd.print ("ABRASIFS");

}

int incrementerCompteur (int materiel) {

  switch (materiel) {
    case 1:
      nombreVis = nombreVis + 10;
      return nombreVis;
      break;

    case 2:
      nombreClous = nombreClous + 50;
      return nombreClous;
      break;

    case 3:
      nombreBieres = nombreBieres + 3;
      return nombreBieres;
      break;

    case 4:
      nombreAbrasifs = nombreAbrasifs + 5;
      return nombreAbrasifs;
      break;

  }
}

void afficherMessage (int materiel) {
  unsigned long currentTime = millis(); //on définit le temps présent
  if (currentTime - departAffichage < dureeAffichage) {
    switch (materiel) {
      case 1 :
      
        lcd.setCursor (0, 0);
        lcd.print ("+ 10 vis !");
        lcd.setCursor (0, 1);
        lcd.print (" Une vis avertie en vaut 2 !");
        break;

      case 2 :
        lcd.setCursor (0, 0);
        lcd.print ("+ 50 clous !");
        lcd.setCursor (0, 1);
        lcd.print (" Tu es le clou du spectacle !");
        break;

      case 3 :
        lcd.setCursor (0, 0);
        lcd.print ("+ 3 bieres !");
        lcd.setCursor (0, 1);
        lcd.print ("#YOLO");
        break;


      case 4 :
        lcd.setCursor (0, 0);
        lcd.print ("+ 5 abrasifs !");
        lcd.setCursor (0, 1);
        lcd.print ("Poncer avec modération");
        break;
    }

  }
}


void shutDownLeds() {
  digitalWrite(6, LOW);
  digitalWrite(7, LOW);
  digitalWrite(8, LOW);
  digitalWrite(9, LOW);
}

Du coup, j'ai enlevé toutes mes instructions lcd.clear() inutiles, pour en ajouter une seule au debut après l'initialisation, et une autre au début de la loop après le premier if. Comme attendu, l'écran ne grésille plus pendant l'affichage du message, mais en revanche quand la phase de clignotage/affichage du message se termine, l'affichage standard conserve les caractères liés à l'affichage du message. J'ai essayé d'ajouter lcd.clear() à plusieurs endroits, sans succès : soit ça ne fait rien, soit ça fait rafraîchir mon affichage standard à chaque loop.

Pourtant, il me semble que si je concluais ma fonction afficherMessage par un lcd clear, une fois que l'intervalle est dépassé, ça devrait fonctionner...

Bref, je tatonne un peu, as-tu des conseils ?

Merci

léo

quand la phase de clignotage/affichage du message se termine

Il faut donc effacer le LCD "quand la phase de clignotage/affichage du message se termine" et pas ailleurs.

Si ton ton indicateur de fin de phase est isBlinking (je suppose) :

    } else {//si l'intervalle de blink est dépassé
      isBlinking = false; //mode isblinking désactivé
      lcd.clear();
      shutDownLeds(); //on éteint les leds
      incrementerCompteur (typeMateriel);
    }

C'est ce que tu avais fait dans ta première version, mais il y avait tellement d'effacements partout que l'effet était caché.

Merci !

J'ai suivi ton conseil, ça ne fonctionnait pas au début : la boucle de blink durait 2 secondes alors que celle de l'affichage du message en durait 4, l'écran s'effaçait donc au bout des 2 secondes et non de 4.

Pour mon application, j'ai simplement augmenté la durée de la boucle blink à 4100 millisecondes, ça fonctionne bien et ne me pose pas de problème.

Si j'avais voulu conserver la boucle de blink à 2 secondes, je pense que j'aurais du créer une variable isPrinting liée à l'affichage comme deuxième indicateur de fin de phase. J'ai essayé de le faire, ça fonctionnait, mais le nombre incrémenté devenait beaucoup plus élevé ! (1250 clous pour un seul déclenchement au lieu de 50). Mon intuition est-elle, à ton avis, la bonne ?

Et par ailleurs, je ne comprends pas pourquoi le blink se fait plus ou moins rapidement selon le nombre incrémenté, car aucune variable de délai ne me semble liée à la valeur de ce nombre : si j'incrémente + 3 bières, ça clignote très vite, et 50 clous, très lentement (l'inverse de ce que j'ai dit dans mon post initial). As-tu une idée là dessus ?

Merci !

Léo

Je te suggère d'afficher sur le terminal avec Serial.println() là où cela te semble adéquat, afin de suivre le déroulement.

Quelques remarques :

  pinMode (A0, INPUT);
  if (analogRead(port) > 300)

// une broche analogique peut se lire avec digitalRead() (elle est en mode INPUT)

Y a t-il des résistances de pull-down ou pull-up sur ces entrées ?
Parce que dans le cas contraire cela va produire des tas d'impulsions.
Sinon, les brancher entre GND et entrée et configurer en INPUT_PULLUP.
La logique sera inversée.

Il serait judicieux de donner des petits noms parlants :

  #define BOITE_1     A0
// ...
  pinMode (BOITE_1, INPUT_PULLUP);
// ...
      port = BOITE_1;

Sinon, c'est très difficile de s'y retrouver ...

Merci pour tes réponses.

Dans l'ordre :
-Ce serait surement plus simple en suivant le déroulement du programme sur le moniteur, merci du conseil

  • Oui, il y a bien des résistances de pull-down sur toutes les entrées, mais peut-être que les passer en digitalRead serait plus judicieux.

  • C'est vrai, je vais réécrire le code de manière plus claire, ce sera utile pour mieux m'y retrouver !

En tout cas, le programme fonctionne à présent comme je le souhaitais, merci pour ton aide !

Léo