Der SystemTick
Der AT90CAN128 Controller sind mit seiner Leistungsfähigkeit prädestiniert für den Einsatz einer entsprechenden Laufzeitumgebung oder sogar einem kleinen Echtzeitbetriebssystem. Solche basieren meist auf einer Timer-getriggerten Verteilung von Ressourcen, vor allem der Ressource Rechenzeit. Dafür steht beim AVR leider kein spezieller Timer zur Verfügung, der ausschließlich diese Aufgabe hat. Im PEC Framework wird bei AVR Controllern immer der Timer0 für die Generierung des System-Trigger-Ereignis genutzt.
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:
- 10 Millisekunden Ereignis onTimer10ms() direkt aus der Interupptbehandlung
- 100 Millisekunden Ereinis onEvent100ms() über eine Ereigniswarteschlange (Event-Queue)
- 1 Sekunden Ereignis onEvent1s() über eine Ereigniswarteschlange (Event-Queue)
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.
Die Aufgabe
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.
Vorbereitung
Führen Sie folgende Vorbereitungsarbeiten durch:
- neues Klassendiagramm anlegen
- Zielsprache AVR C++
- Zielplattform AT90CAN128 myAVR Board XXLight
- Diagrammvorlage Application Grundgerüst für PEC Anwendungen (XMC, STM32, AVR) laden
- Treiberpaket für ATmega_xxx zuweisen
Lösungsansatz
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
Realisierung
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:
- Klassen RedLED, YellowLED, GreenLED anlegen und mit Controller verbinden (Aggregation)
- Klasse Speaker anlegen und mit Controller verbinden (Aggregation)
- PecPinOutput als Bibliotheksbaustein für die angelegten Klassen verwenden
- Pins B0, B1, B3 und B4 den Systembausteinen zuordnen (B2 ist in der gegebenen Gehäuseform nicht verfügbar)
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:
- onTimer10ms()
- onEvent100ms()
- onEvent1s()
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 stuern 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.
Test
Übersetzen Sie das Programm. Korrigieren Sie ggf. Schreibfehler. Übertragen Sie das lauffähige Programm in den Programmspeicher des Controllers.
- Erstellen (Kompilieren und Linken)
- Brennen
- verbinden Sie …
Erweiterung
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);
Variante
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.
Videozusammenfassung
Erlernte und gefestigte Arbeitsschritte:
- Klassendiagramm anlegen und öffnen
- Diagrammvorlage für PEC Applikation auswählen, laden und Treiberpaket ATmega_xxx einfügen
- Navigator auf UML Pakete umschalten
- gewünschte Klasse LED im Navigator/Explorer suchen und ins Diagramm ziehen
- Klasse aggregieren
- Operationen anlegen und in eine Klasse einfügen
- Operationen einer Basisklasse überschreiben
- den nötigen Quellcode in den Operationen erstellen
- Erstellen und Brennen eine PEC Anwendung im Klassendiagramm
Und hier diesen Abschnitt wiederum als Videozusammenfassung.
Übung 4
Ändern Sie zur Übung die Anwendung wie folgt:
- Änderung: Kommentieren Sie den Code in onTimer10ms aus und lassen den Speaker in onWork toggeln.
Bilden und Testen Sie diese geänderte Anwendung.
Vergleichen und bewerten Sie das akustische Ergebnis.- Der Ton ist viel höher. Was bedeutet das?
- Der Ton hat ist nicht durchgehend sondern hat Störungen. Warum?
- Änderung: Fügen Sie in onWork mit waitMs eine Wartezeit von 500 ms ein.
Bilden und Testen Sie diese geänderte Anwendung.
Vergleichen und bewerten Sie das optische und akustische Ergebnis.- Der Ton ist nur noch ein Knacksen.
- Das Timing der Blinkenden LEDs hat sich verändert.
Ziehen Sie aus dem Ergebnis dieser Änderungen Schlussfolgerungen für die Anwendung von Wartefunktionen.