I have been using the Texas Instruments TPL5010 in my remote outdoor projects and it has served me well by saving me trips reset errant devices. However, as my projects have matured and with the capabilities of the new Generation 3 devices, I found the TPL5010 lacking.
There are three things the TPL5010 cannot do:
- Allow my to dynamically set the interrupt interval
- Reset the interval when the watchdog is “petted”. - @TI, this one is a serious miss!
- Support more robust “petting” to preclude the possibility of a flailing device twiddling the “done” line.
So, with @shanevanj’s encouragement and help, I have started to build the watchdog of my dreams. I am sharing this work here in the hope it helps some of you and in case you have suggestions to improve this project.
Hardware, I used the ATTINY85 for this first iteration as I am very familiar with its operation, it is cheap, and it does not need many external components. Here is the schematic including the programming pads.
For this first iteration, I am simply going to implement the same functionality of the TPL5010 with the (HUGE!) added benefit of resetting the interrupt interval when it is “pet”.
A short aside on why this is a big deal: If my device needs to report on the hour potentially sleeping for the hour between reporting periods, I can set an interrupt period that is just over one hour. This gets me max sleep while preventing the watchdog from waking the device unnecessarily. The watchdog resets the device if it locks up missing only one reporting period. However, this requires me to align the watchdog interrupt interval to the hourly sleep cycle. There is no way to do this with the TPL5010.
Here is the simple code for the watchdog and the code for the Particle device that you can use to test it.
ATTINY Watchdog Code
// ATtiny85 Watchdog Timer - Basic Example
// Author: Chip McClelland
// Date: 3-14-20
// License - GPL3
// ATMEL ATTINY85, 1MHz, Internal clock
//
// +-\/-+
// Reset - Ain0 (D 5) PB5 1| |8 Vcc
// Wake - Ain3 (D 3) PB3 2| |7 PB2 (D 2) Ain1 - SCL - Not used in this example
// Done - Ain2 (D 4) PB4 3| |6 PB1 (D 1) MISO - Reset uC
// GND 4| |5 PB0 (D 0) MOSI - SDA - Not used in this example
// +----+
// Interrupt code: https://gammon.com.au/forum/?id=11488&reply=9#reply9
/*
This is my dream Watchdog timer. It will function exactly as I want it - unlike the TPL5010.
First - You can set the interval in the header - wakeIntervalSeconds
Second - At the end of the interval, it will bring WAKE HIGH
Third - It will start a timer to reset which you can set in the header - resetIntervalSeconds
Finally - It will either Reset the device or restart the interval if it received a HIGH / LOW on Debounce
Version 1.0 - Minimally Viable Product - No Sleep
*/
#define adc_disable() (ADCSRA &= ~(1<<ADEN)) // disable ADC (before power-off)
#include <avr/power.h> // Power management
enum State {INITIALIZATION_STATE, IDLE_STATE, INTERRUPT_STATE, DONE_WAIT_STATE, RESET_STATE};
State state = INITIALIZATION_STATE;
// Pin assignments will for the ATTINY85
const int resetPin = PB1; // Pin to reset the uC - Active LOW
const int wakePin = PB3; // Pin that wakes the uC - Active HIGH
const int donePin = PB4; // Pin the uC uses to "pet" the watchdog
// Timing Variables
const unsigned long wakeIntervalSeconds = 3660UL; // One Hour and one minute
const unsigned long resetIntervalSeconds = 5UL; // You have this many seconds to pet the watchdog
unsigned long lastWake = 0;
unsigned long resetWait = 0;
// Program Variables
volatile bool donePinInterrupt = false; // Volatile as this flag is set in the Interrupt Service Routine
void setup() {
pinMode(resetPin,OUTPUT); // Pin to reset the uC
pinMode(wakePin,OUTPUT); // Pin to wake the uC
pinMode(donePin,INPUT); // uC to Watchdog pin
digitalWrite(resetPin, HIGH); // Unlike the TPL5010, we don't want to reset on startup - Reset is active LOW
digitalWrite(wakePin, LOW); // Wake pin is active HIGH
adc_disable(); // This saves power
PCMSK |= bit (PCINT4); // Pinchange interrupt on pin D4 / pin 3
GIFR |= bit (PCIF); // clear any outstanding interrupts
GIMSK |= bit (PCIE); // enable pin change interrupts
state = IDLE_STATE;
}
void loop() {
switch (state) {
case IDLE_STATE:
if (millis() - lastWake >= wakeIntervalSeconds * 1000UL) { // Time to send a "wake" signal?
state = INTERRUPT_STATE;
}
if (donePinInterrupt) { // This is where we can reset the wake cycle using the Done pin
lastWake = millis(); // A "done" signal will reset the interrupt interval - unlike the TPL5010!
donePinInterrupt = false;
}
break;
case INTERRUPT_STATE: // Here we will send the "wake" signal and start the timer for a response
digitalWrite(wakePin, HIGH);
resetWait = millis();
state = DONE_WAIT_STATE;
break;
case DONE_WAIT_STATE: // We will wait here until we receive a "done" if time runs out - we will reset
if (millis() - resetWait >= resetIntervalSeconds * 1000UL) { // No response - reset
digitalWrite(wakePin,LOW);
state = RESET_STATE;
}
else if (donePinInterrupt) { // Got a response - reset interval
donePinInterrupt = false;
digitalWrite(wakePin,LOW);
lastWake = millis();
state = IDLE_STATE;
}
break;
case RESET_STATE:
digitalWrite(resetPin, LOW); // Reset is active low
delay(1000); // How long do we hold the reset pin
digitalWrite(resetPin, HIGH); // Need to bring high for device to come out of reset
lastWake = millis();
state = IDLE_STATE;
break;
}
}
ISR (PCINT0_vect) { // Interrupt service routine
donePinInterrupt = true;
}
Particle Device Test Code
/*
* Project Watchdog Test Sketch
* Description: Simplest possible sketch to put the new Watchdog Timer through its paces
* Author: Charles McClelland
* Date: Started 3-12-2020
*
* Implements the following Tests
* 1 - Detects and reports a watchdog interrupt - ISR pets the watchdog
*
* v0.10 - Initial Release - Basic functionality - Straight Non-Programmable Watchdog Timer
*
*/
// Pin Constants for Boron
const int donePin = D5; // Pin the Electron uses to "pet" the watchdog
const int wakeUpPin = D8; // This is the Particle Electron WKP pin
// Program Variables
volatile bool watchDogFlag = false; // variable used to see if the watchdogInterrupt had fired
// setup() runs once, when the device is first turned on.
void setup() {
pinMode(wakeUpPin,INPUT); // This pin is active HIGH
pinMode(donePin,OUTPUT); // Allows us to pet the watchdog
Particle.publish("Status", "Beginning Test Run",PRIVATE);
attachInterrupt(wakeUpPin, watchdogISR, RISING); // Need to pet the watchdog when needed - may not be relevant to your application
}
void loop() {
if (watchDogFlag) { // Publish that we detected a watchdog event
Particle.publish("Watchdog","Detected",PRIVATE);
watchDogFlag = false;
}
}
void watchdogISR() // Watchdog Interrupt Service Routine
{
digitalWrite(donePin,HIGH);
digitalWrite(donePin,LOW);
watchDogFlag = true;
}
Please take a look and let me know if you have questions / suggestions.
Next step is to add the programmability via i2c.
Thanks, Chip