Fråga:
Hur kan jag skapa flera löpande trådar?
Bja
2014-02-18 21:11:42 UTC
view on stackexchange narkive permalink

Finns det ett sätt som jag kan få flera delar av programmet att köra tillsammans utan att göra flera saker i samma kodblock?

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

Du bör 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 vill kolla in Uzebox också. Det är en tv-chip homebrew-videospelkonsol. Så även om det inte exakt är 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 bra referens.
Nio svar:
#1
+54
sachleen
2014-02-18 23:23:18 UTC
view on stackexchange narkive permalink

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

Du vill titta på Protothreads:

Protothreads är extremt lätta stapelfria trådar designad för system med mycket minnesbegränsning, såsom små inbäddade system eller trådlösa sensornätverksnoder. Protothreads tillhandahåller 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 komplexa tillståndsmaskiner eller full multitrådning.

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

ArduinoThread är också bra.

Observera att Arduino DUE har ett undantag från detta, med flera kontrollöglor: https://www.arduino.cc/en/Tutorial/MultipleBlinks
#2
+19
jippie
2014-02-19 00:14:37 UTC
view on stackexchange narkive permalink

AVR-baserade Arduino stöder inte (hårdvara) trådning, jag känner inte till ARM-baserade Arduino. 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

#3
+15
asheeshr
2014-02-18 21:36:33 UTC
view on stackexchange narkive permalink

Det är möjligt att göra flertrådning på mjukvarusidan 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 uppgifterna som måste köras.

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

  // Pseudocodevoid loop () {for (i = o; i<n; i ++) run ( uppgiftslista [i] för tidsbegränsning):}  

Här kan uppgiftslista vara en uppsättning funktionspekare.

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

Med varje funktion i formuläret:

  int function1 (long time_available) {top: // Gör kort uppgift om (run_time<time_available) goto top;}  

Varje funktion kan utföra en separat uppgift som function1 utför LED-manipulationer och function2 gör flytande berä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.

Jag är inte säker på att jag skulle prata om "trådar" när jag använder en icke förebyggande schemaläggare. Förresten, en sådan schemaläggare finns redan som ett arduino-bibliotek: http://arduino.cc/en/Reference/Scheduler
@jfpoilpret - Kooperativ 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 mina tankar måste vara förebyggande.
#4
+9
jfpoilpret
2014-02-19 02:58:56 UTC
view on stackexchange narkive permalink

Enligt beskrivningen av dina krav:

  • en tråd 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").

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 borde det inte finnas något -fördröjning () -samtal eller något annat API som beror på fördröjning()).

Om du har en lång uppgift för att aktivera vid extern händelseutlösare kan du eventuellt använda en samarbetsplanerare och lägga till en ny uppgift till den från din avbrottsfunktion.

En andra viktig punkt 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, måste du 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.

#5
+7
Mikael Patel
2016-01-21 18:34:10 UTC
view on stackexchange narkive permalink

En enkel lösning är att använda en Scheduler. Det finns flera implementeringar. Detta beskriver kort en som är tillgänglig för AVR- och SAM-baserade kort. I grund och botten kommer ett enda samtal att starta en uppgift; "skiss inom en skiss".

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

Scheduler.start () lägger till en ny uppgift som kommer att köra taskSetup en gång och sedan upprepade gånger kalla taskLoop precis som Arduino-skissen fungerar. Uppgiften har sin egen stack. Storleken på stacken är en valfri parameter. Standardstorlek är 128 byte.

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

await(Serial.available());

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

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

Await kan också användas för att synkronisera uppgifter. Nedan följer ett exempel på utdrag:

  flyktig int taskEvent = 0; #define signal (evt) do {await (taskEvent == 0); taskEvent = evt; } medan (0) ... ogiltig taskLoop () {väntar (taskEvent); switch (taskEvent) {case 1: ...} taskEvent = 0;} ... void loop () {... signal (1);}  

