Contactos

Temporizadores Arduino mega 2560. AVR. Curso de entrenamiento. Temporizadores. Temporizadores en Arduino

Recientemente, cada vez más principiantes se enfrentan al problema de dominar los temporizadores/contadores (en adelante, T/C) en la etapa de estudio de los microcontroladores. En este artículo intentaré disipar los temores sobre estos módulos y explicar claramente cómo y con qué se utilizan estos mismos T/S.

Tomaremos como base un libro muy popular entre los desarrolladores de dispositivos MK, escrito por A.V. Evstifeev. Usando los enlaces al final del artículo puede encontrar el proyecto en y el proyecto en. En este artículo analizaremos el funcionamiento del T/S T2 de 8 bits, que forma parte del Atmega8 T/S MK.

Entonces, ¿qué es un temporizador/contador? T/S es uno de los módulos del AVR MK con el que podrás medir determinados periodos de tiempo, organizar PWM y muchas otras tareas. Dependiendo del modelo MK, el número de T/S puede ser 4 o más. Un ejemplo de esto son los MK Atmega640x, 1280x/1281x, 2560x/2561x, que contienen 6 T/S a bordo: dos de 8 bits y cuatro de 16 bits. El Atmega8 MK contiene tres T/S: T0 y T2 con 8 bits, T1 con 16 bits.

Echemos un vistazo más de cerca al microcontrolador T/C T2 Atmega8.

Este temporizador puede funcionar en varios modos: Normal, PWM de fase correcta, CTC (reinicio en caso de coincidencia), PWM rápido. Puedes leer más sobre cada modo en el libro.

Este T/C consta de un registro de control, un registro de conteo, un registro de comparación y un registro de estado de modo asíncrono. El diagrama de bloques de T2 se muestra en la Fig. 1.

Veamos cómo funciona este módulo en teoría. Para que quede más claro para usted, no consideraremos todos los dispositivos innecesarios del temporizador y consideraremos su modo más común: NORMAL. Determinemos por nosotros mismos que el MK está sincronizado desde un oscilador RC interno con una frecuencia de 1 MHz y que el temporizador está configurado para funcionar en modo NORMAL.

Los pulsos de reloj llegan a la entrada clk i\o y entran al preescalador del temporizador. El preescalador se puede configurar, según sus necesidades, para pasar pulsos de reloj directamente o para dividir los pulsos entrantes, pasando solo una determinada parte de ellos. Los pulsos entrantes se pueden dividir en /8, /64, /256, /1024. Dado que nuestro T\S puede funcionar en modo asíncrono, cuando se enciende en este modo, el número de preescaladores aumenta significativamente, pero no los consideraremos por ahora. Desde el preescalador, los pulsos de reloj ingresan a la unidad de control y desde allí ingresan al registro de conteo. El registro de conteo, a su vez, aumenta con cada pulso entrante. El registro de conteo T2 es de 8 bits, por lo que solo puede contar hasta 255. Cuando el registro de conteo se desborda, se restablece a 0 y comienza a contar nuevamente en el mismo ciclo. Además, cuando el registro del contador se desborda, se activa el indicador TOV2 (indicador de interrupción de desbordamiento) del registro TIFR.

Ahora que hemos tocado palabras como REGISTRAR, es hora de familiarizarse con ellas. Para empezar, tocaremos solo aquellos registros con los que trabajaremos directamente, para no llenar el cerebro con información innecesaria.

TCNT2 es un registro de conteo, ya hemos hablado de su funcionamiento.

TCCR2 - registro de control del temporizador.

TIMSK: registro de máscara de interrupción (en Atmega8 este registro es el único para todos los temporizadores).

TIFR - registro de bandera de interrupción (en Atmega8 este registro es el único para todos los temporizadores).

Y ahora sobre cada uno en detalle:

Registro de control TCCR2. Puedes ver el contenido de este registro en la Fig. 2.


Figura 2

Los bits 0-2 son responsables de cronometrar el temporizador. Establecer ciertas combinaciones de estos bits configura el preescalador para un temporizador determinado. Si los tres bits están limpios, el temporizador se apaga.

Los bits 3.6 son responsables del modo de funcionamiento del temporizador.

Los bits 4.5 son necesarios para configurar el comportamiento de la salida OSn (en otras palabras, se utilizan al configurar PWM)

Y el último bit de este registro es el bit 7. Con su ayuda, podemos cambiar a la fuerza el estado del pin OSn.

Registro de máscara de interrupción - TIMSK. Lo vemos en la foto nº 3.

De este registro sólo nos interesan los dos últimos bits, los bits 6 y 7. Con estos bits habilitamos las interrupciones.

El bit 6, si está escrito en él, habilita la interrupción debido al evento "T\C T2 overflow"

Bit 7, si le escribesNitsya, permite interrumpir el evento "Coincidencia del registro de conteo con el registro de comparación"

Registro de bandera de interrupción TIFR. Lo vemos en la foto nº4.

Fig.4

En este registro también nos interesan los dos últimos bits: bits 6 y 7.

Bit 6 - bandera, establecida por el evento "T\C T2 overflow"
Bit 7 - bandera, esta instalado por el evento "Coincidencia del registro de conteo con el registro de comparación"

Estos bits se restablecen automáticamente cuando sale el controlador de interrupciones, pero para estar seguro, puede restablecerlos usted mismo restableciendo estos bits a "0".

Los bits restantes de los registros TIMSK y TIFR son utilizados por T\C T0 y T1. Como ya habrás notado, los bits de estos registros incluso tienen los mismos nombres, con la excepción del número al final del nombre, que indica a qué temporizador se aplica este bit.

Queda por considerar dos tablas simples, a saber: una tabla que describe el control de la señal del reloj (Fig. 6) y una tabla que describe cómo configurar en general un temporizador (Fig. 5).

Escribí anteriormente sobre lo que hay en estas tablas, pero se las presento para mayor claridad.

Ahora hemos terminado con la teoría y es hora de comenzar la parte práctica. Haré una reserva de inmediato.

LOS RELOJES QUE SE OBTIENEN AL ESTUDIAR ESTE ARTÍCULO NO SON MUY EXACTOS. ESTE ARTÍCULO ESTÁ ORIENTADO A LOS PRINCIPIOS GENERALES DEL TRABAJO CON TEMPORIZADORES.

Abra Studio 6, cree un proyecto y seleccione Atmega8.

Al principio, indicamos la frecuencia del reloj y conectamos las bibliotecas que necesitamos para trabajar.

< avr/io.h >#incluir< avr/interrupt.h >

En la primera línea indicamos la frecuencia. Esto es necesario para que el compilador nos entienda mejor si de repente queremos utilizar las funciones _delay_().

La segunda línea de código incluye una biblioteca con una descripción general de los registros de nuestro MK. También asigna nombres legibles a todos los registros.

La tercera línea incluye una biblioteca para trabajar con vectores de interrupción.

TIMSK |= (1< < TOIE2); TCCR2 |= (1< < CS22)|(1< < CS20); SREG |= (1< < 7);

Esto completa la configuración de nuestro temporizador. Echemos un vistazo más de cerca a las últimas tres líneas de código.

En la primera línea habilitamos las interrupciones para el evento "Desbordamiento del temporizador/contador T2"

Y en la tercera línea habilitamos las interrupciones globalmente. También podría escribirse así:

Asm("sei");

Todo lo que queda es agregar el controlador de interrupciones y el código para nuestro reloj en tiempo real.

ISR (TIMER2_OVF_vect) ( takt++; if (takt>=4)(sek++; takt=0x00;) if (sek>=60) (min++; sek=0x00;) if (min>=60) (hora++; min=0x00 ;) si (hora>=24) (hora=0x00); )

No hay nada complicado ni nuevo para usted en el código que se encuentra en el controlador de interrupciones. Prestemos atención sólo a la variable takt y al número mágico "4". ¿De dónde surgió esta cifra? Echemos un vistazo más de cerca a este punto.

Sabemos que nuestro MK opera desde un oscilador interno con una frecuencia de 1 MHz, el temporizador está sincronizado con un preescalador de \1024, nuestro temporizador puede contar hasta 255. Conociendo estos parámetros, podemos calcular cuántos desbordamientos realizará en 1 segundo

1 000 000 \ 1024 \ 256 = 3,814697.....

Bueno, como estamos aprendiendo a trabajar con temporizadores y no nos fijamos el objetivo de obtener un reloj súper preciso, redondeamos nuestro resultado y obtenemos "4". Aquellos. en 1 segundo el temporizador se desbordará ~4 veces.

