Power Save Mode (Sleep)

Hola,

He estado jugando un poco con sleep del ATMega168. En concreto con Power save Mode, para sustituir esto en mis delays y poder ahorrar bateria.

En concreto pongo el micro en Sleep y se despierta cuando hay un Overflow del Timer2.

Viendo resultados, merece la pena....

Pongo el código de una simple aplicación que hace parpadear el led del pin 13 del Diecimila durante 2 segundos (para poder medir la corriente).
Los resultados han sido:
-Sleep Mode and turn off devices=> Led_on=12,98 mA & Led_off=9,91 mA
-No Sleep and turn off devices => Led_on=28,9 mA & Led_off=25,9 mA
-No Sleep => Led_on=32,2 mA & Led_off=29,2 mA

Turn off devices es que apago el ADC, Timer 0, Timer 1, USART, TWI y SPI.

SLEEP MODE TEST CODE

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


volatile int cont;

void setup()
{
  pinMode(13,OUTPUT);
  
  TCCR2A=0;  
  TCCR2B=(0<<7)|(0<<6)|(0<<5)|(0<<4)|(0<<3)|(1<<2)|(1<<1)|(1<<0);
  // Bit 0.- Overflow
  TIMSK2=(0<<2)|(0<<1)|(1<<0);
  //Power Save Mode
  SMCR=(0<<3)|(1<<2)|(1<<1)|(0<<0);
    
}

ISR(TIMER2_OVF_vect)
{
  cont++;
}

void loop()
{

   sleep_enable();
   //Turn off ADC
   ADCSRA=0;
   // TWI | TIMER2 | TIMER0 | VOID | TIMER1 | SPI | USART | ADC
   PRR=(1<<7)|(0<<6)|(1<<5)|(1<<4)|(1<<3)|(1<<2)|(1<<1)|(1<<0);
   sei();
   while (cont<122)
   {
   //Go to Sleep
   sleep_mode();
   }
   //Disable Sleep
   sleep_disable();
   //Disable interrupts
   cli();
   cont=0;
   digitalWrite(13,!digitalRead(13));
   
   
}

NO SLEEP AND TURN OFF DEVICES

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


volatile int cont;

void setup()
{
  pinMode(13,OUTPUT);
  
  TCCR2A=0;  
  TCCR2B=(0<<7)|(0<<6)|(0<<5)|(0<<4)|(0<<3)|(1<<2)|(1<<1)|(1<<0);
  // Bit 0.- Overflow
  TIMSK2=(0<<2)|(0<<1)|(1<<0);
  //Turn off ADC
  ADCSRA=0;
  // TWI | TIMER2 | TIMER0 | VOID | TIMER1 | SPI | USART | ADC
  PRR=(1<<7)|(0<<6)|(1<<5)|(1<<4)|(1<<3)|(1<<2)|(1<<1)|(1<<0);
  sei();
}

ISR(TIMER2_OVF_vect)
{
  cont++;
  if (cont<122)
  {
    cont=0;
    digitalWrite(13,!digitalRead(13));  
  }
    
}

void loop()
{

   
}

NO SLEEP

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


volatile int cont;

void setup()
{
  pinMode(13,OUTPUT);
  
  TCCR2A=0;  
  TCCR2B=(0<<7)|(0<<6)|(0<<5)|(0<<4)|(0<<3)|(1<<2)|(1<<1)|(1<<0);
  // Bit 0.- Overflow
  TIMSK2=(0<<2)|(0<<1)|(1<<0);
  //Turn off ADC
  //ADCSRA=0;
  // TWI | TIMER2 | TIMER0 | VOID | TIMER1 | SPI | USART | ADC
  //PRR=(1<<7)|(0<<6)|(1<<5)|(1<<4)|(1<<3)|(1<<2)|(1<<1)|(1<<0);
  sei();
}

ISR(TIMER2_OVF_vect)
{
  cont++;
  if (cont==122)
  {
    cont=0;
    digitalWrite(13,!digitalRead(13));  
  }
    
}

void loop()
{

   
}

Por si alguien le pueda venir bien...

Salu2, :slight_smile:

Igor R.

Hola,

