Interrupciones con TIMER 2 problema con retardos[SOLUCIONADO]

Buenas!! estoy haciendo pruebas con un programa del compañero IGOR R para bajar el consumo del arduino y la verdad es que es una pasado como usando interrupciones podemos "dormir" al Atmega, he conseguido quitando el micro de la placa y montandolo con el hardware basico bajar el consumo de 15mA a 0,31mA lo que alarga la vida de las baterias para mi aplicación. En el programa original del compañero tiene esta linea: (marco en rojo la diferencia)

PRR=(1<<7)|(0<<6)|(1<<5)|(1<<4)|(1<<3)|(1<<2)|(1<<1)|(1<<0);

yo la he cambiado por

PRR=(1<<7)|(0<<6)|(0<<5)|(1<<4)|(1<<3)|(1<<2)|(1<<1)|(1<<0);

Asi habilito en Timer 0 que es el encargado de las temporizaciones DELAY, si no se modifica el programa se queda esperando en esa linea a que pase el tiempo, pero como esta deshabilitado nunca llega y el programa se queda ahi colgado.

Bueno a lo que iba, cargo el programa del compañero IGOR, que es este:

#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); // Preescaler a clock/1024
TIMSK2=(0<<2)|(0<<1)|(1<<0); // Bit 0.- Overflow
SMCR=(0<<3)|(1<<2)|(1<<1)|(0<<0); //Power Save Mode

}

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)|(0<<5)|(1<<4)|(1<<3)|(1<<2)|(1<<1)|(1<<0); // un "1" apaga el modulo un "0" deja en marcha el modulo
sei();
while (cont<122)
{
//Go to Sleep
sleep_mode();
}
//Disable Sleep
sleep_disable();
//Disable interrupts
cli();
cont=0;
digitalWrite(13,!digitalRead(13));
}

Y añado esta linea => delay(3000); debajo del digitalWrite(13,!digitalRead(13)); el programa hace exactamente lo mismo, sin hacer caso del delay. Googleando he visto que en caso de estar en una interrupcion el delay no funciona y se ha de usar delayMicroseconds, pero lo he probado y tampoco funciona.

He seguido Googleando y al parecer despues e la version 0.18 el daleyMicroseconds dejo de funcionar.

A todo esto y despues de pasarme muchas horas haciendo pruebas, leyendo, googleando, etc.. he caido en que el Delay debería funcionar pues está despues de la inhabilitacion de las interrupciones con sleep_disable(); y cli(); por lo que en teoria debería funcionar normalmente porque no está en una interrupcion.

Este es el codigo de la prueba que estoy haciendo, que es el mismo pero añadiendo el delay(3000); al final

#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); // Preescaler a clock/1024
TIMSK2=(0<<2)|(0<<1)|(1<<0); // Bit 0.- Overflow
SMCR=(0<<3)|(1<<2)|(1<<1)|(0<<0); //Power Save Mode

}

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)|(0<<5)|(1<<4)|(1<<3)|(1<<2)|(1<<1)|(1<<0); // un "1" apaga el modulo un "0" deja en marcha el modulo
sei();
while (cont<122)
{
//Go to Sleep
sleep_mode();
}
//Disable Sleep
sleep_disable();
//Disable interrupts
cli();
cont=0;
digitalWrite(13,!digitalRead(13));
delay(3000);
}

