Problème interruption en mode veille [Résolu]

Bonjour,
J'ai un programme en mode veille qui fonctionne bien avec un réveil tout les 8 secondes .
Le problème est l'ajout d'un bouton à 2 actions :

  • une action instantanée qui fonctionne
  • une deuxième action quand l'appui est maintenu 2 secondes.

Cette deuxième action ne fonctionne pas en mode veille. J'utilise la librairie Low-Power/powerDownWakeExternalInterrupt.ino at master · rocketscream/Low-Power · GitHub

N'étant pas très familier avec l'anglais, pouvez-vous me montrer ou est mon erreur ?

#include <LowPower.h>

const byte interruptPin = 2;
const byte led = 13 ; //

volatile bool etatBP = 0;
bool acquitBP = 0;
unsigned long chrono = 0; // débutappui bouton
unsigned long chrono1 = 0; // fin appui bouton
void setup()
{
  // initialisation entree/sortie
  pinMode(interruptPin, INPUT_PULLUP);
  pinMode(led, OUTPUT);
  etatBP = 0;

}
void pressBP() {// appui bouton sur interruption
  etatBP = 1 ;
}

void loop() {
  attachInterrupt(0, pressBP, FALLING);
  LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
  detachInterrupt(0);
  if (etatBP)//appuie bouton
  {
    digitalWrite(led, 1); delay(20); digitalWrite(led, 0); // action1
    chrono = millis();
    etatBP = 0;
    acquitBP = 1;
  }
  if (!digitalRead(interruptPin)  && acquitBP) {// appui maintenu
    if (millis() - chrono > 2000) {
      digitalWrite(led, 1); delay(200); digitalWrite(led, 0); // action2
      acquitBP = 0;
    }
  }
// le reste du programme
}// fin de loop

Millis ne tourne plus pendant que vous êtes en sleep.
De plus si vous tenez le bouton appuyé vous n’aurez plus de front FALLING

Falling déclenche la première action dès l'appui bouton.
Ce que j'aurais voulu, c'est que le mode veille ne redémarre qu'au relaché du bouton.

On ne peut peut-être pas utiliser le bouton d'interruption pour un appui long ?

Edit : Il semble que c'est ça le problème. Pas grave j'ajouterai un bouton. C'était pour un usage exceptionnel, pour enregistrer une valeur en EEPROM.

Merci pour ta réponse, JML

Il suffit de ne pas rendormir l’arduino tant que le bouton n’est pas relâché. Il faut donc rendre la loop un peu plus intelligente (si le bouton est appuyé ne pas faire sleep )

Je suppose que vous avez un pull down externe ?

pull down ? un bouton externe oui.

et comment ça marche avec cette librairie ?
Dans ce dernier code, à la sortie de veille, les fonctions s'exécutent entièrement et retour en veille automatiquement quand l'action est terminée.

Ça fonctionne sans le detachInterrupt.
Si je mets RISING, l'action 1 se déchenche au relaché du bouton, mais toujours pas l'action2 ?

OK dans le dernier code je vois que vous avez le PULLUP interne.

  pinMode(interruptPin, INPUT_PULLUP);
  ...
  attachInterrupt(digitalPinToInterrupt(interruptPin), pressBP, RISING);

si le bouton est câblé PIN 2 --- BOUTON --- GND alors le bouton au repos est HIGH et lors de l'appui du bouton on passe à LOW. C'est donc FALLING qui devrait déclencher l'interruption?

ensuite si vous voulez attendre avant de dormir que le bouton soit relâché il suffit de tester la condition dans la loop

  if (digitalRead(interruptPin) == LOW) {
    // ici utiliser des variables pour voir si vous venez d'appuyer ou si 
    // la loop est en train de tourner dans ce cas mesurer le temps qui passe
    // pour faire autre chose au bout de 2 secondes d'appui
    ...
  } else {
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF); // on s'endort
    // ici on se réveille, préparer les variables utiles (raz du compteur de 2 secondes etc)
    ...
  }

Cette partie de code ne mesure pas le temps d'appui sur le bouton tel que tu le voudrais.
Lorsque le CPU se réveille sur appui bouton tu arrives directement sur ce if, le temps écoulé et évidemment inférieur à 2 secondes donc tu en sors immédiatement et repars en veille.
Il faudrait faire une boucle while(digitalRead(interruptPin)) et

  • soit tester si les 2 secondes sont écoulées et forcer une sortie de la boucle,
  • soit tester le temps écoulé en sortant de la boucle.
