Descripción
Aunque se puede medir el tiempo en milisegundos y usar la función delay(x); para temporizar, el problema del delay(), es que congela Arduino el tiempo especificado. Esto sirve mientras sea unos poco segundos, si se requiere más, no es práctico, por ejemplo 30 segundos, durante los cuales no se puede hacer otra cosa, ya que Arduino está congelado.
Así que es buena idea usar delays en la mayoría de las situaciones.
No es raro programar tareas periódicas en Arduino en rangos que van desde unos microsegundos hasta varios minutos, pero se quiere hacer de modo que entre tanto se pueda seguir trabajando.
Ya vimos el concepto de interrupción hardware. Es el equivalente a un timbre que nos avisa de que alguien está en la puerta y debemos atenderle.
Arduino dispone además de una segunda clase de interrupciones, los Timers, que hacen lo mismo que las interrupciones hardware, pero en lugar de dispararse cuando se cumple un cierto proceso hardware en uno de los pines, se dispara cuando ha transcurrido un tiempo preciso, previamente programado. Es el equivalente del despertador, que cada mañana suena a la misma hora.
Los contadores internos de los Timers
Nuestros Arduinos UNOS y MEGAs tienen un cristal que oscilan a 16 MHz, o 16.000.000 de veces por segundo.
Teóricamente podríamos fijar una interrupción cada 1/16000000 segundos, lo que no sería muy útil porque cada instrucción de Arduino necesita varios pulsos de reloj para ejecutarse (y algunos muchos pulsos).
Por eso cada Timer dispone de un registro interno que indica cada cuantos ticks del reloj debe dispararse. Básicamente el que nos interesa es un Timer cuyo registro es un entero sin signo de 16 bits, lo que le permite contar hasta 216 = 65.536.
Si este contador se disparase cada tick del cristal, no nos serviría de mucho porque, el máximo tiempo que el contador podría abarcar sería de:
Como es muy probable que necesitemos algo más de flexibilidad en los tiempos, el ATMEGA328, incluye unos divisores de esta frecuencia básica del cristal de cuarzo., de modo que solo salta un tick al contador cada ciertos ticks del patrón básico.
En Arduino UNO esos divisores pueden ser 1, 8, 64, 256 y 1024. De ese modo podemos “frenar” hasta mil veces la velocidad de incremento del contador de disparo.
Un cálculo rápido nos dice que el anterior máximo de disparo puede subir desde los 4 ms de antes a 1024 veces más, o sea alrededor de 5 segundos.
Aunque pueda pareceros poco tiempo, ya es un margen considerable para atender a las cuestiones urgentes con precisión.
Naturalmente existen librerías que a partir de esto van incrementando contadores externos para multiplicar el plazo de disparo.
Hablamos de contadores de disparo, llamados Compare Match Registers o CMRs y a los divisores les llamaremos prescalers.
Además, la documentación técnica sobre los Timer Interrupts, hablan inmediatamente de los registros internos del chip y de cómo calcular la frecuencia de la interrupción en Hz y otras cosas parecidas.
Vamos con el uso de una librería para controlar las interrupciones programadas como la TimerOne.
No hace falta ninguna librería para programar un Timer en Arduino UNO. Basta con programar directamente los registros internos del ATMEGA328
Arduino soporta sus instrucciones y conoce todos los valores y funciones precisas, pero obliga a conocer los entresijos a nivel de hardware del procesador.
Así que se usa la librería TimerOne, que facilita la programación de las interrupciones
Esquema
Montaje
Código
La librería TimerOne
Hay varias versiones de esta librería, aquí se ha elegido esta, básicamente porque parece soportar más modelos de Arduino (Incluyendo al MEGA) y porque parece que el código es más rápido que el original.
La librería TimerOne
Hay varias versiones de esta librería, aquí se ha elegido esta, básicamente porque parece soportar más modelos de Arduino (Incluyendo al MEGA) y porque parece que el código es más rápido que el original.
Las cosas importantes de la librería. Cuando la importéis tendréis esta línea:
#include <TimerOne.h>
Esto nos crea un objeto llamado Timer1 directamente, sin necesidad de instanciarlos. Lo siguiente es programar el intervalo de disparo en microsegundos:
Timer1.initialize(250000); // Dispara cada 250 ms
Y ya solo falta hacer el attach de la interrupción con el servicio de gestión o ISR:
Timer1.attachInterrupt(ISR_Blink); // Activa la interrupcion y la asocia a ISR_Blink
Y con esto ya se ha programado una interrupción temporizada que saltara cada 150 ms y llamara la función ISR_Callback.
Veamos ahora un programa que se aproveche de esto. Podeís descarlo completo aqui:
Prog_57_2
Empecemos definiendo cosas
Y ahora veamos la Función de servicio, recordad que conviene que una ISR sea la mínima expresion:
#include <TimerOne.h>
const int led = 13; // the pin with a LED
int ledState = LOW; // El LED empieza apagado
volatile unsigned long blinkCount = 0; // La definimos como volatile
void setup(void){
pinMode(led, OUTPUT);
Timer1.initialize(250000); // Dispara cada 250 ms
Timer1.attachInterrupt(ISR_Blink); // Activa la interrupcion y la asocia a ISR_Blink
Serial.begin(115200);
}
void ISR_Blink(){
ledState = !ledState ;
blinkCount++ ; // Contador veces se enciende el LED
}
void loop(void){
unsigned long N; // Haremos copia del blinkCount
digitalWrite(led, ledState); // Asignamos el valor del
// status a la salida
noInterrupts(); // Suspende las interrupciones
N = blinkCount;
interrupts(); // Autoriza las interrupciones
Serial.print("Ciclos = ");
Serial.println(N);
delay(250); // Este delay hace que haya una impresión por cada ciclo en el monitor serie.
}
El código completo es el siguiente:
#include <TimerOne.h>
const int led = 13; // the pin with a LED
int ledState = LOW; // El LED empieza apagado
volatile unsigned long blinkCount = 0; // La definimos como volatile
void setup(void){
pinMode(led, OUTPUT);
Timer1.initialize(250000); // Dispara cada 250 ms
Timer1.attachInterrupt(ISR_Blink); // Activa la interrupcion y la asocia a ISR_Blink
Serial.begin(115200);
}
void loop(void){
unsigned long N; // Haremos copia del blinkCount
digitalWrite(led, ledState); // Asignamos el valor del
// status a la salida
noInterrupts(); // Suspende las interrupciones
N = blinkCount;
interrupts(); // Autoriza las interrupciones
Serial.print("Ciclos = ");
Serial.println(N);
delay(250); // Este delay hace que haya una impresión por cada ciclo en el monitor serie.
}
void ISR_Blink(){
ledState = !ledState ;
blinkCount++ ; // Contador veces se enciende el LED
}
Después podemos seguir con el programa normal sin preocuparnos de comprobar si toca o no toca saltar a la interrupción programada. ISR_Blink se ejecuta limpiamente cada tanto tiempo solita.
Además podemos usar delays tranquilamente, porque las interrupciones tienen prioridad y se ejecutaran aun cuando el delay esté activo.
Las interrupciones tienen ventajas e inconvenientes.
Entre las ventajas tenemos:
Código limpio y elegante.
Las interrupciones tienen ventajas e inconvenientes.
Entre las ventajas tenemos:
Código limpio y elegante.
No tenemos que calcular en el loop si estaremos perdiéndonos algo o no.
Cuando el tiempo programado se cumple la interrupción salta y se ejecuta limpiamente.
Conceptualmente la programación orientada a eventos es la predominante en los moderno sistemas operativos como Linux, Windows o OSX y si aprendéis a pensar así no os resultara difícil entender el concepto bajo Visual Basic o C#.
No importa que estemos en un delay, la interrupción salta impecable.
La medida del tiempo es muy precisa.
Entre los inconvenientes tenemos:
El primero y grave, es que si jugamos con los timers, muchas de las instrucciones que dependen de ellos dejaran de funcionar.
Entre estos están, los pines PWM y analogWrite() y la librería Servo. Dependiendo del modelo Arduino y del Timer que usemos la cosa es grave.
Si vuestro Timer entra en conflicto con algo puede ser muy complicado comprender el problema.
Si tu Servicio ISR tarda más en ejecutarse de lo que tarda en saltar la nueva interrupción (Y te puede pasar por un error de cálculo) antes de acabar puede volver a entrar porque ha disparado de nuevo el Timer. La situación alcanzara un nivel de peligro inmediato porque tu Arduino se colgará y no sabrás porque.
No hay comentarios:
Publicar un comentario