Verwenden Sie AVR Watchdog wie normale ISR

17

Ich versuche, meinen Kopf um den Watchdog-Timer der ATTinyX5-Serie zu legen. Ich habe also gelesen, dass Sie es verwenden könnten, um das Programm N Sekunden lang zu etwas Bestimmtem zu bewegen, aber nie wirklich gezeigt, wie. Andere ließen es so aussehen, als würde es nur den Chip zurücksetzen, wenn in der Zwischenzeit nichts im Code zurückgesetzt wird (was die "normale" Verwendung zu sein scheint).

Gibt es eine Möglichkeit, das WDT wie TIMER1_COMPA_vect oder ähnliches zu verwenden? Mir ist aufgefallen, dass es einen 1-Sekunden-Timeout-Modus gibt, und ich würde das sehr gerne nutzen, um alle 1 Sekunde in meinem Code etwas passieren zu lassen (und am liebsten dazwischen zu schlafen).

Gedanken?

* Update: * Da es gefragt wurde, beziehe ich mich auf Abschnitt 8.4 des ATTinyX5-Datenblattes . Nicht, dass ich es vollständig verstehe, was mein Problem ist ...

Adam Haile
quelle
1
+1 für über den Tellerrand hinaus denken. Zusätzliche Daumen hoch, wenn Sie einen Link zum Datenblatt des AVR hinzufügen.
jippie

Antworten:

23

Das können Sie mit Sicherheit. Laut Datenblatt kann der Watchdog-Timer so eingestellt werden, dass die MCU zurückgesetzt wird oder ein Interrupt ausgelöst wird. Es scheint, dass Sie mehr an der Interrupt-Möglichkeit interessiert sind.

Der WDT ist aus dem gleichen Grund, aus dem er weniger nützlich ist: weniger Optionen, einfacher einzurichten als ein normaler Timer. Es läuft mit einem intern kalibrierten 128-kHz-Takt, was bedeutet, dass das Timing nicht durch die Haupttaktrate der MCU beeinflusst wird. Es kann auch im Tiefschlafmodus weiterlaufen, um eine Weckquelle bereitzustellen.

Ich werde ein paar Datenblattbeispiele sowie einen Code durchgehen, den ich verwendet habe (in C).

Enthaltene Dateien und Definitionen

Zu Beginn möchten Sie wahrscheinlich die folgenden zwei Header-Dateien einbinden, damit die Dinge funktionieren:

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

Außerdem verwende ich das Makro <_BV (BIT)>, das in einem der Standard-AVR-Header wie folgt definiert ist (was für Sie möglicherweise vertrauter ist):

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

Beginn des Codes

Wenn die MCU zum ersten Mal gestartet wird, initialisieren Sie in der Regel die E / A, richten Timer ein usw. Hier ist ein guter Zeitpunkt, um sicherzustellen, dass der WDT keinen Reset verursacht, da er dies erneut ausführen kann, und Ihr Programm dabei belässt eine instabile Schleife.

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

Nachdem Sie den Rest des Chips eingerichtet haben, wiederholen Sie den WDT. Das Einrichten des WDT erfordert eine "zeitgesteuerte Sequenz", aber es ist wirklich einfach zu tun ...

// 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

Natürlich sollten Ihre Interrupts während dieses Codes deaktiviert sein. Vergessen Sie nicht, sie danach wieder zu aktivieren!

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

WDT-Interrupt-Serviceroutine Das nächste, worüber Sie sich Sorgen machen müssen, ist die Behandlung des WDT-ISR. Dies geschieht so:

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-Schlaf

Anstatt die MCU innerhalb des WDT-ISR in den Ruhezustand zu versetzen, empfehle ich, einfach den Ruhezustand am Ende des ISR zu aktivieren und die MCU dann vom MAIN-Programm in den Ruhezustand zu versetzen. Auf diese Weise verlässt das Programm den ISR tatsächlich, bevor es in den Ruhezustand wechselt, und wird aufgeweckt und kehrt direkt in den WDT-ISR zurück.

// 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   
  ****************************/

   }
 }
