Hur kan jag skapa flera löpande trådar?

Finns det ett sätt att jag kan ha flera delar av programmet igång utan att göra flera saker i samma kodblock?

En tråd väntar på en extern enhet samtidigt som en LED blinkar i en annan tråd.

Kommentarer

  • Du borde nog först fråga dig själv om du verkligen behöver trådar. Timers kan vara OK för dina behov redan och de stöds inbyggt på Arduino.
  • Du kanske också vill kolla in Uzebox. Det ’ är en tv-chip hembrytad videospelkonsol. Så även om det inte är ’ t ex en Arduino, är hela systemet byggt på avbrott. Så ljud, video, kontroller etc. är alla avbrottsdrivna medan huvudprogrammet inte ’ behöver oroa sig för något av det. Kan vara en bra referens.

Svar

Det finns inget stöd för flera processer eller multitrådning Arduino. Du kan dock göra något nära flera trådar med viss programvara.

Du vill titta på Protothreads :

Protothreads är extremt lätta stapelfria trådar som är utformade för system med mycket minnesbegränsning, såsom små inbäddade system eller trådlösa sensornätverksnoder. Protothreads ger linjär kodkörning för händelsestyrda system implementerade i C. Protothreads kan användas med eller utan ett underliggande operativsystem för att tillhandahålla blockerande händelsehanterare. Protothreads ger sekventiellt kontrollflöde utan komplicerade tillståndsmaskiner eller full multitrådning.

Det finns naturligtvis ett Arduino-exempel här med exempelkod . Den här SO-frågan kan också vara användbar.

ArduinoThread är en bra också.

Kommentarer

Svar

AVR-baserade Arduino ”stöder inte (hårdvaru) trådning, jag känner inte till ARM-baserade Arduino” s. En väg runt denna begränsning är användningen av avbrott, särskilt avbrott i tid. Du kan programmera en timer för att avbryta huvudrutinen var så många mikrosekunder, för att köra en specifik annan rutin.

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

Svar

Det är möjligt att göra mjukvarusidans multitrådning på Uno. Trådning på hårdvarunivå stöds inte.

För att uppnå multitrådning krävs det implementering av en grundläggande schemaläggare och underhåll av en process eller uppgiftslista för att spåra de olika uppgifter som behöver köras.

Strukturen för en mycket enkel icke förebyggande schemaläggare skulle vara som:

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

Här, tasklist kan vara en uppsättning funktionspekare.

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

Med varje funktion i formen:

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

Varje funktion kan utföra en separat uppgift som function1 utför LED-manipulationer och function2 gör floatberäkningar. Det kommer att vara varje uppgifts (funktion) ansvar att följa den tid som tilldelats den.

Förhoppningsvis borde detta vara tillräckligt för att komma igång.

Kommentarer

  • Jag är inte säker på att jag skulle prata om ” trådar ” när du använder en icke förebyggande schemaläggare. Förresten, en sådan schemaläggare finns redan som ett arduino-bibliotek: arduino.cc/en/Reference/Scheduler
  • @jfpoilpret – Cooperative multithreading är en riktig sak.
  • Ja du ’ har rätt! Mitt fel; det hade varit så länge sedan att jag inte hade mött samarbetsvillig multitrådning att multitrådning i mitt sinne måste vara förebyggande.

Svar

Enligt beskrivningen av dina krav:

  • en tråd som väntar på en extern enhet
  • en tråd som blinkar en lysdiod

Det verkar som om du kan använda ett Arduino-avbrott för den första ”tråden” (jag skulle hellre kalla det ”uppgift” i själva verket).

Arduino-avbrott kan ringa en funktion (din kod) baserat på en extern händelse (spänningsnivå eller nivåändring på en digital ingångsstift), som kommer att utlösa din funktion omedelbart.

En viktig punkt att tänka på vid avbrott är att den anropade funktionen ska vara så snabb som möjligt (vanligtvis bör det inte finnas något delay() -samtal eller något annat API som beror på delay()).

Om du har en lång uppgift att aktivera vid utlösare av extern händelse kan du eventuellt använda en samarbetsplanerare och lägga till en ny uppgift till den från din avbrytningsfunktion.

En andra viktig poäng om avbrott är att deras antal är begränsat (t.ex. endast 2 på UNO). Så om du börjar ha fler externa händelser skulle du behöva implementera någon form av multiplexering av alla ingångar till en och få din avbrytningsfunktion att avgöra vilken multiplexad inut som var den faktiska utlösaren.

Svar

En enkel lösning är att använda en Schemaläggare . Det finns flera implementeringar. Detta beskriver kort en som är tillgänglig för AVR- och SAM-baserade kort. I grund och botten startar ett enda samtal en uppgift; ”skiss i en skiss”.

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

Scheduler.start () lägger till en ny uppgift som kör uppgiften Inställning en gång och sedan upprepade gånger anropar uppdragsloop precis som Arduino skiss fungerar. Uppgiften har sin egen stack. Storleken på stacken är en valfri parameter. Standardstorlek är 128 byte.

För att tillåta kontextbyte måste uppgifterna ringa yield () eller fördröjning ( ) . Det finns också ett stödmakro för att vänta på ett tillstånd.

await(Serial.available()); 

Makrot är syntaktiskt socker för följande:

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

Väntar kan också användas för att synkronisera uppgifter. Nedan följer ett exempelutdrag:

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

För ytterligare information se exempel . Det finns exempel från flera LED-blinkningar för att avbryta och ett enkelt skal med icke-blockerande kommandorad läst. Mallar och namnområden kan användas för att strukturera och minska källkoden. Nedanför skiss visas hur man använder mallfunktioner för flerblink. Det räcker med 64 byte för stacken.

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

Det finns också ett riktmärke för att ge en uppfattning om föreställningen, dvs. tid för att starta uppgift, kontextväxling, etc.

Sist, det finns några stödklasser för synkronisering och kommunikation på aktivitetsnivå; och Semaphore .

Svar

Jag kom också till detta ämne när jag implementerade en matris-LED-skärm.

I ett ord , du kan bygga en polling schemaläggare genom att använda millis () funktion och timeravbrott i Arduino.

Jag föreslår följande artiklar från 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

Svar

Från en tidigare besvärjelse av detta forum flyttades följande fråga / svar till Elektroteknik. Den har exempel på arduino-kod för att blinka en lysdiod med hjälp av en timeravbrott medan du använder huvudslingan för att göra serie IO.

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

Repost:

Avbrott är ett vanligt sätt att få saker gjort medan något annat pågår. I exemplet nedan blinkar lysdioden utan att använda delay(). Närhelst Timer1 utlöses kallas avbrottsrutinen (ISR) isrBlinker(). Den slår på / av lysdioden.

För att visa att andra saker kan hända samtidigt, loop() skriver upprepade gånger foo / bar till serieporten oberoende av att lysdioden blinkar .

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

Detta är en mycket enkel demo. ISR kan vara mycket mer komplexa och kan utlösas av timers och externa händelser (pins). Många av de vanliga biblioteken implementeras med ISR.

Svar

Du kan också prova mitt ThreadHandler-bibliotek

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

Den använder en avbrytande schemaläggare för att tillåta kontextbyte utan att vidarebefordra yield () eller delay ().

Jag skapade biblioteket eftersom jag behövde tre trådar och jag behövde två av dem för att springa vid en exakt tid oavsett vad de andra gjorde. Den första tråden hanterade seriell kommunikation. Den andra körde ett Kalman-filter med hjälp av flytmatrismultiplikation med Eigen-biblioteket. Och den tredje var en snabbströmskontrolltråd som var tvungen att kunna avbryta matrisberäkningarna.

Hur det fungerar

Varje cyklisk tråd har en prioritet och en period. Om en tråd, med högre prioritet än den nuvarande körtråden, når sin nästa exekveringstid kommer schemaläggaren att pausa den aktuella tråden och växla till den högre prioriteten. När tråden med hög prioritet har slutfört körningen byter schemaläggaren tillbaka till föregående tråd.

Schemaläggningsregler