¿Por qué dividimos entre 256 si el cronómetro solo cuenta hasta 255? Porque "0" también es un número. Creo que aquí todo está claro.

No olvide que todas las variables deben declararse como globales.

Aquí está la lista completa del programa que obtuvimos.

#definir F_CPU 1000000UL #incluir< avr/io.h >#incluir< avr/interrupt.h >takt de carácter sin firmar = 0; carácter sin firmar sek = 0; carácter sin firmar min=0; hora de carbón sin firmar = 0; ISR (TIMER2_OVF_vect) ( takt++; if (takt>=4)(sek++; takt=0x00;) if (sek>=60) (min++; sek=0x00;) if (min>=60) (hora++; min=0x00 ;) if (hora>=24) (hora=0x00); ) int main(void) ( TIMSK |= (1< < TOIE2); TCCR2 |= (1< < CS22)|(1< < CS20); SREG |= (1< < 7); while(1) { } }

Pero ¿qué pasa con la salida de información al usuario? Y luego a quien le guste. Puede utilizar indicadores de siete segmentos, pantallas gráficas o generadoras de caracteres, etc.

En el archivo encontrará un proyecto con información de visualización de nokia5110, un proyecto en Proteus 7 y todos los archivos y bibliotecas necesarios para trabajar.

Tenga en cuenta que la biblioteca LCD_5110 para trabajar con la pantalla fue escrita por un participante del foro y proporcionada con su permiso.

Teniendo en cuenta todo lo dicho, escribamos un programa que encienda el LED. En este caso, lo hará basándose en el evento de desbordamiento del contador del temporizador. Temporizador 1(nuestro vector está designado: TIM1_OVF). Dado que el contador es de 16 bits, se producirá un evento de desbordamiento cada 65.536 pulsos de la frecuencia de entrada. Si configuramos el factor de división de frecuencia de reloj en la entrada Temporizador 1 igual a 64, entonces a una frecuencia del generador de 4 MHz obtenemos aproximadamente 1 Hz: 4.000.000/64/65.536 = 0,953674 Hz.

Esto no es exactamente lo que necesitamos y, además, la frecuencia no es exactamente un hercio. Para que el LED cambie exactamente una vez cada medio segundo (es decir, su período sea igual a un segundo), el programa tendrá que complicarse un poco cargando cada vez un cierto valor en los registros de conteo, que se calcula de manera simple: Si el período de un pulso de reloj del temporizador es igual a 16 μs (frecuencia 4 000 000/64), entonces para obtener 500 000 microsegundos es necesario contar 31 250 de esos pulsos, ya que el contador está sumando y se produce una interrupción cuando llega al número 65 536. se alcanza, primero debe cargar en él el número requerido 65,536 – 31250 = 34,286.

Este no es el único método, pero sí el más universal, adecuado para todos los temporizadores. Por cierto, así es exactamente como se implementa el conteo del tiempo en arduino(cm. capitulo 21). Otra forma es utilizar una interrupción cuando se alcanza un cierto número cargado en el registro de comparación. A o EN. Veremos cómo se hace esto más adelante en este capítulo. Para realizar el cambio de rojo a verde tendremos que hacer lo mismo que antes, es decir, por cada evento de desbordamiento, invertir dos bits en el registro. PuertoD .

El programa completo entonces se verá así:

No comentaré en detalle cada afirmación porque ocuparía demasiado espacio. Después de ejecutar todos los comandos de instalación iniciales, el MK entra en un bucle, pero el bucle sin fin se interrumpirá cuando se produzca una interrupción; aquí todo es similar al sistema operativo Windows, que también representa un bucle sin fin de espera de eventos. Como aprenderá en los capítulos siguientes, arduino Un bucle de este tipo es uno de los componentes principales de cualquier programa, precisamente porque allí casi nunca se utilizan interrupciones. Dentro de un bucle sin fin, puedes poner aquí un comando familiar dormir, sin configuraciones adicionales del modo de consumo de energía, ahorrará aproximadamente un 30% de energía. Pero no podrás ahorrar aún más, ya que tendrás que detener el núcleo del procesador y el temporizador dejará de funcionar.

Notas en los márgenes

Por cierto, ¿cómo se puede detener un cronómetro en funcionamiento si es necesario? Es muy simple: si reinicia el registro TCCR1B (aquel en el que está configurado el factor de división de frecuencia del reloj), el temporizador se detendrá. Para ejecutarlo nuevamente con un coeficiente de 1/64, debe escribir nuevamente el valor 0b00000011 en este registro.

Tenga en cuenta que el operador reti(fin del procesamiento de interrupción) ocurre dos veces cuando se procesa una interrupción del temporizador; esta es una técnica completamente normal cuando una subrutina se bifurca. Por supuesto, puedes marcar la última declaración. reti etiqueta, y luego el texto del procedimiento se volvería indistinguible de la primera opción, pero esto sería más correcto.

Por favor, preste atención también al formulario de registro. temperatura ldi, (1<< TOIE1) . Dado que el bit designado como TOIE1 en el registro TIMSK es el número 7, esta entrada es equivalente a la entrada ldi temp,0b10000000; puede escribirla de esta manera, de aquella y de muchas maneras diferentes. Por ejemplo, para iniciar un temporizador con un coeficiente de 1/64, es necesario, como puede verse en el texto del programa, configurar los dos bits menos significativos del registro TCCR1B. Aquí los instalamos en temperatura directamente, pero como estos bits se llaman CS11 y CS10, puedes escribirlo así:

temperatura ldi, (1<< CS11) I (1 << CS10)

o incluso así:

temperatura ldi, (3<< CS10)

Este método de grabación se describe en detalle en la descripción del ensamblador AVR en el sitio web. Atmel .

Detalles

Hay un punto sutil en este programa relacionado con la carga de los registros de conteo del temporizador. Al leer y escribir registros del Temporizador 1 de 16 bits, su contenido puede cambiar en el intervalo entre la lectura o escritura de "mitades" individuales de 8 bits (después de todo, por ejemplo, en este caso el temporizador continúa contando mientras se procesa la interrupción). ). Por lo tanto, los temporizadores AVR de 16 bits proporcionan un mecanismo especial para leer y escribir dichos registros. Al escribir, primero se carga el valor del byte alto, que se coloca automáticamente en un determinado registro de búfer (inaccesible para el programador). Luego, cuando se recibe un comando para escribir el byte bajo, ambos valores se combinan y la escritura se realiza simultáneamente en ambas “mitades” del registro de 16 bits. Por el contrario, al leer, se debe leer primero el byte bajo, el valor del byte alto se fija automáticamente colocándolo en el mismo registro de búfer, y la próxima vez que se lee el byte alto, su valor se recupera de allí. Así, al leer un valor, ambos bytes corresponden al mismo momento.

Cuando decidí desarrollar para Arduino, encontré varios problemas:
  • Seleccionar un modelo de la lista de disponibles
  • Tratando de entender qué necesitaré además de la plataforma misma
  • Instalación y configuración del entorno de desarrollo.
  • Búsqueda y análisis de casos de prueba.
  • "Enfrentamiento" con la pantalla
  • "Enfrentamiento" con el procesador

Para resolver estos problemas, busqué y leí muchas fuentes diferentes y en este artículo intentaré revisar las soluciones que encontré y los métodos para encontrarlas.

Selección de plataforma

Antes de comenzar a programar una pieza de hardware, primero debe comprarla. Y luego me encontré con el primer problema: resultó que había bastantes *duins diferentes. Existe una amplia gama de Arduino y aproximadamente la misma amplia gama de Freeduino y otros análogos. Al final resultó que, no hay gran diferencia en qué tomar exactamente. Es decir, algunos de estos dispositivos son un poco más rápidos, otros un poco más lentos, algunos son más baratos, otros más caros, pero los principios básicos de funcionamiento son prácticamente los mismos. Las diferencias aparecen casi sólo cuando se trabaja con registros de procesador, y luego explicaré con más detalle cómo evitar problemas, si es posible.

Elegí la plataforma Arduino Leonardo como la más asequible y disponible en ese momento en la tienda online donde pedí todo. Se diferencia del resto de la línea en que lleva instalado a bordo un único controlador, que se encarga tanto de trabajar con el puerto USB como de realizar las propias tareas que le asignemos a nuestro dispositivo. Esto tiene sus pros y sus contras, pero no podrás toparte con ellos durante el estudio inicial, así que olvidémonos de ellos por ahora. Resultó que se conecta a la computadora a través de micro-USB, y no USB-B, como parecen ser la mayoría de los demás. Esto me sorprendió un poco, pero también me hizo feliz, porque yo, como propietario de un dispositivo Android moderno, nunca salgo de casa sin este cable.
Sí, casi cualquier pieza de hardware *compatible con Duino se alimenta de varias formas, incluso desde el mismo cable a través del cual se programa. Además, casi todas las placas tienen un LED ubicado directamente en la placa del controlador, lo que le permite comenzar a trabajar con el dispositivo inmediatamente después de la compra, incluso sin tener nada en sus manos excepto un cable compatible.

