Watchdog

The watchdog is a build-in module present in all atmega controllers. It triggers a reset if for a predefined period of time nothing is done to prevent it.

The controller has a special machine instruction for the watchdog reset called wdr. Amforth has a wrapper forth word with the same name after including the file wdr.asm.

This word needs to be called often enough to keep the watchdog from resetting the controller. For a system that basically waits at the command prompt the pause command could be sufficient:

> ' wdr is pause

Initialization

Early atmega variants need to initialize the watchdog every time after a reset, newer ones keep it active even over resets. This may cause troubles since the WDR needs to be called much earlier for these controllers. One solution is to place the WDR activation at the beginning of the turnkey actions.

Watchdog Timer

Watchdog timer words, build AmForth with

wdr.asm
provides wdr ( – ) resets watchdog (wdr)
store-wd.asm
provides !wdc ( n – ) changes WDTCSR & clears WDRF

from the avr8/words directory. It also makes sense to build with sleep.asm.

+wdt ( -- )
turn on System Reset Mode
-wdt ( -- )
turn off System Reset Mode
+wdi ( -- )
turn on Interrupt Mode
-wdi ( -- )
turn off Interrupt Mode
wd.delay! ( n -- )
write prescaler AND -wdi -wdt

include the correct constants for device below are for atmega328p, or use the amforth-shell script.

&12 constant WDTAddr     \ Watchdog Time-out Interrupt
&96 constant WDTCSR      \ Watchdog control register

\             7    6    5    4   3    2    1    0
\ WDTCSR = WDIF WDIE WDP3 WDCE WDE WDP2 WDP1 WDP0

: +wdt ( -- ) WDTCSR c@ %00001000 or !wdc ;
: +wdi ( -- ) WDTCSR c@ %01000000 or !wdc ;
: -wdt ( -- ) WDTCSR c@ %00001000 invert and !wdc ;
: -wdi ( -- ) WDTCSR c@ %01000000 invert and !wdc ;

: wd.delay! ( n -- )
  \ !wdc is given 00?00??? to write to WDTCSR
  \ set prescaler and unset WDIE and unset WDE
  dup $7 and swap $8 and 2 lshift or !wdc
;

\ From page 55 of atmega328 datasheet
\ WDP3 WDP2 WDP1 WDP0

%0000 constant wd.16ms
%0001 constant wd.32ms
%0010 constant wd.64ms
%0011 constant wd.125ms
%0100 constant wd.250ms
%0101 constant wd.500ms
%0110 constant wd.1s
%0111 constant wd.2s
%1000 constant wd.4s
%1001 constant wd.8s

Examples

Warning

Many of these example intentionally result in your AVR8 microprocessor being reset.

#include ms.frt
#include ./wd.forth

: ex.1 ( -- ) \ reset in 8 seconds
  wd.8s wd.delay! +wdt 8 0 ?do 1000 ms i 1+ . cr loop
;

: ex.2 ( -- ) \ use wdr to defer reset but eventually fail
  wd.4s wd.delay! +wdt 6 0 ?do wdr 1000 i * dup ms . cr loop
;

\ constants for atmega328p and UNO for PIN 13 LED

$24 constant DDRB
$25 constant PORTB

: hb.isr ( -- ) \ toggle PIN 13 on UNO
  #32 PORTB c@ xor PORTB c!
;

: ex.3 ( -- ) \ interrupt only no reset and toggle an led
  #32 DDRB c@ or DDRB c!  \ set PIN13 on UNO for output
  ['] hb.isr WDTAddr int! \ load xt of word to be run on wd timeout
  wd.500ms wd.delay! +wdi \
;

: ex.4 ( -- ) \ run after ex.3
            \ turn off watchdog interrupt and then turn on again
  -wdi 4 0 ?do 1000 ms i loop +wdi cr
;

use watchdog interrupt to wake from sleep this needs an AmForth built with sleep.asm

variable snooze

: ex.5 ( -- ) \ use watchdog interrupt to wake from deep sleep
  0 snooze !
  ['] noop WDTAddr int! \ interrupt routine does nothing
  wd.4s wd.delay! +wdi  \ except wake the MCU up.
  begin
      3 sleep           \ sleep
      snooze dup @ 1+ dup . cr swap ! \ inc print store
      50 ms             \ small delay to allow print to finish
  snooze @ 5 > until    \ exit after 6 sleeps
;

\ use watchdog interrupt and reset

#include is.frt
#include values.frt
#include avr-values.frt
#include defers.frt

variable app-reg          \ my "application" status register
0 Evalue app-reg-save     \ persistant EEPROM store for above
                        \ to survive a reset
: panic.isr ( -- )

  \ wdr wasn't called in time
  \ ...

  app-reg @ to app-reg-save  \ store "application" status register
  #32 PORTB c@ xor PORTB c!  \ turn on PIN 13 LED

  \ will reset on next
  \ watchdog time out

;

: ex.6 ( -- ) \ use watchdog interrupt and reset
  #32 DDRB c@ or DDRB c!            \ set PIN13 on UNO for output
  #32 invert PORTB c@ and PORTB c!  \ set PIN13 on low
  ['] panic.isr WDTAddr int!        \ load xt of word to be run on wd timeout
  0 to app-reg-save                 \ zero eeprom store of "register"

  wd.125ms wd.delay! +wdt +wdi

  s" Will reset in a short while. Look at app-reg-save after" itype cr

  250 1 ?do
      i ms wdr app-reg dup @        \ some "made up" app-reg value
      i +  swap !
  loop

  \ after the reset/power cycle look at Evalue app-reg-save
;

: ex.7 ( -- ) \ (roughly) what frequency is my 128 kHz ?

  #32 DDRB c@ or DDRB c!  \ set PIN13 on UNO for output
  ['] hb.isr WDTAddr int! \ load xt of word to be run on wd timeout

  \ frequency f on PIN 13
  \ 1 period is 2 timeouts
  \ each timeout is 2000 ticks

  \ so 2*f*2000 is roughly
  \ the frequency of my UNO's
  \ 128 kHz oscillator.

  wd.16ms wd.delay! +wdi

  \ I measure f to be 28.56Hz
  \ so watchdog  ~114.2kHz

  \ compare datasheet page 606
  \ for VCC=5V T=25DegC
  \ from chart ~114.2kHz
;

Acknowledgements

This recipe is based upon work by David Wallis and Tristan Williams