Multi tâches arduino

Bonjour,

Mon projet : arduino Mini + A4988 + Pompe péristaltiques
J'ai besoin de l'aide d'experts car je tourne en rond sur mon problème malgré mes recherches.

Le besoin est simple :

  1. Capteurs de niveaux d'eau comme déclencheurs
  2. Un RTC pour conserver la date et comme déclencheur d'actions

Problème :

  1. Enchainement des tâches

Mon code est trop long donc je le poste en message séparé

Je n'ai pas de problèmes pour gérer séparément chaque action :

  1. Déclenchement des moteurs avec les capteurs de niveau ou à travers une date de départ
  2. Allumage des LED
  3. Affichage des données sur le LCD
  4. Gestion du temps avec le RTC (vérifier à travers la console)

Lorsque je lance mon programme et met en route un capteur, le relais se déclenche correctement mais le moteur semble changer d'état (LOW / HIGH) très lentement et reste bloqué.

J'ai essayé avec la fonction Millis pour justement permettre une gestion du multi tâches mais en vain.

Je ne comprends pas ou le programme bloque. Mon raisonnement était le suivant :

  1. Tu vérifies si la condition est vérifiée, si oui tu tournes en boucle l'action sous la condition jusqu'à ce que cette dernière ne soit plus vérifiée, etc...

Je ne demande pas que l'on bosse à ma place, juste que l'on oriente. Je pars de zéro sur la programmation alors merci pour votre indulgence.

Merci et bonne journée.
Greg

J'ai comme l'impression qu'il manque le code du programme...

Bonjour,

oui je dépasse les 9000 caractéres, je ne sais pas comment le poster

bonjour,
ça va sembler bourrin, mais tu peux le découper en tranches et joindre chacune à un post différent ... ce n'est jamais qu'un fichier texte, après tout !

// Define stepper motor connections and steps per revolution:
#define dirPin_out 7
#define stepPin_out 8 //Moteur de droite sur la carte Arduino soit l'évacuation en eau
#define ENPin_out 9

#define dirPin 4
#define stepPin 5 // Moteur de gauche sur la carte arduino soit l'alimentation en eau
#define ENPin 6 

#define Led_in 10 
//#define Led_out 11 


// Define time for start procedure
const int OnHour = 14;
const int OnMin = 31;
const int OffHour = 00;
const int OffMin = 9;

#define Gate 13 //Pin for the MOFSET which control the fan

#include <Wire.h>
#include <DS3231.h> //Bibliotheque pour le RTC qui conserve la date et heure même en cas de coupure
#include <LiquidCrystal_I2C.h> //Bibliothéque pour l'écran LCD

// indiquer (adresse i2c, nombre de colonnes, nombre de lignes)
LiquidCrystal_I2C lcd(0x27, 16, 2); // 0x27 define the type of LCD and 16, 2 the position on the screen 

// Init the DS3231 using the hardware interface
DS3231 rtc(SDA, SCL); //SDA & SCL define the pin needed for the RTC module on the arduino

// Init a Time-data structure
Time t;

// Numéro du pin pour les capteurs de niveau
const byte PIN_CAPTEUR_IN = 2; //niveau eau réserve eau propre

// Numéro du pin pour les capteurs de niveau
const byte PIN_CAPTEUR_OUT = 3;// Niveau réserve eau sale

// Numéro du pin pour les capteurs de niveau
const byte PIN_CAPTEUR_AQUA = 11; //Niveau eau aquarium à définir sur la carte; 


// Numéro du pin pour les capteurs de niveau
const byte RELAY = 12; 

unsigned long temps_debut_osmolation; // Variable de type long non signée pour enregristrer un temps

//const unsigned long DUREE_MIN_OSMOL = 20000; // Durée minimum d'osmolation en milliseconde (20000ms = 20s)


// Nombre de millisecondes entre deux changements d'état des void
const unsigned long INTERVAL_1 = 1000;
const unsigned long INTERVAL_2 = 500;
const unsigned long INTERVAL_3 = 2000;

// Précédente valeur de millis() pour les différents void
unsigned long previousMillis1 = 0;
unsigned long previousMillis2 = 0;
unsigned long previousMillis3 = 0;

void setup() {

// Setup Serial connection
Serial.begin(38400);

// Declare pins as output:
pinMode(stepPin, OUTPUT);
pinMode(dirPin, OUTPUT);
pinMode(ENPin, OUTPUT);

digitalWrite(ENPin, LOW);

pinMode(stepPin_out, OUTPUT);
pinMode(dirPin_out, OUTPUT);
pinMode(ENPin_out, OUTPUT);
pinMode(Led_in, OUTPUT);
//pinMode(Led_out, OUTPUT);


digitalWrite(ENPin_out, LOW);


pinMode(PIN_CAPTEUR_IN, INPUT_PULLUP); // Affecte le mode Entrée au pin capteur. En l'absence de branchement, la résistance interne Pullup tire vers un état HIGH non aléatoire.
pinMode(PIN_CAPTEUR_OUT, INPUT_PULLUP); // Affecte le mode Entrée au pin capteur. En l'absence de branchement, la résistance interne Pullup tire vers un état HIGH non aléatoire.
pinMode(PIN_CAPTEUR_AQUA, INPUT_PULLUP); // Affecte le mode Entrée au pin capteur. En l'absence de branchement, la résistance interne Pullup tire vers un état HIGH non aléatoire.

pinMode (RELAY, OUTPUT); //Pin for relay
digitalWrite(RELAY, LOW);

pinMode (Gate, OUTPUT);
digitalWrite (Gate, LOW);

// initialise l'afficheur LCD
lcd.begin();

rtc.begin();

//The following lines can be uncommented to set the date and time
//rtc.setDOW(FRIDAY);     // Set Day-of-Week to SUNDAY
//rtc.setTime(23, 41, 0);     // Set the time to 12:00:00 (24hr format)
//rtc.setDate(22, 5, 2020);   // Set the date to January 1st, 2014


}


void loop() 
{

lcd.setCursor(0,0);
lcd.print("Time:  ");
lcd.print(rtc.getTimeStr());
lcd.setCursor(0,1);
lcd.print("Date: ");
lcd.print(rtc.getDateStr());

void RTC();
void NIVEAU();
void RESERVE();

}


void RTC()
{
static unsigned long previousMillis1 = 0;
unsigned long currentMillis = millis();

t = rtc.getTime();
Serial.print(t.hour);
Serial.print(" hour(s), ");
Serial.print(t.min);
Serial.print(" minute(s)");
Serial.println(" ");
//delay (1000);

      // Si INTERVAL_1 ou plus millisecondes se sont écoulés
      if(currentMillis - previousMillis1 >= INTERVAL_1) 
      {
  

             if (t.hour == OnHour && t.min == OnMin) 
             { //Si il est l'heure de faire un truc
                    
                    digitalWrite (RELAY, HIGH); //Allumage du relais pour alimenter les moteurs pas à pas
                    digitalWrite (Gate, HIGH); //Allumage du ventilo
                    //delay(100); // Tu attends 100 milli seconds
                    
                    if (digitalRead(PIN_CAPTEUR_AQUA) == HIGH) 
                    { // Sinon si le pin du capteur de niveau bas est HIGH (contact pullup ouvert, flotteur haut)
                    digitalWrite(stepPin_out, HIGH);
                    delayMicroseconds(1000);
                    digitalWrite(stepPin_out, LOW);
                    }
        
        
                    if (digitalRead(PIN_CAPTEUR_AQUA) == LOW) 
                    { // Sinon si le pin du capteur de niveau bas est LOW (contact pullup fermé, flotteur bas)
                    digitalWrite(stepPin, HIGH);
                    delayMicroseconds(1000);
                    digitalWrite(stepPin, LOW);
                    }
              }

              else if(t.hour == OffHour && t.min == OffMin) 
              { // Sinon si il est telle heure, tu coupes les moteurs et le ventilo
              digitalWrite(ENPin, LOW); //0V à la pin du moteur 1
              digitalWrite(ENPin_out, LOW); //OV à la pin du moteur 2
              digitalWrite(RELAY, LOW); // Extinction du relais d'alimentation des moteurs pas à pas
              digitalWrite (Gate, LOW); //Extinction du ventilo
              }
              
// Garde en mémoire la valeur actuelle de millis()
previousMillis1 = currentMillis;             
      }

}


void NIVEAU()
{
static unsigned long previousMillis2 = 0;
//static byte etatBrocheLed1 = LOW;
unsigned long currentMillis = millis();

      // Si INTERVAL_1 ou plus millisecondes se sont écoulés
      if(currentMillis - previousMillis2 >= INTERVAL_2) 
      {


              if (digitalRead(PIN_CAPTEUR_AQUA) == LOW) 
              { // Sinon si le pin du capteur de niveau bas est LOW (contact pullup fermé, flotteur bas)
              
              digitalWrite (RELAY, HIGH); 
               
              lcd.begin();
              lcd.setCursor(2,0);
              lcd.print("Niveau bas");
              lcd.setCursor(0,1);
              lcd.print("Activation pompe");
              delay(150);
                  
              digitalWrite (Gate, HIGH); //Allumage du ventilo
                            
              digitalWrite(stepPin, HIGH);
              delayMicroseconds(1000);
              digitalWrite(stepPin, LOW);
              }


              else if (digitalRead(PIN_CAPTEUR_AQUA) == HIGH) 
              { // Si le pin du capteur de niveau bas est LOW (contact pullup fermé, flotteur haut), tu arrêtes les moteurs et le ventilo
                 
              //lcd.begin();
              //lcd.setCursor(2,0);
              //lcd.print("Niveau bon");
              //lcd.setCursor(0,1);
              //lcd.print("Arret pompe");
              //delay(1500);
              //lcd.clear();
                  
              digitalWrite (RELAY, LOW);  
              
              digitalWrite(ENPin, LOW);
              digitalWrite(stepPin, LOW);
              
              digitalWrite(ENPin_out, LOW);
              digitalWrite(stepPin_out, LOW);
              
              digitalWrite (Gate, LOW); //Exctintion du ventilo
                
              }

// Garde en mémoire la valeur actuelle de millis()
previousMillis2 = currentMillis;             
      }

}


void RESERVE()

{
static unsigned long previousMillis3 = 0;
unsigned long currentMillis = millis();

      // Si INTERVAL_1 ou plus millisecondes se sont écoulés
      if(currentMillis - previousMillis3 >= INTERVAL_3) 
      {

              if (digitalRead(PIN_CAPTEUR_IN) == LOW) 
              { // Si le pin du capteur de niveau bas est LOW (contact pullup fermé, flotteur haut), tu arrêtes les moteurs et le ventilo
              digitalWrite(Led_in, LOW);
              delay(1000);
              digitalWrite(Led_in, HIGH);
              delay(1000);  
              }
              
              else if (digitalRead(PIN_CAPTEUR_OUT) == LOW) 
              { // Si le pin du capteur de niveau bas est LOW (contact pullup fermé, flotteur haut), tu arrêtes les moteurs et le ventilo
              digitalWrite(Led_in, LOW);
              delay(100);
              digitalWrite(Led_in, HIGH);
              delay(100);
              }
              
// Garde en mémoire la valeur actuelle de millis()
previousMillis3 = currentMillis;             
      }

}

Bonjour,

Tu ne peux pas obtenir une exécution fluide de ton code si tu utilise la fonction delay().

Même si cette utilisation se limite à une portion qui n'est exécuté que de temps en temps, ton programme se bloquera donc de temps en temps.

Les variables previousMillis1, previousMillis2, previousMillis3 que tu déclares en début de programme sont des déclarations globales.

Elles sont donc accessibles depuis n'importe quel endroit de ton code... sauf si tu déclares des variables locales avec le même nom qui seront alors prioritaire.

Tes variables globales ne sont donc pas nécessaires et tu peux même, si tu le veux, utiliser des noms de variables locales identiques dans tes différentes fonctions.

Quand tu as des événements à surveiller, et en même temps tu dois faire des attentes, je te conseille l'organisation suivante:

1ère partie, tu fais toutes tes acquisitions de capteurs. Tu gardes les valeurs en mémoire.

2ème partie, tu exploites tes mesures pour voir si un événement s'est produit (par ex.dépassement d'un seuil).

3ème partie : tu vérifies s'il y a des attentes en cours (voir ci-après) et si l'une d'entre elle est échue, ça te donne un autre événement de type "Timer échu".

4ème partie : maintenant tu connais tout ce qui s'est passé d'intéressant depuis le dernier cycle. Tu peux faire les calculs qui vont t'amener à lancer (ou pas) des actions : allumer des leds, lancer une temporisation, faire péter l'alarme...

