Why does my AVR reset when I call wdt_disable() to try to turn the watchdog timer off?
There is a bug in the wdt_reset() library routine.

Here is the code...

__asm__ __volatile__ ( \
   "in __tmp_reg__, __SREG__" "\n\t" \
   "cli" "\n\t" \
   "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" \
)


The fourth line expands out to...

out _WD_CONTROL_REG, _BV(_WD_CHANGE_BIT) | _BV(WDE)


The intention of this line is to write a 1 to the WD_CHANGE_BIT, which will enable the following line to write a 0 to the watchdog enable bit (WDE). From the datasheet:


  To disable an enabled Watchdog Timer, the following procedure must be
  followed:
  1. In the same operation, write a logic one to WDCE and WDE. A logic one must be written to WDE regardless of the previous value of the WDE
  bit.
  2. Within the next four clock cycles, in the same operation, write the WDE and WDP bits as desired, but with the WDCE bit cleared.


Unfortunately, this assignment has the side effect of also setting the bottom 3 bits of the Watchdog Control Register (WDCE) to 0's. This immediately sets the prescaler to its shortest value. If the new prescaler has already been fired at the moment this instruction executes, the processor will be reset.

Since the watchdog timer runs off a physically independent 128&nbsp;kHz oscillator, it is hard to predict what the state of the new prescaler will be in relation to the running program. This accounts for the wide range of observed behaviors where the bug can be correlated with supply voltage, temperature, and manufacturing batch since all of these things can affect the speed of the watchdog oscillator and the system clock asymmetrically. This was a very hard bug to find!

Here is updated code that avoids this problem...

__asm__ __volatile__ ( \
   "in __tmp_reg__, __SREG__" "\n\t" \
   "cli" "\n\t" \
   "wdr" "\n\t" \
   "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" \
)


The extra wdr instruction resets the watchdog timer, so when the following line potentially switches to a different prescaler, it is guaranteed to not have timed out yet.

This could also be fixed by ORing the WD_CHANGE_BIT and WDE bits into the WD_CONTROL_REGISTER as suggested in the datasheets...

; Write logical one to WDCE and WDE
; Keep old prescaler setting to prevent unintentional Watchdog Reset
in r16, WDTCR
ori r16, (1&lt;&lt;WDCE)|(1&lt;&lt;WDE)
out WDTCR, r16


...but this requires more code and an extra scratch register. Since the watchdog counter is reset when it is disabled anyway, the extra reset does not clobber anything and has no unintentional side effects.