Gibt es eine Möglichkeit, mehrere Teile des Programms zusammen auszuführen, ohne mehrere Dinge im selben Codeblock auszuführen?
Eins Thread wartet auf ein externes Gerät und blinkt gleichzeitig eine LED in einem anderen Thread.
Kommentare
- Sie sollten sich wahrscheinlich zuerst fragen, ob Sie wirklich Threads benötigen. Timer sind möglicherweise bereits für Ihre Anforderungen in Ordnung und werden von Arduino nativ unterstützt.
- Sie können auch die Uzebox überprüfen. ‚ ist eine Homebrew-Videospielkonsole mit zwei Chips. Während es also nicht ‚ genau ein Arduino ist, basiert das gesamte System auf Interrupts. Audio, Video, Steuerelemente usw. werden also alle von Interrupts gesteuert, während sich das Hauptprogramm ‚ um nichts kümmern muss. Kann eine gute Referenz sein.
Antwort
Es gibt keine Unterstützung für mehrere Prozesse oder Multithreading der Arduino. Mit etwas Software können Sie jedoch mehrere Threads ausführen.
Sie möchten sich Protothreads ansehen:
Protothreads sind extrem leichte, stapellose Threads, die für stark speicherbeschränkte Systeme wie kleine eingebettete Systeme oder drahtlose Sensornetzwerkknoten entwickelt wurden. Protothreads bieten eine lineare Codeausführung für in C implementierte ereignisgesteuerte Systeme. Protothreads können mit oder ohne zugrunde liegendes Betriebssystem verwendet werden, um blockierende Ereignishandler bereitzustellen. Protothreads bieten einen sequentiellen Steuerungsfluss ohne komplexe Zustandsautomaten oder vollständiges Multithreading.
Natürlich gibt es ein Arduino-Beispiel hier mit Beispielcode . Diese SO-Frage könnte ebenfalls nützlich sein.
ArduinoThread ist eine gute auch.
Kommentare
- Beachten Sie, dass der Arduino DUE eine Ausnahme mit mehreren Regelkreisen hat: arduino.cc/en/Tutorial/MultipleBlinks
Antwort
AVR-basierte Arduinos unterstützen kein (Hardware-) Threading. Ich bin mit den ARM-basierten Arduinos nicht vertraut. Ein Weg, um diese Einschränkung zu umgehen, ist die Verwendung von Interrupts, insbesondere zeitgesteuerten Interrupts. Sie können einen Timer so programmieren, dass die Hauptroutine alle paar Mikrosekunden unterbrochen wird, um eine bestimmte andere Routine auszuführen.
Antwort
Es ist möglich, softwareseitiges Multithreading auf dem Uno durchzuführen. Threading auf Hardwareebene wird nicht unterstützt.
Um Multithreading zu erreichen, muss ein grundlegender Scheduler implementiert und eine Prozess- oder Aufgabenliste verwaltet werden, um die verschiedenen Aufgaben zu verfolgen, die ausgeführt werden müssen.
Die Struktur eines sehr einfachen nicht präemptiven Schedulers wäre wie folgt:
//Pseudocode void loop() { for(i=o; i<n; i++) run(tasklist[i] for timelimit): }
Hier tasklist
kann ein Array von Funktionszeigern sein.
tasklist [] = {function1, function2, function3, ...}
Mit jeder Funktion der Form:
int function1(long time_available) { top: //Do short task if (run_time<time_available) goto top; }
Jede Funktion kann eine separate Aufgabe ausführen, z. B. function1
LED-Manipulationen durchführen und function2
Float-Berechnungen durchführen. Es liegt in der Verantwortung jeder Aufgabe (Funktion), die ihr zugewiesene Zeit einzuhalten.
Hoffentlich sollte dies ausreichen, um Ihnen den Einstieg zu erleichtern.
Kommentare
- Ich bin nicht sicher, ob ich über “ Threads bei Verwendung eines nicht präemptiven Schedulers. Ein solcher Scheduler existiert übrigens bereits als Arduino-Bibliothek: arduino.cc/en/Reference/Scheduler
- @jfpoilpret – Cooperative Multithreading ist eine echte Sache.
- Ja, Sie ‚ haben Recht! Mein Fehler; Es war so lange her, dass ich nicht mit kooperativem Multithreading konfrontiert war, dass Multithreading meiner Meinung nach präventiv sein musste.
Antwort
Gemäß der Beschreibung Ihrer Anforderungen:
- ein Thread wartet auf ein externes Gerät
- ein Thread blinkt eine LED
Es scheint, dass Sie einen Arduino-Interrupt für den ersten „Thread“ verwenden könnten (ich würde es eher „Task“ nennen).
Arduino-Interrupts können eine Funktion (Ihren Code) basierend auf einem externen aufrufen Ereignis (Spannungspegel oder Pegeländerung an einem digitalen Eingangspin), das Ihre Funktion sofort auslöst.
Ein wichtiger Punkt bei Interrupts ist jedoch, dass die aufgerufene Funktion so schnell wie möglich sein sollte (Normalerweise sollte es keinen delay()
-Aufruf oder eine andere API geben, die von delay()
abhängt.)
Wenn Sie beim Auslösen eines externen Ereignisses eine lange Aufgabe aktivieren müssen, können Sie möglicherweise einen kooperativen Scheduler verwenden und über Ihre Interrupt-Funktion eine neue Aufgabe hinzufügen.
Eine zweite wichtige Aufgabe Punkt über Interrupts ist, dass ihre Anzahl begrenzt ist (zB nur 2 auf UNO). Wenn Sie also mehr externe Ereignisse haben, müssen Sie eine Art Multiplexing aller Eingänge in einem implementieren und Ihre Interrupt-Funktion bestimmen lassen, welcher Multiplex-Eingang der eigentliche Auslöser ist.
Antwort
Eine einfache Lösung besteht darin, einen Scheduler zu verwenden. Es gibt mehrere Implementierungen. Dies beschreibt in Kürze eine, die für AVR- und SAM-basierte Karten verfügbar ist. Grundsätzlich startet ein einzelner Anruf eine Aufgabe; „Skizze innerhalb einer Skizze“.
#include <Scheduler.h> .... void setup() { ... Scheduler.start(taskSetup, taskLoop); }
Scheduler.start () fügt eine neue Aufgabe hinzu, die das taskSetup einmal ausführt und dann wiederholt taskLoop aufruft Arduino Skizze funktioniert. Die Aufgabe hat einen eigenen Stapel. Die Größe des Stapels ist ein optionaler Parameter. Die Standardstapelgröße beträgt 128 Byte.
Um das Umschalten des Kontexts zu ermöglichen, müssen die Aufgaben yield () oder delay ( ) . Es gibt auch ein Unterstützungsmakro zum Warten auf eine Bedingung.
await(Serial.available());
Das Makro ist syntaktischer Zucker für Folgendes:
while (!(Serial.available())) yield();
Warten Sie auch zum Synchronisieren von Aufgaben verwendet werden. Unten finden Sie ein Beispiel-Snippet:
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); }
Weitere Informationen finden Sie in den Beispielen . Es gibt Beispiele für das Blinken mehrerer LEDs zum Entprellen und eine einfache Shell mit nicht blockierendem Befehlszeilenlesen. Vorlagen und Namespaces können verwendet werden, um den Quellcode zu strukturieren und zu reduzieren. Die folgende Skizze zeigt, wie Vorlagenfunktionen für Multi-Blink verwendet werden. Mit 64 Bytes reicht es für den Stack.
#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(); }
Es gibt auch einen Benchmark , um eine Vorstellung von der Leistung, dh der Zeit, zu geben zum Starten von Aufgaben, Kontextwechseln usw.
Zuletzt gibt es einige Unterstützungsklassen für die Synchronisierung und Kommunikation auf Aufgabenebene. Warteschlange und Semaphor .
Antwort
Ich bin auch zu diesem Thema gekommen, als ich eine Matrix-LED-Anzeige implementiert habe.
In einem Wort Sie können einen Abfrageplaner mithilfe der Millis () – Funktion und des Timer-Interrupts in Arduino erstellen.
Ich schlage die folgenden Artikel von Bill Earl vor:
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
Antwort
Aus einer früheren Beschwörung dieses Forums wurde die folgende Frage / Antwort in die Elektrotechnik verschoben. Es verfügt über einen Arduino-Beispielcode zum Blinken einer LED mithilfe eines Timer-Interrupts, während die Hauptschleife für die serielle E / A verwendet wird.
Repost:
Interrupts sind eine übliche Methode, um Dinge zu erledigen, während etwas anderes läuft. Im folgenden Beispiel blinkt die LED ohne Verwendung von delay()
. Immer wenn Timer1
ausgelöst wird, wird die Interrupt-Serviceroutine (ISR) isrBlinker()
aufgerufen. Es schaltet die LED ein / aus.
Um zu zeigen, dass andere Dinge gleichzeitig passieren können, schreibt loop()
wiederholt foo / bar an die serielle Schnittstelle, unabhängig davon, ob die LED blinkt .
#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); }
Dies ist eine sehr einfache Demo. ISRs können viel komplexer sein und durch Timer und externe Ereignisse (Pins) ausgelöst werden. Viele der gängigen Bibliotheken werden mithilfe von ISRs implementiert.
Antwort
Sie können auch meine ThreadHandler-Bibliothek ausprobieren
https://bitbucket.org/adamb3_14/threadhandler/src/master/
Es wird ein unterbrechender Scheduler verwendet, um das Umschalten des Kontexts ohne Weiterleitung zu ermöglichen Yield () oder Delay ().
Ich habe die Bibliothek erstellt, weil ich drei Threads benötigte und zwei davon, um zu einem genauen Zeitpunkt ausgeführt zu werden, unabhängig davon, was die anderen taten. Der erste Thread behandelte die serielle Kommunikation. Der zweite war das Ausführen eines Kalman-Filters unter Verwendung der Float-Matrix-Multiplikation mit der Eigen-Bibliothek. Und der dritte war ein schneller Stromregelkreis-Thread, der in der Lage sein musste, die Matrixberechnungen zu unterbrechen.
Funktionsweise
Jeder zyklische Thread hat eine Priorität und eine Periode. Wenn ein Thread mit einer höheren Priorität als der aktuell ausgeführte Thread seine nächste Ausführungszeit erreicht, hält der Scheduler den aktuellen Thread an und wechselt zu dem Thread mit der höheren Priorität. Sobald der Thread mit hoher Priorität seine Ausführung abgeschlossen hat, wechselt der Scheduler zurück zum vorherigen Thread.
Planungsregeln
Das Planungsschema der ThreadHandler-Bibliothek lautet wie folgt:
- Höchste Priorität zuerst.
- Wenn die Priorität gleich ist, wird zuerst der Thread mit der frühesten Frist ausgeführt.
- Wenn zwei Threads dieselbe Frist haben, wird der zuerst erstellte Thread zuerst ausgeführt.
- Ein Thread kann nur von Threads mit höherer Priorität unterbrochen werden.
- Sobald ein Thread ausgeführt wird, blockiert er die Ausführung für alle Threads mit niedrigerer Priorität, bis die Ausführungsfunktion zurückkehrt.
- Die Schleifenfunktion hat im Vergleich zu ThreadHandler-Threads die Priorität -128.
Verwendung
Threads können über die C ++ – Vererbung erstellt werden
class MyThread : public Thread { public: MyThread() : Thread(priority, period, offset){} virtual ~MyThread(){} virtual void run() { //code to run } }; MyThread* threadObj = new MyThread();
Oder über createThread und eine Lambda-Funktion
Thread* myThread = createThread(priority, period, offset, []() { //code to run });
Thread-Objekte stellen beim Erstellen automatisch eine Verbindung zum ThreadHandler her.
Um die Ausführung der erstellten Thread-Objekte zu starten, rufen Sie Folgendes auf:
ThreadHandler::getInstance()->enableThreadExecution();
An swer
Und hier ist noch eine weitere kooperative Multitasking-Bibliothek für Mikroprozessoren – PQRST: eine Prioritätswarteschlange zum Ausführen einfacher Aufgaben.
- Homepage
- Dokumentation auf Klassenebene
- Repository mit Downloads und Problemliste
In diesem Modell wird ein Thread als Unterklasse einer Task
implementiert, die für eine zukünftige Zeit geplant ist (und möglicherweise in regelmäßigen Abständen neu geplant wird, wenn er, wie üblich, LoopTask
stattdessen). Die Methode run()
des Objekts wird aufgerufen, wenn die Aufgabe fällig wird. Die Methode run()
erledigt einige ordnungsgemäße Arbeiten und kehrt dann zurück (dies ist das kooperative Bit). In der Regel wird eine Art Zustandsmaschine verwaltet, um ihre Aktionen bei aufeinanderfolgenden Aufrufen zu verwalten (ein triviales Beispiel ist die Variable light_on_p_
im folgenden Beispiel). Es erfordert ein leichtes Überdenken Ihrer Vorgehensweise Organisieren Sie Ihren Code, hat sich jedoch bei ziemlich intensiver Nutzung als sehr flexibel und robust erwiesen.
Es ist agnostisch in Bezug auf die Zeiteinheiten, daher ist es genauso glücklich, in Einheiten von millis()
als micros()
oder ein anderes geeignetes Häkchen.
Hier ist das blink-Programm, das mit dieser Bibliothek implementiert wurde. Hier wird nur eine einzelne Aufgabe ausgeführt: Andere Aufgaben werden normalerweise innerhalb von setup()
erstellt und gestartet.
#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()); }
Kommentare
- Dies sind Aufgaben, die vollständig ausgeführt werden müssen, oder?
- @EdgarBonet I ‚ Ich bin mir nicht ganz sicher, was du meinst. Nachdem die
run()
-Methode aufgerufen wurde, wird sie nicht unterbrochen, sodass sie dafür verantwortlich ist, angemessen schnell fertig zu werden. In der Regel erledigt es jedoch ‚ seine Arbeit und plant sich dann selbst neu (möglicherweise automatisch im Fall einer Unterklasse vonLoopTask
) für einige Zukunft. Ein gängiges Muster besteht darin, dass die Aufgabe eine interne Zustandsmaschine verwaltet (ein triviales Beispiel ist derlight_on_p_
-Zustand oben), damit sie sich bei der nächsten Fälligkeit angemessen verhält. - @EdgarBonet ‚ ist eine nützliche Unterscheidung, ja. Ich würde sowohl diesen Stil als auch Threads im Yield-Stil als einfach unterschiedliche Stile von kooperativem Thread betrachten, im Gegensatz zu präemptiven Threads, aber es ist wahr, dass sie einen anderen Ansatz erfordern, um sie zu verwenden. ‚ Codierung. Es wäre interessant, einen durchdachten und eingehenden Vergleich der verschiedenen hier genannten Ansätze zu sehen. Eine nette Bibliothek, die oben nicht erwähnt wurde, ist Protothreads . Ich finde Dinge zu kritisieren, aber auch zu loben. Ich bevorzuge (natürlich) meinen Ansatz, da er am explizitesten erscheint und keine zusätzlichen Stapel benötigt.
- (Korrektur: Protothreads wurde in @sachleen ‚ s Antwort )
li> Also ja, das sind RtC-Aufgaben (Run-to-Completion): Keine Aufgabe kann ausgeführt werden, bevor die aktuelle ihre Ausführung abgeschlossen hat, indem sie von run()
zurückkehrt. Dies steht im Gegensatz zu kooperativen Threads, die die CPU liefern können, indem sie beispielsweise yield()
oder delay()
aufrufen. Oder präventive Threads, die jederzeit geplant werden können. Ich halte die Unterscheidung für wichtig, da ich gesehen habe, dass viele Leute, die hier nach Threads suchen, dies tun, weil sie lieber Blockcode als Zustandsautomaten schreiben. Das Blockieren von echten Threads, die die CPU ergeben, ist in Ordnung. Das Blockieren von RtC-Aufgaben ist nicht möglich.