Siguiendo con intentar bajar el consumo, he realizado otra prueba.
He quitado el led de power, y he probado otro regulador LP2951CN (Tiene un pin de shut down, asi puedo apagar mi aplicacion si hay un tiempo de inactividad).

Test de Power Sleep Mode:

  • Arduino con su propio regulador => 6'52 mA
  • Arduino con LP2951CN => 1'54 mA

El regulador que hay con Diecimila consume 5mA en comparación con este nuevo.

Espero que alguien le pueda servir esta info.

Salu2 :wink:

Igor R.

Se me olvido decir que hay que dejar libre el jumper que eliges donde viene la alimentación (USB ó EXT). Ya que si lo dejas en EXT y lo alimentas con otro regulador mediante el pin de 5 voltios y GND, el viejo consume corriente....
De esta forma, ya no le llega nada al regulador y no tienes que desoldar nada del Diecimila.

:wink:

Cool!

Muy buenas. Retomando este magnifico post, he estado haciendo unas pruebas y no consigo lo que quiero. Mi intención es gastar la menor energia posible apagando todos los módulos que no utilizo y cuando la interrupcion del timer1 (uso el que me brinda mayor retardo posible dormido, 16 bits) despierte al micro, haga una serie de cosas, en este caso encender y apagar un led. Lo gracioso del asunto es que éste led se queda encendido, osea, como si se parase justamente en el delay que hay antes de apagarlo. Me gustaria alargar el sleep todo lo que se pueda hasta provocar la interrupcion pero no se si lo hago bien o si se puede alargar mas.

Cuelgo el codigo por si alguien es tan amable de echarle un vistazo:

#include <TimerOne.h>
#include <avr/sleep.h>

int led = 9;
int interled = 13;
int state = LOW;
int contador = 0;

void setup() {
pinMode(led, OUTPUT);
pinMode(interled, OUTPUT);

Timer1.initialize(); // Initializes Timer1 (Period)
Timer1.attachInterrupt(flash, 2388480);
}

void loop() {
digitalWrite(led, HIGH);
delay (100);
digitalWrite(led, LOW);
delay (100);

set_sleep_mode(SLEEP_MODE_IDLE); // sleep mode is set here

//Turn off ADC
ADCSRA=0;
// TWI | TIMER2 | TIMER0 | VOID | TIMER1 | SPI | USART | ADC
PRR=(1<<7)| (1<<6) | (1<<5) |(1<<4)| (0<<3) |(1<<2)|(1<<1)|(1<<0);
sei();

sleep_enable(); // enables the sleep bit in the mcucr register
// so sleep is possible. just a safety pin
sleep_mode(); // here the device is actually put to sleep!!
// THE PROGRAM CONTINUES FROM HERE AFTER WAKING UP
sleep_disable();

}
void flash()
{
state = !state;
digitalWrite(interled, state);
}

Mil gracias y saludos.

Hola,

No me he mirado en profundidad tu código porque desconozco la libreria TimerOne.
Yo en estos casos prefiero actuar directamente sobre los registros del micro.
Tambien desconozco si el Timer 1 sirve para despertar el micro en Idle.
Aunque si todo funcionase bien, lo que haces en la función del Timer 1(flash) es cambiar de estado el pin 13, pero resulta que en el void loop, lo dejas apagado. Ten en cuenta que cuando se despierte hará la función flash y luego continuará en el punto anterior donde se durmió... (despues de sleep_mode(), por lo que volverá a encender y apagar el led).Ya que es un bucle que volverá a enceder-retardo-apagar y otra vez en sleep.

En el ejemplo que puse, si te fijas, aunque usa el Timer2 que es de 8 bits, cuando se despierta lo que hace es incrementar una variable (cont) y si no es mayor de 122, lo vuelve a dormir.... más o menos era para dejarlo 2 segundos dormido (es decir, 122 veces Overflow del Timer 2). Puedes aumentar hasta lo que quieras.

Si no me equivoco Save Mode consume menos que Idle. Yo apagaría antes de entrar en Sleep, los módulos que no quieras usar.

Lo que haría es sacarme los registros principales que se usan para los Sleep Modes por el puerto serie para poder monitorizar los valores y estar seguro que todo esta bien.

Salu2

Igor R.