Gama de tareas

Creo que antes de empezar a escribir algo en una pieza de hardware, es interesante entender qué se puede implementar en ella. Con Arduino puedes implementar casi cualquier cosa. Sistemas de automatización, ideas para una “casa inteligente”, controladores para controlar algo útil, el “cerebro” de los robots... Hay muchas opciones. Y una gama bastante amplia de módulos de expansión, que son extremadamente fáciles de conectar a la placa del controlador, ayuda mucho en esta dirección. La lista de ellos es bastante larga y prometedora, y se buscan en Internet mediante la palabra escudo. De todos estos dispositivos, el más útil me pareció una pantalla LCD con un conjunto básico de botones, sin los cuales, en mi humilde opinión, realizar cualquier tipo de proyecto educativo no tiene ningún interés. La pantalla fue tomada desde aquí, también hay una descripción de la misma y también desde la página anterior hay enlaces al sitio web oficial del fabricante.

Formulación del problema

De alguna manera estoy acostumbrado a que, cuando tengo en mis manos una nueva herramienta, inmediatamente me propongo una tarea moderadamente compleja y absolutamente innecesaria, la resuelvo con valentía, luego dejo el código fuente a un lado y solo entonces me encargo de algo verdaderamente complejo y útil. Ahora tenía a mano una pantalla que se parecía mucho a un componente mío de alguna película de Hollywood, con todos los botones necesarios, con los que tuve que aprender a trabajar, y también tenía muchas ganas de dominar el trabajo con interrupciones (de lo contrario, ¿qué pasa? ¿Qué sentido tiene usar un controlador?) Entonces, lo primero que me vino a la mente fue escribir un reloj. Y como el tamaño de la pantalla lo permitía, también incluía una fecha.

Primeros pasos

Finalmente llegaron todos los componentes comprados y los monté. El conector de la pantalla estaba conectado a la placa del controlador como si fuera nativo, la placa estaba conectada a la computadora... Y luego este artículo me ayudó mucho. No repetiré lo mismo.

Texto oculto

Lo único que diré es que recordando mi juventud (o más bien el primer "proyecto" ensamblado mientras estudiaba radioelectrónica en el Palacio de los Pioneros: un multivibrador con dos LED), encontré 2 LED y corregí el ejemplo dado en el artículo. y empezó a parpadearlos :).

"Segundos pasos"

La siguiente pregunta lógica para mí fue "¿cómo trabajar con una pantalla LCD?" La página oficial del dispositivo me proporcionó amablemente enlaces al archivo, que contenía 2 bibliotecas con maravillosos ejemplos. Ella simplemente no dijo qué hacer al respecto. Resultó que simplemente es necesario descomprimir el contenido en la carpeta de bibliotecas del entorno de desarrollo.

Después de esto, puede abrir el ejemplo GuessTheNumber.pde y cargarlo en el tablero de la misma manera que el ejemplo con el LED parpadeante. Sin embargo, personalmente, después de actualizar el firmware, mi pantalla permaneció brillando uniformemente y sin una sola letra. Después de una breve búsqueda del problema, resultó que era necesario simplemente apretar el único potenciómetro en el tablero de la pantalla con un destornillador para establecer el valor de contraste normal.

El conjunto de comandos utilizados en el ejemplo es, en principio, suficiente para un trabajo sencillo con la pantalla, pero si quieres algo más, puedes abrir el código fuente de las bibliotecas LCDKeypad y LiquidCrystal y ver qué más hay allí.

Arquitectura del programa

