Timer interrupts

Página original:  

  https://arduinodiy.wordpress.com/2012/02/28/timer-interrupts/

Traducido por seta43 :

   http://seta43.hol.es/electro.html

   http://seta43.blogspot.com.es/

 

Este artículo discutirá las interrupciones AVR y Arduino y cómo usarlos en proyectos Arduino o circuitos personalizados AVR.


¿Qué es una interrupción?

Al igual que en la vida real, en los microcontroladores, una interrupción es algo que configura para activar una alerta en un cierto punto en el futuro. Cuando llega ese punto, esa alerta interrumpe el microprocesador, recordándole que haga algo, como ejecutar una pieza específica de código.

Las interrupciones, al igual que las interrupciones externas, se ejecutan independientemente de su programa principal. En lugar de ejecutar un ciclo o llamar repetidamente a millis (), puede dejar que una interrupción funcione para usted mientras su código hace otras cosas.

Supongamos que tiene un dispositivo que necesita hacer algo, como parpadear un LED cada 5 segundos. Si no está utilizando interrupciones sino técnicas de códigos convencionales, debería establecer una variable la próxima vez que parpadee el LED, luego verifique constantemente para ver si ese momento había llegado. Con una interrupción de temporizador, puede configurar la interrupción y luego encender el temporizador. El LED parpadeará perfectamente a tiempo, independientemente de lo que esté haciendo su programa principal

 

¿Cómo funcionan las interrupciones?

Las interrupciones funcionan incrementando una variable contraria conocida como registro contador. El registro del contador puede contar hasta cierto valor, dependiendo de su tamaño (generalmente 8 bits o 16 bits). El temporizador incrementa este contador paso a paso hasta que alcanza su valor máximo, en cuyo punto el contador se desborda y vuelve a cero. El temporizador normalmente establece un bit de indicador para avisarte que se ha producido un desbordamiento. Este indicador se puede verificar manualmente, o puede hacer que el temporizador active una interrupción tan pronto como se establezca el indicador. Y como con cualquier otra interrupción, puede especificar una Rutina de Servicio de Interrupción (ISR) para ejecutar su propio código cuando el temporizador se desborde. El ISR restablecerá automáticamente el indicador de desbordamiento, por lo que el uso de interrupciones suele ser la mejor opción para simplificar y acelerar.

Para incrementar el valor del contador a intervalos regulares, el temporizador debe tener una fuente de reloj. El reloj proporciona una señal consistente. Cada vez que el temporizador detecta esta señal, aumenta su contador en uno.

Como las interrupciones dependen de la fuente de reloj, la unidad de tiempo medible más pequeña será el período del reloj. Si proporciona una señal de reloj de 16 MHz a una interrupción, la resolución del temporizador (o el período del temporizador) es:

 

T = 1 / f  (f is the clock frequency)

T = 1 /(16* 10^6)

T = (0.0625 * 10^-6) s

La resolución del temporizador por lo tanto es 0.0625 millonésima de segundo.

Para 8 MHz esto sería 0.125 millonésima de segundo

y para 1 MHz exactamente una millonésima de segundo

Puede suministrar una fuente de reloj externa para usar con interrupciones, pero generalmente el reloj interno del chip se usa como fuente de reloj. El cristal de 16 MHz que generalmente forma parte de la configuración de un Atmega328 se puede considerar como parte del reloj interno.


Diferentes interrupciones

En las variantes estándar de Arduino o en los chips AVR de 8 bits, hay varias interrupciones a su disposición.

ATmega8, ATmega168 y ATmega328 tienen tres interrupciones: Timer0, Timer1 y Timer2. También tienen una interrupción de vigilancia, que se puede utilizar como salvaguarda o un mecanismo de restablecimiento de software. La serie Mega tiene 3 interrupciones adicionales.

 

Timer0

Timer0 es una interrupción de 8 bits, lo que significa que su registro de contador puede registrar un valor máximo de 255 (lo mismo que un byte de 8 bits sin signo). Timer0 es utilizado por las funciones de temporización Arduino nativas, como delay () y millis (), por lo que, a menos que sepa lo que está haciendo, el temporizador 0 es mejor dejarlo solo.

Timer1

Timer1 es una interrupción de 16 bits, con un valor de contador máximo de 65535 (un entero de 16 bits sin signo). La biblioteca Arduino Servo usa este temporizador, así que tenlo en cuenta si usas este temporizador en tus proyectos.

Timer2

Timer2 es una interrupción de 8 bits que es muy similar a Timer0. Es utilizado por la función Arduino tone ().

 

Timer3, Timer4, Timer5

El AVR ATmega1280 y ATmega2560 (que se encuentran en las variantes Arduino Mega) tienen tres interrupciones adicionales. Estos son todos las interrupciones de 16 bits y funcionan de manera similar a Timer1.


Configurar el registro del temporizador

Para utilizar estos interrupciones, el temporizador incorporado registra el chip AVR que necesita configurar los ajustes del temporizador de la tienda. Hay una serie de registros por temporizador. Dos de estos registros -los Registros de control del temporizador / contador- mantienen los valores de configuración, y se llaman TCCRxA y TCCRxB, donde x es el número del temporizador (TCCR1A y TCCR1B, etc.). Cada registro contiene 8 bits, y cada bit almacena un valor de configuración. La hoja de datos de ATmega328 especifica los siguientes de la siguiente manera:

 

 

 

TCCR1A

 

 

 

 

 

 

 

 

 

 

 

Bit

7

6

5

4

3

2

1

0

TCCR1A

 

0x80

COM1A1

COM1A0

COM1B1

COM1B0

WGM11

WGM10

 

 

ReadWrite

RW

RW

RW

RW

R

R

RW

RW

 

 

Initial Value

0

0

0

0

0

0

0

0

 

 

 

 

 

 

 

 

 

 

 

 

TCCR1B

 

 

 

 

 

 

 

 

 

 

 

Bit

7

6

5

4

3

2

1

0

TCCR1B

 

0x81

ICNC1

ICES1

WGM13

WGM12

CS12

CS11

CS10

 

 

ReadWrite

R/W

R/W

R

R/W

R/W

R/W

R/W

R/w

 

 

Initial Value

0

0

0

0

0

0

0

0

 

 

Los ajustes más importantes son los últimos tres bits en TCCR1B, CS12, CS11 y CS10. Estos determinan la configuración del reloj del temporizador. Al configurar estos bits en varias combinaciones, puede hacer que el temporizador se ejecute a diferentes velocidades. Esta tabla muestra la configuración requerida:

 

Clock Select bit description

CS12

CS11

CS10

Description

0

0

0

No clock source (Timer/Counter stopped)

0

0

1

clki/o/1 (No prescaling)

0

1

0

clki/o/8 (From Prescaler)

0

1

1

clki/o/64 (From Prescaler)

1

0

0

clki/o/256 (From Prescaler)

1

0

1

clki/o/1024 (From Prescaler)

1

1

0

External clock source on T1 pin. Clock on falling edge

1

1

1

External clock source on T1 pin. Clock on rising edge

 

Por defecto, estos bits se establecen en cero. Supongamos que quiere que Timer1 funcione a velocidad de reloj, con un conteo por ciclo de reloj. Cuando se desborda, desea ejecutar una rutina de servicio de interrupción (ISR) que active o desactive un LED vinculado al pin 13. A continuación encontrará el código de Arduino para este ejemplo, para completarlo utilizo las rutinas de avr-libc donde no hacen las cosas demasiado complicadas.

Primero, inicializa el cronómetro:

 

// avr-libc library includes

#include <avr/io.h>

#include <avr/interrupt.h>

#define LEDPIN 13

void setup()

{

pinMode(LEDPIN, OUTPUT);

// initialize Timer1

cli();         // disable global interrupts

TCCR1A = 0;    // set entire TCCR1A register to 0

TCCR1B = 0;    // set entire TCCR1B register to 0

               // (as we do not know the initial  values)

 

// enable Timer1 overflow interrupt:

TIMSK1 | = (1 << TOIE1); //Atmega8 has no TIMSK1 but a TIMSK register

 

// Set CS10 bit so timer runs at clock speed: (no prescaling)

TCCR1B |= (1 << CS10); // Sets bit CS10 in TCCR1B

// This is achieved by shifting binary 1 (0b00000001)

// to the left by CS10 bits. This is then bitwise

// OR-ed into the current value of TCCR1B, which effectively set

// this one bit high. Similar: TCCR1B |= _BV(CS10);

 

// enable global interrupts:

sei();

}

El registro TIMSK1 es el registro de la máscara de interrupción del temporizador / contador1. Controla qué interrupciones puede disparar el temporizador. Al configurar el bit TOIE1 (= Habilitar interrupción de desbordamiento del temporizador 1), el temporizador activa una interrupción cuando el temporizador se desborda. También se puede configurar en otros bits para activar otras interrupciones. Más sobre eso más tarde.

Cuando configura el bit CS10, el temporizador se está ejecutando, y dado que una interrupción de desbordamiento está habilitada, llamará al ISR (TIMER1_OVF_vect) cada vez que el temporizador se desborde.

 

ISR(TIMER1_OVF_vect)

{

 

digitalWrite(LEDPIN, !digitalRead(LEDPIN));

// or use: PORTB ^= _BV(PB5);// PB5 =pin 19 is digitalpin 13

}

Ahora puede definir loop () y el LED se encenderá y apagará independientemente de lo que esté sucediendo en el programa principal. Para apagar el temporizador, configure TCCR1B = 0 en cualquier momento.

¿Qué tan rápido parpadeará el LED con este código?

Timer1 está configurado para interrumpir en un desbordamiento, por lo que si está utilizando un ATmega328 con un reloj de 16MHz. Como Timer1 tiene 16 bits, puede contener un valor máximo de (2 ^ 16 - 1) o 65535. A 16MHz, pasaremos por un ciclo de reloj cada 1 / (16 * 10 ^ 6) segundos, o 6.25 * 10-8 s. Eso significa que 65535 conteos del temporizador pasarán (65535 * 6.25 * 10-8s) y el ISR se disparará en aproximadamente 0.0041 segundos. Luego una y otra vez, cada cuatro milésimas de segundo después de eso. Eso es demasiado rápido para verlo parpadear. En todo caso, hemos creado una señal PWM extremadamente rápida para el LED que se ejecuta con un ciclo de trabajo del 50%, por lo que puede parecer estar constantemente encendido pero más oscuro de lo normal. Un experimento como este muestra la increíble potencia de los microprocesadores, incluso un chip de 8 bits económico puede procesar la información mucho más rápido de lo que podemos detectar.


Temporización y precarga del temporizador

Para controlar esto, también puede configurar el temporizador para usar un preescalador, que le permite dividir la señal de su reloj por varias potencias de dos, lo que aumenta su período de temporizador. Por ejemplo, si desea que el LED parpadee a intervalos de un segundo. En el registro TCCR1B, hay tres bits CS para establecer una mejor resolución de temporizador. Si configura CS10 y CS12 usando:

 

TCCR1B | = (1 << CS10); y TCCR1B | = (1 << CS12) ;, la fuente de reloj se divide por 1024. Esto proporciona una resolución de temporizador de 1 / (16 * 10⁶ / 1024) o 0.000064 segundos (15625 Hz). Ahora el temporizador se desbordará cada (65535 * 6.4 * 10-5s) o 4.194s.

Si configurara solo CS12 usando TCCR1B | = (1 << CS12); (o simplemente TCCR1B = 4), la fuente de reloj se divide por 256. Esto proporciona una resolución de temporizador de 1 / (16 * 10⁶ / 256) o 0.000016 seg (62500 Hz) y el temporizador se desbordará cada (65535 * 0.000016 = ) 1.04856 seg.

Supongamos que no desea un intervalo de 1.04856 segundos, sino un intervalo de 1 segundo. Está claro ver que si el contador no era 65535 sino 62500 (que es igual a la frecuencia), el temporizador se configuraría a 1 segundo. El contador por lo tanto es 65535-62500 = 3035 demasiado alto. Para tener una interrupción de 1 segundo más preciso, necesitamos cambiar solo una cosa: el valor de inicio del temporizador guardado por el registro TCNT1 (Contador de temporizador). Hacemos esto con TCNT1 = 0x0BDC; BDC es el valor hexadecimal de 3035. Un valor de 34286 por ejemplo daría 0.5 seg ((65535-34286) / 62500)

El código se ve de la siguiente manera:

 

// avr-libc library includes

#include <avr/io.h> //  can be omitted

#include <avr/interrupt.h> // can be omitted

#define LEDPIN 13

/* or use

DDRB = DDRB | B00100000;  // this sets pin 5  as output                       // without changing the value of the other

                         // pins

*/

void setup()

