Menú
Está libre
Registro
hogar  /  Instalación y configuración/ Arduino interrumpe con attachInterrupt. Cómo ejecutar tareas paralelas (subprocesos) en un programa Arduino

Arduino interrumpe con attachInterrupt. Cómo ejecutar tareas paralelas (subprocesos) en un programa Arduino


interrupciones de hardware

No pude encontrar una imagen divertida para esta lección, solo encontré una lección sobre programación, y el comienzo de esta lección nos explica perfectamente qué interrumpir. Una interrupción en Arduino se puede describir exactamente de la misma manera: el microcontrolador "deja todo", pasa a ejecutar un bloque de funciones en el controlador de interrupciones, las ejecuta y luego regresa exactamente al lugar en el código principal donde se detuvo.

Las interrupciones son diferentes, es decir, no las interrupciones en sí mismas, sino sus causas: una interrupción puede causar un convertidor de analógico a digital, un contador de tiempo o, literalmente, un pin de microcontrolador. Tales interrupciones se llaman externas. hardware y de eso es de lo que estamos hablando hoy.

Interrupción de hardware externa- Esta es una interrupción causada por un cambio de voltaje en el pin del microcontrolador. El punto principal es que el microcontrolador (núcleo de computación) no sondea el pin y no pierdas el tiempo en eso, otra "pieza de hierro" se dedica a clavar. Tan pronto como cambie el voltaje en el pin (lo que significa señal digital, +5 aplicado / +5 eliminado) - el microcontrolador recibe una señal, cierra todo, procesa la interrupción y vuelve a trabajar. ¿Por qué es necesario? La mayoría de las veces, las interrupciones se usan para detectar eventos cortos: pulsos, o incluso para contar su número, sin cargar el código principal. Una interrupción de hardware puede detectar una pulsación corta de un botón o un disparador de sensor durante cálculos largos complejos o retrasos en el código, es decir, en términos generales, el pin está sondeado paralelo al código principal. Además, las interrupciones pueden despertar al microcontrolador de los modos de ahorro de energía, cuando casi todos los periféricos están apagados. Veamos cómo trabajar con interrupciones de hardware en el IDE de Arduino.

Interrupciones en Arduino

Comencemos con el hecho de que no todos los pines "pueden" interrumpir. Sí, existe tal cosa como pinChangeInterrupts, pero hablaremos de ello en lecciones avanzadas. Ahora debemos entender que las interrupciones de hardware solo pueden generar ciertos pines:

MK / número de interrupción INT 0 INT 1 INT 2 INT 3 INT 4 INT 5
ATmega 328/168 (Nano, UNO, Mini) D2 D3
ATmega 32U4 (Leonardo, Micro) D3 D2 D0 D1 D7
ATmega 2560 (Mega) D2 D3 D21 D20 D19 D18

Como entendió de la tabla, las interrupciones tienen su propio número, que es diferente del número pin. De paso característica útil pin digital para interrumpir (pin), que toma un número pin y devuelve el número de interrupción. Al alimentar esta función con el número 3 en el Arduino nano, obtenemos 1. Todo está de acuerdo con la tabla anterior, una función para los perezosos.

Una interrupción se conecta usando la función adjuntar Interrupción (pin, controlador, modo):

  • alfiler- número de interrupción
  • manipulador- el nombre de la función del controlador de interrupciones (debe crearlo usted mismo)
  • modo– interrumpir el “modo” de funcionamiento:
    • BAJO(bajo) - activado por una señal BAJO en pin
    • CRECIENTE(crecimiento) - activado cuando la señal en el pin cambia de BAJO sobre el ALTO
    • DESCENDENTE(caída) - se activa cuando la señal en el pin cambia de ALTO sobre el BAJO
    • CAMBIO(cambio) - activado cuando la señal cambia (con BAJO sobre el ALTO y viceversa)

La interrupción también se puede deshabilitar usando la función desconectar Interrupción (pin), donde pin está de nuevo número de interrupción.

También puede deshabilitar globalmente las interrupciones con la función sin interrupciones () y resolverlos de nuevo con interrupciones (). ¡Cuidado con ellos! sin interrupciones () también detendrá las interrupciones del temporizador, y todas las funciones de tiempo y la generación de PWM se "interrumpirán" por usted.

Veamos un ejemplo en el que las pulsaciones de botones se cuentan en la interrupción y en el ciclo principal se emiten con un retraso de 1 segundo. Trabajando con el botón modo normal, combinar una salida tan aproximada con un retraso es imposible:

Contador int volátil = 0; // variable de contador void setup() ( Serial.begin(9600); // puerto abierto para comunicación // botón conectado en D2 y GND pinMode(2, INPUT_PULLUP); \ // D2 es interrupción 0 // manejador - función buttonTick // FALLING - cuando se hace clic en el botón, la señal será 0, y la capturamos addedInterrupt(0, buttonTick, FALLING); ) void buttonTick() ( counter++; // + presionando ) void loop() ( Serial. println (contador); // retraso de salida (1000); // espera)

Entonces, ¡nuestro código cuenta los clics incluso durante el retraso! Excelente. Pero que es volátil? Hemos declarado una variable global encimera, que almacenará el número de clics en el botón. Si el valor de la variable cambiará en la interrupción, debe informar al microcontrolador sobre esto usando el especificador volátil, que se escribe antes de especificar el tipo de datos de la variable, de lo contrario el trabajo será incorrecto. Solo necesitas recordar esto: si una variable cambia en una interrupción, hazlo volátil.

Algunos puntos más importantes:

  • Las variables modificadas en una interrupción deben declararse como volátil
  • Las interrupciones no tienen retrasos como demora()
  • No cambia su valor en una interrupción. milisegundos() y micros()
  • En la interrupción, la salida al puerto no funciona correctamente ( Serial.imprimir()), tampoco lo use allí: carga el kernel
  • En la interrupción, debe intentar hacer la menor cantidad de cálculos posible y, en general, acciones "largas": ¡esto ralentizará el trabajo del MC con interrupciones frecuentes! ¿Qué hacer? Lee abajo.

Si la interrupción detecta algún evento que no necesita ser procesado inmediatamente, entonces es mejor usar el siguiente algoritmo de manejo de interrupciones:

  • En el controlador de interrupciones, simplemente levante la bandera
  • En el ciclo principal del programa, verificamos la bandera, si está levantada, la reiniciamos y realizamos las acciones necesarias
intFlag booleano volátil = falso; // flag void setup() ( Serial.begin(9600); // puerto abierto para comunicación // botón conectado en D2 y GND pinMode(2, INPUT_PULLUP); // D2 es interrupción 0 // manejador - función buttonTick // FALLING - cuando se presiona el botón, la señal será 0, y la capturamos addedInterrupt(0, buttonTick, FALLING); ) void buttonTick() ( intFlag = true; // levantó la bandera de interrupción ) void loop() ( if (intFlag) ( intFlag = false; // restablecer // hacer algo Serial.println("¡Interrumpir!"); ) )

Esto es básicamente todo lo que necesita saber acerca de las interrupciones, más casos específicos lo cubriremos en tutoriales avanzados.

Video

Y daremos un ejemplo de uso de la función Arduino. adjuntar Interrupción ().

Una interrupción es una señal que informa al procesador sobre la ocurrencia de algún evento que requiere atención inmediata. El procesador debe responder a esta señal interrumpiendo la ejecución de las instrucciones actuales y transfiriendo el control al manejador de interrupciones (ISR, rutina de servicio de interrupción). Un controlador es una función regular que escribimos nosotros mismos y ponemos allí el código que debe responder al evento.

Después de dar servicio a la interrupción de ISR, la función finaliza su trabajo y el procesador felizmente regresa a las actividades interrumpidas; continúa ejecutando el código desde el lugar donde se detuvo. Todo esto sucede automáticamente, por lo que nuestra tarea es solo escribir un controlador de interrupción sin romper nada y sin obligar al procesador a distraerse con demasiada frecuencia. Necesitará una comprensión del circuito, los principios de funcionamiento de los dispositivos conectados y una idea de con qué frecuencia se puede llamar a una interrupción, cuáles son las características de su ocurrencia. Todo esto es la principal dificultad al trabajar con interrupciones.

Interrupciones de hardware y software

Las interrupciones en Arduino se pueden dividir en varios tipos:

  • interrupciones de hardware. Interrupción a nivel de arquitectura del microprocesador. El evento en sí puede ocurrir en un momento productivo desde dispositivo externo– por ejemplo, presionando un botón en el teclado, moviendo ratón de computadora etc.
  • Interrupciones de software. Ejecutar dentro del programa con instrucción especial. Se utiliza para llamar a un controlador de interrupciones.
  • Interrupciones internas (sincrónicas). Se produce una interrupción interna como resultado de un cambio o violación en la ejecución del programa (por ejemplo, al acceder a una dirección no válida, código de operación no válido, etc.).

¿Por qué necesitamos interrupciones de hardware?

Las interrupciones de hardware ocurren en respuesta a un evento externo y provienen de un dispositivo de hardware externo. Hay 4 tipos de interrupciones de hardware en Arduino. Todos ellos difieren en la señal en el pin de interrupción:

  • El contacto se tira al suelo. El controlador de interrupciones se ejecuta siempre que el pin de interrupción esté en BAJO.
  • Cambiar la señal en un contacto. En este caso, Arduino ejecuta un controlador de interrupción cuando se produce un cambio de señal en el pin de interrupción.
  • Cambiar la señal de BAJO a ALTO en un pin: al cambiar de bajo a alto, se ejecutará un controlador de interrupción.
  • Cambiar la señal de ALTA a BAJA en un pin: cuando la señal cambia de alta a baja, se ejecutará un controlador de interrupción.

Las interrupciones son útiles en los programas de Arduino, ya que ayudan a resolver problemas de temporización. Por ejemplo, cuando se trabaja con UART, las interrupciones le permiten no realizar un seguimiento de la llegada de cada personaje. El dispositivo de hardware externo emite una señal de interrupción, el procesador llama inmediatamente al controlador de interrupciones, que captura el carácter a tiempo. Esto ahorra tiempo de CPU, que sin interrupciones se gastaría en verificar el estado del UART; en cambio, el controlador de interrupciones realiza todas las acciones necesarias sin afectar el programa principal. No se requieren capacidades especiales del dispositivo de hardware.

Las principales razones para llamar a una interrupción son:

  • Determinar el cambio de estado de la salida;
  • Interrupción del temporizador;
  • Interrupciones de datos vía SPI, I2C, USART;
  • Conversión de analógico a digital;
  • Disponibilidad para usar EEPROM, memoria flash.

Cómo se implementan las interrupciones en Arduino

Cuando se recibe una señal de interrupción, la operación se suspende. Comienza la ejecución de la función que se declara ejecutar en caso de interrupción. Una función declarada no puede aceptar valores de entrada y devolver valores al finalizar. La interrupción no afecta al código en sí mismo en el bucle del programa principal. Para trabajar con interrupciones en Arduino, use función estándar adjuntar Interrupción ().

La diferencia entre la implementación de interrupciones en diferentes placas Arduino

Dependiendo de la implementación del hardware modelo específico El microcontrolador tiene varias interrupciones. La placa Arduino Uno tiene 2 interrupciones en los pines 2 y 3, pero si se requieren más de dos salidas, la placa admite modo especial pin-cambio. Este modo funciona cambiando la entrada para todos los pines. La diferencia en el modo de interrupción de cambio de entrada es que las interrupciones se pueden generar en cualquiera de los ocho pines. La tramitación en este caso será más complicada y más larga, ya que tendrás que llevar un control del último estado de cada uno de los contactos.

En otras placas, el número de interrupciones es mayor. Por ejemplo, la placa tiene 6 pines que pueden manejar interrupciones externas. Para todas las placas Arduino, cuando se trabaja con la función AttachInterrupt (interrupción, función, modo), el argumento Inerrupt 0 está asociado con el pin digital 2.

Interrupciones en el lenguaje Arduino

Ahora pasemos a la práctica y hablemos sobre cómo usar las interrupciones en sus proyectos.

Sintaxis de AttachInterrupt()

La función AttachInterrupt se utiliza para trabajar con interrupciones. Sirve para conectar una interrupción externa a un controlador.

Sintaxis de llamada: AttachInterrupt (interrupción, función, modo)

Argumentos de la función:

  • interrupción - el número de la interrupción que se llama (estándar 0 - para el segundo pin, para la placa Arduino Uno 1 - para el tercer pin),
  • función: el nombre de la función llamada cuando se interrumpe (importante: la función no debe aceptar ni devolver ningún valor),
  • El modo es la condición para disparar la interrupción.

Se pueden establecer las siguientes condiciones de activación:

  • BAJO - realizado en un nivel de señal bajo, cuando el contacto tiene un valor cero. La interrupción se puede repetir cíclicamente, por ejemplo, cuando se presiona un botón.
  • CAMBIO - en el frente, la interrupción ocurre cuando la señal cambia de alta a baja o viceversa. Se ejecuta una vez en cualquier cambio de señal.
  • RISING - Ejecuta una interrupción una vez cuando la señal cambia de BAJA a ALTA.
  • FALLING - Ejecuta una interrupción una vez cuando la señal cambia de HIGH a LOW.4

Notas importantes

