sleep mode con pwr_down_mode

salve gente,
sto provando a mandare in sleep Arduino UNO tenendo premuto il bottone. All’avvio dello sketch ho un led rosso che resta acceso e dalla seriale stampo “sono sveglio” finchè non premo il bottone e il tutto si blocca e va in sleep e compare la scritta “sto dormendo”. Quando rilascio il bottone dovrebbe rivegliarsi sempre e tornare il led acceso ma a volte non va così… cosa sto sbagliando?

grazie mille a tutti.

Questo è il mio codice:

#include <avr/sleep.h>

int wakePin = 2; // pin per il risveglio
int statoBottone = 0;
int rosso = 4;

void risveglio() {
  detachInterrupt(0);
  sleep_disable();
  delay(10);
}

void setup() {
  // put your setup code here, to run once:
  pinMode(wakePin, INPUT);
  pinMode(rosso, OUTPUT);
  digitalWrite(wakePin, HIGH);
  Serial.begin(9600);


}

void funzioneSleep()
{
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // tipo di modalità usata
  sleep_enable();  // abilito il bit sul registro MCUCR dell’AVR
  attachInterrupt(0, risveglio, FALLING); // uso il pin 2 per l’interrupt
  sleep_mode(); // dispositivo in sleep
  delay(1000);

}
void loop() {

  statoBottone = digitalRead(wakePin);

  if (statoBottone == HIGH) {
    digitalWrite(rosso, LOW);
    Serial.println("Sto dormendo");
    delay(500);
    funzioneSleep();
  }
  else {

    statoBottone = LOW;
    digitalWrite(rosso, HIGH);
    Serial.println("sono sveglio");
    delay(500);
    risveglio();
  }

}

Ti invitiamo a presentarti (dicci quali conoscenze hai di elettronica e di programmazione) qui: Presentazioni
e a leggere il regolamento se non lo hai già fatto: Regolamento
Qui una serie di link utili, non inerenti al tuo problema:

Il codice è un po' contorto, cosa fai nel loop? Perché è presente risveglio() in loop?
Comunque il tuo sembra un semplice problema di debouncing.

ciao Roberto, ho fatto un po’ di modifiche:

nel loop() controllo se il bottone è premuto, e in questo caso spengo il led.
Nel ramo else ho messo il risveglio() nel caso in cui rilascio il bottone e riattivo la scheda.

#include <avr/sleep.h>
#include <avr/power.h>

// sleep_mode_idle  - Turn off CPU clock
// sleep_pwr_save - Allows Timer2 to be running. Power Down + Timer.
// sleep_pwr_down - All clocks off. Cannot wake up from timer
// sleep_mode_ADC - ADC Noise Reduction. Turn of I/O clock in addition.
// standby


int wakePin = 2; // pin per il risveglio
int statoBottone = 0;
int rosso = 13;

void risveglio(void) {
  
  // sleep_disable();  
  detachInterrupt(0);  

}

void setup() {
  // put your setup code here, to run once:
  pinMode(wakePin, INPUT);
  pinMode(rosso, OUTPUT);
  //digitalWrite(wakePin, HIGH);
  attachInterrupt(0, risveglio, LOW);
  Serial.begin(9600);

}

void funzioneSleep(void)
{
  
  delay(100);
  set_sleep_mode(SLEEP_MODE_IDLE);       // tipo di modalità usata
  //set_sleep_mode(SLEEP_MODE_ADC);      // tipo di modalità usata
  //set_sleep_mode(SLEEP_MODE_PWR_SAVE); // tipo di modalità usata
  //set_sleep_mode(SLEEP_MODE_STANDBY);  // tipo di modalità usata
  //set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // tipo di modalità usata
  
  sleep_enable();  // abilito il bit sul registro MCUCR dell’AVR
  attachInterrupt(0, risveglio, LOW);
  // disabilito tutte le periferiche non utilizzate per
  // aumentare il risparmio ma soprattutto alcune di queste
  // potrebbero far risvegliare la scheda
  
//  power_adc_disable();
//  power_spi_disable();
//  power_timer0_disable();
//  power_timer2_disable();
//  power_twi_disable(); 
  
  
   // uso il pin 2 per l’interrupt
  sleep_mode(); // mando la scheda in sleep
  
  // disabilito lo sleep
  sleep_disable();
  detachInterrupt(0);
  
  // riattivo le periferiche
 // power_all_enable();

}
void loop() {

  statoBottone = digitalRead(wakePin);

  if (statoBottone == HIGH) {
    digitalWrite(rosso, LOW);
    Serial.println("Sto dormendo");
    delay(500);
    funzioneSleep();
   
  }
  else {
    risveglio();
    digitalWrite(rosso, HIGH);
    Serial.println("sono sveglio");
    delay(500);
    
  }

}

devo testare le varie sleep_mode, così dovrebbe andar bene giusto?

