04 - Systick-Timer (blockierend)

Einleitung

In den allermeisten Fällen sind blockierende Funktionen in Software unerwünscht, da der Mikroprozessor bzw. der Mikrocontroller erst dann mit der Ausführung weiterer Kommandos fortfahren kann, wenn die blockierende Funktion ihre Arbeit beendet hat.

Es gibt aber eine Ausnahme von dieser Regel! Bevor ein Bare-Metal-Programm seine Kernfunktion ausführt, also bevor es in die Endlosschleife eintritt, ist die verwendete Hardware zu initialisieren. Wenn Sie beispielsweise eine solche Komponente in Betrieb nehmen wollen, steht in den Datenblättern häufig, dass ein bestimmtes Signal mindestens 5ms anstehen muss (z.B. nach einem Reset), bevor das nächste Initialisierungskommando gesendet werden darf!

Aber blockierende Funktionen sind doch böse...

Nun ja: Das kommt auf den Zeitpunkt an, wo diese blockierenden Funktionen genutzt werden! In der Endlosschleife haben blockierende Funktionen nichts zu suchen, da andere, unter Umständen lebenswichtige Funktionen, dann nicht ausgeführt werden können. Während der Initialisierungsphase führt ein Gerät seine Kernaufgabe aber noch nicht durch! Dann ist es auch nicht weiter schlimm, wenn es einige hundert Millisekunden dauert, bis das Gerät betriebsbereit ist.

Eine elegante Implementierung durch Einsatz der MCAL

Nachfolgend zeige ich Ihnen, wie man den Systick-Timer dazu verwenden kann, ein echtes "Delay", also eine echte Verzögerung des Programmablaufs erzielen kann. 

Der Systick-Timer

Vielleicht fragen Sie sich jetzt, warum ich den Systick-Timer für diese Aufgabe verwende! Der Grund ist, dass jeder Cortex-M-Mikrocontroller unabhängig vom Hersteller diesen Timer enthält, da er bereits von der Firma ARM in den MCU-Kern aufgenommen wurde und softwareseitig von CMSIS (Cortex Microcontroller Software Interface Standard) unterstützt wird.

Die MCAL-Bibliothek für den STM32F4xx

Wer mein Buch zur Programmierung des STM32F446 kennt, weiß, dass ich parallel mit der MCAL (Microcontroller Abstraction Layer) eine Bibliothek entwickelt habe, die den Einsatz der integrierten Peripheriekomponenten erleichtern soll. Ein Teil dieser Bibliothek bietet einige Funktionen, die sich mit dem Systick-Timer befassen: Das einzige, was Sie als MCAL-Nutzer tun müssen, ist die Einbindung dieses Teils mit #include <mcalSysTick.h> in ihr Projekt. Etwas unschön, aber nicht vermeidbar, ist zudem, dass eine globale boolesche Variable mit dem Namen timerTrigger verwendet werden muss! Dass diese boolesche Variable global sein muss, ist nicht einmal besonders schlimm: Unschön ist vielmehr, dass sie exakt die Bezeichnung timerTrigger haben muss! Dies liegt daran, dass Interrupt-Serviceroutinen (ISR) niemals Parameter erhalten. Sie sind immer folgendermaßen deklariert: void <Name der Interrupt-Serviceroutine>(void). Dies ist anders aber auch gar nicht möglich, da die wesentliche Eigenschaft von Interruptanforderungen darin liegt, dass der Zeitpunkt der Anforderung nicht vorhergesagt werden kann und somit eine gezielte Beeinflussung durch Übergabe von Parametern nicht möglich ist.

Die Idee

Hinter meinem Ansatz steckt der Wunsch, dass ich das Inkrementieren bzw. Dekrementieren von Variablen unbedingt vermeiden wollte: Jede Variable würde nach einer bestimmten Zeitspanne zwingend über- bzw. unterlaufen. Daher müsste eine solche Variable auf einen Minimum- bzw. Maximumwert überprüft werden. Sobald dieser Wert erreicht ist, müsste die Variable wieder auf ihren Startwert zurückgesetzt werden. Das ist noch sehr leicht zu bewerkstelligen. In meinem Buch haben Sie aber gelernt, dass man theoretisch beliebig viele Variablen vom gleichen Timer (hier: der Systick-Timer) steuern kann, die dann alle ebenfalls korrigiert werden müssen, wenn der Timer bei Erreichen der Schwelle auf seinen Startwert gesetzt wird.

Viel einfacher ist hingegen der Ansatz, in der Interrupt-Service-Routine (ISR) eine Variable zu verwenden, die nur zwei Werte annehmen kann: true und false. Hierbei handelt es sich um die oben erwähnte Variable timerTrigger die vom Typ 'boolean' ist: Sie wird in der ISR auf den Wert true gesetzt, der in der Endlosschleife getestet wird. Hat timerTrigger den Wert true, werden sämtliche abgeleiteten Timer-Variablen in der Funktion systickUpdateTimer() dekrementiert und timerTrigger wieder auf false gesetzt. Das ist alles! Dieser Ansatz verhindert, dass eine Timer-Variable über- oder unterlaufen kann. Darüber hinaus müssen keine Timer-Variablen korrigiert werden, da es keine Schwellwerte mehr gibt.

Implementierung der systickDelay()-Funktion

Ich habe die Funktion systickDelay(uint32_t *timer, uint32_t millis) neu in die MCAL aufgenommen: Die neue Version steht ab sofort (18.05.2021) auf https://gitlab.com/rjesse/mcal-stm.git zum Download bereit.

Parameter

uint32_t *pointer : Pointer auf die Variable, die für das Delay verwendet werden soll.

uint32_t  millis    : Dauer der Verzögerung in Millisekunden

Implementierung

void systickDelay(uint32_t *timer, uint32_t millis)
{
    systickSetMillis(timer, millis);
    while (!isSystickExpired(*timer))
    {
        if (timerTrigger == true)
        {
            systickUpdateTimer(timer);
        }
    }
}
 
Funktionsweise
 
Mit systickSetMillis(timer, millis) wird die zu verwendende Timer-Variable auf ihren Startwert (die Delay-Zeit) gesetzt. In der folgenden while()-Schleife wird überprüft, ob der Timer abgelaufen ist. Ist dies der Fall, so ist die Wartezeit vergangen, und die Funktion kehrt zurück. Ist die Wartezeit noch nicht abgelaufen, so muss der Wert der Timer-Variablen weiter verringert werden. Dies darf aber nur erfolgen, wenn vorher der Systick-Timer "angeschlagen" hat! Und das war's auch schon: Die Timer-Variable wird so lange dekrementiert, bis sie den Wert 0 erreicht, was dem Ende der Wartezeit entspricht.