Al trabajar con interrupciones, se deben tener en cuenta las siguientes limitaciones importantes:

  • La función del controlador no debería tardar demasiado en ejecutarse. La cosa es que Arduino no puede manejar múltiples interrupciones al mismo tiempo. Mientras se ejecuta su función de controlador, todas las demás interrupciones se ignorarán y es posible que se pierda eventos importantes. Si necesita hacer algo grande, simplemente pase el control de eventos en el bucle principal(). En el controlador, solo puede configurar el indicador de evento y, en el ciclo, puede verificar el indicador y procesarlo.
  • Hay que tener mucho cuidado con las variables. Un compilador inteligente de C++ puede "volver a optimizar" su programa eliminando las variables que no necesita. El compilador simplemente no verá que está configurando algunas variables en una parte y usándolas en otra. Para eliminar esta posibilidad en el caso de tipos de datos básicos, puede utilizar palabra clave volatile, así: volatile boolean state = 0. Pero este método no funcionará con estructuras de datos complejas. Así que hay que estar siempre alerta.
  • No se recomienda usar una gran cantidad de interrupciones (trate de no usar más de 6-8). Una gran cantidad de eventos diferentes requiere una complicación grave del código y, por lo tanto, conduce a errores. Además, hay que entender que no hay precisión temporal de ejecución en sistemas con gran cantidad no puede haber interrupciones del habla: nunca comprenderá exactamente cuál es la brecha entre las llamadas de los comandos que son importantes para usted.
  • Delay() no debe usarse en controladores. El mecanismo para determinar el intervalo de retraso usa temporizadores, y también funcionan en las interrupciones que su controlador bloqueará. Como resultado, todos esperarán a todos y el programa se colgará. Por la misma razón, no se pueden utilizar protocolos de comunicación basados ​​en interrupciones (como i2c).

ejemplos de AttachInterrupt

Pongámonos manos a la obra y veamos el ejemplo mas simple utilizando interrupciones. En el ejemplo, definimos una función de controlador que, cuando cambie la señal en el pin 2 del Arduino Uno, cambiará el estado del pin 13, al que tradicionalmente conectaremos el LED.

#define PIN_LED 13 estado de acción booleano volátil = BAJO; void setup() ( pinMode(PIN_LED, OUTPUT); // Establecer la interrupción // Se llamará a la función myEventListener cuando // la señal cambie en el pin 2 (la interrupción 0 está conectada al pin 2) // la señal cambia (no importa en qué dirección) attachInterrupt (0, myEventListener, CHANGE); ) void loop() ( // No hacemos nada en la función de bucle, ya que todo el código de manejo de eventos estará en la función myEventListener) void myEventListener() ( actionState != actionState; // // Hacer otras cosas como encender o apagar el LED digitalWrite(PIN_LED, actionState); )

Veamos algunos ejemplos de interrupciones más complejas y sus controladores: para el temporizador y los botones.

Interrupciones de botón anti-rebote

Cuando ocurre una interrupción, antes de que los contactos hagan contacto firme cuando se presiona el botón, oscilarán, generando varias operaciones. Hay dos formas de lidiar con la charla: hardware, es decir, soldando un condensador al botón y software.

Puede deshacerse de las conversaciones con la función: le permite detectar el tiempo transcurrido desde la primera operación del botón.

If(digitalRead(2)==HIGH) ( //cuando se presiona el botón //Si han transcurrido más de 100 milisegundos desde la última vez que se presionó if (millis() - previousMillis >= 100) ( //Recordar el tiempo de la primera operación previousMillis = millis(); if (led==oldled) ( // comprueba que el estado del botón no ha cambiado led=!led; )

Este código le permite eliminar el parloteo y no bloquea la ejecución del programa, como es el caso de la función de retraso, que no está permitida en las interrupciones.

Interrupciones de temporizador

Un temporizador es un contador que cuenta a una determinada frecuencia obtenida del procesador 16 MHz. Puede configurar el divisor de frecuencia para obtener modo deseado cuentas También puede configurar el contador para generar interrupciones cuando se alcanza un valor determinado.

Y la interrupción del temporizador le permite ejecutar la interrupción una vez por milisegundo. Arduino tiene 3 temporizadores: Timer0, Timer1 y Timer2. Timer0 se usa para generar interrupciones una vez por milisegundo, lo que actualiza el contador que se pasa a la función millis(). Este temporizador es de ocho bits y cuenta de 0 a 255. Se genera una interrupción cuando el valor es 255. De forma predeterminada, se utiliza un divisor de reloj por 65 para obtener una frecuencia cercana a 1 kHz.

Los registros de comparación se utilizan para comparar el estado del temporizador y los datos almacenados. En este ejemplo, el código generará una interrupción cuando el contador llegue a 0xAF.

TIMSK0 |= _BV(OCIE0A);

Es necesario definir un controlador de interrupción para el vector de interrupción del temporizador. El vector de interrupción es un puntero a la ubicación de la instrucción que se ejecutará cuando se llame a la interrupción. Varios vectores de interrupción se combinan en una tabla de vectores de interrupción. El temporizador en este caso tendrá el nombre TIMER0_COMPA_vect. En este controlador, se realizarán las mismas acciones que en bucle ().

SIGNAL(TIMER0_COMPA_vect) ( unsigned long currentMillis = millis(); sweeper1.Update(currentMillis); if(digitalRead(2) == HIGH) ( sweeper2.Update(currentMillis); led1.Update(currentMillis); ) led2.Update( currentMillis); led3.Update(currentMillis); ) // La función loop() permanecerá vacía. bucle vacío() ( )

resumiendo

La interrupción en Arduino es un tema bastante complicado, porque hay que pensar en toda la arquitectura del proyecto a la vez, imaginar cómo se ejecuta el código, qué eventos son posibles, qué sucede cuando se interrumpe el código principal. No pretendíamos revelar todas las características de trabajar con esta construcción del lenguaje, el objetivo principal era presentar los principales casos de uso. En próximos artículos seguiremos hablando de las interrupciones con más detalle.

En términos generales, Arduino no admite la paralelización de tareas verdaderas ni los subprocesos múltiples. Pero es posible con cada repetición del ciclo círculo() indique al microcontrolador que verifique si es hora de ejecutar alguna tarea adicional en segundo plano. En este caso, al usuario le parecerá que se están realizando varias tareas simultáneamente.

Por ejemplo, hagamos parpadear un LED a una frecuencia dada y emitamos simultáneamente sonidos ascendentes y descendentes como una sirena desde un emisor piezoeléctrico. Ya hemos conectado más de una vez tanto el LED como el emisor piezoeléctrico al Arduino. Montemos el circuito, como se muestra en la figura.

Si está conectando un LED a un pin digital que no sea "13", no se olvide de una resistencia limitadora de corriente de 220 ohmios.

2 Control de LED y zumbador piezoeléctrico usando el operador delay()

Escribamos este boceto y subámoslo a Arduino.

Const int soundPin = 3; /* declaramos una variable con el número del pin al que está conectado el elemento piezoeléctrico */ const int ledPin = 13; // declara una variable con el número de pin del LED configuración vacía()( pinMode(pin de sonido, SALIDA); // declara el pin 3 como salida. pinMode(ledPin, SALIDA); // declara el pin 13 como salida. } bucle vacío() (// Control de sonido: tono(soundPin, 700); // hacer un sonido a una frecuencia de 700 Hz delay(200); tono (pin de sonido, 500); // con un retraso de 500 Hz (200); tono (pin de sonido, 300); // con un retraso de 300 Hz (200); tono (pin de sonido, 200); // con un retraso de 200 Hz (200); // control LED: digitalWrite(ledPin, HIGH); // retardo de disparo(200); digitalWrite(ledPin, BAJO); // extingue el retraso (200); }

Después de encenderlo, se puede ver que el boceto no se ejecuta exactamente como lo necesitamos: hasta que la sirena esté completamente resuelta, el LED no parpadeará y nos gustaría que el LED parpadeara durante el sonido de una sirena. ¿Cuál es el problema aquí?

El hecho es que este problema no se puede resolver de la manera habitual. Las tareas son realizadas por el microcontrolador de forma estrictamente secuencial. Operador demora() retrasa la ejecución del programa por un período de tiempo específico, y hasta que expire este tiempo, los siguientes comandos del programa no se ejecutarán. Debido a esto, no podemos establecer una duración de ejecución diferente para cada tarea en el bucle. círculo() programas Por lo tanto, necesita simular de alguna manera la multitarea.

3 Procesos Paralelos sin el operador "delay()"

Los desarrolladores de Arduino proponen la opción en la que Arduino realizará tareas en pseudo-paralelo. La esencia del método es que con cada repetición del ciclo círculo() comprobamos si es hora de hacer parpadear el LED (realizar una tarea en segundo plano) o no. Y si es así, invertimos el estado del LED. Este es un tipo de operador de derivación. demora().

Const int soundPin = 3; // variable con el número de pin del elemento piezoeléctrico const int ledPin = 13; // variable con número de pin LED const long ledInterval = 200; // Intervalo de parpadeo del LED, mseg. int ledState = BAJO; // estado inicial LED sin firmar largo anteriorMillis = 0; // almacena el tiempo del encendido anterior del LED configuración vacía()( pinMode(pin de sonido, SALIDA); // establece el pin 3 como salida. pinMode(ledPin, SALIDA); // establece el pin 13 como salida. } bucle vacío() (// Control de sonido: tono(soundPin, 700); retraso (200); tono (pin de sonido, 500); retraso (200); tono (pin de sonido, 300); retraso (200); tono (pin de sonido, 200); retraso (200); // Parpadeo del LED: // tiempo desde que se encendió Arduino, ms: unsigned long currentMillis = millis(); // Si es hora de parpadear, if (currentMillis - previousMillis >= ledInterval) ( previousMillis = currentMillis; // entonces almacena la hora actual if (ledState == LOW) ( // e invierte el estado del LED ledState = HIGH; ) else (ledState = BAJO;) digitalWrite(ledPin, ledState); // alternar el estado del LED) }

Una desventaja significativa este método es que la sección de código antes del bloque de control LED debe ejecutarse más rápido que el intervalo de tiempo de parpadeo del LED "ledInterval". De lo contrario, el parpadeo ocurrirá con menos frecuencia de la necesaria y no obtendremos el efecto de la ejecución paralela de tareas. En particular, en nuestro esquema, la duración del cambio en el sonido de la sirena es 200+200+200+200 = 800 ms, y configuramos el intervalo de parpadeo del LED en 200 ms. Pero el LED parpadeará en un período de 800ms, que es 4 veces lo que configuramos.

En general, si el código usa el operador demora(), en este caso es difícil simular pseudoparalelismo, por lo que es conveniente evitarlo.

En este caso, sería necesario que la unidad de control de sonido de sirena también verificara si ha llegado el momento o no, y no usar demora(). Pero esto aumentaría la cantidad de código y empeoraría la legibilidad del programa.

4 Uso de la biblioteca ArduinoThread para crear hilos paralelos

Para resolver el problema, utilizaremos una biblioteca maravillosa. Hilo Arduino, que le permite crear fácilmente procesos pseudo-paralelos. Funciona de manera similar, pero le permite no escribir código para verificar el tiempo; debe completar la tarea en este ciclo o no. Esto reduce la cantidad de código y mejora la legibilidad del boceto. Echemos un vistazo a la biblioteca en acción.


En primer lugar, descargue el archivo de la biblioteca del sitio web oficial y descomprímalo en el directorio bibliotecas/ Entorno de desarrollo Arduino IDE. Luego cambie el nombre de la carpeta Arduino Threadmaster en Hilo Arduino.

El diagrama de cableado seguirá siendo el mismo. Solo cambiará el código del programa.

#incluir // conectando la biblioteca ArduinoThread const int soundPin = 3; // variable con el número de pin del elemento piezoeléctrico const int ledPin = 13; // variable con número de pin LED Thread ledThread = Thread(); // crea un hilo para controlar el LED Thread soundThread = Thread(); // crea un flujo de control para la sirena configuración vacía()( pinMode(pin de sonido, SALIDA); // declara el pin 3 como salida. pinMode(ledPin, SALIDA); // declara el pin 13 como salida. ledSubproceso.onRun(ledParpadeo); // asignar una tarea al subproceso ledThread.setInterval(1000); // establece el intervalo de respuesta, ms soundThread.onRun(sound); // asignar una tarea al subproceso soundThread.setInterval(20); // establecer el intervalo de respuesta, ms } bucle vacío() (// Comprobar si es hora de cambiar el LED: if (ledThread.shouldRun()) ledThread.run(); // inicia el hilo // Comprueba si es hora de cambiar el tono de la sirena: if (soundThread.shouldRun()) soundThread.run(); // iniciar hilo } // flujo LED: parpadeo led vacío () ( static bool ledStatus = falso; // Estado del LED Encendido/Apagado ledStatus = !ledStatus; // invertir el estado digitalWrite(ledPin, ledStatus); // enciende/apaga el LED } // Flujo de sirena: sonido vacío() ( tono interno estático = 100; // tono de sonido, tono Hz (soundPin, ton); // enciende la sirena a "ton" Hz if (ton )

En el programa, creamos dos hilos: Hilo led y hilo de sonido, cada uno realiza su propia operación: uno hace parpadear el LED, el segundo controla el sonido de la sirena. En cada iteración del bucle de cada hilo, comprobamos si ha llegado o no el momento de su ejecución. Si llega, se lanza para su ejecución mediante el método correr(). Lo principal es no usar el operador. demora(). Se dan explicaciones más detalladas en el código.


Cargue el código en la memoria de Arduino, ejecútelo. ¡Ahora todo funciona exactamente como debería!

En términos generales, Arduino no admite la paralelización de tareas verdaderas ni los subprocesos múltiples. Pero es posible con cada repetición del ciclo círculo() indique al microcontrolador que verifique si es hora de ejecutar alguna tarea adicional en segundo plano. En este caso, al usuario le parecerá que se están realizando varias tareas simultáneamente.

Por ejemplo, hagamos parpadear un LED a una frecuencia dada y emitamos simultáneamente sonidos ascendentes y descendentes como una sirena desde un emisor piezoeléctrico. Ya hemos conectado más de una vez tanto el LED como el emisor piezoeléctrico al Arduino. Montemos el circuito, como se muestra en la figura.

Si está conectando un LED a un pin digital que no sea "13", no se olvide de una resistencia limitadora de corriente de 220 ohmios.

2 Control de LED y zumbador piezoeléctrico usando el operador delay()

Escribamos este boceto y subámoslo a Arduino.

Const int soundPin = 3; /* declaramos una variable con el número del pin al que está conectado el elemento piezoeléctrico */ const int ledPin = 13; // declara una variable con el número de pin del LED configuración vacía()( pinMode(pin de sonido, SALIDA); // declara el pin 3 como salida. pinMode(ledPin, SALIDA); // declara el pin 13 como salida. } bucle vacío() (// Control de sonido: tono(soundPin, 700); // hacer un sonido a una frecuencia de 700 Hz delay(200); tono (pin de sonido, 500); // con un retraso de 500 Hz (200); tono (pin de sonido, 300); // con un retraso de 300 Hz (200); tono (pin de sonido, 200); // con un retraso de 200 Hz (200); // control LED: digitalWrite(ledPin, HIGH); // retardo de disparo(200); digitalWrite(ledPin, BAJO); // extingue el retraso (200); }

Después de encenderlo, se puede ver que el boceto no se ejecuta exactamente como lo necesitamos: hasta que la sirena esté completamente resuelta, el LED no parpadeará y nos gustaría que el LED parpadeara durante el sonido de una sirena. ¿Cuál es el problema aquí?

El hecho es que este problema no se puede resolver de la manera habitual. Las tareas son realizadas por el microcontrolador de forma estrictamente secuencial. Operador demora() retrasa la ejecución del programa por un período de tiempo específico, y hasta que expire este tiempo, los siguientes comandos del programa no se ejecutarán. Debido a esto, no podemos establecer una duración de ejecución diferente para cada tarea en el bucle. círculo() programas Por lo tanto, necesita simular de alguna manera la multitarea.

3 Procesos Paralelos sin el operador "delay()"

Los desarrolladores de Arduino proponen la opción en la que Arduino realizará tareas en pseudo-paralelo. La esencia del método es que con cada repetición del ciclo círculo() comprobamos si es hora de hacer parpadear el LED (realizar una tarea en segundo plano) o no. Y si es así, invertimos el estado del LED. Este es un tipo de operador de derivación. demora().

Const int soundPin = 3; // variable con el número de pin del elemento piezoeléctrico const int ledPin = 13; // variable con número de pin LED const long ledInterval = 200; // Intervalo de parpadeo del LED, mseg. int ledState = BAJO; // estado inicial del LED sin firmar largo anteriorMillis = 0; // almacena el tiempo del encendido anterior del LED configuración vacía()( pinMode(pin de sonido, SALIDA); // establece el pin 3 como salida. pinMode(ledPin, SALIDA); // establece el pin 13 como salida. } bucle vacío() (// Control de sonido: tono(soundPin, 700); retraso (200); tono (pin de sonido, 500); retraso (200); tono (pin de sonido, 300); retraso (200); tono (pin de sonido, 200); retraso (200); // Parpadeo del LED: // tiempo desde que se encendió Arduino, ms: unsigned long currentMillis = millis(); // Si es hora de parpadear, if (currentMillis - previousMillis >= ledInterval) ( previousMillis = currentMillis; // entonces almacena la hora actual if (ledState == LOW) ( // e invierte el estado del LED ledState = HIGH; ) else (ledState = BAJO;) digitalWrite(ledPin, ledState); // alternar el estado del LED) }

Una desventaja significativa de este método es que la sección de código antes del bloque de control LED debe ejecutarse más rápido que el intervalo de tiempo de parpadeo del LED "ledInterval". De lo contrario, el parpadeo ocurrirá con menos frecuencia de la necesaria y no obtendremos el efecto de la ejecución paralela de tareas. En particular, en nuestro esquema, la duración del cambio en el sonido de la sirena es 200+200+200+200 = 800 ms, y configuramos el intervalo de parpadeo del LED en 200 ms. Pero el LED parpadeará en un período de 800ms, que es 4 veces lo que configuramos.

En general, si el código usa el operador demora(), en este caso es difícil simular pseudoparalelismo, por lo que es conveniente evitarlo.

En este caso, sería necesario que la unidad de control de sonido de sirena también verificara si ha llegado el momento o no, y no usar demora(). Pero esto aumentaría la cantidad de código y empeoraría la legibilidad del programa.

4 Uso de la biblioteca ArduinoThread para crear hilos paralelos

Para resolver el problema, utilizaremos una biblioteca maravillosa. Hilo Arduino, que le permite crear fácilmente procesos pseudo-paralelos. Funciona de manera similar, pero le permite no escribir código para verificar el tiempo; debe completar la tarea en este ciclo o no. Esto reduce la cantidad de código y mejora la legibilidad del boceto. Echemos un vistazo a la biblioteca en acción.


En primer lugar, descargue el archivo de la biblioteca del sitio web oficial y descomprímalo en el directorio bibliotecas/ Entorno de desarrollo Arduino IDE. Luego cambie el nombre de la carpeta Arduino Threadmaster en Hilo Arduino.

El diagrama de cableado seguirá siendo el mismo. Solo cambiará el código del programa.

#incluir // conectando la biblioteca ArduinoThread const int soundPin = 3; // variable con el número de pin del elemento piezoeléctrico const int ledPin = 13; // variable con número de pin LED Thread ledThread = Thread(); // crea un hilo para controlar el LED Thread soundThread = Thread(); // crea un flujo de control para la sirena configuración vacía()( pinMode(pin de sonido, SALIDA); // declara el pin 3 como salida. pinMode(ledPin, SALIDA); // declara el pin 13 como salida. ledSubproceso.onRun(ledParpadeo); // asignar una tarea al subproceso ledThread.setInterval(1000); // establece el intervalo de respuesta, ms soundThread.onRun(sound); // asignar una tarea al subproceso soundThread.setInterval(20); // establecer el intervalo de respuesta, ms } bucle vacío() (// Comprobar si es hora de cambiar el LED: if (ledThread.shouldRun()) ledThread.run(); // inicia el hilo // Comprueba si es hora de cambiar el tono de la sirena: if (soundThread.shouldRun()) soundThread.run(); // iniciar hilo } // flujo LED: parpadeo led vacío () ( static bool ledStatus = falso; // Estado del LED Encendido/Apagado ledStatus = !ledStatus; // invertir el estado digitalWrite(ledPin, ledStatus); // enciende/apaga el LED } // Flujo de sirena: sonido vacío() ( tono interno estático = 100; // tono de sonido, tono Hz (soundPin, ton); // enciende la sirena a "ton" Hz if (ton )

En el programa, creamos dos hilos: Hilo led y hilo de sonido, cada uno realiza su propia operación: uno hace parpadear el LED, el segundo controla el sonido de la sirena. En cada iteración del bucle de cada hilo, comprobamos si ha llegado o no el momento de su ejecución. Si llega, se lanza para su ejecución mediante el método correr(). Lo principal es no usar el operador. demora(). Se dan explicaciones más detalladas en el código.


Cargue el código en la memoria de Arduino, ejecútelo. ¡Ahora todo funciona exactamente como debería!

Es posible que se requieran varias interrupciones durante la implementación del proyecto, pero si cada una de ellas tiene la máxima prioridad, entonces, de hecho, ninguna de las funciones la tendrá. Por la misma razón, no se recomienda utilizar más de una docena de interrupciones.

Los controladores solo deben aplicarse a aquellos procesos que tienen la máxima sensibilidad a los intervalos de tiempo. No olvide que mientras el programa está en el controlador de interrupciones, todas las demás interrupciones están deshabilitadas. Un gran número de interrupciones conduce a un deterioro en su respuesta.

En el momento en que una interrupción está activa y las otras están deshabilitadas, dos matices importantes a tener en cuenta por el diseñador del circuito. En primer lugar, el tiempo de interrupción debe ser lo más breve posible.

Esto asegurará que no se pierdan todas las demás interrupciones programadas. En segundo lugar, al manejar una interrupción código de programación no debe requerir actividad de otras interrupciones. Si esto no se evita, el programa simplemente se congelará.

No utilice un procesamiento prolongado círculo() , es mejor desarrollar código para un controlador de interrupciones con la variable establecida en volátil. Le dirá al programa que no se necesita más procesamiento.

Si la función llama Actualizar() es necesario, primero deberá verificar la variable de estado. Esto determinará si es necesario un procesamiento adicional.

Antes de comenzar a configurar el temporizador, debe verificar el código. Los temporizadores de Anduino deben atribuirse a recursos limitados, porque solo hay tres y se utilizan para realizar la mayoría diferentes funciones. Si se confunde con el uso de temporizadores, es posible que varias operaciones simplemente dejen de funcionar.

¿En qué funciones opera un temporizador en particular?

Para el microcontrolador Arduino Uno, cada uno de los tres temporizadores tiene sus propias operaciones.

Asi que Temporizador0 es responsable de PWM en el quinto y sexto pin, funciones milisegundos() , micros() , demora() .

Otro temporizador - temporizador1, utilizado con PWM en el noveno y décimo pin, con bibliotecas WaveHC y Servo.

Temporizador2 funciona con PWM en los pines 11 y 13, así como con tono.

El diseñador del circuito debe cuidar el uso seguro de los datos compartidos. Después de todo, una interrupción detiene todas las operaciones del procesador por un milisegundo y el intercambio de datos entre círculo() y los manejadores de interrupciones deben ser permanentes. Puede surgir una situación en la que el compilador, para lograr sus propios rendimiento máximo comenzará a optimizar el código.

El resultado de este proceso será guardar una copia de las principales variables de código en el registro, lo que garantizará la máxima velocidad de acceso a las mismas.

La desventaja de este proceso puede ser que los valores reales se reemplacen con copias guardadas, lo que puede provocar la pérdida de funcionalidad.

Para evitar que esto suceda, necesita usar una variable voltatil , lo que ayudará a evitar optimizaciones innecesarias. Cuando utilice arreglos grandes que requieran ciclos para las actualizaciones, debe deshabilitar las interrupciones en el momento de estas actualizaciones.