Ton chronomètre (fais-en un objet d'une classe que tu écris) doit gérer plusieurs temporisations en même temps. Pour chaque tempo., il conserve l'heure de départ (en millisecondes, obtenue par millis() au lancement de la tempo.) et la durée à attendre. Plus tard quand tu l'interroges, il appelle millis() pour connaitre l'heure courante, et il te renvoie la liste des temporisations qui viennent d'échoir. Puis il vire de sa liste les attentes terminées. Il a aussi une méthode qui permet de supprimer une attente qui est en train de courir.

Bonjour Zlika,

merci de ta réponse.
Pour la fonction Delay, c'est clair. Effectivement, ça me semble logique dorénavant. En fonction, ça ne pose pas de problèmes car il n'a que ça à faire. En multi tâches c'est la fonction Delay qui devient bloquante.

Pour la fonction Millis, de ce que je comprends, je pourrais la mettre en début de loop sans que cela ne change quoique ce soit.

Par contre je ne comprends pas ta dernière phrase "Tes variables globales ne sont donc pas nécessaires et tu peux même, si tu le veux, utiliser des noms de variables locales identiques dans tes différentes fonctions."

Merci

Bonjour Biggil,

merci également de ta réponse.

Pour la 1ére partie, je comprends dans la théorie. Mais en pratique un peu moins. En ligne de commande comment "interroger" le capteur et enregistrer sa valeur?

Pour la 2éme partie, donc aucune action à déclencher si je comprends bien plus reprendre l'état des capteurs pour les réexploiter plus tard dans le code en actions? Je comprends encore le principe dans ce cas. C est la philosophie du code qui m'échappe encore.

Pour la 3éme partie, cela implique donc la fonction Millis pour vérifier si le temps dédié à une condition "If" est échue ou non?

Pour la 4éme partie, ok ça forcément c'est les actions à mettre en place en fonction des résultats précedents, j'ai saisi.

"fais-en un objet d'une classe que tu écris" : Je ne comprends pas.

Globalement, je pense avoir saisi la philosophie de vos explications pour la gestion du temps (entre bloquant et non bloquant) ainsi que l'ordonnancement du code.
Là ou il me manque des connaissances, c'est sur la mise en oeuvre au niveau du code.

Je ne veux pas que l'on code pour moi, j'aime bien apprendre et comprendre. Je pars de loin, j'ai conscience de mes lacunes alors je me documente, il existe beaucoup d'exemples et de tutos grâce auxquels j'en suis arrivé là (je partais de "c'est quoi Arduino au fait", il y a 3 mois).

Alors encore merci pour votre aide et soutien.
Greg

Une variable déclarée en dehors de toute fonction est une variable globale.
Sa valeur persiste ton au long de l'exécution du programme et permet donc de partager de l'information entre chaques fonctions.

Une variable déclarée dans une fonction est une variable locale.
Elle est propre à la fonction et n'est pas accessible en dehors de la fonction. On peut la rendre persistante entre deux appel de la fonction en la déclarant 'static' , évitant ainsi l'utilisation d'une variable globale pouvant être modifiée par erreur depuis l'extérieur de la fonction, et limitant également le nombre de variables globales, et donc facilite la compréhension du programme.

Rien n'empêche l'utilisation d'un même nom de variable pour une variable globale et une variable locale.
A l'intérieur de la fonction ou une variable porte le même nom qu'une variable globale, c'est la variable locale qui sera utilisée.
Si dans la fonction, il n'existe pas de variable locale du même nom, alors c'est la variable globale qui est utilisée.

Variables globales et locales sont complètement indépendantes.

Dans ton programme tu déclares des variables globales, mais tu ne les utilises pas.
Ce sont les variables locales déclarées dans les fonctions que tu utilises.

Comme chaques variables locales ne sont accessible que dans la fonction ou elles sont déclarées, tu peux utiliser un même nom de variable dans chacune de tes fonctions. Elles ne seront pas en conflit.

Pour la fonction millis(), tu peux l'utiliser dans autant de fonctions différentes que nécessaire.

Par exemple si tu dois gérer le clignotement d'une led, tu peux simplement créer une fonction dans loop qui sera activée (test de condition) grâce à une variable booléenne globale pouvant être modifier (presque) partout dans ton programme.
Le temps sera évalué chaque tour grâce à millis() et tu pourras utiliser une variable locale 'static' pour calculer le prochaine événement (basculement de l'état de la Led) de la fonction.

Idem si tu dois scruter un clavier.
Une fonction indépendante dans la loop qui scan en permanence le clavier et modifié une variable si une touche est pressée.
Une autre fonction par la suite se chargera de lire la variable, de l'interpréter et d'agir en conséquence en appelant une autre fonction, ou en modifiant une variable que sera évaluée dans une autre fonction...

Merci Zlika pour ta pédagogie, c est clair.

Donc tu fais référence à mes variables globales telle que "previousMillis1" dans mon code avant mon setup et currentMillis comme variable locale dans mon loop?
Mais quelles seraient celles qui ne seraient pas utilisées dans mon code car je fais référence à toutes me semble-t-il?
Merci
Greg

Déclarations globales:

// Précédente valeur de millis() pour les différents void
unsigned long previousMillis1 = 0;
unsigned long previousMillis2 = 0;
unsigned long previousMillis3 = 0;

Déclarations locales:

void RTC()
{
static unsigned long previousMillis1 = 0;

void NIVEAU()
{
static unsigned long previousMillis2 = 0;

void RESERVE()
{
static unsigned long previousMillis3 = 0;

Les variables locales étant prioritaires sur les variables globales, tu n'utilise donc pas tes variables globales.

En activant le mode verbose du compilateur dans les paramètres de préférences de l'IDE, tu devrais voir un message d'information qui t'indique que ces variables ne sont pas utilisées.

greglamouche:
Pour la 1ére partie, je comprends dans la théorie. Mais en pratique un peu moins. En ligne de commande comment “interroger” le capteur et enregistrer sa valeur?

en ligne de commande ? ? ? on est pas en train de parler de ton programme Arduino ?
Je veux dire que si ton système a 3 boutons poussoirs, une température et une humidité (c’est un exemple simple, je n’ai pas étudié ton cas précis), et bien

  • tu mesures la température, tu la mets dans une variable Temp
  • tu mesures l’humidité, tu la mets dans un variable Humid
  • pour chaque bouton, tu regardes s’il est appuyé ou relâché, et tu gardes ça dans des variables bool.
    Tu as fait tes mesures. Fin de la partie 1

Pour la 2éme partie, donc aucune action à déclencher si je comprends bien plus reprendre l’état des capteurs pour les réexploiter plus tard dans le code en actions? Je comprends encore le principe dans ce cas.

Non tu n’as pas compris.
Tu dois maintenant, à partir de tes mesures, déterminer si quelque chose d’intéressant s’est passé.

Pour la température et l’humidité, ce peut être le franchissement d’un seuil. Par exemple, Temp > 50. ? Si oui, tu crées un évenement de type TEMP_SEUIL_HAUT (c’est toi qui définis les types d’événements). Cet événemement, tu le gardes dans une liste (car tu vas peut-être en avoir d’autres).

Fin de la partie 2 : événements liés aux mesures.

Pour la 3éme partie, cela implique donc la fonction Millis pour vérifier si le temps dédié à une condition “If” est échue ou non?

Tu as un objet Chronometre (les détails plus tard). Cet objet gère toutes les attentes (donc c’est lui qui utilise millis() ). Car il peut y avoir plusieurs attentes en même temps, décalées même. Voici ce que tu peux demander au Chronometre:

  • lancer une attente de X secondes.
  • annuler un attente qui a été lancée et pas encore échue,
  • Obtenir la liste de toutes les attentes qui viennent juste d’échoir (depuis la derniere fois que tu lui as demandé).
    Donc maintenant tu vas lui demander la liste des attentes échues. Pour certaines (toutes ?) tu vas créer un événement de type ATENTE_XXX_ECHUE, où tu remplaces XXX par le nom que tu donnes à ton attente.
    Tu rajoutes ces événements à la liste commencée en partie 2.

Pour la 4éme partie, ok ça forcément c’est les actions à mettre en place en fonction des résultats précedents, j’ai saisi.

Enfin, avant de lancer des actions, tu dois commencer par examiner la liste des événements viennent de se produire, pour déterminer quelles sont les actions à lancer.
Après tu lances des actions. Entre autres tu peux :

  • lancer une nouvelle attente
  • annuler une attente en cours car elle n’a plus de raison d’être (et tu ne veux pas récupérer un événement quand elle échoira)

“fais-en un objet d’une classe que tu écris” : Je ne comprends pas.

C’est la base du langage C++. Il faut absolument progresser en C++ si tu en est là. Voir Internet.
Je ne rigole pas !

Globalement, je pense avoir saisi la philosophie de vos explications pour la gestion du temps (entre bloquant et non bloquant) ainsi que l’ordonnancement du code.

Euuuh, à lire ton retour, j’en doute un peu (et même plus :slight_smile: )

Mias bon, c’est normal si tu débutes. Prends le temps de bien comprendre tout ça. Tu n’est pas obligé de faire comme ça, mais tu peux au moins retenir l’organisation par fonctions. Les débutants font de l’organisation par appareil (je lis le capteur 1, je fais des actions, puis le 2, etc…) et s’il y a des attentes ça coince.

Merci Zilka, c est plus clair dorénavant.

Je ne connaissais pas ce principe effectivement fondamental. Je ne lis pas suffisamment à tort les messages lors du téléversement.

Le problème est que j'essaie entre les tutoriels et les exemples, de l'appliquer à mon cas sans cerner convenablement la logique de programmation et la language associé.

Re Biggil,

Tout d'abord, merci du temps que tu m'accordes pour tâcher de m'expliquer.

(...) C'est la base du langage C++. Il faut absolument progresser en C++ si tu en est là. Voir Internet.
Je ne rigole pas !(...)

Un peu dur mais en même je comprends ta réaction. Il est vrai que je n'ai absolument pas le vocabulaire adapté et que j'ai beaucoup à apprendre sur le sujet.

Je débutes et même si j'ai parcouru beaucoup de tutoriels, il est difficile de tout assimiler correctement sans avoir quelqu'un pour te corriger au fur et à mesure, principe de la transmission de savoir.

Ta dernière phrase prends tout sons sens :
(...)
Les débutants font de l'organisation par appareil (je lis le capteur 1, je fais des actions, puis le 2, etc..) et s'il y a des attentes ça coince.
(...)

Effectivement c'est tout à fait mon mode de raisonnement que j'avais d'abord écris simplement sur papier pour essayer d'assimiler la logique et ensuite de la "traduire", un échec visiblement...

L'ordonnancement est plus clair même si je dois avouer que dorénavant je me sens beaucoup plus perdu.
Je dois naïvement avouer que j'étais fier d'avoir réussi à effectuer certaines actions non sans mal pour finalement m'apercevoir que rien ne va dans mon code et ma compréhension, ça mets un coup au moral et à la motivation :disappointed_relieved:

Bon plus qu'à persévérer et recommencer.

Merci pour votre temps.

Si tu veux vraiment te faire plaisir en développant tes programmes, il est indispensable de prendre le temps de comprendre le language C, les possibilité de ta carte, et commande y ajouter des composants ou modules pour parvenir à ce que tu veux.

Je te conseille fortement la lecture du site d'eskimon pour prendre conscience des possibilités de ta carte et comprendre comment la maîtriser (language C, bibliothèques, astuces de programmation...).

Avec la pratique et l'expérience, tes programmes seront plus simples et mieux organisés.

Tu pourras évidement compter sur la communauté Arduino pour t'aiguiller quand tu bloques sur la réalisation d'une fonction ou si tu veux une explication plus détaillée.

Tu as déjà bien commencé en faisant chaque fonctions séparément.

Tu dois justes apporter des modifications pour supprimer les delay() bloquant en modifier ton organigramme.

Je vais m'y rendre de suite et tâcher de revoir d'autres bases et mon programme.
Merci à vous deux.

Je te conseille également la lecture sur la machine à état qui va te permettre de mieux organiser ton code.

Top merci, un autre raccourci dans mes favoris. J’ai de quoi lire, heureusement j’avais déjà parcouru certaines notions.

eh, greglamouche, ça fait 45 ans que je ponds du code. Il ne faut pas t'excuser de débuter, ce n'est pas une faute.
Je ne suis pas dur en t'encourageant à aprendre le C++. Si tu veux progresser c'est la bonne route. Mais tu n'est pas obligé, tu peux rester en C, sans les objets.

C'est sûr que je n'ai pas opté pour la même approche que Zlika, qui t'aide sur ton code. Je que je t'explique est la programmation événementielle - par opposition à séquentielle.
En séquentiel, l'ordre des opérations est défini par avance. C'est valable pour les gros calculs.
En événementiel, le programme attend et réagit à des événements. C'est comme ça que fonctionnent toutes les interfaces graphiques (fenêtres sous Windows, Mac, Linux...)

Peut-être tu n'utiliseras jamais ce que je propose, pas grave, j'espère que ça t'aura ouvert des fenêtres insoupçonnées.