Réelle question de réel débutant sur les interruptions

Bonjour à tous,

d'abord je me présente : CREB, utilisateur (vraiment newbie) ARDUINO, dont je souhaite me servir pour domotiser quelque peu mon domicile.
J'aurais pu faire simple en achetant des systèmes tout prêts, mais je suis tombé amoureux d'Arduino...

Alors voilà mon souci:
je suis en train de monter un système d'ouverture / fermeture de mes volets battants.
Toute la partie électrique et mécanique est prête et fonctionne très bien en manuelle, mais je rencontre un problème avec mon programme test.
Ma séquence se décompose comme suit:

  1. mise en marche d'un avertisseur sonore et lumineux pour prévenir de l'activité prochaine des volets
  2. après 5 secondes, un relais vient mettre un moteur dans la bonne polarité (soit pour l'ouverture soit pour la fermeture)
  3. après une demi-seconde le moteur se met en route jusqu'à ce qu'une fin de course soit détectée sur l'interruption 1
  4. dès contact en fin de course, le moteur s'arrête
  5. après une demi seconde , le relais se remet en position de repos
  6. après 1.5 secondes l'avertisseur se coupe

jusque là tout va bien avec un de mes programmes de test. Le souci se pose car je souhaite mettre en interruption 0 un bouton d'arrêt de séquence au cas où...
tout s'arrête rapidement, mais le souci est que après l'arrêt se soit réalisé, le programme continue, et remet tout en marche.

En gros je souhaiterais savoir comment on peut revenir au tout début de la fonction loop après une interruption

voici mon code si quelqu'un peut m'aider (j'ai remplacé sur cet exemple mon interruption générée par les fins de course par une temporisation de 10 secondes pour ne pas trop embrouiller l'affaire):

// Définition des variables

const int relaismoteur = 4; // le relais de contrôle du moteur est branché sur la broche 4
const int relaisinverseur = 5; // le relais de contrôle de l'inverseur est branché sur la broche 5
const int relaisavertisseur = 8; // le relais de contrôle de l'avertisseur est branché sur la broche 8
const int boutonouverture = 6; // le bouton d'ouverture est branché sur la broche 6
const int boutonfermeture = 7; // le bouton de femreture est branché sur la broche 7
const int findecourse = 3; // les fins de course sont branchées sur la broche 2 (interruption 0)
const int boutonarret = 2; // le bouton d'arrêt est branché sur la broche 3 (interruption 1)
int etatboutonouverture = LOW; // le bouton d'ouverture est déclaré comme relaché
int etatboutonfermeture = LOW; // le bouton de fermeture est déclaré comme relaché
int etatfindecourse = LOW; // les fins de course sont déclarées comme relachées
int etatboutonarret = LOW; // le bouton d'arrêt est déclaré comme relaché

// Initialisation
void setup()
{
pinMode (relaismoteur, OUTPUT); // la broche du relais de contrôle moteur (broche 4) est une sortie
pinMode (relaisinverseur, OUTPUT); // la broche du relais de contrôle de l'inverseur (broche 5) est une sortie
pinMode (relaisavertisseur, OUTPUT); // la broche du relais de contrôle de l'avertisseur d'activité (broche 8) est une sortie
pinMode (boutonouverture, INPUT); // la broche du bouton d'ouverture (broche 6) est une entrée
pinMode (boutonfermeture, INPUT); // le broche du bouton de fermeture (broche 7) est une entrée
pinMode (findecourse, INPUT); // la broche des capteurs de fin de course (broche 3) est une entrée (interruption 1)
pinMode (boutonarret, INPUT); // la broche du bouton d'arrêt de séquence (broche 2) est une entrée (interruption 0)
}

// boucle principale
void loop()
{
attachInterrupt (0, procedurearret, CHANGE); // l'interruption 0 (broche 2) déclenchée par le changement détat du bouton d'arrêt de séquence lance l'exécution de la fonction "procedurearret"
digitalWrite (relaismoteur, HIGH); // le relais de contrôle du moteur est inactif (moteur arrêté)
digitalWrite (relaisinverseur, HIGH); // le relais inverseur est inactif (position de fermeture)
digitalWrite (relaisavertisseur, HIGH); // le relais de contrôle de l'avertisseur d'activité est inactif (avertisseur à l'arrêt)
etatboutonouverture = digitalRead (boutonouverture); // etatboutonouverture correspond à la lecture de l'état du bouton d'ouverture (broche 6)
etatboutonfermeture = digitalRead (boutonfermeture); // etatboutonfermeture correspond à la lecture de l'état du bouton de fermeture (broche 7)
if (etatboutonouverture == LOW) // si le bouton d'ouverture est appuyé...
{
procedureouverture(); // ... lancement de la fonction "procedureouverture"
}
else if (etatboutonfermeture == LOW) // sinon, si le bouton de fermeture est appuyé...
{
procedurefermeture(); // lancement de la fonction "procedurefermeture"
}
}

// Procédure d'ouverture
void procedureouverture()
{
delay (1000); // attendre 1 seconde
digitalWrite (relaisavertisseur, LOW); // activer le relais de contrôle de l'avertisseur d'activité (avertisseur d'activité en marche)
delay (5000); // attendre 5 secondes
digitalWrite (relaisinverseur, LOW); // activer le relais inverseur (mise en position d'ouverture)
delay (500); // attendre 0,5 seconde
digitalWrite (relaismoteur, LOW); // activer le relais de contrôle du moteur (moteur en marche)
delay (10000); // attendre 10 secondes
digitalWrite (relaismoteur, HIGH); // désactiver le relais de contrôle du moteur (moteur arrêté)
delay (500); // attendre 0,5 secondes
digitalWrite (relaisinverseur, HIGH); // désactiver le relais inverseur (mise en position de fermeture)
delay (1500); // attendre 1,5 secondes0
digitalWrite (relaisavertisseur, HIGH); // désactiver le relais de contrôle de l'avertisseur d'activité (avertisseur d'activité arrêté)
}

// Procédure de fermeture
void procedurefermeture()
{
delay (1000); // attendre 1 seconde
digitalWrite (relaisavertisseur, LOW); // activer le relais de contrôle de l'avertisseur d'activité (avertisseur d'activité en marche)
delay (5000); // attendre 5 secondes
digitalWrite(relaisinverseur, HIGH); // désactiver le relais inverseur (mise en position de fermeture)
delay(500); // attendre 0,5 seconde
digitalWrite (relaismoteur, LOW); // activer le relais de contrôle moteur (moteur en marche)
delay (10000); // attendre 10 secondes
digitalWrite (relaismoteur, HIGH); // désactiver le relais de contrôle moteur (moteur arrêté)
delay (500); // attendre 0,5 seconde
digitalWrite (relaisinverseur, HIGH); // désactiver le relais inverseur (mise en position de fermeture)
delay (1500); // attendre 1,5 secondes
digitalWrite (relaisavertisseur, HIGH); // désactiver le relais de contrôle de l'avertisseur d'activité (avertisseur d'activité arrêté)
}

// Procédure d'arrêt de séquence
void procedurearret()
{
digitalWrite (relaismoteur, HIGH); // désactiver le relais de contrôle moteur (moteur arrêté)
delay (500); // attendre 0,5 seconde
digitalWrite (relaisinverseur, HIGH); // désactiver le relais inverseur (mise en position de fermeture)
delay (3000); // attendre 3 secondes
digitalWrite (relaisavertisseur, HIGH); // désactiver le relais de contrôle de l'avertisseur d'activité (avertisseur d'activité arrêté)
}

Merci d'avance et énormément pour votre aide

Bonjour,
Je pense qu'un reset serait la solution la plus simple pour revenir au début de loop().
En effet, une fois qu'une interruption a été déclenchée puis exécutée, le programme reprend exactement là où il s'était arrêté. Par la même occasion, je doute que les delay() fonctionnent durant l'interruption dans la fonction procedurearret() car delay() fonctionne grâce aux interruptions :grin: . Enfin c'est un autre problème...

Pour déclencher une procédure de reset via le programme, on peut par exemple utiliser une sortie de l'arduino qu'on raccorde à la pin reset. Si vous passez cette pin à LOW durant l'interruption, l'effet sera le même que si vous appuyez sur le bouton reset. Seulement il ne faut pas que l'Arduino se reset indéfiniment...

Pour ça :
Dans le setup :

void setup() {
    pinMode(RESET_PIN, INPUT);   //Toute pin est en INPUT par défaut mais autant prévenir...
    digitalWrite(RESET_PIN, LOW);  //On désactive le pull-up sur la pin pour pouvoir reset l'Arduino manuellement avec     le bouton...
}

Dans l'interruption :

procedurearret() {
     //Dernières actions du programme avant le reset
     pinMode(RESET_PIN, OUTPUT);   //On force le niveau 0 sur la pin reset
     while(1);   //Boucle infinie pour empêcher les moteurs de se réactiver dangereusement pendant le cours laps de temps nécessaire au reset 
}

Et du côté électronique donc, un fil qui relie la pin RESET_PIN que vous choisirez différente de 0 ou 1, à la pin qui s'appelle reset en bas à gauche de l'Arduino UNO :slight_smile:
Après il y a d'autres solutions comme pour la plupart des problèmes...

En espérant vous aider ! :sweat_smile:

P.S. : C'est une bonne habitude de mettre le attachInterrupt() dans le setup :wink:

Je confirme : les delay et les interruptions ça ne va pas bien ensemble, et à la sortie d'une fonction appelée par une interruption, le programme reprend la ou il a été interrompu ...

une autre solution que le reset de la carte :
Dans les fonctions procedureOuverture/Fermeture ... Il faudrait surveiller en continu l'état d'une variable qui serait modifiée par l'interruption ... Mais ça revient à ne pas utiliser de delay dans ces procédures car le programme est bloqué pendant les delay

Pas trop le temps de tout écrire mais regarde les exemple blink without delay pour lancer des actions a des dates précises, sans bloquer le programme avec des delay... Et une boucle while qui aurait comme condition de sortie une variable qui serait modifiée par l'interruption

Volatile boolean arreter // état false par defaut sauf si interruption

Setup
AttatchInterrupt ...

Loop
Arreter=false //reset des éventuelles interruptions
If bouton... Ouverture
If bouton... Fermeture

Void prodecurearret();
Arreter=true;

Void ouverture();
While arreter=false
//exécution des actions aux dates voulues mais sans utliser delay
// on quitte le while avec un break quand la dernière etape est terminée
//fin du while

// si on quitte le while car arreter=true ... On lance l'arrêt d'urgence
// sinon, la procédure s'est terminée normalement

C'est super clair, n'est-il pas ?

voila ma version détaillée pour une gestion des interruptions et sans utilisation de delay ... il reste à faire la procédure de fermeture

volatile boolean interruption;  //variable modifiée lors de l'interruption, à déclarer en volatile
volatile boolean findecourse;   //variable modifiée lors de l'interruption fin de course, à déclarer en volatile

void setup() {
  // fonctions liées aux interruptions
  attachInterrupt (0, procedurearret, RISING);   // ou FALLING mais si tu mets CHANGE la procédure est appelé à l'appui et au relachement
  attachInterrupt (1, procedurefindecourse, ??); // procedure d'arret quand fin de course atteinte
  
  //suite de l'initialisation
  
}

// boucle principale
void loop()
{
  //je mettrai ces trois lignes dans le setup, car on termine chaque mouvement en désactivant déjà ces 3 relais
  digitalWrite (relaismoteur, HIGH); // le relais de contrôle du moteur est inactif (moteur arrêté)
  digitalWrite (relaisinverseur, HIGH); // le relais inverseur est inactif (position de fermeture)
  digitalWrite (relaisavertisseur, HIGH); // le relais de contrôle de l'avertisseur d'activité est inactif (avertisseur à l'arrêt)
  
  //attente d'action sur les boutons
  etatboutonouverture = digitalRead (boutonouverture); // etatboutonouverture correspond à la lecture de l'état du bouton d'ouverture (broche 6)
  etatboutonfermeture = digitalRead (boutonfermeture); // etatboutonfermeture correspond à la lecture de l'état du bouton de fermeture (broche 7)
  if (etatboutonouverture == LOW) // si le bouton d'ouverture est appuyé...
  {
    procedureouverture(); // ... lancement de la fonction "procedureouverture"
  }
  else if (etatboutonfermeture == LOW) // sinon, si le bouton de fermeture est appuyé...   
  {
    procedurefermeture(); // lancement de la fonction "procedurefermeture"
  }
  interruption = false; //reset si il y a eu interruption
  findecourse = true;   //reset de fin de course ? à voir
}

// Procédure d'arrêt de séquence par interruption
void procedurearret() {
  interruption = true;
}

// Procédure d'arrêt en fin de course atteinte
void procedurefindecourse() {
  findecourse = true;
}

// Procédure d'ouverture
void procedureouverture() {
  unsigned long debut = millis(); //on mémorise la date du début de la procédure
  unsigned long chrono;           //vriable pour le calcul du temps écoulé depuis l'appui sur le bouton
  
  //on allume immédiatement l'avertisseur (on a un retour immédiat de l'appui sur le bouton)
  digitalWrite (relaisavertisseur, LOW); // activer le relais de contrôle de l'avertisseur d'activité (avertisseur d'activité en marche)
  
  //appel des actions de démarrage dans l'ordre chronologique ... en surveillant l'interruption éventuelle
  while (arreter == false) {  //tant que la procédure n'a pas été arretée par interruption
    chrono = millis() - debut;  //calcul du temps écoulé depuis le début de la procédure
    //au bout de 5s, on actionne le relai inverseur
    if (chrono > 5000) digitalWrite(relaisinverseur, LOW); // active le relais inverseur au bout de 5s (mise en position d'ouverture)
    //au bout de 5.5s, on actionne le relai moteur
    if (chrono > 5500) {
      digitalWrite (relaismoteur, LOW);   // activer le relais de contrôle moteur au bout de 5.5s (moteur en marche)
      break;      //le moteur est lancé, le volet s'ouvre... on sort d'ici
    }
    //il n'y a pas de delay dans cette boucle, elle tourne à fond la caisse et surveille en permanence la variable arreter
  }
  
  //on arrive ici soit parce que la séquence de démarrage a été lancée entièrement
  //soit parce qu'elle a été arrêtée par l'interruption (arreter = true)
  //si elle a été interrompue on agit en conséquence et on sort d'ici
  if (arreter == true) {
    //appel de la procédure d'arret d'urgence
    procedureStop();
    return; //on quitte cette séquence et on retourne dans le loop
  }
  
  //si on arrive ici, c'est que le volet est en cours de mouvement, 
  //on attend :
  //    soit la fin de course, 
  //    soit un arret par interruption, 
  //    soit une durée maxi (arret meme si la findecourse était ratée .. ça peut arriver)
  while (arreter == false && findecourse == false && chrono < 30000) {
    chrono = millis() - debut; //actualisation du chrono
  };
  
  //on arrive ici pour 3 raisons possibles :
  //soit par arret venant de l'interruption
  //soit par atteinte de la fin de course ou durée maxi atteinte

  if (arreter == true) { //ici le mouvement a été interrompu
    //appel de la procédure d'arret d'urgence
    procedureStop();
    return;
  }
  //if (findecourse == true || chrono > 30000) { //ici la fin de course est atteinte ou le temps maxi est dépassé
  //appel de la procedure de fin de mouvement (peut être la même que procedureStop() .. à toi de voir...)
  procedureFin();
  
  //that's all...on retourne tranquillement dans le loop
}

void procedureStop() {
  //arret en cours de mouvement (on peut laisser les delay ici), 
  //ca bloquera toute action tant que la procedure d'arret n'est pas terminée
  digitalWrite (relaismoteur, HIGH);    // désactiver le relais de contrôle moteur (moteur arrêté)
  delay (500); // attendre 0,5 seconde
  digitalWrite (relaisinverseur, HIGH); // désactiver le relais inverseur (mise en position de fermeture)
  delay (3000); // attendre 3 secondes
  digitalWrite (relaisavertisseur, HIGH); // désactiver le relais de contrôle de l'avertisseur d'activité (avertisseur d'activité arrêté)
}

void procedureFin() {
  //arretsur fin de mouvement (on peut laisser les delay ici), 
  //ca bloquera toute action tant que la procedure d'arret n'est pas terminée
  digitalWrite (relaismoteur, HIGH);    // désactiver le relais de contrôle moteur (moteur arrêté)
  delay (500); // attendre 0,5 seconde
  digitalWrite (relaisinverseur, HIGH); // désactiver le relais inverseur (mise en position de fermeture)
  delay (3000); // attendre 3 secondes
  digitalWrite (relaisavertisseur, HIGH); // désactiver le relais de contrôle de l'avertisseur d'activité (avertisseur d'activité arrêté)
}

Merci à tous pour votre aide.
c'est vrai que je n'avais pas pensé à faire un RESET complet de la carte (je vous l'ai dit : je débute...).
Après modifications, mon système fonctionne parfaitement.
Il ne me reste plus qu'à compléter mon programme qui tourne sur UNO pour le moment pour qu'il puisse prendre en charge les 2 volets sur une MEGA (avec les interruptions supplémentaires).

@ bientôt je pense... et encore un grand merci pour vos réponses.