Arduino-Projekt: Watchdog-Timer und Soft-Reset

Prof. Jürgen Plate

Watchdog-Timer und Soft-Reset

Der Watchdog Timer

Alle ATmega-Controller verfügen über einen Watchdog-Timer ("Watchdog"), der die Stabilität des Systems bei unvorhersehbaren Problemen, z. B. dem Ausfall eines Sensors oder die Unterbrechung einer Kommunikationsverbindung, sicherstellen soll. Der Watchdog-Timer ist bei den ATmega-Controllern ein zusätzlicher Zähler auf dem Chip mit eigenem 128-kHz-Taktoszillator. Einmal aktiviert, wird der Zähler unabhängig vom laufenden Programm regelmäßig erhöht und, falls er nicht rechtzeitig von der Software zurückgesetzt wird, beim Überlaufen des Zählers einen Reset auslösen.

Der Watchdog-Timer besitzt einen einstellbaren Vorteiler, um die Zeit bis zum Überlauf in Stufen festlegen zu können. Die Reaktionszeit muss vom Programmierer sorgfältig gewählt werden - ein Reset darf nur ausgelöst werden, wenn es wirklich nötig ist. Man rechnet also aus, wie lange die Hauptschleife loop() im ungünstigsten Fall für einen Durchgang benötigt und setzt den Watchdog-Timer eine Zeitstufe über diesen Wert.

Wird der Watchdog falsch konfiguriert (Ablaufzeit zu kurz) kann dies zur Folge haben, dass der Watchdog vor dem Bootloader auslöst und damit ein Herunterladen eines geänderten Programms verhindert. Wenn Sie Experimente mit dem Watchdog durchführen, solle die Reaktionszeit des Wathcdog nicht zu niedrig gewählt werden. Im schlimmsten Fall muss dann nämlich der Arduino über die ISP-Schnittstelle programmiert werdeen oder man muss den Controllerchip mit Arduino als ISP wieder zum Leben erwecken (siehe Bootloader flashen).

Anwendung des Arduino Watchdogs

Die Anwendung des Watchdogs ist recht einfach, die AVR-Watchdog-Library besitzt nur wenige Funktionen. Zuerst wird die Library auf die C-typische Art und Weise eingebunden:

#include <avr/wdt.h>
Für die Programmierung und Aktivierung werden nur drei Funktionen benötigt: Die Watchdog-Zeitkonstanten können dem ATmega328-Datenblatt entnommen werden, möglich sind folgende Werte:

ZeitKonstantePrescaler-Bits
WDP0WDP1WDP2WDP3
16 msWDTO_15MS0000
32 msWDTO_30MS1000
64 msWDTO_60MS0100
0,125 sWDTO_120MS1100
0,25 sWDTO_250MS0010
0,5 sWDTO_500MS1010
1,0 sWDTO_1S0110
2,0 sWDTO_2S1110
4,0 sWDTO_4S0001
8,0 sWDTO_8S1001

Watchdog-Beispiele

Um einfach mal die Funktion des Watchdogs zu verdeutlichen, soll das folgende - an sich sinnlose - Programm dienen. Der Watchdog wird auf 1 Sekunde eingestellt. Da der delay(2000)-Aufruf jedoch zwei Sekunden wartet, wird immer wieder ein Reset ausgelöst.

#include <avr/wdt.h>

int count = 0;

void setup()
  {
  pinMode(3, INPUT);     // Digital-Pin 3 als Eingang
  digitalWrite(3, HIGH); // Pullup-Widerstand einschalten
  Serial.begin(9600);
  Serial.println("Arduino gestartet ...");
  wdt_enable(WDTO_1S);   // Watchdog auf 1 s stellen
  }

void loop()
  {
  count++;
  Serial.println(count);
  // Warten
  delay(2000);
  // Setze Watchdog Zähler zurück
  wdt_reset();
  }
Dementsprechend zeigt sich die Ausgabe recht monoton:
Arduino gestartet ...
1
Arduino gestartet ...
1
Arduino gestartet ...
1
Arduino gestartet ...
  ...
Nun wird die Wartezeit heruntergesetzt, indem delay(2000) durch delay(900) ersetzt wird. Dann läuft das Programm ungehindert durch:
Arduino gestartet ...
1
2
3
4
5
6
7
  ...
Das folgende Beispiel ist da ein wenig realistischer, hier reagiert das Programm auf einen eventuell gestörten Sensor mit einem Reset. Als Sensor dient hier eine Taste zwischen Digital-Pin 3 und GND. Wird sie häufig genug betätigt, läuft das Programm normal weiter. Dauert das Warten in der Funktion waitForSensor() zu lange, erzeugt der Watchdog einen Reset. Im Programm ist noch ein Feature eingebaut: Nach dem zehnten Tastendruck wird der Watchdog abgeschaltet. Ab da "hängt" das System, wenn der Sensor nicht reagiert.
#include <avr/wdt.h>

int count = 0; 

void waitForSensor() 
  // simuliert das Lesen eines Sensors, in diesem
  // Fall eine Taste zwischen Digital-Pin 3 und GND 
  {
  while (digitalRead(3) == HIGH) 
    {
    delay(500);
    // Warte auf Sensor
    }
  Serial.println("Sensor reagiert ...");
  delay(100); 
  }

