AVR Interrupt Routinen mit C++: Unterschied zwischen den Versionen

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche
(erste Implementierung fertig)
(noch schnell den Wettbewerbsheader nachschieben...)
Zeile 1: Zeile 1:
''von Christian M.''
{{Wettbewerb Header}}
Einen Mikrocontroller mit C++ zu programmieren scheitert oft daran die Interrupt Routinen in die Klassenhierarchie zu integrieren. Ohne saubere Objektorientierung verliert C++ jedoch schnell an Wert. Zum Beispiel Frameworks um die Hardware zu abstrahieren sind mit den Interrupt Makros der avr-libc zwar machbar, jedoch mit reinem C++ weit flexibler.  
Einen Mikrocontroller mit C++ zu programmieren scheitert oft daran die Interrupt Routinen in die Klassenhierarchie zu integrieren. Ohne saubere Objektorientierung verliert C++ jedoch schnell an Wert. Zum Beispiel Frameworks um die Hardware zu abstrahieren sind mit den Interrupt Makros der avr-libc zwar machbar, jedoch mit reinem C++ weit flexibler.  


Zeile 81: Zeile 85:


[[Kategorie:AVR| ]]
[[Kategorie:AVR| ]]
[[Kategorie:C++| ]]
[[Kategorie:C++ ]]
[[Kategorie:Wettbewerb]]

Version vom 7. März 2013, 20:47 Uhr

von Christian M.

Dieser Artikel nimmt am Artikelwettbewerb 2012/2013 teil.

Einen Mikrocontroller mit C++ zu programmieren scheitert oft daran die Interrupt Routinen in die Klassenhierarchie zu integrieren. Ohne saubere Objektorientierung verliert C++ jedoch schnell an Wert. Zum Beispiel Frameworks um die Hardware zu abstrahieren sind mit den Interrupt Makros der avr-libc zwar machbar, jedoch mit reinem C++ weit flexibler.

Deshalb soll dieser Artikel einen Einblick in die Möglichkeiten des g++ Compilers geben, der einige Stellschrauben besitzt mit denen sich Interrupts trotz aller Probleme in Klassen, mit Zugriff auf nicht statische Membervariablen, integrieren lassen.

Probleme

name mangling

In C++ gibt es Namensräume, daher werden nicht nur die Methodennamen für die Symbolnamen benutzt, sondern unter anderem auch die Klassennamen mit eingebracht. Die Schattenseite dieses Features ist allerdings, dass die Interrupt Makros der avr-libc nicht mehr genutzt werden können. ISR(...) ist eines davon; es wird vom Präprozessor über einige weitere Makros zur Funktion void __vector_n(void) (n = Interruptnummer) aufgelöst.

Zugriff auf nicht statische Membervariablen

Da Methoden als ersten impliziten Parameter den this Zeiger ihres Objekts bekommen,[1] Interrupt Handler allerdings keine Parameter haben, müssen gezwungermaßen statische Methoden verwendet werden. Statische Methoden haben allerdings nur auf statische Variablen Zugriff. Dies ermöglicht allerdings nur eine suboptimale Objektorientierung. Beispielsweise soll eine Timer Klasse benutzt werden. Wollte man je ein Objekt für zwei verschiedene Timer erstellen, bräuchte man für jedes eine statische Variable.

Lösungen

das asm Schlüsselwort

Das Schlüsselwort asm ist nicht nur dazu da inline Assembler zu programmieren, sondern kann auch dazu verwendet werden Symbolnamen zu bestimmen.[2] Somit ergibt sich die Möglichkeit das name mangling zu steuern. Nennt man eine statische Methode zum Beispiel __vector_1 gehört sie weiterhin zur Klasse, bekommt aber diesen Symbolnamen. Da die avr-libc eine Interruptvektortabelle anlegt in der ein Sprungbefehl zu __vector_1 für den ersten Interrupt steht wird die Methode auch im Interruptfall aufgerufen.

befreundete Klassen

Zu den pragmatischen Teilen der Sprache gehört das Schlüsselwort friend, da es einer fremden Klasse Zugriff auf private Teile einer anderen Klasse gibt. Um mithilfe von friend einer Interrupt Routine Zugriff auf nicht statische Membervariablen zu geben kann eine geschachtelte befreundete Klasse angelegt werden, die eine statische Zeigervariable enthält.[3] Wird mithilfe einer weiteren statischen Methode dieser Klasse der this Zeiger der äußeren Klasse gespeichert, kann durch die Interrupt Methode auf alle Member der äußeren Klasse zugegriffen werden.

Implementierungen

ohne Elternklasse

Eine einfache Implementierung am Beispiel des Timer 2 Overflow Interrupts auf einem AVR ATmega32 könnte wie folgt aussehen:

Mit __asm__("__vector_5") wird der Symbolname von serviceRoutine() zum Namen des Interrupt Vektors für den Timer 2 Overflow Interrupt. Das Attribut __signal__ teilt dem Compiler mit, dass es sich hier um eine Interrupt Routine handelt, und alle Register gesichert und wiederhergestellt werden müssen. Mithilfe von __used__ wird verhindert, dass der Code wegoptimiert wird, denn es gibt nirgends im C++ Teil des Codes einen konkreten Aufruf der Methode. Damit die serviceRoutine() auch außerhalb der Timer Übersetzungseinheit sichtbar ist, sollte __externally_visible__ dem Quellcode hinzugefügt werden. Anstelle von __signal__ kann auch __interrupt__ verwendet werden um Interrupts sofort nach Eintritt in die Handler Methode wieder zu zulassen, anstatt erst nach deren Ende.[4] <c> class timer { volatile uint32_t i;

class timerInterrupt { static timer *t; static void serviceRoutine(void) __asm__("__vector_5") __attribute__((__signal__, __used__, __externally_visible__));

public: static void record(timer *timer); };

friend timerInterrupt;

public: timer(void); }; </c>

Der Vollständigkeit halber soll die Initialisierung von t auf Null nicht fehlen. <c> timer *timer::timerInterrupt::t = 0; </c>

Die Methode record() speichert lediglich ihren Parameter in die statische Zeigervariable t. <c> void timer::timerInterrupt::record(timer *timer) { t = timer; } </c>

Dies ist die Interrupt Routine. Um keinen Speicherzugriffsfehler zu erzeugen, falls der Interrupt auftritt bevor t auf ein Objekt der äußeren Klasse zeigt, wird t gegen Null gerüft. Zur Demonstration, dass der Zugriff möglich ist, wird i vom Objekt der timer Klasse um Eins erhöht. <c> void timer::timerInterrupt::serviceRoutine(void) { if(t != 0) ++t->i; } </c>

Im Konstruktor der timer Klasse wird zuerst der this Zeiger der inneren Klasse bekannt gemacht. Dann wird der Prescaler von Timer 2 auf 1024 eingestellt, und somit der Timer gestartet. In der dritten Zeile wird der Overflow Interrupt aktiviert um danach Interrupts global zu aktivieren. <c> timer::timer(void): i(0) { timerInterrupt::record(this);

       TCCR2 |= (1 << CS20) + (1 << CS21) + (1 << CS22);
       TIMSK |= 1 << TOIE2;

sei(); } </c>

Die folgende Warnung kann ignoriert werden, denn es wird bereits nach Durchlauf des Präprozessors geprüft ob alle Funktionen mit dem __signal__ Attribut mit __vector_ beginnen.[5] Der __asm__ Befehl wird allerdings erst nach dem kompilieren wirksam.

warning: 'serviceRoutine' appears to be a misspelled signal handler

mit Elternklasse

pragmatischer Ansatz

Einzelnachweise