Lo he cargado con arduino 023, 1.0 y 1.0.1 y en ninguno funciona =(

Lo he leido rápido y me tendría que mirar otra vez el datasheet ya que hace mucho tiempo de ésto, pero en el primer código, verás que un delay no te va a funcionar debajo del digitalWrite porque las interrupciones estan deshabilitadas con la función cli() (global interrupt mask). Se necesitan tener las interrupciones habilitadas para que delay (que usa el timer 0) funcionen.

Aunque yo lo que hice para mi dispositivo a batería, es sustituir todos los delays, con rutinas que ponían a dormir el micro. Ya que dónde tengas un delay, no estás haciendo nada... por definición! Así que ahí es donde puedes ahorrar mucho. Si durante un delay(3000) lo pones a dormir, seguro que vas a salvar mucho consumo.

Es decir, con lo que juegas para tener el tiempo que quieras, es con la parte:
while (cont<122)
{
//Go to Sleep
sleep_mode();
}

Cuando el timer 2 realiza un overflow, despertará el micro aumentantando en uno la variable contador cont e irá otra vez a dormir.
Sabiendo el cristal de tu micro (ejemplo 16 Mhz), sabes cada cuanto tardará un overflow (tiempo en contar de 0 a 255 ya que es 8bits de resolución).Hay que tener en cuenta el prescaler.
Por lo que el while lo que hace es 122 veces overflow del timer 2. Así es como calculas tu tiempo total.

Pongamos un ejemplo que configuras tu timer para que tengas un overflow cada 1ms, pues si quieres conseguir 200ms, tendrás que estar haciendo ese loop del while durante 200 veces.

Si utilizas un cristal menor, también salvarás bastante consumo.

Espero que aclare un poco más.

Saludos,

Igor R.

Hola IGOR, gracias por tu respuesta y veo que has editado para describirla con mas detenimiento, lo cual te agradezco, ahora mismo estoy trabajando con la placa Arduino pero el montaje final sera con el Atmega y un cristal de 4Mhz ya que de 1,31mA (a 16Mhz) pasa a consumir 0,31mA (a 4MHz) y el gasto energetico es muy importante para la aplicacion que quiero hacer.
Tienes razón en que cambiando los delays por rutinas de sleep ahorrare bateria y hará la misma funcion, lo que pasa es que estoy haciendo pruebas y la manera mas "rápida" para pruebas era hacer un delay, pero no cuesta tanto hacer un rutina como me has indicado y hacer pruebas mas reales.

La necesidad que tengo es que el micro ha de dormir 1 minuto, y al despertar ha de activar un led, enviar por el puerto serie un texto y apagar el led, para ello he modificado el programa y funciona todo excepto que por el puerto serie envia unos caracteres que no son lo que yo quiero, esta claro que la USART funciona por interrupciones y el problema vendrá por ahi, pero despues de estar desde esta mañana guerreando con el programa haciendole 1000 modificaciones ademas de buscar y buscar por google sigo sin que que funcione correctamente.

El programa imprime el texto, pero luego el led TX va parpadeando y va imprimiendo cuadritos (mas abajo he dejado la imagen) hasta que imprime de nuevo el texto

He habilitado todos los TIMERS para probar si era que al hacer serial.print usaba alguno de ellos, he mejorado algo pero siguen llegando caracteres raros.

Este es el codigo:

#include <avr/interrupt.h>
#include <avr/sleep.h>
volatile int cont;
void setup()
{
** Serial.begin(9600);**
** pinMode (13,OUTPUT);**
** cli();**
** TCCR2A=0; **
** TCCR2B=(0<<7)|(0<<6)|(0<<5)|(0<<4)|(0<<3)|(1<<2)|(1<<1)|(1<<0); // Preescaler a clock/1024**
** TIMSK2=(0<<2)|(0<<1)|(1<<0); // Bit 0.- Overflow**
** SMCR=(0<<3)|(1<<2)|(1<<1)|(0<<0); //Power Save Mode**
** //Turn off ADC**
** ADCSRA=0;**
** // TWI | TIMER2 | TIMER0 | VOID | TIMER1 | SPI | USART | ADC**
** PRR=(0<<7)|(0<<6)|(0<<5)|(1<<4)|(0<<3)|(1<<2)|(0<<1)|(1<<0); // un "1" apaga el modulo un "0" deja en marcha el modulo**
}
ISR(TIMER2_OVF_vect)
{
cont++;
}
void loop(){
** retardo(5); //rutina tipo delay, entre parentesis indicar el numero de segundos**
** digitalWrite(13,HIGH);**
** Serial.println("YA HA PASADO EL MINUTO");**
** digitalWrite(13,LOW); **
** }**
//************************************************************************************************************************************************
void retardo(int segundos){
** sleep_enable();**
__ segundos=segundos*61;__
** cont=0;**
** sei();**
** while (cont<segundos)**
** {**
** sleep_mode();**
** }**
** cli();**
** sleep_disable();**
}

Esto lo que recibo por el puerto serie del PC: (Enciende el led, imprime el texto correctamente y apaga el led, pero no se porque mientras espera comienza a imprimir cuadritos =( )

Finalmente ya he encontrado el problema, este era que cuando rellenamos el registro SMCR asi:
SMCR=(0<<3)|(1<<2)|(1<<1)|(0<<0); //Power Save Mode

Estamos poniendo el power save mode en STAND BY, este modo no es compatible con el uso de la USART porque se pierden caracteres, segun el datasheet en caso de tener que usar el puerto serie solo se puede usar el modo IDLE para ello debemos cambiar los registros marcados en rojo:
SMCR=(0<<3)|(0<<2)|(0<<1)|(0<<0); //Power Save Mode

Ahora funciona a la perfección, cuando algo falla a veces es mejor dejarlo, hacer otra cosa para despejarte y volver, ayer estuve todo el dia liado con esto y esta mañana con la mente mas fresca lo he encontrado en 5 minutos y es que estaba obsesionado con que el problema estaba en el registro PRR y no veía mas allá.

El programa final a quedado asi:

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

volatile int cont;
String (contador);

void setup()
{
Serial.begin(9600);
pinMode (13,OUTPUT);
cli();
TCCR2A=0;
TCCR2B=(0<<7)|(0<<6)|(0<<5)|(0<<4)|(0<<3)|(1<<2)|(1<<1)|(1<<0); // Preescaler a clock/1024
TIMSK2=(0<<2)|(0<<1)|(1<<0); // Bit 0.- Overflow
SMCR=(0<<3)|(0<<2)|(0<<1)|(0<<0); //Power Save Mode
//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)|(0<<1)|(1<<0); // un "1" apaga el modulo un "0" deja en marcha el modulo
}

ISR(TIMER2_OVF_vect)
{
cont++;
}

void loop(){
retardo(60); //rutina tipo delay, entre parentesis indicar el numero de segundos
digitalWrite(13,HIGH);
Serial.println("YA HA PASADO EL MINUTO");
digitalWrite(13,LOW);
}

//************************************************************************************************************************************************

void retardo(int segundos){
segundos=segundos*61;
sleep_enable();
sei();
while (cont<segundos)
{
sleep_mode();
}
cli();
sleep_disable();
cont=0;
}

Gracias IGOR por tu ayuda. :slight_smile:

Hola,

Me alegro que te funcione. Se me olvido preguntarte que probaras de monitorizar cuando termina el envio serie antes de poner el micro en sleep.
Es decir, volviendo a power-save mode, y después del Serial.print("loquesea"), pones la siguiente linea:

while (!(UCSR0A & (1 << TXC0)));

Esto se quedará esperando hasta que se haya enviando todo. Es una prueba rápida a ver si así funciona.
No estoy seguro en que versión de Arduino.... juraría que cambiaron las rutinas del puerto serie cuando pasaron a la versión 1.0, y uno de los cambios era las rutinas de envio....
Repasando las notas de arduino:
Por ejemplo en la versión 1.0.3, puede que el Serial.flush() también te sirva ( "Serial.flush() waits for last byte to transmit. (michele.mazzucchi)").
A partir de la versión 1.0:
" Serial transmission is now asynchronous - that is, calls to Serial.print(), etc. add data to an outgoing buffer which is transmitted in the background. Also, the Serial.flush() command has been repurposed to wait for outgoing data to be transmitted, rather than dropping received incoming data."

Lo de monitorear el bit funcionaba antes que la versión 1.0, ahora tendría que repasar cómo son las rutinas...ya que esto me lo estuve mirando para RS485 para saber cuando cambiar el bit de control de transmisión a recepción....
Asi que puedes probar el Serial.flush() si usas las últimas versiones a ver si te funciona (antes de dormir el micro). Vaya, lo que quiero decir, es que problablemente venga de ahí el problema. Aunque hay que mirarse un poco en profundidad cómo son las rutinas serie (hace mucho tiempo que no me las miro).

Digo de probar porque consumirá menos en power-save... y por investigar un poco más! :wink:

Saludos,

Igor R.

Lo de cambiar el modo del power mode antes de ir a dormir y al despertar volver a cambiarlo ya lo tenía hecho, aunque para que no perdiera caracteres había realizado un bucle con un contador hasta 7, esto es porque cuando el micro se despierta de STANDBY todo arranca tras 6 ciclos de reloj segun datasheet, de ahi que el contador espere 7 (1 mas por si las moscas), aunque es un metodo un poco "chapucilla" y no me gustaba, iba a leerme otra vez el datasheet para ver si habia algo que pudiera servirme pero he visto tu comentario antes de leermelo, con lo que me comentas del while (!(UCSR0A & (1 << TXC0))); me gusta mas, lo voy a probar y comento resultados.

EDITO: Si pongo el POWER MODE en STANDBY y como me comentas esperamos a que se envie toda la trama con while (!(UCSR0A & (1 << TXC0))); funciona a la perfeccion. He sacado el micro de la placa y lo he montado en una protoboard con un cristal de 4MHz que es el mas pequeño que tengo a mano y el consumo total es de 0,31mA

De nuevo gracias por tu ayuda IGOR

Un saludo
Dystrom