Is er een manier waarop ik meerdere delen van het programma samen kan laten draaien zonder meerdere dingen in hetzelfde codeblok te doen?
Een thread wacht op een extern apparaat terwijl ook een LED in een andere thread knippert.
Is er een manier waarop ik meerdere delen van het programma samen kan laten draaien zonder meerdere dingen in hetzelfde codeblok te doen?
Een thread wacht op een extern apparaat terwijl ook een LED in een andere thread knippert.
Er is geen ondersteuning voor meerdere processen of meerdere threads op de Arduino. Je kunt met sommige software echter iets doen dat in de buurt komt van meerdere threads.
Je wilt protothreads bekijken:
Protothreads zijn extreem lichtgewicht stapelloze threads ontworpen voor systemen met een ernstig geheugenbeperking, 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 hier een Arduino-voorbeeld met voorbeeldcode. Deze SO-vraag kan ook nuttig zijn.
ArduinoThread is ook een goede.
AVR gebaseerde Arduino's ondersteunen geen (hardware) threading, ik ben niet bekend met de ARM gebaseerde Arduino's. Een manier om deze beperking te omzeilen, is het gebruik van interrupts, vooral getimede interrupts. Je kunt een timer programmeren om de hoofdroutine om de zoveel microseconden te onderbreken, om een specifieke andere routine uit te voeren.
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:
// Pseudocodevoid loop () {voor (i = o; i<n; i ++) run ( takenlijst [i] voor tijdslimiet):}
Hier kan takenlijst
een reeks functie-aanwijzers zijn.
takenlijst [ ] = {functie1, functie2, functie3, ...}
Met elke functie van de vorm:
int functie1 (lange tijd_beschikbaar) {top: // Voer een korte taak uit als (run_time<time_available) goto top;}
Elke functie kan een afzonderlijke taak uitvoeren, zoals function1
LED-manipulaties uitvoeren, en function2
doet float-berekeningen. 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.
Volgens de beschrijving van uw vereisten:
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) oproepen op basis van een externe gebeurtenis (spanningsniveau of niveauverandering op een digitale ingangspen), dat zal uw functie onmiddellijk 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 of een andere API moeten zijn die afhankelijk is van delay()
).
Als je een lange taak hebt om te activeren bij trigger van externe gebeurtenissen, dan zou je mogelijk een coöperatieve planner kunnen gebruiken en er een nieuwe taak aan toevoegen vanuit je interrupt-functie.
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 ingangen in één moeten implementeren, en je interruptfunctie laten bepalen welke gemultiplexte ingang de feitelijke trigger was.
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; "schets binnen een schets".
#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 zal aanroepen, 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 () aanroepen. 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 ();
Await kan ook worden gebruikt om taken te synchroniseren. Hieronder is een voorbeeldfragment:
vluchtig int taskEvent = 0; #define signal (evt) do {await (taskEvent == 0); taskEvent = evt; } while (0) ... void taskLoop () {wachten (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. Onderstaande schets laat zien hoe u sjabloonfuncties gebruikt voor meervoudig knipperen. Het is voldoende met 64 bytes voor de stapel.
#include <Scheduler.h>template<int pin> void setupBlink () {pinMode (pin, OUTPUT);} template<int pin, niet-ondertekende int ms<int vertraging (ms); digitalWrite (pin, LOW); vertraging (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 wat idee van de prestatie, dwz tijd om de taak te starten, contextwisseling, enz.
Ten slotte zijn er een paar ondersteuningsklassen voor synchronisatie en communicatie op taakniveau; wachtrij en semafoor.
Vanuit een eerdere bezwering van dit forum is de volgende vraag / antwoord verplaatst naar Electrical Engineering. Het heeft een voorbeeld van Arduino-code om een LED te laten knipperen met behulp van een timeronderbreking terwijl de hoofdlus wordt gebruikt om seriële IO uit te voeren.
Repost:
Onderbrekingen zijn een gebruikelijke manier om dingen gedaan te krijgen terwijl er iets anders aan de hand is Aan. In het onderstaande voorbeeld knippert de LED zonder gebruik te maken van delay ()
. 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, schrijft loop ()
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, aan? HIGH: LOW); on =! on;} void setup () {Serial.begin (9600); Serial.flush (); Serial.println ("Serial geïnitialiseerd"); pinMode (led, OUTPUT); // initialiseer de ISR-blinker Timer1.initialize (1000000); Timer1.attachInterrupt (isrBlinker);} void loop () {Serial.println ("foo"); vertraging (1000); Serial.println ("balk"); delay (1000);}
Dit is een heel eenvoudige demo. ISR's kunnen veel complexer zijn en kunnen worden geactiveerd door timers en externe gebeurtenissen (pinnen). Veel van de gebruikelijke bibliotheken worden geïmplementeerd met behulp van ISR's.
Ik kwam ook op dit onderwerp toen ik een matrix LED-display implementeerde.
In één woord, je kunt 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: // leren .adafruit.com / multi-tasking-the-arduino-part-3 / overzicht
Je zou mijn ThreadHandler-bibliotheek ook eens kunnen proberen
https://bitbucket.org/adamb3_14/threadhandler/src/master/
gebruikt een onderbrekende planner om contextwisseling mogelijk te maken zonder door te geven aan 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, wat er ook gebeurde de anderen deden het. 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.
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.
Het planningsschema van de ThreadHandler-bibliotheek is als volgt:
Threads kunnen worden gemaakt via c ++ overerving
class MyThread: public Thread {public: MyThread (): Thread (prioriteit, punt, offset) {} virtual ~ MyThread () {} virtuele leegte run () {// code om uit te voeren }}; MyThread * threadObj = nieuwe MyThread ();
Of via createThread en een lambda-functie
Thread * myThread = createThread (prioriteit, periode, 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 ();
En hier is nog een andere coöperatieve multitaskingbibliotheek met microprocessors - PQRST: een prioriteitswachtrij voor het uitvoeren van eenvoudige taken.
In dit model wordt een thread geïmplementeerd als een subklasse van een Taak
, die is gepland voor een toekomstige tijd (en mogelijk met regelmatige tussenpozen opnieuw wordt gepland, als het, zoals gebruikelijk, subklassen LoopTask
in plaats daarvan). De run ()
-methode van het object wordt aangeroepen wanneer de taak vervalt. De run ()
methode doet wat gepast werk, en keert dan terug (dit is het coöperatieve bit); het zal typisch een soort toestandsmachine onderhouden om zijn acties op opeenvolgende aanroepen te beheren (een triviaal voorbeeld is de variabele light_on_p_
in het onderstaande voorbeeld). Het vereist een kleine heroverweging van hoe je je code organiseert, maar het 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 te rennen. millis ()
als micros ()
, of een ander vinkje dat handig is.
Hier is het 'blink'-programma geïmplementeerd met behulp van deze bibliotheek. Dit laat zien dat slechts een enkele taak wordt uitgevoerd: andere taken worden normaal gesproken gemaakt en gestart met setup()
.
#include "pqrst.h" class BlinkTask: public LoopTask {privé: int my_pin_; bool light_on_p_; public: BlinkTask (int pin, ms_t cadans); 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) { // schakel de LED-status elke keer dat we light_on_p_ =! light_on_p_ worden genoemd; digitalWrite (my_pin_, light_on_p _);} // flash de ingebouwde LED met een 500ms cadenceBlinkTask flasher (LED_BUILTIN, 500); void setup () {pinMode (LED_BUILTIN, OUTPUT); flasher.start (2000); // start na 2000ms (= 2s)}
void loop () {Queue.run_ready (millis ());}