Is er een manier waarop ik meerdere delen van het programma samen kan laten draaien zonder meerdere dingen in hetzelfde codeblok te doen?
Eén thread wacht op een extern apparaat terwijl ook een LED in een andere thread knippert.
Reacties
- Je moet je waarschijnlijk eerst afvragen of je echt threads nodig hebt. Timers zijn misschien al in orde voor uw behoeften en ze worden standaard ondersteund op Arduino.
- Misschien wilt u ook de Uzebox bekijken. Het ‘ is een homebrew videogameconsole met twee chips. Dus hoewel het niet ‘ t precies een Arduino is, is het hele systeem gebouwd op interrupts. Dus audio, video, bedieningselementen, enz. Worden allemaal onderbroken, terwijl het hoofdprogramma zich er geen ‘ zorgen over hoeft te maken. Misschien een goede referentie.
Answer
Er is geen ondersteuning voor meerdere processen of meerdere threads de Arduino. U kunt met sommige software echter iets doen dat in de buurt komt van meerdere threads.
U wilt kijken naar Protothreads :
Protothreads zijn extreem lichtgewicht stapelloze threads die zijn ontworpen voor systemen met ernstige geheugenbeperkingen, zoals kleine embedded systemen of draadloze sensornetwerkknooppunten. Protothreads bieden lineaire code-uitvoering voor gebeurtenisgestuurde systemen die zijn geïmplementeerd in C. Protothreads kunnen met of zonder een onderliggend besturingssysteem worden gebruikt om gebeurtenisafhandelaars te blokkeren. Protothreads bieden een sequentiële controlestroom zonder complexe toestandsmachines of volledige multi-threading.
Natuurlijk is er een Arduino-voorbeeld hier met voorbeeldcode . Deze DUS vraag kan ook nuttig zijn.
ArduinoThread is ook een goede.
Opmerkingen
- Merk op dat de Arduino DUE hierop een uitzondering heeft, met meerdere controlelussen: arduino.cc/en/Tutorial/MultipleBlinks
Antwoord
Op AVR gebaseerde Arduinos ondersteunen geen (hardware) threading, ik ben niet bekend met de ARM-gebaseerde Arduinos. Een manier om deze beperking te omzeilen, is het gebruik van interrupts, met name getimede interrupts. U kunt een timer programmeren om de hoofdroutine om de zoveel microseconden te onderbreken, om een specifieke andere routine uit te voeren.
Answer
Het is mogelijk om software-side multi-threading uit te voeren op de Uno. Threading op hardwareniveau wordt niet ondersteund.
Om multithreading te bereiken, is de implementatie van een basisplanner vereist en het bijhouden van een proces of takenlijst om de verschillende taken bij te houden die moeten worden uitgevoerd.
De structuur van een zeer eenvoudige niet-preventieve planner zou er als volgt uitzien:
//Pseudocode void loop() { for(i=o; i<n; i++) run(tasklist[i] for timelimit): }
Hier tasklist
kan een reeks functie-aanwijzers zijn.
tasklist [] = {function1, function2, function3, ...}
Met elke functie van het formulier:
int function1(long time_available) { top: //Do short task if (run_time<time_available) goto top; }
Elke functie kan een afzonderlijke taak uitvoeren, zoals function1
LED-manipulaties uitvoeren, en function2
float-berekeningen uitvoeren. Het is de verantwoordelijkheid van elke taak (functie) om zich te houden aan de tijd die eraan is toegewezen.
Hopelijk zou dit voldoende moeten zijn om u op weg te helpen.
Reacties
- Ik weet niet zeker of ik het zou hebben over ” discussies ” bij gebruik van een niet-preventieve planner. Overigens bestaat zon planner al als een arduino-bibliotheek: arduino.cc/en/Reference/Scheduler
- @jfpoilpret – Cooperative multithreading is echt.
- Ja, je hebt ‘ gelijk! Mijn fout; het was zo lang geleden dat ik geen coöperatieve multithreading had meegemaakt dat in mijn gedachten multithreading preventief moest zijn.
Answer
Volgens de beschrijving van uw vereisten:
- een thread wachtend op een extern apparaat
- een thread knippert een LED
Het lijkt erop dat je één Arduino-interrupt zou kunnen gebruiken voor de eerste “thread” (ik zou het eigenlijk liever “task” noemen).
Arduino-interrupts kunnen één functie (jouw code) aanroepen op basis van een externe gebeurtenis (spanningsniveau of niveauverandering op een digitale ingangspen), die uw functie onmiddellijk zal activeren.
Een belangrijk punt om in gedachten te houden met interrupts is dat de aangeroepen functie zo snel mogelijk moet zijn (normaal gesproken zou er geen delay()
aanroep moeten zijn of enige andere API die afhankelijk is van delay()
).
Als u een lange taak moet activeren bij het activeren van een externe gebeurtenis, dan kunt u mogelijk een coöperatieve planner gebruiken en er een nieuwe taak aan toevoegen vanuit uw interruptfunctie.
Een tweede belangrijk punt over interrupts is dat hun aantal beperkt is (bijvoorbeeld slechts 2 op UNO). Dus als je meer externe gebeurtenissen begint te krijgen, zou je een soort van multiplexing van alle inputs in één moeten implementeren, en je interrupt-functie laten bepalen welke gemultiplexte input de feitelijke trigger was.
Antwoord
Een eenvoudige oplossing is om een Scheduler te gebruiken. Er zijn verschillende implementaties. Dit beschrijft binnenkort een die beschikbaar is voor op AVR en SAM gebaseerde kaarten. In principe start een enkele oproep een taak; “sketch within a sketch”.
#include <Scheduler.h> .... void setup() { ... Scheduler.start(taskSetup, taskLoop); }
Scheduler.start () zal een nieuwe taak toevoegen die de taskSetup één keer zal uitvoeren en vervolgens herhaaldelijk taskLoop aanroept, net zoals de Arduino sketch werkt. De taak heeft zijn eigen stapel. De grootte van de stapel is een optionele parameter. De standaard stackgrootte is 128 bytes.
Om contextwisseling mogelijk te maken, moeten de taken yield () of delay ( ) . Er is ook een ondersteuningsmacro voor het wachten op een conditie.
await(Serial.available());
De macro is syntactisch voor het volgende:
while (!(Serial.available())) yield();
Wachten kan ook worden gebruikt om taken te synchroniseren. Hieronder is een voorbeeldfragment:
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); }
Zie voor meer details de voorbeelden . Er zijn voorbeelden van meerdere LED-knipper- tot debounce-knop en een eenvoudige shell met niet-blokkerende opdrachtregel om te lezen. Sjablonen en naamruimten kunnen worden gebruikt om de broncode te structureren en te verkleinen. Hieronder ziet u sketch hoe u sjabloonfuncties kunt gebruiken voor multi-blink. Het is voldoende met 64 bytes voor de stapel.
#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(); }
Er is ook een benchmark om een idee te geven van de prestaties, bijvoorbeeld tijd om de taak te starten, contextwisseling, enz.
Als laatste zijn er een paar ondersteuningsklassen voor synchronisatie en communicatie op taakniveau; Wachtrij en Semafoor .
Antwoord
Ik kwam ook op dit onderwerp toen ik een matrix LED-display implementeerde.
In één woord , kun je een polling-planner bouwen door de millis () -functie en timer-interrupt in Arduino te gebruiken.
Ik stel de volgende artikelen van Bill Earl voor:
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
Antwoord
Van een eerdere bezwering van dit forum, is de volgende vraag / antwoord verplaatst naar Electrical Engineering. Het heeft een voorbeeld Arduino-code om een LED te laten knipperen met behulp van een timer-interrupt terwijl de hoofdlus wordt gebruikt om seriële IO uit te voeren.
Repost:
Interrupts zijn een gebruikelijke manier om dingen gedaan te krijgen terwijl er iets anders aan de hand is. In het onderstaande voorbeeld knippert de LED zonder delay()
te gebruiken. Telkens wanneer Timer1
wordt geactiveerd, wordt de interruptserviceroutine (ISR) isrBlinker()
aangeroepen. Het schakelt de LED aan / uit.
Om te laten zien dat er andere dingen tegelijkertijd kunnen gebeuren, loop()
schrijft herhaaldelijk foo / bar naar de seriële poort, onafhankelijk van het knipperen van de 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); }
Dit is een heel eenvoudige demo. ISRs kunnen veel complexer zijn en kunnen worden geactiveerd door timers en externe gebeurtenissen (pinnen). Veel van de gangbare bibliotheken worden geïmplementeerd met behulp van ISRs.
Antwoord
Je kunt mijn ThreadHandler-bibliotheek ook eens proberen
https://bitbucket.org/adamb3_14/threadhandler/src/master/
Het gebruikt een onderbrekende planner om van context te wisselen zonder door te sturen yield () of delay ().
Ik heb de bibliotheek gemaakt omdat ik drie threads nodig had en ik had er twee nodig om op een precies tijdstip te draaien, ongeacht wat de anderen aan het doen waren. De eerste thread behandelde seriële communicatie. De tweede was het uitvoeren van een Kalman-filter met behulp van float-matrixvermenigvuldiging met de Eigen-bibliotheek. En de derde was een snelle stroomregellus-thread die de matrixberekeningen moest kunnen onderbreken.
Hoe het werkt
Elke cyclische thread heeft een prioriteit en een periode. Als een thread, met een hogere prioriteit dan de huidige uitvoerende thread, de volgende uitvoeringstijd bereikt, zal de planner de huidige thread pauzeren en overschakelen naar de thread met een hogere prioriteit. Zodra de thread met hoge prioriteit zijn uitvoering heeft voltooid, schakelt de planner terug naar de vorige thread.
Planningsregels
Het planningsschema van de ThreadHandler-bibliotheek is als volgt:
- De hoogste prioriteit eerst.
- Als de prioriteit hetzelfde is, wordt de thread met de vroegste deadline als eerste uitgevoerd.
- Als twee threads dezelfde deadline hebben, wordt de eerst gemaakte thread als eerste uitgevoerd.
- Een thread kan alleen worden onderbroken door threads met een hogere prioriteit.
- Zodra een thread wordt uitgevoerd, wordt de uitvoering voor alle threads met lagere prioriteit geblokkeerd totdat de functie run terugkeert.
- De loop-functie heeft prioriteit -128 in vergelijking met ThreadHandler-threads.
Hoe te gebruiken
Threads kunnen worden gemaakt via c ++ overerving
class MyThread : public Thread { public: MyThread() : Thread(priority, period, offset){} virtual ~MyThread(){} virtual void run() { //code to run } }; MyThread* threadObj = new MyThread();
Of via createThread en een lambda-functie
Thread* myThread = createThread(priority, period, offset, []() { //code to run });
Thread-objecten maken automatisch verbinding met de ThreadHandler wanneer ze worden gemaakt.
Om de uitvoering van gemaakte thread-objecten te starten, roept u:
ThreadHandler::getInstance()->enableThreadExecution();
Een swer
En hier is nog een microprocessor coöperatieve multitasking-bibliotheek – PQRST: een prioriteitswachtrij voor het uitvoeren van eenvoudige taken.
- Startpagina
- Documentatie op klasniveau
- Repository , met downloads en lijst met problemen
In deze model wordt een thread geïmplementeerd als een subklasse van een Task
, die is gepland voor een toekomstige tijd (en mogelijk met regelmatige tussenpozen opnieuw wordt gepland, als, zoals gebruikelijk, deze subklassen LoopTask
in plaats daarvan). De run()
-methode van het object wordt aangeroepen wanneer de taak moet worden uitgevoerd. De run()
methode doet wat gepast werk, en keert dan terug (dit is het coöperatieve bit); het “zal meestal een soort toestandsmachine onderhouden om zijn acties op opeenvolgende aanroepen te beheren (een triviaal voorbeeld is de light_on_p_
variabele in het onderstaande voorbeeld). Het vereist een lichte heroverweging van hoe u organiseer je code, maar is erg flexibel en robuust gebleken bij redelijk intensief gebruik.
Het is agnostisch over de tijdseenheden, dus het is net zo gelukkig om in eenheden van millis()
als micros()
, of een ander vinkje dat handig is.
Hier is het blink-programma geïmplementeerd met behulp van deze bibliotheek. Dit toont slechts een enkele taak die wordt uitgevoerd: andere taken worden doorgaans gemaakt en gestart binnen 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()); }
Opmerkingen
- Dit zijn “run-to-complete” -taken, toch?
- @EdgarBonet I ‘ Ik weet niet precies wat je bedoelt. Nadat de
run()
-methode is aangeroepen, wordt deze niet onderbroken, dus het heeft de verantwoordelijkheid om redelijk snel te eindigen. Meestal zal het echter ‘ zijn werk doen en zichzelf vervolgens opnieuw plannen (mogelijk automatisch, in het geval van een subklasse vanLoopTask
) voor sommigen toekomstige tijd. Een veelvoorkomend patroon is dat de taak een interne toestandsmachine onderhoudt (een triviaal voorbeeld is delight_on_p_
toestand hierboven), zodat deze zich op de juiste manier gedraagt wanneer deze de volgende keer moet plaatsvinden. - @EdgarBonet Het ‘ is een nuttig onderscheid, ja. Ik zou zowel deze stijl als de yield-stijl threads beschouwen als eenvoudig verschillende stijlen van coöperatieve threads, in tegenstelling tot preventieve threads, maar het ‘ is waar dat ze een andere benadering van ze coderen. Het zou interessant zijn om een doordachte en diepgaande vergelijking te zien van de verschillende hier genoemde benaderingen; een leuke bibliotheek die hierboven niet wordt genoemd, is protothreads . In beide vind ik dingen om te bekritiseren, maar ook om te prijzen. Ik geef (natuurlijk) de voorkeur aan mijn benadering, omdat deze het meest expliciet lijkt en geen extra stapels nodig heeft.
- (correctie: protothreads werd genoemd, in @sachleen ‘ s antwoord )
li> Dus ja, dit zijn run-to-completing (RtC) -taken: geen enkele taak kan worden uitgevoerd voordat de huidige zijn uitvoering heeft voltooid door terug te keren van run()
. Dit is in tegenstelling tot coöperatieve threads, die de CPU kunnen opleveren door bijvoorbeeld yield()
of delay()
aan te roepen. Of preventieve threads, die op elk moment kunnen worden gepland. Ik denk dat het onderscheid belangrijk is, omdat ik heb gezien dat veel mensen die hier naar threads zoeken, dit doen omdat ze liever blokkeercode schrijven dan toestandsmachines. Het blokkeren van echte threads die de CPU opleveren, is prima. Het blokkeren van RtC-taken is dat niet.