C'è un modo in cui posso far funzionare più parti del programma senza fare più cose nello stesso blocco di codice?
Un thread in attesa di un dispositivo esterno mentre lampeggia anche un LED in un altro thread.
C'è un modo in cui posso far funzionare più parti del programma senza fare più cose nello stesso blocco di codice?
Un thread in attesa di un dispositivo esterno mentre lampeggia anche un LED in un altro thread.
Risposte:
Non esiste supporto multi-processo, né multi-threading su Arduino. Puoi fare qualcosa vicino a più thread con alcuni software però.
Vuoi dare un'occhiata a Protothreads :
I prototipi sono thread stackless estremamente leggeri progettati per sistemi con gravi limiti di memoria, come piccoli sistemi integrati o nodi di rete di sensori wireless. I prototipi forniscono l'esecuzione di codice lineare per i sistemi guidati da eventi implementati in C. I prototipi possono essere utilizzati con o senza un sistema operativo sottostante per fornire gestori di eventi bloccanti. I prototipi offrono un flusso sequenziale di controllo senza macchine a stati complessi o multi-threading completo.
Naturalmente, c'è un esempio di Arduino qui con un codice di esempio . Anche questa domanda SO potrebbe essere utile.
Anche ArduinoThread è buono.
Gli Arduino basati su AVR non supportano il threading (hardware), non ho familiarità con gli Arduino basati su ARM. Un modo per aggirare questa limitazione è l'uso di interruzioni, in particolare interruzioni a tempo. È possibile programmare un timer per interrompere la routine principale ogni tanti microsecondi, per eseguire un'altra routine specifica.
È possibile eseguire il multi-threading lato software su Uno. Il threading a livello hardware non è supportato.
Per ottenere il multithreading, sarà necessario implementare un programmatore di base e mantenere un processo o un elenco di attività per tenere traccia delle diverse attività che devono essere eseguite.
La struttura di un pianificatore non preventivo molto semplice sarebbe come:
//Pseudocode
void loop()
{
for(i=o; i<n; i++)
run(tasklist[i] for timelimit):
}
Qui, tasklist
può essere una matrice di puntatori a funzione.
tasklist [] = {function1, function2, function3, ...}
Con ogni funzione del modulo:
int function1(long time_available)
{
top:
//Do short task
if (run_time<time_available)
goto top;
}
Ogni funzione può eseguire un'attività separata come l' function1
esecuzione di manipolazioni dei LED e l' function2
esecuzione di calcoli float. Sarà responsabilità di ciascun compito (funzione) aderire al tempo assegnato ad esso.
Spero che questo sia sufficiente per iniziare.
Secondo la descrizione delle tue esigenze:
Sembra che potresti usare un interrupt Arduino per il primo "thread" (preferirei chiamarlo "compito" in effetti).
Gli interrupt di Arduino possono chiamare una funzione (il tuo codice) in base a un evento esterno (livello di tensione o variazione di livello su un pin di ingresso digitale), che attiverà immediatamente la tua funzione.
Tuttavia, un punto importante da tenere presente con gli interrupt è che la funzione chiamata dovrebbe essere il più veloce possibile (in genere, non dovrebbero esserci delay()
chiamate o altre API da cui dipenderebbero delay()
).
Se hai una lunga attività da attivare al trigger di un evento esterno, allora potresti potenzialmente utilizzare uno scheduler cooperativo e aggiungere una nuova attività dalla tua funzione di interruzione.
Un secondo punto importante sugli interrupt è che il loro numero è limitato (ad es. Solo 2 su UNO). Quindi, se inizi ad avere più eventi esterni, dovresti implementare un qualche tipo di multiplexing di tutti gli input in uno e la tua funzione di interruzione determinerà quale inutile multiplex era il trigger effettivo.
Una soluzione semplice è utilizzare un Scheduler . Esistono diverse implementazioni. Questo ne descrive brevemente uno disponibile per schede basate su AVR e SAM. Fondamentalmente una singola chiamata avvierà un'attività; "schizzo all'interno di uno schizzo".
#include <Scheduler.h>
....
void setup()
{
...
Scheduler.start(taskSetup, taskLoop);
}
Scheduler.start () aggiungerà una nuova attività che eseguirà taskSetup una volta, quindi chiamerà ripetutamente taskLoop proprio mentre lo schizzo di Arduino funziona. L'attività ha il proprio stack. La dimensione dello stack è un parametro facoltativo. La dimensione predefinita dello stack è di 128 byte.
Per consentire il cambio di contesto, le attività devono chiamare yield () o delay () . Esiste anche una macro di supporto per l'attesa di una condizione.
await(Serial.available());
La macro è zucchero sintattico per quanto segue:
while (!(Serial.available())) yield();
In attesa può anche essere utilizzato per sincronizzare le attività. Di seguito è riportato un frammento di esempio:
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);
}
Per ulteriori dettagli vedere gli esempi . Ci sono esempi da più lampeggi LED per pulsante di rimbalzo e una semplice shell con lettura della riga di comando non bloccante. Modelli e spazi dei nomi possono essere utilizzati per aiutare a strutturare e ridurre il codice sorgente. Lo schizzo seguente mostra come utilizzare le funzioni del modello per il multi-lampeggiamento. È sufficiente con 64 byte per lo stack.
#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();
}
C'è anche un benchmark per dare un'idea delle prestazioni, ovvero il tempo di iniziare l'attività, il cambio di contesto, ecc.
Infine, ci sono alcune classi di supporto per la sincronizzazione e la comunicazione a livello di attività; Coda e semaforo .
Da un precedente incantesimo di questo forum, la seguente domanda / risposta è stata spostata in Ingegneria Elettrica. Ha un codice arduino di esempio per far lampeggiare un LED utilizzando un interrupt del timer mentre si utilizza il loop principale per eseguire l'IO seriale.
Repost:
Gli interrupt sono un modo comune per fare le cose mentre sta succedendo qualcos'altro. Nell'esempio seguente, il LED lampeggia senza utilizzare delay()
. Ogni volta che si Timer1
attiva, isrBlinker()
viene chiamata la routine di servizio di interruzione (ISR) . Accende / spegne il LED.
Per mostrare che possono accadere simultaneamente altre cose, loop()
scrive ripetutamente foo / bar sulla porta seriale indipendentemente dal lampeggio 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);
}
Questa è una demo molto semplice. Gli ISR possono essere molto più complessi e possono essere attivati da timer ed eventi esterni (pin). Molte delle librerie comuni sono implementate utilizzando gli ISR.
Sono arrivato anche a questo argomento durante l'implementazione di un display a LED a matrice.
In una parola, è possibile creare uno scheduler di polling utilizzando la funzione millis () e l'interruzione del timer in Arduino.
Suggerisco i seguenti articoli di 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
Puoi anche provare la mia libreria ThreadHandler
https://bitbucket.org/adamb3_14/threadhandler/src/master/
Utilizza uno scheduler di interruzione per consentire il cambio di contesto senza inoltro su yield () o delay ().
Ho creato la libreria perché avevo bisogno di tre thread e ne avevo bisogno di due per essere eseguiti in un momento preciso, indipendentemente da cosa facessero gli altri. Il primo thread ha gestito la comunicazione seriale. Il secondo stava eseguendo un filtro Kalman usando la moltiplicazione della matrice float con la libreria Eigen. E il terzo era un thread loop di controllo corrente veloce che doveva essere in grado di interrompere i calcoli della matrice.
Ogni thread ciclico ha una priorità e un punto. Se un thread, con priorità più alta rispetto al thread in esecuzione corrente, raggiunge il tempo di esecuzione successivo, lo scheduler mette in pausa il thread corrente e passa a quello con priorità più alta. Una volta completata l'esecuzione del thread ad alta priorità, lo scheduler ritorna al thread precedente.
Lo schema di pianificazione della libreria ThreadHandler è il seguente:
I thread possono essere creati tramite ereditarietà c ++
class MyThread : public Thread
{
public:
MyThread() : Thread(priority, period, offset){}
virtual ~MyThread(){}
virtual void run()
{
//code to run
}
};
MyThread* threadObj = new MyThread();
O tramite createThread e una funzione lambda
Thread* myThread = createThread(priority, period, offset,
[]()
{
//code to run
});
Gli oggetti thread si connettono automaticamente a ThreadHandler quando vengono creati.
Per avviare l'esecuzione degli oggetti thread creati, chiamare:
ThreadHandler::getInstance()->enableThreadExecution();
Ed ecco un'altra libreria multitasking cooperativa a microprocessore: PQRST: una coda di priorità per l'esecuzione di attività semplici.
In questo modello, un thread viene implementato come sottoclasse di a Task
, che è programmato per un certo periodo di tempo futuro (e possibilmente riprogrammato a intervalli regolari, se, come è comune, LoopTask
invece subclasse ). Il run()
metodo dell'oggetto viene chiamato quando l'attività diventa scaduta. Il run()
metodo fa il dovuto e poi ritorna (questo è il bit cooperativo); manterrà in genere una sorta di macchina a stati per gestire le sue azioni su invocazioni successive (un esempio banale è la light_on_p_
variabile nell'esempio seguente). Richiede un leggero ripensamento su come organizzare il codice, ma si è dimostrato molto flessibile e robusto in un uso abbastanza intenso.
È agnostico riguardo alle unità di tempo, quindi è felice correre in unità di millis()
come micros()
, o qualsiasi altro segno di spunta che è conveniente.
Ecco il programma "lampeggiante" implementato usando questa libreria. Questo mostra solo una singola attività in esecuzione: altre attività sarebbero in genere create e avviate all'interno 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()
metodo è stato chiamato, non viene interrotto, quindi ha la responsabilità di terminare ragionevolmente tempestivamente. In genere, tuttavia, farà il suo lavoro, quindi riprogrammare se stesso (possibilmente automaticamente, nel caso di una sottoclasse di LoopTask
) per qualche tempo futuro. Un modello comune è che l'attività mantenga una macchina a stati interna (un esempio banale è lo light_on_p_
stato sopra) in modo che si comporti adeguatamente alla prossima scadenza.
run()
. Ciò è in contrasto con i thread cooperativi, che possono produrre la CPU, ad esempio, chiamando yield()
o delay()
. O discussioni preventive, che possono essere programmate in qualsiasi momento. Ritengo che la distinzione sia importante, poiché ho visto che molte persone che vengono qui in cerca di thread lo fanno perché preferiscono scrivere codice di blocco piuttosto che macchine a stati. Il blocco di thread reali che producono la CPU va bene. Il blocco delle attività RtC non lo è.