Software / Watchdog e risparmio energetico ATtiny85

salve, sono alle prime armi nel mondo della programmazione.

Ho comprato un AT Tiny 85 per riuscire a svolgere piccoli azioni riducendo al massimo gli spazi ed i consumi.
Come primo obiettivo ho cercato di far lampeggiare un led per 2 minuti, allo scadere dei due minuti, tenere acceso il secondo led per 1 minuto.
Fin qui, tutto ok, il mio codice funziona.

Ho deciso, quindi, di voler ridurre ancora i consumi e cercando su internet vari esempi di sleep_mode, watchdog e qualsiasi altra cosa sono capitato su un blog italiano che spiega bene come far lampeggiare un LED e limitando i consumi.

Faccio copia incolla del codice, modifico l'uscita del LEd ma il codice non funziona, questo perchè quello che nella guida relativa a ATmega328p viene chiamato WDTCSR sul mio AT Tiny 85 si chiama invece WDTCR, modificato questo il codice funziona.

Il problema sorge quando vorrei fare l'operazione che ho descritto all'inizio.

vi lascio tutti i riferimenti necessari per potermi aiutare:

//varie librerie per lo sleep mode 
#include <Arduino.h>
#include <WProgram.h> 
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/power.h>
//alcune variabili sono state dichiarate in precedenti versioni o sono per fare dei tentativi
int out1 = A2;
int out2 = A3;
byte statouscita1 = 1;
byte statouscita2 = 1;
int t = 0; //primo counter per tenere acceso un led 1 minuto
int s = 0; //secondo counter per tenere acceso l'altro led 5s
volatile unsigned int counter = 0;
volatile byte flag = 0;

void setup() 
{        
  MCUSR = 0; //resetta il registro di stato della MCU
  wdt_disable(); //disattiva il watchdog  
  pinMode(out2, OUTPUT);
  pinMode(out2, OUTPUT);
  setWdt(); //imposta il watchdog
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}


void loop() 
{
  power_all_disable();
  sleep_mode(); //cpu a nanna - corrisponde a sleep_enable+sleep_cpu+sleep_disable
  power_all_enable();
  //qui inizia il punto cruciale che non mi torna, ci sono varie prove ma continua a comportarsi in maniera che non comprendo
  if (flag)
  {
    wdt_disable();
    counter = 0;
    flag = 0;
    statouscita1 ^= 1; //cambio lo stato al led
    digitalWrite(out1, statouscita1);
    if(t=5)
    {
      digitalWrite(out1,LOW);
      digitalWrite(out2,HIGH);
      delay(5000);
      digitalWrite(out2,LOW);
    }
    t=0;
    setWdt(); //reimposta il watchdog
  }
}

void setWdt() {
  SREG &= ~(1<<SREG_I); //disattiva tutti gli interrupt
  //imposta il registro del watchdog
  WDTCR  |= ((1<<WDCE) | (1<<WDE));
  //imposta la modalità "interrupt" ed il timeout ad 1 secondo
  WDTCR  = ((1<<WDIE)| (1<<WDP2) | (1<<WDP1)); 
  SREG |= (1<<SREG_I); //riattiviamo gli interrupt globali
}
// come funziona esattamente questa funzione ?
ISR(WDT_vect) {
  t++;
  if (++counter >= 1) { //impostare qui il numero di timeout (secondi)
    flag = 1;
  }
}

Sito riguardo il watchdog: http://www.leonardomiliani.com/2013/impariamo-ad-usare-il-watchdog-2/

Datasheet Atmel ATtiny: http://www.atmel.com/images/atmel-2586-avr-8-bit-microcontroller-attiny25-attiny45-attiny85_datasheet.pdf (a pagina 45 parla del WDTCR )

PS: questo forum è davvero complicato o.o

Ti invitiamo a presentarti (dicci quali conoscenze hai di elettronica e di programmazione) qui: Presentazioni
e a leggere il regolamento: Regolamento

Sono l'autore degli articoli di quel sito, anzi a dirla tutta quel sito è il mio :wink:
Ovviamente quando si cambia MCU codici che usano i registri sono radicati sull'hardware in modo profondo per cui bisogna stare attenti a tutto.

Inoltre nel tuo codice c'è un errore:

if(t=5)

Questo è sbagliato, i confronti si fanno col doppio segno uguale:

if(t==5)

Ottimo consiglio Leo72, che svista. non me ne ero proprio accorto!
Ho fatto qualche modifica al codice e ora sembra fare quello che voglio, devo solo ricontrollare i tempi ma è un problema secondario.
Questo è il codice che utilizzo ora, nei commenti ci sono alcune note o domande che vorrei farvi.

//sapevo che alcuni compilatori escludevano direttamente le librerie che difatto non venivano usate 
//funziona lo stesso per il compilatore di Arduino ? 
#include <Arduino.h>
#include <WProgram.h> 
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/power.h>
//variabili
int out1 = A2;
int out2 = A3;
byte statouscita = 1;
int t = 0; //primo counter per tenere acceso un led 1 minuto
volatile unsigned int counter = 0;
volatile byte flag = 0;

void setup() 
{        
  MCUSR = 0; //resetta il registro di stato della MCU
  wdt_disable(); //disattiva il watchdog  
  pinMode(out2, OUTPUT);
  pinMode(out2, OUTPUT);
  setWdt(); //imposta il watchdog
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}


void loop() 
{
  power_all_disable();
  sleep_mode(); //cpu a nanna - corrisponde a sleep_enable+sleep_cpu+sleep_disable
  power_all_enable();
  //"punto cruciale"
  if (flag)
  {
    wdt_disable();
    counter = 0;
    flag = 0;
    statouscita ^= 1; //cambio lo stato al led
    digitalWrite(out1, statouscita);
    if(t==10) 
    {
      digitalWrite(out1,LOW);
      digitalWrite(out2,HIGH);
      delay(5000);
      digitalWrite(out2,LOW);
      t=0;
    }
    setWdt(); //reimposta il watchdog
  }
}

void setWdt() {
  SREG &= ~(1<<SREG_I); //disattiva tutti gli interrupt
  //imposta il registro del watchdog
  WDTCR  |= ((1<<WDCE) | (1<<WDE)); //qui è il watchdog time control registrer di cui parlavo
  //imposta la modalità "interrupt" ed il timeout ad 1 secondo
  WDTCR  = ((1<<WDIE)| (1<<WDP2) | (1<<WDP1)); 
  SREG |= (1<<SREG_I); //riattiviamo gli interrupt globali
}
// come funziona esattamente questo punto ? io sfrutto questo "clock" per incrementare il contatore che mi permette di spegnere il led1 e accendere il led2 ad un certo momento.
ISR(WDT_vect) {
  if (++counter >= 1) { //impostare qui il numero di timeout (secondi) - timeout di cosa?
    flag = 1;
    t++;
  }
}

allora sfutto l'occasione per farti i complimenti per l'ottimo materiale online!

Altra cosa, nella ISR c'è il controllo su una variabile, counter, e tu ne hai messa un'altra, t. Ora, non sono stato a controllare come la usi ed a cosa ti serve, ma ovviamente il mio codice funzionava in una certa maniera. Se aggiungi/modifichi/togli dei pezzi poi è normale che non funzioni più come dovrebbe :stuck_out_tongue:
Cerchiamo quindi di capire questo:

Come primo obiettivo ho cercato di far lampeggiare un led per 2 minuti, allo scadere dei due minuti, tenere acceso il secondo led per 1 minuto.

Che significa "far lampeggiare un led per 2 minuti"?
Poi "tenere acceso il secondo led per 1 minuto"?
Quindi hai 2 led che si accendono in sequenza? E come pensi di risparmiare energia se hai dei led accesi? Quelli consumano diversi mA di corrente l'uno. Cerca di spiegare meglio quello che stai cercando di fare, come lo stai facendo e quello che vorresti ottenere. Vediamo se riesco a darti una mano :wink:

Scusami, pensavo fosse chiaro !

Allora, ora ho editato il codice, se puoi dare un occhiata giusto al primo commento e all'ultimo.

Per la storia del Led è così:

C'è il LED1 che lampeggia (si accende 1 secondo poi si spegne 1 secondo) per due minuti, allo scadere due due minuti il LED1 si spegne per un minuto e si accende il LED2 per un minuto, allo scadere del minuto LED2 si spegne e rinizia da capo!

Con il codice che ho postato ora riesco a gestire abbastanza bene la situazione

In questo modo risparmi solo quando nessuno dei 2 led è acceso. Quando ne è acceso 1 solo, risparmi al 50%.
Chiarito questo, dovresti modificare il codice per usare il watchdog solo come contasecondi e poi nel loop principale controllare:

  1. quale led accendere
  2. se nessuno dei 2 led è acceso, mettere in sleep il chip
    Non ricordo con esattezza se mettendo in sleep le uscite vengono resettate o meno, prova. Ma in questo caso il risparmio sarebbe limitato dal led stesso che "succhia" corrente.

Quindi tu hai un ciclo principale di 180 secondi (3 minuti).
Fino a 120 secondi, fai lampeggiare il led 1, dal 121° al 180° minuto fai lampeggiare l'altro.

Già un consumo dimezzato può essere conveniente secondo me..

Il punto che non mi è chiaro è come usare il watchdog come contasecondi.

ISR(WDT_vect) {
   if (++counter >= 1) { //impostare qui il numero di timeout (secondi)
      flag = 1;
   }
}

è per caso qui il punto sul quale devo riflettere?

La ISR, per com'è ora, viene chiamata ogni secondo, ed ogni secondo incrementa counter. Se "counter" è maggiore di 1 (attualmente) viene messo ad 1 "flag". In questo modo dal codice principale sai che è passato l'intervallo che ti serve.
Ovviamente è stata scritta per un certo modo di operare del codice esempio. Tu potresti togliere questa parte e controllare sempre i secondi trascorsi dato che ad ogni interrupt il chip viene risvegliato. Ci sono tanti modi per fare quel che vuoi, comunque.

Ok! grazie delle info, farò alcuni tentativi!

#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/power.h>
const byte LED1 = 3;
const byte LED2 = 4;

byte led1Status = 1;
byte led2Status = 0;

volatile unsigned int counter = 0;

void setup() {
    MCUSR = 0; //resetta il registro di stato della MCU
    wdt_disable(); //disattiva il watchdog

    //imposto i LED
    pinMode(LED1, OUTPUT);
    pinMode(LED2, OUTPUT);
    digitalWrite(LED1, led1Status); //stato iniziale acceso
    digitalWrite(LED2, led2Status); //stato iniziale acceso
    setWdt(); //imposta il watchdog
    //imposta lo sleep
    set_sleep_mode(SLEEP_MODE_PWR_DOWN); //modalità di sleep - POWER DOWN
}
void loop() {
    power_all_disable();
    sleep_mode(); //cpu a nanna - corrisponde a sleep_enable+sleep_cpu+sleep_disable
    power_all_enable();
    wdt_disable();
    //fai qui quello che devi fare
    if (counter >= 0 && counter < 120) { //blinka il primo led
        led1Status ^= 1; //cambio lo stato al led
        digitalWrite(LED1, led1Status);
    }
    if (counter >= 120 && counter < 180) {
        led2Status ^= 1; //cambio lo stato al led
        digitalWrite(LED2, led2Status);
    }
    if (counter >= 180) {
        counter = 0;
    }
    setWdt(); //reimposta il watchdog
}


void setWdt() {
    SREG &= ~(1<<SREG_I); //disattiva tutti gli interrupt
    //imposta il registro del watchdog
    WDTCR |= ((1<<WDCE) | (1<<WDE));
    //imposta la modalità "interrupt" ed il timeout ad 1 secondo
    WDTCR = ((1<<WDIE)| (1<<WDP2) | (1<<WDP1)); 
    SREG |= (1<<SREG_I); //riattiviamo gli interrupt globali
}


ISR(WDT_vect) {
    counter++;
}

Questo funziona, Controlla i consumi e poi fammi sapere.

Mi è venuta in mente un'altra miglioria, per ottimizzare i consumi. Però devi fare dei test.Ora ti spiego...ù
Invece di pilotare i LED con le uscite digitali, usa le uscite PWM: metti ad esempio un analogWrite(128) che è un duty cycle del 50%. Avrai una luminosità inferiore ma un consumo dimezzato con il LED on perché starà acceso per il 50% del tempo e spento per altrettanto periodo.... MA... c'è un MA. Devi sostituire l'istruzione power_all_disable();
(e la corrispondente "enable") perché questa disattiva tutte le periferiche interne, compresi i timer, quindi non ti andrebbe il PWM. Vedi un pò tu qual'è quella energeticamente più vantaggiosa

Grazie mille davvero per aver "preso a cuore" il mio problema!
Ora mi leggo il codice e faccio altre prove!
Grazie ancora!

Interessante anche la storia delle pwm in effetti, mi metto subito a guardarmi come usarle che sinceramente non le ho mai usate :smiley:

Discussione interessante.

Dunque, ho cambiato un attimo il codice per fare quello che dovevo fare

 if (counter >= 0 && counter < 12) {
        led1Status ^= 1; //cambio lo stato al led
        digitalWrite(LED2, LOW); //Non so se è una soluzione elegante la mia, ma funziona
        digitalWrite(LED1, led1Status);
    }
    if (counter >= 12 && counter < 18) {
        //led2Status ^= 1; //cambio lo stato al led
        digitalWrite(LED2, HIGH);
    }
    if (counter >= 18) {
        counter = 0;
        led1Status ^= 1; //ho dovuto fare così per evitare che al secondo ciclo si accendessero tutti e due i led
    }
    setWdt(); //reimposta il watchdog
}

In seguito mi sono riguardato le pwm, si utilizzano con la funzione analogWrite e ok, il punto è che come mi avevi fatto notare la funzione power_all_disable(); disattiva ogi modulo del processore, non mi pare di trovare una alternativa economicamente vantaggiosa a questa funziona sinceramente, perchè comunque le altre sembrano disattivare solo uno o due moduli del processore.
Come riferimento ho utilizzato questo sito: avr-libc: <avr/power.h>: Power Reduction Management )

Appunto, disattiva i moduli che NON ti servono.
Se fai le cose bene, puoi agganciare i 2 PWM allo stesso timer. Usando il timer 0, hai OCR0A sul pin D0 e OCR0B sul pin D1.
A questo punto puoi disattivare tutto il resto che non ti serve: Timer 1, USI e ADC.

Puoi fare tutto con un'unica istruzione:

PRR |= ((1<<PRTIM1) | (1<<PRUSI) | (1<<PRADC));

per riattivare:

PRR &= ~((1<<PRTIM1) | (1<<PRUSI) | (1<<PRADC));

Sul perché e sul come ti rimando alla lettura del datasheet dell'Attiny85, capp 7 e nello specifico 7.5.2 per i flag del registro di riduzione dei consumi.
Ah, altra cosa. Prima di togliere il clock all'ADC, esso andrebbe disattivato con:

ADCSRA &= ~(1<<ADEN);

(datasheet cap. 17.13.2)

Interessante, appena riesco (causa lavoro sti giorni vado a rilento.-.) mi do una letta a quei paragrafi! Grazie ancora, davvero.