Sat 31 Jan 2015 05:05:09 PM UTC, original submission:
The wdt_disable() macro in wdt.h overwrites the prescaler bits as an unintended side effect when setting the watchdog change enable bit.
The relevant line in wdt_disable() macro looks like this (expanded for clarity)...
===
out WDTCSR, _BV(_WD_CHANGE_BIT) | _BV(WDE)
===
IN safely level 1 (WatchDog fuse not set), the above code writes 0's to the WDTCSR register's lower 3 bits, which contain a prescaler for the counter. When changing to a lower prescaler, it is possible to trigger an unintended reset if the new prescaler has already expired.
The data sheet specially mentions this possibility in the comments for the example code to disable the watchdog, and uses an OR to set the relevant bits and preserve the prescaler bits...
===
; Write logical one to WDCE and WDE
; Keep old prescaler setting to prevent unintentional Watchdog Reset
in r16, WDTCSR
orir16, (1<<WDCE)|(1<<WDE)
out WDTCSR, r16
===
The problem can also be fixed with only one extra instruction/cycle and no extra register use by placing a watchdog reset before the assignment. Since interrupts are already disabled, this ensures that the new prescaler will not have already tripped. Note that the watchdog is reset when it is disabled anyway, so this should not have any side effects.
New macro with the reset strategy would look like...
===
_asm_ __volatile__ ( \
"in _tmp_reg_, _SREG_" "\n\t" \
"cli" "\n\t" \
"wdr" "\n\r" \
"out %0, %1" "\n\t" \
"out %0, _zero_reg_" "\n\t" \
"out _SREG_,__tmp_reg__" "\n\t" \
: /* no outputs */ \
: "I" (_SFR_IO_ADDR(_WD_CONTROL_REG)), \
"r" ((uint8_t)(_BV(_WD_CHANGE_BIT) | _BV(WDE))) \
: "r0" \
)
===
I have only tested this on an ATTINY84A, but the watchdog function is the same in every 8-bit AVR I've checked.
Let me know if you have any questions.
Thanks!
-josh
|