Forum: Compiler & IDEs Buchempfehlung C++ und MCUs


von Franz W. (woyzeck)


Lesenswert?

Da ich bei meinen Spielereien mit diversen MCUs (STM32, ESP32) bezüglich 
der Programmierung immer wieder schnell an meine Grenzen stoße, da ich 
kaum über C++ Erfahrung verfüge und diese Sprache ganz allgemein nicht 
so prickelnd finde (um nicht zu sagen, dass ich mittlerweile ein 
erklärter C++-Hasser bin), bin ich auf der Suche nach einem guten Buch, 
das sich vor allem C++ widmet, idealerweise auch auf die Einschränkungen 
in Mikrocontrollerumgebungen eingeht.

Hat jemand von euch hier einen Tipp ? Mir geht es wie gesagt in erster 
Linie um C++ - eine Einführung in Mikrocontroller ist nicht so 
notwendig.

:
von Lalala (Gast)


Lesenswert?

Lippmann c++ primer.

von Franz W. (woyzeck)


Lesenswert?

Lalala schrieb:
> Lippmann c++ primer.

Danke - soeben bestellt !

von Johannes S. (Gast)


Lesenswert?

Der Autor dieses Buches macht hier gerne dafür Werbung, damit er es 
nicht immer selber machen muss übernehme ich es diesmal:
http://www.ruediger-asche.de/
(obwohl weniger C++)

Dann gibt es hier einiges:
https://www.grimm-jaud.de/

und zu modernen C++ Features:
https://github.com/AnthonyCalandra/modern-cpp-features

Zu OO selber finde ich auch das alte Standardwerk von Stroustrup gut, er 
selber hat auch eine große Linkliste:
https://www.stroustrup.com/C++.html

Und praktischerweise kann man auch gleich ein C++ orientiertes OS 
benutzen, namentlich Mbed-OS. Da sind reichlich Tricks und Konzepte drin 
die man auch erstmal verstehen muss, auf jedenfall hat man eine gute 
Basis für Komponenten.

von MaWin (Gast)


Lesenswert?

C++ ist aber nur sehr eingeschränkt auf µCs zu nutzen. Auf alle Fälle 
ist es besser als das veralterte C.

Die interessanten Dinge wie polymorphe Objekte mit virtuellen Methoden 
sind aufgrund mangelnder Resourcen nicht nutzbar. :-<

von mh (Gast)


Lesenswert?

MaWin schrieb:
> Die interessanten Dinge wie polymorphe Objekte mit virtuellen Methoden
> sind aufgrund mangelnder Resourcen nicht nutzbar. :-<
Das stimmt so natürlich nicht.

von Johannes S. (Gast)


Lesenswert?

mh schrieb:
> Das stimmt so natürlich nicht.

Aber sicher doch. Gibts schon was besseres als den 8051?
Für einige hier ist die Erde wohl immer noch eine Scheibe.

von mh (Gast)


Lesenswert?

Johannes S. schrieb:
> mh schrieb:
>> Das stimmt so natürlich nicht.
>
> Aber sicher doch. Gibts schon was besseres als den 8051?
> Für einige hier ist die Erde wohl immer noch eine Scheibe.
Auch auf einen 8051 sollte das gehen, wenn es einen modernen Compiler 
dafür gibt.

von MaWin (Gast)


Lesenswert?

mh schrieb:
> Das stimmt so natürlich nicht.

An welche STM32 denkst du, bei denen das problemlos geht?

von mh (Gast)


Lesenswert?

MaWin schrieb:
> mh schrieb:
>> Das stimmt so natürlich nicht.
>
> An welche STM32 denkst du, bei denen das problemlos geht?
Das problemlos kommt von dir. Probleme gibt es immer. Eben hast du noch 
behauptet, dass sie nicht nutzbar sind und das ist einfach falsch.
MaWin schrieb:
> Die interessanten Dinge wie polymorphe Objekte mit virtuellen Methoden
> sind aufgrund mangelnder Resourcen nicht nutzbar. :-<

von MaWin (Gast)


Lesenswert?

Aha, geht also nicht, sonst würdest du es ja schreiben.
Danke für deine Bestätigung, dass es nicht geht. ;-)

von M.K. B. (mkbit)


Lesenswert?

MaWin schrieb:
> Die interessanten Dinge wie polymorphe Objekte mit virtuellen Methoden
> sind aufgrund mangelnder Resourcen nicht nutzbar.

Das stimmt pauschal so nicht!
Ich würde es mit C++ auf Mikrocontrollern versuchen. Durch die 
Kompatibilität mit C kann man ja auch beliebig mischen. Lernt man etwas 
neues dazu, kann man auch nach und nach von C auf C++ refactorn.

Auf einfachen Mikrocontrollern sind Teile der Standardbibliothek nicht 
nutzbar, weil diese einen Heap oder eine Betriebssystem erfordern. Alles 
andere ist aber kein Problem. Es könnte sogar sein, dass der Compiler 
besser optimieren kann oder die Sytax weniger Fehler erlaubt.
Und es stehen viele Algorithmen bereits fertig und optimiert zur 
Verfügung.
z.B.
1
// C
2
void foo(int* data) // Es wird erwatet, dass data 4 Element hat, der Aufrufer sieht das aber nicht.
3
{
4
   for(int* p = data; p < data + 4; ++p);
5
} 
6
// C++
7
// Größe wird vom Compiler erzwungen, data kann kein null pointer sein.
8
void foo(std::array<int,4>& data)
9
{
10
  // Mit range based for ist sofort ersichtlich, dass man über alles iteriert
11
  for(int i : data);
12
}

Und speziell noch zu den virtuellen Methoden. Beispiel ist ein Interface 
für einen Treiber mit zwei Implementierung, für verschiedene Geräte.
1
class IDriver
2
{
3
public:
4
   virtual void start() = 0;
5
};
6
7
class Driver_DevA : public IDriver
8
{
9
public:
10
  void start() override;
11
};
12
13
class Driver_DevB : public IDriver
14
{
15
public:
16
  void start() override;
17
};
18
19
void bootSystem(IDriver&);
20
21
int main()
22
{
23
#ifdef DEV_A
24
  // Im Code gibt es eine Sprungtabelle (v_table) für Driver_DevA, egal wie viele Instanzen davon angelegt werden.
25
  // Driver_DevA setzt hier den Pointer auf seine Sprungtabelle.
26
  Driver_DevA driver;
27
#else
28
  Driver_DevB driver;
29
#endif
30
  // Hier greift Polymorphismus, es wird aber weder ein Heap noch RTTI (Run Time Type Infomation) benötigt.
31
  bootSystem(driver);
32
}
In C müsste man dies mit Funktionspointern lösen und kann dabei mehr 
Fehler machen, weil man es selbst machen muss. Wird start in einer 
Ableitung nicht implementiert, sagt einem der Compiler dass direkt. Von 
den Resourcen wäre es das gleiche wie in C per Hand, wenn das Interface 
mehr als eine Methode hat.

Mit neuen C++ Standards ist es auch möglich Funktionen zu schreiben, die 
garantiert zur Compilezeit ausgewertet werden (constexpr, consteval), 
ohne dafür den Präprozessor zu misbrauchen. Damit lasse sich z.B. 
Parameter mit floating point Code berechnen, ohne dass dieser Code 
später auf dem Controller zur Laufzeit ausgeführt wird.

Beitrag #6684669 wurde von einem Moderator gelöscht.
Beitrag #6684675 wurde von einem Moderator gelöscht.
Beitrag #6684746 wurde von einem Moderator gelöscht.
Beitrag #6684754 wurde von einem Moderator gelöscht.
von M.K. B. (mkbit)


Lesenswert?

Hallo Otto,

für mein Beispiel brauchst du keinen größeren Controller. Es sollte fast 
auf das gleiche wie bei C rauskommen, wenn der C Code korrekt war. In C 
kann man nämlich leicht die Freigabe von Resourcen beim return 
vergessen, dann ist der C Code natürlich kleiner, aber auch falsch.

Ich gebe dir recht, dass C++ komplexer ist, wenn man C gewöhnt ist und 
sehr hardwarenah denkt. (Bei deinem Kommentar würde ich dich genau hier 
einordnen.)
Deshalb fragt der TO ja auch nach Büchern um das zu lernen oder sein 
Wissen zu erweitern.
Mit C++11 und neueren Standards kann man aussagekräftigen Code schreiben 
und nicht alles, was in C++ Text ist, produziert auch später Code.

von M.K. B. (mkbit)


Lesenswert?

Aber jetzt doch nochmal war hilfreiches für den TO.
Ich würde an deiner Stelle erstmal C++ lernen und die Denkweise 
verinnerlichen. Aus eigener Erfahrung kann das auch etwas dauern.
C++ bringt dir erst dann einen Vorteil, wenn du in Objekten denkst und 
diese nutzt.
z.B.
* std::array<int, 10> a; std::find(a.begin(), a.end(), 42); statt eine 
for Schleife, die mit Pointern und Größen manuell arbeitet
* Freigabe von Locks am Ende des Funktion, egal wie man aus der Funktion 
rausspringt. (Scoped Lock)

Für Embedded kenne ich keine Bücher, weil ich das nur als Schulung 
hatte.
Bei der Anwendung hängt es von deinem System ab:
* OS mit Heap => Es geht wahrscheinlich alles. Interessant ist, wie 
teuer es dann wirklich ist.
* Bare Metal => Die Standardbibliothek ist nur eingeschränkt nutzbar. 
Sonst kannst du aber alles nutzen, was keinen dynamischen Speicher 
braucht.

Als Resourcen beim Üben kann ich noch folgendes empfehlen:
* Compiler Explorer: Hier kann man schnell mal ein C++ Konstrukt 
eingeben und sich im Assembler anschauen, wie es umgesetzt wird und was 
eine Optimierung ändert. https://godbolt.org/
* Cpp Core Guidelines: Das sind best Practices für C++ Code. Immer wenn 
man denkt, dass muss doch besser gehen kann man hier mal reinschauen. 
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
* Die Referenz, wenn du dir anschauen willst, was die Standardbibliothek 
bietet. https://en.cppreference.com/w/

Und wenn du deinen Code auch auf einem Desktop bauen kannst, dann ist 
clang-tidy sehr hilfreich. Das ist eine Codeanalyse, die dir aber in 
vielen Fällen auch gleich sagt, wie man es besser machen könnte.
https://clang.llvm.org/extra/clang-tidy/

Und dann happy coding :-)

Beitrag #6684786 wurde von einem Moderator gelöscht.
von Gerald K. (geku)


Lesenswert?

Da gab es einen Versuch mit EC++ (Embedded C) :

https://de.m.wikipedia.org/wiki/Embedded_C%2B%2B

http://www.caravan.net/ec2plus/

Hat sich aber nicht durchgesetzt.

von Johannes S. (Gast)


Lesenswert?

Daniel schrieb im Beitrag #6684786:
> C++ und Mikrocontroller.
> Warum denn einfach wenns auch kompliziert geht?

Wenn dein Hirn zu klein ist um C++ zu verstehen ist das dein Problem, 
deshalb musst du nicht einen Thread zur Frage nach C++ Literatur voll 
müllen.
Mach einen eigenen Thread auf oder lies erstmal die zigtausend 
existierenden Beiträge zu diesem Thema.

Dieses Unterforum sollte auch für anonyme Störer gesperrt werden, das 
war bisher ein seriöser Teil des Forums.

von M.K. B. (mkbit)


Lesenswert?

Danke, Embedded C++ kannte ich auch noch nicht.

Mich wunder aber, dass sie folgender Konstrukte auch weglassen wollten.

Namensräume:
Das ist ja nur eine Bennenung, die nach der Compilierung sowieso keine 
Rolle mehr spielt. Vorteilhaft gegenüber den Funktionspräfixen bei C 
ist, dass man innerhalb des Namensraum (namespace) diese nicht angeben 
muss.

Templates:
Es müssen ja nicht gleich die komplizierten sein. Aber wenn ich einen 
Algorithmus für zwei Datentypen haben möchte, dann habe ich es einmal 
als Template und erzeuge es dann für beide Typen. Besser als Kopieren.

: Bearbeitet durch User
von Gerald K. (geku)


Lesenswert?

C++ Wissen kann die Porgrammierung in C positiv beeinflussen. Z.B. beim 
Organisieren von Daten durch Kapselung von Daten. 
https://de.m.wikipedia.org/wiki/Datenkapselung_(Programmierung)

Und Erstellung von Zugriffsfunktionen.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Franz W. schrieb:
> Hat jemand von euch hier einen Tipp ? Mir geht es wie gesagt in erster
> Linie um C++ - eine Einführung in Mikrocontroller ist nicht so
> notwendig.

Du kannst mal diesen Thread durcharbeiten:

Beitrag "Informationen zu C vs C++ / aka Futter für die Diskussion"

Da findest Du viele Hinweise.

von Lutz S. (lutzs)


Lesenswert?

Kann das Buch "C++ Das umfassende Handbuch" von Torsten T. Will 
empfehlen. Mit seinem Stil des erklärens kam ich gut zurecht.

von Gerhard W. (dd4da) Flattr this


Lesenswert?

Franz W. schrieb:
> Da ich bei meinen Spielereien mit diversen MCUs (STM32, ESP32) bezüglich
> der Programmierung immer wieder schnell an meine Grenzen stoße, da ich
> kaum über C++ Erfahrung verfüge und diese Sprache ganz allgemein nicht
> so prickelnd finde (um nicht zu sagen, dass ich mittlerweile ein
> erklärter C++-Hasser bin)

Nun,ich verstehe dies sehr gut. Insbesondere wenn man vor hat, die 
Libraries von ST zu verwenden, wird es echt unübersichtlich. Kommt dazu 
noch sowas wie LWIP, kann man sich von der Übersichtlichkeit 
verabschieden. Das Ganze ist in C schon eine Herausforderung und wird in 
C++ durch das ganze Mapping zu einem Kauderwelsch, was man nicht 
braucht.

Ja, man kann alles auch selber schreiben - muss man aber nicht.

In C++ gefällt mit das Bilden von Klassen und Methoden, sowie die 
Möglichkeit der Kapselung von Variablen, sehr gut. Warum C++ auf 
Microcontrollern so wenig Verwendung findet, ist wohl nur durch den 
begrenzten Speicherplatz zu erklären. Viele Vorteile der Sprache kosten 
wohl zuviel Platz.

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

Gerhard W. schrieb:
> Warum C++ auf Microcontrollern so wenig Verwendung findet, ist wohl nur
> durch den begrenzten Speicherplatz zu erklären.

Nein, eher durch Vorurteile wie:

>Viele Vorteile der
> Sprache kosten wohl zuviel Platz.

von Wilhelm M. (wimalopaan)


Lesenswert?

Johannes S. schrieb:
> Gerhard W. schrieb:
>> Warum C++ auf Microcontrollern so wenig Verwendung findet, ist wohl nur
>> durch den begrenzten Speicherplatz zu erklären.
>
> Nein, eher durch Vorurteile wie:
>
>>Viele Vorteile der
>> Sprache kosten wohl zuviel Platz.

Sehe ich genauso: gerade templates und header-only ermöglichen dem 
Compiler beste Optimierungsvoraussetzungen, die man eben mit 
object-libraries nicht hat.

Compiletime-Computation spart Laufzeit und/oder externe Tools. Und 
Meta-Funktionen ermöglichen bestmögliche Expressivität und damit 
Fehleraufdeckung ebenfalls zur Compilezeit.

von Johannes S. (Gast)


Lesenswert?

Man muss nur etwas länger lernen bis man auf deinem Level ist, dafür 
haben auch nicht alle die Zeit und Nerven :)

von Veit D. (devil-elec)


Lesenswert?

Hallo,

laut meiner Erfahrung eignen sich Klassen Templates aber nicht dazu 
Objekt Instanzen mittels Array anzulegen um damit einfach darüber in 
einer for Schleife iterieren zu können. Dazu benötigt man wieder 'new' 
zum initialisieren und für den Zugriff virtuelle Methoden was die 
Codegröße regelrecht explodieren lässt. Was genau den gegenteiligen 
Effekt bringt was man vorher mühselig eingespart hat. Deswegen komme ich 
wieder weg von Klassen Templates. Wenn der Code nicht explodieren soll 
kann man nur einzelne Instanzen anlegen und muss dann auch alle einzeln 
behandeln was keinen großen Sinn macht.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> laut meiner Erfahrung eignen sich Klassen Templates aber nicht dazu
> Objekt Instanzen

das ist eine Tautologie: Objekte sind (unbenannte) Instanzen von Typen 
(Klassen)

> mittels Array anzulegen um damit einfach darüber in
> einer for Schleife iterieren zu können.

Da std::array<> ein homogener Container ist, kannst Du ja nur Objekte 
eines Typ darin aufnehmen.

Veit D. schrieb:
> Dazu benötigt man wieder 'new'
> zum initialisieren und für den Zugriff virtuelle Methoden was die
> Codegröße regelrecht explodieren lässt.

Du meinst also hier wohl eher, dass Du ein Array mit Basisklassenzeigern 
anlegst. Was hat das jetzt mit dem code-bloat durch templates zu tun, 
wenn die std::array<Base*,N> verwendest?

Veit D. schrieb:
> Was genau den gegenteiligen
> Effekt bringt was man vorher mühselig eingespart hat.

Nun ist also Laufzeitpolymorphie doch schlechter in Deinen Augen als 
statische Polymorphie? Etwas wirr.

Veit D. schrieb:
> Wenn der Code nicht explodieren soll
> kann man nur einzelne Instanzen anlegen und muss dann auch alle einzeln
> behandeln was keinen großen Sinn macht.

Du meinst statt eines std::array<A,N> dann N-mal eine eigene Variable?

Ich glaube, Du sortierst besser erst einmal Deine Gedanken.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

vielleicht nur unglücklich ausgedrückt.
Ich habe zum Bsp. eine Klasse ohne Template.
Mit der Klasse kann ich mir ein Array namens object bauen und mittels 
for darüber iterieren.
1
class Foo
2
{
3
  private:
4
  const uint8_t member;
5
  
6
  public:
7
    Foo(byte n) : member {n}
8
    {}
9
            
10
    uint8_t getData() { return member; }
11
};
12
13
Foo object []=
14
{
15
  {10},
16
  {5}
17
};
18
19
void setup (void)
20
{
21
  Serial.begin(250000);
22
  Serial.println(F("\nµC Reset ### ### ###")); 
23
  
24
  for (Foo &i : object) {
25
    Serial.println(i.getData() );
26
  }
27
}
28
29
void loop (void)
30
{ }

Das geht laut meines Wissens mit einem Klassen Template nicht so leicht. 
Dieses sieht wie folgt aus. Jedes Objekt muss einzeln angelegt und 
behandelt werden.
1
template <const uint8_t n>
2
class Foo
3
{
4
  private:
5
  const uint8_t member {n};
6
  
7
  public:
8
    Foo() = default;
9
            
10
    uint8_t getData() { return member; }
11
};
12
13
Foo <10> obj1;
14
Foo <5>  obj2;
15
16
void setup (void)
17
{
18
  Serial.begin(250000);
19
  Serial.println(F("\nµC Reset ### ### ###")); 
20
  
21
  Serial.println(obj1.getData() );
22
  Serial.println(obj2.getData() );
23
}
24
25
void loop (void)
26
{ }

Um die Klassen Template Objekte als Array anzulegen benötige ich new zum 
anlegen dieser und eine zusätzliche Schnittstelle die mir durch 
virtuelle Methoden Zugriff auf die eigentlichen Methoden erlaubt. Genau 
das lässt den Code explodieren. Oder kennst du eine einfachere 
Möglichkeit das man diesen Aufwand nicht treiben muss und die Codegröße 
nicht weiter ansteigt? Wohlgemerkt mit normalen Mitteln was die avr-gcc 
Toolchain bietet.

von Veit D. (devil-elec)


Lesenswert?

Das Klassen Template war noch nicht ganz zu Ende umgeformt. Aber ihr 
wisst sicherlich auch so was ich meine.
1
template <const uint8_t n>
2
class Foo
3
{    
4
  public:
5
    Foo() = default;
6
            
7
    uint8_t getData() { return n; }
8
};
9
10
Foo <10> obj1;
11
Foo <5>  obj2;
12
13
void setup (void)
14
{
15
  Serial.begin(250000);
16
  Serial.println(F("\nµC Reset ### ### ###")); 
17
  
18
  Serial.println(obj1.getData() );
19
  Serial.println(obj2.getData() );
20
}
21
22
void loop (void)
23
{ }

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Um die Klassen Template Objekte als Array anzulegen benötige ich new zum
> anlegen dieser und eine zusätzliche Schnittstelle die mir durch
> virtuelle Methoden Zugriff auf die eigentlichen Methoden erlaubt.

Ja, denn in Deinem Beispiel sind Foo<1> und Foo<2> ja unterschiedliche 
Template-Instanziierungen und damit unterschiedliche Datentypen. Und in 
einen homogenen Container kannst Du nur Objekte desselben DT ablegen.
(Das const in der Template-Präambel ist btw. überflüssig für NNTP.)

Natürlich kannst Du das mit einem Interface für das Template Foo<> 
umgehen. Also etwa:
1
struct I{};
2
template<auto N> struct Foo : public I {
3
// ...
4
};
5
6
Foo<0> f0{42};
7
Foo<1> f1{43};
8
9
I* a[] {&f0, &f1};

Allerdings wäre jetzt die Frage: was modelliert denn Dein template? Eine 
HW-Geräte-Instanz wie etwa einen Timer?

Um heterogene bzw. quasi-heterogene Container zu verwenden, kann man 
std::variant<> einsetzen (also OHNE den Basistyp I):
1
std::variant<Foo<0>, Foo<1>> a[] { {42}, {43} };

oder
1
std::variant<Foo<0>*, Foo<1>*> a[] {&f0, &f1};

(bzw. statt einem rohen C-Array besser mit std::array<>).

Als (echt) heterogener Container dient dann std::tuple<>:
1
auto a = std::make_tuple(Foo<0>{42}, Foo<1>{42}};

Allerdings vermute ich, dass Du ggf. etwas modellieren möchte, was man 
anders machen sollte.

(P.S.: alles obige kann beliebig viele Tippfehler enthalten)

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

Danke. Das sieht auf den ersten Blick vielversprechend aus. Beim 
ausprobieren stelle ich aber fest, das struct I keinen Zugriff auf die 
Member/Methoden von Foo hat. Wie auch, die Vererbung verläuft doch von I 
nach Foo und nicht umgekehrt.
1
struct I {};
2
3
template<uint8_t n>
4
struct Foo : public I
5
{
6
    Foo() = default;           
7
    uint8_t getData() { return n; }
8
};
9
10
Foo<42> obj0;
11
Foo<43> obj1;
12
13
I *object[] {&obj0, &obj1};
14
15
void setup (void)
16
{
17
  Serial.begin(250000);
18
  Serial.println(F("\nµC Reset ### ### ###")); 
19
20
  Serial.println(obj0.getData() );
21
  Serial.println(obj1.getData() );
22
23
  for (I &i : object)
24
  {
25
    Serial.println(i.getData() );
26
  }
27
}
28
29
void loop (void)
30
{ }

Wenn ich das verstanden habe bzw. mein Fehler geklärt ist können wir uns 
über den Zweck meinen Anliegens unterhalten. Sonst wird es mir zu 
durcheinander.

Eigentlich benötigt man wie du das zeigst new zur Objekt Initialisierung 
im Array und das struct I beinhaltet die virtuellen Methoden. Ich lande 
demzufolge immer wieder bei dem Aufbau.
1
struct Interface
2
{
3
  virtual uint8_t getData() = 0;
4
};
5
6
template<uint8_t n>
7
struct Foo : public Interface
8
{
9
    Foo() = default;           
10
    uint8_t getData() { return n; }
11
};
12
13
Interface *foo[2] =
14
{
15
  new Foo <42>(),  
16
  new Foo <43>()   
17
};
18
19
void setup (void)
20
{
21
  Serial.begin(250000);
22
  Serial.println(F("\nµC Reset ### ### ###")); 
23
24
  for (Interface *i : foo)
25
  {
26
    Serial.println(i->getData() );
27
  }
28
29
}
30
31
void loop (void)
32
{ }

von M.K. B. (mkbit)


Lesenswert?

Veit D. schrieb:
> Eigentlich benötigt man wie du das zeigst new zur Objekt Initialisierung
> im Array und das struct I beinhaltet die virtuellen Methoden. Ich lande
> demzufolge immer wieder bei dem Aufbau.

Ich denke in der Realität könnte es auch so aussehen. Dafür wäre dann 
auch kein Heap notwendig.
1
// Code der Klassen, wie bei devil-elec
2
struct Interface;
3
template<uint8_t n>
4
struct Foo : public Interface;
5
6
std::array<Interface*, 2> foo;
7
8
void setup ()
9
{
10
  Serial.begin(250000);
11
  Serial.println(F("\nµC Reset ### ### ###")); 
12
  for (Interface *i : foo)
13
  {
14
    Serial.println(i->getData());
15
  }
16
}
17
18
int main()
19
{
20
  // Geräte spezifischer Code
21
  Foo<42> interface_A;
22
  Foo<43> interface_B;
23
24
  foo[0] = &interface_A;
25
  foo[1] = &interface_B;
26
27
  // Allgemeiner Code
28
  setup();
29
30
  // Man darf main nicht verlassen, sonst wären die Pointer in foo dangling.
31
  for(;;);
32
33
  foo.fill(nullptr);
34
}

von Veit D. (devil-elec)


Lesenswert?

Hallo,

Danke, selbst wenn ich das sortiere, ich habe doch gar kein std::array<> 
zur Verfügung. Ich nutze doch den avr-gcc. Dem fehlt die all umfassende 
Standard Libray.

von Mark B. (markbrandis)


Lesenswert?

Gerald K. schrieb:
> C++ Wissen kann die Porgrammierung in C positiv beeinflussen. Z.B. beim
> Organisieren von Daten durch Kapselung von Daten.
> https://de.m.wikipedia.org/wiki/Datenkapselung_(Programmierung)
>
> Und Erstellung von Zugriffsfunktionen.

Nun ja. Wenn man auf sämtliche Daten einer Klasse über Getter- und 
Setter-Funktionen zugreifen kann, dann hat man an Datenkapselung nicht 
wirklich viel gewonnen. ;-)

Besser: Objekte tauschen zur Laufzeit Nachrichten miteinander aus. So 
wie es ursprünglich in Smalltalk gemacht wurde.

Beitrag #6699729 wurde vom Autor gelöscht.
von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Danke, selbst wenn ich das sortiere, ich habe doch gar kein std::array<>
> zur Verfügung. Ich nutze doch den avr-gcc. Dem fehlt die all umfassende
> Standard Libray.

Dann ist es eine gute Übung, sich gerade mal ein std::array<> zu 
schreiben.

Oben stand Arduino, dass muss ja nicht zwingend AVR sein. Aber ich habe 
es mir schon gedacht, dass Du Arduino/AVR meinst.

std::variant<> ist natürlich schon kompilzierter, damit man UB 
vermeidet. Es schadet aber auch nicht, sich die Implementierung in der 
stdlib anzuschauen.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Wenn ich das verstanden habe bzw. mein Fehler geklärt ist können wir uns
> über den Zweck meinen Anliegens unterhalten. Sonst wird es mir zu
> durcheinander.

Ich hatte ja geschrieben, dass ich das ohne IDE so herunter getippt 
habe. Und dabei habe ich es mir einfach gemacht, und Deine 
Basisklasse/Schnittstelle nicht ausformuliert. Ich dachte, wenigstens 
das sein klar. Ok, Du hast es dann selbst gewmerkt.
Allerdings ist der new-Op wirklich nicht nötig, dass hatte ich Dir oben 
ja schon gezeigt.
Ich bin immer noch der Ansicht, dass Du hier etwas versuchst, was 
entweder der dyn. oder der statischen Polymorphie entgegen läuft. Eine 
Mischung von beidem ist meistens nicht gut.

von Wilhelm M. (wimalopaan)


Lesenswert?

Mark B. schrieb:
> Nun ja. Wenn man auf sämtliche Daten einer Klasse über Getter- und
> Setter-Funktionen zugreifen kann, dann hat man an Datenkapselung nicht
> wirklich viel gewonnen. ;-)

Das sollte man ja auch auf keine Falls tun. Auch die Bezeichnung 
getter-/Setter-Funktionen ist ja m.E. daneben, auch wenn es viel 
verwendet wird (aber weniger im C++ Umfeld).
In C++ ist eine const-Elementfunktion ein Beobachter (getter), und alles 
was nicht const ist, ist dann ein Mutator (setter). Denn der 
beobachtbare Zusatnd eines Objektes muss nicht 1:1 aus den 
Datenelementen des Typs bestehen, sondern kann beliebig daraus berechnet 
werden.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ganz ehrlich, ich komme mir etwas veralbert vor. Weil ohne new geht es 
nicht. Probiere es bitte aus. Wie es ohne new gehen soll weiß ich nicht 
und ich kann es auch nicht erkennen. Wenn das alles Quark ist dann zeige 
mir bitte wie man es anders macht. Im Grunde sollte es im Bsp. auch egal 
wofür man ein Klassen Template baut. Hier gehts doch nur darum wie man 
die dann davon instanzierten Objekte mittels for Schleife iteriert, 
wofür man ein Array benötigt. Ganz am Anfang hatte ich geschrieben das 
ich einen avr-gcc verwende. Das ist unabhängig ob Arduino oder nicht 
Arduino. Mit der Arduino IDE bin ich nur schneller am testen. Tief 
durchatmen ...

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> ganz ehrlich, ich komme mir etwas veralbert vor.

Habe ich mich über Dich lustig gemacht?
1
namespace {
2
    Foo<42> f0;  
3
    Foo<43> f1;  
4
}
5
6
Interface* foo[2] = {&f1, &f1};

Das hatte ich Dir oben schon geschrieben.
Und ändere bitte die Signatur Deines Beobachters ("getters").

von M.K. B. (mkbit)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> ganz ehrlich, ich komme mir etwas veralbert vor. Weil ohne new geht es
> nicht. Probiere es bitte aus.

Mit welchem konkreten Code und Compiler
hast du es denn ausprobiert? Was sagt dir, dass es nicht geht? Wie von 
wimalopaan und mir gezeigt braucht man dafür keinen Heap, also kein new.

Man braucht new nur, wenn man die Objekte dynamisch Objekte anlegen, 
weil es verschiedene Typen sind.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

irgendwie hatten wir aneinander vorbei geredet.  ;-)
So Leute, jetzt habe ich das hinbekommen.
Vielen Dank an eure und meine Geduld.  :-)
1
struct Interface
2
{
3
  virtual uint8_t getData() = 0;
4
};
5
6
template<uint8_t n>
7
struct Foo : public Interface
8
{
9
    Foo() = default;           
10
    uint8_t getData() { return n; }
11
};
12
13
Foo<42> f0;  
14
Foo<43> f1;  
15
16
Interface *foo[2] = {&f0, &f1};
17
18
void setup (void)
19
{
20
  Serial.begin(250000);
21
  Serial.println(F("\nµC Reset ### ### ###")); 
22
23
  for (Interface *i : foo)
24
  {
25
    Serial.println(i->getData() );
26
  }
27
}
28
29
void loop (void)
30
{ }

Das heißt ohne new klappt das aber das Interface mit den virtuellen 
Methoden benötigt man weiterhin? Richtig?

Was ist am Namen getData() verkehrt? Ich hole mir doch Daten mittels 
einer Methode. Wie benennst du solche Methoden? Sowas nennt man doch 
Gettermethoden --> get...

@ M.K.B.
Ich bin von dem unteren Code 13:49 Uhr ausgegangen und habe new 
weggelassen wollen, was nicht ging. Das ist nun hinfällig.   :-)
avr-gcc 10.3.0 auf C++20 eingestellt.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

gleich noch eine Frage. Warum klappt das nicht mit einer Referenz statt 
Zeiger?
1
for (Interface &i : foo)
2
{
3
  Serial.println(i.getData() );
4
}

von M.K. B. (mkbit)


Lesenswert?

Bei dem range based for muss der Typ links der selbe wie im Container 
sein oder konvertierbar sein.

Interface* kann nicht zu Interface& konvertiert werden. Man könnte 
Interface*& machen, aber beim Pointer ist das nicht so sinnvoll.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

verstehe, Danke.

Wenn man ohne virtuelle Methoden auskommen möchte, dann benötigt man 
std::array? Soweit sollte ich dann richtig verstanden haben?

An dem Punkt macht es Sinn weiter auszuholen. Wofür die generelle Frage?
Meine bisherigen Klassen Templates beziehen sich auf die Hardware eines 
µC. Aktuell für einen ATmega4809 und AVR128DB48 bezogen. Diese dienen 
zum konfigurieren, schalten und walten der I/O Pins und zum 
konfigurieren der Timer TCA und TCB. Bei der Pin Klasse würde ein Array 
richtig Sinn machen. Beim TCA eher weniger. Beim TCB schon mehr, weil 
vom TCB Typ mehrere vorhanden sind.
Es würde also Sinn machen wenn man über alles bequem iterieren kann.
Desweiteren habe ich noch eine Timer Klasse erstellt die als 
Kurzzeitwecker oder Stoppuhr dienen kann. Die nutzt den millis Zähler 
von Arduino und ist ansonsten unabhängig. Davon kann man auch mehrere 
Instanzen gebrauchen.

Was wäre die anfangs angesprochene Alternative?

von M.K. B. (mkbit)


Lesenswert?

Ich glaube da gibt es noch ein Missverständnis.

Die virtuellen Methoden braucht man, damit man über den gemeinsamen Typ 
des Interfaces verschiedene Ableitungen mit verschiedenen 
Implementierung ansprechen kann.
Damit kann man z.B. eine Methode mit Referenz oder Pointer auf das 
Interface anlegen, die aber nichts von den möglichen Implementierungen 
wissen muss.
1
void setup(Interface& ifc);

Das Problem mit dem Array und new ist ein anderes. Für den Zugriff auf 
das Interface reicht ein Pointer/Referenz, um damit arbeiten zu können.
Möchte man jedoch die implementierenden Objekte anlegen, dann brauchen 
diese Speicher, dessen Größe und Initialisierung aber vom konkreten 
Objekt abhängen.
Ein Array kann von seinem Prinzip her aber nur gleiche Typen speichern.
Mit new holt man sich den Speicher vom Heap. Das praktische daran ist, 
dass es reicht wenn man den Pointer auf die Basisklasse behält, um das 
Objekt später zerstören zu können. Das Array hält dann nur noch die 
Pointer, die dann wieder vom gleichen Typ sind.

Statt Heap könnte man auch noch std::variant für das Array verwenden.
Oder man legt sich einen statischen Speicherbereich an, gibt diesen 
einer std::pmr::monotonic_buffer_resource und legt dort seine Objekte 
mit std::pmr::polymorphic_allocator an. Das ist aber sehr neu und 
vermutlich nicht in allen Compilern verfügbar. Wenn es trotzdem jemand 
ausprobieren will, dann sollten wir dafür aber einen neuen Thread 
aufmachen.

von Robert M. (r_mu)


Lesenswert?

https://opensource.googleblog.com/2020/03/pigweed-collection-of-embedded-libraries.html 
ist auch großteils in C++ geschrieben.

Virtuelle Funktionen sind natürlich sehr wohl möglich und auch sinnvoll 
verwendbar. Kommt halt auf die Anwendung drauf an. Die meisten embedded 
Applikationen sind halt recht statisch und sollten da auch mit einem 
"statischen" Polymorphismus auskommen, da können templates helfen den 
Code übersichtlich und sicher zu machen (im Vergleich zu C-Makros). Auch 
exceptions sind kein Problem und funktionieren ausgezeichnet auf ARM. 
Man muss halt herumfiddln und newlib-nano mit aktivierten exceptions 
selber bauen.

Insgesamt muss man halt genau wissen welches feature wieviel overhead 
erzeugt, und man darf sich auch vorm asm listing nicht fürchten.

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Was ist am Namen getData() verkehrt? Ich hole mir doch Daten mittels
> einer Methode. Wie benennst du solche Methoden? Sowas nennt man doch
> Gettermethoden --> get...

Es geht überhaupt nicht um die Benennung: Hast Du meinen Post weiter 
oben eigentlich gelesen? Dort stand:

Wilhelm M. schrieb:
> In C++ ist eine const-Elementfunktion ein Beobachter (getter), und alles
> was nicht const ist, ist dann ein Mutator (setter).

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Was wäre die anfangs angesprochene Alternative?

Die Alternative ist parametrische (statische) Polymorphie. Denn die 
(meisten ;-) HW-Geräte sind nur in endlicher Anzahl vorhanden. Der macht 
es wenig Sinn, für die 4 TCB der avr0 eine konkrete Klasse zu schreiben. 
Denn diese kann man beliebig oft instanziieren. Um das zu verhindern, 
müsste man entweder factorys einsetzen und / oder die Klasse als 
monostate realisieren.
Beides ist eigentlich eine Krücke, weil Fehler erst zur Laufzeit 
auftauchen, obwohl es ein statisches Problem ist. Die Alternative wäre 
ein Klassentemplate wie etwa
1
template<auto N, typename MCU>
2
requires(N <4) // c++20
3
struct TCB {
4
    static_assert(N<4); // pre-c++20
5
// ...
6
};

wobei man natürlich den Contraint bzw. die statische Zusicherung besser 
als Metafunktion über den MCU-Type schreibt, denn nicht jede MCU hat 4 
Instanzen den TCB.

Also: statt Objekte zu instanziieren (Laufzeit), instanziiert man 
Templates (Compilezeit).
Das ist das grobe Muster, was man beliebig verfeinern kann.
Und es wird wohl kaum bis nie vorkommen, dass Du über die Timer 
iterieren willst. Und wenn doch, dann geht das auch z.B. durch 
Fold-Expressions ganz einfach und Maschinencode-sparender als zur 
Laufzeit mit dyn. Polymorphie.

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Wenn man ohne virtuelle Methoden auskommen möchte, dann benötigt man
> std::array? Soweit sollte ich dann richtig verstanden haben?

Nein, da besteht kein Zusammenhang.

Dein Problem scheint zu sein, dass Du nicht verstanden hast, was dyn. 
Polymorphie bedeutet. Google mal bzw. schlage eine Buch auf an der 
Stelle: Liskov'sches Substitutionsprinzip und eben 
Schnittstellenvererbung sowie Sub-Typ / Super-Typ-Beziehung ("is-a"). 
Wikipedia geht auch.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich muss das alles erstmal verdauen. Ich danke euch. Mal sehen wie ich 
damit klar komme. Beim Timer habe ich mit
1
static_assert(n < 4, "ATmega4809 has only 4 TCB");
schon vorgesorgt das man keinen Unsinn macht.  :-)

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Beim Timer habe ich mitstatic_assert(n < 4, "ATmega4809 has only 4
> TCB");
> schon vorgesorgt das man keinen Unsinn macht.  :-)

Jetzt musst Du allerdings natürlich auch noch unterscheiden, dass die 
mega0-Serie keinen TCD hat, die avr128db allerdings schon.

Wenn Du nun den MCU-Typ als DT modellierst, dann brauchst Du eine 
Meta-Funktion, um den DT der MCU auf ein Integer abzubilden (type-traits 
sind eine Form von Meta-Funktionen). Das ist ganz leicht, aber auch ein 
gutes Beispiel für solche Compilezeit-Berechnungen.

von M.K. B. (mkbit)


Lesenswert?

Wenn man Compilerzeitberechnungen macht, die Werte und keine Typen 
berechnen, dann kann man mit neueren C++ Versionen auch constexpr 
Funktionen und constexpr if verwenden.
Das sieht dann aus wie normaler Code und ist leichter lesbar als 
Templates.

von Wilhelm M. (wimalopaan)


Lesenswert?

Man kann auch Typ->Typ, oder Wert->Typ Abbildungen mit constexpr bzw. 
immediate Funktionen machen, allerdings benötigt der Aufrufer kann immer 
noch eine extra Typinferenz via decltype, was auch nicht besonders toll 
aussieht.
Ich bevorzuge für die beiden Arten von den vier immer noch traditionelle 
Meta-Funktionen als template, während ich oft Typ->Wert oder natürlich 
immer Wert->Wert als reguläre Funktionen schreibe.

von Veit D. (devil-elec)


Lesenswert?

Die MCU Abhängigkeit wollte ich einfacher lösen.

Jeder Timer hat seine eigene Klasse. TCA, TCB, TCD.
Die Klassen werden intern auf die Anzahl der Timer pro Typ je nach MCU 
begrenzt. Soweit die aktuelle Überlegung dafür.
Ganz am Ende gibts MCU spezifisch eine "Hauptklasse" die alle anderen 
inkludiert was diese MCU an Hardware hat. Wenn ein ATmega4809 keinen TCD 
hat, dann wird die TCD Klasse einfach nicht inkludiert. Sprich die 
#include Zeile nicht getippt. Und selbst wenn man das fälschlicherweise 
machen würde geht das auch nicht, weil die Registeradressen und Bit 
Definitionen nicht vorhanden wären und mir der Compiler dann sowieso 
alles um die Ohren haut.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Die Klassen werden intern auf die Anzahl der Timer pro Typ je nach MCU
> begrenzt.

Ja, genau das meine ich ja (s.o.). Dazu kannst Du dann die Anzahl aus 
dem MCU-Typ berechnen, bei anderer interner Peripherie ist das ja 
genauso. Und das Nichtvorhandensein entspricht dann Anzahl 0.

von Falk S. (falk_s831)


Lesenswert?

Franz W. schrieb:
> Hat jemand von euch hier einen Tipp ? Mir geht es wie gesagt in erster
> Linie um C++ - eine Einführung in Mikrocontroller ist nicht so
> notwendig.

Weiß nicht, ob's noch notwendig ist, aber als gute Bücher zum Thema habe 
ich die folgenden selber gekauft und kann die bedenkenlos empfehlen:

- Stephan Roth: Clean C++
- Bruce Eckel: Thinking in C++
- Andrei Alexandrescu: Modern C++

Gruß,
db8fs

von Franz W. (woyzeck)


Lesenswert?

So, da hab ich ja was losgetreten...

Danke für die Literaturtipps - ich hab mir folgende Bücher gekauft und 
wollte meinen Eindruck schildern:

Programming Principles and Practice Using C++ (Stroustroup)
- verständlich geschrieben
- sehr umfangreich
- hat sogar ein eigenes Kapitel, das sich mit den Einschränkungen der 
Embedded-Programmierung beschäftigt (Memory-Fragmentation etc.)
- Standardwerk, daher sind die Beispiele vielleicht etwas gekünstelt 
bzw. von meinem Bedarf entfernt

C++ Primer (Lippmann, Lajoje, Moo)
- noch verständlicher geschrieben
- wirkt auf mich etwas kompakter
- wenn ich was suche, schaue ich zuerst in dieses Buch
- wirkt auf mich etwas praxisnäher als der Stroustroup (wobei der für 
ein Standardwerk auch recht zugänglich ist)

Den Rest hole ich mir über diverse youtube-Videos.

Von C++ verwende ich nur einen kleinen Teil, aber der ist mir den 
Wechsel von C allemal wert:
* weit besseres Typsystem mit strikteren Compiler-Regeln
* daher mehr Fehler zur Compile-Zeit erkennbar
* Namespaces
* Klassen (ohne komplizierte Vererbungskonstrukte)
* die etwas modernere Syntax

C++ habe ich zuletzt irgendwann in den 90er-Jahren programmiert, konnte 
mich aber nie wirklich damit anfreunden. Allerdings muss ich zugeben, 
dass es hier in den letzten 30 Jahren eine offensichtliche 
Weiterentwicklung gegeben hat und das heutige C++ (11/14) schon um 
einiges moderner ist als damals. Nichts desto trotz ist es leider recht 
mühsam, auf dynamische Speicherreservierung zu verzichten und so gleich 
mal die std-lib auszuschließen. Jede String-Manipulation mit char* fühlt 
sich an wie Long Covid, aber das liegt nicht an der Sprache, sondern an 
der Zielumgebung.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich habe nochmal klassische Lib mit .h/.cpp mit der Klassen Template 
Variante verglichen. Irgendwie stört mich das mit jeder virtuellen 
Methode, die nur zur Verfügung steht aber nicht genutzt werden muss, der 
Flashverbrauch immer weiter ansteigt. Man hat zwar vom Flash eigentlich 
immer genügend nur wenn das mehr Libs werden wird das unschön.

Jetzt bin ich erstmal bei der klassischen .h/.cpp Variante hängen 
geblieben. Hierbei stellt sich mir die Frage warum beim Pin Toggle ein 
Pegelwechsel 2µs dauert. Alle verwendeten Variablenwerte sind const bzw. 
wo immer möglich constexpr. Aus meiner Sicht ist alles zur Compiletime 
bekannt.

Aus einer Port Basisadresse wird mittels Offset das eigentliche Register 
ermittelt und dann mit einer Bitmaske geändert. Mehr ist das nicht. Das 
Einzigste was ich nicht constexpr machen kann ist die letztlich 
verwendete Methode toggle(). Warum weiß ich nicht. Kann es sein das das 
mit getrennten .h/.cpp einfach nicht geht? Beim Versuch bekomme ich 
immer
"error: inline function 'constexpr void OutputP::toggle() const' used 
but never defined [-Werror]". Lasse const weg

Lasse ich const weg bekomme ich:
"error: inline function 'constexpr void OutputP::toggle()' used but 
never defined [-Werror]".
1
// .h
2
constexpr void toggle() const;
3
4
//.cpp
5
constexpr void OutputP::toggle() const { *regVPORTin(pin)  = getMask(pin); }

Gibt es eine Möglichkeit das zu beschleunigen bzw. zur Compiletime 
erledigen/auflösen zu lassen?
So sieht das derzeit aus ...

Registersatz
1
#pragma once
2
3
namespace Pins
4
{
5
  namespace Addr
6
  {
7
    // Arduino Nano Every
8
    struct Offset
9
    {
10
      // VPORTs
11
      const uint8_t vDir  = 0x00;
12
      const uint8_t vOut  = 0x01;
13
      const uint8_t vIn   = 0x02;
14
      const uint8_t vFlag = 0x03;
15
      // PORTs
16
      const uint8_t dir       = 0x00;
17
      const uint8_t dirSet    = 0x01;
18
      const uint8_t dirClear  = 0x02;
19
      const uint8_t dirToggle = 0x03;
20
      const uint8_t out       = 0x04;
21
      const uint8_t outSet    = 0x05;
22
      const uint8_t outClear  = 0x06;
23
      const uint8_t outToggle = 0x07;
24
      const uint8_t in        = 0x08;
25
      const uint8_t flag      = 0x09;   // Interrupt Flag
26
      const uint8_t portCtrl  = 0x0A;   // Slewrate Bit
27
    };
28
    constexpr Offset addrOffset;
29
30
    struct Address // base addresses of registers
31
    {
32
      const uint16_t vport;  
33
      const uint16_t port;
34
      const uint8_t  pinCtrl;
35
      const uint8_t  mask;
36
    };
37
38
    // The index is always the Arduino pin number.
39
    constexpr Address baseAddr[] 
40
    {
41
    // | VPORT | PORT | PINn | BIT  |
42
    // | Base  | Base | CTRL | MASK | // BIT  | PIN
43
       {0x0008, 0x0440, 0x15, 0x20},  //  5   |   0
44
       {0x0008, 0x0440, 0x14, 0x10},  //  4   |   1
45
       {0x0000, 0x0400, 0x10, 0x01},  //  0   |   2
46
       {0x0014, 0x04A0, 0x15, 0x20},  //  5   |   3
47
       {0x0008, 0x0440, 0x16, 0x40},  //  6   |   4
48
       {0x0004, 0x0420, 0x12, 0x04},  //  2   |   5
49
       {0x0014, 0x04A0, 0x14, 0x10},  //  4   |   6
50
       {0x0000, 0x0400, 0x11, 0x02},  //  1   |   7
51
       {0x0010, 0x0480, 0x13, 0x08},  //  3   |   8
52
       {0x0004, 0x0420, 0x10, 0x01},  //  0   |   9
53
       {0x0004, 0x0420, 0x11, 0x02},  //  1   |  10
54
       {0x0010, 0x0480, 0x10, 0x01},  //  0   |  11
55
       {0x0010, 0x0480, 0x11, 0x02},  //  1   |  12
56
       {0x0010, 0x0480, 0x12, 0x04},  //  2   |  13
57
       {0x000C, 0x0460, 0x13, 0x08},  //  3   |  14
58
       {0x000C, 0x0460, 0x12, 0x04},  //  2   |  15
59
       {0x000C, 0x0460, 0x11, 0x02},  //  1   |  16
60
       {0x000C, 0x0460, 0x10, 0x01},  //  0   |  17
61
       {0x0014, 0x04A0, 0x12, 0x04},  //  2   |  18
62
       {0x0014, 0x04A0, 0x13, 0x08},  //  3   |  19
63
       {0x000C, 0x0460, 0x14, 0x10},  //  4   |  20
64
       {0x000C, 0x0460, 0x15, 0x20},  //  5   |  21
65
    };
66
  }
67
}


.h
1
#pragma once
2
3
#include <Arduino.h> 
4
5
class OutputP
6
{
7
  private:
8
  const uint8_t pin;
9
  
10
  public:   
11
    OutputP (uint8_t p);
12
    
13
    void init(); 
14
    void toggle() const;
15
};

.cpp
1
#include <NanoEveryPinNeu.h>
2
       
3
#if defined(ARDUINO_AVR_NANO_EVERY) && defined(__AVR_ATmega4809__)
4
  #include "include/NanoEveryRegister.h"
5
#else
6
  #error "This library only tested with Arduino Nano Every Board (ATmega4809)."
7
#endif
8
9
using Register8 = volatile uint8_t*;
10
11
// ------------------ VPORTs -------------------- //                
12
// Direction
13
constexpr Register8 regVPORTdir(const uint8_t pin)      { using namespace Pins::Addr; return (Register8) (baseAddr[pin].vport + addrOffset.vDir); }
14
  
15
// Input Level Status
16
constexpr Register8 regVPORTin(const uint8_t pin)       { using namespace Pins::Addr; return (Register8) (baseAddr[pin].vport + addrOffset.vIn); }
17
18
// Pin Bitmaske
19
constexpr uint8_t getMask(const uint8_t pin)            { using namespace Pins::Addr; return (baseAddr[pin].mask); } 
20
21
OutputP::OutputP(uint8_t p) : pin {p}
22
{}
23
  
24
// Outputs
25
void OutputP::init() { *regVPORTdir(pin) = *regVPORTdir(pin) | getMask(pin); }
26
27
void OutputP::toggle() const { *regVPORTin(pin) = getMask(pin); }

von Oliver S. (oliverso)


Lesenswert?

Veit D. schrieb:
> Irgendwie stört mich das mit jeder virtuellen
> Methode, die nur zur Verfügung steht aber nicht genutzt werden muss, der
> Flashverbrauch immer weiter ansteigt.

Das Thema zieht sich ja nun schon durch deine ganzen Beträge dieses 
Subthemas hier, aber nach wie vor ist völlig unklar, was virtuelle 
Funktionen mit Templates zu tun haben sollen.

Und warum man virtuelle Funktionen zur Verfügung stellen sollen müsste, 
die gar nicht genutzt werden, musst du auch mal erklären. Abgesehen 
davon, daß der Compiler die bei der Optimierung einfach rauswirft. Das 
einzige, was bei virtuellen Funktionen in Zusammenhang mit Arduino-AVR 
wirklich stört, ist die unschöne Platzierung der vtables im SRAm durch 
den avr-gcc.

Irgend etwas wirfst du da noch ganz gehörig durcheinander.

Veit D. schrieb:
> Alle verwendeten Variablenwerte sind const bzw.
> wo immer möglich constexpr. Aus meiner Sicht ist alles zur Compiletime
> bekannt.

Dann wird der Compiler das auch zur Compilezeit auswerten, und das auch 
ohne constexpr. Allerdings nur, wenn er das auch kann, d.h. er kann alle 
const(expr)-Deklarationen sehen.

Bei solchen Fingerübungen muß man halt immer den generierten 
Asemblercode anschauen.

Oliver

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

Oliver S. schrieb:
> Das einzige, was bei virtuellen Funktionen in Zusammenhang mit
> Arduino-AVR wirklich stört, ist die unschöne Platzierung der vtables
> im SRAm durch den avr-gcc.

Naja, jede virtuelle Funktion kostet damit 2 Byte im SRAM. Lägen die
Vtables im Flash, bräuchte jeder virtuelle Funktionsrufruf ein paar
Taktzyklen mehr, was auch nicht perfekt ist. Wenn man virtuelle
Funktionen irgendwo wirklich sinnvoll einsetzen kann, ist IMHO beides
ganz gut verschmerzbar.

von Veit D. (devil-elec)



Lesenswert?

Hallo,

virtuelle Methoden werden nicht rausgewurfen. Auch dafür habe ich ein 
Assembler Listing gemacht.

Ich erkläre es nochmal.

Wenn ich mit mehreren erstellten Objekten eines Klassentemplates das 
selbe machen möchte, wäre es ja blöd immer alle einzeln zu behandeln was 
im Bandwurm endet. Wofür wurden Arrays und for Schleifen erfunden? Genau 
das geht nun einmal nicht mit Klassen Templates. Darum braucht es ein 
zusätzliches Interface mit virtuellen Methoden.

Ich habe das nochmal am Beispiel kompilieren lassen.

Ein Objekt eines Klassen Templates erstellt. Ohne virtuelle Methoden, 
die sind in der Lib noch auskommentiert. Sprich einen Output Pin 
initialisiert, 1x init und 1x toggle aufgerufen. Macht 806 Byte Flash 
und 22 Byte RAM. Soweit alles okay.

Danach init und toggle als virtuelle Methode zur Verfügung gestellt. Im 
Code noch nicht genutzt.
Macht 880 Byte Flash und 24 Byte RAM.

Danach alle 11 Methoden virtuell zur Verfügung gestellt. Im Code noch 
nicht genutzt.
Macht 1006 Byte Flash und 24 Byte RAM.

Nehme ich so einen zweiten Pin dazu macht das 1172 Byte Flash und 26 
Byte RAM.

Werfe ich die beiden Pin Objekte in ein Array und nutze damit die 
virtuellen Methoden jeweils 1x pro Objekt macht das 1250 Byte Flash und 
30 Byte RAM.

Schaut man sich das Assembler Listung an sieht man ab Zeile 114 alle 
virtuellen Methoden.
1
#include <NanoEveryPin.h>
2
3
OutputPin <12> led12;
4
OutputPin <13> led13;
5
6
Interface *outpin[2] = {&led12, &led13};
7
8
void setup(void) 
9
{
10
  for (Interface *i : outpin)
11
  {
12
    i->init();
13
  }
14
 
15
}
16
17
void loop(void) 
18
{
19
  for (Interface *i : outpin)
20
  {
21
    i->toggle();    // 1,25µs
22
  }
23
}

Mache ich das wie anfangs mit 2 Objekten ohne virtuelle Methoden und 
damit ohne Array macht das 812 Byte Flash und 22 Byte RAM. Jetzt kann 
sich jeder ausrechnen wohin das explodiert mit den virtuellen Methoden. 
Das new weglassen macht keinen Unterschied. Etwas langsamer taktet ein 
Pin mittels virtuellen Methoden auch. 1,25µs für einen Pegelwechseln. 
Ohne sind 125ns drin.

Wenn mir das Oliver ohne virtuelle Methoden zeigen könnte wäre ich sehr 
dankbar.  ;-)

Meine Frage lautet bekommt man die klasssische Varianten .h/.cpp noch 
weiter optimiert? Sodass zur Compilezeit noch mehr aufgelöst werden 
kann? Wie das alles ausschaut habe ich gezeigt. Es steht im Grunde alles 
vorher fest. Nur die Methode toggle lässt sich nicht inline und auch 
nicht constexpr machen. Wobei er es auch selbst optimieren könnte aber 
nicht macht.

Ansonsten müßte ich mich entscheiden ob ich Schnelligkeit hätte oder 
einen Code Bandwurm. Jedesmal die Lib zu ändern oder unterschiedliche 
Libs anzulegen macht ja auch keinen Sinn. Man wäre nur am ändern und 
bekommt keine Ruhe rein. Deswegen der Aufwand und meine Fragen.

von Klaus W. (mfgkw)


Lesenswert?

(wieder gelöscht...)

Um mehr sagen zu können, müsste man den aktuellen Stand sehen.
Am besten mit den bisherigen Tipps eingepflegt, wie z.B. getter-Methoden 
als const.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Wofür wurden Arrays und for Schleifen erfunden? Genau
> das geht nun einmal nicht mit Klassen Templates.

Natürlich geht das. Das hatte ich schon zigmal gesagt und Dir auch 
Beispiel-Code dazu gegeben. Du brauchst kein Laufzeit-Interface.

Irgendwie habe ich das Gefühl, dass Du das entweder gar nicht liest, 
nicht durchdenkst oder nicht willst.

Du kannst sowohl eine Laufzeit-Iteration formulieren (die der Compiler 
bis zu einer begrenzten Array-Größe auch ausrollt) oder auch eine 
Compile-Zeit-Iteration benutzen.

von Oliver S. (oliverso)


Lesenswert?

Wilhelm M. schrieb:
> Du kannst sowohl eine Laufzeit-Iteration formulieren (die der Compiler
> bis zu einer begrenzten Array-Größe auch ausrollt)

Allerdings ist der Aufwand dafür ohne verfügbare C++-Standardlib, d.h. 
ohne tuple, variant, array, etc., doch schon etwas größer.

Aber die eigentliche Frage ist doch: was ist das Ziel der ganzen Übung?

Oliver

von Oliver S. (oliverso)


Lesenswert?

Veit D. schrieb:
> Nur die Methode toggle lässt sich nicht inline und auch
> nicht constexpr machen. Wobei er es auch selbst optimieren könnte aber
> nicht macht.

Als Denkübung könntest du ja einmal beschreiben, was genau ein zur 
Compilezeit per constexpr auswertbares toggle eigentlich tun soll. Das 
hilft dir vielleicht, die Abgrenzungen der zur Compilezeit bekannten, 
statischen  constexpr-Template-Typenwelt von der dynamischen 
Laufzeitwelt besser zu erkennen.

Oliver

: Bearbeitet durch User
von Christian T. (christian_t)


Lesenswert?

Das Buch ist nicht wirklich für einen absoluten C++-Anfänger geeignet, 
ich fand es aber interessant:

Real-Time C++

Efficient Object-Oriented and Template Microcontroller Programming

https://www.springer.com/de/book/9783662567173

von Wilhelm M. (wimalopaan)


Lesenswert?

Auch das Buch (in einer vorigen Auflage) steht schon in der Liste in 
diesem Beitrag drin (hatte ich hier sehr weit oben schon erwähnt, wird 
natürlich jetzt nicht mehr gelesen):

Beitrag "Informationen zu C vs C++ / aka Futter für die Diskussion"

Und noch viel mehr.

von M.K. B. (mkbit)


Lesenswert?

Christian T. schrieb:
> Das Buch ist nicht wirklich für einen absoluten C++-Anfänger geeignet,
> ich fand es aber interessant:
>
> Real-Time C++
>
> Efficient Object-Oriented and Template Microcontroller Programming
>
> https://www.springer.com/de/book/9783662567173

Das Buch selbst kenne ich nicht, die Vorschau sieht aber ganz gut aus.
Wichtig ist ja nicht, ob C oder C++, sondern was die einzelnen 
Konstrukte kosten (Laufzeit, Speicher, Entwicklungszeit).
Damit kann man dann auch fundiert entscheiden, ob man es verwenden 
will/kann.

von Christian T. (christian_t)


Lesenswert?

Wilhelm M. schrieb:
> Auch das Buch (in einer vorigen Auflage) steht schon in der Liste in
> diesem Beitrag drin (hatte ich hier sehr weit oben schon erwähnt, wird
> natürlich jetzt nicht mehr gelesen):

Ich denke um solche Duplikate zu vermeiden ist es sinnvoll wenigstens 
den Titel des Buches zu erwähnen, danach hatte ich hier gesucht, aber 
nicht jeden Link in jedem Beitrag geclickt.

Beitrag #6713798 wurde von einem Moderator gelöscht.
von Johannes S. (Gast)


Lesenswert?

Veit D. schrieb:
> ich habe nochmal klassische Lib mit .h/.cpp mit der Klassen Template
> Variante verglichen.

und wie war jetzt die Buchempfehlung?

von Veit D. (devil-elec)


Lesenswert?

Hallo,

@ Wilhelm:
Das ist ganz schön starker Tobak von dir. Ohne Interface und ohne C++ 
Standardlib ist das möglich?

M.K.B. hatte ich auch so verstanden. Ohne C++ Standardlib benötigt man 
virtuelle Methoden.

@ Klaus:
Wenn du die Lib mit Klassen Template meinst kann ich heute Abend 
bereitstellen.

Das Ziel ist ich möchte mir eine Lib schreiben um Pins zu verwalten. 
Eingänge lesen, Ausgänge schalten. Das alles möglichst schlank und 
schnell. Und es muss für eine Array Objektverwaltung nutzbar sein. Bis 
jetzt kann ich entweder das Eine oder das Andere machen. Beides zusammen 
funktioniert wie oben beschrieben. Mit Array und mittels for Schleifen 
Iteration kann man dann zum Bsp. Eingänge mit Ausgängen verknüpfen zum 
schalten und walten.

> Als Denkübung könntest du ja einmal beschreiben, was genau ein zur
> Compilezeit per constexpr auswertbares toggle eigentlich tun soll. Das
> hilft dir vielleicht, die Abgrenzungen der zur Compilezeit bekannten,
> statischen  constexpr-Template-Typenwelt von der dynamischen
> Laufzeitwelt besser zu erkennen.

Genau darum geht es. Ich kann nicht erkennen warum das der Compiler ohne 
Klassen Template nicht zur Compilezeit auswerten kann.
In beiden Fällen müßte er erkennen das er nur eine Bitmaske in VPORTIN 
schreiben muss und das an Ort und Stelle einfügen soll.
Mir hat noch niemand die Frage beantwortet ob es überhaupt möglich ist 
die toggle Methode inline oder constexpr zu machen.

Wenn die Compilezeitauswertung ja/nein genau der Unterschied zwischen 
Klassen Templates und non Templates ist, dann könnt ihr das ruhig sagen. 
Weil genau das steht in keinem Buch und ist keinem Bsp. der Welt 
erkennbar. Ich sehe nur das alle Bsp. Header only sind. Aber nirgends 
steht das es nur Header only überhaupt möglich ist.

@ Johannes S:
Ich habe schon mitbekommen das es komplett offtopic gewurden ist. Aus 
meinem Kommentar wurde eine Unterhaltung. Nur wenn ich jetzt einen 
Thread eröffne fange ich bei Null an. Also was soll ich machen?

von Klaus W. (mfgkw)


Lesenswert?

Veit D. schrieb:
> @ Klaus:
> Wenn du die Lib mit Klassen Template meinst kann ich heute Abend
> bereitstellen.

gerne, aber doch bitte nicht in diesem Thread.
Was hat das bitte mit einer Buchempfehlung zu tun?

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Das ist ganz schön starker Tobak von dir. Ohne Interface und ohne C++
> Standardlib ist das möglich?

Ja. Auch das habe ich Dir schon gezeigt weiter oben.
Ich habe Dir auch gesagt, dass selbst wenn Dur keine stdlib hast, Dir 
ein tuple oder variant selbser schreiben kannst. Und wenn das auch 
nichts für Dich ist, dann google einfach mal nach "fromscratch c++". 
Dann hast Du alles was Du brauchst mundgerecht.

Veit D. schrieb:
> Und es muss für eine Array Objektverwaltung nutzbar sein.

Auch da hatte nicht nur ich sondern auch einige andere auch nach 
gefragt, warum Du das möchtest. Die Frage hast Du nicht beantwortet. 
Auch hatte ich Dir erklärt, warum es (meistens) nicht notwendig ist, 
HW-Ressourcen mit Instanzen von Klassen zu modellieren, sondern 
statische Template-Instanzen zu verwenden. Anscheinend ist das an Dir 
vorbei gegangen.

Veit D. schrieb:
> Mir hat noch niemand die Frage beantwortet ob es überhaupt möglich ist
> die toggle Methode inline oder constexpr zu machen.

Natürlich kann sie inline sein. Funktionstemplates sind es automatisch.

constepr nicht, weil darin ein reinterpret_cast verborgen sein wird. 
Lies Dir mal durch, was constexpr / consteval und constinit bedeutet.

Veit D. schrieb:
> Wenn die Compilezeitauswertung ja/nein genau der Unterschied zwischen
> Klassen Templates und non Templates ist, dann könnt ihr das ruhig sagen.

Was soll das bedeuten? Du drückst Dich sehr unspezifisch aus.

Veit D. schrieb:
> Weil genau das steht in keinem Buch und ist keinem Bsp. der Welt
> erkennbar. Ich sehe nur das alle Bsp. Header only sind. Aber nirgends
> steht das es nur Header only überhaupt möglich ist.

Hast Du schon mal darüber nachgedacht, warum templates in header 
üblicherweise stehen? Hast Du schonmal irgendein Buch darüber gelesen 
oder versucht zu verstehen.

von Oliver S. (oliverso)


Lesenswert?

Veit D. schrieb:
> Mir hat noch niemand die Frage beantwortet ob es überhaupt möglich ist
> die toggle Methode inline oder constexpr zu machen.

Inline ist gar kein Problem.

Bezgl. constexpr hast meine Frage überhaupt nicht verstanden. Ich stelle 
die mal anders: Erkläre mal den Sinn einer constexpr Funktion vom Typ 
void. Oder halt, was ein constexpr Pin-toggle sein soll.

Veit D. schrieb:
> Ich kann nicht erkennen warum das der Compiler ohne
> Klassen Template nicht zur Compilezeit auswerten kann.

Der Compiler kann und wird alles zur Compilezeit auswerten, was er zur 
Compilezeit auswerten kann, egal, ob da nun Template oder constexpr 
dransteht oder auch nicht.

Oliver

: Bearbeitet durch User
von M.K. B. (mkbit)


Lesenswert?

Veit D. schrieb:
> M.K.B. hatte ich auch so verstanden. Ohne C++ Standardlib benötigt man
> virtuelle Methoden.

Das sehe ich nicht so.
Du kannst alle C++ Features nutzen. Evtl. fallen new/delete weg, weil du 
auf dem Target keinen Heap hast.

Die Standardbibliothek bietet für viele Fälle schon fertig Algorithmen 
oder Typen. Wenn diese aber nicht verfügbar sind oder die 
Implementierung zu teuer, muss man diese aber nicht nutzen.
Im Prinzip kann man alles aus der Bibliothek auch selbst nachbauen.

von Veit D. (devil-elec)


Lesenswert?


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.