1 Like

Merci, ça fonctionne.
Depuis des déboires avec While en programmation basic (ZX81) je ne l'employait plus.

Merci aussi pour l'explication fdunews,

Le code modifié

#include <LowPower.h>

const byte interruptPin = 2;
const byte led = 13 ; //

volatile bool etatBP = 0;
bool acquitBP = 0;
unsigned long chrono = 0; // débutappui bouton
unsigned long chrono1 = 0; // fin appui bouton
void setup()
{
  // initialisation entree/sortie
  pinMode(interruptPin, INPUT_PULLUP);
  pinMode(led, OUTPUT);
  etatBP = 0;
  attachInterrupt(0, pressBP, FALLING);
}
void pressBP() {// appui bouton sur interruption
  etatBP = 1 ;
}

void loop() {
  LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);

  if (etatBP)// bouton appuyé
  {
    digitalWrite(led, 1); delay(20); digitalWrite(led, 0); // action1
    chrono = millis();
    etatBP = 0;
    acquitBP = 1;
  }
  while (!digitalRead(interruptPin) && acquitBP) {
    if (millis() - chrono > 2000) { // bouton maintenu appuyé 2 secondes
      digitalWrite(led, 1); delay(200); digitalWrite(led, 0); // action2
      acquitBP = 0;
    }
  }
  // le reste du programme
}// fin de loop

disons que mettre un while dans la loop c'est un peu contre productif puisque la loop() boucle déjà. c'est pour cela qu'on vous proposait plutôt un if() {} else {} afin de laisser la boucle tourner (si vous voulez faire autre chose en attendant le relâchement du bouton par exemple)

C'est vrai qu'un while est bloquant . Dans ce cas, ce n'est pas gênant, le retour en veille se fait dès que le bouton est relaché. 15 mA/ 5µA
Comment faire alors pour empêcher le retour en veille ?

comme proposé en #6. On ne fait le LowPower.powerDown()que quand le bouton est relâché

J'avais déjà essayé de cette manière, mais ça ne marchait pas avec cette librairie et faudrait que je refasses le reste du programme, par contre ça fonctionnait avec un ancien code https://forum.arduino.cc/uploads/short-url/z0WK69Zh3YKJu1BRcykgLYiNPfx.ino

Je ne vous pas ce que la bibliothèque change mais il fallait adapter un peu le code

Ça ne fonctionne pas. Peut-être qu'avec if (millis() - chrono > 2000)
le temps d'inaction est trop long, retour en veille et du coups, perte de l'info millis() ?

Mais il ne faut pas retourner en veille tant que le bouton est enfoncé
Il faut aussi gérer les rebonds du bouton

Bonjour,
Merci pour ta persévérance,
Je ne doute pas qu'il y ait une solution dans ce sens, mais ne suis pas assez féru ...

Je n'ai pas de soucis avec un rebond et l'interruption.
Je n'ai pas trouvé comment empêcher le retour en veille
J'ai essayé de mettre un delay(50) un peu partout sans succès.

La solution du while me va bien. Je n'attends que des évènements lents qui fonctionnent même avec plusieurs minutes de blocage système et puis dans ce cas, le blocage est volontaire, contrôlé.
En tout cas j'ai compris qu'avec cette librairie, on pouvait utiliser millis() à condition d'être "englobée" dans une fonction bloquante.

Salut,

voilà un bout de code à tester avec le moniteur série réglé à 115200 bauds pour expliquer ce qu'il se passe (et il y a le clignotement de la LED comme vous le faisiez)

J'ai tapé cela ici, il se peut qu'il y ait des bugs mais ça devrait vous donner une idée:

#include <LowPower.h>

const byte pinBouton = 2;
const byte pinLed = LED_BUILTIN;
bool sleepOK = true;
volatile bool afficheDeuxSecondes;
volatile bool appuiBouton = false;
volatile uint32_t chrono;
uint32_t copieChrono;
uint32_t blinkChrono;

void bouton() {
  appuiBouton = true;
  afficheDeuxSecondes = true;
  chrono = millis();
}

void setup() {
  Serial.begin(115200);
  Serial.println(F("\nTest sleep")); Serial.flush();
  pinMode(pinBouton, INPUT_PULLUP);
  pinMode(pinLed, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(pinBouton), bouton, FALLING);
}

