AVR Interrupt Routinen mit C++: Unterschied zwischen den Versionen
(Bugs im Quellcode behoben, Text ist noch anzupassen.) |
(Korrigiert und überarbeitet) |
||
Zeile 5: | Zeile 5: | ||
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. | ||
Deshalb soll dieser Artikel einen Einblick in die Möglichkeiten des | Deshalb soll dieser Artikel einen Einblick in die Möglichkeiten des GNU C++ Compilers geben, der einige Stellschrauben besitzt mit denen sich Interrupts trotz aller Probleme in Klassen, mit Zugriff auf nicht statische Membervariablen, integrieren lassen. | ||
== Probleme == | == Probleme == | ||
Zeile 16: | Zeile 16: | ||
== Lösungen == | == Lösungen == | ||
=== das asm Schlüsselwort === | === 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.<ref>http://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Asm-Labels.html</ref> Somit ergibt sich die Möglichkeit das name mangling zu steuern. | Das Schlüsselwort '''asm''' ist nicht nur dazu da inline Assembler zu programmieren, sondern kann auch dazu verwendet werden Symbolnamen zu bestimmen.<ref>http://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Asm-Labels.html</ref> Somit ergibt sich die Möglichkeit das name mangling zu steuern. Weist man zum Beispiel einer statische Methode den Symbolnamen __vector_1 zu, so gehört sie weiterhin zur Klasse; es wird nur der Symbolname geändert. 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 === | === befreundete Klassen === | ||
Zu den | Zu den pragmatischeren Teilen der Sprache C++ 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 beispielsweise eine geschachtelte befreundete Klasse angelegt werden, die nebern der Interrupt Routine eine statische Zeigervariable auf ein Objekt der äußeren Klasse enthält.<ref>http://www.embedded.com/design/embedded/4023817/Interrupts-in-C-</ref> Wird mithilfe einer weiteren statischen Methode dieser Klasse der this Zeiger eines Objekts der äußeren Klasse gespeichert, kann durch die Interrupt Methode auf alle Member der äußeren Klasse zugegriffen werden. | ||
== Implementierungen == | == Implementierungen == | ||
=== | === Interrupt Routine in einer geschachtelten Klasse === | ||
Eine einfache Implementierung am Beispiel des Timer 2 Overflow Interrupts auf einem AVR ATmega32 könnte wie folgt aussehen: | 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 | Mit __asm__("__vector_5") wird der Symbolname der Methode 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.<ref>http://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html</ref> | ||
<c> | <c> | ||
class timer { | class timer { | ||
Zeile 31: | Zeile 31: | ||
class timerInterrupt { | class timerInterrupt { | ||
static timer * | static timer *timer; | ||
static void serviceRoutine( | static void serviceRoutine() __asm__("__vector_5") __attribute__((__signal__, __used__, __externally_visible__)); | ||
public: | public: | ||
Zeile 41: | Zeile 41: | ||
public: | public: | ||
timer( | timer(); | ||
}; | }; | ||
</c> | </c> | ||
Der Vollständigkeit halber soll die Initialisierung | Der Vollständigkeit halber soll die Initialisierung der Zeigervariable timer auf Null nicht fehlen. | ||
<c> | <c> | ||
timer *timer::timerInterrupt:: | timer *timer::timerInterrupt::timer = 0; | ||
</c> | </c> | ||
Die Methode | Die folgende Methode wird gebraucht um ein timer Objekt für die Interrupt Routine zu registrieren. | ||
<c> | <c> | ||
void timer::timerInterrupt::record(timer * | void timer::timerInterrupt::record(timer *t) { | ||
t | timer = t; | ||
} | } | ||
</c> | </c> | ||
Dies ist die Interrupt Routine. Um keinen Speicherzugriffsfehler zu erzeugen, falls der Interrupt auftritt bevor | Dies ist die Interrupt Routine. Um keinen Speicherzugriffsfehler zu erzeugen, falls der Interrupt auftritt bevor timer auf ein Objekt der äußeren Klasse zeigt, wird timer gegen Null gerüft. Zur Demonstration, dass der Zugriff möglich ist, wird i vom Objekt der timer Klasse um Eins erhöht. | ||
<c> | <c> | ||
void timer::timerInterrupt::serviceRoutine( | void timer::timerInterrupt::serviceRoutine() { | ||
if( | if(timer != 0) | ||
++ | ++timer->i; | ||
} | } | ||
</c> | </c> | ||
Zeile 67: | Zeile 67: | ||
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. | 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> | <c> | ||
timer::timer( | timer::timer() : i(0) { | ||
timerInterrupt::record(this); | timerInterrupt::record(this); | ||
TCCR2 |= (1 << CS20) + (1 << CS21) + (1 << CS22); | TCCR2 |= (1 << CS20) + (1 << CS21) + (1 << CS22); | ||
Zeile 85: | Zeile 85: | ||
<pre>warning: 'serviceRoutine' appears to be a misspelled signal handler</pre> | <pre>warning: 'serviceRoutine' appears to be a misspelled signal handler</pre> | ||
=== mit Elternklasse === | === mit Interrupt Elternklasse === | ||
Eine Interrupt Elternklasse erhöht die Flexibilität des Interrupt handlings enorm, allerdings auf Kosten des Speichers. | Eine Interrupt Elternklasse erhöht die Flexibilität des Interrupt handlings enorm, allerdings auf Kosten des RAM und Flash Speichers. | ||
Die pur virtuelle interrupt Klasse enthält ein statisches Array | Die pur virtuelle interrupt Klasse enthält ein statisches Array von Zeigern auf die interrupt Kindklassen. Die Methode serviceRoutine() sollte in den Kindklassen überladen werden. Dann folgen 20 Interrupt Handler jeweils einer für jeden Interrupt des AVR ATmega32. Die record() Methode nimmt nun auch die Interrupt Nummer als Parameter. Als Nummer können die Makros aus der Headerdatei iom32.h verwendet werden. | ||
<c> | <c> | ||
class interrupt { | class interrupt { | ||
Zeile 120: | Zeile 120: | ||
</c> | </c> | ||
Das | Das owner Array wird - auch hier der Vollständigkeit des Codes halber - auf Null initialisiert. | ||
<c> | <c> | ||
interrupt *interrupt::owner[] = {0}; | interrupt *interrupt::owner[] = {0}; | ||
</c> | </c> | ||
Die Interrupt Handler rufen | Die Interrupt Handler rufen, nachdem überprüft wurde ob ein Objekt einer Kindklasse registriet worden ist, dessen überladene Service Routine auf. | ||
<c> | <c> | ||
void interrupt::handler1() { | void interrupt::handler1() { | ||
Zeile 228: | Zeile 228: | ||
</c> | </c> | ||
Die Methode record speichert den Zeiger auf die Kindklasse an die entsprechende Interruptnummer. | Die Methode record speichert den Zeiger auf die Kindklasse an die entsprechende Interruptnummer im owener Array. | ||
<c> | <c> | ||
void interrupt::record(int interruptNumber, interrupt *i) { | void interrupt::record(int interruptNumber, interrupt *i) { | ||
Zeile 235: | Zeile 235: | ||
</c> | </c> | ||
Um einen Linker Fehler zu vermeiden | Um einen Linker Fehler zu vermeiden der Autritt, da es für die AVR Plattform keine C++ Standard Bibliothek gibt, sollte ein Handler für fehlgeleitete rein virtuelle Methoden selbst erstellt werden. | ||
<c> | <c> | ||
extern "C" void __cxa_pure_virtual() { | extern "C" void __cxa_pure_virtual() { | ||
Zeile 243: | Zeile 243: | ||
</c> | </c> | ||
In der timer Klasse ist die Klasse timerInterrupt enthalten, welche ein Kind der obigen interrupt Klasse ist. Ihre Zeigervariable timer soll auf ein Objekt der timer Klasse zeigen. Die Methode serviceRoutine() wird im Interruptfall vom Interrupt Handler der Elternklasse aufgerufen. Da von der timerInterrupt Klasse ein Objekt erstellt wird gibt es einen Konstruktor der als Parameter die Interruptnummer sowie einen Zeiger auf die äußere Klasse erwartet. Der Konstruktor der timer Klasse erwartet als ersten Parameter das Timer Counter Control Register des zu konfigurierenden Timers. Als Zweites sollte der in dieses Register einzutragende Wert angegeben werden. Die nächsten beiden Parameter widmen sich in der selben Art und Weise dem Timer Interrupt Mask Register. Zu letzt ist die Nummer des Timer Overflow Interrupts anzugeben. | |||
<c> | <c> | ||
class timer { | class timer { | ||
Zeile 249: | Zeile 249: | ||
class timerInterrupt : interrupt { | class timerInterrupt : interrupt { | ||
timer * | timer *timer; | ||
void serviceRoutine( | void serviceRoutine(); | ||
public: | public: | ||
timerInterrupt(int interruptNumber, timer * | timerInterrupt(int interruptNumber, timer *timer); | ||
} tI; | } tI; | ||
Zeile 267: | Zeile 267: | ||
</c> | </c> | ||
Der timerInterrupt | Der Konstruktor der Klasse timerInterrupt sichert einen Zeiger auf das Objekt mit dem die Interrupt Service Routine arbeiten soll. Beim Aufruf der record() Methode seiner Elternklasse gibt er den this Zeiger seines Objekts weiter. | ||
<c> | <c> | ||
timer::timerInterrupt::timerInterrupt(int interruptNumber, timer *timer) : | timer::timerInterrupt::timerInterrupt(int interruptNumber, timer *timer) : timer(timer) { | ||
record(interruptNumber, this); | record(interruptNumber, this); | ||
} | } | ||
</c> | </c> | ||
Die Methode serviceRoutine() wird vom eigentlichen Interrupt Handler der interrupt Klasse aufgerufen. In sie sollten die Aufgaben des Interrupts geschrieben werden. Im Beispiel wird die Membervariable i des im Konstruktor bestimmten timer Objekts erhöht. | |||
<c> | <c> | ||
void timer::timerInterrupt::serviceRoutine( | void timer::timerInterrupt::serviceRoutine() { | ||
++timer->i; | |||
} | } | ||
</c> | </c> | ||
Der Konstruktor der timer Klasse | Der Konstruktor der timer Klasse initialisiert i auf 0 und gibt die ihm mitgeteilte interruptNummer sowie den this Zeiger seines Objekts an den Konstruktor der timerInterrupt Klasse weiter. Er beschreibt zudem die beiden Timer Register mit den ihm übergebenen Werten. Zudem aktiviert er Interrupts global. | ||
<c> | <c> | ||
timer::timer(volatile uint8_t &tccr, uint8_t tccrNewState, volatile uint8_t &timsk, uint8_t timskNewState, int interruptNumber) : i(0), tI(interruptNumber, this) { | timer::timer(volatile uint8_t &tccr, uint8_t tccrNewState, volatile uint8_t &timsk, uint8_t timskNewState, int interruptNumber) : i(0), tI(interruptNumber, this) { | ||
Zeile 291: | Zeile 290: | ||
</c> | </c> | ||
Ein beispielhafter Aufruf in main() könnte etwa so aussehen. | Ein beispielhafter Aufruf in main() könnte etwa so aussehen. Hier werden Timer0 und Timer2 mit Prescalerwerten von 1024 initialisiert. | ||
<c> | <c> | ||
int main( | int main() { | ||
timer | timer timer0(TCCR0, TCCR0 | (1 << CS00) & ~(1 << CS01) | (1 << CS02), TIMSK, TIMSK | (1 << TOIE0), TIMER0_OVF_vect_num); | ||
timer | timer timer2(TCCR2, TCCR2 | (1 << CS20) | (1 << CS21) | (1 << CS22), TIMSK, TIMSK | (1 << TOIE2), TIMER2_OVF_vect_num); | ||
for(;;) | for(;;) | ||
Zeile 303: | Zeile 302: | ||
} | } | ||
</c> | </c> | ||
== Einzelnachweise == | == Einzelnachweise == |
Version vom 8. März 2013, 18:22 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 GNU C++ 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. Weist man zum Beispiel einer statische Methode den Symbolnamen __vector_1 zu, so gehört sie weiterhin zur Klasse; es wird nur der Symbolname geändert. 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 pragmatischeren Teilen der Sprache C++ 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 beispielsweise eine geschachtelte befreundete Klasse angelegt werden, die nebern der Interrupt Routine eine statische Zeigervariable auf ein Objekt der äußeren Klasse enthält.[3] Wird mithilfe einer weiteren statischen Methode dieser Klasse der this Zeiger eines Objekts der äußeren Klasse gespeichert, kann durch die Interrupt Methode auf alle Member der äußeren Klasse zugegriffen werden.
Implementierungen
Interrupt Routine in einer geschachtelten Klasse
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 der Methode 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 *timer; static void serviceRoutine() __asm__("__vector_5") __attribute__((__signal__, __used__, __externally_visible__));
public: static void record(timer *timer); };
friend timerInterrupt;
public: timer(); }; </c>
Der Vollständigkeit halber soll die Initialisierung der Zeigervariable timer auf Null nicht fehlen. <c> timer *timer::timerInterrupt::timer = 0; </c>
Die folgende Methode wird gebraucht um ein timer Objekt für die Interrupt Routine zu registrieren. <c> void timer::timerInterrupt::record(timer *t) { timer = t; } </c>
Dies ist die Interrupt Routine. Um keinen Speicherzugriffsfehler zu erzeugen, falls der Interrupt auftritt bevor timer auf ein Objekt der äußeren Klasse zeigt, wird timer 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() { if(timer != 0) ++timer->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() : i(0) { timerInterrupt::record(this);
TCCR2 |= (1 << CS20) + (1 << CS21) + (1 << CS22); TIMSK |= 1 << TOIE2;
sei(); } </c>
Um das obige Beispiel zu kompilieren sollten die folgenden Header eingebunden werden: Für uint32_t stdint.h, für sei() interrupt.h und für die Registermakros io.h. <c>
- include <stdint.h>
- include <avr/interrupt.h>
- include <avr/io.h>
</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 Interrupt Elternklasse
Eine Interrupt Elternklasse erhöht die Flexibilität des Interrupt handlings enorm, allerdings auf Kosten des RAM und Flash Speichers.
Die pur virtuelle interrupt Klasse enthält ein statisches Array von Zeigern auf die interrupt Kindklassen. Die Methode serviceRoutine() sollte in den Kindklassen überladen werden. Dann folgen 20 Interrupt Handler jeweils einer für jeden Interrupt des AVR ATmega32. Die record() Methode nimmt nun auch die Interrupt Nummer als Parameter. Als Nummer können die Makros aus der Headerdatei iom32.h verwendet werden. <c> class interrupt {
static interrupt *owner[20];
virtual void serviceRoutine() = 0;
static void handler1() __asm__("__vector_1") __attribute__((__signal__, __used__, __externally_visible__));
static void handler2() __asm__("__vector_2") __attribute__((__signal__, __used__, __externally_visible__)); static void handler3() __asm__("__vector_3") __attribute__((__signal__, __used__, __externally_visible__)); static void handler4() __asm__("__vector_4") __attribute__((__signal__, __used__, __externally_visible__)); static void handler5() __asm__("__vector_5") __attribute__((__signal__, __used__, __externally_visible__)); static void handler6() __asm__("__vector_6") __attribute__((__signal__, __used__, __externally_visible__)); static void handler7() __asm__("__vector_7") __attribute__((__signal__, __used__, __externally_visible__)); static void handler8() __asm__("__vector_8") __attribute__((__signal__, __used__, __externally_visible__)); static void handler9() __asm__("__vector_9") __attribute__((__signal__, __used__, __externally_visible__)); static void handler10() __asm__("__vector_10") __attribute__((__signal__, __used__, __externally_visible__)); static void handler11() __asm__("__vector_11") __attribute__((__signal__, __used__, __externally_visible__)); static void handler12() __asm__("__vector_12") __attribute__((__signal__, __used__, __externally_visible__)); static void handler13() __asm__("__vector_13") __attribute__((__signal__, __used__, __externally_visible__)); static void handler14() __asm__("__vector_14") __attribute__((__signal__, __used__, __externally_visible__)); static void handler15() __asm__("__vector_15") __attribute__((__signal__, __used__, __externally_visible__)); static void handler16() __asm__("__vector_16") __attribute__((__signal__, __used__, __externally_visible__)); static void handler17() __asm__("__vector_17") __attribute__((__signal__, __used__, __externally_visible__)); static void handler18() __asm__("__vector_18") __attribute__((__signal__, __used__, __externally_visible__)); static void handler19() __asm__("__vector_19") __attribute__((__signal__, __used__, __externally_visible__)); static void handler20() __asm__("__vector_20") __attribute__((__signal__, __used__, __externally_visible__));
public: static void record(int interruptNumber, interrupt *i);
}; </c>
Das owner Array wird - auch hier der Vollständigkeit des Codes halber - auf Null initialisiert. <c> interrupt *interrupt::owner[] = {0}; </c>
Die Interrupt Handler rufen, nachdem überprüft wurde ob ein Objekt einer Kindklasse registriet worden ist, dessen überladene Service Routine auf. <c> void interrupt::handler1() { if(owner[0]) owner[0]->serviceRoutine(); }
void interrupt::handler2() { if(owner[1]) owner[1]->serviceRoutine(); }
void interrupt::handler3() { if(owner[2]) owner[2]->serviceRoutine(); }
void interrupt::handler4() { if(owner[3]) owner[3]->serviceRoutine(); }
void interrupt::handler5() { if(owner[4]) owner[4]->serviceRoutine(); }
void interrupt::handler6() { if(owner[5]) owner[5]->serviceRoutine(); }
void interrupt::handler7() { if(owner[6]) owner[6]->serviceRoutine(); }
void interrupt::handler8() { if(owner[7]) owner[7]->serviceRoutine(); }
void interrupt::handler9() { if(owner[8]) owner[8]->serviceRoutine(); }
void interrupt::handler10() { if(owner[9]) owner[9]->serviceRoutine(); }
void interrupt::handler11() { if(owner[10]) owner[10]->serviceRoutine(); }
void interrupt::handler12() { if(owner[11]) owner[11]->serviceRoutine(); }
void interrupt::handler13() { if(owner[12]) owner[12]->serviceRoutine(); }
void interrupt::handler14() { if(owner[13]) owner[13]->serviceRoutine(); }
void interrupt::handler15() { if(owner[14]) owner[14]->serviceRoutine(); }
void interrupt::handler16() { if(owner[15]) owner[15]->serviceRoutine(); }
void interrupt::handler17() { if(owner[16]) owner[16]->serviceRoutine(); }
void interrupt::handler18() { if(owner[17]) owner[17]->serviceRoutine(); }
void interrupt::handler19() { if(owner[18]) owner[18]->serviceRoutine(); }
void interrupt::handler20() { if(owner[19]) owner[19]->serviceRoutine(); } </c>
Die Methode record speichert den Zeiger auf die Kindklasse an die entsprechende Interruptnummer im owener Array. <c> void interrupt::record(int interruptNumber, interrupt *i) { owner[interruptNumber - 1] = i; } </c>
Um einen Linker Fehler zu vermeiden der Autritt, da es für die AVR Plattform keine C++ Standard Bibliothek gibt, sollte ein Handler für fehlgeleitete rein virtuelle Methoden selbst erstellt werden. <c> extern "C" void __cxa_pure_virtual() { for(;;) ; } </c>
In der timer Klasse ist die Klasse timerInterrupt enthalten, welche ein Kind der obigen interrupt Klasse ist. Ihre Zeigervariable timer soll auf ein Objekt der timer Klasse zeigen. Die Methode serviceRoutine() wird im Interruptfall vom Interrupt Handler der Elternklasse aufgerufen. Da von der timerInterrupt Klasse ein Objekt erstellt wird gibt es einen Konstruktor der als Parameter die Interruptnummer sowie einen Zeiger auf die äußere Klasse erwartet. Der Konstruktor der timer Klasse erwartet als ersten Parameter das Timer Counter Control Register des zu konfigurierenden Timers. Als Zweites sollte der in dieses Register einzutragende Wert angegeben werden. Die nächsten beiden Parameter widmen sich in der selben Art und Weise dem Timer Interrupt Mask Register. Zu letzt ist die Nummer des Timer Overflow Interrupts anzugeben. <c> class timer { volatile uint32_t i;
class timerInterrupt : interrupt { timer *timer; void serviceRoutine();
public: timerInterrupt(int interruptNumber, timer *timer); } tI;
friend timerInterrupt;
public: timer(volatile uint8_t &timerCounterControlRegister, uint8_t tccrNewState, volatile uint8_t &timerInterruptMaskRegister, uint8_t timskNewState, int interruptNumber); }; </c>
Der Konstruktor der Klasse timerInterrupt sichert einen Zeiger auf das Objekt mit dem die Interrupt Service Routine arbeiten soll. Beim Aufruf der record() Methode seiner Elternklasse gibt er den this Zeiger seines Objekts weiter. <c> timer::timerInterrupt::timerInterrupt(int interruptNumber, timer *timer) : timer(timer) { record(interruptNumber, this); } </c>
Die Methode serviceRoutine() wird vom eigentlichen Interrupt Handler der interrupt Klasse aufgerufen. In sie sollten die Aufgaben des Interrupts geschrieben werden. Im Beispiel wird die Membervariable i des im Konstruktor bestimmten timer Objekts erhöht. <c> void timer::timerInterrupt::serviceRoutine() { ++timer->i; } </c>
Der Konstruktor der timer Klasse initialisiert i auf 0 und gibt die ihm mitgeteilte interruptNummer sowie den this Zeiger seines Objekts an den Konstruktor der timerInterrupt Klasse weiter. Er beschreibt zudem die beiden Timer Register mit den ihm übergebenen Werten. Zudem aktiviert er Interrupts global. <c> timer::timer(volatile uint8_t &tccr, uint8_t tccrNewState, volatile uint8_t &timsk, uint8_t timskNewState, int interruptNumber) : i(0), tI(interruptNumber, this) {
tccr = tccrNewState; timsk = timskNewState; sei();
} </c>
Ein beispielhafter Aufruf in main() könnte etwa so aussehen. Hier werden Timer0 und Timer2 mit Prescalerwerten von 1024 initialisiert. <c> int main() { timer timer0(TCCR0, TCCR0 | (1 << CS00) & ~(1 << CS01) | (1 << CS02), TIMSK, TIMSK | (1 << TOIE0), TIMER0_OVF_vect_num); timer timer2(TCCR2, TCCR2 | (1 << CS20) | (1 << CS21) | (1 << CS22), TIMSK, TIMSK | (1 << TOIE2), TIMER2_OVF_vect_num);
for(;;) ;
return 0; } </c>
Einzelnachweise
- ↑ Eckel, Bruce: Thinking in C++. Second Edition. Upper Saddle River: Prentice Hall Inc. 2000. S. 429.
- ↑ http://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Asm-Labels.html
- ↑ http://www.embedded.com/design/embedded/4023817/Interrupts-in-C-
- ↑ http://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html
- ↑ http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html