Mer information finns i 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 hjälpa till att strukturera och minska källkoden. Nedan 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 fördröjning (ms); digitalWrite (pin, LOW); fördröjning (ms);} ogiltig installation () {
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 lite uppfattning om prestanda, 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.

#6
+3
walrii
2015-09-04 02:57:26 UTC
view on stackexchange narkive permalink

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 huvudslingan används för att göra seriell IO.

https://electronics.stackexchange.com/questions/67089/how-can -i-kontroll-saker-utan-att använda-fördröjning / 67091 # 67091

Repost:

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

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

  #include "TimerOne.h" int led = 13; void isrBlinker () {static bool on = false; digitalWrite (led, på? HÖG: LÅG); on =! on;} ogiltig installation () {Serial.begin (9600); Serial.flush (); Serial.println ("Serial initialized"); pinMode (led, OUTPUT); // initialisera ISR-blinkern Timer1.initialize (1000000); Timer1.attachInterrupt (isrBlinker);} void loop () {Serial.println ("foo"); fördröjning (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.

#7
+3
intelliarm
2015-09-04 14:22:56 UTC
view on stackexchange narkive permalink

Jag kom också till det här ämnet när jag implementerade en matris-LED-skärm.

I ett ord kan du skapa en polling-schemaläggare genom att använda millis () -funktionen 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: // lär .adafruit.com / multi-tasking-the-arduino-part-3 / översikt

#8
+2
Adam Bäckström
2019-04-22 15:59:03 UTC
view on stackexchange narkive permalink

Du kan också prova mitt ThreadHandler-bibliotek

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

Det använder en avbrytande schemaläggare för att tillåta kontextbyte utan att vidarebefordra på utbyte () eller fördröjning ().

Jag skapade biblioteket eftersom jag behövde tre trådar och jag behövde två av dem för att köra 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 körs tråden med den tidigaste deadline först.
  3. Om två trådar har samma deadline så skapas den första tråden körs först.
  4. En tråd kan bara avbrytas av trådar med högre prioritet.
  5. När en tråd körs blockerar den körning för alla trådar med lägre prioritet tills körfunktionen returnerar.
  6. Loop-funktionen har prioritet -128 jämfört med ThreadHandler-trådar.

Hur man använder

Trådar kan skapas via c ++ arv

  klass MyThread: public Thread {public: MyThread (): Thread (priority, period, offset) {} virtual ~ MyThread () {} virtual void kör () {// kod som ska köras }}; MyThread * threadObj = new MyThread ();  

Eller via createThread och en lambda-funktion

  Tråd * myThread = createThread (prioritet, period, offset, [] () {// kod att köra});  

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

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

  ThreadHandler :: getInstance () - >enableThreadExecution ();  
#9
+1
Norman Gray
2019-07-08 20:41:54 UTC
view on stackexchange narkive permalink

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

I den här modellen, en tråd implementeras som en underklass av en Uppgift , som är schemalagd för en framtida tid (och eventuellt omplaneras med jämna mellanrum, om den, som vanligt, underklasserar LoopTask kod> istället). run () -metoden för objektet anropas när uppgiften förfaller. Metoden run () gör vederbörligt arbete och returnerar sedan (detta är kooperativbiten); det kommer vanligtvis att upprätthålla någon form av tillståndsmaskin för att hantera sina åtgärder vid successiva anrop (ett trivialt exempel är variabeln light_on_p_ i exemplet nedan). Det kräver en liten omprövning av hur du organiserar din kod, men har visat sig vara mycket flexibel och robust vid ganska intensiv användning.

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

Här är "blink" -programmet implementerat med hjälp av detta bibliotek. Detta visar bara en enda uppgift som körs: andra uppgifter skulle vanligtvis skapas och startas inom uppsättning().

  #inkludera "pqrst.h" -klass BlinkTask: offentlig LoopTask {privat: int my_pin_; bool light_on_p_; public: BlinkTask (int pin, ms_t cadence); ogiltig körning (ms_t) åsidosätta;}; BlinkTask :: BlinkTask (int-stift, ms_t-kadens): LoopTask (kadens), my_pin_ (pin), light_on_p_ (falsk) {// tom} ogiltig BlinkTask :: kör (ms_t t) { // växla LED-tillståndet varje gång vi kallas light_on_p_ =! light_on_p_; digitalWrite (my_pin_, light_on_p _);} // blinkar den inbyggda lysdioden vid en 500ms kadensBlinkTask-blinkare (LED_BUILTIN, 500); ogiltig inställning () {pinMode (LED_BUILTIN, OUTPUT); flasher.start (2000); // starta efter 2000ms (= 2s)}
ogiltig loop () {Queue.run_ready (millis ());}  
Dessa är "kör-till-slutförande" -uppgifter, eller hur?
@EdgarBonet Jag vet inte riktigt vad du menar. Efter att 'run ()' -metoden har anropats avbryts den inte, så den har ansvaret för att slutföra rimligt snabbt. Vanligtvis kommer det dock att göra sitt arbete och sedan omplanera sig själv (eventuellt automatiskt, i fallet med en underklass av 'LoopTask') under en framtida tid. Ett vanligt mönster är att uppgiften ska upprätthålla någon intern tillståndsmaskin (ett trivialt exempel är tillståndet 'light_on_p_' ovan) så att det beter sig lämpligt när det är nästa gång.
Så ja, dessa är kör-till-slutförda (RtC) -uppgifter: ingen uppgift kan köras innan den nuvarande slutför 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 Det är en användbar åtskillnad, ja. Jag skulle betrakta både denna stil och avkastningstrådar 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 metod för att koda 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 [protothreads] (http://dunkels.com/adam/pt/). Jag hittar saker att kritisera i båda, men också att berömma. Jag (naturligtvis) föredrar mitt tillvägagångssätt, eftersom det verkar tydligast och behöver inga extra stackar.
(korrigering: prototrådar _ nämndes, i [@sachleen's svar] (https://arduino.stackexchange.com/a/288/8391))


Denna fråga och svar översattes automatiskt från det engelska språket.Det ursprungliga innehållet finns tillgängligt på stackexchange, vilket vi tackar för cc by-sa 3.0-licensen som det distribueras under.
Loading...