Cum pot crea mai multe fire de execuție?

Există o modalitate prin care pot avea mai multe părți ale programului care rulează împreună fără a face mai multe lucruri în același bloc de cod?

Una firul de așteptare pentru un dispozitiv extern în timp ce clipește și un LED într-un alt fir.

Comentarii

  • Probabil ar trebui să vă întrebați mai întâi dacă într-adevăr aveți nevoie de fire. Este posibil ca temporizatoarele să fie în regulă pentru nevoile dvs. și sunt acceptate în mod nativ pe Arduino.
  • Poate doriți să verificați și Uzebox. ‘ este o consolă de jocuri video homebrew cu două cipuri. Deci, în timp ce nu este ‘ exact un Arduino, întregul sistem este construit pe întreruperi. Deci, audio, video, controale etc. sunt toate întrerupte în timp ce programul principal nu trebuie să ‘ nu trebuie să-și facă griji. Poate fi o bună referință.

Răspuns

Nu există suport multi-proces, nici multi-threading, Arduino. Cu toate acestea, puteți face ceva apropiat de mai multe fire de lucru cu unele programe.

Vrei să te uiți la Protothreads :

Protothreads sunt fire de execuție extrem de ușoare, concepute pentru sisteme cu restricții severe de memorie, cum ar fi sistemele încorporate mici sau nodurile de rețea ale senzorilor fără fir. Protothreads oferă executarea liniară a codului pentru sistemele bazate pe evenimente implementate în C. Protothreads pot fi utilizate cu sau fără un sistem de operare subiacent pentru a asigura blocarea gestionarilor de evenimente. Protothreads oferă un flux secvențial de control fără mașini cu stări complexe sau multi-threading complet.

Desigur, există un exemplu Arduino aici cu cod de exemplu . Această SO întrebare ar putea fi utilă și ea.

ArduinoThread este una bună și ea.

Comentarii

Răspuns

Arduino bazat pe AVR nu acceptă threading (hardware), nu sunt familiarizat cu Arduino bazat pe ARM. Un mod în jurul acestei limitări este utilizarea întreruperilor, în special a întreruperilor temporizate. Puteți programa un cronometru pentru a întrerupe rutina principală la fiecare atât de multe microsecunde, pentru a rula o altă rutină specifică.

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

Răspuns

Este posibil să faceți multi-threading în partea software-ului pe Uno. Filetarea la nivel hardware nu este acceptată.

Pentru a realiza multithreading, va necesita implementarea unui planificator de bază și menținerea unui proces sau a unei liste de sarcini pentru a urmări diferitele sarcini care trebuie executate.

Structura unui planificator non-preventiv foarte simplu ar fi ca:

//Pseudocode void loop() { for(i=o; i<n; i++) run(tasklist[i] for timelimit): } 

Aici, tasklist poate fi o serie de indicatori de funcții.

tasklist [] = {function1, function2, function3, ...} 

Cu fiecare funcție a formei:

int function1(long time_available) { top: //Do short task if (run_time<time_available) goto top; } 

Fiecare funcție poate efectua o sarcină separată, cum ar fi function1 efectuarea manipulărilor LED-urilor și function2 efectuarea de calcule flotante. Va fi responsabilitatea fiecărei sarcini (funcție) să adere la timpul alocat acesteia.

Sperăm că acest lucru ar trebui să fie suficient pentru a începe.

Comentarii

  • Nu sunt sigur că aș vorbi despre ” fire ” atunci când utilizați un planificator non-preventiv. Apropo, un astfel de programator există deja ca bibliotecă arduino: arduino.cc/en/Reference/Scheduler
  • @jfpoilpret – Cooperative multithreading este un lucru real.
  • Da, ai ‘ dreptate! Greseala mea; fusese cu atât de mult timp în urmă că nu mă confruntasem cu multithreading-ul cooperant încât, în mintea mea, multi-threading-ul trebuia să fie preventiv.

Răspuns

Conform descrierii cerințelor dvs.:

  • un fir de așteptare pentru un dispozitiv extern
  • un fir care clipeste un LED

Se pare că ați putea folosi o întrerupere Arduino pentru primul „fir” (aș prefera să o numesc „sarcină” de fapt).

Întreruperile Arduino pot apela o funcție (codul dvs.) bazată pe o externă eveniment (nivelul de tensiune sau schimbarea nivelului pe un pin digital de intrare), care vă va declanșa funcția imediat.

Cu toate acestea, un punct important de reținut cu întreruperile este că funcția apelată ar trebui să fie cât mai rapidă posibil (de obicei, nu ar trebui să existe niciun apel delay() sau orice alt API care ar depinde de delay()).

Dacă aveți o sarcină lungă de activat la declanșarea evenimentului extern, atunci puteți utiliza un programator cooperativ și adăugați o nouă sarcină din funcția dvs. de întrerupere.

O a doua importantă Ideea despre întreruperi este că numărul lor este limitat (de exemplu, doar 2 la UNO). Deci, dacă începeți să aveți mai multe evenimente externe, va trebui să implementați un fel de multiplexare a tuturor intrărilor într-o singură și funcția dvs. de întrerupere să determine ce intrare multiplexată a fost declanșatorul real.

Răspuns

O soluție simplă este utilizarea unui Scheduler . Există mai multe implementări. Aceasta descrie în scurt timp una care este disponibilă pentru plăcile bazate pe AVR și SAM. Practic, un singur apel va începe o sarcină; „schiță într-o schiță”.

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

Scheduler.start () va adăuga o nouă sarcină care va rula taskSetup o dată și apoi va apela în mod repetat taskLoop la fel ca Schița Arduino funcționează. Sarcina are propria stivă. Dimensiunea stivei este un parametru opțional. Dimensiunea implicită a stivei este de 128 de octeți.

Pentru a permite schimbarea contextului, sarcinile trebuie să apeleze yield () sau întârziere ( ) . Există, de asemenea, un macro de asistență pentru așteptarea unei stări.

await(Serial.available()); 

Macrocomanda este zahăr sintactic pentru următoarele:

while (!(Serial.available())) yield(); 

Await poate, de asemenea, fi folosit pentru a sincroniza sarcinile. Mai jos este un fragment de exemplu:

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); } 

Pentru detalii suplimentare, consultați exemple . Există exemple de la mai multe LED-uri intermitente pentru a descărca butonul și un shell simplu cu citire linie de comandă non-blocantă. Șabloanele și spațiile de nume pot fi utilizate pentru a ajuta la structurarea și reducerea codului sursă. Mai jos, schiță arată cum să utilizați funcțiile șablonului pentru multi-clipire. Este suficient cu 64 de octeți pentru stivă.

#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(); } 

Există, de asemenea, un benchmark pentru a oferi o idee despre performanță, adică timpul pentru a începe sarcina, schimbarea contextului etc.

În sfârșit, există câteva clase de suport pentru sincronizarea și comunicarea la nivel de sarcină; Coadă și Semafor .

Răspuns

De asemenea, am ajuns la acest subiect în timp ce implementam un afișaj cu matrice LED.

Într-un singur cuvânt , puteți construi un programator de votare folosind funcția millis () și întreruperea temporizatorului în Arduino.

Vă sugerez următoarele articole de la 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

Răspuns

Dintr-o incantație anterioară a acestui forum, următoarea întrebare / răspuns a fost mutată în Inginerie electrică. Are un exemplu de cod arduino pentru a clipi un LED folosind o întrerupere temporizată în timp ce utilizați bucla principală pentru a face IO serial.

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

Repost:

Întreruperile sunt o modalitate obișnuită de a face lucrurile în timp ce se întâmplă altceva. În exemplul de mai jos, LED-ul clipește fără a utiliza delay(). Ori de câte ori se declanșează Timer1, se apelează rutina de servicii de întrerupere (ISR) isrBlinker(). Acesta pornește / oprește LED-ul.

Pentru a arăta că se pot întâmpla simultan și alte lucruri, loop() scrie în mod repetat foo / bar pe portul serial independent de LED-ul care clipește .

#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); } 

Acesta este un demo foarte simplu. ISR-urile pot fi mult mai complexe și pot fi declanșate de cronometre și evenimente externe (pini). Multe dintre bibliotecile comune sunt implementate folosind ISR-uri.

Răspuns

De asemenea, ați putea încerca biblioteca ThreadHandler

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

Utilizează un planificator de întrerupere pentru a permite comutarea contextului fără a relua yield () sau delay ().

Am creat biblioteca pentru că aveam nevoie de trei fire și aveam nevoie de două dintre ele pentru a rula la un moment precis, indiferent de ce făceau ceilalți. Primul fir s-a ocupat de comunicarea în serie. Al doilea a rulat un filtru Kalman folosind multiplicarea matricei flotante cu biblioteca Eigen. Iar al treilea a fost un fir de buclă de control al curentului rapid care a trebuit să poată întrerupe calculele matricei.

Cum funcționează

Fiecare fir ciclic are o prioritate și o perioadă. Dacă un fir de execuție, cu prioritate mai mare decât firul de execuție curent, atinge următorul său timp de execuție, planificatorul va întrerupe firul actual și va trece la cel de prioritate superioară. Odată ce firul cu prioritate ridicată își finalizează execuția, planificatorul revine la firul anterior.

Reguli de planificare

Schema de planificare a bibliotecii ThreadHandler este după cum urmează:

  1. Mai întâi cea mai mare prioritate.
  2. Dacă prioritatea este aceeași, atunci se execută mai întâi firul cu cel mai scurt termen.
  3. Dacă două fire au același termen, atunci primul fir creat se va executa mai întâi.
  4. Un fir poate fi întrerupt numai de fire cu prioritate mai mare.
  5. Odată ce un fir se execută, acesta va bloca execuția pentru toate firele cu prioritate mai mică până când funcția de rulare revine.
  6. Funcția buclă are prioritate -128 în comparație cu firele ThreadHandler.

Cum se utilizează

Firele pot fi create prin moștenirea c ++

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

Sau prin createThread și o funcție lambda

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

Obiectele Thread se conectează automat la ThreadHandler atunci când sunt create.

Pentru a începe executarea obiectelor thread create sunați:

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

Un swer

Și aici este încă o bibliotecă multitasking cooperativă cu microprocesor – PQRST: o coadă prioritară pentru executarea de sarcini simple.

În acest model, un fir este implementat ca o subclasă a unui Task, care este programat pentru o perioadă viitoare (și posibil reprogramat la intervale regulate, dacă, așa cum este obișnuit, acesta subclasează LoopTask în schimb). Metoda run() a obiectului este apelată când sarcina devine scadentă. Metoda run() face unele lucrări cuvenite și apoi revine (acesta este bitul cooperativ); va menține de obicei un fel de mașină de stare pentru a-și gestiona acțiunile la invocații succesive (un exemplu banal este variabila light_on_p_ din exemplul de mai jos). Necesită o ușoară regândire a modului în care organizează-ți codul, dar s-a dovedit foarte flexibil și robust în utilizarea destul de intensă.

Este agnostic în ceea ce privește unitățile de timp, deci este la fel de fericit să rulezi în unități de millis() ca micros() sau orice altă bifă care este convenabilă.

Iată programul „clipire” implementat folosind această bibliotecă. Aceasta arată doar o singură sarcină care rulează: alte sarcini ar fi de obicei create și începute în 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()); } 

Comentarii

  • Acestea sunt sarcini „executate până la finalizare”, nu?
  • @EdgarBonet I ‘ Nu sunt sigur ce vrei să spui. După ce se apelează metoda run(), aceasta nu este întreruptă, deci are responsabilitatea de a termina în mod rezonabil prompt. În mod tipic, totuși, ‘ își va face treaba apoi se reprogramează singură (posibil automat, în cazul unei subclase de LoopTask) pentru unii timpul viitor. Un model obișnuit este ca sarcina să mențină o mașină de stare internă (un exemplu trivial este starea light_on_p_ de mai sus), astfel încât să se comporte în mod adecvat la data următoare.
  • Deci da, acestea sunt sarcini executate până la finalizare (RtC): nicio activitate nu poate rula înainte ca cea curentă să-și finalizeze execuția revenind de la run(). Acest lucru este în contrast cu firele de cooperare, care pot produce CPU prin, de exemplu, apelând yield() sau delay(). Sau fire preventive, care pot fi programate în orice moment. Consider că distincția este importantă, deoarece am văzut că mulți oameni care vin aici în căutarea de fire de discurs o fac pentru că preferă să scrie codul de blocare în locul mașinilor de stat. Blocarea firelor reale care produc CPU este în regulă. Blocarea sarcinilor RtC nu este.
  • @EdgarBonet ‘ este o distincție utilă, da. Aș considera atât acest stil, cât și firele de tip randament, ca fiind pur și simplu diferite stiluri de fire de cooperare, spre deosebire de firele preventive, dar este ‘ adevărat că necesită o abordare diferită a codându-le. Ar fi interesant să vedem o comparație atentă și aprofundată a diferitelor abordări menționate aici; o frumoasă bibliotecă care nu este menționată mai sus este protothreads . Găsesc lucruri de criticat în ambele, dar și de laudat. (Desigur) prefer abordarea mea, deoarece pare cea mai explicită și nu are nevoie de stive suplimentare.
  • (corecție: filele prototice au fost menționate , în @sachleen ‘ răspuns )

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *