Er der en måde, hvorpå jeg kan have flere dele af programmet kørende sammen uden at gøre flere ting i den samme kodeblok?
En tråd venter på en ekstern enhed, mens du også blinker en LED i en anden tråd.
Kommentarer
- Du bør sandsynligvis først spørge dig selv, om du virkelig har brug for tråde. Timere kan muligvis allerede være i orden til dine behov, og de understøttes oprindeligt på Arduino.
- Det kan også være en god idé at tjekke Uzebox. Det ‘ er en tochips hjemmebrygget videospilkonsol. Så selvom det ikke er ‘ t nøjagtigt en Arduino, er hele systemet bygget på afbrydelser. Så lyd, video, kontrol osv. Er alle afbrydelsesdrevne, mens hovedprogrammet ikke ‘ behøver at bekymre sig om noget af det. Kan være en god reference.
Svar
Der er ingen understøttelse af flere processer eller multi-threading Arduino. Du kan dog gøre noget tæt på flere tråde med noget software.
Du vil se på Protothreads :
Protothreads er ekstremt lette stakløse tråde designet til stærkt hukommelsesbegrænsede systemer, såsom små indlejrede systemer eller trådløse sensornetværksnoder. Protothreads giver lineær kodeudførelse til hændelsesdrevne systemer implementeret i C. Protothreads kan bruges med eller uden et underliggende operativsystem til at tilvejebringe blokerende hændelseshåndterere. Protothreads giver sekventiel kontrol af kontrol uden komplekse tilstandsmaskiner eller fuld multitrådning.
Der er naturligvis et Arduino-eksempel her med eksempelkode . Dette SO-spørgsmål kan også være nyttigt.
ArduinoThread er også en god.
Kommentarer
- Bemærk, at Arduino DUE har en undtagelse fra dette med flere kontrolsløjfer: arduino.cc/da/Tutorial/MultipleBlinks
Svar
AVR-baserede Arduino “understøtter ikke (hardware) threading, jeg er ikke bekendt med de ARM-baserede Arduino” s. En vej rundt denne begrænsning er brugen af afbrydelser, især afbrudte tider. Du kan programmere en timer til at afbryde hovedrutinen hver så mange mikrosekunder for at køre en bestemt anden rutine.
Svar
Det er muligt at lave softwareside multitrådning på Uno. Trådning af hardwareniveau understøttes ikke.
For at opnå multithreading kræves det implementering af en grundlæggende planlægning og vedligeholdelse af en proces eller opgaveliste for at spore de forskellige opgaver, der skal køres.
Strukturen i en meget enkel ikke-forebyggende planlægning vil være som:
//Pseudocode void loop() { for(i=o; i<n; i++) run(tasklist[i] for timelimit): }
Her, tasklist
kan være en række funktionsmarkører.
tasklist [] = {function1, function2, function3, ...}
Med hver funktion i formen:
int function1(long time_available) { top: //Do short task if (run_time<time_available) goto top; }
Hver funktion kan udføre en separat opgave såsom function1
udføre LED-manipulationer og function2
udføre floatberegninger. Det er hver opgave (funktion )s ansvar at overholde den tid, der er afsat til den.
Forhåbentlig skulle dette være nok til at komme i gang.
Kommentarer
- Jeg er ikke sikker på, at jeg vil tale om ” tråde ” når du bruger en ikke-forebyggende planlægning. Forresten eksisterer en sådan planlægning allerede som et arduino-bibliotek: arduino.cc/da/Reference/Scheduler
- @jfpoilpret – Kooperativ multithreading er en rigtig ting.
- Ja du ‘ har ret! Min fejl; det havde været så længe siden, at jeg ikke stod over for kooperativ multithreading, at multithreading i mine øjne måtte være præventiv.
Svar
Som beskrevet i beskrivelsen af dine krav:
- en tråd venter på en ekstern enhed
- en tråd blinker en LED
Det ser ud til, at du kunne bruge en Arduino-afbrydelse til den første “tråd” (jeg vil hellere kalde det “opgave”).
Arduino-afbrydelser kan kalde en funktion (din kode) baseret på en ekstern begivenhed (spændingsniveau eller niveauændring på en digital indgangsstift), der vil udløse din funktion med det samme.
Et vigtigt punkt at huske på ved afbrydelser er imidlertid, at den kaldte funktion skal være så hurtig som muligt (typisk bør der ikke være noget delay()
-opkald eller noget andet API, der afhænger af delay()
).
Hvis du har en lang opgave at aktivere ved udløsning af ekstern begivenhed, kan du muligvis bruge en samarbejdsplanlægger og tilføje en ny opgave til den fra din afbrydelsesfunktion.
Et andet vigtigt pointen om afbrydelser er, at antallet er begrænset (f.eks. kun 2 på UNO). Så hvis du begynder at have flere eksterne begivenheder, skal du implementere en slags multiplexing af alle input til en og få din afbrydelsesfunktion til at bestemme, hvilken multiplexed inut der var den egentlige trigger.
Svar
En enkel løsning er at bruge en Planlægger . Der er flere implementeringer. Dette beskriver kort en, der er tilgængelig for AVR- og SAM-baserede kort. Grundlæggende starter et enkelt opkald en opgave; “sketch in a sketch”.
#include <Scheduler.h> .... void setup() { ... Scheduler.start(taskSetup, taskLoop); }
Scheduler.start () tilføjer en ny opgave, der kører taskSetup en gang og derefter gentagne gange kalder taskLoop lige som Arduino skitseværker. Opgaven har sin egen stak. Stakkens størrelse er en valgfri parameter. Standardstørrelsen på stakken er 128 byte.
For at tillade kontekstskift skal opgaverne ringe til yield () eller forsinkelse ( ) . Der er også en supportmakro til at vente på en tilstand.
await(Serial.available());
Makroen er syntaktisk sukker for følgende:
while (!(Serial.available())) yield();
Afventer kan også bruges til at synkronisere opgaver. Nedenfor er et eksempel på et uddrag:
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); }
For yderligere oplysninger se eksempler . Der er eksempler fra flere LED-blink for at afvise-knappen og en simpel skal med ikke-blokerende kommandolinje læst. Skabeloner og navneområder kan bruges til at strukturere og reducere kildekoden. Nedenfor skitse vises, hvordan man bruger skabelonfunktioner til multi-blink. Det er tilstrækkeligt med 64 byte til stakken.
#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(); }
Der er også en benchmark for at give en ide om forestillingen, dvs. tid at starte opgave, kontekstskifte osv.
Endelig er der et par supportklasser til synkronisering og kommunikation på jobniveau; Kø og Semaphore .
Svar
Jeg kom også til dette emne, mens jeg implementerede en matrix-LED-skærm.
I et ord , kan du oprette en afstemningsplanlægning ved hjælp af millis () -funktionen og timer-afbrydelse i Arduino.
Jeg foreslår følgende artikler fra 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
Fra en tidligere besværgelse af dette forum blev følgende spørgsmål / svar flyttet til elektroteknik. Det har en arduino-kode til at blinke en LED ved hjælp af en timer-afbrydelse, mens du bruger hovedsløjfen til at udføre seriel IO.
Repost:
Interrupts er en almindelig måde at få tingene til, mens noget andet foregår. I eksemplet nedenfor blinker LEDen uden at bruge delay()
. Når Timer1
udløses, kaldes afbrydelsesrutinen (ISR) isrBlinker()
. Det tænder / slukker for lysdioden.
For at vise, at andre ting kan ske samtidigt, loop()
skriver gentagne gange foo / bar til den serielle port uafhængig af, at LED blinker .
#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); }
Dette er en meget enkel demo. ISRer kan være meget mere komplekse og kan udløses af timere og eksterne begivenheder (pins). Mange af de almindelige biblioteker implementeres ved hjælp af ISRer.
Svar
Du kan også prøve mit ThreadHandler-bibliotek
https://bitbucket.org/adamb3_14/threadhandler/src/master/
Det bruger en afbrydende planlægning til at tillade kontekstskift uden at videresende yield () eller delay ().
Jeg oprettede biblioteket, fordi jeg havde brug for tre tråde, og jeg havde brug for to af dem til at køre på et præcist tidspunkt, uanset hvad de andre gjorde. Den første trådhåndterede seriel kommunikation. Den anden kørte et Kalman-filter ved hjælp af floatmatrix-multiplikation med Eigen-biblioteket. Og den tredje var en hurtigstrømstyringsløbetråd, som skulle kunne afbryde matrixberegningerne.
Sådan fungerer det
Hver cyklisk tråd har en prioritet og en periode. Hvis en tråd, der har højere prioritet end den aktuelle udførende tråd, når sin næste udførelsestid, planlægger planlæggeren den aktuelle tråd på pause og skifter til den højere prioritet. Når tråden med høj prioritet er færdig med sin udførelse, skifter planlæggeren tilbage til den forrige tråd.
Planlægningsregler
Planlægningsskemaet for ThreadHandler-biblioteket er som følger:
- Højeste prioritet først.
- Hvis prioriteten er den samme, udføres tråden med den tidligste deadline først.
- Hvis to tråde har den samme deadline, udføres den først oprettede tråd først.
- En tråd kan kun afbrydes af tråde med højere prioritet.
- Når en tråd er udført, blokerer den udførelse for alle tråde med lavere prioritet, indtil kørselsfunktionen vender tilbage.
- Loop-funktionen har prioritet -128 sammenlignet med ThreadHandler-tråde.
Sådan bruges
Tråde kan oprettes 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 og en lambda-funktion
Thread* myThread = createThread(priority, period, offset, []() { //code to run });
Trådobjekter opretter automatisk forbindelse til ThreadHandler, når de oprettes.
For at starte udførelsen af oprettede trådobjekter, ring:
ThreadHandler::getInstance()->enableThreadExecution();
En svare
Og her er endnu et mikroprocessor-kooperativt multitasking-bibliotek – PQRST: en prioritetskø til kørsel af enkle opgaver.
- Startside
- Klassedokumentation
- Repository med downloads og listen over udgaver
I denne model, er en tråd implementeret som en underklasse af en Task
, som er planlagt til et fremtidigt tidspunkt (og muligvis omlagt med regelmæssige intervaller, hvis den, som det er almindeligt, underklasser LoopTask
i stedet). run()
-metoden for objektet kaldes, når opgaven forfalder. run()
-metoden gør noget arbejde, og returnerer derefter (dette er den kooperative bit); det “opretholder typisk en slags statsmaskine til at styre sine handlinger på successive påkaldelser (et trivielt eksempel er light_on_p_
-variablen i nedenstående eksempel). Det kræver en lille overvejelse af, hvordan organisere din kode, men har vist sig at være meget fleksibel og robust i forholdsvis intensiv brug.
Det er agnostisk om tidsenhederne, så det er lige så lykkeligt at køre i enheder på millis()
som micros()
eller ethvert andet kryds, der er praktisk.
Her er blink-programmet implementeret ved hjælp af dette bibliotek. Dette viser kun en enkelt opgave, der kører: andre opgaver oprettes typisk og startes inden for 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
- Dette er “run-to-complete” opgaver, ikke?
- @EdgarBonet I ‘ Jeg er ikke helt sikker på, hvad du mener. Når
run()
-metoden er kaldt, afbrydes den ikke, så den har ansvaret for at afslutte med rimelighed straks. Typisk dog ‘ vil udføre sit arbejde og derefter omlægge sig selv (muligvis automatisk i tilfælde af en underklasse afLoopTask
) for nogle fremtidig tid. Et almindeligt mønster er, at opgaven skal opretholde en intern tilstandsmaskine (et trivielt eksempel erlight_on_p_
-tilstanden ovenfor), så den opfører sig passende, når den næste gang forfalder. - Så ja, det er RtC-opgaver (run-to-complete): ingen opgave kan køre, før den nuværende fuldfører sin udførelse ved at vende tilbage fra
run()
. Dette er i modsætning til kooperative tråde, der kan give CPUen ved f.eks. At ringe tilyield()
ellerdelay()
. Eller præemptive tråde, der kan planlægges ud når som helst. Jeg føler forskellen er vigtig, da jeg har set, at mange mennesker, der kommer rundt her og søger efter tråde, gør det, fordi de foretrækker at skrive blokeringskode frem for statsmaskiner. At blokere ægte tråde, der giver CPUen, er fint. At blokere RtC-opgaver er ikke. - @EdgarBonet Det ‘ er en nyttig skelnen, ja. Jeg vil betragte både denne stil og tråde med udbyttestil som ganske enkelt forskellige stilarter af kooperativ tråd i modsætning til præemptive tråde, men det ‘ er sandt, at de kræver en anden tilgang til kodning af dem. Det ville være interessant at se en tankevækkende og dybtgående sammenligning af de forskellige tilgange, der er nævnt her; et godt bibliotek, der ikke er nævnt ovenfor, er protothreads . Jeg finder ting at kritisere i begge dele, men også at rose. Jeg foretrækker (selvfølgelig) min tilgang, fordi den virker mest eksplicit og ikke behøver ekstra stakke.
- (rettelse: prototråde blev nævnt i @sachleen ‘ s svar )