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!
11 Comments
1 year ago
Hey, are you in some way not exchanging the "hang" of the Arduino with the "hang" of the Tinker board? I mean - you are maybe reducing the statistics by a factor - but - replacing one computer with another computer rarely works.
Would it not be possible instead, to create a physical, analogue watchdog timer which cannot fail (unless physically wire-wise disconnected), due to that there is no digital stuff in it?
My thought was to create a super primitive logic which will see you having to maintain the voltage of an output at a certain level similar to the dead-man button for a train driver. So, if you put a capacitor from GND connecting via a resistor to ie. D4 (output), and then via another resistor to VCC, then you toggle the D4 forth and back - and then you monitor the voltage on the capacitor - and if it goes to 0 (as the D4 has got stuck at 0) or if it goes to 1 (as the D4 has got stuck at 1), or goes to 1 as the D4 has got into Tri-state (disconnected), then the logic thereafter will reset the Arduino.
We have the same problem as you - we are ALSO super talented - and have the same problem with seemingly random hanging of the Arduino boards.
What are your thoughts?
7 years ago
Good soluton. I took it one step further since my ESP8266 sometimes even hangs without responding to its reset pin. Therefore I used a 555-timer that interrupts power to the ESP (using a logical mosfet) while the ESP heartbeat can prevent this from happening by activating the 555-reset pin.
Reply 3 years ago
The solution with the 555 is exactly what I'm looking for. Could you please share some details?
Reply 3 years ago
It’s not something I have actually implemented, but would basically be a 555 timer circuit that would “activate” and remove power from the circuit being monitored. The circuit being monitored would need to reset the 555 timer prior to it triggering. (i.e., a 30 second timer and a 10 second “heartbeat”/reset, or maybe a 5 minute timer and a 2 minute reset - something where the time hung would not be caused by a long running loop, but an actual hang...)
Reply 3 years ago
Dear jonfrei,
Thanks for your quick reply.
I did some testing with a 555 WDT, it’s working but you need
a FET to empty the capacitor in order to restart the 555 clock. Also this restarting
is done on the ‘high’ heartbeat, when the controller hangs during the ‘high’
heartbeat it does not work.
I believe your ATtiny85 solution is the best option.
Reply 7 years ago
I'll have to look into that. Sounds like another "bulletproof" option!
7 years ago
Hello, I am lost please help me.
Should I write a file named "timer.h" in the Arduino library included part of text in Step 1 (below Timer.h)? Thank you.
Reply 7 years ago
Yes, you need to create the file "timer.h". It can be placed in the same directory as your sketch, in which case the Arduino IDE will open it automatically when you open your sketch. If you place it in the Arduino library, create a directory "Timer" and place it there. You would then '#include <timer.h>' instead of '#include "timer.h"'. This is what I have done, as I use this class a lot in my own code. In my other instructable ( https://www.instructables.com/id/Crawl-Space-Monitor-aka-No-More-Frozen-Pipes/ ) I provided a link ( https://www.dropbox.com/s/o8dof3h43qf3srv/timer.h?dl=0 ) so you can download the code directly instead of copying it.
Reply 7 years ago
Hi Jonfrei, thank you for prompt reply,
Now you code compiled with no errors!
Have a nice weekend and many thanks for great Instructables!
Reply 7 years ago
Glad to help!
7 years ago
Great first Instructable. I hope you will share more of your projects.