¿Hay alguna manera de que varias partes del programa se ejecuten juntas sin hacer varias cosas en el mismo bloque de código?
Un hilo esperando un dispositivo externo mientras parpadea un LED en otro hilo.
¿Hay alguna manera de que varias partes del programa se ejecuten juntas sin hacer varias cosas en el mismo bloque de código?
Un hilo esperando un dispositivo externo mientras parpadea un LED en otro hilo.
Respuestas:
No hay soporte multiproceso, ni multiproceso, en el Arduino. Sin embargo, puede hacer algo cercano a múltiples hilos con algún software.
Quiere ver Protothreads :
Los Protothreads son hilos sin pila extremadamente ligeros diseñados para sistemas con limitaciones severas de memoria, como pequeños sistemas integrados o nodos de red de sensores inalámbricos. Los Protothreads proporcionan ejecución de código lineal para sistemas controlados por eventos implementados en C. Los Protothreads se pueden usar con o sin un sistema operativo subyacente para proporcionar controladores de eventos de bloqueo. Los Protothreads proporcionan un flujo de control secuencial sin máquinas de estado complejas o multihilo completo.
Por supuesto, hay un ejemplo de Arduino aquí con código de ejemplo . Esta pregunta SO también podría ser útil.
ArduinoThread también es bueno.
Los Arduino basados en AVR no admiten subprocesos (hardware), no estoy familiarizado con los Arduino basados en ARM. Una forma de evitar esta limitación es el uso de interrupciones, especialmente interrupciones temporizadas. Puede programar un temporizador para interrumpir la rutina principal cada tantos microsegundos, para ejecutar otra rutina específica.
Es posible realizar subprocesos múltiples del lado del software en el Uno. El subproceso de nivel de hardware no es compatible.
Para lograr el subprocesamiento múltiple, requerirá la implementación de un planificador básico y mantener un proceso o una lista de tareas para rastrear las diferentes tareas que deben ejecutarse.
La estructura de un planificador no preventivo muy simple sería como:
//Pseudocode
void loop()
{
for(i=o; i<n; i++)
run(tasklist[i] for timelimit):
}
Aquí, tasklist
puede haber una matriz de punteros de función.
tasklist [] = {function1, function2, function3, ...}
Con cada función de la forma:
int function1(long time_available)
{
top:
//Do short task
if (run_time<time_available)
goto top;
}
Cada función puede realizar una tarea separada, como function1
realizar manipulaciones de LED y function2
hacer cálculos flotantes. Será responsabilidad de cada tarea (función) cumplir con el tiempo asignado.
Con suerte, esto debería ser suficiente para comenzar.
Según la descripción de sus requisitos:
Parece que podría usar una interrupción de Arduino para el primer "hilo" (de hecho, preferiría llamarlo "tarea").
Las interrupciones de Arduino pueden llamar a una función (su código) en función de un evento externo (nivel de voltaje o cambio de nivel en un pin de entrada digital), que activará su función de inmediato.
Sin embargo, un punto importante a tener en cuenta con las interrupciones es que la función llamada debe ser lo más rápida posible (por lo general, no debe haber ninguna delay()
llamada ni ninguna otra API de la que dependa delay()
).
Si tiene una tarea larga para activar en el desencadenante de eventos externos, podría utilizar un programador cooperativo y agregarle una nueva tarea desde su función de interrupción.
Un segundo punto importante sobre las interrupciones es que su número es limitado (por ejemplo, solo 2 en UNO). Entonces, si comienza a tener más eventos externos, necesitará implementar algún tipo de multiplexación de todas las entradas en una sola, y que su función de interrupción determine qué entrada multiplexada fue el disparador real.
Una solución simple es usar un Programador . Hay varias implementaciones. Esto describe brevemente uno que está disponible para placas basadas en AVR y SAM. Básicamente, una sola llamada comenzará una tarea; "bosquejo dentro de un bosquejo".
#include <Scheduler.h>
....
void setup()
{
...
Scheduler.start(taskSetup, taskLoop);
}
Scheduler.start () agregará una nueva tarea que ejecutará taskSetup una vez y luego llamará repetidamente taskLoop tal como funciona el boceto Arduino. La tarea tiene su propia pila. El tamaño de la pila es un parámetro opcional. El tamaño predeterminado de la pila es de 128 bytes.
Para permitir el cambio de contexto, las tareas deben llamar a yield () o delay () . También hay una macro de soporte para esperar una condición.
await(Serial.available());
La macro es azúcar sintáctica para lo siguiente:
while (!(Serial.available())) yield();
Await también se puede usar para sincronizar tareas. A continuación se muestra un fragmento de ejemplo:
volatile int taskEvent = 0;
#define signal(evt) do { await(taskEvent == 0); taskEvent = evt; } while (0)
...
void taskLoop()
{
await(taskEvent);
switch (taskEvent) {
case 1:
...
}
taskEvent = 0;
}
...
void loop()
{
...
signal(1);
}
Para más detalles ver los ejemplos . Hay ejemplos de múltiples parpadeos de LED para eliminar el botón de rebote y un shell simple con lectura de línea de comando sin bloqueo. Se pueden usar plantillas y espacios de nombres para ayudar a estructurar y reducir el código fuente. El siguiente bosquejo muestra cómo usar las funciones de plantilla para el parpadeo múltiple. Es suficiente con 64 bytes para la pila.
#include <Scheduler.h>
template<int pin> void setupBlink()
{
pinMode(pin, OUTPUT);
}
template<int pin, unsigned int ms> void loopBlink()
{
digitalWrite(pin, HIGH);
delay(ms);
digitalWrite(pin, LOW);
delay(ms);
}
void setup()
{
Scheduler.start(setupBlink<11>, loopBlink<11,500>, 64);
Scheduler.start(setupBlink<12>, loopBlink<12,250>, 64);
Scheduler.start(setupBlink<13>, loopBlink<13,1000>, 64);
}
void loop()
{
yield();
}
También hay un punto de referencia para dar una idea del rendimiento, es decir, el tiempo para comenzar la tarea, el cambio de contexto, etc.
Por último, hay algunas clases de soporte para la sincronización y comunicación a nivel de tarea; Cola y semáforo .
De un encantamiento anterior de este foro, la siguiente pregunta / respuesta fue trasladada a Ingeniería Eléctrica. Tiene un código arduino de muestra para parpadear un LED usando una interrupción del temporizador mientras usa el bucle principal para hacer IO en serie.
Volver a publicar:
Las interrupciones son una forma común de hacer las cosas mientras sucede algo más. En el ejemplo a continuación, el LED parpadea sin usar delay()
. Siempre que se Timer1
dispara, isrBlinker()
se llama a la rutina de servicio de interrupción (ISR) . Enciende / apaga el LED.
Para mostrar que otras cosas pueden suceder simultáneamente, loop()
escribe repetidamente foo / bar en el puerto serie independientemente del parpadeo del LED.
#include "TimerOne.h"
int led = 13;
void isrBlinker()
{
static bool on = false;
digitalWrite( led, on ? HIGH : LOW );
on = !on;
}
void setup() {
Serial.begin(9600);
Serial.flush();
Serial.println("Serial initialized");
pinMode(led, OUTPUT);
// initialize the ISR blinker
Timer1.initialize(1000000);
Timer1.attachInterrupt( isrBlinker );
}
void loop() {
Serial.println("foo");
delay(1000);
Serial.println("bar");
delay(1000);
}
Esta es una demostración muy simple. Los ISR pueden ser mucho más complejos y pueden ser activados por temporizadores y eventos externos (pines). Muchas de las bibliotecas comunes se implementan mediante ISR.
También llegué a este tema mientras implementaba una pantalla LED de matriz.
En una palabra, puede construir un planificador de sondeo utilizando la función millis () y la interrupción del temporizador en Arduino.
Sugiero los siguientes artículos de Bill Earl:
https://learn.adafruit.com/multi-tasking-the-arduino-part-1/overview
https://learn.adafruit.com/multi-tasking-the-arduino-part-2/overview
https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview
También puedes probar mi biblioteca ThreadHandler
https://bitbucket.org/adamb3_14/threadhandler/src/master/
Utiliza un programador de interrupción para permitir el cambio de contexto sin transmitir en yield () o delay ().
Creé la biblioteca porque necesitaba tres subprocesos y dos para ejecutarlos en un momento preciso, sin importar lo que estuvieran haciendo los demás. El primer hilo manejó la comunicación en serie. El segundo estaba ejecutando un filtro de Kalman usando la multiplicación de matriz flotante con la biblioteca Eigen. Y el tercero era un hilo de bucle de control de corriente rápido que tenía que poder interrumpir los cálculos de la matriz.
Cada hilo cíclico tiene una prioridad y un punto. Si un subproceso, con mayor prioridad que el subproceso actual en ejecución, alcanza su próximo tiempo de ejecución, el programador pausará el subproceso actual y cambiará al de mayor prioridad. Una vez que el subproceso de alta prioridad completa su ejecución, el planificador vuelve al subproceso anterior.
El esquema de programación de la biblioteca ThreadHandler es el siguiente:
Los hilos se pueden crear a través de la herencia de c ++
class MyThread : public Thread
{
public:
MyThread() : Thread(priority, period, offset){}
virtual ~MyThread(){}
virtual void run()
{
//code to run
}
};
MyThread* threadObj = new MyThread();
O a través de createThread y una función lambda
Thread* myThread = createThread(priority, period, offset,
[]()
{
//code to run
});
Los objetos de subproceso se conectan automáticamente al ThreadHandler cuando se crean.
Para iniciar la ejecución de los objetos de hilo creados, llame a:
ThreadHandler::getInstance()->enableThreadExecution();
Y aquí hay otra biblioteca multitarea cooperativa de microprocesador: PQRST: una cola prioritaria para ejecutar tareas simples.
En este modelo, un subproceso se implementa como una subclase de a Task
, que está programado para algún tiempo futuro (y posiblemente reprogramado a intervalos regulares si, como es común, en su LoopTask
lugar se subclasifica ). El run()
método del objeto se llama cuando la tarea se vence. El run()
método realiza el trabajo debido y luego regresa (este es el bit cooperativo); normalmente mantendrá algún tipo de máquina de estado para administrar sus acciones en invocaciones sucesivas (un ejemplo trivial es la light_on_p_
variable en el ejemplo a continuación). Requiere un ligero replanteamiento de cómo organizar su código, pero ha demostrado ser muy flexible y robusto en un uso bastante intensivo.
Es agnóstico acerca de las unidades de tiempo, por lo que es tan feliz correr en unidades de millis()
como micros()
, o cualquier otro tic que sea conveniente.
Aquí está el programa 'blink' implementado usando esta biblioteca. Esto muestra solo una tarea en ejecución: otras tareas normalmente se crearían y comenzarían dentro setup()
.
#include "pqrst.h"
class BlinkTask : public LoopTask {
private:
int my_pin_;
bool light_on_p_;
public:
BlinkTask(int pin, ms_t cadence);
void run(ms_t) override;
};
BlinkTask::BlinkTask(int pin, ms_t cadence)
: LoopTask(cadence),
my_pin_(pin),
light_on_p_(false)
{
// empty
}
void BlinkTask::run(ms_t t)
{
// toggle the LED state every time we are called
light_on_p_ = !light_on_p_;
digitalWrite(my_pin_, light_on_p_);
}
// flash the built-in LED at a 500ms cadence
BlinkTask flasher(LED_BUILTIN, 500);
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
flasher.start(2000); // start after 2000ms (=2s)
}
void loop()
{
Queue.run_ready(millis());
}
run()
que se llama al método, no se interrumpe, por lo que tiene la responsabilidad de finalizarlo razonablemente de inmediato. Normalmente, sin embargo, hará su trabajo y luego se reprogramará (posiblemente de forma automática, en el caso de una subclase de LoopTask
) para algún tiempo futuro. Un patrón común es que la tarea mantenga alguna máquina de estado interna (un ejemplo trivial es el light_on_p_
estado anterior) para que se comporte adecuadamente cuando sea el próximo vencimiento.
run()
. Esto está en contraste con los hilos cooperativos, que pueden producir la CPU, por ejemplo, llamando yield()
o delay()
. O subprocesos preventivos, que se pueden programar en cualquier momento. Siento que la distinción es importante, ya que he visto que muchas personas que vienen por aquí buscando hilos lo hacen porque prefieren escribir código de bloqueo en lugar de máquinas de estado. El bloqueo de hilos reales que producen la CPU está bien. Bloquear tareas RtC no lo es.