Forum: Mikrocontroller und Digitale Elektronik C++ auf einem MC, wie geht das?


von Karl Käfer (Gast)


Lesenswert?

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

von Daniel A. (daniel-a)


Lesenswert?

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;

von FelixW (Gast)


Lesenswert?

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).

von robin (Gast)


Lesenswert?

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.

von Karl Käfer (Gast)


Lesenswert?

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

von Karl Käfer (Gast)


Lesenswert?

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

von Karl Käfer (Gast)


Lesenswert?

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

von FelixW (Gast)


Lesenswert?

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.

von Ralf G. (ralg)


Lesenswert?

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.

von FelixW (Gast)


Lesenswert?

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.

von noreply@noreply.com (Gast)


Lesenswert?

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.

von FelixW (Gast)


Lesenswert?

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

von Karl Käfer (Gast)


Lesenswert?

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

von FelixW (Gast)


Angehängte Dateien:

Lesenswert?

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.

von Karl Käfer (Gast)


Lesenswert?

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

von Thomas (Gast)


Angehängte Dateien:

Lesenswert?

Ich spiele auch gerade eine wenig mit C++ auf dem AVR.
Vermutlich sind VMTs garnicht sooo bloaty...

von Torsten C. (torsten_c) Benutzerseite


Lesenswert?

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?

von Matjes (Gast)


Lesenswert?

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.

von ekiwi (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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
von Klaus W. (mfgkw)


Lesenswert?

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! :-)

von Martin J. (bluematrix) Benutzerseite


Lesenswert?

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.

von F. F. (foldi)


Lesenswert?

Arduino ist im Hintergrund C++.

von MitLeserin (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.