Domanda molto tecnica su timer/int/sleep

Chiedo aiuto, un grooosso aiuto.
E' da ieri che sto "giocando" con i timer, gli interrupt e la modalità sleep.

Vorrei capire una cosa, che devo poi applicare alla mia stazione meteo per ridurre i consumi.

La condizione di test è questa:

  • Atmega
  • contatore sul Timer2 abilitato con interrupt all'overflow in modo da ottenere un overflow ogni 1 ms
  • ad ogni overflow incremento una variabile: raggiunto il valore di 1000 (ossia 1 secondo), eseguo un'operazione di flash di un led
  • nel loop ho un'unica istruzione per la "messa in letargo" del micro
  • ho attivato l'INT0 affinché svegli il micro alla pressione di un pulsantino, bloccando il timer2
  • una volta risvegliato il micro, attende finché l'utente non preme un pulsante e poi rimette in letargo il micro e riattiva il timer2

Il codice è questo:

#include <avr/interrupt.h> //per usare gli interrupt
#include <avr/sleep.h> //per mettere in letargo il micro

int contatore=0;
byte flag=0;

void setup(){
    pinMode(13, OUTPUT);
    pinMode(7, INPUT);
    digitalWrite(7, HIGH); //attivo la pull-up interna sul pin 7

    //imposto il timer 2
    //Prescaler/64,
    TCCR2A |= (1<<CS22); 
    TCCR2A &= ~((1<<CS21) | (1<<CS20)); 
    // Modalità normale: incrementa il contatore fino all'overflow
    TCCR2A &= ~((1<<WGM21) | (1<<WGM20)); 
    TCCR2B &= ~(1<<WGM22);
    // Usa il clock intero dell'Atmega come fonte di clock per il prescaler
    ASSR |= (0<<AS2);
    // Attiva un segnale di interrupt all'overflow del contatore
    TIMSK2 = (1<<TOIE2); 
    /*valore iniziale del contatore: 6. Questo perché 1/(CLOCK_MICRO_IN_HZ/PRESCALER/(VALORE_CONTATORE))
    restituisce i ms prima che il contatore vada in overflow, quindi 1/(16000000/64/(256-6)=0,001s ossia 
    1 ms esatto. */
    TCNT2=6;
    //attiva l'interrupt
    SREG|=1<<SREG_I;
    
    set_sleep_mode(SLEEP_MODE_PWR_SAVE);   // setta registri per il modo sleep
    sleep_enable();                        // abilita la sleep all'uso
}


/*Routine di interrupt legato al timer 2 */
ISR(TIMER2_OVF_vect) {
    contatore++;
    if (contatore>=1000) {
        contatore=0;
        digitalWrite(13, flag?1:0); //led sul pin 13 alternato ON/OFF ogni 1 sec.
        flag^=1;
    }
}


void loop() {
    letargo(); //metto il micro in legargo
}


void risveglio() {
    SREG&=~(1<<SREG_I); //disattivo l'interrupt sul timer2
    digitalWrite(13, LOW); //spengo il led
    do {
    } while (digitalRead(7)!=LOW); //aspetto finché l'utente non preme il pulsantino
    TCNT2=6;
    SREG|=1<<SREG_I; //riattivo il timer 2
}


//routine da eseguire per mettere in letargo il micro
void letargo() {
    attachInterrupt(0, risveglio, RISING); // riattiva l'interrupt 0: controlla se arriva un segnale di salita verso HIGH
    ADCSRA &= ~(1 << ADEN); //spenge l'ADC
    sleep_mode();           // mette in letargo il micro
    sleep_disable();        // esce dal letargo e riporta il micro a piena operatività
    detachInterrupt(0);  // disattiva l'interrupt
}

La domanda è questa:
quando il timer2 va in overflow (1 volta ogni millisecondo) e si attiva il relativo interrupt, il micro si risveglia oppure no dallo sleep? Oppure il codice dell'interrupt viene eseguito con il micro che resta a nanna? Non capisco questo, ecco. La logica mi dice "no", ossia che il micro viene risvegliato perché la CPU, per poter eseguire del codice, deve "funzionare": se il suo clock è fermo, non può lavorare.
E' importante capire questa cosa alla luce dell'ottimizzazione dei consumi.

secondo mè, in questo caso il micro si risveglia completamente ed esegue le istruzioni da sveglio.
ricorda che il timer2 può lavorare sia in maniera sincrona che asincrona, ed i comportamenti rispetto alle modalità di risparmio energetico del micro, possono essere differenti a seconda del tipo di funzionamento del timer.

leo72:
La domanda è questa:

Si ma quanto vale, è quella da 300000 o da 1000000 ?