void loop() {
  if (appuiBouton) {
    Serial.println(F("Reveil par appui bouton.")); Serial.flush();
    // section critique pour copier les variables modifiables dans l'ISR
    noInterrupts();
    blinkChrono = copieChrono = chrono;
    appuiBouton = false;
    interrupts();
    // fin de section critique  
    sleepOK = false;  // on pourra se rendormir que lorsque le bouton sera relâché
    digitalWrite(pinLed, HIGH); delay(20); digitalWrite(pinLed, LOW);
  }

  if (sleepOK) {
    Serial.println(F("Je vais dormir.")); Serial.flush();
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
    Serial.println(F("Je suis réveillé.")); Serial.flush();
  } else { // on n'a pas encore le droit de dormir
    if (digitalRead(pinBouton) == LOW) {        // si le bouton est appuyé
      if (millis() - copieChrono >= 2000) {     // a-t-on atteint les 2s ?
        if (afficheDeuxSecondes) {              // pour ne faire l'affichage qu'une fois
          Serial.println(F("Appui de deux secondes atteint. On clignote.")); Serial.flush();
          afficheDeuxSecondes = false;
        }
        if (millis() - blinkChrono >= 200) {    // doit-on inverser l'état de la LED ?
          digitalWrite(pinLed, (digitalRead(pinLed) == HIGH ? LOW : HIGH));
          blinkChrono = millis();
        }
      }
    } else {                                    // sinon le bouton est relâché
      Serial.println(F("Bouton relâché.")); Serial.flush();
      digitalWrite(pinLed, LOW);
      sleepOK = true;
    }
  }
}

Le principe c'est que lors de l'appui du bouton ça déclenche l'ISR où on met la variable appuiBouton à vrai et on enregistre le moment dans chrono.

Comme l'ISR a réveillé l'arduino, on devrait voir * Je suis réveillé.* qui s'affiche, puis la loop() boucle et on va détecter appuiBouton qui est vrai et donc afficher Reveil par appui bouton.. On en profite pour copier chrono, on désactive l'appui bouton mais on met sleepOKà faux pour empêcher le code de se rendormir. Ensuite dans la loop on testera ce sleepOKpour voir quand on relâche le bouton, sinon on regarde la durée de l'appui.

il y a une petite variable en plus afficheDeuxSecondes qui ne sert à afficher qu'une seule fois le fait que le bouton a été tenu enfoncé plus de 2 secondes.

Le clignotement se fait comme dans l'exemple "blink without delay", j'ai une variable temporelle blinkChrono qui me dit quand alterner l'état de la LED.

Essayez :slight_smile:

1 Like

Merci et bravo, belle démonstration, mais ça me dépasse.
A 69 balais c'est plus difficile de retenir les nouveautés.
les 0/1 me parlent plus que true/false

Plein de fonctions inconnues pour moi
Serial.flush(); noInterrupts(); interrupts(); ...

3 variables temporelles, ça se complique
Pourquoi ne pas utiliser chrono dans sleepOK ?
L'antirebond est géré ?

J'ai compris comment faire simplement avec if... else, n'autoriser la mise en veille qu'après le relâché du bouton.
Quelques variables renommées pour être plus parlant :

/*
   fonctionne bien
   Il fallait retourner en veille après le relaché du bouton
*/
#include <LowPower.h>
const byte boutonPin = 2;
const byte ledPin = LED_BUILTIN;

volatile bool action1 = 0;
bool action2 = 0;
unsigned long chrono = 0; // débutappui bouton

void setup()
{
  // initialisation entree/sortie
  pinMode(boutonPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
  action1 = 0;
  attachInterrupt(digitalPinToInterrupt(boutonPin), bouton, FALLING);
}

void bouton() {// appui bouton sur interruption
  action1 = 1 ;
}

void loop() {
  if (!digitalRead(boutonPin)) {// bouton appuyé
    if (action1)// dès l'appui
    {
      digitalWrite(ledPin, 1); delay(20); digitalWrite(ledPin, 0); // action1
      chrono = millis();  // démarre le compteur
      action1 = 0; // validé
      action2 = 1; // autorise la suivante
    }
    if (action2 && millis() - chrono > 2000) {// après 2 secondes
      digitalWrite(ledPin, 1); delay(200); digitalWrite(ledPin, 0); // action2
      action2 = 0; // validé
    }
  }
  else LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);// bouton relaché
  // le reste du programme
}// fin de loop

Très bien.
Il y a les delay de 200ms que je voulais éviter si vous souhaitez vous rendormir dès le relâchement