void setup()
  {
  pinMode(3, INPUT);     // Digital-Pin 3 als Eingang
  digitalWrite(3, HIGH); // Pullup-Widerstand einschalten
  Serial.begin(9600);
  Serial.println("Arduino gestartet ...");
  wdt_enable(WDTO_4S);   // Watchdog auf 4 s stellen
  }

void loop()
  {
  Serial.print(count);
  Serial.println(": warte auf Sensor ...");
  waitForSensor();
  // Setze Watchdog Zähler zurück
  wdt_reset();
  count++;
  if (count == 10)
    // ab hier geht es ohne Watchdog weiter
    {
    wdt_disable();
    Serial.println("WD disabled ...");
    }
  }
Der erste Probelauf zeigt einen "funktionierenden Sensor" (Ausgabe verkürzt). Ab der zehnten Abfrage wird dann der Watchdog abgeschaltet (Zeile fett gedruckt):
Arduino gestartet ...
0: warte auf Sensor ...
Sensor reagiert ...
1: warte auf Sensor ...
Sensor reagiert ...
2: warte auf Sensor ...
Sensor reagiert ...
3: warte auf Sensor ...
Sensor reagiert ...
  ...
8: warte auf Sensor ...
Sensor reagiert ...
9: warte auf Sensor ...
Sensor reagiert ...
WD disabled ...
10: warte auf Sensor ...
Sensor reagiert ...
11: warte auf Sensor ...
Sensor reagiert ...
  ...
Im folgenden Beispiel fällt der Sensor nach der fünften Abfrage aus bzw. braucht zu lange:
Arduino gestartet ...
0: warte auf Sensor ...
Sensor reagiert ...
1: warte auf Sensor ...
Sensor reagiert ...
2: warte auf Sensor ...
Sensor reagiert ...
3: warte auf Sensor ...
Sensor reagiert ...
4: warte auf Sensor ...
Arduino gestartet ...
0: warte auf Sensor ...
Arduino gestartet ...
0: warte auf Sensor ...
Arduino gestartet ...
  ...

Wenn man längere Wartezeiten als acht Sekunden in der Hauptschleife hat, geht auch das, ohne dass man auf den Watchdog verzichten muss. Mann muss die lange Wartezeit nur in kleine Häppchen zerlegen. Das folgende Codefragment zeigt das beispielhaft. Die Reaktionszeit des Watchdog muss natürlich größer als eine Sekunde sein:

  ...
// Delayzeit fuer 5 Min.
#define LOOP_DELAY 300 
  ...
  
void loop()
  {
  int j;
     ...
  // 5 Min. warten
  for (j = 0; j < LOOP_DELAY; j++) 
    {
    delay(1000);
    wdt_reset();
    } 
     ...

Software-Reset

In manchen Fälle kann es auch sinnvoll sein, wenn sich die Software ganz ohne Watchdog selbst zurücksetzt. Das einfachste Beispiel wäre eine Ersatz des realen Rest-Tasters bzw. des Reset-Eingangs durch eine Software-Aktion. Beim Reset wird ja nicht nur der Controller initialisiert, sondern auch die init()-Funktion durchlaufen. Anders als beim von der Hardware ausgelösten Watchdog-Reset kann hier das Programm entscheiden, ob ein Reset erfolgen soll.

Das Vorgehen ist eigentlich ganz einfach: Es muss nur der Programmablauf an Adresse 0 fortgesetzt werden. Bei der Programmierung in Assembler würde dazu der Befehl jmp 0 genügen. Das geht mit dem Inline-Assembler ganz gut als Einzeiler:

void Reset_A(void) { asm volatile ("jmp 0 \n"); }
Aber auch, wenn man auf der C-Ebene bleibt, ist der Aufwand nicht größer. Es wird (Trick!) ein Funktionszeiger auf die Adresse 0 erzeugt:
  
void (*Reset_C)(void) = 0;
Beide Funktionen lassen sich gleichwertig verwenden. Man muss nur den Aufruf von Reset_A() durch Reset_C() ersetzen. Das folgende (wieder total nutzlose) Beispiel zeigt die Anwendung:
// Reset-Funktionen: Sprung zu Adresse 0
void (*Reset_C)(void) = 0;

void Reset_A(void) { asm volatile ("jmp 0 \n"); }

int count = 0; 

void setup()
  {
  Serial.begin(9600);
  Serial.println("Arduino gestartet ...");
  }

void loop()
  {
  count++;
  Serial.println(count);
  delay(500);
  if (count == 10)
    {
    Serial.println("RESET ...");
    delay(500);
    Reset_A();
    }
  }
An der seriellen Ausgabe kann man das Funktionieren überprüfen. Der delay()-Aufruf nach der Ausgabe "RESET ..." sorgt dafür, dass ma diese auch zu sehen bekommt - beim Reset wird ja auch die serielle Schnittstelle brutal zurückgesetzt.
Arduino gestartet ...
1
2
3
4
5
6
7
8
9
10
RESET ...
Arduino gestartet ...
1
2
  ...


Copyright © Hochschule München, FK 04, Prof. Jürgen Plate und die Autoren
Letzte Aktualisierung: