Watchdog Timer

Über den Beitrag

Nach meinen Beiträgen über die 8-Bit Timer und den 16-Bit Timer des Arduinos bzw. des ATmega 328P möchte ich das Thema nun mit dem Watchdog Timer komplettieren. Darum geht es in diesem Beitrag:

Was ist ein Watchdog Timer?

Unter bestimmten Bedingungen kann sich ein Microcontroller genau wie ein PC aufgrund unerwarteter Bedingungen oder Programmierfehlern „aufhängen“. Meistens machen sich Programmierfehler sofort bemerkbar, manchmal aber auch erst später unter speziellen Bedingungen, die man bei der Programmierung nicht berücksichtigt hat. Auch können Fehler in Peripherie, wie z.B. Kommunikationsleitungen oder Sensoren, dazu führen, dass der Sketch auf einmal in einer Programmschleife festhängt.

Einfach ausgedrückt wacht der Watchdog Timer über den Sketch, indem er regelmäßig Lebenszeichen von ihm erwartet. Bleiben diese aus, wird je nach Einstellung und Mikrocontroller, nach einer einstellbaren Zeit ein Reset durchgeführt, ein Interrupt ausgelöst oder beides. Etwas genauer ausgedrückt ist der Watchdog Timer ein automatischer Zähler, der die besagten Aktionen bei Erreichen seines Limits auslöst, wenn er nicht vorher zurückgesetzt wird.

Ein Interrupt vor dem Reset kann zum Beispiel dazu genutzt werden, um

  • sichere Bedingungen bei der gesteuerten Anlage zu schaffen
  • Parameter auf einem EEPROM zum Zwecke der Fehleranalyse zu speichern
  • den Stand des Programmes zu speichern, damit es nach dem Reset an der richtigen Stelle seine Arbeit fortsetzt (Beispiel: Steuerung einer Waschmaschine)

Der Watchdog Timer des Arduino (UNO)

Die folgende Anleitung gilt für die ganze ATmega 48 / 88 / 168 / 328 / 328P Familie. Nachzulesen ist das im Datenblatt ab Seite 60. Prinzipiell gelten die Angaben auch für die anderen Vertreter der AVR Microcontroller. Nur ist nicht jeder Time-Out bei jedem Modell verfügbar. Weitere Angaben zu den Time-Outs gibt es hier.

Einstellungen

Der Watchdog Timer des Arduino UNO bzw. des ATmega 328P wird nicht wie die anderen, zuvor besprochenen Timer über den Systemtakt gespeist, sondern über einen separaten 128 kHz Oszillator. Dadurch sind die Sketche leichter übertragbar.

Die Einstellungen des Watchdog Timers nehmt ihr im Watchdog Timer Control Register WDTCSR vor:

Das Watchdog Timer Control Register
Das Watchdog Timer Control Register
  • WDIF: Watchdog Interrupt Flag – das Bit wird bei Time-Out des  Watchdog Timers gesetzt, wenn zuvor auch WDIE gesetzt wurde
  • WDIE: Watchdog Interrupt Enable – setzt ihr dieses Bit, dann löst ein Time-Out des Watchdog Timers einen Interrupt aus
  • WDPx: Watchdog Timer Prescaler – mit diesen Bits legt ihr die Periode des Watchdog Timers entsprechend der nachfolgenden Tabelle fest
  • WDCE: Watchdog Change Enable – wollt ihr die Watchdog Timer Einstellungen ändern, dann müsst ihr zunächst dieses Bit setzen. Die Prozedur wird noch erklärt.
  • WDE: Watchdog System Reset Enable – setzt Ihr dieses Bit, dann löst ein Time-Out des Watchdog Timers ein Reset des Microcontrollers aus.
Tabelle 1: Prescaler Einstellungen des Watchdog Timers
Tabelle 1: Prescaler Einstellungen des Watchdog Timers

Einrichten des Watchdog Timers – Schritt für Schritt

Wir fangen ganz einfach an. Der folgende Sketch bleibt in einer while-Schleife hängen, da ich das Inkrementieren meines Zählers i „vergessen“ habe.

void setup(){
  Serial.begin(9600);
  Serial.println("Program with endless loop...");
  Serial.println("Sketch starts...");
}

void loop(){
  endlessLoop();  
  Serial.println("I will never print this...");
}

void endlessLoop(){
  int i = 0;
  while(i < 5){
    //do nothing
  }
}

 

Watchdog Timer mit System Reset

Nun soll ein Watchdog eingerichtet werden, der den Sketch aus der Schleife befreit und den Microcontroller neu startet:

void setup(){
  Serial.begin(9600);
  Serial.println("Watchdog Timer Test");
  Serial.println("Sketch starts...");
  watchdogSetup();
}

void loop(){
  endlessLoop();  
  Serial.println("I will never print this...");
  asm("WDR"); // this will not be executed
}

void watchdogSetup(void){
  cli(); // disable all interrupts
  asm("WDR"); // watchdog reset
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = (1<<WDE) | (1<<WDP3); // 4s / no interrupt, system reset
  sei();
}

void endlessLoop(){
  static int seconds = 0;
  int i = 0;
  while(i < 5){
    Serial.print(seconds);
    Serial.println(" Sekunden");
    delay(1000);
    seconds++;
  }
}

 

Die Einrichtung des Watchdog Timers läuft nach dem folgenden Schema:

  • cli() schaltet alle Interrupts ab. Das ist notwendig, da die Einrichtung des Watchdog Timers gestört werden könnte
  • asm("WDR"); ist eine Assembler Anweisung für den Watchdog Reset; wir werden bald sehen, wie man eine weniger kryptische Funktion einsetzen kann.
  • WDTCSR |= (1<<WDCE) | (1<<WDE); leitet die Änderung der Watchdog Parameter ein. Es ist wichtig, dass „|=“ und nicht „=“ verwendet wird. Nach dieser Initialisierung muss die eigentliche Änderung innerhalb der nächsten 4 Taktzyklen erfolgen.
  • WDTCSR = (1<<WDE) | (1<<WDP3); bedeutet: Reset ist aktiviert und der Watchdog Timer ist auf vier Sekunden eingestellt (siehe Einstellungstabelle).
  • sei(); lässt Interrupts wieder zu.

Der Watchdog Reset am Ende der Loop-Schleife (Zeile 11) würde den System Reset verhindern. Diese Anweisung ist aber unerreichbar.

Und so sieht die Ausgabe am seriellen Monitor aus:

Watchdog Timer Reset
Watchdog Timer Reset

Der Watchdog funktioniert – alle vier Sekunden startet der Arduino neu. Theoretisch dürfte der Sketch „4 Sekunden“ schon gar nicht mehr ausgeben. Der Watchdog Timer ist aber weniger exakt als die anderen Timer. So gibt das Datenblatt die Time-Out Werte auch nur als „typisch“ an.

Watchdog Timer mit System Reset und Interrupt

Der folgende Sketch enthält immer noch die Endlos-Schleife, die zum Watchdog Time-Out führt. Hier führt der Time-Out aber nicht nur zum Reset, sondern vorher wird noch ein Interrupt ausgelöst. Dieser wiederum ruft die ISR (Interrupt Service Routine) auf. Dazu muss ihr „WDT_vect“ übergeben werden.

void setup(){
  Serial.begin(9600);
  Serial.println("*******************");
  Serial.println("Watchdog Timer Test");
  Serial.println("Sketch starts...");
  watchdogSetup();
}

void loop(){
  endlessLoop();  
  Serial.println("I will never print this...");
  asm("WDR"); // Watchdog Reset will not happen
}

void watchdogSetup(void){
  cli(); // disable all interrupts
  asm("WDR");
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = (1<<WDIE) | (1<<WDE) | (1<<WDP3); // 4s / interrupt, system reset
  sei();
}

void endlessLoop(){
  static int seconds = 0;
  int i = 0;
  while(i < 5){
    Serial.print(seconds);
    Serial.println(" Sekunden");
    delay(1000);
    seconds++;
  }
}

ISR(WDT_vect){
  Serial.println("....but I will print this before I reset soon");
}

 

Und so sieht dann die Ausgabe aus:

Watchdog Timer Reset mit vorherigem Interrupt
Watchdog Timer Reset mit vorherigem Interrupt

Wie ihr erkennt, läuft der Sketch noch vier Sekunden weiter, bevor der Reset ausgelöst wird. Der Interrupt gewährt einen Aufschub um eine Watchdog Periode. Bei der Auswahl der Watchdog Timer Periode müsst ihr also darauf achten, dass die vor dem Reset auszuführenden Aktionen auch in eine Periode „hineinpassen“.

Vereinfachte Schreibweise mit wdt.h

Die kryptische Einrichtung des Watchdog Timers könnt ihr durch Einbinden der Bibliothek „avr/wdt.h“ etwas angenehmer gestalten:

  • um das Abschalten der Interrupts durch cli(); braucht ihr euch nicht mehr zu kümmern
  • aus asm("WDR"); wird wdt_reset();
  • die Initialisierung durch das Setzen von WDCE und die nachfolgende Prescale Festlegung wird ersetzt durch wdt_enable(WDTO_XYZ);, wobei ihr WDTO_XYZ der Tabelle 1 entnehmt.
  • einzig für das Aktivieren des Watchdog Interrupts hat man komischerweise keine Funktion vorgesehen. Dafür müsst ihr immer noch die Binäroperation WDTCSR = (1<<WDIE); verwenden.
  • Der Watchdog Interrupt ohne Systemreset lässt sich nicht mit den wdt.h Funktionen einrichten
#include <avr/wdt.h>
void setup(){
  Serial.begin(9600);
  Serial.println("*******************");
  Serial.println("Watchdog Timer Test");
  Serial.println("Sketch starts...");
  watchdogSetup();
}

void loop(){
  endlessLoop();  
  Serial.println("I will never print this...");
  wdt_reset(); // Watchdog Reset will not happen
}

void watchdogSetup(void){
  wdt_reset();
  wdt_enable(WDTO_4S); // 4s / System Reset
  WDTCSR = (1<<WDIE); // interrupt
}

void endlessLoop(){
  static int seconds = 0;
  int i = 0;
  while(i < 5){
    Serial.print(seconds);
    Serial.println(" Sekunden");
    delay(1000);
    seconds++;
  }
}

ISR(WDT_vect){
  Serial.println("....but I will print this before I reset");
}

 

Ein „OK“-Sketch

Der Vollständigkeit halber hier noch ein Sketch mit einer Schleife, die rechtzeitig beendet wird. Dazu habe ich in Zeile 30 ein i++ eingefügt und i<4 als Abbruchbedingung für die while-Schleife.

#include <avr/wdt.h>
void setup(){
  Serial.begin(9600);
  Serial.println("*******************");
  Serial.println("Watchdog Timer Test");
  Serial.println("Sketch starts...");
  watchdogSetup();
}

void loop(){
  finiteLoop();  
  Serial.println("Now I will print this...");
  wdt_reset();
}

void watchdogSetup(void){
  wdt_reset();
  wdt_enable(WDTO_4S);
  //wdt_enable(WDTO_2S);
  WDTCSR = (1<<WDIE);
}

void finiteLoop(){
  static int seconds = 0;
  int i = 0;
  while(i < 4){
    Serial.print(seconds);
    Serial.println(" Sekunden");
    delay(1000);
    i++;
    seconds++;
  }
}

ISR(WDT_vect){
  Serial.println("....but not this - as long there's no failure");
}

 

So sieht dann die Ausgabe aus:

Wenn der Watchdog nicht beißt....
Wenn der Watchdog nicht beißt….

Spaßeshalber könnt ihr ja mal Zeile 18 aus- und Zeile 19 entkommentieren und schauen, wie sich die Ausgabe verändert.

Abschalten des Watchdog Timers

Wenn ihr avr/wdt.h eingebunden habt, dann könnt ihr den Watchdog Timer einfach mit wdt_disable(); abschalten. „Zu Fuß“ geht das so:

void watchdogOff(){
  cli();
  asm("WDR");
  MCUSR &= ~(1<<WDRF);
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = 0x00;
  sei();
}

 

Was euch dabei vielleicht auffällt, ist die Anweisung MCUSR &= ~(1<<WDRF);. MCUSR ist das Microcontroller Status Register und WDRF ist das Watchdog System Reset Flag Bit. Letzteres überschreibt das WDE Bit in WDTCSR und muss deswegen zwingendermaßen zuerst gelöscht werden.

Watchdog Timer „immer an“ mit dem Fuse Bit WDTON

Ihr könnt – sofern Ihr ein Programm wie Atmel Studio mit zugehörigem Programmer habt – den Watchdog Timer auch dauerhaft aktivieren, indem ihr das Fuse Bit WDTON setzt. In diesem Fall ist der reine Reset Modus ohne Watchdog Interrupt aktiviert. Ihr könnt also nur den Time-Out ändern.

Fuse Bit Programmierung in Atmel Studio
Fuse Bit Programmierung in Atmel Studio

Watchdog Timer mit Interrupt und ohne Reset

Die Variante mit Watchdog Interrupt, aber ohne System Reset, lässt sich, wie schon zuvor erwähnt, nicht in Verbindung mit wdt_enable(); realisieren. Hier müsst ihr deshalb wieder die binären Operationen verwenden. Dazu folgendes Beispiel:

#include <avr/wdt.h>

void setup(){
  Serial.begin(9600);
  watchdogSetup();
}

void loop(){
  Serial.println("Here's the loop!");
  delay(1000);
}

void watchdogSetup(void){
  cli();
  wdt_reset();
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = (1<<WDIE) | (1<<WDP3);  // 4s / interrupt, no system reset
  sei();
}

ISR(WDT_vect){
  Serial.println("Greetings from the ISR!");
}

 

Watchdog Timer Interrupt ohne Reset
Watchdog Timer Interrupt ohne Reset

Diese Einstellung des Watchdog Timers ist sinnvoll um periodisch Dinge erledigen zu lassen, ohne dabei auf zeitabhängige Vorgänge in der loop-Schleife Rücksicht nehmen zu müssen. Vielleicht wartet z.B. in der loop-Schleife ein Taster auf Betätigung und über den Watchdog wird parallel regelmäßig ein Sensor ausgelesen. Dinge der Art eben, die sonst schnell komplex werden können, wenn man sie nebeneinander in der loop-Schleife unterbringen möchte.

Aufwecken mit dem Watchdog Timer

Der Watchdog Timer kann auch verwendet werden, um den Microcontroller aus dem Schlaf zu wecken. Eine mögliche Anwendung wäre zum Beispiel die Ansteuerung eines Sensors, der alle paar Sekunden eine Messung durchführen soll. Ist zwischendurch nichts zu tun, kann man den Microcontroller auch gerne in den Schlaf schicken, um (Batterie-) Strom zu sparen. Wie das prinzipiell geht, seht ihr im folgenden Sketch:

#include <avr/wdt.h>
#include <avr/sleep.h>
const int ledPin = 12;

void setup(){
  pinMode(ledPin,OUTPUT);
  watchdogSetup();
}

void loop(){
  digitalWrite(ledPin,HIGH);
  delay(500);
  digitalWrite(ledPin,LOW);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // chose power down modus
  sleep_mode(); // sleep now!
  sleep_disable(); // disable sleep after wake up
}

void watchdogSetup(void){
  cli();
  wdt_reset();
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = (1<<WDIE) | (0<<WDE) | (1<<WDP3) | (1<<WDP0);  // 8s / interrupt, no system reset
  sei();
}

ISR(WDT_vect){//put in additional code here
}

 

Länger als 8 Sekunden Schlaf geht mit der Methode nicht. Bei Bedarf könntet ihr aber noch einen Zähler einbauen, der dafür sorgt, dass die gewünschte Aktion erst nach dem xten Aufwachen ausgelöst wird. Ansonsten geht es wieder in den Schlaf.

Watchdog am Arduino Pro Mini

Die Problematik mit dem Reset

Verschiedene Arduino Pro Mini Boards

Der folgende Sketch funktioniert am Arduino Uno ohne Probleme. Alle vier Sekunden löst der Watchdog Timer einen Reset aus. Probiert ihr denselben Sketch auf dem Arduino Pro Mini, dann stellt ihr fest, dass der Sketch nur einmal durchläuft und dann hängt.

#include <avr/wdt.h>
const int ledPin = 12;

void setup(){
  pinMode(ledPin,OUTPUT);
  watchdogSetup();
}

void loop(){
  digitalWrite(ledPin,HIGH);
  delay(1000);
  digitalWrite(ledPin,LOW);
  delay(500);
  endlessLoop(); 
  wdt_reset(); // will not happen 
}

void watchdogSetup(void){
  cli(); // disable all interrupts
  wdt_reset();
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = (1<<WDE) | (1<<WDP3); // 4s / no interrupt, system reset
  sei();
}

void endlessLoop(){
  int i = 0;
  while(i < 5){
    delay(1000);
  }
}

 

Ein Reset durch den Watchdog Timer ist auf dem Arduino Pro Mini nicht möglich. Das Problem ist dabei der Bootloader. Wie man das Problem durch einen Wechsel des Bootloaders löst, ist hier beschrieben. Es gibt aber auch zwei andere, simple Alternativen, die ich im Folgenden beschreiben werde. Beide beruhen darauf, dass der Watchdog mit Interrupt, aber ohne Reset eingestellt wird. Der Reset erfolgt dann „manuell“ in der ISR.

Workaround 1: Interrupt mit Hardware Reset

Ein LOW Signal am Reset Pin des Arduino Pro Mini initiiert einen Hardware Reset. Dazu könnt Ihr folgende Schaltung und folgenden Sketch verwenden:

Reset-Schaltung für den Arduino Pro Mini
Reset-Schaltung für den Arduino Pro Mini
#include <avr/wdt.h>
const int ledPin = 12;
const int resetPin = 9;

void setup(){
  pinMode(ledPin,OUTPUT);
  pinMode(resetPin,OUTPUT);
  digitalWrite(resetPin,HIGH);
  watchdogSetup();
}

void loop(){
  digitalWrite(ledPin,HIGH);
  delay(1000);
  digitalWrite(ledPin,LOW);
  delay(500);
  endlessLoop(); 
  wdt_reset(); // will not happen 
}

void watchdogSetup(void){
  cli(); // disable all interrupts
  wdt_reset();
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = (1<<WDIE) | (0<<WDE) | (1<<WDP3); // 4s / no interrupt, system reset
  sei();
}

void hardwareReset(){
  digitalWrite(resetPin,LOW);
}

void endlessLoop(){
  int i = 0;
  while(i < 5){
    delay(1000);
  }
}

ISR(WDT_vect){
  hardwareReset();
}

 

Vertauscht ihr in dem Sketch Zeile 7 und Zeile 8, dann könnt ihr auf den 1 kOhm Widerstand zwischen Pin 9 und RESET verzichten. Ohne das Vertauschen reicht die kurze Zeit, in der Pin 9 auf OUTPUT steht und noch nicht HIGH ist aus, um ein Reset Signal zu geben.

Workaround 2: Interrupt mit Software Reset

Für den zweiten Workaround ersetzt ihr die Funktion hardwareReset() durch eine Software Resetfunktion: void (*resetFunc)() = 0. Dann ruft ihr die Funktion resetFunc(); in der ISR auf. Oder ihr schreibt lediglich direkt in die ISR: asm volatile ("jmp 0");. Dafür ist dann keine weitere Verdrahtung notwendig.

Der Nachteil an dieser Methode ist jedoch, dass kein echter Reboot stattfindet, sondern lediglich ein Programmneustart. Der Microcontroller springt auf die Adresse Null. Alle Registerinhalte, Pinstatus, usw. bleiben erhalten wie sie sind und können so für unerwünschte Effekte sorgen. Gegebenenfalls müsst ihr also noch eine Funktion ins Setup einfügen, die für einen sicheren Ausgangszustand sorgt.

Der Watchdog am ESP8266

ESP8266 Ausführungen
ESP8266 Ausführungen

Im Vergleich zu den AVR Microcontrollern ist der Watchdog Timer des ESP8266 grundsätzlich anders konzipiert. Zunächst einmal verfügt er über einen Hardware und einen Software Watchdog Timer. Ohne weiteres Zutun ist der Software Watchdog Timer automatisch aktiviert. Ladet den folgenden Sketch auf euren ESP8266 und schaut auf dem seriellen Monitor (115200 Baud einstellen!) was passiert.

void setup() {
  //ESP.wdtDisable();
}

void loop() { 
  while(1){}             
}

 

Watchdog Timer Software Reset - Ausgabe am seriellen Monitor
Software Reset – Ausgabe am seriellen Monitor

Wie ihr seht, startet der ESP8266 ca. alle drei Sekunden neu. Bildlich gesprochen „füttert“ der Software Watchdog den Hardware Watchdog, damit dieser nicht „beißt“. Um den Software Watchdog auszuschalten, entkommentiert die Funktion ESP.wdDisable(); und schaut was passiert:

Watchdog Timer Hardware Reset - Ausgabe am seriellen Monitor
Hardware Reset – Ausgabe am seriellen Monitor

Nun gibt es ca. alle acht Sekunden einen Reset und auf dem seriellen Monitor erscheint „wdt reset“. Das war der Hardware Watchdog. Um zu verhindern, dass der Watchdog beißt, könnt ihr ihn füttern, indem ihr in die while Schleife ein ESP.wdtFeed(); einfügt. Ihr könntet allerdings genauso gut ein delay(x ms); oder irgendeine andere Funktion einfügen.

Limitierte Einstellungen

Möglichkeiten, Hardware und Software Watchdog Timer am ESP8266 zu konfigurieren, gibt es praktisch nicht. Im Grunde könnt ihr lediglich zwischen den beiden Watchdog Varianten wählen. Habt ihr den Software Watchdog ausgeschaltet, dann könnt ihr ihn mit ESP.wdtEnable(x); wieder aktivieren. x ist dabei ein Integerwert, der zwingendermaßen übergeben werden muss. Das weckt die Hoffnung, dass man damit Time-Out Periode wählen könnte. Das ist leider nicht der Fall. Egal, ob ihr z.B. ESP.wdtEnable(0); oder ESP.wdtEnable(4000); oder ESP.wdtEnable(WDTO_4S); wählt, der Effekt ist immer derselbe.

Einen komfortablen Watchdog Timer selber kreieren

Wenn ihr einen Watchdog Timer braucht, der ähnliche Funktionen wie die AVR Watchdog Timer besitzt, dann könnt ihr ihn mithilfe der Library Ticker.h mit wenigen Programmzeilen selbst erzeugen. Wie das geht, ist hier kurz und knapp beschrieben.

Danksagung

Den Wachhund im Beitragsbild habe ich von Manfred Richter auf Pixabay, die Stoppuhr stammt von OpenClipart-Vectors, auch Pixabay.

 

5 thoughts on “Watchdog Timer

  1. Klasse Tutorial, aber in Tabelle 1 mit den Prescaler-Einstellungen befindet sich ein Fehler. Bei 16000 Oszillatorzyklen beträgt der timeout 125 ms oder 0.125 s, aber nicht 0.125 ms.

  2. Schönes Tutorial, jetzt bin ich endlich mal kurz davor das zu verstehen.
    WDTCSR |= (1<<WDCE) | (1<<WDE);
    WDTCSR = (1<<WDE) | (1<<WDP3); // 4s / no interrupt, system reset

    Warum muss hier WDE zweimal aufgerufen werden? und könnte man beide Zeilen auch in einer Schreiben?

    1. Eine Frage hätte ich noch, wo sind die Werte, die hinter zB WDCE stehen definiert? Soweit ich das verstehe wird das Bit ja an einer entsprechenden Stelle gesetzt/geshiftet und zwar am Wert WDCE, oder?

      1. Zur ersten Frage: Man muss diese Sequenz einhalten. Das führt in die tiefen der Register des ATmega328P. Im Datenblatt ist das beschrieben. Es würde hier etwas zu weit führen, das zu erklären.

        Zur zweiten Frage:
        Es gibt diverse Dateien, die automatisch mit eingebunden werden ohne dass man es merkt, z.B. bei Verwendung des ATmega328P:
        C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include\avr\iom328p.h

        Und da finden sich die ganzen Definitionen, z.B.:

        #define WDP0 0
        #define WDP1 1
        #define WDP2 2
        #define WDE 3
        #define WDCE 4
        #define WDP3 5
        #define WDIE 6
        #define WDIF 7

        Das heißt ein (1<<WDCE) heißt schlicht: (1<<4), sprich setze das Bit 4.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.