Interrupciones y eventos temporizados de Arduino

En este tutorial aprendemos la funcionalidad de las interrupciones de Arduino y programamos 3 ejemplos con el modo Clear Timer on Compare

En este tutorial se aprende la funcionalidad de las interrupciones de Arduino y cómo temporizar eventos con esta función.

La ventaja de los eventos temporizados es realizar una tarea específica cuando se produce un evento, independientemente de la tarea actual que el microcontrolador está realizando, como el envío de datos importantes del sensor, incluso si el Arduino está atascado en una función de retardo.


¿Qué son las interrupciones de Arduino y los eventos temporizados?

El propósito de las interrupciones de Arduino es realizar una tarea específica cuando expira un temporizador preestablecido, independientemente de las tareas o programas que se estén ejecutando actualmente y que tengan lugar en la función de configuración o bucle. Algunos ejemplos comunes son:

  • Mida una señal entrante a intervalos regulares (frecuencia de muestreo constante)
  • Calcular el tiempo entre dos eventos
  • Enviar una señal de una frecuencia específica
  • Verificación periódica de los datos seriales entrantes

Hay muchas tareas como un bucle for o la ejecución de bibliotecas Arduino que se componen de muchos comandos, donde no es posible predecir el tiempo de cálculo de una sola tarea. Por lo tanto, necesitamos un enfoque diferente para interrumpir la función de bucle, realizar una tarea específica y luego continuar realizando la función de bucle.

Dependiendo de su microcontrolador, tiene varios temporizadores disponibles. Cada temporizador tiene un contador que incrementa el temporizador con cada tic del reloj del temporizador. El ATmega328 que está construido sobre el Arduino Uno tiene un total de 3 temporizadores disponibles. Si no sabe qué microcontrolador está integrado en su placa, en este artículo encontrará una descripción general de las placas más utilizadas.

Una vez que conocemos nuestro microcontrolador, la siguiente tabla muestra cuántos temporizadores están disponibles, su tamaño y pin para la conexión. También encuentra la velocidad del reloj del chip, que es la velocidad más rápida a la que el temporizador puede incrementar el contador.

. . . . . . 16 MHz.

 ATmega168, ATmega328ATmega1280, ATmega2560
Temporizador 08 bits, PWM valor máximo de 255.8 bits, PWM valor máximo de 255.
Temporizador 116 bits, PWM Valor máximo de 65535.16 bits, PWM Valor máximo de 65535.
Temporizador 28 bits, PWM valor máximo de 255.8 bits, PWM valor máximo de 255.
Temporizador 316 bits, PWM Valor máximo de 65535.16 bits, PWM Valor máximo de 65535.
Temporizador 416 bits, PWM Valor máximo de 65535.16 bits, PWM Valor máximo de 65535.
Temporizador 516 bits, PWM Valor máximo de 65535.16 bits, PWM Valor máximo de 65535.
Frecuencia de reloj16 MHz

Reducir la frecuencia del reloj para usar el temporizador

El chip ATmega328 que se usa a menudo en las placas Arduino tiene un total de 3 contadores. Uno de los temporizadores es de 16 bits y los otros dos tienen un tamaño de 8 bits. La frecuencia del reloj del chip es de 16 MHz, lo que significa que un tic necesita alrededor de 63 ns.

Si cree que es rápido, tiene razón. Honestamente, tiene más razón porque esta frecuencia de reloj es demasiado rápida para interrupciones y eventos cronometrados. El temporizador de 8 bits alcanzaría la entrada máxima en 256 / 16.000.000 s = 16 μs y los temporizadores de 16 bits en 256 / 16.000.000 s = 4 ms.
Si queremos interrumpir la función de bucle cada segundo, estos temporizadores no son muy útiles.

Pero existe una solución llamada preescalador para este problema. El preescalador es una constante de los valores 8, 64, 245, 1024 que reduce la velocidad del reloj como factor:
Velocidad del temporizador (Hz) = velocidad del reloj (16 MHz) / preestablecido

Borrar temporizador en modo comparar o CTC

Las interrupciones del temporizador CTC son eventos que se activan cuando el temporizador alcanza un valor preestablecido. Este valor se almacena en el registro de comparación de coincidencias. Al mismo tiempo, el temporizador se borra y se reinicia y reinicia el conteo.

Los registros de temporizador más utilizados son:

  • TCNTx – Registro de temporizador / contador. El valor real del temporizador se almacena aquí.
  • OCRx – Registro de comparación de salida
  • ICRx – Registro de captura de entrada (solo para temporizador de 16 bits)
  • TIMSKx – Registro de máscara de interrupción del temporizador / contador. Para activar / desactivar las interrupciones del temporizador.
  • TIFRx – Registro de banderas de interrupción del temporizador / contador. Indica una interrupción del temporizador pendiente.

Si queremos interrumpir cada segundo, el valor en el registro de coincidencia es = (velocidad del reloj / preescalador * frecuencia de interrupción) -1. Necesitamos restar 1 porque el registro de coincidencia está indexado a cero (depende del microcontrolador).
Para el ATmega328 con el preescalador más grande de 1024, el valor de registro sería: (16MHz / 1024 * 1Hz) -1 = 15624. Ahora necesitamos elegir el temporizador para esta segunda interrupción de 1Hz. El temporizador 0 y el temporizador 2 no pueden realizar esta tarea porque su valor máximo para almacenar es 255 con 8 bits. Por lo tanto, solo el temporizador 1 puede realizar la tarea 15624 <65535.

Cómo configurar los precalibradores

Los precalibradores están configurados con diferentes bits de selección de reloj. Puede encontrar la descripción en el Ficha técnica Atmega. Las siguientes tablas muestran los bits seleccionados para el Atmel-7810 como ejemplo.

Bits de selección de reloj para el temporizador Atmel-7810 0

. . . . . .

CS02CS01CS00Descripción
000Sin fuente de reloj (Temporizador/Contador parado)
001clkI/O/(sin preescalado)
010clkI/O/8 (desde prescaler)
011clkI/O/64 (desde el preescalador)
100clkI/O/256 (desde el preescalador)
101clkI/O/1024 (desde el preescalador)
110Fuente de reloj externa en el pin T0. Reloj en el flanco descendente
111Fuente de reloj externa en el pin T0. Reloj en el flanco ascendente.

Bits de selección de reloj para el temporizador 1 de Atmel-7810

. . . . . .

CS12CS11CS10Descripción
000Sin fuente de reloj (Temporizador/Contador parado)
001clkI/O/(sin preescalado)
010clkI/O/8 (desde prescaler)
011clkI/O/64 (desde el preescalador)
100clkI/O/256 (desde el preescalador)
101clkI/O/1024 (desde el preescalador)
110Fuente de reloj externa en el pin T1. Reloj en el flanco descendente
111Fuente de reloj externa en el pin T1. Reloj en el flanco ascendente.

Bits de selección de reloj para Atmel-7810 Timer 2

. . . . . . . .

CS22CS21CS20Descripción
000Sin fuente de reloj (Temporizador/Contador parado)
001clkT2S/(sin preescalado)
010clkT2S/8 (desde el preescalador)
011clkT2S/32 (desde el preescalador)
100clkT2S/64 (del preescalador)
101clkT2S/128 (desde el preescalador)
110clkT2S/256 (desde el preescalador)
111clkT2S/1024 (desde el preescalador)

Interrumpe Arduino en 3 frecuencias diferentes

En el siguiente ejemplo, queremos crear un boceto con tres interrupciones diferentes basadas en el modo CTC:

  1. Primeros 2kHz interruptores de tiempo de espera Arduino Uno pin 8
  2. El segundo tiempo de espera a 1Hz cambia el pin 13 de Arduino Uno
  3. El tercer tiempo de espera a 8 kHz cambia el pin 9 de Arduino Uno

El guión tiene la siguiente estructura

  1. Definir variables para la conmutación por error
  2. En la función de configuración, creamos las interrupciones CTC
    1. Ponga los 2 registros TCCRxA y TCCRxB a cero
    2. Inicializar el valor del contador TCNTx a 0
    3. Establezca el valor en el que el valor de la El contador debe compararse OCRxA con la siguiente ecuación: (16 * 10 ^ 6) / (frecuencia[Hz]* 64) – 1
    4. Activar el modo CTC
    5. Configurar precalibradores
    6. Compare el temporizador de interrupción
  3. Con la función llamada sei (); permitimos interrupciones mientras el microcontrolador está funcionando.
  4. Configure las funciones ISR (TIMERx_COMPA_vect) para definir qué debe hacer Arduino cuando se produce la interrupción. En este ejemplo, configuramos el pin HIGH si no hay interrupción y LOW si hay una interrupción. Por tanto, podemos verificar el comportamiento de la interrupción con el osciloscopio.

Básicamente, la estructura es la misma para las tres interrupciones. Solo se debe ajustar la frecuencia de interrupción y los registros según sus valores máximos.

El siguiente script muestra el código Arduino, que estamos discutiendo paso a paso.

//storage variables
boolean toggle0 = 0;
boolean toggle1 = 0;
boolean toggle2 = 0;

void setup(){
  
  //set pins as outputs
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(13, OUTPUT);

cli();//stop interrupts

//set timer0 interrupt at 2kHz
  TCCR0A = 0;// set entire TCCR2A register to 0
  TCCR0B = 0;// same for TCCR2B
  TCNT0  = 0;//initialize counter value to 0
  // set compare match register for 2khz increments
  OCR0A = 124;// = (16*10^6) / (2000*64) - 1 (must be <256)
  // turn on CTC mode
  TCCR0A |= (1 << WGM01);
  // Set CS01 and CS00 bits for 64 prescaler
  TCCR0B |= (1 << CS01) | (1 << CS00);   
  // enable timer compare interrupt
  TIMSK0 |= (1 << OCIE0A);

//set timer1 interrupt at 1Hz
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1hz increments
  OCR1A = 15624;// = (16*10^6) / (1*1024) - 1 (must be <65536)
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS12 and CS10 bits for 1024 prescaler
  TCCR1B |= (1 << CS12) | (1 << CS10);  
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);

//set timer2 interrupt at 8kHz
  TCCR2A = 0;// set entire TCCR2A register to 0
  TCCR2B = 0;// same for TCCR2B
  TCNT2  = 0;//initialize counter value to 0
  // set compare match register for 8khz increments
  OCR2A = 249;// = (16*10^6) / (8000*8) - 1 (must be <256)
  // turn on CTC mode
  TCCR2A |= (1 << WGM21);
  // Set CS21 bit for 8 prescaler
  TCCR2B |= (1 << CS21);   
  // enable timer compare interrupt
  TIMSK2 |= (1 << OCIE2A);


sei();//allow interrupts

}//end setup

ISR(TIMER0_COMPA_vect){//timer0 interrupt 2kHz toggles pin 8
//generates pulse wave of frequency 2kHz/2 = 1kHz (takes two cycles for full wave- toggle high then toggle low)
  if (toggle0){
    digitalWrite(8,HIGH);
    toggle0 = 0;
  }
  else{
    digitalWrite(8,LOW);
    toggle0 = 1;
  }
}

ISR(TIMER1_COMPA_vect){//timer1 interrupt 1Hz toggles pin 13
//generates pulse wave of frequency 1Hz/2 = 0.5kHz (takes two cycles for full wave- toggle high then toggle low)
  if (toggle1){
    digitalWrite(13,HIGH);
    toggle1 = 0;
  }
  else{
    digitalWrite(13,LOW);
    toggle1 = 1;
  }
}
  
ISR(TIMER2_COMPA_vect){//timer1 interrupt 8kHz toggles pin 9
//generates pulse wave of frequency 8kHz/2 = 4kHz (takes two cycles for full wave- toggle high then toggle low)
  if (toggle2){
    digitalWrite(9,HIGH);
    toggle2 = 0;
  }
  else{
    digitalWrite(9,LOW);
    toggle2 = 1;
  }
}

void loop(){
}

Script original

Las siguientes tres imágenes son capturas de pantalla del osciloscopio, donde podemos comprobar la frecuencia de la interrupción. Medí el voltaje directamente en el pin de salida del Arduino Uno. En la parte inferior de cada imagen, puede ver que medí el promedio del ancho de pulso alto en segundos. Si invertimos el ancho de pulso alto, obtenemos la frecuencia de la interrupción.

Temporizador 0 interrupción a 2 kHz

Interrupciones y eventos temporizados de Arduino

El ancho de pulso promedio alto es de 501,9 μs con una frecuencia de 1,99 kHz

Temporizador de interrupción 1 a 1 Hz

Interrupciones y eventos temporizados de Arduino

El ancho de pulso promedio alto es 1.002 s con una frecuencia de 1 Hz

Interrupción del temporizador 2 a 8 kHz

Interrupciones y eventos temporizados de Arduino

El ancho de pulso promedio alto es de 126,1 μs con una frecuencia de 7,93 kHz

Conclusión

En este tutorial, aprendimos mucho sobre las interrupciones de Arduino y los eventos cronometrados, mejor dicho los diversos microcontroladores ATmega. En el ejemplo, solo usamos la función numérica ALTA y BAJA si ocurre un evento cronometrado. En un ejemplo del mundo real, cambiaríamos la función a algo más productivo como calcular el tiempo entre dos eventos. Si tiene alguna pregunta sobre este tutorial, utilice la sección de comentarios a continuación.


Deja un comentario