Scheduler / Zeitgeber

Einleitung:

Oftmals sieht man viele Projekte, wo die Ressourcen eines MC weitestgehend brach liegen. Der Knackpunkt dabei ist daß irgendwo eine Wartezeit benötigt wird und damit schlagartig die Rechenleistung einbricht. Dann werden oft auch weitere MCs verwendet, obwohl ein MC bei besserer Programmstruktur alles zusammen erledigen könnte.

Lösung:

Die einzige Lösung dieses Dilemmas ist es Wartezeiten zu vermeiden, d.h.:

Warten ist verboten !!!

Aber wie soll das gehen ?
Eine Methode ist die Abfrage eines Zeitgebers im Vorübergehen, z.B. eines Timers oder eines Registers, welches durch einen Timerinterrupt weitergezählt wird, je nach der benötigten Verzögerungszeit. Damit ist es möglich, zwischen den einzelnen Abfragen, ob die Zeit abgelaufen ist, noch viele weitere Aufgaben zu erledigen. Dieses Konzept ist jedoch relativ starr, d.h. bei Erweiterungen sind teilweise erhebliche Änderungen am bestehenden Code notwendig.

Wesentlich komfortabler ist ein Scheduler, dem man einfach die zu erledigende Aufgabe und die dazugehörende Wartezeit übergibt. Danach braucht man sich um nichts mehr zu kümmern und kann sofort mit den anderen Aufgaben fortfahren.

Der Scheduler:

Das ist der eigentliche Arbeitsknecht, für alle zeitabhängigen Aufgaben. Sobald eine Aufgabe nicht sofort, sondern erst nach einer bestimmten Zeit zu erledigen ist, stellt man sie dort rein und fertig. Nach Ablauf der Zeit führt sie der Scheduler selbsttätig aus.

Grob gesagt ist das ein zentraler Weckdienst, dem man Aufträge übermitteln kann. Man kann die Aufträge aber auch widerrufen (entfernen), wenn sich durch den Programfluß inzwischen eine andere Situation ergeben hat.

Z.B. erwartet man innerhalb einer bestimmten Zeit weitere Daten von der UART und setzt für diese Zeit einen Timeout-Handler in den Scheduler. Kommen dann die Daten rechtzeitig, wird der Timerout-Handler einfach wieder entfernt. Ansonsten schlägt er zu und macht die nötige Fehlerbehandlung.

Das Programm ist relativ komplex und schwer zu erklären. Es arbeitet mit einer verketteten Liste, in der alle Aufgaben, d.h. die Adresse der auszuführenden Funktion und die Wartezeit gespeichert sind. Nach Ablauf der Zeit wird die Funktion ausgeführt und der entsprechende Eintrag wieder als frei markiert. Im Beispiel ist die maximale Anzahl möglicher Einträge auf 10 festgelegt, was für die meisten Aufgaben ausreichend ist.

Original habe ich das Programm für einen 8051 geschrieben, deshalb sind noch die dort üblichen Speicherbereiche (code, idata, bit) zu finden. Im main.h werden sie für den AVR entsprechend umdefiniert.

Der Vorteil dieser Version ist, daß alle Einträge immer in die richtige Reihenfolge sortiert werden. Damit ist ein minimaler CPU-Zeit Verbrauch möglich, ist keine Funktion auszuführen, wird nur ein einziges Zählregister runtergezählt.

Es gibt auch andere Versionen, die zwar einfacher aussehen, aber jedesmal den runtergezählten Wert mit allen Einträgen in der Liste vergleichen. Und wenn man dann eine Liste mit 100 Einträgen anlegt, kann das ne ganze Menge CPU-Zeit kosten.

Modulbeschreibung:

Das Programm ist in einzelne Module aufgeteilt.

In diesem File sind alle Definitionen zusammengefaßt, wie z.B. die Anschlußbelegung, Quarztakt.

Das ist das Hauptprogramm mit den Unterprogrammen für die LED-Steuerung. Um die gleichzeitige Abarbeitung mehrerer Tasks zu verdeutlichen, wurde die Timerfunktion von LED0 für LED1 kopiert und LED2 als einfache An-Aus-Schaltung programmiert.

Timerfunktion:

Die LED wird durch einmaliges Drücken der Taste eingeschaltet und geht nach einer bestimten Zeit (10s) wieder aus (Treppenhausautomatik). Jedes weitere Drücken bewirkt, daß die Ablaufzeit von neuem beginnt. Wird dagegen die Taste 2 mal kurz hintereinander gedrückt (innerhalb 0,8s) so geht die LED sofort aus.

Es sind also 2 Wartezeiten (10s und 0,8s) im Programmablauf notwendig. Diese werden dem Scheduler übergeben und es entsteht keine unnötige Ressourcenverschwendung des MCs, d.h. er kann noch viele weitere Aufgaben ausführen.

Die Doppeldruckerkennung erfolgt durch die Wartezeit 0,8s, nach der eine Funktion ausgeführt wird, die absolut nichts tut !
Des Rätsels Lösung, vor dem Einfügen der 0,8s Wartezeit wird versucht, diese Funktion aus dem Scheduler zu entfernen. Gelingt das, dann ist sie noch nicht ausgeführt worden, d.h. seit dem letzten Tastendruck sind weniger als 0,8s vergangen.

Das ist der Timerinterrupt, der meine schon bekannte Routine zur Tastenentprellung (4-fach-Abfrage) enthält, sowie den Timertakt für den Scheduler bereitstellt.

Dieses Unterprogramm dient der Abfrage der entprellten Tasten. Über eine Maske kann man bestimmen, ob man nur eine Taste oder mehrere oder alle abfragen will.

 

Die Funktion timertick() wird zyklisch aufgerufen und führt alle die Funktionen aus, deren Wartezeit abgelaufen ist. Timeradd( ) fügt eine Funktion der Liste zu, timerremove( ) kann diese wieder entfernen, beide liefern den Wert 0 zurück, sobald sie erfolgreich waren. Dadurch kann man z.B. erkennen, ob eine Funktion noch nicht abgelaufen war, als sie entfernt wurde.

Und zu Beginn muß der Listenspeicher mit timerinit() initialisiert werden.

Wichtig ! Besteht die Möglichkeit, daß eine Funktion mehrmals dem Scheduler hinzugefügt wird, ist vorher zu versuchen sie zu entfernen, sonst kann es dazu kommen, daß diese Funktion mehrmals ausgeführt wird oder die maximale Anzahl der Einträge erreicht wird. Es kann natürlich in manchen Fällen auch durchaus sinnvoll sein, daß eine Funktion mehrmals ausgeführt wird