Embedded C/C++: ein Mini-RTOS für Arduino
Ein Mini-RTOS für den Arduino Uno - Teil 1
Ernsthafte Entwickler nehmen den Arduino nicht ernst. Alle anderen arbeiten sehr gerne mit dem Arduino und glauben deshalb bereits ersthafte Entwickler zu sein. Nachdem ich erst kürzlich wieder einmal einen Arduino für Ausbildungszwecke eingesetzt habe, ist mir die Idee zu dieser kleinen Tagebuchreihe gekommen. Ernsthafte Entwickler sollten den Arduino durchaus ernst nehmen und Laien sollten erkennen, dass Entwicklung mehr ist als nur Beispielprogramme aus dem Internet herunterzuladen.
Irgendwann kommt man auch mit einem Arduino an den Punkt, dass die der Arduino-IDE beigefügten Templates für komplexe Steuerungsaufgaben nicht mehr ausreichen.
Wenn, wie im Beispiel rechts gezeigt, lediglich eine LED jede Sekunde ein- bzw. ausgeschaltet werden soll, dann genügt es gerade noch, die Ausführungsdauer von digitalWrite(LED_BUILTIN, HIGH) zu ver-nachlässigen und das Intervall der Schaltaufgabe durch einfache delay() einzustellen. Was aber tun, wenn die Ausführungszeit einer regelmäßig aufzurufenden Funktion nicht vorhergesagt werden kann, weil z.B. ein Entscheidungsbaum über unterschiedlich "lange" Äste verfügt? Man könnte vielleicht versuchen, das an delay() übergebene Zeitintervall variabel zu gestalten und in geeigneter Weise zu berechnen. Doch das wäre nur ein umständlicher und vermutlich wenig effizienter Workaround.
Wie wäre es, wenn man z.B. dem Arduino ein kleines Betriebssystem verpassen könnte. Der Kern eines solchen Mini-RTOS ist eine präzise Zeitablaufsteuerung in der main loop.


Das Bild links zeigt bereits den Code zum Mini-RTOS zusammen mit seinen wesentlichen Komponenten. Den Code gibt es weiter unten auch zum Download.
Die Hauptschleife des Programms while(1) {...} wird von einer darin verschachtelten while(release_rtos_loop==0) Schleife zunächst angehalten.
Der Hardware-Timer1 des ATMEGA328 löst regelmäßig (sysTick_ms) einen Interrupt (TIMER1_COMPA) aus. Im entsprechenden Interrupthandler wird lediglich die Semaphore release_rtos_loop=1 gesetzt.
Nach der Rückkehr in die Hauptschleife, kann nun die "Falle" while(release_rtos_loop==0) verlassen werden, nur um sofort wieder release_rtos_loop=0 zu setzen.
Auf diese Weise ist sichergestellt, dass die Hauptschleife in regelmäßigen Abständen (sysTick_ms) genau einmal durch-laufen wird.
Code innerhalb der so gesteuerten Hauptschleife wird so immer zu festen Zeitpunkten und unabhängig von seiner Ausführungs-dauer ausgeführt.
Es kann dennoch vorkommen, dass sich der Code innerhalb der Hauptschleife einmal "aufhängt". Beispielsweise könnte das Warten auf eine Eingabe zu lange dauern. Dann würde zwar die Hauptschleife durch den Hw-Timer-Int freigegeben, aber ohne dass das Programm schon an der "Startlinie" steht.
Damit man den Controller aus seiner misslichen Lage retten kann, führt man einen sog. Watchdog-Timer ein. Läuft des Zähler nach einer bestimmten Zeit über, dann erfährt der Controller einen Reset.
Damit der Watchdog nicht immer wieder zuschlägt, muss er in regelmäßigen Abständen zurückgesetzt werden (wdt_reset()). Der Timeout des Watchdog-Timer sollte stets etwas größer als sysTick_ms gewählt werden.

Im zweiten Teil dieser kleiner Blog-Reihe werde ich dann eine sehr einfache Task vorstellen, die von dem hier vorgestellten Mini-RTOS aufgerufen werden kann.
Den Code zum Blog gibt es hier: