Watchdog Time-out Interrupt (WDT)
Der Watchdog (englisch für Wachhund; auch WDC für Watchdog Counter genannt) ist eine Systemkomponente, die die Funktion anderer Komponenten überwacht. Im Speziellen werden Watchdogs in von Mikrocontrollern gesteuerten Geräten eingesetzt, um einem Komplettausfall des Gerätes durch Softwareversagen zuvorzukommen. Verhindert wird der Reset eines Mikrocomputersystems, indem die Software in regelmäßigen Abständen dem Watchdog mitteilt, dass sie noch ordnungsgemäß arbeitet. (Quelle: https://de.wikipedia.org/wiki/Watchdog)
Bei Arduino Uno (bzw. alle Arduinos mit Atmega328P Controller) und Attiny45/85 ist ein sogenannter Time-out Watchdog eingesetzt. Dabei muss das Anwenderprogramm kontinuierlich vor Ablauf einer vorgegebenen Zeit den Watchdog Timer zurücksetzen (Prinzip der Totmanneinrichtung z.B. bei der Bahn). Passiert das nicht, da das Programm z.B. hängen geblieben ist, wird je nach Konfiguration ein Interrupt oder ein System-Reset oder Beides (zuerst ein Interrupt und dann ein System-Reset) ausgeführt.
Wird der Watchdog in seiner eigentlichen Funktion nicht benötigt, so steht es dem Programmierer frei, den Watchdog Time-out Interrupt (WDT) für seine eigenen Zwecke zu benutzen. Ein typischer Anwendungsfall wäre z.B. das zyklische Wecken eines Arduinos oder eines Attinys aus dem Schlafmodus. Prinzipiell kann natürlich jede zyklische Tätigkeit mit einem WDT angestoßen werden, Einschränkungen gibt es allerdings bei der Vorgabe der möglichen Zykluszeiten.
Hier möchte ich nun zeigen, wie ich einen Watchdog Time-out Interrupt, sowohl mit Arduino, als auch mit Attiny programmiere:
Watchdog Time-out Interrrupt beim Arduino
(oder beim Attiny)
Verwendete Register:
- Status Register: SREG
- MCU Status Register: MCUSR
- Watchdog Timer Control Register: WDTCSR
Vorgangsweise:
- Alle Interrupts sperren
- Watchdog Reset ausführen (Rücksetzen des Watchdog Timers)
- Watchdog System Reset Flag rücksetzen
- Freigabe zur Änderung des Prescaler (Vorteiler) setzen
- Prescaler setzen
- Watchdog Time-out Interrupt freigeben
- Alle Interrupts freigeben
- Interrupt-Serviceroutine erstellen
1. Alle Interrupts sperren
Durch die Anweisung "cli();" oder durch Löschen des Global Interrupt Enable Bits (I) im Status Register (SREG) werden alle Interrupts gesperrt. Während der Manipulation des Watchdog Timer Control Register soll kein Interrupt ausgelöst werden können.
cli(); //oder SREG &= 0x7;
2. Watchdog Reset ausführen (Rücksetzen des Watchdog Timers)
Vor der Änderung der Prescaler Bits sollte der Watchdog Timer mit der Funktion wdt_reset() zurückgesetzt werden. Dazu ist die Library wdt.h vorher einzubinden. Die Library ist Bestandteil der Arduino-IDE und muss daher nicht installiert werden.
#include <avr/wdt.h>
wdt_reset(); // Reset Watchdog Timer
3. Watchdog System Reset rücksetzen
Das Bit Watchdog System Reset Flag (WDRF) des MCU Status Register (MCUSR) wird gesetzt, wenn ein Watchdog System Reset auftritt. Es wird durch ein Power-on Reset oder durch Beschreiben mit '0' zurückgesetzt. Um Watchdog Reset System Enable (WDE) löschen zu können, muss zuerst das WDRF Bit gelöscht werden.
MCUSR &= ~(1 << WDRF);
4. Freigabe zur Änderung des Prescaler (Vorteiler) setzen
Das Setzen des Watchdog Change Enable Bit (WDCE) des Watchdog Timer Control Register (WDTCSR) ist Voraussetzung, dass die Bits zur Vorgabe des Prescalers verändert werden können. Ist das Bit gesetzt, wird es hardwaremäßig nach 4 Taktzyklen wieder rückgesetzt. Das bedeutet, dass das Setzen der Prescaler-Bits unmittelbar nach dem Setzen des WDCE-Bit erfolgen muss!
Allerdings - und hier ist das Datenblatt des Atmega328P (Uno, Nano, ..) für mich nicht ganz schlüssig - steht an anderer Stelle, dass auch gleichzeitig das Watchdog System Reset Enable Bit (WDE) gesetzt werden muss, gleichgültig, welchen Wert WDE später haben soll. Auf jeden Fall muss WDCE und WDE gesetzt werden, sonst ist keine Vorgabe der Prescaler-Bits möglich!
WDTCSR = (1 << WDCE) | (1 << WDE); //Watchdog Change Enable
5. Prescaler setzen
Der Watchdog Timer beim Arduino/Attiny beruht auf einem separaten "On-Chip" Oszillator mit einer Taktfrequenz von 128 KHz. Mittels der Watchdog Timer Prescaler Bits WDP3 - WDP0 des Watchdog Timer Control Register (WDTCSR) kann diese Taktfrequenz so geteilt werden, dass nachfolgende Time-outs, also Zeiten zwischen 16 ms und 8 s, eingestellt werden können
Durch nachfolgender Programmzeile (Setzen von Prescaler Bits) wird auch das vorher gesetzte WDE-Bit wieder gelöscht!
WDTCSR = (1 << WDP3) | (1 << WDP0); //Time-out = 8 s
6. Watchdog Time-out Interrupt freigeben
Durch Setzen der Bits Watchdog Interrupt Enable (WDIE) und Watchdog System Reset Enable (WDE) des Watchdog Timer Control Register (WDTCSR) kann das Verhalten des Watchdog nach dem Time-out, entsprechend nachfolgender Tabelle, bestimmt werden.
WDTCSR |= (1 << WDIE); //Watchdog Mode = Interrupt Mode
7. Alle Interrupts freigeben
Durch die Anweisung "sei();" oder durch Setzen des Global Interrupt Enable Bits (I) im Status Register (SREG) werden alle Interrupts freigegeben.
sei(); //oder SREG |= 0x80;
8. Erstellen der Interrupt-Serviceroutine
Wird ein Watchdog Time-out Interrupt ausgelöst, verzweigt das Programm in eine zu erstellende Serviceroutine ISR(WDT_vect).
Die Namen der Interrupt-Serviceroutine ist vom System fix vorgegeben und darf nicht verändert werden!
ISR(WDT_vect)
{
............
}
Programmbeispiel:
Im nachfolgenen Programmbeispiel wird die on-board LED eines Uno oder Nano (Pin 13) alle 4 Sekunden, ausgelöst durch einen Watchdog Time-out Interrupt, für 100 ms zum Leuchten gebracht.
Verwendete Library:
wdt: Die verwendete Library wdt ist Bestandteil der Arduino-IDE und muss deshalb nicht installiert werden.
Hinweis:
Wer mit Registerprogrammierung nicht vertraut ist, sollte sich zum Verständnis des Programms folgende Seiten ansehen: Bitmanipulation
//Watchdog Time-out Interrupt
//Code fuer Arduino
//Author Retian
//Version 1.0
#include <avr/wdt.h>
#define ledPin 13 //LED auf Pin 13
volatile bool wdtFlag = false;
void setup() {
Serial.begin(115200);
pinMode(ledPin, OUTPUT);
//Register fuer Watchdog Time-out Interrupt setzen
cli();
wdt_reset(); // Reset Watchdog Timer
MCUSR &= ~(1 << WDRF); //Rücksetzen des Watchdog System Reset Flag
WDTCSR = (1 << WDCE) | (1 << WDE); //Watchdog Change Enable
WDTCSR = (1 << WDP3); //Watchdog Zyklus = 4 s
WDTCSR |= (1 << WDIE); //Watchdog Interrupt enable
sei();
}
void loop() {
if (wdtFlag)
{
wdtFlag = false;
digitalWrite(ledPin, HIGH);
delay(100);
digitalWrite(ledPin, LOW);
}
}
//Interrupt Service Routine
ISR(WDT_vect)
{
wdtFlag = true;
}