
Tutorial WDT: Arduino Durmiente -Parte 2
En el tutorial anterior aprendimos sobre dormir la Arduino. Usamos eventos externos como un interrupt en un pin, (Pin2=INT0) o recepción de data Serial (la cual mandamos del Rx al Pin2(INT0) y la usamos como interrupt externo.
Ahora veremos como podemos usar eventos internos al MCU para despertarlo, como timers.
Requisitos:
- Computadora (mac)
- Arduino UNO
- Arduino IDE (https://www.arduino.cc/en/Main/Software)

Repaso de los modos de dormir:
- SLEEP_MODE_IDLE: 15 mA (ahorra poco pero retiene muchas funciones)
- SLEEP_MODE_ADC: 6.5 mA
- SLEEP_MODE_PWR_SAVE: 1.62 mA
- SLEEP_MODE_EXT_STANDBY: 1.62 mA
- SLEEP_MODE_STANDBY : 0.84 mA
- SLEEP_MODE_PWR_DOWN : 0.36 mA (ahorra mucho pero OJO!)
Para mayor información puede visitar (http://www.gammon.com.au/forum/?id=11497)
Repaso de las formas de despertar una MCU dormida:
- via interrupt externo. Alguna acción externa es requerida para despertarla.
- via el UART (USB serial interface). Una acción via el puerto serial USB puede despertarla.
- via un timer interno. La Arduino puede ser despertada por Timer1, uno de los timers internos que corren en su chip.
- via el watchdog timer (wdt). La Arduino solo puede ser despertada por el WDT, un timer especial que corre por aparte.
Repasemos los wakeups disponibles a cada modo de dormir según el datasheet:

Eventos Internos
Para comprender eventos internos que puedan despertar la Arduino es necesario entender un poco sobre como funciona el MCU. El video original esta inglés aquí () sin embargo hacemos una reseña sencilla a continuación:

La Atmega tiene un counter de 16MHz que controla sus operaciones (significativamente mas lento que nuestras computadoras de escritorio, portátiles e incluso nuestros celulares). Pero adicionalmente tiene otro counter de 128kHz que es aun MAS lento, llamado el wdt. Este otro counter se puede usar como alarma con un timeout o alarma. Esto también es conocido como “la mordida del perro”. El perro guardian muerde cuando se dispara la alarma o se termina el timeout. Al suceder esto se puede generar un interrupt o un reset del sistema. Esto se usa comúnmente en proyectos remotos donde, si algo llegara a pasar como falta o fluctuación de energía o algo que “atasca” la operación normal del MCU, se puede resetear automáticamente.
Como podemos ver en la imagen, el reloj principal que maneja el código principal del loop (el azul), tiene la habilidad de resetear el timer wdt. Es decir, configuramos el wat (verde) para que cuente (desde 16ms hasta 8s) hasta el timeout que deseamos. Al llegar al valor del timeout, la alarma de dispara, o el perro muerde. Pero desde el código principal (azul) podemos reiniciar el counter (verde) nuevamente a 0, antes que llegue a su valor de timeout. Esto se conoce como “Patting the Dog” o darle una palmada de cariño al perro, para que no nos muerda y se vuelva a dormir!
Veremos muchos valores de estos “relojes” y del chip que tenemos que setear al usar el wdt o cualquier de los otros 3 timers internos de la atmega328. Pero algo importante de notar es que cuando el timer se dispara y se genera un interrupt, se hace a través del ISR(), una función que veremos en los siguientes ejemplos.
Ahora veamos como despertar la Arduino via el Timer1, interno:
#include <avr/sleep.h>
#include <avr/power.h>
#define LED_PIN (13)
volatile int f_timer=0;
ISR(TIMER1_OVF_vect){
// Set la flag llamada f_timer
if(f_timer == 0){
f_timer = 1;
}
}
void enterSleep(void){
set_sleep_mode(SLEEP_MODE_IDLE);
sleep_enable();
/* Deshabilitar los peripherals para reducir consumo y también para que no generen interrupts que puedan despertar la Arduino sin querer
power_adc_disable();
power_spi_disable();
power_timer0_disable();
power_timer2_disable();
power_twi_disable();
// Dormir
sleep_mode();
// Ejecucion continua aquí luego de timer1 timeout. Es critico deshabilitar sleep aqui
sleep_disable();
// Rehabilitar peripherals
power_all_enable();
}
void setup(){
Serial.begin(9600);
// Pin LED de Arduino
pinMode(LED_PIN, OUTPUT)
// Configurar el Timer1 interno
TCCR1A = 0x00;
// Resetear el timer counter register.
// Aquí se puede asignar un valor para reducir el timeout.
TCNT1=0x0000;
// Configurar el prescaler a 1:1024 para un timeout de 4.09 segundos
TCCR1B = 0x05;
// Habilitar el timer overlook interrupt
TIMSK1=0x01;
}
void loop(){
if(f_timer==1){
f_timer = 0;
// Encender una LED
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
// Dormir!
enterSleep();
}
}
Aquí nuevamente tenemos el setup() y la función de dormir llamada enterSleep(). En el setup() configuramos el timer según el datasheet. En enterSleep() contiene al igual que sleepNow, el código para dormir la MCU. loop() revisa cuando dormir. Finalmente ISR es nueva, es una rutina que ejecuta el MCU cuando recibe un interrupt.
Finalmente veamos un ejemplo con WDT:
#include <avr/sleep.h> #include <avr/power.h> #include <avr/wdt.h> #define LED_PIN (13) volatile int f_wdt=1; ISR(WDT_vect){ if(f_wdt == 0) { f_wdt=1; } else { Serial.println("WDT Overrun!!!"); } } void enterSleep(void){ set_sleep_mode(SLEEP_MODE_PWR_SAVE);
// O usar SLEEP_MODE_PWR_DOWN para menor consumo
sleep_enable(); // Dormir sleep_mode(); // Programa continua aquí luego del WDT Overrun sleep_disable(); // Impt prioridad deshabilitar sleep // Re-habilitar peripherals power_all_enable(); } void setup(){ Serial.begin(9600); Serial.println("Inicializando..."); delay(100); // Delay requerido para el Serial pinMode(LED_PIN,OUTPUT); // Configurar WDT // Clear reset flag MCUSR &= ~(1<<WDRF); // Para cambiar WDE o el precaver, debemos set WDCE // Esto permite updates por 4 ciclos WDTCSR |= (1<<WDCE) | (1<<WDE); // Configurar nuevo watchdog-timeout prescalar WDTCSR = 1<<WDP0 | 1<<WDP3; /* 8.0 segundos */ // Habilitar WD interrupt (note no reset) WDTCSR |= _BV(WDIE); Serial.println("Inicio completo"); delay(100); // Delay requerido para el Serial } void loop(){ if(f_wdt == 1) { // Encender LED digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // Set la flag f_wdt = 0; // Entrar modo de dormir enterSleep(); } else { // Hacer algo o nada } }
Puede que encuentren otra manera mas sencilla de usar el WDT como esta:
[code]
//Al correr, Arduino Rebooted se muestra y wat comienza a contar 8s
//Ya que ifdef, calmamos al perro y no muerde e imprime
//Arduino Running. Luego de 1 segundo
//Si ifdef se comments, no calmamos al perro y muerde cada 8 segundos
#include &lt;avr/wdt.h&gt;
#define RESETWATCHDOG
void setup(){
Serial.begin(57600);
Serial.println(&quot;&quot;);
Serial.println (&quot;——-&gt;Arduino Rebooted&quot;);
Serial.println(&quot;&quot;);
wdt_enable(WDTO_8S);
}
void loop(){
#ifdef RESETWATCHDOG
wdt_reset(); //pat the dog
#endif
Serial.println(&quot;Arduino Running&quot;);
delay(1000);
}
[/code]
Esto lo que hace es reiniciar la MCU, no la duerme. Todo esto depende de que queremos lograr. Con lo que hemos visto podemos dormir o reiniciar.
DORMIR. Es para cuando queremos ahorrar energía ya que estamos corriendo nuestro proyecto de una fuente de energía limitada, como ser una batería cargada de un panel solar u otra fuente intermitente.
RESETEAR. Esta función es util cuando tenemos un proyecto remoto y debemos asegurarnos que cualquier problema con el código o con la fuente de energía que pueda “atascar” el correr normal del main loop del programa, pueda ser resuelto reiniciando la MCU.