La principal tarea de un reloj es contar el tiempo. Y deben hacer esto precisamente. Naturalmente, sin utilizar el mecanismo de interrupción, nadie puede garantizar que el tiempo se calcule con suficiente precisión. Por lo tanto, el cálculo del tiempo definitivamente debería dejarse en sus manos. Todo lo demás se puede colocar en el cuerpo del programa principal. Y tenemos mucho de este "descanso": todo el trabajo con la interfaz. Sería posible hacerlo de otra manera, crear una pila de eventos, creados incluso mediante el mecanismo de manejo de interrupciones, y procesados ​​dentro de la aplicación principal, esto permitiría, por ejemplo, actualizar la pantalla no más de una vez cada medio segundo ( o con solo presionar un botón), pero lo calculé superfluo para una tarea tan simple, ya que además de volver a dibujar la pantalla, el procesador todavía no tiene nada que hacer. Por tanto, durante todo el tiempo libre el programa vuelve a leer el estado de los botones y vuelve a dibujar la pantalla.
Problemas con este enfoque
Cambios periódicos de pantalla
Tenía muchas ganas de hacer dos puntos parpadeantes entre las horas, los minutos y los segundos, de modo que, como en un reloj clásico, se iluminaran durante medio segundo, pero el suelo no. Pero como la pantalla se vuelve a dibujar todo el tiempo, era necesario determinar de alguna manera en qué mitad de segundo dibujarlos y en qué mitad de segundo no. La forma más sencilla era hacer 120 segundos en un minuto y dibujar dos puntos cada segundo impar.
Brillante
Cuando la pantalla se vuelve a dibujar constantemente, se nota el parpadeo. Para evitar que esto suceda, tiene sentido no borrar la pantalla, sino dibujar texto nuevo sobre el anterior. Si el texto en sí no cambia, no habrá ningún parpadeo en la pantalla. Entonces la función de redibujado de tiempo se verá así:
LCD Teclado LCD; void showTime())( lcd.home(); si (hora<10) lcd.print("0"); // Случай разной длины приходится обрабатывать руками lcd.print(hour,DEC); // английские буквы и цифры ОНО пишет само, русские буквы нужно определять программисту if (second %2) lcd.print(" "); else lcd.print(":"); // вот они где используются, мои 120 секунд в минуте if (minute<10) lcd.print("0"); lcd.print(minute,DEC); if (second %2) lcd.print(" "); else lcd.print(":"); if (second<20) lcd.print("0"); lcd.print(second / 2,DEC); lcd.print(" "); lcd.setCursor(0,1); // переходим в координаты x=0, y=1 то есть в начало второй строки lcd.print(" "); lcd.print(day,DEC); lcd.print(months); // месяцы мне было приятнее нумеровать от 1 до 12, а массив с названиями от 0 до 11 lcd.print(year,DEC); }
Trabajar con botones
La situación es similar con los botones. El botón presionado aparece como presionado durante cada ejecución del programa, por lo que una pulsación se puede procesar cualquier número de veces. Tenemos que obligar al programa a esperar las “flexiones” por separado. Comencemos el programa principal así:
int libra=0; // la variable almacena el valor antiguo del botón void loop())( // programa principal int cb,rb; // define 2 variables, para el botón realmente presionado y para el que el programa considerará presionado cb=rb =lcd.button(); // al principio podemos asumir que son el mismo botón if (rb!=KEYPAD_NONE) showval=1; // la variable indica que mientras se presiona el botón lo que se está configurando no debe parpadear if (cb!=lb) lb= cb; // si el estado del botón ha cambiado, recuerda el nuevo, sino cb=KEYPAD_NONE; // en caso contrario le decimos al programa que todos los botones han estado liberados durante mucho tiempo .

Trabajando con el temporizador

En realidad, todo trabajo con el temporizador consta de dos componentes importantes:
  • Inicializando el mecanismo de interrupción del temporizador en un modo conveniente para nosotros
  • En realidad, interrumpa el manejo.
Inicialización del temporizador
Para poder empezar a recibir las interrupciones que necesitamos, debemos configurar el procesador para que empiece a generarlas. Para hacer esto, necesitamos configurar los registros que necesitamos con los valores requeridos. Qué registros y qué valores se deben configurar, debe buscar en... la hoja de datos del procesador :(. Para ser honesto, realmente esperaba que esta información se pudiera encontrar en la documentación del propio Arduino, pero No, sería demasiado simple. Además, los números de bits de la serie de diferentes procesadores pueden diferir. Y yo mismo me encontré personalmente con el hecho de que un intento de configurar los bits de acuerdo con la hoja de datos en un procesador vecino condujo a resultados desastrosos. Pero sin embargo, no todo es tan triste como podría parecer, porque estos bits también tienen nombres, son más o menos comunes para diferentes procesadores. Por lo tanto, no usaremos valores digitales, solo nombres.

Para empezar recordemos que los microcontroladores AVR cuentan con varios temporizadores. Null se usa para calcular valores de retardo () y cosas así, por lo que no lo usaremos. En consecuencia, utilizamos el primero. Por lo tanto, más adelante en la designación de registros suele haber un 1; para configurar, digamos, un segundo temporizador, es necesario poner un 2 allí.

Toda la inicialización del temporizador debe ocurrir en la rutina setup(). Consiste en colocar valores en 4 registros, TCCR1A, TCCR1B, TIMSK1, OCR1A. Los 2 primeros se denominan "Registros de control A y B del temporizador-contador 1". El tercero es el "Registro de máscara de interrupción del temporizador/contador 1" y el último es el "Registro de comparación A del contador 1".

Los comandos para configurar bits se suelen utilizar de la siguiente manera (por supuesto, hay muchas opciones, pero estas son las más utilizadas):
MORDEDURA |= (1<< POSITION)
es decir, presionamos "1" en el bit de POSICIÓN de derecha a izquierda y realizamos un "o" lógico entre el byte de destino y el recibido. Cuando se enciende el controlador, los valores de todos estos registros contienen 0, por lo que simplemente nos olvidamos de los ceros. Entonces después de ejecutar el siguiente código

A=0; A |= (1<< 3)

El valor de A pasará a ser 8.

Hay muchas opciones para configurar el temporizador, pero debemos lograr lo siguiente desde el temporizador:

  • Para que el temporizador cambie al modo de funcionamiento CTC (es decir, al modo de conteo con reinicio después de una coincidencia, "Borrar temporizador al comparar una coincidencia"), a juzgar por la hoja de datos, esto se logra configurando los bits WGM12:0 = 2, que en sí mismo significa poner los bits del segundo a cero al valor "2", es decir, "010", comando TCCR1B |= (1<< WGM12) ;
  • Dado que 16 MHz (es decir, esta es la frecuencia del resonador de cuarzo en mi placa) es mucho, elija el divisor máximo posible, 1024 (es decir, solo cada 1024 ciclos de reloj llegará a nuestro contador), CS12: 0 = 5
  • Asegúrese de que la interrupción se produzca cuando el registro A coincida, para este contador TIMSK1 |= (1<< OCIE1A)
  • Especifica cuándo se alcanza un valor específico para activar el procesamiento de la interrupción; este valor se coloca en el mismo registro A del contador 1 (su nombre completo es OCR1A), coincidiendo la interrupción con la que habilitamos en el párrafo anterior.

¿Cómo calcular cuánto tiempo necesitamos para realizar los cálculos? - Es fácil, si la frecuencia de reloj del resonador de cuarzo es de 16 MHz, entonces cuando el contador alcance el valor de 16000, pasaría un segundo si el coeficiente de división fuera 1. Como es 1024, entonces obtenemos 16000000/1024 = 15625 por segundo. Y todo estaría bien, pero necesitamos obtener valores cada medio segundo y 15625 no es divisible por 2. Esto significa que cometimos un error antes y tendremos que tomar un factor de división menor. Y el siguiente más pequeño que tenemos es 256, lo que da 62500 ticks por segundo o 31250 en medio segundo. Nuestro contador es de 16 bits, por lo que puede contar hasta 65536. En otras palabras, nos basta tanto para medio segundo como para un segundo. Entramos en la hoja de datos, luego en el código fuente y lo corregimos a CS12:0=4, y luego OCR1A = 31249; (Según tengo entendido, un ciclo se gasta en restablecer o en otro lugar, por lo que hay consejos para restablecer otro a partir del número recibido).

Manejo de interrupciones
La sintaxis de la función del servicio de interrupción ha cambiado ligeramente; ahora se parece al ejemplo siguiente. Así que no se sorprenda si ve una descripción ligeramente diferente del nombre de la función en alguna parte.

En realidad, ahora consta de la palabra reservada ISR y una indicación entre paréntesis de la interrupción específica que procesa esta función. Pero como puedes ver, esta función no tiene nada de fantástico en su interior. Incluso el RETI obligatorio, como puede ver, el compilador lo inserta automáticamente.

ISR(TIMER1_COMPA_vect) ( digitalWrite(LEDPIN, !digitalRead(LEDPIN)); // LEDPIN=13. Esta línea hace parpadear el LED en la placa. Conveniente y genial :) segundo++; if ((segundo %2) && lastshowval) ( // esta y las siguientes 7 líneas son necesarias solo para lastshowval = 0; // para que puedas lograr este efecto divertido, como en un reloj de hardware, showval = 0; // cuando está en modo de configuración, digamos minutos, el valor del parámetro configurado parpadea) if (!(segundo %2) && !lastshowval)( // solo cuando se sueltan los botones, y mientras se presionan los botones, simplemente se ilumina. lastshowval = 1; showval = 1; ) if ( second>=120) ( // otra vez mis 120 segundos en un minuto. Bueno, ¿quién lo tiene fácil ahora? second-=120; minuto++; if (minuto>=60)( minuto-=60; hora++; if (hora>=24) ( hora-=24; día++; if (díagrande(día,mes,año) // devuelve verdadero si el valor del día // es mayor que el máximo posible para este mes de este año.) ( día=1; mes++; if (mes>12) ( mes = 1; año++; ) ) ) ) ) )

Espero que este artículo sea útil para alguien, porque hay bastantes instrucciones detalladas sobre cómo trabajar con interrupciones del temporizador en ruso.

Descubrimos el contador de iteraciones del bucle principal y descubrimos que es completamente inadecuado para lecturas de tiempo precisas: la velocidad de obturación flota y es difícil contarla. ¿Qué hacer?

Obviamente, necesitamos algún tipo de contador externo que funcione independientemente del funcionamiento del procesador, y que el procesador pueda ver en cualquier momento lo que está funcionando en él. O para que el contador genere eventos de desbordamiento o desbordamiento insuficiente: levante la bandera o genere una interrupción. Y el porcentaje lo olerá y lo procesará.

Y no existe tal contador, ni siquiera uno: estos son temporizadores periféricos. En un AVR puede haber varios de ellos, e incluso con diferentes profundidades de bits. ATmega16 tiene tres, ATmega128 tiene cuatro. Y en los nuevos MK de la serie AVR puede haber aún más, no lo reconocí.

Además, un temporizador puede ser algo más que un simple contador: un temporizador es uno de los dispositivos periféricos más sofisticados (en términos de funciones alternativas).

¿Qué pueden hacer los cronómetros?

  • Marque a diferentes velocidades, contando el tiempo.
  • Cuente los pulsos entrantes desde el exterior (modo contador)
  • Tic de cuarzo externo a 32768Hz
  • Genera varios tipos de señal PWM.
  • Emitir interrupciones (media docena de eventos diferentes) y establecer banderas

Diferentes temporizadores tienen diferentes funcionalidades y diferentes profundidades de bits. Consulte la hoja de datos para obtener más detalles.

Fuente de tic del temporizador
El temporizador/contador (en adelante lo llamaré T/C) cuenta los pulsos de reloj del generador de reloj incorporado o de la entrada de conteo.

Mire atentamente la distribución de pines de las patas ATmega16, ¿ve las patas T1 y T0 allí?

Entonces estas son las entradas de conteo Temporizador 0 y Temporizador 1. Con los ajustes apropiados, el T/S contará ya sea el borde anterior (borde de 0-1) o el borde posterior (borde de 1-0) del impulsos que llegan a estas entradas.

Lo principal es que la frecuencia de los pulsos entrantes no exceda la frecuencia de reloj del procesador, de lo contrario no tendrá tiempo de procesar los pulsos.

Además, T/C2 es capaz de funcionar en modo asíncrono. Es decir, el T/S no cuenta los pulsos del procesador, ni los pulsos que llegan a las piernas, sino los pulsos de su propio oscilador, alimentado por un cuarzo separado. Para ello, el T/C2 dispone de entradas TOSC1 y TOSC2, a las que se puede conectar un resonador de cuarzo.

¿Por qué es esto necesario? Sí, al menos organiza un reloj en tiempo real. Les colgué un reloj de cuarzo a 32768 Hz y cuento el tiempo: se producirán 128 desbordamientos por segundo (ya que T/C2 es de ocho bits). Entonces un desbordamiento es 1/128 de segundo. Además, el temporizador no se detiene mientras se procesa la interrupción de desbordamiento; también continúa contando. ¡Así que el reloj es muy sencillo!

Predivisor
Si el temporizador cuenta los impulsos de un generador de reloj o de uno interno, aún pueden pasar a través de un preescalador.

Es decir, incluso antes de ingresar al registro de conteo, se dividirá la frecuencia del pulso. Puedes dividir entre 8, 32, 64, 128, 256, 1024. Entonces, si colocas un reloj de cuarzo en T/C2 y lo pasas por un preescalador en 128, el cronómetro funcionará a una velocidad de un tic por segundo.

¡Cómodo! También es conveniente usar un preescalador cuando solo necesitas obtener un intervalo grande, y la única fuente de ticks es el generador de reloj del procesador a 8 MHz, te cansarás de contar estos megahercios, pero si lo pasas por el preescalador , en 1024, entonces todo es mucho más feliz.

Pero aquí hay una peculiaridad, el hecho es que si iniciamos el T/S con algún tipo de preescalador brutal, por ejemplo en 1024, entonces el primer tic en el registro de conteo no necesariamente llegará después de 1024 pulsos.

Depende del estado en el que se encuentre el prescaler, ¿y si cuando lo encendiéramos ya hubiera contado casi 1024? Esto significa que habrá un tic inmediatamente. El preescalador funciona todo el tiempo, independientemente de si el temporizador está encendido o no.

Por lo tanto, los preescaladores pueden y deben restablecerse. También debe tener en cuenta el hecho de que el preescalador es el mismo para todos los contadores, por lo que al restablecerlo debe tener en cuenta el hecho de que otro temporizador perderá tiempo hasta el siguiente tic y puede salir mal exactamente en Por aquí.

Por ejemplo, el primer temporizador funciona en el pin 1:64 y el segundo en el pin 1:1024 del preescalador. El segundo casi alcanzó 1024 en el preescalador y ahora debería haber un tic del temporizador, pero luego reiniciaste el preescalador para iniciar el primer temporizador exactamente desde cero. ¿Lo que sucederá? Así es, el segundo divisor se pondrá inmediatamente a 0 (el preescalador es el mismo, tiene un registro) y el segundo temporizador tendrá que esperar otros 1024 ciclos de reloj para obtener el impulso deseado.

Y si reinicia el preescalador en el bucle, en beneficio del primer temporizador, más de una vez cada 1024 ciclos de reloj, entonces el segundo temporizador nunca funcionará y usted se golpeará la cabeza contra la mesa, tratando de entender por qué. Su segundo temporizador no funciona, aunque debería hacerlo.

Para restablecer los preescaladores, simplemente escriba el bit PSR10 en el registro SFIOR. El bit PSR10 se restablecerá automáticamente en el siguiente ciclo de reloj.

registro de cuenta
Todo el resultado del tormento descrito anteriormente se acumula en el registro de conteo TCNTx, donde x es el número del temporizador. puede ser de ocho o dieciséis bits, en cuyo caso consta de dos registros TCNTxH y TCNTxL: los bytes alto y bajo, respectivamente.

Y aquí hay un problema, si necesita poner un número en un registro de ocho bits, entonces no hay problemas con OUT TCNT0, Rx y sin clavos, entonces con los registros de dos bytes tendrá que jugar.

Y el punto es que el temporizador cuenta independientemente del procesador, por lo que podemos poner un byte primero, comenzará a contar, luego el segundo, y el recálculo comenzará teniendo en cuenta el segundo byte.

¿Sientes que me estoy llevando bien? ¡Aquí! El temporizador es un dispositivo preciso, por lo que sus registros de conteo deben cargarse al mismo tiempo. ¿Pero cómo? Y los ingenieros de Atmel resolvieron el problema de forma sencilla:
El registro alto (TCNTxH) se escribe primero en el registro TEMP. Este registro es puramente oficial y de ningún modo es accesible para nosotros.

Con lo que terminamos: escribimos el byte alto en el registro TEMP (para nosotros esto es un TCNTxH increíble) y luego escribimos el byte bajo. En este momento, el valor que registramos anteriormente se ingresa en el TCNTxH real. Es decir, ¡dos bytes, alto y bajo, se escriben simultáneamente! ¡No puedes cambiar el orden! La única forma

Se parece a esto:

CLI; Prohibimos las interrupciones, ¡sin falta! SALIDA TCNT1H,R16 ; El byte más significativo se escribió primero en TEMP OUT TCNT1L,R17; ¡Y ahora me he inscrito en las clases senior y junior! SEI; Habilitar interrupciones

¿Por qué desactivar las interrupciones? Sí, para que después de escribir el primer byte, el programa no se ejecute accidentalmente sin interrupción y luego alguien viole nuestro temporizador. Entonces en sus registros no estará lo que enviamos aquí (o en la interrupción), sino qué carajo. ¡Así que intenta detectar ese error más tarde! Pero puede surgir en el momento más inoportuno, pero no lo captarás, porque una interrupción es casi una variable aleatoria. Por lo tanto, es necesario abordar esos momentos de inmediato.

Todo se lee de la misma forma, sólo que en orden inverso. Primero, el byte bajo (mientras el alto se inserta en TEMP), luego el alto. Esto garantiza que estamos contando exactamente el byte que estaba actualmente en el registro de conteo, y no el que se estaba ejecutando mientras lo estábamos seleccionando del registro de conteo.

Registros de control
No describiré todas las funciones de los temporizadores, de lo contrario resultará un tratado abrumador. Es mejor hablar de la principal: la de conteo, y todo tipo de PWM y otros generadores estarán en otro artículo. Así que tenga paciencia o lea la hoja de datos, también es útil.

Entonces el registro principal es TCCRx
Para T/C0 y T/C2, estos son TCCR0 y TCCR2, respectivamente, y para T/C1, esto es TCCR1B.

Por ahora sólo nos interesan los tres primeros bits de este registro:
CSx2.. CSx0, reemplace x con el número del temporizador.
Son responsables de configurar el preescalador y la fuente del reloj.

Los diferentes temporizadores son ligeramente diferentes, por lo que describiré los bits CS02..CS00 solo para el temporizador 0.

  • 000 - temporizador detenido
  • 001 — el preescalador es igual a 1, es decir, apagado. El temporizador cuenta los pulsos del reloj.
  • 010 - el preescalador es 8, la frecuencia del reloj se divide por 8
  • 011 - el preescalador es 64, la frecuencia del reloj se divide por 64
  • 100 - el preescalador es 256, la frecuencia del reloj se divide por 256
  • 101 - el preescalador es 1024, la frecuencia del reloj se divide por 1024
  • 110 - los pulsos de reloj provienen del pin T0 en la transición de 1 a 0
  • 111 - los pulsos de reloj provienen del pin T0 en la transición de 0 a 1

Interrumpe
Cada evento de hardware tiene una interrupción y el temporizador no es una excepción. Tan pronto como ocurre un desbordamiento o algún otro evento curioso, inmediatamente aparece una interrupción.

Los registros TIMSK y TIFR son responsables de las interrupciones de los temporizadores. Y los AVR más fríos, como ATMega128, también tienen ETIFR y ETIMSK, una especie de continuación, ya que habrá más temporizadores allí.

TIMSK es un registro de máscara. Es decir, los bits que contiene permiten interrupciones localmente. Si el bit está establecido, la interrupción específica está habilitada. Si el bit es cero, entonces esta interrupción se cubre con una cuenca. Por defecto, todos los bits son cero.

Por el momento sólo nos interesan las interrupciones por desbordamiento. Los bits son responsables de ellos.

  • TOIE0: permiso para interrumpir el desbordamiento del temporizador 0
  • TOIE1: permiso para interrumpir el desbordamiento del temporizador 1
  • TOIE2: permiso para interrumpir el desbordamiento del temporizador 2

Hablaremos de otras funciones y de las interrupciones del temporizador más adelante, cuando analicemos el PWM.

El registro TIFR es directamente un registro de bandera. Cuando se activa alguna interrupción, aparece una bandera que indica que tenemos una interrupción. Este indicador se restablece mediante hardware cuando el programa abandona el vector. Si las interrupciones están deshabilitadas, entonces la bandera permanecerá allí hasta que se habiliten las interrupciones y el programa pase a la interrupción.

Para evitar que esto suceda, la bandera se puede restablecer manualmente. ¡Para hacer esto, debe escribir 1 en el registro TIFR!

Ahora vamos a follar
Bueno, rediseñemos el programa para que funcione con un temporizador. Introduzcamos un temporizador de programa. El organillo seguirá así, déjalo funcionar. Y añadiremos una segunda variable, también de cuatro bytes:

ORG $010 RETI ; (TIMER1 OVF) Temporizador/Contador1 Desbordamiento .ORG $012 RJMP Timer0_OV ; (TIMER0 OVF) Temporizador/Contador0 Desbordamiento .ORG $014 RETI ; (SPI,STC) Transferencia en serie completa

Agreguemos un controlador de interrupciones para el desbordamiento del temporizador 0 a la sección Interrupción. Dado que nuestra macro de ticking funciona activamente con registros y corrompe indicadores, primero debemos guardar todo esto en la pila:

Por cierto, creemos otra macro que inserte el registro del indicador SREG en la pila y una segunda que lo recupere de allí.

1 2 3 4 5 6 7 8 9 10 11 12 .MACRO PUSHF EMPUJE R16 ENTRADA R16,SREG EMPUJE R16 .ENDM .MACRO POPF POP R16 SALIDA SREG,R16 POP R16 .ENDM

MACRO PUSHF EMPUJAR R16 ENTRADA R16,SREG EMPUJAR R16 .ENDM .MACRO POPF POP R16 SALIDA SREG,R16 POP R16 .ENDM

Como efecto secundario, también conserva R16, recuerda que :)

1 2 3 4 5 6 7 8 9 10 11 12 13 Timer0_OV: PUSHF PUSH R17 PUSH R18 PUSH R19 INCM TCNT POP R19 POP R18 POP R17 POPF RETI

Timer0_OV: PUSHF PUSH R17 PUSH R18 PUSH R19 INCM TCNT POP R19 POP R18 POP R17 POPF RETI

Ahora inicialice el temporizador. Agréguelo a la sección Inicio de hardware interno.

; Inicio de hardware interno ======================================== SETB DDRD,4,R16; DDRD.4 = 1 AJUSTE DDRD,5,R16 ; DDRD.5 = 1 SETB DDRD,7,R16 ; DDRD.7 = 1 AJUSTE PUERTO B,6,R16 ; Salida PD6 a entrada pull-up CLRB DDRD,6,R16; Para leer el botón SETB TIMSK,TOIE0,R16 ; Habilitar interrupción del temporizador OUTI TCCR0,1<

Todo lo que queda es reescribir nuestro bloque de comparación y recalcular el número. Ahora todo es sencillo, un tick en una barra. Sin ningún problema con diferentes longitudes de código. Durante un segundo a 8 MHz se deben realizar 8 millones de ticks. En hexadecimal este es 7A 12 00, tomando en cuenta que el byte bajo es TCNT0, luego queda 7A 12 para nuestro contador y también los dos bytes más altos 00 00, no es necesario verificarlos. No es necesario usar mascarilla; de todos modos, restableceremos el cronómetro más tarde.

Sólo hay un problema: el byte bajo, el que está en el temporizador. Cumple todos los requisitos y será casi imposible comprobar su cumplimiento. Porque la más mínima discrepancia y la condición de comparación aparecerán en NoMatch, pero adivinarla para que la verificación de su valor coincida con este paso en particular... Es más fácil sacar una aguja de un pajar en el primer intento al azar.

Por lo tanto, la precisión en este caso es limitada: es necesario tener tiempo para comprobar el valor antes de que abandone el rango. En este caso, el rango será, por simplicidad, 255: el valor del byte bajo, el que está en el temporizador.

Entonces nuestro segundo cuenta con una precisión de 8.000.000 más menos 256 ciclos. El error no es grande, sólo el 0,003%.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 ; Principal ==================================================== ================ ======== Principal: SBIS PIND,6 ; Si se presiona el botón - transición RJMP BT_Push SETB PORTD,5 ; Encendamos el LED2 CLRB PORTD,4 ; Apague el LED1 Siguiente: LDS R16,TCNT; Cargar números en los registros LDS R17,TCNT+1 CPI R16.0x12; Comparemos byte por byte. Primer byte BRCS NoMatch; Si es menos, significa que no acertó. IPC R17.0x7A ; Segundo byte BRCS NoMatch; Si es menos, significa que no acertó. ; Si coincide, realizamos la acción Coincidir: INVB PORTD,7,R16,R17; LED3 invertido; Ahora necesitamos restablecer el contador; de lo contrario, durante la misma iteración del bucle principal; llegaremos aquí más de una vez: el temporizador no tendrá tiempo de alcanzar los 255 valores; de modo que el número en los dos primeros bytes del contador cambie y se active la condición. ; Por supuesto, puedes evitar esto con una bandera adicional, pero es más fácil restablecer el contador :) CLR R16; Necesitamos cero CLI; Acceso a una variable multibyte; simultáneamente de interrupción y fondo; Se necesita acceso atómico. Deshabilitar interrupciones OUTU TCNT0,R16; Cero al registro del contador del temporizador STS TCNT, R16; Cero en el primer byte del contador en RAM STS TCNT+1,R16; Cero en el segundo byte del contador en RAM STS TCNT+2,R16; Cero en el tercer byte del contador en RAM STS TCNT+3,R16; Cero en el primer byte del contador en RAM SEI; Habilitemos las interrupciones nuevamente. ; Si no coincide, no lo hacemos :) NoMatch: NOP INCM CCNT ; El contador de ciclos avanza; Incluso si no se usa. JMP BT_Push principal: SETB PORTD,4; Encendamos el LED1 CLRB PORTD,5; Apague el LED2 RJMP Siguiente; Fin principal =================================================== ================= =====

; Principal ==================================================== ================ ======== Principal: SBIS PIND,6 ; Si se presiona el botón - transición RJMP BT_Push SETB PORTD,5 ; Encendamos el LED2 CLRB PORTD,4 ; Apague el LED1 Siguiente: LDS R16,TCNT; Cargar números en los registros LDS R17,TCNT+1 CPI R16.0x12; Comparemos byte por byte. Primer byte BRCS NoMatch; Si es menos, significa que no acertó. IPC R17.0x7A ; Segundo byte BRCS NoMatch; Si es menos, significa que no acertó. ; Si coincide, realizamos la acción Coincidir: INVB PORTD,7,R16,R17; LED3 invertido; Ahora necesitamos restablecer el contador; de lo contrario, durante la misma iteración del bucle principal; llegaremos aquí más de una vez: el temporizador no tendrá tiempo de alcanzar los 255 valores; de modo que el número en los dos primeros bytes del contador cambie y se active la condición. ; Por supuesto, puedes evitar esto con una bandera adicional, pero es más fácil restablecer el contador :) CLR R16; Necesitamos cero CLI; Acceso a una variable multibyte; simultáneamente de interrupción y fondo; Se necesita acceso atómico. Deshabilitar interrupciones OUTU TCNT0,R16; Cero al registro del contador del temporizador STS TCNT, R16; Cero en el primer byte del contador en RAM STS TCNT+1,R16; Cero en el segundo byte del contador en RAM STS TCNT+2,R16; Cero en el tercer byte del contador en RAM STS TCNT+3,R16; Cero en el primer byte del contador en RAM SEI; Habilitemos las interrupciones nuevamente. ; Si no coincide, no lo hacemos :) NoMatch: NOP INCM CCNT ; El contador de ciclos avanza; Incluso si no se usa. JMP BT_Push principal: SETB PORTD,4; Encendamos el LED1 CLRB PORTD,5; Apague el LED2 RJMP Siguiente; Fin principal =================================================== ================= =====

Así es como se ve en acción.

Y si necesitamos hacer parpadear un segundo diodo con un período diferente, entonces podemos poner con seguridad otra variable en el programa, y ​​en el controlador de interrupciones del temporizador podemos incrementar dos variables a la vez. Comprobándolos uno por uno en el bucle principal del programa.

Puedes optimizar un poco más el proceso de verificación. Hazlo mas rapido.

Solo necesita hacer que la cuenta no suba, sino que baje. Aquellos. Cargamos un número en una variable y comenzamos a disminuirlo en la interrupción. Y allí, en el controlador, lo comprobamos en cero. Si es cero, establezca una bandera en la memoria. Y nuestro programa en segundo plano detecta esta bandera y lanza la acción, restableciendo simultáneamente la velocidad de obturación.

¿Pero qué pasa si necesitas ser más preciso? Bueno, solo hay una opción: usar el procesamiento de eventos directamente en el controlador de interrupciones y ajustar el valor en TCNT:TCNT0 cada vez para que la interrupción ocurra exactamente en el momento correcto.

Las interrupciones permiten a los microcontroladores responder a eventos sin tener que verificar constantemente las condiciones para determinar cuándo han ocurrido cambios importantes. Además de la capacidad de conectar fuentes de interrupción a algunos pines, también puedes usar interrupciones generadas por un temporizador.

Interrupciones de hardware

Para demostrar el uso de interrupciones, volvamos a las entradas digitales. A menudo, para determinar el momento de algún evento de entrada (por ejemplo, presionar un botón), se utiliza el siguiente código:

si (digitalRead(inputPin) == BAJO)

//Realiza algunas acciones

Este código verifica constantemente el nivel de voltaje en el pin de entrada, y cuando digitalRead devuelve BAJO, hace algo, indicado por el comentario // Hacer algo. Esta es una solución completamente viable, pero ¿qué pasa si hay muchas otras operaciones que deben realizarse dentro de la función de bucle? Todas estas operaciones llevan tiempo, por lo que es posible omitir una pulsación breve de un botón mientras el procesador está ocupado con otra cosa. De hecho, es casi imposible pasar por alto el hecho de que se presionó el botón, porque según los estándares del microcontrolador permanece presionado durante mucho tiempo.

Pero ¿qué pasa con los breves impulsos del sensor, que pueden durar millonésimas de segundo? Para recibir tales eventos, debe usar interrupciones, definiendo funciones que se llamarán en estos eventos, independientemente de lo que esté haciendo el microcontrolador. Estas interrupciones se denominan interrupciones de hardware(interrupciones de hardware).

En Arduino Uno, sólo dos pines están asociados con interrupciones de hardware, por lo que se utilizan con moderación. Leonardo tiene cinco de estos pines, las placas más grandes como la Mega2560 tienen muchos más y todos los pines de Due tienen capacidad de interrupción.

A continuación se explica cómo funcionan las interrupciones de hardware. Para probar el ejemplo que se muestra, necesitará una placa adicional, un botón, una resistencia de 1k ohmios y algunos cables de puente.

En la Fig. La Figura 3.1 muestra el circuito ensamblado. A través de la resistencia, el voltaje ALTO se aplica al pin D2 hasta que se presiona el botón, momento en el cual el pin D2 se conectará a tierra y el nivel de voltaje caerá a BAJO.

Sube el siguiente boceto a tu placa Arduino:

// bosquejo 03_01_interrupts

intledPin = 13;

pinMode(ledPin, SALIDA);

cosas vacíasOcurridas()

escritura digital (ledPin, ALTA);

Arroz. 3.1. Diagrama de circuito para pruebas de interrupción.

Además de configurar el pin LED para que funcione como una salida digital, la función de configuración utiliza otra línea para asociar la función con una interrupción. Ahora esta función se llamará automáticamente en respuesta a cada interrupción. Echemos un vistazo más de cerca a esta línea, porque los argumentos de la función llamada aquí parecen un poco inusuales:

adjuntarInterrupción (0, cosas sucedidas, FALLING);

El primer argumento, 0, es el número de interrupción. Sería más claro si el número de interrupción coincidiera con el número de pin, pero no es así. En Arduino Uno, la interrupción 0 está asociada con el pin D2 y la interrupción 1 está asociada con el pin D3. La situación se vuelve aún más confusa por el hecho de que en otros modelos de Arduino estas interrupciones están asociadas con diferentes pines y, además, en Arduino Due es necesario especificar el número de pin. En la placa Arduino Due, todos los pines están asociados con interrupciones.

Volveré a este problema más adelante, pero por ahora pasemos al segundo argumento. Este argumento, cosas que sucedieron, representa el nombre de la función que se debe llamar para manejar la interrupción. Esta función se define con más detalle en el boceto. Tales funciones se llaman interrumpir rutinas(Rutina de servicio de interrupción, ISR), existen requisitos especiales. No pueden tener parámetros y no deben devolver nada. Esto tiene cierto sentido: aunque se llaman en diferentes lugares del boceto, no hay una sola línea de código que llame directamente al ISR, por lo que no hay forma de pasarles parámetros u obtener un valor de retorno.

El último parámetro de la función, adjuntarInterrupción, es una constante, en este caso FALLING. Significa que la rutina de interrupción se llamará solo cuando el voltaje en el pin D2 cambie del nivel ALTO al nivel BAJO (es decir, cuando cae), lo que ocurre cuando se presiona el botón.

Observe que no hay ningún código en la función de bucle. En general, esta función puede contener código que se ejecuta hasta que ocurre una interrupción. La rutina de interrupción en sí simplemente enciende el LED L.

Cuando experimentas, después de reiniciar el Arduino, el LED L debería apagarse. Y después de presionar el botón, se iluminará inmediatamente y permanecerá encendido hasta el próximo reinicio.

Después de experimentar, intente cambiar el último argumento en la llamada adjuntaInterrupt a RISING y cargue el boceto modificado. Después de reiniciar el Arduino, el LED debe permanecer apagado, porque el voltaje en el pin, aunque está en el nivel ALTO, se ha mantenido en este nivel desde el reinicio. Hasta este momento, el voltaje en el contacto no cayó al nivel BAJO y luego subió (subiendo) al nivel ALTO.

Una vez que presione y mantenga presionado el botón, el LED debe permanecer apagado hasta que lo suelte. Soltar el botón provocará una interrupción asociada con el pin D2 porque mientras se mantenía presionado el botón, el nivel de voltaje en el pin era BAJO y, cuando se soltaba, subió a ALTO.

Si durante la prueba resulta que lo que le está sucediendo no se corresponde con la descripción dada anteriormente, lo más probable es que se deba al efecto de rebote de los contactos en el botón. Este efecto se debe a que el botón no proporciona una transición clara entre los estados “encendido” y “apagado”; en cambio, en el momento de presionarlo, hay una transición repetida entre estos estados hasta que se pasa al estado “encendido”. fijado. Intente presionar el botón con más fuerza; esto debería ayudar a obtener una transición clara entre estados sin el efecto de rebote.

Otra forma de probar este boceto es presionar y mantener presionado el botón mientras presiona y suelta el botón Restablecer en la placa Arduino. Luego, cuando comience el boceto, suelte el botón en la placa y el LED L se iluminará.

Pines habilitados para interrupción

Volvamos ahora al problema de nombrar las interrupciones. En mesa 3.1 enumera los modelos más comunes de placas Arduino y muestra la correspondencia de los números de interrupciones y contactos en ellos.

Tabla 3.1. Pines habilitados para interrupción en diferentes modelos de Arduino

Modelo número de interrupción Notas
0 1 2 3 4 5
Uno D2 D3 - - - -
leonardo D3 D2 D0 D1 D7 - De hecho, en comparación con Uno, las dos primeras interrupciones están asignadas a pines diferentes
Mega2560 D2 D3 D21 D20 D19 D18
Pendiente - - - - - - En lugar de números de interrupción, a la función adjuntarinterrupción se le deben pasar números de PIN

Cambiar los pines de las dos primeras interrupciones en Uno y Leonardo crea una trampa en la que es fácil caer. En el modelo Due, en lugar de números de interrupción, a la función adjuntarInterrupción se le deben pasar números de pin, lo que parece más lógico.

Modos de interrupción

Los modos de interrupción RISING (flanco ascendente) y FALLING (flanco descendente) utilizados en el ejemplo anterior son los que se utilizan con mayor frecuencia en la práctica. Sin embargo, existen varios otros modos. Estos modos se enumeran y describen en la tabla. 3.2.

Tabla 3.2. Modos de interrupción

Modo Acción Descripción
BAJO La interrupción se genera a un nivel de voltaje BAJO En este modo, la rutina de interrupción se llamará continuamente mientras el pin permanezca bajo.
CRECIENTE Se genera una interrupción cuando el voltaje cae positivo, de BAJO a ALTO. -
DESCENDENTE Se genera una interrupción ante una caída de voltaje negativa, de ALTO a BAJO. -
ALTO La interrupción se genera a un nivel de voltaje ALTO Este modo sólo es compatible con el modelo Arduino Due y, al igual que el modo BAJO, rara vez se utiliza en la práctica.

Habilitar impedancia interna

El circuito del ejemplo anterior utilizó una resistencia pull-up externa. Sin embargo, en la práctica, las señales que causan interrupciones a menudo provienen de las salidas digitales de los sensores, en cuyo caso no es necesario utilizar una resistencia pull-up.

Pero si el papel del sensor lo desempeña un botón conectado exactamente de la misma manera que la placa de pruebas en la Fig. 3.1, es posible deshacerse de la resistencia activando la resistencia interna "pull-up" con un valor nominal de aproximadamente 40 kOhm. Para hacer esto, debe configurar explícitamente el modo INPUT_PULLUP para el pin relacionado con la interrupción, como se muestra en la línea en negrita:

pinMode(ledPin, SALIDA);

modopin(2, INPUT_PULLUP);

adjuntarInterrupción (0, cosas sucedidas, FALLING);

interrumpir rutinas

A veces puede parecer que la capacidad de manejar interrupciones mientras se ejecuta una función de bucle proporciona una manera fácil de manejar eventos como pulsaciones de teclas. Pero en realidad existen restricciones muy estrictas sobre lo que las rutinas de interrupción pueden y no pueden hacer.

Las rutinas de interrupción deben ser lo más breves y rápidas posible. Si ocurre otra interrupción mientras se ejecuta la rutina de interrupción, la rutina no se interrumpirá y la señal resultante simplemente se ignorará. Esto significa, por ejemplo, que si se utilizan interrupciones para medir la frecuencia, es posible que se obtenga un valor incorrecto.

Además, mientras se ejecuta la rutina de interrupción, el código de la función de bucle está inactivo.

Las interrupciones se desactivan automáticamente durante el procesamiento. Esta solución evita la confusión entre subrutinas que se interrumpen entre sí, pero tiene efectos secundarios no deseados. La función de retardo utiliza temporizadores e interrupciones, por lo que no funcionará en rutinas de interrupción. Lo mismo se aplica a la función milis. Intentar usar milisegundos para obtener el número de milisegundos que han pasado desde que se reinició la placa por última vez para realizar un retraso de esta manera no tendrá éxito, ya que devolverá el mismo valor hasta que se complete la rutina de interrupción. Sin embargo, puede utilizar la función delayMicrosegundos, que no utiliza interrupciones.

Las interrupciones también se utilizan en las comunicaciones serie, así que no intente utilizar Serial.print o las funciones de lectura del puerto serie. Sin embargo, puede intentarlo y, a veces, incluso funcionarán, pero no espere una alta confiabilidad de dicha conexión.

Variables operativas

Dado que la rutina de interrupción no puede tener parámetros y no puede devolver nada, se necesita alguna forma de pasar información entre ella y el resto del programa. Normalmente, esto se hace utilizando variables globales, como se muestra en el siguiente ejemplo:

// bosquejo 03_02_interrupt_flash

intledPin = 13;

flashFast booleano volátil = falso;

pinMode(ledPin, SALIDA);

adjuntarInterrupción (0, cosas sucedidas, FALLING);

período int = 1000;

si (flashFast) período = 100;

escritura digital (ledPin, ALTA);

escritura digital (ledPin, BAJO);

cosas vacíasOcurridas()

flashRápido = ! flash rápido;

En este boceto, la función de bucle utiliza la variable global flashFast para determinar el período de retraso. La rutina de procesamiento cambia el valor de esta variable entre verdadero y falso.

Tenga en cuenta que la declaración de la variable flashFast incluye la palabra volátil. Puede desarrollar exitosamente un boceto sin el especificador volátil, pero es absolutamente necesario porque sin el especificador volátil, el compilador de C puede generar código de máquina que almacena en caché el valor de la variable en un registro para mejorar el rendimiento. Si, como en este caso, se interrumpe el código de almacenamiento en caché, es posible que no note el cambio en el valor de la variable.

En conclusión sobre las rutinas de interrupción.

Cuando escriba rutinas de interrupción, recuerde las siguientes reglas.

Las subrutinas deben actuar rápidamente.

Para transferir datos entre la rutina de interrupción y el resto del programa, se deben utilizar variables declaradas con el especificador volátil.

No uses retraso, pero puedes usar retrasoMicrosegundos.

No espere comunicaciones altamente confiables a través de puertos serie.

No espere que cambie el valor devuelto por la función millis.

Activar y desactivar interrupciones

De forma predeterminada, las interrupciones están habilitadas en los bocetos y, como se mencionó anteriormente, se deshabilitan automáticamente mientras se ejecuta la rutina de interrupción. Sin embargo, es posible deshabilitar y habilitar explícitamente las interrupciones en el código del programa llamando a las funciones noInterrupts e interrupts. Estas funciones no tienen parámetros, y la primera de ellas desactiva las interrupciones y la segunda las habilita.

Es posible que se necesite un control explícito para garantizar que un fragmento de código, como uno que genera una secuencia de datos o genera una secuencia de pulsos y se sincroniza con precisión mediante la función de retardo de microsegundos, no pueda interrumpirse.

Interrupciones del temporizador

La llamada de rutinas de manejo de interrupciones se puede organizar no solo por eventos externos, sino también por eventos internos de cambio de hora. Esta función es especialmente útil cuando necesita realizar determinadas operaciones en determinados intervalos.

La biblioteca TimerOne facilita la configuración de interrupciones del temporizador. Se puede encontrar y descargar en http://playground.arduino.cc/Code/Timer1.

El siguiente ejemplo muestra cómo utilizar TimerOne para generar un tren de pulsos de onda cuadrada de 1 kHz. Si tienes un osciloscopio o multímetro con capacidad para medir frecuencia, conéctalo al pin 12 para ver la señal (Fig. 3.2).

Arroz. 3.2. Secuencia de pulsos rectangular generada mediante un temporizador.

// boceto_03_03_1kHz

#incluir

int salidaPin = 12;

salida int volátil = BAJA;

pinMode(12, SALIDA);

Temporizador1.inicializar(500);

Timer1.attachInterrupt(alternarSalida);

salida de palanca vacía()

escritura digital (pin de salida, salida);

salida = ! producción;

Lo mismo podría implementarse usando un retraso, pero el uso de interrupciones del temporizador le permite organizar la ejecución de cualquier otra operación dentro del bucle. Además, el uso de la función de retardo no logrará una alta precisión, porque el tiempo necesario para cambiar el nivel de voltaje en el contacto no se tendrá en cuenta en el valor del retardo.

NOTA

Todas las restricciones para las rutinas de interrupción externa que se analizaron anteriormente también se aplican a las rutinas de interrupción del temporizador.

Con el método presentado, puede establecer cualquier intervalo entre interrupciones en el rango de 1 a 8.388.480 μs, es decir, hasta aproximadamente 8,4 s. El valor del intervalo se pasa a la función de inicialización en microsegundos.

La biblioteca TimerOne también permite utilizar un temporizador para generar señales de modulación de ancho de pulso (PWM) en los pines 9 y 10 de la placa. Esto puede parecer excesivo porque analogWrite hace lo mismo, pero el uso de interrupciones permite un control más preciso de la señal PWM. En particular, utilizando este enfoque, es posible organizar la medición de la longitud de un pulso positivo en el rango 0...1023 en lugar de 0...255 en la función analogWrite. Además, cuando se utiliza analogWrite, la frecuencia de repetición del pulso en la señal PWM es de 500 Hz, y con TimerOne puede aumentar o disminuir esta frecuencia.

Para generar una señal PWM usando la biblioteca TimerOne, use la función Timer1.pwm, como se muestra en el siguiente ejemplo:

// boceto_03_04_pwm

#incluir

pinMode(9, SALIDA);

pinMode(10, SALIDA);

Temporizador1.initialize(1000);

Temporizador1.pwm(9, 512);

Temporizador1.pwm(10, 255);

Aquí, el período de repetición del pulso se elige para que sea de 1000 μs, es decir, la frecuencia de la señal PWM es de 1 kHz. En la Fig. 3.3 muestra la forma de onda en los pines 10 ( arriba) y 9 ( en el fondo).

Arroz. 3.3. Señal de ancho de pulso de 1 kHz generada usando TimerOne

Sólo por diversión, veamos hasta qué punto se puede aumentar la frecuencia de la señal PWM. Si la duración del período se reduce a 10, la frecuencia de la señal PWM debería aumentar a 100 kHz. Las formas de onda obtenidas con estos parámetros se muestran en la Fig. 3.4.

A pesar de la presencia de importantes distorsiones transitorias, lo cual es bastante esperado, la duración de los pulsos positivos sigue estando bastante cerca del 25 y el 50%, respectivamente.

Arroz. 3.4. Señal de ancho de pulso de 100 kHz generada con TimerOne

Finalmente

Las interrupciones, que a veces parecen la solución ideal para proyectos difíciles, pueden dificultar la depuración del código y no siempre son la mejor manera de resolver problemas difíciles. Considere cuidadosamente las posibles soluciones antes de comprometerse con ellas. En el Capítulo 14, veremos otro truco para superar la dificultad de la incapacidad de Arduino para manejar más de una tarea a la vez.

Volveremos a las interrupciones en el Capítulo 5, donde veremos cómo se pueden usar para reducir el consumo de energía de la placa Arduino poniéndola periódicamente en modo de ahorro de energía, y en el Capítulo 13, donde usaremos interrupciones para aumentar la precisión del procesamiento de señales digitales.

En el próximo capítulo, exploraremos técnicas para maximizar el rendimiento de Arduino.



¿Te gustó el artículo? Compártelo