Hola. Ante todo gracias por tu respuesta. El led no lo apago en bucle loop puesto que utilizo dos leds, en el bucle loop enciendo y apago uno, para saber que se ha despertado y en la rutina flash cambio de estado el otro cada vez que desborda el timer. El timer2 si lo he utilizado, pero su temporización es muy pequeña y quiero dormir al micro unos minutos sin que se despierte muchas veces por minuto porque al final me va gastando la bateria de una forma un poco inutil. Si que habia pensado hacer lo del contador que me comentas pero con un sleep largo para poder llegar a varios minutos.
La libreria TimerOne se utiliza más que nada para las salidas PWM, pero yo queria utilizar el timer de 16 bits con interrupciones y soy novato en arduino (estoy acostumbrado a los PIC).
Si que en save mode consume menos, pero me apaga el timer1, y no se si se puede utilizar el save mode y activar tambien el timer1.
Intentare sacar los valores por puerto serie como me dices para visualizarlos mejor.

Gracias de nuevo y un saludo.

Perdón,no me habia fijado bien que tenias dos leds...

Te digo lo de los registros, porque cuando compilas, te introduce código de Arduino(wiring) que por ejemplo modifica el Timer 1 (ya que se usan en las funciones de pwm). Aunque se supone que esto lo hace antes que setup().

Aunque uses el Timer 1 de 16 bits, si no me equivoco, tendras un overflow cada 4,2 segudos aproximadamente teniendo el prescaler máximo (1024) y el reloj de 16 Mhz. Por lo que minutos tampoco puedes conseguir... Tendrás que usar un contador para ampliar tiempo. En la libreria dicen que son 8,3 segundos, pero a mi no me salen las cuentas, a no ser que el clock sea de 8 Mhz.

En Power Save sólo el Timer 2 puede despertar el micro.

Yo cambiaría el código para manipular directamente los registros en vez de usar la libreria Timer1 y manipules directamente la interrupción del Timer 1. Lo digo para que tengas control total... http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=50106

Es tan sólo modificar lo que hice yo con los registros del Timer 1, ISR(TIMER1_OVF_vect)
y cambiar el modo de sleep a Idle.

Ya nos contarás tus avances.... :wink:

Saludos

Igor R.

Acabo de ver en la documentacion de Atmel que en Phase and Frequency Correct sube hasta Top y Baja, por lo que es el doble....TOV1 Flag se activa en Bottom.
En la libreria Timer 1:
WGM13 esta a 1.
WGM12-10 esta a 0.
Por lo que Top lo marca ICR1, el update de OCR1x es abajo y TOV1 Flag es en abajo.
Tabla 15-4 de la página 133 de la documentacion de ATmega168.

Por esto lo de los 8,4 segundos....
Por lo que lo máximo que pudes obtener son 8.38 segundos

Salu2

Igor R.

Gracias por tus consejos. Voy a intentar mezclar la libreria con lo que me has enseñado, a ver si consigo llegar a algo más concreto.

Un saludo.

Hola de nuevo. Pues sigo trabado, ahora he cambiado el parpadear un led en el loop por mandar un mensaje serie, pero solo me funciona una vez, ya lo mande al principio o al final del loop, es como si solamente hiciese el loop una vez y luego se quedase trabado...

#include <TimerOne.h>
#include <avr/sleep.h>

int led = 9;
int interled = 13;
int state = LOW;
int contador = 0;

void setup() {
Serial.begin(9600); // open serial
pinMode(led, OUTPUT);
pinMode(interled, OUTPUT);

Timer1.initialize(); // Initializes Timer1 (Period)
Timer1.attachInterrupt(flash, 2388480);
}

void loop() {

Serial.print("BUCLE ");
set_sleep_mode(SLEEP_MODE_IDLE); // sleep mode is set here

//Turn off ADC
ADCSRA=0;
// TWI | TIMER2 | TIMER0 | VOID | TIMER1 | SPI | USART | ADC
PRR=(1<<7)| (1<<6) | (1<<5) |(1<<4)| (0<<3) |(1<<2)|(1<<1)|(1<<0);

sei(); // habilita interrupciones

//Go to Sleep
sleep_mode();

//Disable Sleep
sleep_disable();
//Disable interrupts
cli(); // deshabilita interrupciones

}
void flash()
{
state = !state;
digitalWrite(interled, state);
}

Hola otra vez. He simplificado un poco el codigo para que se vea mas claro. Ahora lo que ocurre es que no se duerme con el modo idle, me manda continuamente por el puerto serie cada 500ms en vez de despertarse con la interrupcion y sacarlo cada 2 segundos. Si le cambio el modo sleep a uno mas potente el programa se detiene despues de enviar un solo mensaje serie, osea, que se duerme pero no sale del reposo por las interrupciones del timer1. He probado a activar todos los modulos en el registro PRR pero sigue igual. Creo que solamente con conseguir despertarlo del sleep podria ser suficiente... He leido algo sobre poner el timer2 en modo asincrono, asi que me pondré con ello.

Saludos!!

#include <TimerOne.h>
#include <avr/sleep.h>

int interled = 13;
int state = LOW;

void setup() {
Serial.begin(9600); // open serial
pinMode(interled, OUTPUT);

Timer1.initialize(); // Initializes Timer1 (Period)
Timer1.attachInterrupt(flash, 2000000); // 8388480 maximo

set_sleep_mode(SLEEP_MODE_PWR_SAVE); // sleep mode is set here

}

void loop() {

Serial.print("BUCLE");

//Go to Sleep
sleep_mode();

//Disable Sleep
sleep_disable();

delay(500);
}
void flash()
{
state = !state;
digitalWrite(interled, state);
}

Cuando me referia a monitorizar por el puerto serie los registros, me refiero a que mirando la documentación de atmel, te fijes en los registros que se deben configurar y monitorices su estado.
Como por ejemplo los registros son: TCCR1A, TCCR1B, TIMSK1,SMCR, ...

¿Has probado el programa sin el sleep? ¿Es decir, el programa funciona saltando a la interrupción?

:wink:

Muy buenas. Si, el programa funciona bien sin el sleep o poniendolo en modo IDLE, pero es como si el modo idle no le dejase dormido, ya que hace la rutina del loop constantemente, no espera a despertarse por la interrupcion del timer1, y si pongo otro modo de sleep se duerme pero no es capaz de despertar con la interrupcion. Voy a visualizar los registros con sleep y sin sleep a ver si viendo las diferencias llegamos a alguna conclusion.

Gracias por tu ayuda!!! :slight_smile:

Buenos dias. He intentado realizar los protocolos que vienen en el datasheet para establecer el timer2 como asincrono y asi poder usar el PWR_SAVE, pero tampoco funciona.... Lo de enviar los datos por puerto serie no es complicado, y puedo ver que efectivamente los valores son los que he metido. Lo que si es interesante es ver que si pongo un delay antes de sacar los valores por el puerto serie se queda bloqueado, porque no me saca nada por el puerto serie, aunque el delay se de 1ms...

#include <TimerOne.h>
#include <avr/sleep.h>

int interled = 13;
int state = LOW;

void setup() {
Serial.begin(9600); // open serial
pinMode(interled, OUTPUT);

Timer1.initialize(); // Initializes Timer1 (Period)
Timer1.attachInterrupt(flash, 2000000); // 8388480 maximo

TCCR1A=0;
TCCR1B=5;
TCCR1C=0;
TCNT1H=0;
TCNT1L=0;
TIMSK1=1;
TIFR1=0;

TCCR2A=0;
TCCR2B=7;
TIFR2=0;
ASSR=0;
GTCCR=0;
TIMSK2=0; // borramos los bits OCIE2x y TOIE2
ASSR=(0<<5); // bit AS2 a 0
TCNT2=0; // escribimos algo en estos registros
OCR2A=0;
OCR2B=0;
delay(2); // testeamos que el bit busy del ASSR esté a 0 (lo hacemos con un delay y listo)
TIMSK2=0; // borramos los bits OCIE2x y TOIE2

ADCSRA=0;
// TWI | TIMER2 | TIMER0 | VOID | TIMER1 | SPI | USART | ADC
PRR=(1<<7)| (0<<6) | (1<<5) | (0<<3) |(1<<2)|(0<<1)|(1<<0);

set_sleep_mode(SLEEP_MODE_PWR_SAVE); // sleep mode is set here

}

void loop() {

Serial.println();
Serial.print("TCCR1A ");
Serial.println(TCCR1A, HEX);
Serial.print("TCCR1B ");
Serial.println(TCCR1B, HEX);
Serial.print("TCCR1C ");
Serial.println(TCCR1C, HEX);
Serial.print("TIMSK1 ");
Serial.println(TIMSK1, HEX);
Serial.print("TIFR1 ");
Serial.println(TIFR1, HEX);
Serial.print("TCCR2A ");
Serial.println(TCCR2A, HEX);
Serial.print("TCCR2B ");
Serial.println(TCCR2B, HEX);
Serial.print("TIMSK2 ");
Serial.println(TIMSK2, HEX);
Serial.print("TIFR2 ");
Serial.println(TIFR2, HEX);
Serial.print("ASSR ");
Serial.println(ASSR, HEX);
Serial.print("GTCCR ");
Serial.println(GTCCR, HEX);
Serial.print("SMCR ");
Serial.print(SMCR, HEX);

delay(500);
//Turn off ADC

sei(); // habilita interrupciones

TCCR2A=0; // escribimos algo segun el protocolo
delay(2); // testeamos que el bit busy del ASSR esté a 0 (lo hacemos con un delay y listo)
sleep_mode(); // entramos en sleep

//Disable Sleep
sleep_disable();
//Disable interrupts
cli(); // deshabilita interrupciones

delay(500);
}
void flash()
{
state = !state;
digitalWrite(interled, state);
}

Creo que voy a intentar usar el timer2 pero con un cristal de 32khz externo, así podría usar la rutina que tu usas y obtendria una interrupcion cada 8 segundos aproximadamente... o quizas con un 555 consiga un pulso mayor con un consumo bajo.

Saludos.

  • ¿Por qué no te gusta la rutina que yo uso aunque se despierte varias veces y vuelve a dormir?
  • ¿Es muy crítico el consumo?
  • Para bajar el consumo del micro, si utilizas velocidades menores, también lo hará el consumo (en vez los 16 MHz)

Yo con esa rutina, quitando los leds de la placa del diecimila y usando un LDO (LP2951CN) => 1'54 mA.

Hola. Si, todas esas ideas tuyas de conservar energia me gustan mucho y me vienen bien. El tema es que quiero manejar un sensor digital de temperatura y humedad y enviar los datos mediante modulos ZigBee XBee, ha de funcionar a baterias, por lo que el tema del consumo es un poco crítico y querria llegar a la mejor de las soluciones en este aspecto. Si puedo hacer que se despierte una vez cada 8 segundos mucho mejor que si se despierta 30 veces por segundo y se vuelve a dormir, imagino que a la hora del consumo se notará un poco. Habia pensado en utilizar 4Mhz, pero aun asi se mejora el tiempo 4 veces, lo que tampoco es mucho. Quiero hacer una placa definitiva sin leds y con un regulador decente, pero de momento para practicar tengo el duemilanove. Tambien me ha surgido una duda mirando la documentación del reloj externo, en el datasheet habla de colocar un cristal de 32khz entre las patas 9 y 10, pero entonces... ¿donde va el de 16Mhz? ¿Es solamente para el timer? ¿la velocidad del chip es independiente? Logicamente si el chip va a ir a 32khz no voy a poder manejar de la misma forma el resto de componentes como el sensor digital y el modulo zigbee...

Saludos.

Se suele utilizar un cristal de aprox 32 Khz (es una cifra con decimales) que no recuerdo para tener un RTC (Real Time Clock), ya que con esa frecuencia te sale fácilmente temporizar 1 segundo. Y usas el Timer como reloj. Es independiente del cristal de tu micro.

De todas formas, yo pasaría de la libreria Timer 1 y gestionaría la interrupción directamente, con ISR. Lo único que desconozco es si la interrupción del Timer 1 puede despertar el micro, sólo se la de Power Save, que tiene que ser el Timer 2.

¿Has mirado la página de libellum?
http://www.libelium.com/squidbee/index.php?index.php

Tengo una placa funnel io, que es como una lilypad a 8Mhz pero esta preparada para conectar directamente el XBee, por lo que creo que funciona con el oscilador interno y puedo conectarle el cristal de 32khz tranquilamente en las patas 9 y 10, quizas tenga que probar a programar este en vez del duemilanove...