Power Savings with Watchdog Arduino Santiapps

Arduino Honduras Santiapps Marcio Valenzuela

Tutorial WDT: Arduino Durmiente -Parte 1

En este tutorial aprenderemos sobre dormir la Arduino.  Esto es necesario para ahorrar energía en proyectos autónomos.  En el tutoríal de la Estación Ambiental usamos una librería llamada la LowPower library para dormir nuestros electrónicos después de tomar datos para ahorrar energía.  Así nuestra batería (la cual se cargaba solamente) duraría mas tiempo ya que solo era necesario tomar muestras cada tanto tiempo.

Requisitos:

  1. Computadora (mac)
  2. Arduino UNO
  3. Arduino IDE (https://www.arduino.cc/en/Main/Software)
Arduino Tutorial Sleep Wakeup Dormir Despertar Santiapps Marcio Valenzuela
Arduino Tutorial Sleep Wakeup Dormir Despertar

Dormir la Arduino es sencillo y necesitamos usar:

#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/io.h>

Y código tan sencillo como:

void sleepNow(){
    // Cual modo queremos usar:
    set_sleep_mode(SLEEP_MODE_IDLE);
    // Habilitar sleep (SE) bit:
    sleep_enable();
    // Dormir:
    sleep_mode();
    // Al despertar continua aqui y deshabilitamos SE sleep bit.
    sleep_disable();
}

Arduino puede dormir en 5 distintos modos y una idea de los ahorros:

  • 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)

Debido a que el modo Power Down deshabilita tanta cosa (para ahorrar tanta energía) se vuelve importante el tema “Qué tenemos a disposición para despertarla?!”  Aquí entra el tema de que formas podemos despertar una Arduino durmiente:

  • 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 del chip principal.

Es importante saber en que modo dormimos para saber como despertarla porque, por ejemplo, en modo Power Down, solo el WDT y los interrupts externos son opciones para despertar, ya que el puerto serial y el timer interno están deshabilitados y por ende no los podemos usar para despertarnos!

Normalmente creamos una función como sleepNow() para dormir la Arduino.  Luego en el loop hacemos algo (tomar mediciones), luego llamamos sleepNow() para dormir la Arduino y la despertamos de distintas formas dependiendo si usamos una señal interna, externa, wdt o UART.

Eventos Externos.Señal Análoga

La connexion que usaremos para eventos externos es así de sencilla:

Arduino Simple Tutorial Sleeping Durmiendo Wakeup Despertar Santiapps Marcio Valenzuela
Arduino Simple Tutorial Sleeping Durmiendo Wakeup Despertar

Veamos un ejemplo con un Interrupt Externo:

#include <avr/sleep.h>
#include <avr/power.h>
int pin2 = 2;

void pin2Interrupt(void){
  // Este corre al despertar y solo tiene código vital de despertar...
  // Como por ejemplo desconectar el interrupt para que no se vuelva a despertar
  detachInterrupt(0);
}

void enterSleep(void){
  // Pin 2 sera el receptor del interrupt = INT0 segun atmega328
  attachInterrupt(0, pin2Interrupt, LOW);
  delay(100);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_mode();
  // Aquí se acaba de dormir...
  // y es aquí donde continuará al despertar
  sleep_disable(); 
}

void setup(){
  Serial.begin(9600);
  pinMode(pin2, INPUT);
  Serial.println("Inicio de MCU completo!");
}

int seconds=0;
void loop(){
  delay(1000);
  seconds++;
  Serial.print("Despierta por ");
  Serial.print(seconds, DEC);
  Serial.println(" segundo");
  if(seconds == 3){
    Serial.println("Durmiendome....");
    delay(200);
    seconds = 0;
    enterSleep(); //La mandamos a dormir...
  }
}

Es necesario definir el pin que recibirá el interrupt externo.  La Arduino tiene 2 pines para este fin, pin #2 y #3.  Se definen en attachInterrupt(a,b,c) usando el primer parametro “a” donde 0 = pin 2 y 1 = pin 3.  Luego con el parámetro “b” definimos la función que sera llamada al despertar. c define que tipo de interrupt esperamos en ese pin.

En este caso usamos el botón para enviar la señal al INT0, el cual activa la función pin2Interrupt(), el cual nos regresa a sleepNow() justo en la linea de sleep_disable() y continua con el loop() y la vuelve a dormir después de 3 segundos.