Il risveglio lo devi effettuare solo nell'interrupt, nel ciclo loop il microcontrollore è sempre sveglio!
Allora, in loop verifichi che sia stato premuto il pulsante, se è cosi attivi l'interrupt e lo fai addormentare. L'interrupt lo devi associare ad una funzione che si deve occupare di risvegliare il micro.
Quando è in sleep mode il loop si ferma e il micro risponde solo agli interrupt (questo in generale, poi esistono diverse modalità di sleep)

Roberto mi sono perso, ho aggiunto la millis() per paura che il debounce desse fastidio:

#include <avr/sleep.h>
#include <avr/power.h>

// sleep_mode_idle  - Turn off CPU clock
// sleep_pwr_save - Allows Timer2 to be running. Power Down + Timer.
// sleep_pwr_down - All clocks off. Cannot wake up from timer
// sleep_mode_ADC - ADC Noise Reduction. Turn of I/O clock in addition.
// standby


int wakePin = 2; // pin per il risveglio
int buttonState = LOW;
int rosso = 13;

int statoLed = -1;
long lastDebounceTime = 0;
long debounceDelay = 50;

void risveglio(void) {

  // sleep_disable();
  detachInterrupt(0);

}

void setup() {
  // put your setup code here, to run once:
  pinMode(wakePin, INPUT);
  pinMode(rosso, OUTPUT);
  //digitalWrite(wakePin, HIGH);
  attachInterrupt(0, risveglio, LOW);
  digitalWrite(rosso, HIGH);
  Serial.begin(9600);

}

void funzioneSleep(void)
{

  delay(100);
  // set_sleep_mode(SLEEP_MODE_IDLE);       // tipo di modalità usata
  //set_sleep_mode(SLEEP_MODE_ADC);      // tipo di modalità usata
  //set_sleep_mode(SLEEP_MODE_PWR_SAVE); // tipo di modalità usata
  //set_sleep_mode(SLEEP_MODE_STANDBY);  // tipo di modalità usata
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // tipo di modalità usata

  sleep_enable();  // abilito il bit sul registro MCUCR dell’AVR
  attachInterrupt(0, risveglio, LOW);
  // disabilito tutte le periferiche non utilizzate per
  // aumentare il risparmio ma soprattutto alcune di queste
  // potrebbero far risvegliare la scheda

  // power_adc_disable();
  //  power_spi_disable();
  //  power_timer0_disable();
  //  power_timer2_disable();
  //  power_twi_disable();


  // uso il pin 2 per l’interrupt
  sleep_mode(); // mando la scheda in sleep

  // disabilito lo sleep
  sleep_disable();
  detachInterrupt(0);

  // riattivo le periferiche
  // power_all_enable();

}
void loop() {

  buttonState = digitalRead(wakePin);

  if ( (millis() - lastDebounceTime) > debounceDelay) {

    if ( (buttonState == HIGH) && (ledState < 0) ) {

      digitalWrite(rosso, LOW);
      statoLed = -statoLed;
      lastDebounceTime = millis();
      Serial.println("Sto dormendo");
      delay(500);
      funzioneSleep();
    }
    else {
      digitalWrite(rosso, HIGH);
      Serial.println("sono sveglio");
      delay(500);
    }
  }
}

Allora ascoltami bene.
Monitoro la pressione di un pulsante, quando questo viene premuto eseguo un delay di qualche millisecondo (per evitare il floating del pulsante, qui il delay può andare perche stiamo per entrare in sleep, in casi generali sarebbe meglio usare millis) a questo punto imposto la modalità di sleep, attacco l'interrupt allo stesso pin dove è collegato il pulsante, ed entro in modalità sleep.
Ora nella funzione dell'interrupt risveglio il micro sleep_disable() distacco l'interrupt ed eseguo un delay giusto per un debouncing di sicurezza (questo debouncing invece sarebbe meglio implementarlo con la millis, ma in per un primo approccio delay puo andare).
Fine cosi controllo l'attivazione e la disattivazione della sleep.

ok, la situazione quindi sarebbe così:

#include <avr/sleep.h>
#include <avr/power.h>

// sleep_mode_idle  - Turn off CPU clock
// sleep_pwr_save - Allows Timer2 to be running. Power Down + Timer.
// sleep_pwr_down - All clocks off. Cannot wake up from timer
// sleep_mode_ADC - ADC Noise Reduction. Turn of I/O clock in addition.
// sleep_mode_standby


int wakePin = 2; 
int statoBottone = LOW;
int rosso = 13;


void risveglio(void) {
  
  sleep_disable();
  detachInterrupt(0);
  delay(100);
}

void setup() {
  // put your setup code here, to run once:
  pinMode(wakePin, INPUT);
  pinMode(rosso, OUTPUT);
  //digitalWrite(wakePin, HIGH);
  attachInterrupt(0, risveglio, RISING);
  //digitalWrite(rosso, HIGH);
  Serial.begin(9600);

}

void funzioneSleep(void)
{
   //set_sleep_mode(SLEEP_MODE_IDLE);       // tipo di modalità usata
  //set_sleep_mode(SLEEP_MODE_ADC);      // tipo di modalità usata
  //set_sleep_mode(SLEEP_MODE_PWR_SAVE); // tipo di modalità usata
  //set_sleep_mode(SLEEP_MODE_STANDBY);  // tipo di modalità usata
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // tipo di modalità usata

  sleep_enable();  // abilito il bit sul registro MCUCR dell’AVR

  noInterrupts();

  attachInterrupt(0, risveglio, RISING);

  interrupts();

  // disabilito tutte le periferiche non utilizzate per
  // aumentare il risparmio ma soprattutto alcune di queste
  // potrebbero far risvegliare la scheda

  // power_adc_disable();
  //  power_spi_disable();
  //  power_timer0_disable();
  //  power_timer2_disable();
  //  power_twi_disable();


  // uso il pin 2 per l’interrupt
  sleep_mode(); // mando la scheda in sleep

  // disabilito lo sleep
  sleep_disable();
  detachInterrupt(0);

  // riattivo le periferiche
  // power_all_enable();

}
void loop() {

  statoBottone = digitalRead(wakePin);

  if (statoBottone == HIGH)
  {
    delay(50);
    digitalWrite(rosso, LOW);
    Serial.println("Sto dormendo");
    delay(500);
    funzioneSleep();
  }
  else {
    digitalWrite(rosso, HIGH);
    Serial.println("sono sveglio");
    delay(500);
  }
}

Via l’attachInterrupt dalla setup, quello va fatto solo quando vuoi entrare in sleepmode.
Per il resto sembra possa andare, sinceramente non capisco il sleep_disable() sia in risveglio() che in funzioneSleep(), vedo che su internet molti lo fanno, ma sinceramente credo ne basti uno o nell’interrupt o subito dopo sleep_mode().
Prova e facci sapere.

L'ho messo anche sulla funzione risveglio() giusto per sicurezza... quello che mi chiedo ora... se utilizzo un'altra modalità di sleep, il codice andrà bene lo stesso o dovrò fare altri accorgimenti? Io pensavo di fare dei test per misurare il consumo energetico con tutte e 5 però ho paura che per ognuna di esse devo andare a modificare qualcosa...

edit: purtroppo sembra non funzionare bene, premo il bottone va in sleep, stampa "Sto dormendo" e non si sveglia più quando rilascio il bottone...

Puoi far riferimento al datasheet del ATmega328P da questo paragrafo.
Come vedi in ogni modalità il risveglio del micro è disponibile mediante gli interrupt INT0 e INT1, quindi non dovrebbero essere necessari dei cambiamenti.
La modalità power_down è la piu profonda dove il micro ha tutti i segnali di clock disabilitati.

Non riesco a capire cosa sto sbagliando, secondo me c'è una race condition in giro perchè con alcune modalità non si sveglia...

Fai un elenco di cosa funziona e cosa no, e un altra domanda, che tipo di oscillatore usi? o meglioche Arduino stai usando?

Uso Arduino Uno, in pratica si blocca al momento "dell'andare a dormire", ora sto cercando di capire se nella setup devo inizializzare il bottone digitalWrite(wakePin, HIGH); e poi nell'interrupt chiamarlo con FALLING o RISING..

Devi impostarlo come input, la modalità di interrupt è indifferente,in un pulsante le due condizioni si verificano in rapida successione, meglio scegliere risalita a parer mio, piu facile effettuare il debouncing. Non hai specificato in quali modalità funziona e in quali no.

Sì, infatti dentro la setup faccio:

void setup() {

pinMode(wakePin, INPUT);

digitalWrite(wakePin, HIGH);

e poi nell'interrupt chiamo la RISING

con la IDLE sembra andare,
con la ADC premo una volta, va in sleep, poi si sveglia va in sleep e basta
con la pwr_save sembra adare,
con la standby sembra funzionare,
con pwr_down va in sleep e addio...

Il debounce si fa hardware ! ... specialmente sugli interrupt ! ... :stuck_out_tongue: :smiley:

@Roberto: mi sono accorto di una cosa dentro la sleep... avevo messo la chiamata nointerrupts() DOPO la sleep_enable().. ora l'ho messo prima e la pwr_down funziona... possibile?

In quel caso è possibile che spostare la disabilitazione degli interrupt contribuisca al debouncing.
Etemananki hai assolutamente ragione, anche io sono della stessa opinione, però ho riscontrato in un mio progetto di poco tempo fa che ho discusso per giorni qui sul forum che il debouncing HW non bastava, era sempre necessario disabilitare l'interrupt quando si verificava e riabilitarlo altrove dopo un determinato tempo. Astrobeed spingeva molto sul fatto che non ci si possa affidare solo ad un debouncing HW con gli interrupt e purtroppo la mia esperienza diretta gli ha dato ragione anche se non comprendo ancora bene il perché.

Sto notando che se tengo premuto più di 3 secondi il bottone alcune modalità vanno a ortiche....gli interrupt sono troppo veloci come faccio a gestire questa cosa?