{

pinMode(LEDPIN, OUTPUT);

 

// initialize Timer1

cli();         // disable global interrupts

TCCR1A = 0;    // set entire TCCR1A register to 0

TCCR1B = 0;    // set entire TCCR1A register to 0

 

// enable Timer1 overflow interrupt:

TIMSK1 |= (1 << TOIE1);

// Preload with value 3036

//use 64886 for 100Hz

//use 64286 for 50 Hz

//use 34286 for 2 Hz

TCNT1=0x0BDC;

// Set CS10 bit so timer runs at clock speed: (no prescaling)

TCCR1B |= (1 << CS12); // Sets bit CS12 in TCCR1B

// This is achieved by shifting binary 1 (0b00000001)

// to the left by CS12 bits. This is then bitwise

// OR-ed into the current value of TCCR1B, which effectively set

// this one bit high. Similar: TCCR1B |= _BV(CS12);

//  or: TCCR1B= 0x04;

 

// enable global interrupts:

sei();

}

 

ISR(TIMER1_OVF_vect)

{

digitalWrite(LEDPIN, !digitalRead(LEDPIN));

TCNT1=0x0BDC; // reload the timer preload

}

 

void loop() {}

CTC

Pero hay otro modo de operación para las interrupciones AVR. Este modo se llama Clear Timer en Compare Match, o CTC. En lugar de contar hasta que se produce un desbordamiento, el temporizador compara su recuento con un valor que estaba previamente almacenado en un registro. Cuando el recuento coincide con ese valor, el temporizador puede establecer un indicador o desencadenar una interrupción, al igual que el caso de desbordamiento.

Para usar CTC, necesita averiguar cuántos conteos necesita para llegar a un intervalo de un segundo. Suponiendo que conservemos el preescalador 1024 como antes, calcularemos de la siguiente manera:

(target time) = (timer resolution) * (# timer counts + 1)



y reorganizar para obtener

 

(# timer counts + 1) = (target time) / (timer resolution)

(# timer counts + 1) = (1 s) / (6.4e-5 s)

(# timer counts + 1) = 15625

(# timer counts) = 15625 - 1 = 15624

Debe agregar el +1 extra al número de conteos del temporizador porque en el modo CTC, cuando el temporizador coincide con el conteo deseado, se reiniciará a cero. Esto requiere un ciclo de reloj para funcionar, por lo que debe tenerse en cuenta en los cálculos. En muchos casos, una marca de temporizador no es un gran problema, pero si tiene una aplicación de tiempo crítico, puede marcar la diferencia en el mundo.

Ahora la función setup () para configurar el temporizador para estas configuraciones es la siguiente:

 

void setup()

{

 

pinMode(LEDPIN, OUTPUT); // you have to define the LEDPIN as say 13

                         // or so earllier in yr program

// initialize Timer1

cli();          // disable global interrupts

TCCR1A = 0;     // set entire TCCR1A register to 0

TCCR1B = 0;     // same for TCCR1B

 

// set compare match register to desired timer count:

OCR1A = 15624;

 

// turn on CTC mode:

TCCR1B |= (1 << WGM12);

 

// Set CS10 and CS12 bits for 1024 prescaler:

TCCR1B |= (1 << CS10);

TCCR1B |= (1 << CS12);

 

// enable timer compare interrupt:

TIMSK1 |= (1 << OCIE1A);

sei();          // enable global interrupts

}

Y debe reemplazar el ISR de desbordamiento por una versión de comparación comparable:

 

ISR(TIMER1_COMPA_vect)

{

digitalWrite(LEDPIN, !digitalRead(LEDPIN));

}



El LED ahora parpadeará intermitentemente a intervalos de un segundo. Y usted es libre de hacer lo que quiera en loop (). Siempre que no cambie la configuración del temporizador, no interferirá con las interrupciones. Con diferentes configuraciones de modo y preescalador, no hay límite en la forma de usar interrupciones.

Este es el ejemplo completo en caso de que quiera utilizarlo como punto de partida para su propio proyecto.

// Arduino timer CTC interrupt example

//

// avr-libc library includes

#include <avr/io.h>

#include <avr/interrupt.h>

#define LEDPIN 13

void setup()

{

pinMode(LEDPIN, OUTPUT);

// initialize Timer1

cli();          // disable global interrupts

TCCR1A = 0;     // set entire TCCR1A register to 0

TCCR1B = 0;     // same for TCCR1B

 

// set compare match register to desired timer count:

OCR1A = 15624;

 

// turn on CTC mode:

TCCR1B |= (1 << WGM12);

 

// Set CS10 and CS12 bits for 1024 prescaler:

TCCR1B |= (1 << CS10);

TCCR1B |= (1 << CS12);

 

// enable timer compare interrupt:

TIMSK1 |= (1 << OCIE1A);

 

// enable global interrupts:

sei();

}

 

void loop()

{

// main program

}

 

ISR(TIMER1_COMPA_vect)

{

digitalWrite(LEDPIN, !digitalRead(LEDPIN));

}

Recuerde que puede usar los ISR incorporados para extender la funcionalidad del temporizador. Por ejemplo, si quiere leer un sensor cada 10 segundos, no hay una configuración de temporizador que pueda durar tanto sin desbordamiento. Sin embargo, puede usar el ISR para incrementar una variable de contador en su programa una vez por segundo, luego lea el sensor cuando la variable llegue a 10. Utilizando la misma configuración de CTC que en nuestro ejemplo anterior, el ISR se vería así:

 
ISR(TIMER1_COMPA_vect)

{

seconds++;

if(seconds == 10)

{

seconds = 0;

readSensor();

}

}


Para que una variable se modifique dentro de un ISR, es una buena costumbre declararlo como volátil. En este caso, necesita declarar segundos de bytes volátiles; o similar al comienzo del programa.

 

Una palabra sobre el Atmega8

El Atmega8 parece dar problemas a las personas con el uso de las interrupciones, una de las razones es que no tiene un registro TIMSK1 (de hecho, no tiene un registro TIMSKn), pero tiene un registro TIMSK que se comparte entre los 3 interrupciones . Como no tengo un Atmega8 (como el primer Arduino NG) no puedo probarlo, pero si encuentras problemas, los siguientes programas te ayudarán:

 

// this code sets up counter0 with interrupts enabled on an Atmega8

// beware, it may generate  errors in Arduino IDE

// as 'milis' uses timer0

#include <avr/io.h>

#include <avr/io.h>

 

void setup()

{

DDRD &= ~(1 << DDD4); // Clear the PD4 pin

// PD0 is now an input

 

PORTD |= (1 << PORTD4); // turn On the Pull-up

// PD4 is now an input with pull-up enabled

 

TIMSK |= (1 << TOIE0); // enable timer interrupt

 

TCCR0 |= (1 << CS02) | (1 << CS01) | (1 << CS00);

// Turn on the counter, Clock on Rise

 

sei();

}

void loop()

{

// Stuff

}

 

 

ISR (TIMER0_OVF_vect)

{

// interrupt just fired, do stuff

}

Una luz intermitente de 1 seg que utiliza el modo CTC del temporizador 1 para Atmega 8 se vería así:

 

void setup()

{    

pinMode(13,OUTPUT);

/* or use:

DDRB = DDRB | B00100000;  // this sets pin 5  as output

                       // without changing the value of the other pins

*/

// Disable interrupts while loading registers

cli();

// Set the registers

TCCR1A = 0; //Timer Counter Control register

// Set mode

TCCR1B = (1 << WGM12); // turn on CTC mode

// Set prescale values (1024). (Could be done in same statement

// as setting the WGM12 bit.)

TCCR1B |= (1 << CS12) | (1 << CS10);

//Enable timer compare interrupt===> TIMSK1 for ATmega328,

//TIMSK for ATmega8

TIMSK |= (1 << OCIE1A);

// Set OCR1A

OCR1A = 15624;

// Enable global interrupts

sei();

}

void loop(){}

ISR (TIMER1_COMPA_vect) {

   digitalWrite(13, !digitalRead(13));

   //PORTB ^= _BV(PB5); // as digitalWrite(13,x) is an Arduino

   //function, direct writing to the port may be preferable

}

Es obvio que esto es muy similar al programa CTC presentado anteriormente para el Atmega328 y de hecho también trabajará en el Atmega238 al cambiar el nombre de 'TIMSK' a 'TIMSK1'.

Otros chips Atmega:

TCCR0 debe ser TCCR0A en ATmega164P / 324P / 644

Attiny

La serie Attiny también tiene interrupciones de temporizador. Este código configura una interrupción de 50uS en modo CTC en el Attiny85 (página de datos de pag 79)

TCCR0A = (1 << WGM01);   //CTC mode. set WGM01

TCCR0B = (2 << CS00);    //divide by 8  sets

OCR0A = F_CPU/8 * 0.000050 - 1;    // 50us compare value

TIMSK |= (1<<OCIE0A);              //set interrupt

 

ISR(TIMER0_COMPA_vect)

{

                                   // code of choice!

}