Verwenden Sie AVR Watchdog wie normale ISR

Ich versuche, meinen Kopf um den Watchdog-Timer der ATTinyX5-Serie zu wickeln. Die Dinge, die ich gelesen habe, ließen es so aussehen, als könnten Sie es verwenden, um das Programm dazu zu bringen, alle N Sekunden etwas Bestimmtes zu tun, aber nie wirklich gezeigt, wie. Andere ließen es so aussehen, als würde es den Chip nur zurücksetzen, es sei denn, etwas im Code würde in der Zwischenzeit seinen Zähler zurücksetzen (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 hat, und ich würde ihn wirklich gerne verwenden, um jede Sekunde etwas in meinem Code passieren zu lassen (und vorzugsweise dazwischen zu schlafen).

Gedanken?

* Update: * Da es gefragt wurde, beziehe ich mich auf Abschnitt 8.4 des ATTinyX5-Datenblatts . Nicht, dass ich es ganz verstehe, das ist mein Problem ...

+1 für das Denken über den Tellerrand hinaus. Zusätzliche Daumen hoch, wenn Sie einen Link zum Datenblatt für den AVR hinzufügen.

Antworten (3)

Das können Sie sicherlich. Laut Datenblatt kann der Watchdog-Timer so eingerichtet werden, dass er die MCU zurücksetzt oder einen Interrupt verursacht, wenn er auslöst. Es scheint, dass Sie mehr an der Interrupt-Möglichkeit interessiert sind.

Der WDT ist eigentlich einfacher einzurichten als ein normaler Timer, aus dem gleichen Grund, aus dem er weniger nützlich ist: weniger Optionen. Es läuft auf einem intern kalibrierten 128-kHz-Takt, was bedeutet, dass sein Timing nicht von der Haupttaktgeschwindigkeit der MCU beeinflusst wird. Es kann auch während der tiefsten Schlafmodi weiterlaufen, um eine Aufwachquelle bereitzustellen.

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

Enthaltene Dateien und Definitionen

Zu Beginn möchten Sie wahrscheinlich die folgenden zwei Header-Dateien einfügen, 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 Ihnen vertrauter sein könnte):

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

Anfang des Codes

Wenn die MCU zum ersten Mal gestartet wird, würden Sie normalerweise die E / A initialisieren, Timer einrichten usw. Irgendwo hier ist ein guter Zeitpunkt, um sicherzustellen, dass der WDT keinen Reset verursacht hat, da er dies erneut tun und Ihr Programm behalten könnte 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 die WDT. Das Einrichten des WDT erfordert eine "zeitgesteuerte Sequenz", aber es ist wirklich einfach zu bewerkstelligen ...

// 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. Achten Sie darauf, sie danach wieder zu aktivieren!

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

WDT-Interrupt-Service-Routine Das nächste, worüber Sie sich Gedanken machen müssen, ist der Umgang mit der WDT-ISR. Dies geschieht wie folgt:

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 der WDT ISR in den Ruhezustand zu versetzen, empfehle ich, einfach den Ruhemodus am Ende der ISR zu aktivieren und dann das MAIN-Programm die MCU in den Ruhezustand versetzen zu lassen. Auf diese Weise verlässt das Programm tatsächlich die ISR, bevor es in den Ruhezustand übergeht, und es wacht auf und geht direkt zurück in die WDT-ISR.

// 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 ... sehr detailliert. Vielen Dank! Ich bin ein wenig verwirrt über den Schlafabschnitt, in dem Sie die Hauptschleife anzeigen (if (MCUCR & _BV(SE)){ // If Sleep is Enabled ... usw.). Ich bin verwirrt, warum Sie im Hauptlook aussehen würden Unterbrechungen kontinuierlich deaktivieren und aktivieren. Und der Teil ganz oben in diesem Abschnitt (set_sleep_mode(SLEEP_MODE_PWR_DOWN); ) Wo soll das laufen?
OK, der Teil "set_sleep_mode(MODE)" sollte im Hauptteil VOR der Hauptschleife sein, idealerweise im anderen Initialisierungscode, wo Sie die I/O-Ports, Timer usw. einrichten. Sie müssen nicht wirklich enable_sleep(); zu diesem Zeitpunkt, da dies nach Ihrem ersten WDT-Trigger erfolgen wird. INNERHALB der Hauptschleife wird dieser Sleep-Code nur ausgeführt, wenn Sleep aktiviert ist, und es ist nicht unbedingt erforderlich, die Interrupts dort zu deaktivieren/reaktivieren, nur wenn Sie sleep_bod_disable () ausführen. Diese gesamte IF-Anweisung könnte sich am Ende (aber immer noch innerhalb) der MAIN-Schleife nach jedem anderen Code befinden, den Sie dort ausführen.
Ok... das macht jetzt mehr Sinn. Das einzige, worüber ich unscharf bin, ist, was diese "zeitgesteuerte Sequenz" ist ...
Nebenbemerkung: Ich weiß, warum Sie schlafen gehen möchten, aber ich nehme an, dass Sie dies nicht müssen , wenn Sie das WDT verwenden?
Werfen Sie einen Blick auf diesen kleinen Abschnitt des Datenblatts: 8.4.1. Grundsätzlich müssen Sie zum Ändern des WDT-Registers das Änderungsbit setzen und dann innerhalb so vieler Taktzyklen die richtigen WDTCR-Bits setzen. Der Code, den ich im WDT-Setup-Abschnitt bereitgestellt habe, tut dies, indem er zuerst das WD-Änderungsbit aktiviert. Und nein, Sie müssen die Schlaffunktion überhaupt nicht mit dem WDT verwenden. Es könnte aus irgendeinem Grund eine standardmäßige zeitgesteuerte Interruptquelle sein.

Laut Datenblatt ist das möglich. Sie können sogar beides aktivieren, einen Interrupt und den Reset. Wenn beide aktiviert sind, löst das erste Watchdog-Timeout den Interrupt aus, wodurch das Interrupt Enable-Bit zurückgesetzt wird (Interrupt deaktiviert). Beim nächsten Timeout wird dann Ihre CPU zurückgesetzt. Wenn Sie den Interrupt direkt nach seiner Ausführung aktivieren, wird beim nächsten Timeout (wieder) nur ein Interrupt ausgelöst.

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

Hmmm .... Ich denke, das macht Sinn. Ich werde es versuchen. Ich mag es immer, Dinge für das zu verwenden, wofür sie nicht bestimmt waren :)
Eigentlich denke ich, dass es ein cleveres Design ist. Spart Ihnen einen Timer, während die Watchdog-Funktionalität beibehalten wird.
Diese anfängliche Zeitüberschreitung des WDT und die anschließende Unterbrechung können auch vorteilhaft für einige Anwendungen verwendet werden, die den WDT für die eigentliche Aufhängungswiederherstellung aktivieren. Man kann sich die gestapelte Rücksprungadresse in der WDT-ISR ansehen, um abzuleiten, was der Code zu tun versuchte, als das „unerwartete Timeout“ auftrat.

Dies ist viel einfacher als oben und an anderer Stelle vorgeschlagen.

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

  1. Setzen Sie das Watchdog-Interrupt-Enable-Bit und den Timeout im Watchdog-Steuerregister.
  2. Interrupts aktivieren.

Hier ist ein Codebeispiel, das eine ISR einmal alle 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 nie aktivieren, müssen wir nie mit den zeitgesteuerten Sequenzen herumspielen, um ihn zu deaktivieren. Das Watchdog-Interrupt-Flag wird automatisch gelöscht, wenn die ISR aufgerufen wird.

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

Geben Sie hier die Bildbeschreibung ein

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
Wenn Sie die zeitgesteuerte Sequenz während des Setups nicht ausführen, wird eine Codezeile eingespart - eine Lese-Änderungs-Schreib-Operation. Die Anfangssequenz wird im Datenblatt "If the Watchdog is versehentlich enabled, for example by a runaway pointer or brown-out condition" empfohlen, und der Rest meines Codes ist spezifisch für die Verwendung des WDT in Kombination mit einem Schlafmodus, wie von angefordert die Operation. Ihre Lösung ist nicht einfacher, Sie haben nur den empfohlenen/erforderlichen Boilerplate-Code vernachlässigt.
@KurtE.Clothier Tut mir leid, ich versuche nur, das einfachste Arbeitsbeispiel zu geben!