¿Cómo puedo crear varios subprocesos en ejecución?

¿Hay alguna forma de que pueda ejecutar varias partes del programa juntas sin hacer varias cosas en el mismo bloque de código?

Una hilo esperando un dispositivo externo mientras también parpadea un LED en otro hilo.

Comentarios

  • Probablemente primero debería preguntarse si realmente necesita hilos. Los temporizadores ya pueden estar bien para sus necesidades y son compatibles de forma nativa con Arduino.
  • Es posible que también desee consultar Uzebox. Es ‘ una consola de videojuegos casera de dos chips. Entonces, aunque no es ‘ t exactamente un Arduino, todo el sistema se basa en interrupciones. Por lo tanto, el audio, el video, los controles, etc. son controlados por interrupciones mientras que el programa principal no ‘ no tiene que preocuparse por nada de eso. Puede ser una buena referencia.

Respuesta

No hay soporte para multiprocesos ni subprocesos múltiples en el Arduino. Sin embargo, puedes hacer algo parecido a varios subprocesos con algún software.

Quieres ver Protothreads :

Los protothreads son subprocesos sin pila extremadamente livianos diseñados para sistemas con limitaciones de memoria severas, como pequeños sistemas integrados o nodos de redes de sensores inalámbricos. Protothreads proporciona ejecución de código lineal para sistemas controlados por eventos implementados en C. Protothreads puede usarse con o sin un sistema operativo subyacente para proporcionar controladores de eventos de bloqueo. Protothreads proporciona un flujo secuencial de control sin máquinas de estado complejas o multi-threading completo.

Por supuesto, hay un ejemplo de Arduino aquí con código de ejemplo . Esta SO pregunta también podría ser útil.

ArduinoThread es una buena también.

Comentarios

Respuesta

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.

http://arduino.cc/en/Reference/Interrupts

Respuesta

Es posible realizar múltiples subprocesos del lado del software en Uno. El subproceso a nivel de hardware no es compatible.

Para lograr el subproceso múltiple, se requerirá la implementación de un planificador básico y el mantenimiento de un proceso o 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 ser 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 LED y function2 realizar cálculos flotantes. Será responsabilidad de cada tarea (función) cumplir con el tiempo asignado.

Con suerte, esto debería ser suficiente para empezar.

Comentarios

  • No estoy seguro de si hablaría sobre » hilos » cuando se utiliza un planificador no preventivo. Por cierto, dicho programador ya existe como una biblioteca arduino: arduino.cc/en/Reference/Scheduler
  • @jfpoilpret – Cooperative El multihilo es algo real.
  • Sí, ‘ ¡tienes razón! Mi error; había pasado tanto tiempo que no me había enfrentado al multiproceso cooperativo que, en mi opinión, el multiproceso tenía que ser preventivo.

Responder

Según la descripción de sus requisitos:

  • un hilo esperando un dispositivo externo
  • un hilo parpadeando un LED

Parece que podrías usar una interrupción de Arduino para el primer «hilo» (de hecho, prefiero llamarlo «tarea»).

Las interrupciones de Arduino pueden llamar a una función (tu código) basada en una evento (nivel de voltaje o cambio de nivel en un pin de entrada digital), que activará su función inmediatamente.

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 (normalmente, no debería haber una llamada delay() o cualquier otra API que dependa de delay()).

Si tiene una tarea larga para activar al activarse un evento externo, entonces podría usar un programador cooperativo y agregarle una nueva tarea desde su función de interrupción.

Una segunda cosa importante El punto 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ía implementar algún tipo de multiplexación de todas las entradas en una, y hacer que su función de interrupción determine qué entrada multiplexada fue el disparador real.

Respuesta

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 iniciará una tarea; «boceto dentro de un boceto».

#include <Scheduler.h> .... void setup() { ... Scheduler.start(taskSetup, taskLoop); } 

Scheduler.start () agregará una nueva tarea que ejecutará taskSetup una vez y luego llamará repetidamente a taskLoop como el El boceto de Arduino funciona. La tarea tiene su propia pila. El tamaño de la pila es un parámetro opcional. El tamaño de pila predeterminado es 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 puede utilizarse 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 obtener más detalles, consulte los ejemplos . Hay ejemplos desde el parpadeo de múltiples LED hasta el botón antirrebote y un shell simple con lectura de línea de comando sin bloqueo. Se pueden utilizar plantillas y espacios de nombres para ayudar a estructurar y reducir el código fuente. Debajo de sketch se muestra cómo usar las funciones de plantilla para múltiples parpadeos. 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 una benchmark para dar una idea del rendimiento, es decir, el tiempo para iniciar la tarea, cambiar 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 .

Respuesta

También llegué a este tema mientras implementaba una pantalla LED de matriz.

En una palabra , puede construir un programador de sondeo usando 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

Respuesta

De un encantamiento anterior de este foro, la siguiente pregunta / respuesta se movió a Ingeniería Eléctrica. Tiene un código arduino de muestra para hacer parpadear un LED usando una interrupción del temporizador mientras usa el bucle principal para hacer IO en serie.

https://electronics.stackexchange.com/questions/67089/how-can-i-control-things-without-using-delay/67091#67091

Volver a publicar:

Las interrupciones son una forma común de hacer las cosas mientras ocurre otra cosa. En el siguiente ejemplo, el LED parpadea sin usar delay(). Siempre que Timer1 se activa, se llama a la rutina de servicio de interrupción (ISR) isrBlinker(). 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 activarse mediante temporizadores y eventos externos (pines). Muchas de las bibliotecas comunes se implementan mediante ISR.

Respuesta

También puedes probar mi biblioteca ThreadHandler

https://bitbucket.org/adamb3_14/threadhandler/src/master/

Utiliza un programador de interrupciones para permitir el cambio de contexto sin retransmisión yield () o delay ().

Creé la biblioteca porque necesitaba tres subprocesos y necesitaba que dos de ellos se ejecutaran en un momento preciso sin importar lo que estuvieran haciendo los demás. El primer hilo manejó la comunicación en serie. El segundo fue ejecutar un filtro de Kalman utilizando la multiplicación de matrices flotantes 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.

Cómo funciona

Cada hilo cíclico tiene una prioridad y un punto. Si un subproceso, con mayor prioridad que el subproceso en ejecución actual, alcanza su siguiente tiempo de ejecución, el planificador pausará el subproceso actual y cambiará al de mayor prioridad. Una vez que el hilo de alta prioridad completa su ejecución, el programador vuelve al hilo anterior.

Reglas de programación

El esquema de programación de la biblioteca ThreadHandler es el siguiente:

  1. La prioridad más alta primero.
  2. Si la prioridad es la misma, entonces el hilo con la fecha límite más temprana se ejecuta primero.
  3. Si dos hilos tienen el mismo plazo, entonces el primer hilo creado se ejecutará primero.
  4. Un subproceso solo puede ser interrumpido por subprocesos con mayor prioridad.
  5. Una vez que un subproceso se está ejecutando, bloqueará la ejecución de todos los subprocesos con menor prioridad hasta que vuelva la función de ejecución.
  6. La función de bucle tiene prioridad -128 en comparación con los subprocesos ThreadHandler.

Cómo usar

Los subprocesos se pueden crear mediante la herencia c ++

 class MyThread : public Thread { public: MyThread() : Thread(priority, period, offset){} virtual ~MyThread(){} virtual void run() { //code to run } }; MyThread* threadObj = new MyThread();  

O mediante createThread y una función lambda

 Thread* myThread = createThread(priority, period, offset, []() { //code to run });  

Los objetos Thread se conectan automáticamente al ThreadHandler cuando se crean.

Para iniciar la ejecución de los objetos thread creados, llame a:

 ThreadHandler::getInstance()->enableThreadExecution();  

Una swer

Y aquí hay otra biblioteca multitarea cooperativa de microprocesador: PQRST: una cola de prioridad para ejecutar tareas simples.

En este modelo, un hilo se implementa como una subclase de un Task, que está programado para algún tiempo futuro (y posiblemente reprogramado a intervalos regulares, si, como es común, subclasifica LoopTask en su lugar). El método run() del objeto se llama cuando la tarea vence. El método run() hace el trabajo debido y luego regresa (este es el bit cooperativo); Por lo general, mantendrá algún tipo de máquina de estado para administrar sus acciones en invocaciones sucesivas (un ejemplo trivial es la variable light_on_p_ en el ejemplo siguiente). Requiere un ligero replanteamiento de cómo organiza tu código, pero ha demostrado ser muy flexible y robusto en un uso bastante intensivo.

Es independiente de las unidades de tiempo, por lo que es igual de feliz que se ejecute en unidades de millis() como micros(), o cualquier otro tick 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 de 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()); } 

Comentarios

  • Estas son tareas de «ejecución hasta el final», ¿verdad?
  • @EdgarBonet I ‘ No estoy seguro de lo que quieres decir. Después de que se llama al método run(), no se interrumpe, por lo que tiene la responsabilidad de finalizar con razonable rapidez. Por lo general, sin embargo, ‘ hará su trabajo y luego se reprogramará (posiblemente automáticamente, en el caso de una subclase de LoopTask) para algunos 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 la próxima vez.
  • Así que sí, esas son tareas de ejecución hasta el final (RtC): ninguna tarea puede ejecutarse antes de que la actual complete su ejecución al regresar de run(). Esto contrasta con los subprocesos cooperativos, que pueden generar la CPU, por ejemplo, llamando a yield() o delay(). O subprocesos preventivos, que se pueden programar en cualquier momento. Creo que la distinción es importante, ya que he visto que muchas personas que vienen por aquí en busca de hilos lo hacen porque prefieren escribir código de bloqueo en lugar de máquinas de estado. Bloquear subprocesos reales que producen la CPU está bien. Bloquear tareas RtC no lo es.
  • @EdgarBonet Es ‘ una distinción útil, sí. Consideraría tanto este estilo como los hilos de estilo de rendimiento como simplemente diferentes estilos de hilo cooperativo, en contraposición a los hilos preventivos, pero es ‘ cierto que requieren un enfoque diferente para codificarlos. Sería interesante ver una comparación reflexiva y en profundidad de los diversos enfoques mencionados aquí; una buena biblioteca no mencionada anteriormente es protothreads . En ambos encuentro cosas para criticar, pero también para alabar. Yo (por supuesto) prefiero mi enfoque, porque parece más explícito y no necesita pilas adicionales.
  • (corrección: protothreads fue mencionado, en @sachleen ‘ s respuesta )

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *