ARM Controller sind mit ihrer Leistungsfähigkeit prädestiniert für den Einsatz entsprechender Laufzeitumgebungen oder geeigneter Echtzeitbetriebssysteme. Solche basieren meist auf einer Timer-getriggerten Verteilung von Ressourcen, vor allem der Ressource Rechenzeit. Dafür steht beim ARM ein spezieller Timer zur Verfügung, der ausschließlich die Aufgabe hat, ein System-Trigger-Ereignis zu generieren.
Auch ohne Echtzeitbetriebssystem ist dieser SystemTick für den Anwendungsentwickler sehr interessant. Die hier im Tutorial verwendeten Programmvorlagen sind bereits auf die Nutzung des SysTick vorbereitet bzw. basieren darauf. Im PEC-Framework, der Bibliothek die wir hier verwenden, wird aus dem SysTick standardmäßig ein 10 ms Ereignis für den PecAppKernel generiert. Der PecAppKernel verteilt folgende Ereignisse an alle AppModule:
Diese Ereignisse werden wir uns für diese Übung zunutze machen. Gleichzeitig sollen Sie ein gewisses Verständnis für die Anwendung des PecFramwork-Timings gewinnen.
Es ist eine Mikrocontrolleranwendung zu entwickeln, bei der der Anwender sieht, wie der SysTick des Mikrocontrollers funktioniert.
Die Aufgabe lautet:
Entwickeln Sie dafür eine Lösung bei der die verschiedenen SysTick Ereignisse (10 ms, 100 ms, 1s) des Mikrocontrollers durch unterschiedliches blinken von LEDs und einem Signalton des Speakers verdeutlicht werden. Die LEDs soll an die Pins B0, B1, B3 und der Speaker an Pin B4 angeschlossen werden.
Führen Sie folgende Vorbereitungsarbeiten durch:
Die Aufgabe besteht darin in geeigneter Form das Konzept des System-Timers mit den daraus folgenden Systemereignissen zu demonstrieren. Als Rückmeldung für den Anwender benutzen wir optische Signale, das sind blinkende LEDs und ein akustisches Signal, das ist der Speaker.
Zunächst zum 10 Millisekunden Ereignis. Dieses Ereignis wird direkt aus der Interruptfunktion (Interrupt Service Routine oder auch Interrupt Handler) aufgerufen. Für den Anwendungsentwickler bedeutet das diese Funktion nur im Ausnahmefall und wenn dann nur mit sehr wenig also schnellem Code zu benutzen, da wir sonst das System ausbremsen. Dass es sich um ein Ereignis handelt erkennen wir an dem Präfix on bei der Operation onTimer10ms(). Die 10 Millisekunden sind viel zu schnell um es als blinkende LED wahrzunehmen. Deshalb legen wir das Signal auf den Speaker denn hören können wir Frequenzen bis weit in den Kilohertz-Bereich.
Das Ereignis für die 100 Millisekunden wird nicht direkt aus der Interruptfunktion aufgerufen sondern geht den Weg über eine Ereigniswarteschlange (Event Queue). Das bedeutet, dass Ereignisse sich ihrer Reihenfolge nach in der Warteschlange anstellen und wenn das System genug Zeit hat werden diese abgearbeitet. Das führt zwar dazu, dass einige Mikrosekunden Verzögerung (Latenzzeit) zwischen dem Auftreten des Ereignisses und dem Abarbeiten der Ereignisreaktion liegen aber dafür kann das System die dafür aufgewendete Prozessorzeit viel besser managen und das System läuft trotzdem flüssig. Wir erkennen diesen Sachverhalt an dem Bezeichner Event im Ereignisnamen onEvent100ms(). Hier können wir beruhigt eine LED anschließen. Die 100 Millisekunden zwischen jedem Umschalten ergeben 5 Hertz, das nehmen wir locker als blinken wahr.
Das 1-Sekunden-Ereignis ist schön langsam. Also benutzen wir auch hier eine LED zur Visualisierung.
Zum Vergleich und zur Verdeutlichung der Parallelität dieses ereignisorientierten Programmierkonzeptes bauen wir noch eine blinkende LED mit einem Software-PWM (Dimmen) auf der Basis der Wartefunktion waitUs in das onWork-Ereignis ein. Das ist eine gute Gelegenheit über die Charakteristik des Ereignisses onWork() zu reden. Dieses Ereignis wird aus der sogenannten Mainloop heraus aufgerufen. Das bedeutet onWork wird immer aufgerufen wenn gerade keine anderen Ereignisse abgearbeitet werden. Das sind oft 90 oder 99% der verfügbaren Prozessorzeit. Daraus folgt dass wir in onWork alles unterbringen was nicht direkt und zeitnah an einem Hardwareinterrupt hängt, dessen Abarbeitung also in der Regel keine Zeitkritischen Aufgaben abbilden. Für die Visualisierung des Durchlaufens von onWork benutzen wir auch wieder eine LED. Für einen einfachen Algorithmus zum Dimmen der LED haben wir in onWork genug Rechenzeit.
Zusammenfassend brauchen wir also folgende Systembausteine:
Diese müssen wieder in Beziehung gesetzt werden. Für die gestellten Anforderungen (toggle) reicht als Bibliothek Baustein PecPinOutput völlig aus. Der Grobentwurf sieht dann so aus:
MERKE: Präfix ON = ein Ereignis
Die Realisierung sollte die im obigen Grobentwurf beschriebenen Elemente beinhalten. Zusätzlich muss müssen wieder die konkreten Ausgabe-Pins den Systembausteinen zugewiesen werden.
Vervollständigen Sie ihr Klassendiagramm wie folgt:
Ihr Klassenmodell sollte jetzt dem folgenden Bild entsprechen. Verifizieren Sie ihr Modell und ordnen Sie die Elemente übersichtlich an.
Im nächsten Schritt hängen wir uns an die Systemereignisse für 10, 100 und 1000 Millisekunden indem wir jeweils Operationen auf die Klasse Controller ziehen und Schritt für Schritt folgende Funktionen zur Ereignisbehandlung einfügen:
Nach diesen Arbeitsschritten sollte ihr Klassenmodell dem folgenden Bild entsprechen. Verifizieren Sie ihr Modell und ordnen Sie die Elemente übersichtlich an.
für die Realisierung der geforderten Funktionalität ergänzen wir die folgenden Codes in den jeweiligen Ereignisfunktionen. Zuerst lassen wir die rote LED blinken. Dazu steuern wir im 100 Millisekunden Ereignis die rote LED wie folgt an.
Controller::onEvent100ms():voidredLED.toggle();
Die grüne LED soll am langsamsten blinken. Diese nutzen wir für das 1 Sekunden Event.
Controller::onEvent1s():voidgreenLED.toggle();
Im Kontrast dazu das schnelle 10 Millisekunden Ereignis. Hier toggeln wir den Speaker.
Controller::onTimer10ms():voidspeaker.toggle();
Auch hier ist der eigentliche Aufwand den wir in das Schreiben von C++ Codezeilen stecken müssen vergleichsweise gering. Der Hauptteil unserer Arbeit lag in der Konstruktion einer geeigneten Klassenstruktur und in der Ausnutzung des vorbereiteten Timings im PEC-Framework. Jetzt können wir die Anwendung testen.
Übersetzen Sie das Programm. Korrigieren Sie ggf. Schreibfehler. Übertragen Sie das lauffähige Programm in den Programmspeicher des Controllers.
Als nächstes erweitern wir die Anwendung so, dass wir das Ereignis onWork() welches fortlaufend aus der Mainloop der Laufzeitumgebung des Frameworks getriggert wird, benutzen um die gelbe LED ganz schwach glimmen zu lassen (dimmen). Dabei soll das sicht- und hörbare Laufzeitverhalten der anderen Ausgabegeräte nicht beeinflusst werden. Deshalb verwenden wir keine langen Wartefunktionen wie waitMs().
Controller::onWork():void// continuous event from the Mainloop uint8_t brightness=1; // use here 1 to 255 for fix brightness of the yellow LED yellowLED.on(); for (int i=brightness; i>0; i--); yellowLED.off(); for (int i=255-brightness; i>0; i--);
Erstellen, Übertragen und testen Sie diese Erweiterung. Experimentieren Sie mit verschiedenen Werten der Variablen brightness (1-255);
Das Dimmen der gelben LED können wir noch erweitern indem die LED selbständig auf und abblendet. Probieren Sie die folgende Variante. Sie werden Feststellen, dass auch diese recht komplexe Lösung das Laufzeitverhalten der anderen LEDs und des Speakers nicht spürbar beeinflussen.
Controller::onWork():voidstatic uint16_t dim=0; if (dim < 1000) { yellowLED.on(); waitUs(dim); yellowLED.off(); waitUs(1000-dim); dim++; } else if (dim <2000) { yellowLED.on(); waitUs(2000-dim); yellowLED.off(); waitUs(dim-1000); dim++; } else { dim=0; }
Vertiefen sie die Bedeutung des Schlüsselwortes static in C/C++ und warum dieses Schlüsselwort bei der obigen Lösung verwendet wurde.
Erlernte und gefestigte Arbeitsschritte:
Und hier diesen Abschnitt wiederum als Videozusammenfassung.
Ändern Sie zur Übung die Anwendung wie folgt:
Ziehen Sie aus dem Ergebnis dieser Änderungen Schlussfolgerungen für die Anwendung von Wartefunktionen.