Existuje způsob, jak můžu mít více částí programu spuštěných společně, aniž bych dělal více věcí ve stejném bloku kódu?
Jeden vlákno čeká na externí zařízení a zároveň bliká LED v jiném vlákně.
Komentáře
- Nejspíš byste si měli nejprve položit otázku, zda vlákna opravdu potřebujete. Časovače mohou být pro vaše potřeby již v pořádku a na Arduinu jsou nativně podporovány.
- Možná budete chtít vyzkoušet také Uzebox. Je to ‚ dvoukomorová domácí herní konzole. Takže i když to není ‚ t přesně Arduino, celý systém je postaven na přerušeních. Zvuk, video, ovládací prvky atd. Jsou tedy řízeny přerušením, zatímco hlavní program se nemusí ničeho bát. ‚ Může to být dobrá reference.
Odpovědět
Neexistuje podpora více procesů ani podprocesů Arduino. S nějakým softwarem však můžete udělat něco blízkého více vláknům.
Chcete se podívat na Protothreads :
Protothreads jsou extrémně lehká stackless vlákna navržená pro systémy s velkým omezením paměti, jako jsou malé vestavěné systémy nebo bezdrátové síťové uzly senzorů. Protothreads poskytují lineární provádění kódu pro systémy řízené událostmi implementované v C. Protothreads lze použít s nebo bez základního operačního systému k poskytnutí blokujících obslužných rutin událostí. Protothreads poskytují sekvenční tok řízení bez složitých stavových strojů nebo plného multi-threadingu.
Samozřejmě existuje příklad Arduina zde s ukázkovým kódem . Tato SO otázka může být také užitečná.
ArduinoThread je dobrý taky.
Komentáře
- Pamatujte, že Arduino DUE má výjimku z toho, s několika ovládacími smyčkami: arduino.cc/en/Tutorial/MultipleBlinks
odpověď
Arduino založené na AVR nepodporuje (hardwarové) vytváření závitů, neznám ARU založené Arduino. Jednou z možností, jak toto omezení obejít, je použití přerušení, zejména časovaných přerušení. Můžete naprogramovat časovač tak, aby přerušil hlavní rutinu každých tolik mikrosekund a spustil další konkrétní rutinu.
Odpověď
Na Uno je možné provádět multi-threading na straně softwaru. Threading na úrovni hardwaru není podporován.
K dosažení multithreadingu bude vyžadovat implementaci základního plánovače a udržování seznamu procesů nebo úkolů pro sledování různých úkolů, které je třeba spustit.
Struktura velmi jednoduchého nepreventivního plánovače by vypadala takto:
//Pseudocode void loop() { for(i=o; i<n; i++) run(tasklist[i] for timelimit): }
Zde, tasklist
může být pole ukazatelů funkcí.
tasklist [] = {function1, function2, function3, ...}
S každou funkcí formuláře:
int function1(long time_available) { top: //Do short task if (run_time<time_available) goto top; }
Každá funkce může provádět samostatnou úlohu, například function1
provádět LED manipulace a function2
provádět floatové výpočty. Je úkolem každé úlohy (funkce) dodržet čas, který jí byl přidělen.
Doufejme, že by to mělo stačit, abyste mohli začít.
Komentáře
- Nejsem si jistý, jestli budu mluvit o “ vláknech “ při použití nepreventivního plánovače. Mimochodem, takový plánovač již existuje jako knihovna arduino: arduino.cc/en/Reference/Scheduler
- @jfpoilpret – kooperativní multithreading je skutečná věc.
- Ano, ‚ máte pravdu! Moje chyba; bylo to tak dávno, co jsem nečelil kooperativnímu multithreadingu, že v mé mysli muselo být multithreading preventivní.
Odpověď
Podle popisu vašich požadavků:
- jedno vlákno čekající na externí zařízení
- jedno vlákno blikající LED
Zdá se, že byste mohli použít jedno přerušení Arduino pro první „vlákno“ (ve skutečnosti bych to nazval spíše „úkol“).
Přerušení Arduino mohou volat jednu funkci (váš kód) na základě externího událost (úroveň napětí nebo změna úrovně na digitálním vstupním kolíku), která okamžitě spustí vaši funkci.
Jedním důležitým bodem, který je třeba mít na paměti při přerušeních, je to, že volaná funkce by měla být co nejrychlejší (obvykle by nemělo docházet k delay()
volání ani k žádnému jinému rozhraní API, které by záviselo na delay()
).
Pokud máte dlouhý úkol k aktivaci při aktivaci externí události, můžete potenciálně použít kooperativní plánovač a přidat k němu nový úkol ze své funkce přerušení.
Druhá důležitá bodem přerušení je, že jejich počet je omezený (např. pouze 2 na UNO). Takže pokud začnete mít více externích událostí, budete muset implementovat nějaký druh multiplexování všech vstupů do jednoho a nechat svou funkci přerušení určit, který multiplexovaný inut byl skutečným spouštěčem.
Odpověď
Jednoduchým řešením je použít plánovač . Existuje několik implementací. Toto stručně popisuje jeden, který je k dispozici pro desky založené na AVR a SAM. Jediným hovorem bude zahájen úkol; „skica do skici“.
#include <Scheduler.h> .... void setup() { ... Scheduler.start(taskSetup, taskLoop); }
Scheduler.start () přidá nový úkol, který jednou spustí taskSetup a poté opakovaně zavolá taskLoop stejně jako Arduino skica funguje. Úkol má svůj vlastní zásobník. Velikost zásobníku je volitelný parametr. Výchozí velikost zásobníku je 128 bajtů.
Chcete-li povolit přepínání kontextu, je třeba volat yield () nebo zpoždění ( ) . K dispozici je také podpůrné makro pro čekání na podmínku.
await(Serial.available());
Makro je syntaktický cukr pro následující:
while (!(Serial.available())) yield();
Await can also slouží k synchronizaci úkolů. Níže je uveden ukázkový úryvek:
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); }
Další podrobnosti naleznete v příkladech . Existují příklady od několika bliknutí LED po odblokovací tlačítko a jednoduchého prostředí s přečtením neblokujícího příkazového řádku. Šablony a obory názvů lze použít ke strukturování a zmenšení zdrojového kódu. Níže náčrtek ukazuje, jak používat funkce šablony pro více blikání. Pro zásobník je to dostatečné s 64 bajty.
#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(); }
K dispozici je také měřítko , které poskytuje představu o výkonu, tj. čase ke spuštění úkolu, přepnutí kontextu atd.
Nakonec existuje několik podpůrných tříd pro synchronizaci a komunikaci na úrovni úkolu; Fronta a semafor .
Odpověď
K tomuto tématu jsem se také dostal při implementaci maticového LED displeje.
Jedním slovem , můžete vytvořit plánovač dotazování pomocí funkce millis () a přerušení časovače v Arduinu.
Navrhuji následující články od Billa Earla:
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
Odpověď
Z předchozího zaklínadla tohoto fóra byla následující otázka / odpověď přesunuta do Elektrotechniky. Má ukázkový arduino kód k blikání kontrolky LED pomocí přerušení časovače při použití hlavní smyčky k provádění sériového vstupu.
Repost:
Přerušení je běžný způsob, jak dělat věci, když se děje něco jiného. V níže uvedeném příkladu bliká kontrolka LED bez použití delay()
. Kdykoli Timer1
vystřelí, je volána rutina služby přerušení (ISR) isrBlinker()
. Zapíná / vypíná LED.
Chcete-li ukázat, že se mohou současně stát i jiné věci, loop()
opakovaně zapisuje foo / bar na sériový port nezávisle na blikající 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); }
Toto je velmi jednoduchá ukázka. ISR mohou být mnohem složitější a mohou být spuštěny časovači a externími událostmi (piny). Mnoho běžných knihoven je implementováno pomocí ISR.
Odpověď
Můžete také vyzkoušet moji knihovnu ThreadHandler
https://bitbucket.org/adamb3_14/threadhandler/src/master/
Používá plánovač přerušení, který umožňuje přepínání kontextu bez nutnosti přepínání výnos () nebo delay ().
Knihovnu jsem vytvořil proto, že jsem potřeboval tři vlákna a dva z nich jsem potřeboval k běhu v přesný čas bez ohledu na to, co ostatní dělají. První vlákno zvládlo sériovou komunikaci. Druhým bylo spuštění Kalmanova filtru pomocí násobení float matrix s knihovnou Eigen. A třetí bylo vlákno rychlého řízení proudu, které muselo být schopné přerušit maticové výpočty.
Jak to funguje
Každé cyklické vlákno má prioritu a tečku. Pokud vlákno s vyšší prioritou než aktuální provádějící vlákno dosáhne svého dalšího času spuštění, plánovač pozastaví aktuální vlákno a přepne se na vlákno s vyšší prioritou. Jakmile vlákno s vysokou prioritou dokončí své spuštění, plánovač se přepne zpět na předchozí vlákno.
Pravidla plánování
Schéma plánování knihovny ThreadHandler je následující:
- Nejvyšší priorita jako první.
- Pokud je priorita stejná, nejprve se provede vlákno s nejranějším termínem.
- Pokud mají dvě vlákna stejnou lhůtu, provede se nejprve vytvořené vlákno jako první.
- Vlákno lze přerušit pouze vlákny s vyšší prioritou.
- Jakmile se vlákno spustí, zablokuje provádění pro všechna vlákna s nižší prioritou, dokud se funkce běhu nevrátí.
- Funkce smyčky má ve srovnání s vlákny ThreadHandler prioritu -128.
Jak používat
Vlákna lze vytvářet pomocí dědičnosti c ++
class MyThread : public Thread { public: MyThread() : Thread(priority, period, offset){} virtual ~MyThread(){} virtual void run() { //code to run } }; MyThread* threadObj = new MyThread();
Nebo pomocí createThread a funkce lambda
Thread* myThread = createThread(priority, period, offset, []() { //code to run });
Objekty vláken se automaticky připojí k ThreadHandler, jakmile jsou vytvořeny.
Chcete-li spustit provádění vytvořených objektů vlákna, volejte:
ThreadHandler::getInstance()->enableThreadExecution();
swer
A je tu ještě další mikroprocesorová kooperativní multitaskingová knihovna – PQRST: prioritní fronta pro spouštění jednoduchých úkolů.
- Domovská stránka
- Dokumentace na úrovni třídy
- Úložiště se stažením a seznamem problémů
V tomto model, vlákno je implementováno jako podtřída Task
, která je naplánována na nějakou budoucnost (a případně přeložena v pravidelných intervalech, pokud, jak je běžné, podtřídy LoopTask
)). Metoda run()
objektu je volána, když je úkol splatný. Metoda run()
provede nějakou náležitou práci a poté se vrátí (jedná se o kooperativní bit); obvykle bude udržovat nějaký stavový stroj, který bude spravovat jeho akce při následných vyvoláních (triviálním příkladem je proměnná v níže uvedeném příkladu). Uspořádejte svůj kód, ale ukázalo se, že je velmi flexibilní a robustní při poměrně intenzivním používání.
Je agnostický ohledně časových jednotek, takže je stejně šťastný, že běží v jednotkách millis()
as micros()
nebo jakékoli jiné vhodné zaškrtnutí.
Zde je program „blink“ implementovaný pomocí této knihovny. Zobrazuje se pouze spuštěný jeden úkol: obvykle by byly vytvořeny další úkoly, které by byly spuštěny v rámci 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()); }
Komentáře
- Toto jsou úkoly „run-to-completion“, že?
- @EdgarBonet I ‚ nejsem si úplně jistý, co tím myslíte. Poté, co je metoda
run()
volána, není přerušena, takže má zodpovědnost přiměřeně rychle dokončit. Typicky však ‚ provede svoji práci a poté si sám naplánuje (případně automaticky, v případě podtřídyLoopTask
) pro některé budoucí čas. Běžným vzorem je úkol udržovat nějaký interní stavový stroj (triviálním příkladem je stavlight_on_p_
výše), aby se choval vhodně, až bude příště. - Takže ano, jedná se o úkoly run-to-completion (RtC): žádný úkol nelze spustit dříve, než ten aktuální dokončí své zpracování návratem z
run()
. To je v kontrastu s kooperativními vlákny, která mohou CPU získat například volánímyield()
nebodelay()
. Nebo preventivní vlákna, která lze kdykoli naplánovat. Cítím, že rozdíl je důležitý, protože jsem viděl, že mnoho lidí, kteří sem přijdou hledat vlákna, to dělají, protože dávají přednost psaní blokovacího kódu spíše než stavovým strojům. Blokování skutečných vláken, která přinášejí CPU, je v pořádku. Blokování úkolů RtC není. - @EdgarBonet Je to ‚ užitečný rozdíl, ano. Tento styl i vlákna ve stylu výnosu bych považoval za jednoduše různé styly kooperativního vlákna, na rozdíl od preemptivních vláken, ale ‚ je pravda, že vyžadují odlišný přístup k jejich kódování. Bylo by zajímavé vidět promyšlené a hloubkové srovnání různých zde zmíněných přístupů; jedna pěkná knihovna, která nebyla zmíněna výše, je protothreads . V obou případech najdu věci, které mohu kritizovat, ale také chválit. (Samozřejmě) dávám přednost svému přístupu, protože se zdá být nejexplicitnější a nepotřebuje žádné další hromádky.
- (oprava: protothreads byla zmíněna v @sachleen ‚ s odpovědí )