Ok despertar la Arduino basado en un evento externo es muy fácil de entender.  Lo que resta aquí es definir ese evento para nuestras necesidades.  Es decir, como vamos a generar el evento que enviara el interrupt a nuestra Arduino.  Esto esta fuera del alcance de este tutorial pero es sencillamente un evento como una alarma (basada en un modulo RTC) o una medición de algún sensor como cambio en luz, temperatura, un botón o SMS.

Eventos Externos.Señal UART

Ahora veamos un ejemplo usando la UART, que en realidad no es mas que un tipo especial de interrupt externo.  Recordemos que el UART (USB-Serial) esta desahabilitado en MODE_PWR_DOWN sin embargo es muy común hacer un truco para usarlo.  La razón es que es muy deseado usar MODE_PWR_DOWN ya que es el modo que mas ahorra pero al mismo tiempo es muy practico usar el puerto Serial para recibir datos y despertar la Arduino.

Primero veremos un ejemplo de usar el Serial para dormir la Arduino y explicaremos porque.

#include <avr/sleep.h>

// Resistor entre RX y pin2. Por default RX se lleva a 5V
// Asi podemos usar una secuencia de datos Seriales de RX a 0
// Esto hara que pin2 vaya a LOW, activando INT0 via external interrupt
// despertando la MCU.
// También hay un counter duerme la MCU luego de 10 secs
int wakePin = 2;                 // pin para despertar
int sleepStatus = 0;             // variable para el estatus
int count = 0;                   // counter

void wakeUpNow()  {      // El interrupt nos trae aqui al despertar
// Luego continua en la ultima linea en loop() despues de dormir.
// Funciones como timers y otros como serial.print no funcionan aquí
}

void setup(){
  pinMode(wakePin, INPUT);
  Serial.begin(9600);
}

void sleepNow()     {    // Aqui dormimos la MCU
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);   // Modo PWR_DOWN
    sleep_enable();      // Habilita el sleep bit en el mcucr register
                         // para poder dormir 

    // attachInterrupt(A, B, C) 
    //A Sera 0 o 1 para interrupts en pin 2 o 3. 
    //B Handler que se llamara al interrumpir. 
    //C Modo de activation del interrupt pin: 
    // LOW bajo 
    // CHANGE cambio de nivel 
    // RISING creciente 
    // FALLING decreciente 
    // En todos menos IDLE se puede usar LOW. 
    attachInterrupt(0, wakeUpNow, LOW); 
    // usar interrupt 0 (pin 2) y correr función.
    // wakeUpNow cuando pin 2 llegue a LOW 
    sleep_mode(); // Este comando la duerme
                  // y el programa continua aqui al despertar
    sleep_disable();         // Prioridad luego de despertar!
    detachInterrupt(0);      // Deshabilita interrupt 0 en pin 2 para
                             // que wakeUpNow no se vuelva a ejecutar 
                             // durante el corrido normal.
}
void loop(){
  // Mostrar counter
  Serial.print("Despierta por ");
  Serial.print(count);
  Serial.println("sec");
  count++;
  delay(1000);                           // Esperar 1 segundo

  // Procesar input del Serial
  if (Serial.available()) {
    int val = Serial.read();
    if (val == 'S') {
      Serial.println("Serial: Entrando modo dormir");
      delay(100);     // Se requiere un pequeño delay  
      count = 0;
      sleepNow();     // Aquí llamamos a dormir según input 
    }
    if (val == 'A') {
      Serial.println("Hola Caracola"); // Aqui despertamos SI en IDLE*
    }
  }

  // Revisamos si pasaron 10 segundos para también dormir
  if (count >= 10) {
      Serial.println("Timer: Entrando dormir por timer");
      delay(100);     // Delay requerido
      count = 0;
      sleepNow();     // Dormir
  }
}

Recordemos que en PWR_DOWN el Serial esta deshabilitado según el datasheet:

Arduino Tutorial Sleep Wake up Dormir Despertar Santiapps Marcio Valenzuela
Arduino Tutorial Sleep Wake up Dormir Despertar

Por eso no pudimos usar datos seriales en el código anterior para despertar la MCU.  Teníamos que usar el botón.

Si queremos usar Serial UART para despertar la Arduino tendríamos que conectar el Pin 2 con el Rx.  Así cuando enviamos datos al Serial, bajaran los 5V del Pin 2 y eso disparará el interrupt.  De otra manera, tendríamos que modificar el código cambiando el modo de dormir a SLEEP_MODE_IDLE y allí si podríamos usar Serial input directo para despertar, sin interconectar el Rx al Pin 2(INT0).

