Hola Habr! Les presento la traducción del artículo "Interrupciones del temporizador" de E.
Prólogo
La placa Arduino le permite resolver rápida y mínimamente una variedad de problemas. Pero cuando se necesitan intervalos de tiempo arbitrarios (sondeo periódico de sensores, señales PWM de alta precisión, pulsos de larga duración), las funciones estándar de retraso de la biblioteca no son convenientes. Durante la duración de su acción, el boceto se suspende y se hace imposible administrarlo.
En una situación similar, es mejor usar los temporizadores AVR integrados. Un artículo exitoso le dice cómo hacer esto y no perderse en la naturaleza técnica de las hojas de datos, cuya traducción le llama la atención.

Este artículo aborda los temporizadores AVR y Arduino y cómo usarlos en proyectos Arduino y circuitos de usuario.
¿Qué es un temporizador?
Como en la vida cotidiana en los microcontroladores, un temporizador es algo que puede señalar en el futuro, en el momento en que lo configura. Cuando llega este momento, el microcontrolador se interrumpe y le recuerda que haga algo, por ejemplo, para ejecutar un determinado código.
Los temporizadores, como las interrupciones externas, funcionan independientemente del programa principal. En lugar de repetir o repetir la llamada de retraso millis () , puede asignar un temporizador para hacer su trabajo mientras su código hace otras cosas.
Entonces, supongamos que hay un dispositivo que necesita hacer algo, por ejemplo, parpadear un LED cada 5 segundos. Si no utiliza temporizadores, pero escribe un código normal, debe establecer una variable en el momento en que se enciende el LED y verificar constantemente si ha llegado el momento de su conmutación. Con una interrupción del temporizador, solo necesita configurar la interrupción y luego iniciar el temporizador. El LED parpadeará exactamente a tiempo, independientemente de las acciones del programa principal.
¿Cómo funciona el temporizador?
Actúa incrementando una variable llamada registro de conteo . El registro de conteo puede contar hasta cierto valor, dependiendo de su tamaño. El temporizador incrementa su contador una y otra vez hasta que alcanza su valor máximo, en este punto el contador se desbordará y se restablecerá a cero. Un temporizador generalmente establece un bit de indicador para informarle que se ha producido un desbordamiento.
Puede verificar esta bandera manualmente o puede hacer un cambio de temporizador; esto causa una interrupción automáticamente cuando se establece la bandera. Al igual que cualquier otra interrupción, puede asignar una Rutina de servicio de interrupción ( ISR ) para ejecutar el código especificado cuando el temporizador se desborde. El ISR en sí borrará el indicador de desbordamiento, por lo que el uso de interrupciones suele ser la mejor opción debido a su simplicidad y velocidad.
Para aumentar los valores del contador a intervalos de tiempo exactos, el temporizador debe estar conectado a la fuente del reloj. La fuente del reloj genera una señal que se repite constantemente. Cada vez que el temporizador detecta esta señal, aumenta el valor del contador en uno. Como el temporizador funciona con una fuente de reloj, la unidad de tiempo más pequeña que se puede medir es el período del ciclo. Si conecta una señal de reloj de 1 MHz, la resolución del temporizador (o período del temporizador) será:
T = 1 / f (f es la frecuencia del reloj)
T = 1/1 MHz = 1/10 ^ 6 Hz
T = (1 ∗ 10 ^ -6) s
Por lo tanto, la resolución del temporizador es una millonésima de segundo. Aunque puede usar una fuente de reloj externa para temporizadores, en la mayoría de los casos se usa la fuente interna del chip.
Tipos de temporizador
En las placas Arduino estándar en un chip AVR de 8 bits, hay varios temporizadores a la vez. Los chips Atmega168 y Atmega328 tienen tres temporizadores Timer0, Timer1 y Timer2. También tienen un temporizador de vigilancia que se puede utilizar para proteger contra fallas o como un mecanismo de reinicio de software. Aquí hay algunas características de cada temporizador.
Timer0:
Timer0 es un temporizador de 8 bits, lo que significa que su registro de conteo puede almacenar números de hasta 255 (es decir, un byte sin signo). Timer0 es utilizado por las funciones temporales estándar de Arduino como delay () y millis () , por lo que es mejor no confundirlo si le importan las consecuencias.
Temporizador1:
Timer1 es un temporizador de 16 bits con un valor de conteo máximo de 65535 (entero sin signo). Este temporizador usa la biblioteca Servo Arduino, tenga esto en cuenta si la usa en sus proyectos.
Timer2:
Timer2 es de 8 bits y es muy similar a Timer0. Se utiliza en la función Arduino tone () .
Timer3, Timer4, Timer5:
Los chips ATmega1280 y ATmega2560 (instalados en las variantes Arduino Mega) tienen tres temporizadores adicionales. Todos ellos son de 16 bits y funcionan de manera similar a Timer1.
Configuración de registro
Para usar estos temporizadores, el AVR tiene registros de configuración. Los temporizadores contienen muchos de esos registros. Dos de ellos: los registros de control del temporizador / contador contienen variables de configuración y se denominan TCCRxA y TCCRxB, donde x es el número del temporizador (TCCR1A y TCCR1B, etc.). Cada registro contiene 8 bits y cada bit almacena una variable de configuración. Aquí están los detalles de la hoja de datos Atmega328:
Los más importantes son los últimos tres bits en TCCR1B: CS12, CS11 y CS10. Determinan la frecuencia de reloj del temporizador. Al elegirlos en diferentes combinaciones, puede ordenar que el temporizador actúe a diferentes velocidades. Aquí hay una tabla de hoja de datos que describe el efecto de los bits seleccionados:
Por defecto, todos estos bits están establecidos en cero.
Suponga que desea que Timer1 se ejecute a una frecuencia de reloj con una muestra por período. Cuando se desborda, desea llamar a la rutina de interrupción, que cambia el LED conectado a la pata 13 al estado encendido o apagado. Para este ejemplo, escribiremos el código Arduino, pero usaremos los procedimientos y funciones de la biblioteca avr-libc siempre que esto no haga las cosas demasiado complicadas. Los partidarios de AVR puro pueden adaptar el código como lo deseen.
Primero, inicialice el temporizador:
El registro TIMSK1 es un registro de máscara de interrupción de temporizador / contador1. Controla la interrupción que puede provocar el temporizador. Establecer el bit TOIE1 le dice al temporizador que interrumpa cuando el temporizador se desborda. Más sobre esto más tarde.
Cuando establece el bit CS10, el temporizador comienza a contar y, tan pronto como se produce una interrupción por desbordamiento, se llama al ISR (TIMER1_OVF_vect). Esto siempre sucede cuando el temporizador se desborda.
A continuación definimos la función de interrupción ISR:
ISR(TIMER1_OVF_vect) { digitalWrite(LEDPIN, !digitalRead(LEDPIN)); }
Ahora podemos definir el ciclo loop () y cambiar el LED independientemente de lo que ocurra en el programa principal. Para apagar el temporizador, configure TCCR1B = 0 en cualquier momento.
¿Con qué frecuencia parpadeará el LED?
El temporizador 1 está configurado para interrumpir el desbordamiento y supongamos que está utilizando un Atmega328 con una frecuencia de reloj de 16 MHz. Como el temporizador es de 16 bits, puede contar hasta el valor máximo (2 ^ 16 - 1), o 65535. A 16 MHz, el ciclo funciona 1 / (16 ∗ 10 ^ 6) segundos o 6.25e-8 s. Esto significa que se producirán 65535 muestras en (65535 ∗ 6.25e-8 s) y se llamará al ISR después de aproximadamente 0.0041 s. Y así, una y otra vez, cada cuatro milésimas de segundo. Es demasiado rápido para ver el parpadeo.
Si aplicamos una señal PWM muy rápida con una cobertura del 50% al LED, el brillo aparecerá continuo, pero menos brillante de lo habitual. Tal experimento muestra el increíble poder de los microcontroladores: incluso un chip económico de 8 bits puede procesar información mucho más rápido de lo que podemos detectar.
Divisor de temporizador y modo CTC
Para controlar el período, puede usar un divisor que le permite dividir la señal del reloj en diferentes grados de dos y aumentar el período del temporizador. Por ejemplo, le gustaría que el LED parpadee a intervalos de un segundo. Hay tres bits CS en el registro TCCR1B que configuran la resolución más adecuada. Si configura los bits CS10 y CS12 usando:
TCCR1B |= (1 << CS10); TCCR1B |= (1 << CS12);
entonces la frecuencia de la fuente del reloj se dividirá por 1024. Esto proporciona una resolución del temporizador de 1 / (16 ∗ 10 ^ 6/1024) o 6.4e-5 s. Ahora el temporizador se desbordará cada (65535 ∗ 6.4e-5s) o durante 4.194s. Es muy largo
Pero hay otro modo de temporizador AVR. Se llama reinicio del temporizador coincidente o CTC. En lugar de contar para desbordar, el temporizador compara su contador con la variable que se almacenó previamente en el registro. Cuando el recuento coincide con esta variable, el temporizador puede establecer una bandera o provocar una interrupción, como en el caso de un desbordamiento.
Para usar el modo CTC, debe comprender cuántos ciclos necesita para obtener un intervalo de un segundo. Supongamos que la relación de división sigue siendo 1024.
El cálculo será el siguiente:
(target time) = (timer resolution) * (# timer counts + 1) (# 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 una unidad adicional al número de muestras porque en el modo CTC, cuando el contador coincide con el valor establecido, se restablecerá a cero. El reinicio toma un período de reloj, que debe tenerse en cuenta en los cálculos. En muchos casos, un error en un período no es muy significativo, pero en tareas de alta precisión puede ser crítico.
La función setup () será así:
void setup() { pinMode(LEDPIN, OUTPUT);
También debe reemplazar la interrupción por desbordamiento con una interrupción coincidente:
ISR(TIMER1_COMPA_vect) { digitalWrite(LEDPIN, !digitalRead(LEDPIN)); }
Ahora el LED se encenderá y apagará exactamente por un segundo. Y puede hacer cualquier cosa en un bucle loop (). Hasta que cambie la configuración del temporizador, el programa no tiene nada que ver con las interrupciones. No tiene restricciones para usar un temporizador con diferentes modos y configuraciones del divisor.
Aquí hay un ejemplo completo de inicio que puede usar como base para sus propios proyectos:
Recuerde que puede usar las funciones ISR incorporadas para ampliar las funciones del temporizador. Por ejemplo, debe sondear el sensor cada 10 segundos. Pero no hay configuraciones de temporizador que proporcionen una cuenta tan larga sin desbordamiento. Sin embargo, puede usar ISR para incrementar la variable de conteo una vez por segundo y luego sondear el sensor cuando la variable alcanza 10. Usando el modo STS del ejemplo anterior, la interrupción podría verse así:
ISR(TIMER1_COMPA_vect) { seconds++; if(seconds == 10) { seconds = 0; readSensor(); } }
Como la variable se modificará dentro del ISR, debe declararse como volátil . Por lo tanto, al describir variables al comienzo del programa, debe escribir:
volatile byte seconds;
Epílogo del traductor
En un momento, este artículo me ahorró mucho tiempo al desarrollar un prototipo de generador de medición. Espero que sea útil para otros lectores.