Kurt E. Clothier
quelle
WOW ... sehr detailliert. Vielen Dank! Ich bin ein wenig verwirrt von dem Schlafabschnitt, in dem Sie die Hauptschleife anzeigen (if (MCUCR & _BV (SE)) {// Wenn der Schlaf aktiviert ist ... usw.). Ich bin verwirrt, warum Sie dies im Hauptblick tun würden Kontinuierliche Deaktivierung und Aktivierung von Interrupts. Und der Teil oben in diesem Abschnitt (set_sleep_mode (SLEEP_MODE_PWR_DOWN);) Wo soll das laufen?
Adam Haile
OK, der Teil "set_sleep_mode (MODE)" sollte sich im Hauptbereich VOR der Hauptschleife befinden, idealerweise im anderen Initialisierungscode, in dem Sie die E / A-Ports, Timer usw. einrichten. Sie müssen enable_sleep () nicht wirklich aktivieren. zu diesem Zeitpunkt, da dies nach Ihrem ersten WDT-Trigger erfolgt. INNERHALB der Hauptschleife führt dieser Schlafcode nur dann aus, wenn der Schlaf aktiviert ist, und es ist nicht unbedingt erforderlich, die Interrupts dort zu deaktivieren / erneut zu aktivieren, nur wenn Sie sleep_bod_disable () ausführen. Die gesamte IF-Anweisung könnte sich nach jedem anderen Code, den Sie dort ausführen, am Ende der MAIN-Schleife befinden (sich aber immer noch darin befinden).
Kurt E. Clothier
Ok ... das macht jetzt mehr Sinn. Das Letzte, woran ich bin, ist, was diese "zeitgesteuerte Sequenz" ist ...
Adam Haile
Randnotiz: Ich weiß, warum Sie schlafen gehen möchten, aber ich gehe davon aus, dass Sie das WDT nicht verwenden müssen ?
Adam Haile
Schauen Sie sich diesen kleinen Abschnitt des Datenblattes an: 8.4.1. Grundsätzlich müssen Sie zum Ändern des WDT-Registers das Änderungsbit und dann die richtigen WDTCR-Bits innerhalb so vieler Taktzyklen setzen. Der Code, den ich im Abschnitt "WDT-Setup" angegeben habe, aktiviert zuerst das WD-Änderungsbit. Und nein, Sie müssen die Sleep-Funktion beim WDT überhaupt nicht verwenden. Es könnte sich um eine standardmäßige zeitgesteuerte Interruptquelle handeln, aus welchem ​​Grund auch immer Sie dies wünschen.
Kurt E. Clothier
1

Laut Datenblatt ist es möglich. Sie können sogar sowohl einen Interrupt als auch das Zurücksetzen aktivieren. Wenn beide aktiviert sind, löst das erste Watchdog-Timeout den Interrupt aus, wodurch das Bit Interrupt Enable deaktiviert wird (Interrupt deaktiviert). Das nächste Timeout setzt dann Ihre CPU zurück. Wenn Sie den Interrupt direkt nach seiner Ausführung aktivieren, löst das nächste Timeout (erneut) nur einen Interrupt aus.

Sie können den Interrupt auch nur aktivieren und das Zurücksetzen überhaupt nicht aktivieren. Sie müssen das WDIE-Bit jedes Mal setzen, wenn der Interrupt ausgelöst wurde.

Tom L.
quelle
Hmmm .... ich denke das macht Sinn. Ich werde es versuchen. Ich benutze Dinge immer gerne für das, was sie nicht beabsichtigt waren :)
Adam Haile
Eigentlich finde ich es ein cleveres Design. Speichert einen Timer, während die Watchdog-Funktionalität erhalten bleibt.
Tom L.
2
Diese anfängliche Zeitüberschreitung des WDT und die anschließende Unterbrechung können auch für einige Anwendungen von Vorteil sein, die das WDT für die tatsächliche Wiederherstellung nach dem Auflegen aktivieren. Anhand der gestapelten Absenderadresse im WDT-ISR können Sie ablesen, was der Code versucht hat, als das "unerwartete Timeout" auftrat.
Michael Karas
1

Dies ist viel einfacher als oben und anderswo vorgeschlagen.

Solange die WDTONSicherung nicht programmiert ist (es ist nicht standardmäßig programmiert), brauchen Sie nur ...

  1. Setzen Sie das Watchdog Interrupt Enable Bit und das Timeout im Watchdog Control Register.
  2. Interrupts aktivieren.

Hier ist ein Codebeispiel, das eine ISR einmal pro 16 ms ausführt ...

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);
}

Das ist es wirklich. Da wir den Watchdog-Reset niemals aktivieren, müssen wir nie mit den zeitgesteuerten Sequenzen herumspielen, um ihn zu deaktivieren. Das Watchdog-Interrupt-Flag wird automatisch gelöscht, wenn der ISR aufgerufen wird.

Wenn Sie eine andere Periode als jede Sekunde wünschen, können Sie diese Werte hier verwenden, um die entsprechenden Bits in WDTCR...

Bildbeschreibung hier eingeben

Beachten Sie, dass Sie die zeitgesteuerte Sequenz ausführen müssen, um das Timeout zu ändern. Hier ist Code, der das Timeout auf 1 Sekunde setzt ...

   WDTCR = _BV(WDCE) | _BV(WDE);                   // Enable changes
   WDTCR = _BV(WDIE) | _BV( WDP2) | _BV( WDP1);    // Enable WDT interrupt, change timeout to 1 second
bigjosh
quelle
Wenn die zeitgesteuerte Sequenz während des Setups nicht ausgeführt wird, wird eine Codezeile gespart - eine Lese-, Änderungs- und Schreiboperation. Die anfängliche Reihenfolge wird im Datenblatt "Wenn der Watchdog versehentlich aktiviert wurde, z. B. durch einen außer Kontrolle geratenen Zeiger oder einen Brown-Out-Zustand" empfohlen. Der Rest meines Codes bezieht sich speziell auf die Verwendung des WDT in Kombination mit einem Ruhemodus, wie von angefordert die Operation. Ihre Lösung ist nicht einfacher, Sie haben nur den empfohlenen / erforderlichen Kesselschildcode vernachlässigt.
Kurt E. Clothier
@ KurtE.Clothier Sorry, versuche nur das einfachste Arbeitsbeispiel zu geben!
Bigjosh