Utilizzare AVR Watchdog come un normale ISR


17

Sto cercando di avvolgere la testa attorno al timer del watchdog della serie ATTinyX5. Quindi le cose che ho letto mi hanno fatto sembrare che potresti usarlo per fare in modo che il programma faccia qualcosa di specifico ogni N secondi, ma non ha mai mostrato come. Altri hanno fatto sembrare che avrebbe ripristinato solo il chip a meno che qualcosa nel reset del codice non sia conteggiato nel frattempo (che sembra essere l'uso "normale").

Esiste un modo di utilizzare il WDT come TIMER1_COMPA_vect o simile. Ho notato che ha una modalità di timeout di 1 secondo e mi piacerebbe davvero poterlo usare per far accadere qualcosa ogni 1 secondo nel mio codice (e preferibilmente dormire in mezzo).

Pensieri?

* Aggiornamento: * Da quando è stato chiesto, mi riferisco alla sezione 8.4 della scheda tecnica ATTinyX5 . Non che lo capisca perfettamente, che è il mio problema ...


1
+1 per pensare fuori dagli schemi. Pollice in più se aggiungi un link al foglio dati per l'AVR.
jippie,

Risposte:


23

Certamente puoi. Secondo la scheda tecnica, il timer del watchdog può essere impostato per ripristinare l'MCU o causare un interrupt quando si attiva. Sembra che tu sia più interessato alla possibilità di interrompere.

Il WDT è in realtà più facile da installare rispetto a un normale Timer per lo stesso motivo per cui è meno utile: meno opzioni. Funziona su un clock 128kHz calibrato internamente, il che significa che il suo timing non è influenzato dalla velocità di clock principale dell'MCU. Può anche continuare a funzionare durante le modalità di sospensione più profonde per fornire una fonte di sveglia.

Esaminerò un paio di esempi di schede tecniche e alcuni codici che ho usato (in C).

File e definizioni inclusi

Per iniziare, probabilmente vorrai includere i seguenti due file di intestazione affinché le cose funzionino:

#include <avr/wdt.h>        // Supplied Watch Dog Timer Macros 
#include <avr/sleep.h>      // Supplied AVR Sleep Macros

Inoltre, uso la macro <_BV (BIT)> che è definita in una delle intestazioni AVR standard come la seguente (che potrebbe essere più familiare per te):

#define _BV(BIT)   (1<<BIT)

Inizio del codice

Quando la MCU viene avviata per la prima volta, in genere si inizializzano l'I / O, si impostano i timer, ecc. Da qualche parte qui è un buon momento per assicurarsi che il WDT non abbia causato un reset perché potrebbe farlo di nuovo, mantenendo il programma in un ciclo instabile.

if(MCUSR & _BV(WDRF)){            // If a reset was caused by the Watchdog Timer...
    MCUSR &= ~_BV(WDRF);                 // Clear the WDT reset flag
    WDTCSR |= (_BV(WDCE) | _BV(WDE));   // Enable the WD Change Bit
    WDTCSR = 0x00;                      // Disable the WDT
}

WDT Setup

Quindi, dopo aver impostato il resto del chip, ripetere il WDT. L'impostazione del WDT richiede una "sequenza temporizzata", ma è davvero facile da fare ...

// Set up Watch Dog Timer for Inactivity
WDTCSR |= (_BV(WDCE) | _BV(WDE));   // Enable the WD Change Bit
WDTCSR =   _BV(WDIE) |              // Enable WDT Interrupt
           _BV(WDP2) | _BV(WDP1);   // Set Timeout to ~1 seconds

Naturalmente, i tuoi interrupt dovrebbero essere disabilitati durante questo codice. Assicurati di riattivarli in seguito!

cli();    // Disable the Interrupts
sei();    // Enable the Interrupts

Routine di servizio di interruzione WDT La prossima cosa di cui preoccuparsi è la gestione dell'ISR WDT. Questo viene fatto come tale:

ISR(WDT_vect)
{
  sleep_disable();          // Disable Sleep on Wakeup
  // Your code goes here...
  // Whatever needs to happen every 1 second
  sleep_enable();           // Enable Sleep Mode
}

MCU Sleep

Invece di mettere l'MCU in modalità di sospensione all'interno dell'ISR WDT, consiglio semplicemente di abilitare la modalità di sospensione alla fine dell'ISR, quindi fare in modo che il programma MAIN metta l'MCU in modalità sospensione. In questo modo, il programma sta effettivamente lasciando l'ISR prima di andare a dormire e si risveglierà e tornerà direttamente all'ISR WDT.

// Enable Sleep Mode for Power Down
set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // Set Sleep Mode: Power Down
sleep_enable();                     // Enable Sleep Mode  
sei();                              // Enable Interrupts 

/****************************
 *  Enter Main Program Loop  *
 ****************************/
 for(;;)
 {
   if (MCUCR & _BV(SE)){    // If Sleep is Enabled...
     cli();                 // Disable Interrupts
     sleep_bod_disable();   // Disable BOD
     sei();                 // Enable Interrupts
     sleep_cpu();           // Go to Sleep

 /****************************
  *   Sleep Until WDT Times Out  
  *   -> Go to WDT ISR   
  ****************************/

   }
 }

WOW ... molto dettagliato. Grazie! Sono un po 'confuso dalla sezione sleep in cui mostri il loop principale (if (MCUCR & _BV (SE)) {// Se Sleep è abilitato ... ecc.) Sono confuso perché nell'aspetto principale dovresti disabilitare e abilitare continuamente gli interrupt. E la parte nella parte superiore di quella sezione (set_sleep_mode (SLEEP_MODE_PWR_DOWN);) Dove dovrebbe essere eseguito?
Adam Haile,

OK, la parte "set_sleep_mode (MODE)" dovrebbe essere nella parte PRIMA del ciclo principale, idealmente nell'altro codice di inizializzazione in cui si impostano le porte I / O, i timer, ecc. Non è necessario abilitare_sleep (); a quel punto, poiché verrà eseguito dopo il primo trigger WDT. ALL'INTERNO del ciclo principale, quel codice sleep eseguirà solo SE sleep è abilitato, e non è del tutto necessario disabilitare / riattivare gli interrupt lì, solo se stai facendo sleep_bod_disable (); L'intera istruzione IF potrebbe trovarsi nella parte inferiore (ma ancora all'interno) del ciclo MAIN dopo qualsiasi altro codice in esecuzione.
Kurt E. Clothier,

Ok ... ora ha più senso. L'unica cosa su cui sono confuso è ciò che questa "sequenza temporale" è ...
Adam Haile,

Nota a margine: so perché vorresti andare a dormire, ma presumo che non sia necessario quando si utilizza WDT?
Adam Haile,

Dai un'occhiata a questa piccola sezione della scheda tecnica: 8.4.1. Fondamentalmente, per cambiare il registro WDT, è necessario impostare il bit di modifica e quindi impostare i bit WDTCR corretti in così tanti cicli di clock. Il codice che ho fornito nella sezione WDT Setup lo fa abilitando prima il bit di modifica WD. E no, non è necessario utilizzare la funzionalità di sospensione con WDT. Potrebbe essere una fonte di interruzione temporizzata standard per qualsiasi motivo tu voglia.
Kurt E. Clothier,

1

Secondo la scheda tecnica è possibile. Puoi persino abilitare entrambi, un interrupt e il reset. Se entrambi sono abilitati, il primo timeout del watchdog attiverà l'interrupt che provoca la disattivazione del bit di abilitazione interrupt (interruzione disabilitata). Il timeout successivo ripristinerà quindi la CPU. Se si attiva l'interruzione direttamente dopo che è stata eseguita, il timeout successivo (di nuovo) attiverà solo un interrupt.

Puoi anche solo abilitare l'interrupt e non abilitare affatto il reset. Dovrai impostare il bit WDIE ogni volta che si è attivato l'interrupt.


Hmmm .... Penso che abbia senso. Ci proverò. Mi piace sempre usare le cose per quello che non erano destinate :)
Adam Haile,

In realtà penso che sia un design intelligente. Ti salva un timer mantenendo la funzionalità di watchdog.
Tom L.

2
Questo timeout iniziale del WDT e l'interruzione successiva possono essere utilizzati anche per alcune applicazioni che stanno abilitando il WDT per l'effettivo ripristino dell'aggancio. Si può guardare l'indirizzo di ritorno in pila nell'ISR WDT per dedurre cosa stava cercando di fare il codice quando si è verificato il "timeout imprevisto".
Michael Karas,

1

Questo è molto più facile di quanto suggerito sopra e altrove.

Finché il WDTONfusibile non è programmato (non è programmato per impostazione predefinita), è necessario solo ...

  1. Impostare il bit di abilitazione dell'interruzione del watchdog e il timeout nel registro di controllo del watchdog.
  2. Abilita interrupt.

Ecco un esempio di codice che eseguirà un ISR una volta ogni 16ms ...

ISR(WDT_vect) {
   // Any code here will get called each time the watchdog expires
}

void main(void) {
   WDTCR =  _BV(WDIE);    // Enable WDT interrupt, leave existing timeout (default 16ms) 
   sei();                                           // Turn on global interrupts
   // Put any code you want after here.
   // You can also go into deep sleep here and as long as 
   // global interrupts are eneabled, you will get woken 
   // up when the watchdog timer expires
   while (1);
}

Questo è tutto. Dal momento che non abilitiamo mai il reset del watchdog, non dobbiamo mai pasticciare con le sequenze temporizzate per disabilitarlo. Il flag di interruzione del watchdog viene automaticamente cancellato quando viene chiamato l'ISR.

Se si desidera un periodo diverso da ogni 1 secondo, è possibile utilizzare questi valori qui per impostare i bit appropriati in WDTCR...

inserisci qui la descrizione dell'immagine

Si noti che è necessario eseguire la sequenza temporizzata per modificare il timeout. Ecco il codice che imposta il timeout su 1 secondo ...

   WDTCR = _BV(WDCE) | _BV(WDE);                   // Enable changes
   WDTCR = _BV(WDIE) | _BV( WDP2) | _BV( WDP1);    // Enable WDT interrupt, change timeout to 1 second

La mancata esecuzione della sequenza temporizzata durante l'installazione salva una riga di codice: un'operazione di lettura-modifica-scrittura. La sequenza iniziale è consigliata nel foglio dati "Se il Watchdog è abilitato accidentalmente, ad esempio da un puntatore in fuga o da una condizione di brown-out" e il resto del mio codice è specifico per l'utilizzo del WDT in combinazione con una modalità di sospensione, come richiesto da l'OP. La tua soluzione non è più semplice, hai semplicemente trascurato il codice piastra caldaia / raccomandato / richiesto.
Kurt E. Clothier,

@ KurtE.Clothier Siamo spiacenti, sto solo cercando di dare l'esempio di lavoro più semplice!
bigjosh,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.