Embedded C++: Wie man Code für Mikrocontroller schreibt, ohne verrückt zu werden

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einleitung: Warum C++ in Embedded kein Masochismus, sondern eine Superkraft ist

\n\n

Wenn man „Embedded-Entwicklung“ hört, stellt man sich meist einen gestandenen Ingenieur mit Lötkolben vor, der in purem C schreibt und auf das Datenblatt schwört. Oder das Gegenteil – einen Enthusiasten, der digitalWrite() auf einem Arduino nutzt und sich freut, dass eine LED blinkt. Aber zwischen diesen beiden Welten gibt es eine goldene Mitte – Embedded-Entwicklung mit C++.

\n\n

Ja, genau das C++, das mit Templates und virtuellen Funktionen erschreckt. Tatsächlich sind moderne Mikrocontroller (ARM Cortex-M, RISC-V) durchaus in der Lage, C++-Code zu verarbeiten. Mehr noch: Der sinnvolle Einsatz von OOP, Kapselung und sogar Templates kann Ihren Embedded-Code sauberer, sicherer und schneller machen.

\n\n

In diesem Artikel schauen wir uns an, wie man effizienten C++-Code für Mikrocontroller schreibt, umgehen die Fallstricke mit virtuellen Tabellen und lernen, mit der Hardware auf Registerebene zu arbeiten – aber im C++-Stil. Los geht's?

\n\n

1. Grundlagen: Was Embedded C++ von normalem C++ unterscheidet

\n\n

Die Hauptregel der Embedded-Entwicklung: Ressourcen sind begrenzt. Sie haben keine Gigabyte RAM und keinen Gigahertz-Prozessor. Sie haben 32 KB Flash und 8 KB RAM. Jedes Byte zählt. Daher geht es bei C++ in Embedded nicht um „Ich schreibe eine Klasse mit 10 Vererbungsebenen“, sondern um Kontrolle über Abstraktionen.

\n\n

Was Sie ohne Bedenken verwenden können:

\n
    \n
  • Klassen und Strukturen – zum Gruppieren von Daten und Funktionen. Das ist (speichertechnisch) kostenlos, wenn es keine virtuellen Funktionen gibt.
  • \n
  • Namensräume – um keine Präfixe wie hal_gpio_init() zu erzeugen.
  • \n
  • constexpr und Templates – Berechnungen zur Compile-Zeit, spart Taktzyklen.
  • \n
  • Referenzen und Übergabe als konstante Referenz – sicherer als Zeiger.
  • \n
\n\n

Was Sie ohne dringenden Bedarf besser nicht anfassen sollten:

\n
    \n
  • Dynamischer Speicher (new/delete) – der Heap fragmentiert, der Allokator kann versagen. In Bare-Metal-Projekten tabu.
  • \n
  • Ausnahmen (try/catch) – erfordern Runtime-Unterstützung, nehmen viel Platz ein.
  • \n
  • Virtuelle Funktionen – jede virtuelle Funktion erzeugt eine Tabelle (vtable) in RAM/Flash, und der Aufruf erfolgt über doppelte Indirektion. Aber manchmal sind sie gerechtfertigt (Treiber).
  • \n
  • RTTI (typeid) – wird gar nicht benötigt.
  • \n
\n\n

Der Kern ist einfach: Der C++-Compiler erzeugt genauso effizienten Code wie C, wenn Sie keine „schweren“ Features verwenden. Überprüfen Sie den Disassembler – und das Glück ist auf Ihrer Seite.

\n\n

2. Praxis: Arbeit mit Registern im C++-Stil

\n\n

Im klassischen Embedded C schreiben wir so etwas wie:

\n
#define GPIOA_BASE 0x40020000\n#define GPIOA_MODER (*(volatile uint32_t*)(GPIOA_BASE + 0x00))\nGPIOA_MODER |= (1 << 10); // Setzt Bit 10 (Ausgang)
\n\n

Das funktioniert, sieht aber archaisch aus. In C++ können wir einen praktischen Wrapper ohne Overhead erstellen.

\n\n

Beispiel: Klasse für die Arbeit mit einem Pin

\n
#include <cstdint>\n\nclass GpioPin {\npublic:\n    // Kopieren verbieten\n    GpioPin(const GpioPin&) = delete;\n    GpioPin& operator=(const GpioPin&) = delete;\n\n    // Aufzählung für Modi\n    enum class Mode : uint32_t {\n        Input = 0b00,\n        Output = 0b01,\n        AltFunc = 0b10,\n        Analog = 0b11\n    };\n\n    // Konstruktor: Nimmt die Portadresse und die Pin-Nummer (0-15)\n    GpioPin(volatile uint32_t* port_base, uint8_t pin)\n        : port_(port_base), pin_(pin) {\n        // Kann den Pin sofort konfigurieren\n    }\n\n    // Betriebsmodus setzen\n    void setMode(Mode mode) {\n        volatile uint32_t* moder = port_ + 0x00; // Offset des MODER-Registers\n        *moder &= ~(0b11 << (pin_ * 2));\n        *moder |= (static_cast<uint32_t>(mode) << (pin_ * 2));\n    }\n\n    // High-Pegel setzen\n    void high() {\n        volatile uint32_t* bsrr                

Blogs

Buchempfehlungen