Hallo zusammen, im Rahmen meiner C++-Spielchen stehe ich vor zwei Herausforderungen. Zunächst frage ich mich derzeit, wie man eine Timer-Abstraktion entwerfen könnte. Das Problem ist, daß sich die Hardware-Implementierungen von Timern auf verschiedenen AVRs recht stark unterscheiden, einerseits danach, wie alt der AVR ist, andererseits haben die Timer einiger AVRs zusätzliche Funktionen. Eine Strategie zum Umgang mit dem Problem könnte sein, zunächst nur einfache Timer auf den neueren AVRs zu implementieren und weitere später über eine Weiche wie in "avr/io.h" mit "#ifdef __AVR_ATmega[X]__ (...)" nachzurüsten. Das hätte den Vorteil, daß jeweils eine korrekte Timer-Implementierung wie im Datenblatt nutzbar wäre. Andererseits widerspricht das der Idee, über mehrere Plattformen hinweg arbeiten zu können. Hat jemand eine kluge Idee dazu? Die zweite Herausforderung betrifft den GCC-Optimizer. Wenn ich eine Klasse außerhalb der "main()"-Funktion instanziiere, beispielsweise um einen Pin in einer ISR und außerhalb zu benutzen, wird das Kompilat plötzlich beinahe um den Faktor zwei größer. Das ließe sich umgehen, indem die Variable einfach in der ISR und der main()-Funktion instanziiert würde, aber natürlich wäre das nicht sonderlich elegant. Meine Frage ist nun: kann man den Optimizer des GCC irgendwie dazu überreden, die Instanziierung einer Klasse außerhalb der main()-Funktion besser zu optimieren? Über Ideen und Anregungen würde ich mich freuen, solange diese darauf verzichten können, Assembler oder C zu empfehlen... ;-) Lieben Dank, Karl
Karl Käfer schrieb: > kann man den Optimizer des GCC irgendwie dazu überreden, die > Instanziierung einer Klasse außerhalb der main()-Funktion besser zu > optimieren? Ich versuche immer code unabhängig vom verwendeten compiler zu erstellen. Ich sehe 3 möglichkeiten: 1) die klassen nicht Instanzieren 2) als constexpr declarieren 3) Das memorylayout der Classe wird dem in den speicher gemapten abbild der peripherie exakt angepast. Wenn sich PORTx an speicherstelle ab befindet, hat die portklasse eine grösse von einem byte, der einzige member geisst pins, und die "Instanzierung" sieht so aus:
1 | constexpr volatile Port p =(volatile Port*)0xab; |
Karl Käfer schrieb: > Zunächst frage ich mich derzeit, wie man eine Timer-Abstraktion > entwerfen könnte. Das Problem ist, daß sich die > Hardware-Implementierungen von Timern auf verschiedenen AVRs recht stark > unterscheiden, Ich fange das auch gerade erst an zu verstehen: Programmiere gegen ein Interface, nicht gegen eine Implementierung. Interface steht dabei für eine FUNKTION: Was soll passieren? Implementierung steht für die vorhandenen Möglichkeiten. Was kann ich programmieren / was kann mein Timer? Beides ist Grundverschieden. Deine Schnittstelle sollte nur das Verhalten zur Verfügung stellen, was dein Projekt braucht. Dann kannst du es leicht portieren. Brauchst du andere Funktionen? -> Definiere eine neue Schnittstelle und implementiere diese dann bzw. ändere bestehende Implementierungen ab. Mögliche Interfaces: - Interrupt Callback aller xx us - Impulszähler im Zeitfenster - PWM-Generator, 1 Phase - PWM-Generator, 3 Phasen - Was du noch gerade brauchst Karl Käfer schrieb: > Die zweite Herausforderung betrifft den GCC-Optimizer. Wenn ich eine > Klasse außerhalb der "main()"-Funktion instanziiere, beispielsweise um > einen Pin in einer ISR und außerhalb zu benutzen, wird das Kompilat > plötzlich beinahe um den Faktor zwei größer. Gib uns mal konkrete Details, was alles dazukommt. Sonst gibt es Glaskugelraten (vor allem da wir deinen Code nicht kennen).
Karl Käfer schrieb: > Zunächst frage ich mich derzeit, wie man eine Timer-Abstraktion > entwerfen könnte. Das Problem ist, daß sich die > Hardware-Implementierungen von Timern auf verschiedenen AVRs recht stark > unterscheiden, einerseits danach, wie alt der AVR ist, andererseits > haben die Timer einiger AVRs zusätzliche Funktionen. Eine Strategie zum > Umgang mit dem Problem könnte sein, zunächst nur einfache Timer auf den > neueren AVRs zu implementieren und weitere später über eine Weiche wie > in "avr/io.h" mit "#ifdef __AVR_ATmega[X]__ (...)" nachzurüsten. Das > hätte den Vorteil, daß jeweils eine korrekte Timer-Implementierung wie > im Datenblatt nutzbar wäre. Andererseits widerspricht das der Idee, über > mehrere Plattformen hinweg arbeiten zu können. Hat jemand eine kluge > Idee dazu? Ich würde mich von dem "eine Lib (Datei) für alles" Gedanken Lösen und eine Datei pro Controller Familie anlegen. Auserdem würde ich verschiedene Featurelevel definieren, die dann bestimmte Funktionen zur Verfügung stellen. z.B.: Timer_Base: Einfacher Systemtimer, Zeit Einstellbar, ruf bei Interrupt eine bestimmte Funktion aus Timer_Level2: Wie Base + PWM Ausgabe ... Damit liesen sich einige Probleme lösen. Und bei falls man Beispielsweise auf einem Timer_Base Prozessor ein PWM Ausgeben will, bekommt man eben eine Fehlermeldung, dass es nicht unterstützt wird. Dann hat man die Möglichkeit, es einzufügen, oder es mit Timer_Base und Softpwm zu umgehen. Ein weitere Vorteil wäre, dass man auch entsprechende Tests schreiben könnte und so alle Funktionen Testen kann, da eben genau Festgelegt ist, wie sich eine Funktion zu verhalten hat.
Hallo Daniel, Daniel A. schrieb: > Karl Käfer schrieb: >> kann man den Optimizer des GCC irgendwie dazu überreden, die >> Instanziierung einer Klasse außerhalb der main()-Funktion besser zu >> optimieren? > > Ich versuche immer code unabhängig vom verwendeten compiler zu > erstellen. Das ist grundsätzlich immer eine gute Idee. Aber in diesem speziellen Fall möchte ich mich gerne vor allem auf den GCC konzentrieren, weil es für AVR nicht gar so viele C/C++-Compiler gibt und GCC sowohl unter Linux, sowie dank Atmel Studio unter Windows der am weitesten verbreitete Compiler für AVRs ist. Natürlich möchte ich, daß mein Code auch mit anderen Compilern funktioniert, aber mein Anspruch ist ja, daß meine Bibliothek eine ebenso effiziente Programmierung ermöglicht wie C mit dem GCC als Referenz. Daher konzentriere ich mich bei Feinheiten wie der Optimierung auf den GCC. > Ich sehe 3 möglichkeiten: > 1) die klassen nicht Instanzieren > 2) als constexpr declarieren > 3) Das memorylayout der Classe wird dem in den speicher gemapten abbild > der peripherie exakt angepast. Wenn sich PORTx an speicherstelle ab > befindet, hat die portklasse eine grösse von einem byte, der einzige > member geisst pins, und die "Instanzierung" sieht so aus: >
1 | > constexpr volatile Port p =(volatile Port*)0xab; |
2 | >
|
Hm, da sind zwei gute Ideen dabei. 1) fällt hingegen für mich aus rein ästhetischen Gründen aus, weil ich die "::"-Orgien, die manche da machen, einfach nicht leiden kann. Das ist aber eine reine Geschmackssache; wenn mich das nicht so stören würde, könnte ich auch gleich eine der Libraries Mcucpp, savr oder xpcc v benutzen. ;-) Bisher funktioniert meine Bibliothek so, daß ich zunächst einmal einzelne Register in einem Template abstrahiert habe; im Prinzip ist mein Register- Klasse nur ein "const volatile uint8_t *" (oder uint16_t für 16-Bitter) mit ein paar Methoden zur Abfrage und Manipulationen des Registers. Ein GPIO besteht nun aus drei Registern (DDRn, PORTn, PINn) sowie einem "uint8_t" für das betreffende Bit, zB. PB2. Im Prinzip ist das also alles bereits konstant, und solange der Compiler das innerhalb der "main()" oder einer anderen Funktion instanziiert, erkennt er das auch und optimiert die Klassen weg. Ich habe nun mehrere Programme in C und C++ geschrieben, die exakt genauso groß oder in einigen Fällen sogar ein paar Byte kleiner sind als ein C-Programm mit derselben Funktionalität. Die Idee, dem Compiler mit "const" und "constexpr" auf die Sprünge zu helfen, erscheint mir aber vielversprechend, und ich werde ihn nochmal ausführlich ausprobieren. Lieben Dank und viele Grüße, Karl
Hallo Felix, FelixW schrieb: > Karl Käfer schrieb: >> Zunächst frage ich mich derzeit, wie man eine Timer-Abstraktion >> entwerfen könnte. Das Problem ist, daß sich die >> Hardware-Implementierungen von Timern auf verschiedenen AVRs recht stark >> unterscheiden, > > Ich fange das auch gerade erst an zu verstehen: > Programmiere gegen ein Interface, nicht gegen eine Implementierung. So sieht ordentliches Programmierhandwerk aus, keine Frage. Aber hier in diesem speziellen Fall erscheinen mir abstrakte Klassen problematisch, da sie -- Stichwort: VMT (virtual method table) -- dazu neigen, das Kompilat aufzublähen. Andererseits sehe ich abseits von Lehrmeinung und Eleganz für Mikrocontroller-Umgebungen mit ihren begrenzten Ressourcen und das daher meist überschaubaren Funktionalität keinen echten Nutzen von Interfaces, denn die konkrete Implementierung jeder Schnittstelle müßte dann wieder auf die verschiedenen Hardware-Implementierungen portiert werden. Dummerweise unterscheiden sich die Timer von ATtinys und ATmegas schon beim Timer/Counter Control Register TCCRn: einige haben nur ein TCCR0, andere TCCR0A und TCCR0B, wieder andere TCCR0{A,B,C} und wieder andere TCCR0[A-F], manche haben gemeinsame TIMSK- und TIFR-Register, andere wiederum für jeden Timer ein eigenes solches Register. Damit hätte ich zwar am Ende eine abstrakte Klasse mit Methoden wie "setPWM(Prescaler)", aber diese müßten für jede dieser Hardware-Implementierungen wiederum einzeln implementiert werden. > Deine Schnittstelle sollte nur das Verhalten zur Verfügung stellen, was > dein Projekt braucht. Bei der Komplexität üblicher Mikrocontroller-Programme kann ich dann aber auch gleich jeweils eine Timer-Klasse für den konkreten Anwendungsfall und die konkrete Zielplattform schreiben. Kein Problem, sowas läuft hier schon auf einem Atmega328P, aber eben nur für den Timer0 auf diesem und allen registerkompatiblen AVRs... > Karl Käfer schrieb: >> Die zweite Herausforderung betrifft den GCC-Optimizer. Wenn ich eine >> Klasse außerhalb der "main()"-Funktion instanziiere, beispielsweise um >> einen Pin in einer ISR und außerhalb zu benutzen, wird das Kompilat >> plötzlich beinahe um den Faktor zwei größer. > > Gib uns mal konkrete Details, was alles dazukommt. Sonst gibt es > Glaskugelraten (vor allem da wir deinen Code nicht kennen). Weiter oben in diesem Thread findest Du einen Haufen Code von mir. In den nächsten Tagen werde ich eine kleine Zip-Datei zusammenbauen und sie hier einstellen. Im Prinzip geht es um Folgendes (Standardheader und Gedöns mal weggelassen):
1 | typedef volatile uint8_t* const Reg8; |
2 | typedef volatile uint16_t* const Reg16; |
3 | |
4 | template <typename T, typename U> |
5 | class Register { |
6 | protected:
|
7 | const T reg; |
8 | public:
|
9 | Register(T reg): reg(reg) {} |
10 | |
11 | /** set "bit" = 1 */
|
12 | void sbi(const U bit) { *this->reg |= (1 << bit); } |
13 | |
14 | /** clear (unset, set to 0) "bit" */
|
15 | void cbi(const U bit) { *this->reg &= ~(1 << bit); } |
16 | |
17 | /** return true if "bit" is set */
|
18 | bool gbi(const U bit) { return *this->reg & (1 << bit); } |
19 | |
20 | /** set bitmask "mask" */
|
21 | void sms(const U mask) { *this->reg |= mask; } |
22 | |
23 | /** clear bitmask "mask" */
|
24 | void cms(const U mask) { *this->reg &= ~mask;} |
25 | |
26 | /** set all "bits" */
|
27 | void set(const U bits) { *this->reg = bits;} |
28 | |
29 | /** get register value */
|
30 | U get(void) { return (U)*this->reg; } |
31 | |
32 | /** EXPENSIVE! set bit list, eg.
|
33 | * Register(&ADCSRA).sbiList(ADPS1, ADPS0)
|
34 | * @params ... list of bits to set
|
35 | */
|
36 | void sbl(uint8_t n, ...) { |
37 | uint8_t val; |
38 | va_list vl; |
39 | va_start(vl, n); |
40 | for(uint8_t i = 0; i < n; i++) { |
41 | val = va_arg(vl, int); |
42 | this->sbi(val); |
43 | }
|
44 | }
|
45 | |
46 | /** EXPENSIVE! clear bit list, eg.
|
47 | * Register(&ADCSRA).sbiList(ADPS1, ADPS0)
|
48 | * @params ... list of bits to clear
|
49 | */
|
50 | void cbl(uint8_t n, ...) { |
51 | uint8_t val; |
52 | va_list vl; |
53 | va_start(vl, n); |
54 | for(uint8_t i = 0; i < n; i++) { |
55 | val = va_arg(vl, int); |
56 | this->cbi(val); |
57 | }
|
58 | }
|
59 | };
|
60 | |
61 | typedef Register< Reg8, uint8_t > Register8; |
62 | typedef Register< Reg16, uint16_t > Register16; |
63 | |
64 | class Pin { /** base class for a pin */ |
65 | protected:
|
66 | Register8 ddr; |
67 | Register8 port; |
68 | Register8 pin; |
69 | uint8_t num; |
70 | |
71 | public:
|
72 | /** constructor
|
73 | * @param ddr DDRn register address of pin (eg. &DDRB)
|
74 | * @param port PORTn register '' (eg. &PORTB)
|
75 | * @param pin PINn register (eg. &PINB)
|
76 | * @param num pin number (eg. PB2)
|
77 | */
|
78 | Pin(Reg8 ddr, Reg8 port, Reg8 pin, uint8_t num): |
79 | ddr(ddr), port(port), pin(pin), num(num) |
80 | {}
|
81 | };
|
82 | |
83 | class OutputPin : public Pin { /** an output pin */ |
84 | public:
|
85 | OutputPin(Reg8 ddr, |
86 | Reg8 port, |
87 | Reg8 pin, |
88 | uint8_t num): |
89 | /** constructor @params see Pin::Pin() */
|
90 | Pin(ddr, port, pin, num) |
91 | { this->ddr.sbi(this->num); } |
92 | |
93 | /** set output high */
|
94 | void setHigh(void) { this->port.sbi(this->num); } |
95 | |
96 | /** set output low */
|
97 | void setLow(void) { this->port.cbi(this->num);} |
98 | |
99 | /** toggle output */
|
100 | void toggle(void) { |
101 | #if CAN_TOGGLE_PIN == 1
|
102 | this->pin.sbi(this->num); |
103 | #else
|
104 | if(this->pin.gbi(this->num)) { |
105 | this->setLow(); |
106 | } else { |
107 | this->setHigh(); |
108 | }
|
109 | #endif // CAN_TOGGLE_PIN
|
110 | }
|
111 | |
112 | /** assignment operator
|
113 | * @param parm if true: set pin high, else set pin low
|
114 | */
|
115 | void operator=(bool &parm) { |
116 | if(parm) { |
117 | this->setHigh(); |
118 | } else { |
119 | this->setLow(); |
120 | }
|
121 | }
|
122 | };
|
123 | |
124 | |
125 | // "HAL"
|
126 | class Ausgabe: public OutputPin { |
127 | public:
|
128 | Ausgabe(volatile uint8_t* ddr, |
129 | volatile uint8_t* port, |
130 | volatile uint8_t* pin, |
131 | uint8_t num): |
132 | OutputPin(ddr, port, pin, num) |
133 | {}
|
134 | };
|
135 | |
136 | |
137 | int main(void) { /* ZeileA */ |
138 | |
139 | Ausgabe aus{&DDRD, &PORTD, &PIND, PD2}; /* ZeileB */ |
140 | |
141 | while(1) { |
142 | aus.toggle(); |
143 | _delay_ms(50); |
144 | }
|
145 | }
|
Wenn ich dieses Programm so übersetze, wie es da steht, ist das Kompilat genau 156 Bytes groß, also exakt genauso groß wie das funktional gleiche C-Programm. Wenn ich nun ZeileA und ZeileB vertausche, wird das Ergebnis plötzlich 284 Bytes groß -- beinahe das Doppelte. Und jetzt würde ich zu gerne verstehen, warum er das im zweiten Fall nicht genauso hinbekommt, zumal sich an der Funktionalität des Programms gar nichts ändert. Liebe Grüße und vielen Dank, Karl
Hallo robin, robin schrieb: > Karl Käfer schrieb: >> Zunächst frage ich mich derzeit, wie man eine Timer-Abstraktion >> entwerfen könnte. > > Ich würde mich von dem "eine Lib (Datei) für alles" Gedanken Lösen und > eine Datei pro Controller Familie anlegen. Vermutlich habe ich mich falsch ausgedrückt, aber das war ganz und gar nicht mein Gedanke. Deswegen mache ich es bei USARTs schon so: es gibt eine Klasse BaseUsart mit verschiedenen Funktionen wie "getc()", "puts(float)", "puts(uint8_t)" usw., von der meine Usarts erben. Über einen kleinen Codegenerator, der die XML-Partdescription-Datein von Atmel parst, erstelle ich dann für jeden Controller die passenden Usarts: für den tiny2313 nur eine Usart, für einen mega328P eine Usart0, und für einen mega1284 eine Usart0 und eine Usart1 -- korrespondierend zum Datenblatt. Der Entwicklungsprozeß sieht dann so aus: Du schaust ins Datenblatt des Ziel-uC, und wenn da eine Usart1 definiert ist, kannst Du mit "Usart1 pc{}" eine Instanz davon erstellen und mit 'pc.puts("hallo\n")' direkt eine freundliche Begrüßung darauf ausgeben. > Auserdem würde ich verschiedene Featurelevel definieren, die dann > bestimmte Funktionen zur Verfügung stellen. > z.B.: > Timer_Base: Einfacher Systemtimer, Zeit Einstellbar, ruf bei Interrupt > eine bestimmte Funktion aus > Timer_Level2: Wie Base + PWM Ausgabe Im Moment habe ich Partdescription-Dateien für 139 ATtinys und -megas... egal, ob ich das mit einem Codegenerator machen oder von Hand schreiben würde, dürfte das in ziemlich viel Arbeit ausarten. Ich fürchte auch, Ihr setzt den angestrebten Abstraktionslayer viel höher an als ich, oder, anders gesagt: Ihr scheint die Sache von einer anderen Seite aus anzugehen als ich. In meinen Augen braucht es erst eine saubere Abstraktion der Hardware, auf der dann (siehe "HAL" im oben geposteten Codebeispiel) wiederum eine oder mehrere weitere Abstraktionsschichten aufsetzen können. Mir geht es hier zunächst um eine möglichst schlanke, effiziente und dennoch vielseitige Abstraktion der Hardware. So, ich geh' jetzt erstmal mein Repository aufräumen und möchte in den nächsten Tagen eine Zip-Datei mit dem aktuellen Stand und ein wenig Dokumentation dazu bereitstellen. Liebe Grüße, Karl
Karl Käfer schrieb: > Wenn ich dieses Programm so übersetze, wie es da steht, ist das Kompilat > genau 156 Bytes groß, also exakt genauso groß wie das funktional gleiche > C-Programm. Wenn ich nun ZeileA und ZeileB vertausche, wird das Ergebnis > plötzlich 284 Bytes groß -- beinahe das Doppelte. Glaskugel { 1. Vermutung Wenn "aus" global ist, werden wohl alle definierten Funktionen als solches vom Linker übernommen. Die Globale Variable könnte ja in anderen Programmteilen verwendet werden ('extern Ausgabe aus;' im Header). Details im lst-file. 2. Vermutung keine Konstanten Memberfunktionen. Wenn sich *DDR, *PIN, etc. ändern könnten muss der Code darauf angepasst sein. } PS: Kann hier gerade keine 700MB+ zum Nachschauen runterladen.
FelixW schrieb: > Wenn "aus" global ist, Sollte sich ja eigentlich mit 'static' verhindern lassen. Macht es aber nicht. Ob die Variable(n) nur vor oder in 'main' stehen, ist zwar für den Programmablauf egal. Außerhalb würde ich's aber auch schöner finden, wegen der Übersicht über den Speicherverbrauch.
Karl Käfer schrieb: > o sieht ordentliches Programmierhandwerk aus, keine Frage. Aber hier in > diesem speziellen Fall erscheinen mir abstrakte Klassen problematisch, > da sie -- Stichwort: VMT (virtual method table) VMT entsteht erst, wenn der Typ unbekannt ist. (s.o.) Ich glaube da kommen wir mit dem Begriff Interface bzw. Schnittstelle durcheinander. Beispiel für die Sammlung von Schnittstellendefinitionen: * Registerbeschreibung im Datenblatt -> Summe von Welche Bits kann ich setzen und was passiert dann. * Dokumentation der Header-Datei -> Summe von Welche Funktion macht was und wie wird sie aufgerufen Abstrakten Klassen bzgl. Schnittstellen: Sie zwingen dich dazu der gleichen Notation zu folgen. Sie ermöglichen es zur Laufzeit die Implementierung zu wechseln. Es gibt immer eine Verweis auf den gemeinsamen Nenner (Basisklasse). Karl Käfer schrieb: > Ich fürchte auch, Ihr setzt den angestrebten Abstraktionslayer viel > höher an als ich, oder, anders gesagt: Ihr scheint die Sache von einer > anderen Seite aus anzugehen als ich. Ich glaube es gibt da 2 Ansätze. Horizontal/Schichten indem du die Funktion der Geräte hinter Funktionen versteckst und so lange Verhalten emulierst, bis sich alle uCs gleich verhalten. Deine konkrete HAL kann sehr viel. Grundlage sind die Möglichkeiten der Implementierung (Hardware) Du arbeitest Vertikal/nach Aufgaben, du unterteilst ein Gerät in verschiedene Aspekte, die jeweils eine Aufgabe umsetzen. Die Lösung des Aspekts orientiert sich an der Hardware. Deine konkrete HAL kann sehr wenig. Grundlage ist die benötigte Funktion. (Anforderung der Software) Diese Variante bevorzuge ich, stehe erst am Anfang.
Karl Käfer schrieb: > warum er das im zweiten Fall nicht genauso hinbekommt, > zumal sich an der Funktionalität des Programms gar nichts ändert. Da ändert sich eine ganze Menge an der Funktionalität und an der Art, wie das Objekt gespeichert wird. Im zweite Fall könnte was in der Art void ich_komme_von_extern() { aus.toggle(); } funktionieren. Man muß sich aber das Ergebnis anschauen und was der Compiler macht.
Karl Käfer schrieb: >> Karl Käfer schrieb: >>> Die zweite Herausforderung betrifft den GCC-Optimizer. Wenn ich eine >>> Klasse außerhalb der "main()"-Funktion instanziiere, beispielsweise um >>> einen Pin in einer ISR und außerhalb zu benutzen, wird das Kompilat >>> plötzlich beinahe um den Faktor zwei größer. Wenn der Kompiler nicht wirklich optimiert kein wunder :/ Er sollte doch zumindest mitbekommen, dass R25 immer 0 ist?
1 | 00000084 <_GLOBAL__sub_I_j>: |
2 | 84: 8a e2 ldi r24, 0x2A ; 42 |
3 | 86: 90 e0 ldi r25, 0x00 ; 0 |
4 | 88: 90 93 01 01 sts 0x0101, r25 |
5 | 8c: 80 93 00 01 sts 0x0100, r24 |
6 | 90: 8b e2 ldi r24, 0x2B ; 43 |
7 | 92: 90 e0 ldi r25, 0x00 ; 0 |
8 | 94: 90 93 03 01 sts 0x0103, r25 |
9 | 98: 80 93 02 01 sts 0x0102, r24 |
10 | 9c: 89 e2 ldi r24, 0x29 ; 41 |
11 | 9e: 90 e0 ldi r25, 0x00 ; 0 |
12 | a0: 90 93 05 01 sts 0x0105, r25 |
13 | a4: 80 93 04 01 sts 0x0104, r24 |
14 | a8: 82 e0 ldi r24, 0x02 ; 2 |
15 | aa: 80 93 06 01 sts 0x0106, r24 |
16 | ae: 52 9a sbi 0x0a, 2 ; 10 |
17 | b0: 08 95 ret |
Hi Felix, FelixW schrieb: > Karl Käfer schrieb: >>> Karl Käfer schrieb: >>>> Die zweite Herausforderung betrifft den GCC-Optimizer. Wenn ich eine >>>> Klasse außerhalb der "main()"-Funktion instanziiere, beispielsweise um >>>> einen Pin in einer ISR und außerhalb zu benutzen, wird das Kompilat >>>> plötzlich beinahe um den Faktor zwei größer. > > Wenn der Kompiler nicht wirklich optimiert kein wunder :/ > Er sollte doch zumindest mitbekommen, dass R25 immer 0 ist? Das sollte er wohl, ja. Aber vielleicht kann ich das Problem mit einem Singleton lösen, das probiere ich jetzt mal aus. ;-) Liebe Grüße, Karl
Hallo Karl Käfer, bereits Erfolg mit deinem Singleton gehabt? Ich habe ne Lösung für dich und die heißt constexpr Details im Anhang. Damit weiß der Kompiler, dass die Variablen unveränderlich sind.
Hi Felix, FelixW schrieb: > Hallo Karl Käfer, > > bereits Erfolg mit deinem Singleton gehabt? Jaein -- das Singleton funktioniert, aber das Binary ist 292 Bytes und damit um den Faktor 1,7 größer als das äquivalente C-Kompilat mit 176 Bytes. > Ich habe ne Lösung für dich und die heißt constexpr > Details im Anhang. Damit weiß der Kompiler, dass die Variablen > unveränderlich sind. constexpr habe ich auch schon ausprobiert, aber abgesehen von der Kosmetik (das läßt nur einen leeren Konstruktor zu und benötigt darum eine init()-Funktion) bläht es das Kompilat auf 278 Bytes auf, wenn man die ISR() und den sei()-Aufruf hinzufügt. Das ist zwar schon besser, aber noch weit von "gut" entfernt. Liebe Grüße, Karl
Thomas schrieb: > Vermutlich sind VMTs garnicht sooo bloaty... Ich denke, was viel mehr stört, ist wenn sie auf dem Heap liegen. Oder kann man die VMTs z.B. auch in Arrays zwingen?
Hier der Genauigkeit halbe einen kurzen Einwurf, was ARDUINO betrifft. Das hier ist der Code für die Ansteuerung der Pins auf einem ARM Cortex M0 ARDUINO: https://github.com/arduino/ArduinoCore-samd/blob/master/cores/arduino/wiring_digital.c Was man dabei sieht: - Die Funktionen machen die Ansteuerung der ARM-Pins sehr einfach, ohne die Funktionen ist es sehr komplex. Deutlich komplexer als auf einem AVR - Die Funktionen sind unter dem Namen "wiring" zusammengefasst, nicht ARDUINO. - Die Funktionen sind in C geschrieben Der Grund warum ich auf den Namen "wiring" wert lege, findet sich hier: http://arduinohistory.github.io/ Das ist eine ziemlich spannende Geschichte über Wiring vom Programmierer selbst beschrieben. Der erste Absatz ist etwas langweilig aber dann geht die Erzählung richtig ab. Es ist eine Abhandlung darüber was mit der Anerkennung eines Entwicklers passieren kann, wenn ein Projekt eine bestimmte Berümtheit erreicht.
Torsten C. schrieb: > Thomas schrieb: >> Vermutlich sind VMTs garnicht sooo bloaty... > > Ich denke, was viel mehr stört, ist wenn sie auf dem Heap liegen. Oder > kann man die VMTs z.B. auch in Arrays zwingen? Die vTables gibt es ja einmal pro Klasse, nicht pro Objekt und sollten eigentlich im Flash liegen. Pro Instanz kommt meist nur ein Pointer auf eine vTable hinzu. Also wird pro Instanz 16bit (AVR) oder 32bit (ARM) mehr benötigt. Ob das vom RAM oder Flash abgeht hängt davon ab, wo der Compiler das Objekt hin tut. Wenn das Objekt konstant ist, kann es ja auch im Flash landen.
Torsten C. schrieb: > Ich denke, was viel mehr stört, ist wenn sie auf dem Heap liegen. Immer diese unbeschreibliche Angst vor malloc(). Aber: es steht dir bei der avr-libc völlig frei, dein eigenes malloc() zu implementieren, wenn du denkst, dass du das unbedingt machen müsstest. Dann hättest du wenigstens die Gewähr, statt eines recht sauber debuggten malloc()s der Bibliothek deine eigenen, neuen Bugs einzubauen. :-) Edit: aller Wahrscheinlichkeit nach liegen die vtables aber nicht im Heap, damit ist diese Diskussion sowieso hinfällig. Im Flash liegen sie jedoch auch nicht, denn das würde im Compiler eine Sonderbehandlung für den AVR erfordern. (Ich erinnere mich, dass ich dafür mal einen Bugreport geschrieben habe.) Mithin werden sie wohl zwar RAM brauchen, aber ganz normal zusammen mit .data aus dem Flash initialisiert.
:
Bearbeitet durch Moderator
Jörg W. schrieb: > Immer diese unbeschreibliche Angst vor malloc(). Vor allem bei einem allokierten Block pro Klasse kann ein Programmierer ja nicht abschätzen, weiviele neue Klassen zur Laufzeit dazukommen! :-)
Hallo, ich habe mir grad dieses sehr interessante Thema durchgelesen. Momentan arbeite ich im Controllerbereich nur mit C und etwas OOP. OOP wird dabei über structs realisiert... Hat jemand schon C++ Hardware Bibliotheken für einen Xmega bzw. Atmega Controller erstellt und würde diese teilen? Bei den Xmegas macht OOP mit C++ sogar richtig Sinn, da sich die verschiedenen Timer und Schnittstellen (welche mehrfach zur Verfügung stehen) sich nur durch den Port-Namen unterscheiden.
Bibliothek - mit grundlegenden Funktionen Mit Beispielen für GPIO, Timer, USART, SPI, 7Seg, LCD, GLCD, RS485, .. Ist keine vollständige Library, - eher ein umfangreiches Beispiel und gutes Konzept in C++ mit Templates. Konzept passt für verschiedene Architekturen, - bei mir AVR und STM32F3,F4,(F7): Beitrag "Re: C++ auf einem MC, wie geht das?" Beispiel für Timer mit AVR8, anpassbar für alle Varianten: Beitrag "Re: statische Memberfunktion als Interrupt-Handler?"
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.