Cè un modo in cui posso avere più parti del programma in esecuzione insieme senza eseguire più cose nello stesso blocco di codice?
Uno thread in attesa di un dispositivo esterno mentre lampeggia anche un LED in un altro thread.
Commenti
- Probabilmente dovresti prima chiederti se hai davvero bisogno di thread. I timer potrebbero già andare bene per le tue esigenze e sono supportati nativamente su Arduino.
- Potresti voler controllare anche Uzebox. ‘ è una console per videogiochi homebrew a due chip. Quindi, sebbene ‘ non sia esattamente un Arduino, lintero sistema è costruito su interrupt. Quindi audio, video, controlli, ecc. Sono tutti controllati dagli interrupt mentre il programma principale non ‘ deve preoccuparsi di niente di tutto ciò. Può essere un buon riferimento.
Risposta
Non cè supporto multi-processo, né multi-threading su lArduino. Puoi fare qualcosa di simile a più thread con alcuni software.
Ti consigliamo di dare unocchiata a Protothreads :
I protothread sono thread stackless estremamente leggeri progettati per sistemi con forti limitazioni di memoria, come piccoli sistemi embedded o nodi di rete di sensori wireless. I protothread forniscono unesecuzione lineare del codice per i sistemi basati su eventi implementati in C. I protothread possono essere utilizzati con o senza un sistema operativo sottostante per fornire gestori di eventi di blocco. I protothread forniscono un flusso sequenziale di controllo senza macchine a stati complesse o multi-threading completo.
Naturalmente, cè un esempio di Arduino qui con codice di esempio . Anche questa domanda SO potrebbe essere utile.
ArduinoThread è anche una buona.
Commenti
- Nota che Arduino DUE ha uneccezione a questo, con più loop di controllo: arduino.cc/en/Tutorial/MultipleBlinks
Risposta
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 è luso di interrupt, in particolare interrupt a tempo. Puoi programmare un timer per interrompere la routine principale ogni tanti microsecondi, per eseguire unaltra routine specifica.
Risposta
È possibile eseguire il multi-threading lato software su Uno. Il threading a livello hardware non è supportato.
Per ottenere il multithreading, sarà necessaria limplementazione di uno scheduler di base e il mantenimento di un processo o di un elenco di attività per tenere traccia delle diverse attività che devono essere eseguite.
La struttura di uno scheduler non preventivo molto semplice sarebbe come:
//Pseudocode void loop() { for(i=o; i<n; i++) run(tasklist[i] for timelimit): }
Qui, tasklist
può essere un array di puntatori a funzione.
tasklist [] = {function1, function2, function3, ...}
Con ciascuna funzione della forma:
int function1(long time_available) { top: //Do short task if (run_time<time_available) goto top; }
Ogni funzione può eseguire unattività separata come function1
eseguire manipolazioni LED e function2
eseguire calcoli in virgola mobile. Sarà responsabilità di ogni attività (funzione) rispettare il tempo assegnato.
Si spera che questo sia sufficiente per iniziare.
Commenti
- Non sono sicuro che parlerei di ” thread ” quando si utilizza uno scheduler non preventivo. A proposito, tale scheduler esiste già come libreria arduino: arduino.cc/en/Reference/Scheduler
- @jfpoilpret – Cooperative il multithreading è una cosa reale.
- Sì, ‘ hai ragione! Errore mio; era passato così tanto tempo che non avevo affrontato il multithreading cooperativo che nella mia mente il multithreading doveva essere preventivo.
Answer
Secondo la descrizione dei tuoi requisiti:
- un thread in attesa di un dispositivo esterno
- un thread che lampeggia un LED
Sembra che potresti usare un interrupt Arduino per il primo “thread” (preferirei chiamarlo “task” in effetti).
Gli interrupt Arduino possono chiamare una funzione (il tuo codice) basato su un evento (livello di tensione o cambio di livello su un pin di ingresso digitale), che attiverà immediatamente la tua funzione.
Tuttavia, un punto importante da tenere a mente con gli interrupt è che la funzione chiamata dovrebbe essere il più veloce possibile (in genere, non dovrebbe essere presente alcuna chiamata delay()
o qualsiasi altra API che dipenda da delay()
).
Se hai un compito lungo da attivare allattivazione di un evento esterno, potresti potenzialmente utilizzare uno scheduler cooperativo e aggiungervi una nuova attività dalla funzione di interruzione.
Un secondo importante punto sulle interruzioni è che il loro numero è limitato (ad esempio 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 fare in modo che la tua funzione di interrupt determini quale inut multiplexing era il trigger effettivo.
Risposta
Una soluzione semplice è utilizzare uno Scheduler . Esistono diverse implementazioni. Questo ne descrive brevemente uno disponibile per le schede basate su AVR e SAM. Fondamentalmente una singola chiamata avvierà unattività; “sketch within a sketch”.
#include <Scheduler.h> .... void setup() { ... Scheduler.start(taskSetup, taskLoop); }
Scheduler.start () aggiungerà una nuova attività che eseguirà taskSetup una volta e quindi richiamerà ripetutamente taskLoop proprio come il Lo schizzo di Arduino funziona. Lattività ha il proprio stack. La dimensione dello stack è un parametro facoltativo. La dimensione predefinita dello stack è 128 byte.
Per consentire il cambio di contesto, le attività devono chiamare yield () o delay ( ) . Cè anche una macro di supporto per lattesa di una condizione.
await(Serial.available());
La macro è zucchero sintattico per quanto segue:
while (!(Serial.available())) yield();
Await può anche essere utilizzato per sincronizzare le attività. Di seguito è riportato uno snippet 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ù LED lampeggianti a pulsante antirimbalzo e una semplice shell con lettura della riga di comando non bloccante. I modelli e gli spazi dei nomi possono essere utilizzati per aiutare a strutturare e ridurre il codice sorgente. Di seguito schizzo mostra come utilizzare le funzioni del modello per il lampeggio multiplo. È 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(); }
Esiste anche un benchmark per dare unidea delle prestazioni, cioè del tempo per avviare lattività, cambiare contesto, ecc.
Infine, ci sono alcune classi di supporto per la sincronizzazione e la comunicazione a livello di attività; Coda e Semaforo .
Risposta
Sono anche arrivato a questo argomento durante limplementazione di un display LED a matrice.
In una parola , puoi creare uno scheduler polling utilizzando la funzione millis () e linterrupt 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
Risposta
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 lIO seriale.
Ripubblicare:
Gli interrupt sono un modo comune per fare le cose mentre è in corso qualcosaltro. Nellesempio seguente, il LED lampeggia senza utilizzare delay()
. Ogni volta che viene attivato Timer1
, viene richiamata la routine di servizio interrupt (ISR) isrBlinker()
. Accende / spegne il LED.
Per mostrare che altre cose possono accadere contemporaneamente, loop()
scrive ripetutamente foo / bar sulla porta seriale indipendentemente dal lampeggiamento 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.
Risposta
Potresti 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 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 quello che stavano facendo gli altri. Il primo thread ha gestito la comunicazione seriale. Il secondo stava eseguendo un filtro di Kalman utilizzando la moltiplicazione di matrici float con la libreria Eigen. E il terzo era un thread del ciclo di controllo della corrente veloce che doveva essere in grado di interrompere i calcoli della matrice.
Come funziona
Ogni thread ciclico ha una priorità e un punto. Se un thread, con priorità più alta del thread in esecuzione corrente, raggiunge lora di esecuzione successiva, lo scheduler metterà in pausa il thread corrente e passerà a quello con priorità più alta. Una volta che il thread ad alta priorità completa la sua esecuzione, lo scheduler torna al thread precedente.
Regole di pianificazione
Lo schema di pianificazione della libreria ThreadHandler è il seguente:
- Prima la priorità più alta.
- Se la priorità è la stessa, viene eseguito per primo il thread con la prima scadenza.
- Se due thread hanno la stessa scadenza, verrà eseguito per primo il primo thread creato.
- Un thread può essere interrotto solo da thread con priorità più alta.
- Una volta che un thread è in esecuzione, bloccherà lesecuzione di tutti i thread con priorità inferiore finché non viene restituita la funzione run.
- La funzione loop ha priorità -128 rispetto ai thread ThreadHandler.
Come utilizzare
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 lesecuzione degli oggetti thread creati chiama:
ThreadHandler::getInstance()->enableThreadExecution();
An swer
Ed ecco unaltra libreria multitasking cooperativa di microprocessore: PQRST: una coda prioritaria per lesecuzione di attività semplici.
- Home page
- Documentazione a livello di classe
- Repository , con download ed elenco dei problemi
In questo modello, un thread è implementato come una sottoclasse di un Task
, che è programmato per un certo periodo di tempo futuro (e possibilmente riprogrammato a intervalli regolari, se, come è comune, sottoclasse LoopTask
invece). Il metodo run()
delloggetto viene chiamato quando lattività scade. Il metodo run()
fa il dovuto lavoro e poi ritorna (questo è il bit cooperativo); tipicamente manterrà una sorta di macchina a stati per gestire le sue azioni su invocazioni successive (un esempio banale è la variabile light_on_p_
nellesempio sotto). Richiede un leggero ripensamento di come tu organizzare il codice, ma si è dimostrato molto flessibile e robusto in un uso abbastanza intenso.
È agnostico sulle unità di tempo, quindi è altrettanto felice di funzionare in unità di millis()
come micros()
, o qualsiasi altro segno di spunta conveniente.
Ecco il programma “blink” implementato utilizzando questa libreria. Questo mostra solo una singola attività in esecuzione: altre attività normalmente vengono create e avviate allinterno di 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()); }
Commenti
- Queste sono attività “da eseguire fino al completamento”, giusto?
- @EdgarBonet I ‘ Non sono sicuro di cosa intendi. Dopo che il metodo
run()
è stato chiamato, non viene interrotto, quindi ha la responsabilità di terminare ragionevolmente tempestivamente. In genere, tuttavia, ‘ farà il suo lavoro quindi riprogrammerà se stesso (possibilmente automaticamente, nel caso di una sottoclasse diLoopTask
) per alcuni futuro. Uno schema comune è che lattività mantenga una macchina a stati interna (un esempio banale è lo statolight_on_p_
sopra) in modo che si comporti adeguatamente alla prossima scadenza. - Quindi sì, quelle sono attività da eseguire al completamento (RtC): nessuna attività può essere eseguita prima che quella corrente completi la sua esecuzione ritornando da
run()
. Ciò è in contrasto con i thread cooperativi, che possono generare la CPU, ad esempio chiamandoyield()
odelay()
. O thread preventivi, che possono essere programmati in qualsiasi momento. Sento che la distinzione è importante, poiché ho visto che molte persone che vengono da queste parti alla ricerca di thread lo fanno perché preferiscono scrivere codice di blocco piuttosto che macchine a stati. Il blocco dei thread reali che producono la CPU va bene. Il blocco delle attività RtC non è. - @EdgarBonet È ‘ una distinzione utile, sì. Vorrei considerare sia questo stile che i thread in stile yield, semplicemente come stili diversi di thread cooperativi, in opposizione ai thread preventivi, ma è ‘ vero che richiedono un approccio diverso codificarli. Sarebbe interessante vedere un confronto ponderato e approfondito dei vari approcci qui menzionati; una bella libreria non menzionata sopra è protothreads . Trovo cose da criticare in entrambi, ma anche da lodare. Io (ovviamente) preferisco il mio approccio, perché sembra molto esplicito e non necessita di stack aggiuntivi.
- (la correzione: protothreads era menzionata, in @sachleen ‘ s answer )