Usar el watchdog de Arduino como temporizador

Se puede usar el watchdog de la placa para que en lugar de reiniciar el programa ejecute una función que nosotros le definamos. Para cambiar el modo hay que modificar el registro WDTCSR que almacena la configuración del watchdog. Este registro ocupa un byte donde cada bit tiene el siguiente significado:

Bit 7 6 5 4 3 2 1 0
nombre WDIF WDIE WDP3 WDCE WDE WDP2 WDP1 WDP0
  • WDIF: Es un flag que indica si el watchdog ha lanzado la interrupción.
  • WDIE: Si se pone a 1 cuando el tiempo de espera pase el watchdog ejecutara la ISR asociada
  • WDCE: Tiene que ponerse a 1 para poder cambiar al valorde WDE o WDP3..0. Pasados cuatro ciclos de reloj su estado vuelve a 0.
  • WDE: Si se pone a 1 cuando el tiempo de espera pase el watchdog reiniciara la placa
  • WDP3..0: Fija el valor de tiempo de espera del watchdog

Para fijar el valor del tiempo de espera se pueden usar los siguientes valores:

WDP3 WDP2 WDP1 WDP0 Tiempo
0 0 0 0 16 ms
0 0 0 1 32 ms
0 0 1 0 64 ms
0 0 1 1 125 ms
0 1 0 0 250 ms
0 1 0 1 500 ms
0 1 1 0 1 sg
0 1 1 1 2 sg
1 0 0 0 4 sg
1 0 0 1 8 sg

Veamos como configurar WDTCSR correctamente. El funcionamiento es algo extraño, primero hay que habilitar al escritura de los registros poniendo los bits WDCE y WDE a 1:

WDTCSR = (1 << WDCE) | (1 << WDE);

Luego inmediatamente hay que poner WDIE a 1, WDE a 0 y los bits WDP3, WDP2, WDP1, WDP0 con el valor de tiempo que deseemos mirando la tabla. Por ejemplo supongamos que queremos poner 2 segundos de tiempo de espera, miramos la tabla (0111) y colocamos a 1 los bits que correspondan:

WDTCSR = (1<<WDIE) | (1<<WDP2) | (1<<WDP1) | (1<<WDP0)

Si fuera 8 segundos (1001):

WDTCSR = (1<<WDIE) | (1<<WDP3) | (1<<WDP0);

Cada constante contiene el número de bit al que representa por ejemplo WDP3 es 5 al hacer 1<<WDP3 estamos poniendo a 1 el bit 5 y hacemos la operación | (or) para unir todos esos bits en un solo byte. Todo esto hay que hacerlo deshabilitando todas las interrupciones.

noInterrupts();
WDTCSR = (1 << WDCE) | (1 << WDE);
WDTCSR = (1<<WDIE) | (1<<WDP2) | (1<<WDP1) | (1<<WDP0);
interrupts();

La función que se lanzará se define usando la macro ISR:

ISR (WDT_vect){ 
//aqui va el codigo de tu ISR para el watchdog
}

Veamos un ejemplo:

#include <avr/wdt.h>
 
int count = 0;

void setup() {
  noInterrupts();
  WDTCSR = (1 << WDCE) | (1 << WDE);
  WDTCSR = (1<<WDIE) | (1<<WDP2) | (1<<WDP1) | (1<<WDP0);
  interrupts();
  Serial.begin(9600);
}
 
void loop() {
  Serial.println(count);
  delay(400);
}
 
ISR(WDT_vect) { // WDT interrupt vector
  count++;  
}

Este texto mejorado y ampliado forma parte de mi libro sobre como mejorar tus programas en Arduino. Puedes echarle un vistazo aquí.

Puedes ver la explicación en vídeo:

Haz click para ver el vídeo en mi canal de Youtube