Embedded C/C++: einfache Tasks auf Arduino

Ein Mini-RTOS für den Arduino Uno - Teil 2

In Teil 1 dieser Tagebuchreihe wurde ein einfaches Betriebssystem für den Arduino implementiert, das die regelmäßige Ausführung von Aufgaben erlaubt. Wie aber lässt sich eine Aufgabe alle drei Sekunden (3.000 ms) ausführen, wenn der Betriebssystemtakt z.B. nur 50 ms beträgt? Das wollen wir in Teil 2 erfahren.


In der Hauptschleife des Mini-RTOS, die mit einem Systemtakt (sysTick_ms) von 50 ms läuft, soll eine bestimmte Aufgabe oder Task - z.B. das Blinken einer LED - nur jede Sekunde ausgeführt werden.


Würde man das Togglen der LED einfach in der Hauptschleife ausführen, dann würden Epileptiker jetzt ein Problem bekommen. Hektisches Blinken mit einer Frequenz von 1 / ( 2*50 ms ) = 10 Hz wäre die Folge.


Tatsächlich ruft man hingegen einen Task-Handler auf, dem man zum einen das gewünschte Ausführungsintervall übergibt und zum anderen den Systemtakt: simple_LED_task( 1000, sysTick_ms );


Wie ist ein solcher Task-Handler aufgebaut?

Die Task hat ihren eigenen Takt: task_tick;


Die Variable task_tick ist als static definiert, da ihr Wert beim Verlassen der Funktion erhalten bleiben muss.


Mit task_tick wird nichts anderes getan, als die Anzahl der sys_tick abzuzählen, zu denen die eigentliche Task ausgeführt werden soll.


Die Anzahl der erforderlichen sys_ticks ergibt sich schlicht aus dem Quotienten von gewünschter Wiederholrate der Task und Systemtakt: task_time_ms / sys_tick.

Nach der Ausführung der Task muss task_tick wieder zurückgesetzt werden.


Zeitlich ungenau wird diese Anordnung, wenn task_time_ms / sys_tick einen Divisionsrest hinterlässt, das Verhältnis also nicht ganzzahlig ist.

Das regelmäßige Rücksetzen von task_tick könnte man sich ersparen, wenn task_tick % (task_time_ms / sys_tick) auf das Fehlen eines Divisionsrests überprüft wird. (Ich werde mal den Assembler-Code beider Varianten vergleichen.)

Das Code-Beispiel auf der rechten Seite zeigt, dass die Ausführung von verschiedenen Tasks in unterschiedlichen Intervallen mit dem einfachen Mini-RTOS und sehr einfachen Task-Handlern bereits funktioniert.


Laien seien jedoch gewarnt. Das Mini-RTOS ist sehr rudimentär. Es gibt noch viel zu tun!


  1. Noch sehr lästig ist, dass man den Code zur Zeitsteuerung einer Task in jede Task kopieren muss.
  2. Tasks können bisher nur gestartet werden und laufen dann für immer. Lediglich das Ausführungsintervall könnte zur Laufzeit verändert werden. Ein gutes Betriebssystem erlaubt auch Tasks temporär anzuhalten oder zu "killen".
  3. Die hier gezeigten Tasks sind nicht kooperativ, d.h. sie können nicht an beliebiger Stelle unterbrochen werden, um Systemressourcen (Rechenzeit, Speicher etc.) einer anderen Task zu überlassen.
  4. Sämtliche Tasks in diesem einfachen System verfügen über die gleiche Priorität, nämlich die höchste - wer kennt das nicht aus dem Berufsleben?


Fast alle diese Merkmale eines fortgeschrittenen Mini-RTOS lassen sich durch einen sog. Task-Scheduler realisieren. Kooperative Tasks sind jedoch ein anderes Thema, dass jedoch nicht ohne den Rückgriff auf Assembler-Code erscheint, zumindest mir bis heute nicht...


Der Code zu diesem kleinen Beitrag steht wie immer als Download zur Verfügung, dieses Mal jedoch als zip-file, mittlerweile eine Modularisierung eingeführt werden musste. An die Laien gerichtet, es ist aus Gründen der Übersichtlichkeit nicht mehr möglich alles in einem einzigen ino-sketch unterzubringen. Den Experten sei gesagt, dass die Arduino-IDE auch durchaus für komplexere Aufgaben geeignet ist.

Was im dritten Teil dieser Blog-Beitragsserie folgt, habe ich noch nicht entschieden. Entweder wird es der bereits erwähnte Task-Scheduler oder aber es gibt einen Exkurs zu Zustandsautomaten. Warum Zustandsautomaten? Hmm, weil ich das für die Ausbildung junger Kollegen benötige.

Den Code zum Blog gibt es hier:

Zing • 24. Dezember 2025