Schemaläggningsschemat för ThreadHandler-biblioteket är som följer:

  1. Högsta prioritet först.
  2. Om prioriteten är densamma exekveras tråden med den tidigaste deadline först.
  3. Om två trådar har samma deadline kommer den första skapade tråden att köras först.
  4. En tråd kan bara avbrytas av trådar med högre prioritet.
  5. När en tråd körs kommer den att blockera körning för alla trådar med lägre prioritet tills körningsfunktionen återvänder.
  6. Loop-funktionen har prioritet -128 jämfört med ThreadHandler-trådar.

Hur man använder

Trådar kan skapas via c ++ arv

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

Eller via createThread och en lambda-funktion

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

Trådobjekt ansluts automatiskt till ThreadHandler när de skapas.

För att starta körning av skapade trådobjekt, ring:

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

En svara

Och här är ännu ett mikroprocessor-samarbetsbibliotek för multitasking – PQRST: en prioritetskö för körning av enkla uppgifter.

I det här modell, en tråd implementeras som en underklass av en Task, som är schemalagd för en framtida tid (och eventuellt omplaneras med regelbundna intervall, om den, som vanligt, underklasserar LoopTask istället). run() -metoden för objektet anropas när uppgiften förfaller. run() -metoden utför en del arbete och återvänder sedan (detta är kooperativbiten); det kommer vanligtvis att upprätthålla någon form av tillståndsmaskin för att hantera sina handlingar vid successiva anrop (ett trivialt exempel är light_on_p_ variabeln i exemplet nedan). Det kräver en liten omprövning av hur du organisera din kod, men har visat sig vara mycket flexibel och robust vid ganska intensiv användning.

Det är agnostiskt när det gäller tidsenheter, så det är lika glad att köra i enheter av millis() som micros(), eller något annat kryss som är bekvämt.

Här är ”blink” -programmet implementerat med detta bibliotek. Detta visar bara en enda uppgift som körs: andra uppgifter skapas vanligtvis och startas inom 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()); } 

Kommentarer

  • Det här är ”kör-till-slutförande” -uppgifter, eller hur?
  • @EdgarBonet I ’ jag vet inte riktigt vad du menar. Efter att run() -metoden har anropats avbryts den inte, så den har ansvaret att avsluta rimligt snabbt. Men typiskt ’ kommer det att göra sitt arbete och sedan omplanera sig själv (eventuellt automatiskt, i fallet med en underklass av LoopTask) för vissa framtida tid. Ett vanligt mönster är för uppgiften att upprätthålla någon intern tillståndsmaskin (ett trivialt exempel är light_on_p_ -tillståndet ovan) så att det beter sig lämpligt när det är nästa gång.
  • Så ja, det här är run-to-complete (RtC) -uppgifter: ingen uppgift kan köras innan den nuvarande slutförd körningen genom att återvända från run(). Detta står i kontrast till kooperativa trådar som kan ge CPU genom att t.ex. ringa yield() eller delay(). Eller förebyggande trådar som kan planeras ut när som helst. Jag tycker att skillnaden är viktig, eftersom jag har sett att många människor som kommer hit och söker efter trådar gör det för att de föredrar att skriva blockeringskod snarare än statsmaskiner. Att blockera riktiga trådar som ger CPU är bra. Blockering av RtC-uppgifter är inte.
  • @EdgarBonet ’ är en användbar åtskillnad, ja. Jag skulle betrakta både den här stilen och avkastningstrådarna som helt enkelt olika stilar av kooperativ tråd, i motsats till förebyggande trådar, men det är ’ sant att de kräver en annan inställning till kodar dem. Det vore intressant att se en tankeväckande och djupgående jämförelse av de olika tillvägagångssätt som nämns här; ett trevligt bibliotek som inte nämns ovan är prototrådar . Jag hittar saker att kritisera i båda, men också att berömma. Jag (naturligtvis) föredrar mitt tillvägagångssätt, för det verkar tydligast och behöver inga extra stackar.
  • (korrigering: prototrådar nämndes i @sachleen ’ s svar )

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *