AVR-Tutorial: Watchdog

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Dieser Artikel ist im Entstehen: Die Diskussion wird in diesem Forumsthread geführt.

Der Watchdog im AVR (WDT) ist ein spezieller Timer, der nach Ablauf (typisch ein paar Millisekunden) automatisch einen Reset auslöst. Im Normalbetrieb wird der Watchdog in der Hauptschleife des Programms regelmäßig zurückgesetzt. Wenn durch einen Fehler dieses Zurücksetzen nicht mehr stattfindet, läuft der Watchdog-Timer ab und löst einen Reset aus, um den Mikrocontroller und damit das Programm neu zu starten.

Anwendung

(Nach Ganssle-03)

Der Watchdog ist im Prinzip zur Stelle, wenn kein Anwender da ist, um den Resetknopf zu drücken.

Der Watchdog bringt dabei das System aus einem unvorhergesehenen Fehlerzustand wieder in einen betriebsbereiten Zustand.

Dieser Zustand nach einem WDT-Reset kann je nach Implementierung im Programm sein:

  • Debugzustand
  • Sicherheitszustand
  • Betriebszustand

Den Debugzustand kann man während der Entwicklung nutzen, um unvorhergesehene Ereignisse herauszufinden. Im fertigen System sollten diese durch das Debuggen bekannten Ereignisse korrekt, d. h. nicht über WDT behandelt werden.

Den Sicherheitszustand kann man verwenden, wenn das System aufgrund von Hardwareproblemen den WDT ausgelöst hat. Nach dem Reset durch den WDT wird die Resetquelle (normaler Reset oder WDT-Reset?) ausgewertet und das System bzw. die Hardware geprüft und ggf. in eine sichere Konfiguration statt in den normalen Betrieb gebracht.

Der normale Betriebszustand ist im Prinzip ein Sonderfall des Sicherheitszustands. Es ist zwar ein unerwartetes Ereignis eingetreten (z. B. einzelner zufälliger Speicherlesefehler), aber ein Neustart des Programms scheint nach einer Neuinitialisierung möglich. Ein Sonderfall ist die Anwendung des WDT zum bewussten Reset (siehe Tipps & Tricks).

Steuerung

Der WDT wird durch das Watchdog Timer Control Register (WDTCR) gesteuert.

WDTCR
WDCE[1] WDE[2] WDP2[3] WDP1[3] WDP0[3]
7 6 5 4 3 2 1 0
  1. Watch Dog Change Enable; heißt bei ATmega16/32: WDTOE – Watchdog Turn Off Enable
  2. Watch Dog Enable
  3. 3,0 3,1 3,2 Watch Dog Timer Prescaler, bit x
WDP2 WDP1 WDP0 Time-out nach [ms]
0 0 0   16,3
0 0 1   32,5
0 1 0   65  
0 1 1  130  
1 0 0  260  
1 0 1  520  
1 1 0 1100  
1 1 1 2100  
Achtung
Die Zeiten sind abhängig von der Betriebsspannung und der Taktfrequenz des WDT-Oszillators. Sie sollten daher aus dem jeweiligen Datenblatt des Mikrocontrollers entnommen werden.

Beispiele

WDT durch WDTON-Fuse aktivieren

Am einfachsten läßt es sich durch ein kleines Programmbeispiel demonstrieren.

Ein ATmega8 wird mit 4 MHz des internen Taktgenerators mit einer Start-up-Zeit von 64 ms getaktet. Die WDTON-Fuse ist gesetzt (WDT aktiviert). An Port B ist eine LED angeschlossen (Pin egal).

.include "m8def.inc"

; ATmega8L mit internen 4 MHz getaktet + 64 ms Start-up-Zeit
; WDTON aktiviert!

.def Temp1 = R16
.def SubCount = R17

.org 0x0000
        rjmp    Reset               ; Reset Handler
.org OC1Aaddr
        rjmp    timer1_compare      ; Timer Compare Handler

Reset:
        ldi     Temp1, HIGH(RAMEND) ; Stackpointer initialisieren
        out     SPH, Temp1
        ldi     Temp1, LOW(RAMEND)
        out     SPL, Temp1

        ldi     Temp1, 0xFF         ; Port B auf Ausgang
        out     DDRB, Temp1

        ldi     Temp1, high(40000 - 1)
        out     OCR1AH, temp1
        ldi     Temp1, low(40000 - 1)
        out     OCR1AL, temp1
                                    ; CTC Modus einschalten
                                    ; Vorteiler auf 1
        ldi     Temp1, ( 1 << WGM12 ) | ( 1 << CS10 )
        out     TCCR1B, temp1

        ldi     Temp1, 1 << OCIE1A  ; OCIE1A: Interrupt bei Timer Compare
        out     TIMSK, temp1

        ; kann auch weggelassen werden, da nach einem Reset das Register
        ; auf 0x00 steht, WDT-Reset nach 16 ms
        ldi Temp1, (0<<WDCE)|(0<<WDE)|(0<<WDP2)|(0<<WDP1)|(0<<WDP0)
        out WDTCR, Temp1

        sei

Mainloop:
        rjmp    Mainloop

timer1_compare:                     ; Timer 1 Output Compare Handler

        ;** findet 100-mal pro Sekunde statt (10 ms)
        wdr                         ; Watch-Dog-Reset

        inc     SubCount            ; Wenn dies nicht der 50. Interrupt
        cpi     SubCount, 50        ; ist, dann passiert gar nichts
        brne    exit_isr

        ;** findet 2-mal pro Sekunde statt (500 ms)
        clr     SubCount

        ;** Port B negieren
        in      Temp1, PinB
        com     Temp1
        out     PortB, Temp1

exit_isr:
        reti                        ; das wars. Interrupt ist fertig

Der Timer 1 läuft im CTC-Modus mit einer Frequenz von 100 Hz (10 ms). Durch den Soft-Subcounter wird die Frequenz auf 2 Hz geteilt und jeweils nach 500 ms das Port B negiert.

Da die LED in diesem Beispiel nach 500 ms jeweils ein- und ausgeschaltet wird, blinkt sie mit einer Frequenz von 1 Hz. Der WDT wird nach 10 ms zurückgesetzt, so dass er keinen RESET auslösen kann.

Wird jetzt der Befehl wdr auskommentiert, führt der WDT nach 16 ms einen RESET aus. Die LED blinkt nun, bedingt durch die Start-up-Zeit von 64 ms und einem Time-out von 16 ms, mit rund 6 Hz: 1/(64 ms + 16 ms) ≈ 12 Hz (halbe Periode)

WDT durch Software aktivieren/deaktivieren

Der WDT läßt sich auch softwaremäßig durch Setzen des WDE-Bits im WDTCR-Register aktivieren.

WDT_on:

  in Temp1, WDTCR      ; Write logical one to WDE
  ori Temp1, (1<<WDE)
  out WDTCR, Temp1
  ret

Dieses hat den Vorteil, dass man den WDT auch softwaremäßig wieder deaktivieren kann.

Ein Deaktivieren des WDTs ist nicht möglich, wenn die WDTON-Fuse gesetzt ist!

Das softwaremäßige Deaktivieren verlangt allerdings eine besondere Deaktivierungssequenz (in drei Phasen):

WDT_off:

  ;** 1. Phase
  wdr                   ; reset WDT

  ;** 2. Phase
  in  Temp1, WDTCR      ; Write logical one to WDCE and WDE
  ori Temp1, (1<<WDCE)|(1<<WDE)

  in  Temp2, SREG       ; save I Flag
  cli                   ; we have only 5 cycles to reset WDE

  out WDTCR, Temp1

  ;** 3. Phase
  ldi Temp1, (0<<WDE)   ; Turn off WDT
  out WDTCR, Temp1

  out SREG, Temp2       ; restore I Flag
  ret

Wenn WDCE und WDE nicht in einem Zug vor dem Deaktivieren auf 1 gesetzt werden, hat das Rücksetzen des WDE-Bits keine Wirkung und der WDT läuft munter weiter!

Dazu hat man maximal 5 Takte Zeit. Diese Sequenz darf auch nicht durch einen Interrupt unterbrochen werden.

Tipps & Tricks

Wem die Timer im AVR nicht reichen, der kann den Watchdog als einen zusätzlichen Timer verwenden. In den neueren AVRs hat der WDT einen eigenen Interruptvector. Dazu den WDT im Interruptmode einstellen. Der AVR springt dann bei abgelaufenem WD-Timer durch den Vector die ISR an. Dort kann man entscheiden, ob ein RESET durchgeführt wird oder ob man nur irgendwelche anderen Operationen ausführt. Besonders wenn lange Timerzeiten benötigt werden (beim ATtiny2313 läuft eine WDT-Periode bis zu 8 Sekunden) ist der WDT interessant. Insbesondere wird das ganze System nicht weiter belastet, da der WDT von einem eigenen Oszillator getaktet wird.

Nötige Einstellungen: WDE=0, WDIE=1, WDTON=1

WDT nach einem Reset

Der WDT bleibt bei manchen AVRs nach einem Reset (ob durch den Watchdog, extern oder aus sonstigen Gründen, also auch über das Flashen einer neuen Software hinweg!) aktiv, wenn er einmal an war. Er läuft danach mit der kürzesten Zeit weiter, da die Prescaler-Bits beim Reset gelöscht werden und somit die Watchdog-Zeit auf die kürzeste Zeit gesetzt wird. Das kann zu unerwarteten Problemen führen (siehe diesen Forumsthread).

Dies steht nicht explizit im Datenblatt, sondern man kann es nur anhand der Defaultwerte der Bits entnehmen, was viele übersehen. Dies ist vor allem beim Programmieren in einer Hochsprache wie C wichtig, denn da verwendet man meist Makros für den Watchdog und kommt somit nicht direkt mit den Registern aus dem Datenblatt in Berührung. Weiterhin dauert die Initialisierung der globalen Variablen vor dem Start der main-Funktion oft länger als die Watchdog-Periode, was dazu führt, dass die main-Funktion nie erreicht wird. Der Watchdog-Timer muss daher vorher abgeschaltet werden, was beim gcc über Code in einer speziellen Section geschieht, die unmittelbar nach dem Reset ausgeführt wird. Ein Beispiel findet sich dazu in diesem Thread.

WDT gezielt zum Reset verwenden

Man kann den WDT auch verwenden, um gezielt per Software einen Reset des AVR auszulösen. Das wird z. B. im AVR Bootloader FastBoot von Peter Dannegger gemacht.

Variante 1: Ohne Unterbrechung des Programmablaufs über eine Statusvariable oder Statusflag, z. B.:

    tst rFlag
    brne PC+2
    wdr

Um ein WDT-Reset auszulösen wird das Register rFlag an beliebiger Stelle auf einen Wert ungleich null gesetzt.

Variante 2: Programm anhalten und auf WDT-Reset warten

    rjmp PC

Resetquelle auswerten

Nach einem WDT-Reset wird die gleiche Adresse (0x0000) angesprungen wie nach einem normalen Reset. Zur Unterscheidung der Reset-Quelle ist eine Auswertung des WDRF-Flags im MCU-Control- und Statusregister (MCUCSR) erforderlich.

    in Temp1, MCUCSR        ; MCU-Control- und Statusregister lesen
    sbrs Temp1, WDRF        ; Prüfen, ob Watchdog-Restart erfolgte
    rjmp normalReset        ; nein: Normalstart
    cbr Temp1, 1<<WDRF      ; Watchdog-Resetflag zurücksetzen
    out MCUCSR, Temp1
    rjmp watchDogReset      ; WDRStart

Aufwecken aus einem Sleep Mode

Der WDT kann auch verwendet werden, um einen AVR im Rahmen der möglichen WDT-Zeiten zeitgesteuert aus einem Sleep Mode aufzuwecken. Allerdings verbraucht der eingeschaltete WDT einen gewissen Strom (siehe diesen Forumsthread). Beispiel in C: Pollin Funk-AVR-Evaluationsboard: Pennen bis der Hund bellt.

WDTON-Fuse zurücksetzen

Die Änderung der WDTON-Fuse wird erst nach einer Spannungsunterbrechung wirksam. Ein Reset ist nicht ausreichend: siehe diesen Forumsthread.

Siehe auch

Weblinks