Arduino Multithreading mit Protothreading meistern: Ein Schritt-für-Schritt-Tutorial
Komponenten und Zubehör
| Komponente | Menge |
|---|---|
| Arduino UNO | 1 |
| Sunfounder Blue 16x2 LCD-Display | 1 |
| Steckbrett (Breadboard) und Jumper-Kabel | 1 |
| Drehpotentiometer (ca. 1 kΩ) | 1 |
Über dieses Projekt
Dieses Tutorial zeigt, wie man einen einzelnen Arduino-Kern dazu bringt, scheinbar drei Aufgaben gleichzeitig auszuführen:
- Die LCD-Hintergrundbeleuchtung in konstanter Rate pulsieren lassen, ohne Unterbrechung
- Einen Zähler jede Sekunde inkrementieren und auf dem Display anzeigen
- Mehrere Textnachrichten im Wechsel alle paar Sekunden ausgeben
Was ist Protothreading?
Protothreading ist eine Technik, um Multitasking-Operationen (also das gleichzeitige oder zeitversetzte Ausführen mehrerer Aufgaben) auf einem Arduino zu realisieren. Es wirkt wie „Multithreading“, doch der Arduino ist ein Single-Core-Mikrocontroller mit prozeduralem Code – echtes Multithreading ist also hardwaretechnisch unmöglich. Warum funktioniert Protothreading trotzdem?
„Echtes“ Multithreading vs. Protothreading
Um Protothreading zu verstehen, muss man wissen, warum es kein echtes Multithreading ist. Erinnern Sie sich an Intels „Hyperthreading“ bei Pentium-Prozessoren? Damit ließ ein einzelner Kern wie zwei Kerne erscheinen, indem er die Arbeitszyklen (Cycles) aufteilte. Auch Mikrocontroller arbeiten in Taktschritten: Der ATmega328P auf dem Arduino Uno läuft mit 16 MHz, also 16 Millionen Zyklen pro Sekunde. Doch diese Zyklen sind Prozessorinstruktionen, keine Codezeilen.
Bei echtem Multithreading auf Mehrkern-CPUs laufen separate Prozesse parallel auf verschiedenen Kernen. Ein Hauptthread verwaltet Worker-Threads. Da der Arduino nur einen Kern hat, ist das unmöglich. Wir müssen die Arbeit auf einem Kern managen, aber dennoch mehrere Dinge „gleichzeitig“ erledigen – hier kommt Protothreading ins Spiel.
Wie unterscheidet sich Protothreading?
Protothreading ähnelt Hyperthreading, nutzt aber statt virtueller Kerne die Struktur des Programmcodes. Anstatt CPU-Zyklen zu teilen, unterteilen wir die Arbeit in „Loops“ oder Codeabschnitte. Jedes Mal, wenn der Hauptloop nichts Dringendes tut, prüfen wir, ob eine andere Aufgabe an der Reihe ist. Wenn ja, verzweigen wir dorthin, erledigen den Job und kehren zurück.
Wichtig: Blockierende Operationen (z. B. SD-Karten-Lesezugriffe) stoppen auch andere Protothreads. Für schnelle, nicht-blockierende Aufgaben wie Variablenänderungen oder PWM-Ausgänge funktioniert es jedoch hervorragend. Echte RTOS-Lösungen auf leistungsfähigeren MCUs können blockierende Tasks besser handhaben.
Los geht’s
Zuerst definieren wir die Aufgaben und ordnen sie nach Blockierverhalten: Die häufigste, schnellste und am wenigsten blockierende Aufgabe wird zum Hauptthread (Manager). In unserem Beispiel ist das das Pulsieren der Hintergrundbeleuchtung. In dessen Loop rufen wir numberThread.check() und textThread.check() auf, um die anderen Tasks bei Bedarf auszuführen. Dies muss auch in allen inneren Loops geschehen, sonst blockieren sie die anderen Threads.
Die Intervalle werden bei der Initialisierung festgelegt (z. B. 700 ms für den Zähler, 3000 ms für den Textwechsel). Die Bibliothek TimedAction übernimmt das Timing. Der Code ist stark kommentiert – am besten direkt im Sketch nachvollziehen.
Code
Multithreaded LCD Code – multithread.ino (v1.1)
Der Sketch nutzt die Bibliothek TimedAction.h, um drei Aktionen mit unterschiedlichen Intervallen gleichzeitig auf einem Arduino Uno auszuführen: (a) Hintergrundbeleuchtung ein-/ausblenden, (b) Zähler inkrementieren, (c) Text rotieren.
/* Arduino Protothreading Example v1.1 by Drew Alden (@ReanimationXP) 12.01.2016
* Update v1.1 – 18.08.2017: Arduino 1.6.6+ Anpassungen, kleine Fixes.
* Hinweis: TimedAction ist veraltet. Bei Fehlern WProgram.h durch Arduino.h ersetzen.
*/
// KOMPONENTEN: Sunfounder Blue LCD aus Starter Kit (Amazon)
// DRITTBIBLIOTHEKEN
#include <TimedAction.h> // https://playground.arduino.cc/Code/TimedAction
// In TimedAction.h: WProgram.h → Arduino.h ändern
// NATIVE BIBLIOTHEKEN
#include <LiquidCrystal.h>
// GLOBALS
int backlightPin = 9;
int timerCounter = 0;
int stringNo = 0;
char* stringArray[] = {
"Check it out... ",
"I have 3 threads",
"going at once...",
"Cool, huh?! :D "
};
// INIT
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
// FUNKTIONEN
void incrementNumber() {
lcd.setCursor(0, 1);
timerCounter++;
lcd.print(timerCounter);
}
void changeText() {
lcd.setCursor(0, 0);
lcd.print(stringArray[stringNo]);
if (stringNo >= sizeof(stringArray)/sizeof(char*)) {
stringNo = 0;
changeText();
} else {
stringNo++;
}
}
// TIMER-OBJEKTE (nach Funktionsdefinitionen!)
TimedAction numberThread = TimedAction(700, incrementNumber);
TimedAction textThread = TimedAction(3000, changeText);
void setup() {
lcd.begin(16, 2);
changeText(); // initialen String anzeigen
}
void loop() {
numberThread.check();
textThread.check();
// Task 3: Backlight pulsieren (Hauptthread)
digitalWrite(13, HIGH);
for (int fadeValue = 0; fadeValue <= 255; fadeValue += 10) {
numberThread.check(); textThread.check();
analogWrite(backlightPin, fadeValue);
delay(20); // kurz halten!
}
digitalWrite(13, LOW);
for (int fadeValue = 255; fadeValue >= 0; fadeValue -= 10) {
numberThread.check(); textThread.check();
analogWrite(backlightPin, fadeValue);
delay(20);
}
}
Hinweis: Der Code ist ein einfaches Beispiel. Für komplexere Anwendungen bieten Bibliotheken wie FreeRTOS oder ArduinoThread robustere Lösungen.
Herstellungsprozess
- Luftqualität messen mit OpenSensors: Arduino & Python Anleitung
- Sicheres Arduino RFID-Schloss bauen – Schritt-für-Schritt-Anleitung
- Universelle IR-Fernbedienung mit Arduino bauen: Schritt-für-Schritt-Anleitung
- Digitales Stadiometer bauen: Körpergröße messen mit Arduino & Lasersensor VL53L0X
- Thermistor (NTC) einfach nutzen: Schritt-für-Schritt Arduino-Anleitung
- Musikalische Töne mit Arduino erzeugen: Schritt-für-Schritt-Anleitung
- Arduino an NMEA‑0183 anschließen: Schritt-für-Schritt-Anleitung
- Arduino Nano Fingerabdruck-Sensor-Projekt – Schritt-für-Schritt-Tutorial
- Modbus auf Arduino meistern: Schritt-für-Schritt-Anleitung
- Arduino Einsteiger Tutorial #1: Erstes Projekt (Blink)