user82226: I have a custom board that is switching an inductive load through a 10A@230V ac relay.
Due to a poor design, voltage spikes are causing the board to reboot from time to time (brown-out, watchdog and "unknown reason")
The problem here is that from time to time, the microcontroller freezes and no longer executes code (or at least, it seems so), even if the watchdog should be running.
I programmed the WDTON and BROWN-OUT fuses, so the watchdog should be always on.
Once the microcontroller is frozen, an external reset makes it work again.
My questions:
Is there any SOFTWARE fix to guarantee that the device never freezes. Am I using the watchdog incorrectly?
I read in the Atmega 328P datasheet about the 4 cycle restriction between the WDE writing and the prescaler bits writing (page 44), but It´s not clear to me if this restriction applies when WDON fuse is programmed.
Also, I couldn´t find what the "wdt_enable (WDTO_2S);" instruction is doing under the hood.
Is it possible that the watchdog oscillator stops due to a voltage spike?
This is my code (the part to illustrate the watchdog problem)
Hardware: Custom board
microcontroller: ATmega 328P
Brown-out fuse: programmed
WDTON fuse: programmed

#include <avr/wdt.h>

#define R0          3 // Relay output 16A @ 230Vac (10A @ 230Vac)
#define R1          4 // Relay output 10A @ 230Vac (10A @ 230Vac)
#define R2          5 // Relay output 10A @ 230Vac (10A @ 230Vac)

byte outputs;

//-------------------------------------------------------------------------

void test() {

#define TEST_SWITCHING_INTERVAL  500

  static unsigned long previousMillisTest;
  static int count;

  unsigned long actualTemp = millis();
  if ((actualTemp - previousMillisTest) >= TEST_SWITCHING_INTERVAL) {
    bitWrite (outputs, 0, !bitRead (outputs, 0)); Serial.print ("R0 = ");    Serial.print (bitRead (outputs, 0));
    bitWrite (outputs, 1, !bitRead (outputs, 1)); Serial.print ("   R1 = "); Serial.print (bitRead (outputs, 1));
    bitWrite (outputs, 2, !bitRead (outputs, 2)); Serial.print ("   R2 = "); Serial.print (bitRead (outputs, 2));
    Serial.print ("   count: "); Serial.print (count++);
    Serial.print ("  millis: "); Serial.println (previousMillisTest);
    previousMillisTest = actualTemp;
  }
  digitalWrite (R0, bitRead (outputs, 0));
  digitalWrite (R1, bitRead (outputs, 1));
  digitalWrite (R2, bitRead (outputs, 2));
}

//-----------------------------------------------------------------------

void setup() {

  uint8_t MCUSR_save;

  MCUSR_save = MCUSR;
  MCUSR = 0;
  wdt_enable (WDTO_2S);

  Serial.begin (9600);

  if      (MCUSR_save & 0x01)  Serial.println ("*********Restarting . Power-on Reset*********");
  else if (MCUSR_save & 0x02)  Serial.println ("*********Restarting . External Reset*********");
  else if (MCUSR_save & 0x04)  Serial.println ("*********Restarting . Brown-out Reset*********");
  else if (MCUSR_save & 0x08)  Serial.println ("*********Restarting . Watchdog Reset*********");
  else if (MCUSR_save & 0x10)  Serial.println ("*********Restarting . JTAG Reset*********");
  else                         Serial.println ("*********Restarting . Unknown reason*********");
  Serial.print ("MCUSR = 0x"); Serial.println (MCUSR_save, HEX);

  initPins();
  initDevice();

  if (DEBUG) showConfig();

  // prevents flooding the RS485 bus if the device is continuously restarting
  delay (100);

  sendFrame (RESTARTED, 0); // sends the "restarted" event through the RS485 port

}

//---------------------------------------------------------------------------

void loop() {
  wdt_reset();
  test();
}



user10794: You can choose to either reset the processor or execute an interrupt routine. Example code:
#include <avr/wdt.h>

// watchdog intervals
// sleep bit patterns for WDTCSR
enum
{
  WDT_16_MS  =  0b000000,
  WDT_32_MS  =  0b000001,
  WDT_64_MS  =  0b000010,
  WDT_128_MS =  0b000011,
  WDT_256_MS =  0b000100,
  WDT_512_MS =  0b000101,
  WDT_1_SEC  =  0b000110,
  WDT_2_SEC  =  0b000111,
  WDT_4_SEC  =  0b100000,
  WDT_8_SEC  =  0b100001,
 };  // end of WDT intervals enum
 
void setup()
  {
  wdt_disable ();
  Serial.begin(115200);
  Serial.println ();
  Serial.println("After reset");
  watchdogSetup(WDT_4_SEC);
  }

volatile bool watchdogFired = false;

ISR(WDT_vect) // Watchdog timer interrupt.
  {
  watchdogFired = true;
  }
 
void watchdogSetup(byte interval)
  {
  cli(); // disable all interrupts

  // Enter Watchdog Configuration mode:
  WDTCSR = bit (WDCE) | bit (WDE);
  // Set Watchdog settings:
  //  WDTCSR = bit (WDIE) | interval;   // set WDIE, and requested delay - for an INTERRUPT
  WDTCSR = bit (WDE) | interval;    // set WDIE, and requested delay - for a RESET
  wdt_reset(); // reset the WDT timer
  sei();  // enable interrupts again
  }
  
void loop()
  {

  if (watchdogFired)
    {
    watchdogFired = false;
    Serial.println ("Watchdog fired.");
    }  

  // wdt_reset();  // if you want to reset the watchdog
  
  } // end of loop

This example resets the processor, however if you uncomment the line WDTCSR = bit (WDIE) | interval; and comment the one below WDTCSR = bit (WDE) | interval; then it interrupts instead. You can see that in the example output.
If you want an interrupt then you have to supply an interrupt routine (ISR).

Responding to comments:

But there´s little documentation about how to adequately recover from a reset when the fuse is programmed.

The processor resets, which is what you want, right? You don't need to "recover" from a reset. It's like saying how do you recover from powering it on.

Do I still need to re-enable the watchdog, even if it should be enabled by the fuse?.

I doubt it. In my code the watchdog keeps firing, even after you get the message the first time (the interrupt one).
However you might want to change the interval to the one you want. And you might want to reset the watchdog (wdt_reset ()) if things are going OK.

Also, with the WDTON fuse enabled, the ISR mode is not valid (only the reset mode).

Well, there is not necessarily an ISR for the ISR mode (yet, if ever). The datasheet specifically says that you get the reset mode and not the ISR mode.
It is unclear from the datasheet what the time interval is, with the WDTON fuse programmed.

82226: <p>I have a custom board that is switching an inductive load through a 10A@230V ac relay.
Due to a poor design, voltage spikes are causing the board to reboot from time to time (brown-out, watchdog and &quot;unknown reason&quot;)</p>
<p>The problem here is that from time to time, the microcontroller freezes and no longer executes code (or at least, it seems so), even if the watchdog should be running.
I programmed the WDTON and BROWN-OUT fuses, so the watchdog should be always on.</p>
<p>Once the microcontroller is frozen, an external reset makes it work again.</p>
<p>My questions:</p>
<p>Is there any SOFTWARE fix to guarantee that the device never freezes. Am I using the watchdog incorrectly?</p>
<p>I read in the Atmega 328P datasheet about the 4 cycle restriction between the WDE writing and the prescaler bits writing (page 44), but It´s not clear to me if this restriction applies when WDON fuse is programmed.
Also, I couldn´t find what the &quot;wdt_enable (WDTO_2S);&quot; instruction is doing under the hood.</p>
<p>Is it possible that the watchdog oscillator stops due to a voltage spike?</p>
<p>This is my code (the part to illustrate the watchdog problem)</p>
<p>Hardware: Custom board</p>
<p>microcontroller: ATmega 328P</p>
<p>Brown-out fuse: programmed</p>
<p>WDTON fuse: programmed</p>
<pre class="lang-cpp prettyprint-override"><code>
#include &lt;avr/wdt.h&gt;

#define R0          3 // Relay output 16A @ 230Vac (10A @ 230Vac)
#define R1          4 // Relay output 10A @ 230Vac (10A @ 230Vac)
#define R2          5 // Relay output 10A @ 230Vac (10A @ 230Vac)

byte outputs;

//-------------------------------------------------------------------------

void test() {

#define TEST_SWITCHING_INTERVAL  500

  static unsigned long previousMillisTest;
  static int count;

  unsigned long actualTemp = millis();
  if ((actualTemp - previousMillisTest) &gt;= TEST_SWITCHING_INTERVAL) {
    bitWrite (outputs, 0, !bitRead (outputs, 0)); Serial.print (&quot;R0 = &quot;);    Serial.print (bitRead (outputs, 0));
    bitWrite (outputs, 1, !bitRead (outputs, 1)); Serial.print (&quot;   R1 = &quot;); Serial.print (bitRead (outputs, 1));
    bitWrite (outputs, 2, !bitRead (outputs, 2)); Serial.print (&quot;   R2 = &quot;); Serial.print (bitRead (outputs, 2));
    Serial.print (&quot;   count: &quot;); Serial.print (count++);
    Serial.print (&quot;  millis: &quot;); Serial.println (previousMillisTest);
    previousMillisTest = actualTemp;
  }
  digitalWrite (R0, bitRead (outputs, 0));
  digitalWrite (R1, bitRead (outputs, 1));
  digitalWrite (R2, bitRead (outputs, 2));
}

//-----------------------------------------------------------------------

void setup() {

  uint8_t MCUSR_save;

  MCUSR_save = MCUSR;
  MCUSR = 0;
  wdt_enable (WDTO_2S);

  Serial.begin (9600);

  if      (MCUSR_save &amp; 0x01)  Serial.println (&quot;*********Restarting . Power-on Reset*********&quot;);
  else if (MCUSR_save &amp; 0x02)  Serial.println (&quot;*********Restarting . External Reset*********&quot;);
  else if (MCUSR_save &amp; 0x04)  Serial.println (&quot;*********Restarting . Brown-out Reset*********&quot;);
  else if (MCUSR_save &amp; 0x08)  Serial.println (&quot;*********Restarting . Watchdog Reset*********&quot;);
  else if (MCUSR_save &amp; 0x10)  Serial.println (&quot;*********Restarting . JTAG Reset*********&quot;);
  else                         Serial.println (&quot;*********Restarting . Unknown reason*********&quot;);
  Serial.print (&quot;MCUSR = 0x&quot;); Serial.println (MCUSR_save, HEX);

  initPins();
  initDevice();

  if (DEBUG) showConfig();

  // prevents flooding the RS485 bus if the device is continuously restarting
  delay (100);

  sendFrame (RESTARTED, 0); // sends the &quot;restarted&quot; event through the RS485 port

}

//---------------------------------------------------------------------------

void loop() {
  wdt_reset();
  test();
}

</code></pre>
 10794: <p>You can choose to either reset the processor or execute an interrupt routine. Example code:</p>
<pre class="lang-cpp prettyprint-override"><code>#include &lt;avr/wdt.h&gt;

// watchdog intervals
// sleep bit patterns for WDTCSR
enum
{
  WDT_16_MS  =  0b000000,
  WDT_32_MS  =  0b000001,
  WDT_64_MS  =  0b000010,
  WDT_128_MS =  0b000011,
  WDT_256_MS =  0b000100,
  WDT_512_MS =  0b000101,
  WDT_1_SEC  =  0b000110,
  WDT_2_SEC  =  0b000111,
  WDT_4_SEC  =  0b100000,
  WDT_8_SEC  =  0b100001,
 };  // end of WDT intervals enum
 
void setup()
  {
  wdt_disable ();
  Serial.begin(115200);
  Serial.println ();
  Serial.println(&quot;After reset&quot;);
  watchdogSetup(WDT_4_SEC);
  }

volatile bool watchdogFired = false;

ISR(WDT_vect) // Watchdog timer interrupt.
  {
  watchdogFired = true;
  }
 
void watchdogSetup(byte interval)
  {
  cli(); // disable all interrupts

  // Enter Watchdog Configuration mode:
  WDTCSR = bit (WDCE) | bit (WDE);
  // Set Watchdog settings:
  //  WDTCSR = bit (WDIE) | interval;   // set WDIE, and requested delay - for an INTERRUPT
  WDTCSR = bit (WDE) | interval;    // set WDIE, and requested delay - for a RESET
  wdt_reset(); // reset the WDT timer
  sei();  // enable interrupts again
  }
  
void loop()
  {

  if (watchdogFired)
    {
    watchdogFired = false;
    Serial.println (&quot;Watchdog fired.&quot;);
    }  

  // wdt_reset();  // if you want to reset the watchdog
  
  } // end of loop
</code></pre>
<p>This example resets the processor, however if you uncomment the line <code>WDTCSR = bit (WDIE) | interval;</code> and comment the one below <code>WDTCSR = bit (WDE) | interval;</code> then it interrupts instead. You can see that in the example output.</p>
<p>If you want an interrupt then you have to supply an interrupt routine (ISR).</p>
<hr />
<p>Responding to comments:</p>
<blockquote>
<p>But there´s little documentation about how to adequately recover from a reset when the fuse is programmed.</p>
</blockquote>
<p>The processor resets, which is what you want, right? You don't need to &quot;recover&quot; from a reset. It's like saying how do you recover from powering it on.</p>
<blockquote>
<p>Do I still need to re-enable the watchdog, even if it should be enabled by the fuse?.</p>
</blockquote>
<p>I doubt it. In my code the watchdog keeps firing, even after you get the message the first time (the interrupt one).</p>
<p>However you might want to change the interval to the one you want. And you might want to reset the watchdog (wdt_reset ()) if things are going OK.</p>
<blockquote>
<p>Also, with the WDTON fuse enabled, the ISR mode is not valid (only the reset mode).</p>
</blockquote>
<p>Well, there is not necessarily an ISR for the ISR mode (yet, if ever). The datasheet specifically says that you get the reset mode and not the ISR mode.</p>
<p>It is unclear from the datasheet what the time interval is, with the WDTON fuse programmed.</p>
