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:
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
>constexprvolatilePortp=(volatilePort*)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
typedefvolatileuint8_t*constReg8;
2
typedefvolatileuint16_t*constReg16;
3
4
template<typenameT,typenameU>
5
classRegister{
6
protected:
7
constTreg;
8
public:
9
Register(Treg):reg(reg){}
10
11
/** set "bit" = 1 */
12
voidsbi(constUbit){*this->reg|=(1<<bit);}
13
14
/** clear (unset, set to 0) "bit" */
15
voidcbi(constUbit){*this->reg&=~(1<<bit);}
16
17
/** return true if "bit" is set */
18
boolgbi(constUbit){return*this->reg&(1<<bit);}
19
20
/** set bitmask "mask" */
21
voidsms(constUmask){*this->reg|=mask;}
22
23
/** clear bitmask "mask" */
24
voidcms(constUmask){*this->reg&=~mask;}
25
26
/** set all "bits" */
27
voidset(constUbits){*this->reg=bits;}
28
29
/** get register value */
30
Uget(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
voidsbl(uint8_tn,...){
37
uint8_tval;
38
va_listvl;
39
va_start(vl,n);
40
for(uint8_ti=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
voidcbl(uint8_tn,...){
51
uint8_tval;
52
va_listvl;
53
va_start(vl,n);
54
for(uint8_ti=0;i<n;i++){
55
val=va_arg(vl,int);
56
this->cbi(val);
57
}
58
}
59
};
60
61
typedefRegister<Reg8,uint8_t>Register8;
62
typedefRegister<Reg16,uint16_t>Register16;
63
64
classPin{/** base class for a pin */
65
protected:
66
Register8ddr;
67
Register8port;
68
Register8pin;
69
uint8_tnum;
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(Reg8ddr,Reg8port,Reg8pin,uint8_tnum):
79
ddr(ddr),port(port),pin(pin),num(num)
80
{}
81
};
82
83
classOutputPin:publicPin{/** an output pin */
84
public:
85
OutputPin(Reg8ddr,
86
Reg8port,
87
Reg8pin,
88
uint8_tnum):
89
/** constructor @params see Pin::Pin() */
90
Pin(ddr,port,pin,num)
91
{this->ddr.sbi(this->num);}
92
93
/** set output high */
94
voidsetHigh(void){this->port.sbi(this->num);}
95
96
/** set output low */
97
voidsetLow(void){this->port.cbi(this->num);}
98
99
/** toggle output */
100
voidtoggle(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
voidoperator=(bool&parm){
116
if(parm){
117
this->setHigh();
118
}else{
119
this->setLow();
120
}
121
}
122
};
123
124
125
// "HAL"
126
classAusgabe:publicOutputPin{
127
public:
128
Ausgabe(volatileuint8_t*ddr,
129
volatileuint8_t*port,
130
volatileuint8_t*pin,
131
uint8_tnum):
132
OutputPin(ddr,port,pin,num)
133
{}
134
};
135
136
137
intmain(void){/* ZeileA */
138
139
Ausgabeaus{&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?
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.
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?"