CORTOS: ein Mini-Echtzeitkern für den AVR
- english page is missing - you can help the project with a translation!
Einleitung
- Um mehrere Dinge quasi parallel auf einem Prozessor erledigen zu können,
zerteilt man die Programmaufgaben in sog. Tasks, welche dann die Rechenleistung unter sich aufteilen.
Hier gibt es prinzipiell zwei verschiedene Techniken:
- Zeitschlitzzuteilung:
Der Prozessor wird nach einer bestimmten Zeit von einer Task abgezogen und für die Bearbeitung einer anderen Task zugeteilt. - Cooperatives Multitasking:
Jede Task gibt von sich aus die Kontrolle wieder zurück und es kann eine andere Task Rechenleistung erhalten.
Beide Methoden haben Vor- und Nachteile, auf die ich hier nicht näher eingehen will. Das
nachfolgend vorstellte CORTOS ist ein recht einfaches, aber für die Bedürfnisse in einer
kleinen embedded-Anwendung recht geeignetes kooperatives System.
Überblick über CORTOS
-
CORTOS verwaltet kooperative Tasks, diese können folgende Zustände haben:
- set_task_ready(TASK_ID): die angebene Task wird bereit.
- isr_set_task_ready(TASK_ID): die angebene Task wird bereit. Diese Routine ist innerhalb Interrupts zu verwenden (solange global Interrupt enable gesperrt ist).
- set_task_blocked(TASK_ID): die angebene Task wird inaktiv.
Hinweis: dieser Aufruf darf nicht aus der Task erfolgen, welche inaktiv werden soll, der normale Rückgabewert der Task würde diese Einstellung wieder überschreiben. - Eine Task kann sich selbst mit Hilfe Ihres Rückgabewertes einen zukünftigen Zustand zuweisen:
O: die Task ist sofort wieder bereit (d.h. sie hat eigentlich nur der Fairness halber zurückgegeben.)
-1: die Task wird inaktiv
1 .. 20000: die Task will für die angegebene Zeit schlafen.
Anmerkungen zur Schlafenszeit: diese wird nicht ab Rückgabe gerechnet, sondern als Additiv zur letzten Aufwachzeit
hinzugezählt, dadurch wird bei einer Task eine bestimmte Rate erzwungen, auch wenn zwischenzeitlich
mal eine andere Task vielleicht nicht ganz so kooperativ war.
| inaktiv | Diese Task will keine Rechenzeit haben |
| schlafend | Diese Task ruht z.Z., sie will irgendwann wieder Rechenzeit haben. |
| bereit | Diese Task will Prozessorzeit haben. |
Diese Tasks werden bei CORTOS in einer Liste verwaltet, welche folgende Parameter ja Task enthält. Der Listenindex ist die sogenannte TASK_ID:
| Codeadresse | Einsprungadresse der Task |
| Zustand | Das ist ein Flag, welches anzeigt, ob die Task rechnenbereit oder inaktiv ist. |
| Aufwachzeit | Hier wird die Zeit hinterlegt, wann diese Task wieder Prozessorzeit haben möchte. Diese zeit wird in Ticks gerechnet und läuft als signed integer. |
Für den Zustandsübergang einer Task gibt es folgende Aufrufe, wobei zu unterscheiden ist, ob ein Aufruf aus einer Interruptroutine (ISR) oder aus einer anderen Task erfolgt:
Details zu CORTOS
Idle-Zustand
-
Wenn keine Routine ausführbereit ist, so wird die IDLE_TASK (diese ist immer ready) aufgerufen, welche
den Prozessor schlafen legt und so für einen lange Akkulaufzeit sorgt. CORTOS wacht einfach beim nächsten
Tick von selbst wieder auf.
Soll CORTOS nicht einschlafen, sondern ständig prüfen, ob Tasks ready sind, dann läßt man in der IDLE_TASK den sleep() weg. Eine Task, die mit 0 returned, wird dadurch im gleichen Zeitschlitz nochmal aufgerufen.
Tick-Hook
- Routinen, welche bei jedem Lauf des Kerns aktiviert werden sollen, werden sozusagen auf den Tick
'draufgehängt'. Das bietet sich z.B. bei Tastaturabfragen an.
Tick-Rate
- Die Hauptschleife von CORTOS wird sinnvollerweise alle paar ms durchlaufen. Hierzu wird ein
Timerinterrupt benötigt, welcher an den CORTOS-Kern ein 'Event' (eben den Tick) sendet. Der Timerinterrupt
inkrementiert auch die Systemzeit, gemäß der entschieden wird, ob eine Task von schlafend in bereit wechselt.
Producer-Consumer, Fifos
-
Für Ereignisübergabe von Interrupts in Task oder zwischen Tasks werden Fifos verwendet. Die Task, welche auf
ein Ereignis wartet, heißt Consumer. Sollte eine Routine (Consumer) auf ein externes Ereignis warten,
so muß sie beim entsprechenden Fifo warten.
- Die Task prüft, ob was im Fifo ist: Aufruf von cr_fifo_filled(fifo_id)
- Wenn da was vorhanden ist, wird es mit cr_fifo_get_nowait(fifo_id) abgeholt und verarbeitet.
- Wenn da nichts vorhanden ist, dann gibt die Task an CORTOS mit -1 zurück (=not ready)
- Wenn ein neues Ereignis in das Fifo reinkommt (cr_fifo_put(fifo_id)), dann setzt die Fifosteuerung die Task wieder ready. Damit das Fifo weiß welche Task gemeint ist, muß die Consumertask vorher das mal eingestellt haben: cr_fifo_set_comsumer(fifo_id, TASK_ID)
Das erfolgt so:
Beispiel
-
Um eine LED zu blinken, legt man eine Task an. Diese schaltet abhängig von led_state den Port um und gibt dann die
halbe Blinkperiode zurück. Dadurch erhält diese Task nach dieser Zeit wieder die CPU zugeteilt.
#define LED_BLINK_PERIOD 800000L // 800ms
#define LEDPOS 3
static uint8_t led_state;
t_cr_task led_blinker(void)
{
if (led_state) {led_state = 0; PORTA &= ~(1<<LEDPOS);}
else {led_state = 1; PORTA |= (1<<LEDPOS);}
return(LED_BLINK_PERIOD / 2 / SYSTICK_PERIOD);
}
Anwendung
- CORTOS wird u.a. im AniMat,
im MFT und
im Gleisbesetztmedler benutzt.