Arduino · 19 ottobre 2013 0

Impariamo ad usare il Watchdog (2)

Watchdog (Atmel Atmega328P)

Watchdog (Atmel Atmega328P)

Nel precedente articolo sul watchdog abbiamo visto come usare questa periferica per resettare il microcontrollore nel caso il nostro codice finisca in un vicolo cieco.

Con l’articolo di oggi vediamo invece un altro utilizzo del watchdog, ossia quello di semplice generatore di interrupt, utile ad esempio per svegliare la MCU da uno stato di sleep senza usare interrupt esterni o i timer del microcontrollore.

Il watchdog è una periferica che può operare in differenti modalità:

watchdog_modesQuella che abbiamo usato nello scorso articolo è la “System Reset Mode”: in caso di overflow del contatore interno del watchdog, viene generato un segnale di reset. Un’altra modalità è la “Interrupt And System Reset”, usata nel leOS2: la prima volta che il contatore va in overflow, viene sollevato un interrupt; la seconda volta, se il corrispondente flag non viene reimpostato, il microcontrollore viene resettato. La modalità che studieremo oggi è quella più semplice, la “Interrupt Mode”: ogni volta che il contatore va in overflow, viene sollevato un interrupt (un comportamento simile a quello degli altri timer della MCU).

A cosa può servire questa modalità? Essa può ad esempio essere utilizzata per mettere il chip in sleep e risvegliarlo ad intervalli regolari, in modo da risparmiare energia, cosa utile nel caso il nostro progetto sia alimentato a batterie e basato su un chip in standalone (senza tutta la scheda Arduino).

Per far ciò dobbiamo però ricorrere alla manipolazione diretta di alcuni registri del microcontrollore perché la libreria wdt.h che è presente nella toolchain Avr non prende in considerazione la modalità “Interrupt”. Per far ciò abbiamo bisogno del datasheet (o scheda tecnica) del microcontrollore che andremo ad utilizzare. Nel caso dell’Arduino UNO, stiamo lavorando sull’Atmega328P. Il datasheet ci ricorda, a pagina 50, che per modificare il registro WDTCSR che controlla il watchdog dobbiamo seguire una procedura particolare, che consiste nell’impostare ad 1 in questo registro 2 bit particolare (WDCE e WDE) prima di apportare le modifiche che ci occorrono, e di cambiare i bit che servono entro i successivi 4 cicli di clock, una sorta di “sistema di sicurezza” che evita scritture casuali sul registro che potrebbero avere conseguenze pericolose (reset infiniti).

Per l’occasione ci serve anche la libreria sleep.h per la gestione del risparmio energetico, un’altra libreria presente nella toolchain Avr. Lo sketch che segue cambia lo stato del led integrato dell’Arduino UNO ogni x secondi. Durante la maggior parte del tempo il micro è in modalità power down, una modalità di sleep profondo che permette il massimo risparmio energetico. Se si vuole aumentare il risparmio energetico, si può includere la libreria power.h e disattivare tutte le periferiche inutilizzate. Nel caso dello sketch in esempio, ho spento tutte le periferiche perché inutilizzate: sarà cura dell’utente adattarlo in modo da lasciare accese le periferiche in uso.

#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/power.h>
const byte LED13 = 13;
byte ledStatus = 1;
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(LED13, OUTPUT); //pin 13 in OUTPUT
   digitalWrite(LED13, ledStatus); //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();
   if (flag) {
      wdt_disable();
      counter = 0;
      flag = 0;
      //fai qui quello che devi fare
      ledStatus ^= 1; //cambio lo stato al led
      digitalWrite(LED13, ledStatus);
      setWdt(); //reimposta il watchdog
   }
}
void setWdt() {
   SREG &= ~(1<<SREG_I); //disattiva tutti gli interrupt
   //imposta il registro del watchdog
   WDTCSR |= ((1<<WDCE) | (1<<WDE));
   //imposta la modalità "interrupt" ed il timeout ad 1 secondo
   WDTCSR = ((1<<WDIE)| (1<<WDP2) | (1<<WDP1)); 
   SREG |= (1<<SREG_I); //riattiviamo gli interrupt globali
}
ISR(WDT_vect) {
   if (++counter >= 1) { //impostare qui il numero di timeout (secondi)
      flag = 1;
   }
}

Il codice è semplice e ben commentato. Nel setup abbiam prima di tutto la disattivazione del watchdog (per motivi di sicurezza, nel caso facessimo qualcosa di errato nelle impostazioni ed il micro si resettasse da solo), poi impostiamo la periferica e la modalità di risparmio energetico. Nel loop l’istruzione principale è sleep_mode, che mette in sleep il micro in attesa dell’interrupt generato dal watchdog. Quando l’ìinterrupt viene sollevato, la CPU esegue la corrispondente ISR che aggiorna il contatore di overflow. Nel loop controlliamo se è arrivato il momento di eseguire l’azione temporizzata (nella ISR basta contare gli overflow desiderati), nel nostro caso semplicemente facciamo lampeggiare il led integrato, e poi riattivare il watchdog.

[notice]

Nota per gli utilizzatori dell’Arduino MEGA/MEGA2560: il bootloader di queste schede non disattiva il Watchdog per cui se si intende utilizzare questa periferica è necessario utilizzare un bootloader modificato che può essere prelevato da qui. Tale modifica è necessaria perché l’esecuzione del bootloader impedisce l’immediata disattivazione del watchdog portando ad un reset infinito della scheda.

[/notice]