Hallo,
hier fand ich den Hinweis, dass man mit inline-Assembler den gemerkelten
Namen ändern kann, sodass eine statische Member-Funktion als
Interrupt-Handler akzeptiert wird.
Das fand ich richtig schick und habe es weitläufig bei den AVRs
eingesetzt.
Bei der Portierung der Klassen auf stm32 musste ich feststellen, dass
der Compiler mit dem Hack nichts anfangen konnte. Anstatt die
Assembleranweisung auszuführen, führte es dazu, dass die
Interrupt-Handler jetzt mit der Nop-Funktion (die auch inline assembler
hat) zu einer Kollision führen (Funktion ist bereits implementiert ...).
Gibt es einen ähnlichen Hack, der auf stm32 funktioniert, sodass ich
dort auch statische Member zu Interupt-Handler machen kann?
> Das geile ist, dass es sich so schön obiektorientiert schaffen lässt.
Sorry, aber wenn man 10 Seiten schreiben muss, um einen Einzeiler zu
ersetzen, dann finde ich das überhaupt nicht geil :(
Reinhard M. schrieb:> Hallo,>> hier fand ich den Hinweis, dass man mit inline-Assembler den gemerkelten> Namen ändern kann, sodass eine statische Member-Funktion als> Interrupt-Handler akzeptiert wird.> Das fand ich richtig schick und habe es weitläufig bei den AVRs> eingesetzt.
Ja, ich dachte auch erst mal, dass das schick ist.
Aber: es geht mit Klassentemplates nicht (warum, weiß ich noch nicht).
Und da ich bei baremetal-cpp stark auf parametrische Polymorphie setze,
scheidet das für mich aus - wie man sieht, aus gutem Grund ...
Zudem wird ein singulärer Aufruf einer Elementfunktion aus den C-ISRs
einfach zu zero-overhead optimiert. Es besteht als kaum ein Grund für
den Hack ...
> es geht mit Klassentemplates nicht (warum, weiß ich noch nicht).> Und da ich bei baremetal-cpp stark auf parametrische Polymorphie setze,> scheidet das für mich aus
Hm, Du schreibst viel über Templates und Polymorphie - mach doch mal ein
Beispiel, wie das aussehen könnte.
staticvoidf()asm("TIM8_UP_IRQHandler");// IRQ-Name, definiert in STs Header Files
4
};
5
6
voidA::f()
7
{
8
trace_printf("Update Interrupt Timer8\n");
9
}
Nona funktioniert das bei Templates nicht! asm-specifications sind nur
bei Definitionen erlaubt. Sobald man aus einem Template aber eine Klasse
stanzt, is jegliche Definition natürlich wertlos...
1
template<size_tN>
2
structA
3
{
4
staticintx;
5
6
staticvoidfoo()asm("TIM8_UP_IRQHandler");// IRQ-Name, definiert in STs Header Files
7
};
8
9
template<size_tN>
10
voidA<N>::foo()
11
{
12
trace_printf("x is %d:\n",x);
13
}
14
15
template<>
16
intA<10>::x=10;
17
18
template<>
19
intA<6>::x=6;
Woher soll der Compiler denn wissen, ob er nun A<10>::foo() oder
A<6>::foo() umbenennen soll?
Abgesehn davon find ich die Lösung auch eher "unschön", da man sich die
Namen der Interrupts merken muss. Grad ST verwendet teils abstruse Namen
wie etwa TIM1_TRG_COM_TIM17_IRQHandler... Na juhu ;)
Da legt man doch lieber irgendwo in den Tiefen seiner Ordner-Strukturen
ein Makro ab:
Das is zwar alles andere als "modern C++", aber wie Wilhelm schon
erwähnte entsteht dadurch eh 0 Overhead. Der Callback kann dann auch
durchaus in ein spezialisiertes Template führen... Das macht selbst bei
gebastelten Libraries ja durchaus Sinn.
/edit
Typo fixed
Vincent H. schrieb:> Nona funktioniert das bei Templates nicht! asm-specifications sind nur> bei Definitionen erlaubt.
Ja. Meine Vermutungen / Hoffnungen waren:
1) in allen Template-Instanziierungen bekommen die Elementfunktionen
denselben Namen, den man vorgibt. Das hätte dann den schönen
Nebeneffekt, dass unterschiedliche Instanziierungen zu demselben Namen,
damit zur Verletzung der ODR kommt. Was meistens dann sinnvoll wäre.
2) ich könnte bei der Instanziierung den Namen festlegen.
Leider habe ich dazu nicht wirklich Quellen gefunden.
Reinhard M. schrieb:>> es geht mit Klassentemplates nicht (warum, weiß ich noch nicht).>> Und da ich bei baremetal-cpp stark auf parametrische Polymorphie setze,>> scheidet das für mich aus>> Hm, Du schreibst viel über Templates und Polymorphie - mach doch mal ein> Beispiel, wie das aussehen könnte.
Jetzt weiß ich nicht, was Du wissen möchtest. Fangen wir mit "Hello
World" an:
> Jetzt weiß ich nicht, was Du wissen möchtest. Fangen wir mit "Hello> World" an:
Sorry, aber auf dem Level brauchen wir uns nicht unterhalten.
Wertlose Trivialitäten habe ich von dem Kormanyos schon so reichlich
erhalten, dass es mir schon zu den Ohren raus kommt :(
Du hast von parametrischer Polymorphie geschrieben. Da hätte ich gerne
ein Beispiel für gesehen, welches auch im Alltag Sinn macht.
Nehmen wir mal ein nicht-triviales Beispiel:
ein ATmega88 (und Co) hat 3 verschiedene Hardware-Timer. Wie würdest Du
die mit Templates abbilden, sodass es keinen Code-Overhead gibt?
Interrupts der Timer sollen nach wie vor optional bleiben und natürlich
sollen auch nach wie vor alle Möglichkeiten unterstützt werden.
... und bitte nicht so halblebige Codefetzen.
Ist selbst erfunden, weil die meisten Cpp Frameworks sind "zu alt", um
die neuen features zu nutzen.
Ich bin eigentlich dabei, meine ganzen uC Projekte umzustellen. Damit
habe ich konkrete Anwendungsfälle und sehe wie sich das Ganze
entwickelt. Bin aber schon ziemlich weit. Bei der Abstraktionsschicht
beschränkt es sich allerdings derzeit auf ein paar AVRs, denn das ist
reine Fleißarbeit ...
Neben Mini-Projekten wie Wordclock, etc. habe ich auch einen
RC-Sensor-Schaltmodul umgestellt: der kann dann (Graupner) Hott-Sensoren
nachbilden, das SumD-Protokoll auswerten, PWM-Out, PPM-Out,
Schaltfunktionen, Sensoren wie Strom, Temperatur, ... auswerten.
Reinhard M. schrieb:>> Jetzt weiß ich nicht, was Du wissen möchtest. Fangen wir mit "Hello>> World" an:>> Du hast von parametrischer Polymorphie geschrieben.
Ok. ein etwas größeres Beispiel.
Hier hat man jetzt schon eine EventQueue, man kann beliebig viele
logische Timer erstellen (hier nur einer), man hat Ausgabe (hier per
simavr-console, geht aber auch einfach über USART, SW/HW-SPI, SW-Usart
..., indem man terminal umdefiniert). Man hat schon (sieht man nicht)
constant-rate Adapter und variable-rate Adapter...
Die Größe dieses Programm sind 998 Bytes auf einem ATMega328p.
Reinhard M. schrieb:> Sorry, aber wenn man 10 Seiten schreiben muss, um einen Einzeiler zu> ersetzen, dann finde ich das überhaupt nicht geil :(
Naja. Dafür kannst der Endanwender mit einem einzeiler zb. Einen timer
getriggerten dma mem2mem Vorgang ausführen.
Die Lib wird natürlich länger, logischerweise. aber der nutzen ist am
Ende enorm. Vorausgesetzt es kommt nicht auf den letzten krümmel Takt
an.
Wilhelm M. schrieb:
>>> Jetzt weiß ich nicht, was Du wissen möchtest. Fangen wir mit "Hello>>> World" an:>>>> Du hast von parametrischer Polymorphie geschrieben.>> Ok. ein etwas größeres Beispiel.
Bist Du vielleicht beim Kormanyos in die Schule gegangen?
Ich habe zweimal geschrieben, was mich interessiert und beides mal
müllst Du mich mit Belanglosigkeiten zu, die überhaupt nix zeigen.
OK, habe verstanden, dass Du nix zeigen willst.
Ich verzichte auf weitere Beleerungen :(
Reginald Leonczuk schrieb:
>> Sorry, aber wenn man 10 Seiten schreiben muss, um einen Einzeiler zu>> ersetzen, dann finde ich das überhaupt nicht geil :(> Naja. Dafür kannst der Endanwender mit einem einzeiler zb. Einen timer> getriggerten dma mem2mem Vorgang ausführen.
Hm, wenn dem so wäre ... ;)
Ich bin ja Anwender und Bibliotheksentwickler, jedoch mit Schwerpunkt
Anwender. Deshalb wird ständig neu geschrieben und wieder verworfen ...
... bis ich eben mit der Anwendung zufrieden bin.
Sorry, aber ich habe in dem Beispiel nix entdecken können, was ich jetzt
nachmachen wollte. Vielleicht habe ich es ja auch nicht verstanden?
Reinhard M. schrieb:> Nehmen wir mal ein nicht-triviales Beispiel:> ein ATmega88 (und Co) hat 3 verschiedene Hardware-Timer. Wie würdest Du> die mit Templates abbilden, sodass es keinen Code-Overhead gibt?> Interrupts der Timer sollen nach wie vor optional bleiben und natürlich> sollen auch nach wie vor alle Möglichkeiten unterstützt werden.
Willst Du es sehen, oder nicht? Ich möchte Dich auf keinen Fall
langweilen ...
Ein Beispiel ATmega644 - Timer-0,-1,-2:
**************************************
AvrTimer_.zip extrahieren und
das ganze Verzeichnis in
Mcucpp_Master/examples einfügen.
AvrTimer_.cppproj in AtmelStudio(6.2)(*) öffnen und kompilieren.
***************************************************************
BaseTimerD _local.h und
BaseTimerH _local.h und das Verzeichnis
ATmega-164-324-644-1284 mit Timer _local.h
gehören für Normalgebrauch (dann ohne _local) ins Verzeichnis
\mcucpp\AVR
***************************************************************
Die lebendige Diskussion in diesem Thread ist erfreulich.
Ich persönlich hätte gerne etwas detailliertere Ausführungen
mit kompilierbaren Beispielen.
.
Ich verwende c++/mcucpp für AVR8 und versuche mich (noch mit Mühe)
an c++/mcucpp für STM32_F3.
STM32_spaghetticode & Eclipse sind die Baustellen.
***************************************************************
(*) aktuelle Toolchain:
*********************
Beitrag "Re: (dauerhaft) aktuelle Toolchain"
Lustig, Deine Lösung ist 438 bytes groß,
und meine auch 438 Bytes ;-)
Natürlich finde ich meine (etwas) besser, weil z.B. die Konfiguration
der Timer auf eine bestimmte Interruptfrequenz eleganter ist, und auch
das ISR-Händling besser abstrahiert ist.
Fall jemand den Code sehen, dann PN.
Wilhelm M. schrieb:> Die Größe dieses Programm sind 998 Bytes auf einem ATMega328p.
Gehe ich recht in der Annahme, dass (Größenordnung) 950 Bytes davon sich
damit beschäftigen, Text über die Pins hinauszuquetschen?
Ein Programm, welches 2 ISRs definiert, und damit eine globale Variable
manipuliert ist in C 272 Bytes und in C++ 248 Bytes groß für den
Mega328. Sollte eigentlich gleich sein, doch man sieht, dass dummerweise
in der C Isr viel zuviel gepushed wird.
Reinhard M. schrieb:> Ich verzichte auf weitere Beleerungen :(
Ich hab mir deine AVR-libCPP aus dem anderen Thread runtergeladen. Wenn
das obige Beispiel belanglos is, in welche Kategorie fällt dann deine
"Library"?
@mcucpp_timer
Danke für die Beispiele. Allerdings sehe ich (noch?) keine Polymorphie.
Die Registermaps finde ich dagegen toll, habe die auch schon so
eingesetzt.
Womit ich (bei meinem Projekt) noch nicht richtig glücklich bin, ist die
Abstraktion der einzelnen AVR-controller. Ich mag einfach nicht für jede
CPU alle Dateien kopieren, wenn sich nur ein/zwei Zeilen ändern.
Werde da wohl doch bei den gewohnten defines bleiben :O
> Ich hab mir deine AVR-libCPP aus dem anderen Thread runtergeladen. Wenn> das obige Beispiel belanglos is, in welche Kategorie fällt dann deine> "Library"?
Lach - guter Einwand :)
Nun - ich hatte nach templates und parametrierter Polymorphie gefragt.
Weder das eine noch das andere war in dem Codebeispiel.
Wofür also war das Codegeplemper gut?
Es verdeutlicht nix, außer vielleicht die Diskrepanz zwischen den
vollmundigen Äußerungen eines Wilhelm M. und seiner tatsächlichen
Codequalität :O
Meine Klassen sind vielleicht auch belanglos. Keine Ahnung.
Aber die Beispiele sind komplett, lassen sich übersetzen und
funktionieren.
Wer sich also was anschauen will, findet alles in dem Upload (zumindest
dem aus dem letzten Beitrag).
Reinhard M. schrieb:> Nun - ich hatte nach templates und parametrierter Polymorphie gefragt.> Weder das eine noch das andere war in dem Codebeispiel.
Jetzt lache ich auch mal ganz laut!
1) Der obige Code besteht NUR aus templates.
2) Als parametrische Polymorphie bezeichnet man in C++ den Einsatz von
Funktions- und Klassen-Templates.
Deshalb würde ich Dir raten: beschäftige Dich etwas intensiver damit!
Reinhard M. schrieb:> Hm, wenn dem so wäre ... ;)
Also in meiner stm-Lib ist das so.
Reinhard M. schrieb:> Sorry, aber ich habe in dem Beispiel nix entdecken können, was ich jetzt> nachmachen wollte. Vielleicht habe ich es ja auch nicht verstanden?
Na kann ja sein, dass das eben nix für dich ist :)
Ich finde es recht praktisch:
Du hast in der ISR die aktuelle Klasseninstanz und static fällt weg. du
kannst so richtig feine callbacks einsetzen.
@Reginald Leonczuk
heue morgen hatte ich etwas Zeit und Muße, mir den Link aus Deinem
ersten Beitrag genauer zu Gemüte zu führen.
Ich würde mal so sagen: prinzipiell der richtige Weg. Aber mit den
Modellierungsschritten bin ich nicht einverstanden.
Ausgangsthese in dem Artikel ist ja, dass ein device einen Interrupt
hat.
Meiner Ansicht nach überspringt dieser Ansatz aber wichtige Schritte.
Für mich sieht es so aus, dass ein device aus Hardware-Modulen besteht
und jedes dieser Hardware-Module kann beliebig viele Interrupts haben
(es gibt ja - zumindest bei AVRs die Möglichkeit, softwaremäßig
Interrupts auszulösen).
Die Anwendung würde aus div. Schichten bestehen. Die unterste Schicht
wäre die Hardware-Abstraktion, wo ich die Hardware-Module über Klassen
abbilden würde. Diese sollten möglichst frei von Anwendungscode und
somit konstant zwischen unterschiedlichen Anwendungen sein.
Diese Anforderung kann aber von den Interrupts nicht erfüllt werden,
weshalb sie auf einer höheren Abstraktionsebene liegen (für mich auf der
höchsten, nämlich der Anwendungsebene, aber darüber kann man streiten).
Aus dieser Sicht ergibt sich die Notwendigkeit, dass die unterste Ebene
Interruptfunktionen empfangen und einsortieren können muss - also die
Anwendungschicht teilt dem Hardware-Modul mit: hier habe ich eine
Funktion, die als Interrupt für Event XYZ ausgeführt werden soll.
Im Interrupt ist sowohl der Zugriff auf die Daten des Hardware-Moduls,
wie auch der Zugriff auf die Daten des Anwendungsmoduls notwendig.
Die Möglichkeit, hier auf friend-Deklaration zurück zu greifen halte ich
für falsch, weil ein Objekt einer höheren Schicht in einer niederen
Schicht bekannt gemacht werden muss, was die Grundsätze der
Datenkapselung verletzt.
Ich habe das Problem leider noch nicht gelöst, kann also nur schreiben,
was mich an dem Ansatz in dem Artikel stört. Prinzipiell denke ich zwar
in die gleiche Richtung, will es aber anders umsetzen.
Deshalb: herzlichen Dank für Deine Denkanstöße!
Mir gefällt die Technik von ST der leeren Defaultimplementierung, die
als "weak" deklariert ist. Somit könnte die Funktion auf Anwendungsebene
ersetzt werden, ohne auf schmutzige Hacks angewiesen zu sein. Ob das
allerdings auch in C++ funktioniert, habe ich noch nicht ausprobiert.
Mir persönlich gefällt die Idee des universellen Interrupt-Handlings
auch nicht, auch wenn ich den Ansatz und den Code sehr sauber find. Ich
verwende schlichtweg Callbacks, die von den statischen Member-Funktionen
innerhalb meiner Templates aufgerufen werden. Da Interrupts sowieso
ausschließlich void(void) sein können, müssen die Callbacks nichtmal
sonderlich generisch sein.
Der entscheidende Nachteil dieser Variante ist, dass für jeden Interrupt
der genutzt werden will ein C-Wrapper innerhalb der Bibliothek angelegt
werden muss. Geht man aber davon aus, dass man bei einem neuen Chip
ohnehin einmal sämtliche Register und sonstiges Schmafu implementieren
muss, kann man die Wrapper auch gleich anlegen... Die Schreibarbeit ist
bei so gut wie allen Methoden die gleiche. Wer die Vektortabelle nicht
neu schreibt, der muss irgendwo die Namen der Interrupts reinklopfen.
Der Vorteil von einem 0815 Callback ist, dass man ihn zur Laufzeit stets
ändern kann. Außerdem bleibt man bei der Implementierung extrem
flexibel. Ein void(void) Callback kann in C++ ja schlichtweg alles sein.
Normale Funktion, Member Funktion, Statische Member Funktion, Lambda,
Func(k?)tor (grrr dieses Deutsch), usw. usf.
Vincent H. schrieb:> Normale Funktion, Member Funktion, Statische Member Funktion, Lambda,> Func(k?)tor (grrr dieses Deutsch), usw. usf.
Genau!
Ich schreibe in jede C-ISR den Aufruf eines Interrupt-Dispatchers
hinein: und da der Typ des Interrupts ja statisch ist, geschieht das
Dispatching auch durch template-Mechanik statisch. Und die eigentlichen
ISR-Callbacks registriere ich auch statisch bei dem Dispatcher.
Reinhard M. schrieb:> Deshalb: herzlichen Dank für Deine Denkanstöße!
Genau das War meine Intention :)
Ich komme aus dem maschinenbau weshalb ich hier also bei weitem nicht so
tief in der Materie stecke wie du.
Ich wollte halt eine Möglichkeit die Interrupts mit der Peripherie zu
verbinden (Hardware ebene), damit ich mit einem einzeiler eine ganze
dma/isr/Peripherie einschalten kann.
Reinhard M. schrieb:> Die Anwendung würde aus div. Schichten bestehen. Die unterste Schicht> wäre die Hardware-Abstraktion, wo ich die Hardware-Module über Klassen> abbilden würde. Diese sollten möglichst frei von Anwendungscode und> somit konstant zwischen unterschiedlichen Anwendungen sein.
Teilweise habe ich das bei mir schon implementiert. Allerdings ist es
schwierig hier eine Linie zu ziehen, da der Anwender, je nach Anwendung,
zb. nur einen puren Timer mit isr braucht.
Im großen und ganzen bin ich aber mit meiner Lib um Längen besser
bedient als mit der spl oder hal von St.
Reginald Leonczuk schrieb:
>> Die Anwendung würde aus div. Schichten bestehen. Die unterste Schicht>> wäre die Hardware-Abstraktion, wo ich die Hardware-Module über Klassen>> abbilden würde. Diese sollten möglichst frei von Anwendungscode und>> somit konstant zwischen unterschiedlichen Anwendungen sein.> Teilweise habe ich das bei mir schon implementiert. Allerdings ist es> schwierig hier eine Linie zu ziehen, da der Anwender, je nach Anwendung,> zb. nur einen puren Timer mit isr braucht.
Yepp! Den Aspekt decke ich bislang mit bedingter Übersetzung ab und
einer Config-Datei, in der bestimmt wird, welche Module verwendet
werden.
Bei den (nativen) Interrupt-Funktionen ist es ja so, dass die in der
Firmware auftauchen, auch wenn die Interrupts garnicht verwendet werden.
Auch deshalb mag ich die Methode mit dem "weak" ;)
Yo, also da muss ich einfach etwas nachforschen und ausprobieren - was
ich jetzt angehe ;)
> Im großen und ganzen bin ich aber mit meiner Lib um Längen besser> bedient als mit der spl oder hal von St.
Das glaube ich Dir unbesehen. hal von St ist nicht darauf angelegt,
Freunde zu finden =:O