Introduction: External Hardware Watchdog.

Arduino has a built-in watchdog. This article explains how to use it: https://tushev.org/articles/arduino/5/arduino-and-watchdog-timer
There is also the "SleepyDog" library that appears to be a simple wrapper to the built-in functionality.

I must be extra talented (feel the sarcasm, please!!) because I regularly get random hangs in my projects that defy the built-in watchdog and simply stay hung. Given that winter is here, I need my crawl space monitor (more on that later, maybe) to work to keep my pipes from freezing. I work over 40 miles from home and the family has been out of town extensively lately. I had to come up with a solution for the working prototype while I continued to work on "v 0.2" trying to add a bit more stability to the project...

My solution was to add Adafruit's $6.95 Trinket (ATtiny85 on-board, 8K of flash, 512 byte of SRAM, 512 bytes of EEPROM) to the project. I use it to monitor a "heartbeat" signal from the "real" controller for the project (a Metro Mini in this case). The Trinket LED flashes according to the heartbeat signal, so I have a visual on both controllers at once. When the heartbeat times out, the Trinket brings the Reset pin of the host controller to ground and then brings it back high.

I have yet to cobble together enough of a monstrosity that this has failed on me!

https://learn.adafruit.com/adafruit-arduino-ide-setup gives instructions on setting up the Arduino IDE to work with the Adafruit boards.

Step 1: Coding the Heartbeat (host Controller)

On the host, pick a pin and cycle it between high and low. Here is the code I use to cycle pin 4 every 3 seconds (mostly):

#include "timer.h"
#define hbPin 4
int hwHeartbeat = HIGH;
timer hbTimer = timer(1000 * 3); // 3 second heartbeat

In the loop() function:

if (hbTimer.update() == 1) {
if (hwHeartbeat == HIGH) {
digitalWrite(hbPin, LOW);
hwHeartbeat = LOW;
} else {
digitalWrite(hbPin, HIGH);
hwHeartbeat = HIGH;
}
hbTimer.reset();
}

timer.h:

// Class "timer" -- monitors a time instance and returns 1 if it is passed or 0 otherwise
// It requires a time interval in milliseconds

class timer
{
//Class variables initialized at startup
unsigned long timerDuration; // How long is this timer for?

// Runtime variables
unsigned long startMillis; // When did it start?

// Constructor
public:
timer(unsigned long timer) {
timerDuration = timer;
startMillis = millis();
}

int update(void) {
if (millis() - startMillis >= timerDuration) {
return 1;
} else {
return 0;
}
}

void reset(void) {
startMillis = millis();
}

};

Periodically my app branches into a subroutine that takes about 15 seconds to run -- this has to be taken into consideration when I set the values in the Trinket's code.

Step 2: Coding the Watchdog

Here is the Trinket code, complete. timer.h is identical to the previous step.
Note that I set a 30 second heartbeat timeout -- this gives my main sketch plenty of time
to run its 15+ second subroutine and toggle the heartbeat state without timing out.
(The LED not cycling during that time is inconsequential, as there is other, visible activity
taking place.)

// This sketch is for an Adafruit Trinket 5V.
//
// The Trinket acts as a hardware watchdog on any host that has a reset pin which
// functions by being pulled LOW to ground.
//
// The Trinket monitors hbPin, requiring a state change within hbTime.
//
// The Trinket LED mimics the state of the hbPin signal from the host, giving a visual
// heartbeat of both hosts with one LED.
//
// If a state change is not detected, resetPin is pulled LOW to force a reset on the host.
// Obviously, resetPin on the Trinket needs to be connected to the RST pin on the host and both
// devices need to be on a common ground.
//
// On startup and after a reset, resetTime is invoked to insure the host has time to
// properly start up and start emitting the heartbeat signal.

#include "timer.h"

// constants won't change -- used to set pin numbers and define delays
const int hbPin = 3; // Use the signal from the host to set the
// heartbeat status
const int resetPin = 4; // use this pin to reset the host
const int ledPin = 1; // built-in LED on Trinket
const unsigned long resetTime = (1000 * 60 *3); // give the host 3 minutes to boot
const unsigned long hbTime = (1000 * 30); // 30 second heartbeat cycle before reset

// global variables
int currState = HIGH;
int prevState = LOW;

// Create timer instances
timer resetTimer = timer(resetTime); // reset delay timer
timer hbTimer = timer(hbTime); // heartbeat timer

/* --------------------------------------------------------------------------------- */

void setup() {

// Set pin modes
pinMode(resetPin, OUTPUT);
// Make sure we are turned on to start!
digitalWrite(resetPin, HIGH);

// Start with a zeroed out timers
resetTimer.reset();
hbTimer.reset();

}

/* --------------------------------------------------------------------------- */

void loop() {

ledHeartbeat();
if ((hbTimer.update() == 1) && (resetTimer.update() == 1)) {
resetHost(resetPin);
}

}

/* --------------------------------------------------------------------------------- */

void ledHeartbeat(void) {

// Pulse the LED to show WE are alive!
// Use the host's heartbeat signal to drive the LED
// This way we have visibility on both controllers with 1 indicator

digitalWrite(ledPin, currState = digitalRead(hbPin));
if ( currState != prevState) { // State has changed
prevState = currState; // Update
hbTimer.reset(); // Reset the timer
}

}

/* -------------------------------------------------------------------------------- */

void resetHost(int resetPin) {

digitalWrite(resetPin, LOW); // Turn the power relay (transistor) off
delay(5000); // 5 seconds so we don't cycle any bulbs too fast
digitalWrite(resetPin, HIGH); // Turn it back on so the host can boot!
resetTimer.reset(); // Zero out the timer

}

Step 3: Wiring It Up

The Trinket and the host controller should be powered by the same source, allowing them a common ground.

Connect the host's hbPin (4 in my example) to the Trinket's hbPin (3 as shown here). Connect the host's Reset pin to the Trinket's resetPin (4 here).

Step 4: Test It

Apply power to both devices. Once your host controller gets to loop(), you should see the Trinket LED start pulsing in time with the heartbeat value you have set. After resetTime, disconnect the connection between the hbPins of the two devices. At hbTime, the host controller should reset just like if you had pushed the reset button.

Step 5: Deploy It!

This is a cheap, rock solid solution that I am already using several places. Anything I am deploying remote or "mission critical" will certainly have it onboard!