quando il timer2 va in overflow (1 volta ogni millisecondo) e si attiva il relativo interrupt, il micro si risveglia oppure no dallo sleep? Oppure il codice dell'interrupt viene eseguito con il micro che resta a nanna?

E le quattro opzioni dove sono ?

Ok, scherzi a parte quando il micro viene posto in sleep l'esecuzione del programma si arresta subito dopo l'esecuzione dell'istruzione che lo pone in sleep.
Come sai sugli ATmega esistono vari livelli di sleep tutti accomunati dal blocco dell'esecuzione del programma, ma con varie possibilità per tenere le periferiche attive, il livello con il minor consumo di energia, il power down, spegne tutti i clock interni bloccando di fatto tutte le periferiche lasciando attivo solo il risveglio da interrupt esterno su un pin, pura logica combinatoria che non richiede nessun clock, ed eventualmente da watchdog se esplicitamente lasciato attivo.
Negli altri livelli di sleep è possibile lasciare attive varie periferiche, nel livello idle, quello a maggior consumo, tutte le periferiche sono attive ed è possibile il risveglio tramite un qualunque evento che genera un interrupt.
Nel tuo caso ogni volta che il timer va in overflow, con relativo interrupt, il micro si risveglia, esegue l'interrupt e rimane attivo fino a che non lo rimetti in sleep.

Quindi è come pensavo io.
Però vorrei un altro chiarimento. Astrobeed, tu dici:

astrobeed:
Nel tuo caso ogni volta che il timer va in overflow, con relativo interrupt, il micro si risveglia, esegue l'interrupt e rimane attivo fino a che non lo rimetti in sleep.

Ecco, mi puoi spiegare questa cosa: dopo il primo overflow (1 ms) il micro viene risvegliato dall'interrupt, aggiorna la variabile contatore e poi... poi cosa fa? Rende il controllo alla funzione loop()? Se così fosse, secondo logica il micro verrebbe rimesso subito in letargo dato che l'unica istruzione contenuta nel loop è proprio quella di mettere in letargo il micro. Oppure dovrei rimettere in letargo il micro dall'interno della funzione di interrupt (cosa poco logica)?

leo72:
Ecco, mi puoi spiegare questa cosa: dopo il primo overflow (1 ms) il micro viene risvegliato dall'interrupt, aggiorna la variabile contatore e poi..

Non appena si risveglia viene eseguita la prima istruzione abbinata alla ISR.
Terminata la ISR il programma torna alla funzione dove hai attivato la sleep per eseguire l'istruzione subito dopo e così via con tutte le altre.
Nel tuo caso non serve nemmeno eseguire la sleep_disable() e la detachInterrupt(0) perché non fai nulla dopo il risveglio, quelle due istruzioni servono solo se devi eseguire vari compiti tra uno sleep, e l'interrupt va disattivato solo se non ti serve durante l'esecuzione normale del programma.

astrobeed:
Nel tuo caso non serve nemmeno eseguire la sleep_disable() e la detachInterrupt(0) perché non fai nulla dopo il risveglio, quelle due istruzioni servono solo se devi eseguire vari compiti tra uno sleep, e l'interrupt va disattivato solo se non ti serve durante l'esecuzione normale del programma.

Ho capito. Ma questo programma è un test per quello che sto scrivendo per gestire la stazione meteo. Lì avrò un processo del genere:

  • sleep in modalità POWER_SAVE (quella più "aggressiva" che riesce però a tenere il timer 2 attivo in mod. asincrona)
  • il timer 2 mi aggiorna un mio contatore
  • al trascorrere di XX minuti eseguo una lettura dei sensori e salvo i dati su EEPROM
  • se l'utente preme un pulsantino, devo risvegliare comunque il micro, disattivare il timer 2 ed interagire con l'utente
  • quando l'utente ha terminato di fare ciò che deve fare, rimetto il micro in sleep e riattivo il timer 2

Quindi quando risveglio il micro con l'INT0 devo disattivarlo perché devo usare lo stesso pulsantino (insieme ad un altro) per interagire con l'utente. Anzi, dovrò anche disattivare temporaneamente il timer 2, come detto, perché non vorrei eseguire una lettura mentre magari l'utente sta ricopiando i dati da EEPROM a SD.

Per accendere/spegnere il tuo LED perché non usi il modo CTC (invece di quello overflow) ed eviti così istruzioni software nella routine di interrupt?

Come ho scritto, questo è un programma di test, mi serve cioè solo per valutare il reale e corretto funzionamento del codice. Poi verrà integrato in un progetto più grande: in questo progetto c'è la gestione di un contatore e di alcune variabili di checl. Il LED lo usavo solo per capire esternamente se il programma faceva quello che volevo io XD