Gibt es eine Möglichkeit, mehrere Teile des Programms zusammen laufen zu lassen, ohne mehrere Dinge im selben Codeblock zu tun?
Ein Thread wartet auf ein externes Gerät, während gleichzeitig eine LED in einem anderen Thread blinkt.
Gibt es eine Möglichkeit, mehrere Teile des Programms zusammen laufen zu lassen, ohne mehrere Dinge im selben Codeblock zu tun?
Ein Thread wartet auf ein externes Gerät, während gleichzeitig eine LED in einem anderen Thread blinkt.
Antworten:
Auf dem Arduino gibt es keine Unterstützung für mehrere Prozesse und kein Multi-Threading. Mit einiger Software können Sie jedoch nahezu mehrere Threads ausführen.
Sie möchten sich Protothreads ansehen :
Protothreads sind extrem leichte stapellose Threads, die für Systeme mit stark eingeschränktem Arbeitsspeicher entwickelt wurden, z. B. kleine eingebettete Systeme oder drahtlose Sensornetzwerkknoten. Protothreads bieten eine lineare Codeausführung für ereignisgesteuerte Systeme, die in C implementiert sind. Protothreads können mit oder ohne zugrunde liegendes Betriebssystem verwendet werden, um blockierende Ereignishandler bereitzustellen. Protothreads ermöglichen einen sequenziellen Steuerungsfluss ohne komplexe Zustandsmaschinen oder vollständiges Multithreading.
Natürlich gibt es ein Arduino Beispiel hier mit Beispielcode . Diese SO-Frage könnte auch nützlich sein.
ArduinoThread ist auch gut.
AVR-basierte Arduinos unterstützen kein (Hardware-) Threading. Ich bin mit ARM-basierten Arduinos nicht vertraut. Ein Weg, um diese Einschränkung zu umgehen, ist die Verwendung von Interrupts, insbesondere von zeitgesteuerten Interrupts. Sie können einen Timer programmieren, um die Hauptroutine alle so viele Mikrosekunden zu unterbrechen und eine bestimmte andere Routine auszuführen.
Auf dem Uno ist softwareseitiges Multithreading möglich. Hardware Level Threading wird nicht unterstützt.
Um Multithreading zu erreichen, ist die Implementierung eines grundlegenden Schedulers und die Verwaltung eines Prozesses oder einer Aufgabenliste erforderlich, um die verschiedenen auszuführenden Aufgaben zu verfolgen.
Die Struktur eines sehr einfachen nicht präemptiven Schedulers sieht folgendermaßen aus:
//Pseudocode
void loop()
{
for(i=o; i<n; i++)
run(tasklist[i] for timelimit):
}
Hierbei tasklist
kann es sich um ein Array von Funktionszeigern handeln.
tasklist [] = {function1, function2, function3, ...}
Mit jeder Funktion des Formulars:
int function1(long time_available)
{
top:
//Do short task
if (run_time<time_available)
goto top;
}
Jede Funktion kann eine separate Aufgabe function1
ausführen, z. B. LED-Manipulationen und function2
Float-Berechnungen. Es liegt in der Verantwortung jeder Aufgabe (Funktion), die ihr zugewiesene Zeit einzuhalten.
Hoffentlich sollte dies ausreichen, um Ihnen den Einstieg zu erleichtern.
Nach der Beschreibung Ihrer Anforderungen:
Es scheint, dass Sie einen Arduino-Interrupt für den ersten "Thread" verwenden könnten (ich würde es eher als "Task" bezeichnen).
Arduino-Interrupts können eine Funktion (Ihren Code) basierend auf einem externen Ereignis (Spannungspegel oder Pegeländerung an einem digitalen Eingangspin) aufrufen, die Ihre Funktion sofort auslöst.
Ein wichtiger Punkt bei Interrupts ist jedoch, dass die aufgerufene Funktion so schnell wie möglich ist (normalerweise sollte es keinen delay()
Aufruf oder eine andere API geben, die davon abhängt delay()
).
Wenn Sie eine lange Aufgabe haben, die beim Auslösen eines externen Ereignisses aktiviert werden muss, können Sie möglicherweise einen kooperativen Scheduler verwenden und dieser von Ihrer Interrupt-Funktion aus eine neue Aufgabe hinzufügen.
Ein zweiter wichtiger Punkt bei Interrupts ist, dass ihre Anzahl begrenzt ist (z. B. nur 2 bei UNO). Wenn Sie also mehr externe Ereignisse haben möchten, müssen Sie eine Art Multiplexing aller Eingänge in einen implementieren und Ihre Interrupt-Funktion bestimmen lassen, welcher Multiplexing-Eingang der eigentliche Auslöser war.
Eine einfache Lösung ist die Verwendung eines Schedulers . 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. msgstr "innerhalb einer Skizze skizzieren".
#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, genau wie die 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 Kontextwechsel zu ermöglichen, müssen die Tasks yield () oder delay () aufrufen . Es gibt auch ein Unterstützungsmakro für das Warten auf eine Bedingung.
await(Serial.available());
Das Makro ist syntaktischer Zucker für Folgendes:
while (!(Serial.available())) yield();
Warten kann auch zum Synchronisieren von Aufgaben verwendet werden. Unten ist 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 Details finden Sie in den Beispielen . Es gibt Beispiele für das Blinken mehrerer LEDs, um die Schaltfläche zu entprellen, und eine einfache Shell mit nicht blockierendem Befehlszeilenlesevorgang. Vorlagen und Namespaces können zum Strukturieren und Reduzieren des Quellcodes verwendet werden. Die folgende Skizze zeigt, wie Vorlagenfunktionen für Multi-Blink verwendet werden. Es reicht mit 64 Bytes 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 , der eine Vorstellung von der Leistung gibt, dh Zeit zum Starten der Aufgabe, Kontextwechsel usw.
Zuletzt gibt es einige Support-Klassen für die Synchronisierung und Kommunikation auf Task-Ebene. Warteschlange und Semaphor .
Aus einer früheren Beschwörung dieses Forums wurde die folgende Frage / Antwort nach Elektrotechnik verschoben. Es hat Beispiel-Arduino-Code, um eine LED mit einem Timer-Interrupt zu blinken, während die Hauptschleife für 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()
. Bei jedem Timer1
Brand wird die Interrupt Service Routine (ISR) isrBlinker()
aufgerufen. Es schaltet die LED ein / aus.
Um zu zeigen, dass andere Dinge gleichzeitig passieren können, wird loop()
wiederholt foo / bar auf die serielle Schnittstelle geschrieben, 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 sehr viel komplexer sein und durch Timer und externe Ereignisse (Pins) ausgelöst werden. Viele der gängigen Bibliotheken werden mithilfe von ISRs implementiert.
Ich bin auch auf dieses Thema gekommen, als ich eine Matrix-LED-Anzeige implementiert habe.
Mit einem Wort, Sie können einen Abrufplaner erstellen, indem Sie in Arduino die Funktion millis () und den Timer-Interrupt verwenden.
Ich schlage folgende 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
Sie könnten auch meine ThreadHandler-Bibliothek ausprobieren
https://bitbucket.org/adamb3_14/threadhandler/src/master/
Es verwendet einen unterbrechenden Scheduler, um das Umschalten des Kontexts zu ermöglichen, ohne auf yield () oder delay () zu verweisen.
Ich habe die Bibliothek erstellt, weil ich drei Threads brauchte und zwei von ihnen, um genau zu laufen, egal was die anderen taten. Der erste Thread behandelte die serielle Kommunikation. Im zweiten Beispiel wurde ein Kalman-Filter mit Float-Matrix-Multiplikation mit der Eigen-Bibliothek ausgeführt. Und der dritte war ein schneller Stromregelkreis-Thread, der in der Lage sein musste, die Matrixberechnungen zu unterbrechen.
Jeder zyklische Thread hat eine Priorität und eine Periode. Wenn ein Thread mit höherer 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.
Das Planungsschema der ThreadHandler-Bibliothek lautet wie folgt:
Threads können über 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.
So starten Sie die Ausführung von erstellten Thread-Objekten:
ThreadHandler::getInstance()->enableThreadExecution();
Und hier ist noch eine weitere kooperative Mikroprozessor-Multitasking-Bibliothek - PQRST: eine Prioritätswarteschlange zum Ausführen einfacher Aufgaben.
In diesem Modell wird ein Thread als Unterklasse von a implementiert Task
, die für einen späteren Zeitpunkt geplant ist (und möglicherweise in regelmäßigen Abständen neu geplant wird, wenn es wie üblich LoopTask
stattdessen Unterklassen gibt ). Die run()
Methode des Objekts wird aufgerufen, wenn die Aufgabe fällig wird. Die run()
Methode erledigt einige fällige Arbeiten und gibt dann zurück (dies ist das kooperative Bit). In der Regel wird eine Art Zustandsmaschine verwaltet, um die Aktionen bei aufeinanderfolgenden Aufrufen zu verwalten (ein einfaches Beispiel ist die light_on_p_
Variable im folgenden Beispiel). Es erfordert ein leichtes Überdenken der Code-Organisation, hat sich jedoch bei intensiver Nutzung als sehr flexibel und robust erwiesen.
Es ist agnostisch in Bezug auf die Zeiteinheiten, so dass es genauso glücklich ist, in Einheiten von millis()
wie micros()
oder jedem anderen Tick zu laufen, der praktisch ist.
Hier ist das 'Blink'-Programm, das mit dieser Bibliothek implementiert wurde. Dies zeigt nur eine einzige ausgeführte Aufgabe: In der Regel werden andere Aufgaben erstellt und innerhalb von gestartet 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());
}
run()
Methode aufgerufen wurde, wird sie nicht unterbrochen, sodass sie die Verantwortung hat, angemessen schnell fertig zu werden. In der Regel erledigt es jedoch seine Arbeit und plant sich dann LoopTask
für einige Zeit selbst neu (möglicherweise automatisch, im Fall einer Unterklasse von ). Ein gängiges Muster besteht darin, dass die Aufgabe eine interne Zustandsmaschine verwaltet (ein einfaches Beispiel ist der light_on_p_
obige Zustand), damit sie sich bei nächster Fälligkeit angemessen verhält.
run()
. Dies steht im Gegensatz zu kooperativen Threads, die die CPU beispielsweise durch Aufrufen von yield()
oder ausgeben können delay()
. Oder präventive Threads, die jederzeit terminiert 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 es vorziehen, blockierenden Code zu schreiben, anstatt Zustandsautomaten. Das Blockieren von echten Threads, die die CPU belasten, ist in Ordnung. Das Blockieren von RtC-Aufgaben ist nicht möglich.