Eventos Internos.Timers

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:

Arduino Atmel Atmega328 Sleep Mode Watchdog Timer Simple Tutorial Santiapps Marcio Valenzuela
Arduino Atmel Atmega328

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.

Eventos Internos.WDT

Finalmente veamos un ejemplo con WDT:

#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>
#define LED_PIN (13)
volaile 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
pinMde(LED_PIN,OUTPUT);
// Configurar WDT
// Clear reset flag
MCUSR &amp;= ~(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:


//Al correr, Arduino Rebooted se muestra y wdt 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 <avr/wdt.h>
#define RESETWATCHDOG
void setup(){
Serial.begin(57600);
Serial.println("");
Serial.println ("Arduino Rebooted");
Serial.println("");
wdt_enable(WDTO_8S);
}
void loop(){
#ifdef RESETWATCHDOG
wdt_reset(); //pat the dog
#endif
Serial.println("Arduino Running");
delay(1000);
}

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.

6 Comments

  • Hola,

    gracias por el post, me ha servido de gran ayuda, sin embargo no ha resuelto el problema de mi proyecto concreto y es por esto por lo que me gustaría plantearte la siguiente duda:

    He desarrollado una red de sensores inalámbrica y energéticamente autónoma (placa fotofoltaica + bateria ion-LI) consistente en dos nodos que retransmiten mediante Xbee valores de humedad de suelo a un coordinador que mediante Ethernet envía los valores registrados a una plataforma en la nube, la cual representa en tablas los valores registrados y genera también gráficas. Hasta aquí todo bien.

    El paso que me queda y me gustaría poder dar es hacer el sistema lo energéticamente más eficiente posible, es decir, que consuma la menor energía posible, ya que se está alimentando con energía solar y batería. Para ello me gustaría tomar datos de la humedad cada hora, es decir, que el nodo se encuentre en sleep mode y cada hora se active automaticamente, tome valores, los envíe y vuelva a dormir hasta la próxima hora. Entiendo que esto se puede realizar integrando a la placa arduino un reloj externo y configurando el sketch para que, en función de dicho reloj externo, entre o salga del modo sleep.

    Lo que me tiene más despistado es que, aparte de los sensores de humedad, quiero instalar un caudalímetro que mida el caudal cuando pase agua por el mismo. En este caso no puedo controlar cuando despertar al Arduino ya que el caudal puede pasar a cualquier hora del día y, si está durmiendo no se leería el valor del caudalímetro. ¿Es posible despertar a Arduino mediante la entrada de una señal externa digital o analógica?, es decir, ¿es posible que Arduino despierte cuando el caudalímetro empiece a registrar valores?

    Te agradecería me dieses alguna sugerencia si se te ocurre algo o conoces algún caso parecido.

    Quedo a la espera de tu respuesta.

    Gracias,

    Un saludo!

    • marskoko

      Claro que puedes despertar la Arduino vía una señal externa. La Arduino tiene el wdt que puedes programar para despertar vía una señal externa. Pero los módulos wifi también tienen un wake up system que puedes programar y ellos mismos pueden despertarse vía una señal externa. Que módulo usas?

      • Hola marskoko,

        gracias por contestar. Uso el módulo Xbee Digimesh 2.4. Sé que los Xbee tienen también configuración de modo sleep, de hecho he indagado bastante sobre ellos pero entiendo que esto sólo gobernaría los xbee, el Arduino seguiría funcionando, es decir, no entraría en modo sleep, sólo lo haría el módulo xbee y el consumo de energía no se vería muy reducido. En cambio si consigo programar la placa Arduino para que entre en modo Sleep cuando yo quiero, los módulos xbee también lo harán dado que estos se alimentan a través de un shield conectado a la placa Arduino.

        Puedes darme más pistas sobre cómo despertar una placa arduino en sleep mediante una entrada digital entrante, en este caso sería la activación del caudalímetro??

        Conoces proyectos o ejemplos similares??

        Gracias de antemano por tu ayuda.

        Un saludo!

  • Carlos

    Hola! Tengo un problema y no hay manera de solucionarlo. Duermo a Arduino con PWR_DOWN y lo despierto con una señal externa aplicada al pin 2 y al cabo de X segundos se vuelve a “Dormir”. El problema es que no me funciona siempre. Me explico, lo enciendo y quizás la maniobra me funciona 3 veces y deja de funcionar, como 8, como ninguna. Ya no se que probar para solucionarlo. Gracias de antemano.

Leave a Reply