Mikrocontroller & Firmware

Referenz ⚙️ Mechatronik 11 Abschnitte Stand 04.09.2026

Der Mikrocontroller in der Praxis: aktuelle MCU-Landschaft, Toolchains, Peripherie, Interrupts und DMA, Bussysteme, FreeRTOS und Zephyr, Linker und Bootloader, Debugging, Stromsparen und die C-Fallstricke, die jeden einmal erwischen.

01 / 11Start & Landkarte

Firmware ist der Teil, in dem alles zusammenläuft. Die Elektronik gibt vor, was möglich ist, die Regelungstechnik, was passieren soll — und hier wird daraus ein Gerät, das startet, arbeitet, sich meldet und sich aktualisieren lässt. Es ist auch der Teil, in dem die meiste Entwicklungszeit steckt.

Landkarte

Die drei Ebenen, auf denen man arbeiten kann

EbeneBeispielVorteilGrenze
Ökosystem-BibliothekArduino, MicroPython, CircuitPythonIn Minuten zum ersten Ergebnis, riesige BibliothekssammlungVerdeckt, was wirklich passiert; Timing und Stromverbrauch schwer im Griff
Hersteller-SDK / HALSTM32Cube HAL, ESP-IDF, nRF Connect SDK, Pico SDKVoller Zugriff auf die Peripherie, ordentlich dokumentiert, produktionstauglichUmfangreich, herstellerspezifisch, Einarbeitung nötig
Register direktZugriff über die CMSIS-HeaderVolle Kontrolle, minimaler Code, exaktes TimingJedes Detail selbst; nur sinnvoll für kritische Stellen
Ein pragmatischer Weg: Idee im Ökosystem prüfen, fürs fertige Gerät auf das Hersteller-SDK wechseln, Register nur dort direkt, wo es nötig ist (enge ISRs, exaktes Timing). Das Referenzhandbuch des eigenen Controllers einmal durchblättern — nicht lesen, nur wissen, was drinsteckt.

Wie ein Projekt typischerweise verläuft

Board zum Leben erwecken

Versorgung prüfen, Takt konfigurieren, eine LED blinken lassen, serielle Ausgabe zum Laufen bringen. Erst wenn das steht, lohnt alles andere.

Peripherie einzeln in Betrieb nehmen

Jeden Sensor, jeden Aktor für sich testen — mit einem minimalen Programm. Wer alles gleichzeitig aufbaut, sucht Fehler in einem Suchraum, der zu groß ist.

Struktur festlegen

Was läuft im Interrupt, was im Hauptprogramm, was periodisch? Wo liegen die geteilten Daten? Diese Entscheidung im Nachhinein zu ändern ist teuer.

Funktion aufbauen

Zustandsautomaten statt verschachtelter Wartezeiten. Alles Zeitgesteuerte über einen zentralen Systemtick.

Robust machen

Watchdog, Brownout, Plausibilitätsprüfungen, definiertes Verhalten bei Sensorausfall, Fehlerprotokoll im Flash.

Wartbar machen

Versionsnummer, Diagnoseausgabe, Update-Weg, Dokumentation der Pinbelegung.
Grundlage dieses Moduls
  • Elecia White, Making Embedded Systems, 2. Auflage, O'Reilly.
  • Joseph Yiu, The Definitive Guide to Arm Cortex-M (verschiedene Ausgaben je Kern).
  • Interrupt — Memfault Engineering Blog: sehr fundierte Artikel zu Debugging, Fault-Analyse, Bootloadern und Firmware-Betrieb.
  • Referenzhandbuch und Datenblatt des jeweiligen Controllers — die einzige verbindliche Quelle.

02 / 11Mikrocontroller: Familien & Auswahl

Die Wahl des Controllers entscheidet über Monate Arbeit. Nicht die Rechenleistung ist der übliche Engpass, sondern Peripherie, Werkzeuge, Verfügbarkeit und Dokumentation. Ein Controller mit doppelter Taktfrequenz und schlechtem SDK ist die schlechtere Wahl.

Die Familien im Überblick

FamilieKernTaktSpeicherFunkStärke
AVR (ATmega, ATtiny)8 Bit, eigener Kernbis 20 MHz2–256 kB FlashSehr einfach zu verstehen, tolerant, Arduino-Ursprung. Für Neuentwürfe meist zu schwach
RP2040 / RP23502× Cortex-M0+ bzw. 2× M33 oder 2× RISC-V (Hazard3)133 / 150 MHz264 / 520 kB SRAM, externer QSPI-FlashW-Varianten mit WLAN/BLEPIO-Blöcke für frei programmierbare Schnittstellen, exzellente Dokumentation, sehr günstig
STM32Cortex-M0+ bis M7, M33, M5532–800 MHz16 kB – 4 MBbei WB/WBA-SerieRiesige Auswahl, sehr viel Peripherie, industrielle Verfügbarkeit. STM32N6 mit 800 MHz Cortex-M55 und NPU als Spitze
ESP32-FamilieXtensa oder RISC-V80–400 MHzbis 768 kB SRAM, PSRAM möglichimmerFunk eingebaut, sehr preiswert, riesige Community. ESP32-P4 (400 MHz RISC-V) für Multimedia, C5 mit 5-GHz-WLAN, C6 mit Wi-Fi 6 und Thread
nRF52 / nRF54Cortex-M4F / M33 (nRF54H20 zusätzlich mit RISC-V-Koprozessoren)64–320 MHzbis 2 MB Flash, 1 MB RAMBLE, Thread, Matter, ZigbeeDer Maßstab bei Funk mit sehr geringem Stromverbrauch; nRF54L15 mit 128 MHz M33, 1,5 MB NVM
SAM / PIC / AVR-DA (Microchip)Cortex-M0+ bis M7, PIC-Kerne32–300 MHzbreit gefächertteilsSehr lange Verfügbarkeit, gute Analogperipherie
CH32V / GD32VRISC-V48–144 MHz16–256 kBteilsExtrem günstig (unter 20 Cent), wachsende Werkzeugunterstützung
i.MX RT (NXP)Cortex-M7 / M33bis 1 GHzexterner Speicher„Crossover“ — Rechenleistung wie ein Anwendungsprozessor bei Mikrocontroller-Determinismus

Welche Familie wofür

Familie LeistungPeripherieFunkSparsamEinstieg & DokuGünstig
AVRATmega, ATtiny 1 von 5 2 von 5 1 von 5 3 von 5 5 von 5 3 von 5
StarkEinfach zu durchschauen, 5-V-tolerant, Arduino-Ursprung: jede Frage ist schon beantwortet. Schwach8 Bit, wenig Speicher, kein Funk; für Neuentwürfe meist zu schwach. Nimm es fürKleinstaufgaben, Lernen am Register, 5-V-Umfeld.
RP2350Raspberry Pi Pico 2 3 von 5 4 von 5 2 von 5 3 von 5 5 von 5 5 von 5
StarkPIO für beliebige Schnittstellen, das beste Einsteigerhandbuch, sehr günstig, ARM oder RISC-V. SchwachKein Flash im Chip, Funk nur in der W-Variante, ADC mittelmäßig. Nimm es fürErstes Projekt, viele parallele Schnittstellen, LED- und Encoder-Aufgaben.
STM32Cortex-M0+ bis M55 5 von 5 5 von 5 2 von 5 4 von 5 3 von 5 3 von 5
StarkRiesige Auswahl, hochauflösende Timer, schnelle ADCs, industrielle Verfügbarkeit. SchwachGroßes, uneinheitliches Ökosystem; CubeMX und HAL wollen eingearbeitet werden. Nimm es fürMotorsteuerung, Regelung, Messtechnik, Produkte mit langer Laufzeit.
ESP32-FamilieS3, C3, C6, P4 4 von 5 3 von 5 5 von 5 2 von 5 4 von 5 5 von 5
StarkWLAN und Bluetooth eingebaut, viel RAM, riesige Community, sehr billig. SchwachAktiv stromhungrig, Deep Sleep braucht Sorgfalt, Analogteil schwach. Nimm es fürAlles mit Netz: Sensoren im WLAN, MQTT, Displays, Home Assistant.
nRF52 / nRF54Nordic 3 von 5 3 von 5 5 von 5 5 von 5 3 von 5 2 von 5
StarkDer Maßstab bei BLE, Thread und Matter mit sehr geringem Stromverbrauch. SchwachZephyr-basiertes SDK mit steiler Lernkurve; teurere Boards. Nimm es fürBatteriegeräte über Monate, Wearables, Funksensoren.
CH32V / GD32VRISC-V, Cent-Klasse 2 von 5 2 von 5 1 von 5 3 von 5 2 von 5 5 von 5
StarkUnter 20 Cent, brauchbare Peripherie, wachsende Werkzeugunterstützung. SchwachDokumentation dünn, Werkzeuge noch im Aufbau, kleine Community. Nimm es fürKostengetriebene Kleinserie, wenn Zeit für Einarbeitung da ist.

Punkte sind Einordnungen typischer Vertreter (5 = sehr gut), keine Messwerte. Sie helfen bei der Vorauswahl — entschieden wird am Datenblatt.

Auswahl nach Anwendungsfall

Erstes Projekt, Lernen
Raspberry Pi Pico 2 oder ein ESP32-Board. Beide billig, gut dokumentiert, mit MicroPython und C nutzbar. Der Pico hat das mit Abstand beste Handbuch für Einsteiger.
Etwas mit WLAN oder Bluetooth
ESP32-C6 oder ESP32-S3. Funk, Speicher und Werkzeuge in einem, sehr viele fertige Bibliotheken. Für Batteriebetrieb über Monate lieber nRF52840 oder nRF54L15.
Motorsteuerung, Regelung
STM32G4 oder STM32F4 — hochauflösende Timer, mehrere schnelle ADCs, Komparatoren, teils mit Hardware-Beschleuniger für Regelalgorithmen.
Viele parallele Schnittstellen
RP2350 mit seinen PIO-Blöcken: acht kleine Zustandsautomaten, die beliebige Protokolle takten — WS2812, DVI, mehrere UARTs, Encoder, alles ohne CPU-Last.
Batterie, Jahre Laufzeit
nRF54L, STM32U5 oder MSP430. Entscheidend ist nicht der aktive Strom, sondern der Ruhestrom und wie schnell der Controller wieder einschläft.
Bildverarbeitung, KI am Gerät
ESP32-P4, STM32N6 (Cortex-M55 mit NPU) oder gleich ein Linux-System. Vorher genau rechnen: Kamera und Modell bestimmen den Speicherbedarf, nicht die Taktfrequenz.

Die Kriterien in der richtigen Reihenfolge

Peripherie zählen

Wie viele PWM-Kanäle, ADC-Kanäle, UARTs, SPI, CAN? Und: Lassen sich diese Funktionen gleichzeitig auf die vorhandenen Pins legen? Die Alternate-Function-Tabelle im Datenblatt entscheidet häufiger über die Wahl als die Rechenleistung.

Speicher großzügig ansetzen

Ein Netzwerkstack, TLS und ein Dateisystem brauchen schnell 100 kB RAM. Wer knapp wählt, zahlt später mit einem Neuentwurf. Eine Nummer größer kostet meist wenige Cent.

Werkzeuge und Dokumentation prüfen

Gibt es ein vollständiges Referenzhandbuch? Läuft das SDK auf dem eigenen Betriebssystem? Gibt es Beispiele für die benötigte Peripherie? Ein Forum mit aktiven Antworten?

Verfügbarkeit prüfen

Lieferzeit und Lebenszyklus bei Mouser, Digi-Key oder LCSC ansehen. Hersteller geben oft Longevity-Zusagen — für alles, was länger als ein Jahr leben soll, ist das relevant.

Erst dann Rechenleistung

Für Regelung, Kommunikation und Bedienung reichen 48–160 MHz fast immer. Fließkomma-Einheit lohnt sich, sobald Regelalgorithmen oder Signalverarbeitung ins Spiel kommen.

Entwicklungsboards, die sich bewährt haben

BoardPreisBesonderheit
Raspberry Pi Pico 2 / 2 W≈ 5–8 €RP2350, wahlweise ARM- oder RISC-V-Kerne, PIO, sehr gutes Handbuch
ESP32-S3-DevKitC / C6-DevKitC≈ 10–15 €Funk komplett, USB direkt am Chip, viel RAM
STM32 Nucleo-64 / Discovery≈ 15–25 €ST-Link-Debugger eingebaut, Arduino-kompatible Steckleiste
nRF52840-DK / nRF54L15-DK≈ 45–60 €Strommessung eingebaut, hervorragend für Batterieoptimierung
Teensy 4.1≈ 30 €i.MX RT1062 mit 600 MHz, Arduino-kompatibel, sehr schnell
Arduino Uno R4 / Nano ESP32≈ 20–25 €Für den Einstieg mit vertrauter Umgebung, aber moderner Hardware
Ein Board mit eingebautem Debugger ist die beste Investition am Anfang. Ein Nucleo-Board oder ein Debug-Adapter (ST-Link, J-Link EDU, CMSIS-DAP) kostet wenig und macht Breakpoints, Speicheransicht und Fault-Analyse möglich — siehe Debuggen & Messen.
Quellen

03 / 11Toolchain & Build

Vom C-Quelltext zur Datei im Flash sind es vier Schritte: Präprozessor, Compiler, Assembler, Linker. Wer weiß, welcher Schritt gerade meckert, findet den Fehler in Minuten — die meisten unverständlichen Meldungen kommen vom Linker.

Der Weg vom Quelltext zum Gerät

Präprozessor

Löst #include, #define und bedingte Übersetzung auf. Fehlerbild: fehlende Header, falsche Include-Pfade, doppelte Makros. Ergebnis ansehen mit gcc -E.

Compiler

Erzeugt je Übersetzungseinheit Assembler. Fehlerbild: Syntax, Typen, unbekannte Bezeichner. Warnungen ernst nehmen — sie zeigen fast immer echte Probleme.

Assembler

Erzeugt Objektdateien (.o). Selten die Fehlerquelle.

Linker

Fügt Objektdateien und Bibliotheken zusammen und legt sie nach dem Linker-Skript im Speicher ab → Speicherbild & Linker.

Umwandeln und Flashen

objcopy macht aus der ELF-Datei ein .bin oder .hex. Geflasht wird per Debug-Adapter, Bootloader oder Massenspeicher-Emulation (Tabelle unten).
Linker-MeldungBedeutetÜbliche Ursache
undefined reference to …Symbol wird benutzt, aber nirgends definiertQuelldatei nicht im Build, Bibliothek nicht eingebunden, C++-Namensdekoration ohne extern "C"
multiple definition of …Symbol mehrfach definiertVariable im Header statt in einer .c-Datei — dort nur extern deklarieren
region FLASH overflowedProgramm größer als der SpeicherbereichZu viel Code oder zu große Konstanten; mit -Os und --gc-sections prüfen
region RAM overflowedStatische Daten passen nicht ins RAMGroße Puffer, Tabellen ohne const, zu groß dimensionierte Stacks
Zwei Befehle, die man kennen sollte: arm-none-eabi-size firmware.elf zeigt die Belegung (text = Code und Konstanten im Flash, data = initialisierte Variablen, bss = uninitialisierte; Flash = text + data, RAM = data + bss). arm-none-eabi-nm --size-sort -S firmware.elf listet die größten Symbole — der schnellste Weg zur Frage, warum das Programm plötzlich 40 kB größer ist.

Die SDK-Landschaft

SDKFürBuildCharakter
Arduino Corefast allesArduino CLI, PlatformIOEinfachster Einstieg, riesige Bibliothekssammlung. Auf modernen Cores (ESP32, RP2350, STM32) liegt darunter das Hersteller-SDK — beides lässt sich mischen
ESP-IDFESP32-FamilieCMake, idf.pyVollständig, FreeRTOS eingebaut, Konfiguration über menuconfig, sehr gute Dokumentation
STM32Cube (HAL/LL)STM32CMake oder Makefile, CubeIDECubeMX erzeugt Initialisierungscode aus einer grafischen Pinbelegung — spart Zeit, erzeugt viel Code
Pico SDKRP2040 / RP2350CMakeSchlank, sauber dokumentiert, mit PIO-Assembler für eigene Schnittstellen
nRF Connect SDKNordic nRFWest (Zephyr)Basiert auf Zephyr; steile Lernkurve, dafür sehr vollständig für Funkanwendungen
ZephyrherstellerübergreifendWest, CMake, Kconfig, DevicetreePortabel über Hunderte Boards, LTS-Zweig für Produkte. Der Preis ist die Einarbeitung in das Konfigurationssystem
MicroPython / CircuitPythonESP32, RP2, nRF, STM32Interaktiv über REPL, ideal zum Ausprobieren von Sensoren. Für zeitkritische Aufgaben zu langsam und zu wenig deterministisch

Konfiguration: Kconfig und Devicetree

Kconfig — welche Software
Legt fest, was übersetzt wird: Netzwerkstack ja oder nein, Log-Stufe, Puffergrößen. Aus dem Linux-Kernel übernommen, in Zephyr und ESP-IDF verwendet.
Devicetree — welche Hardware
Beschreibt, was vorhanden ist: welcher Sensor an welchem I²C-Bus mit welcher Adresse, welcher Pin eine LED ist. Der Code greift über Aliase zu, statt Pinnummern fest einzutragen.

Der Aufwand lohnt, sobald eine Firmware auf mehreren Boardvarianten laufen soll: statt #ifdef-Ketten ändert man eine Overlay-Datei. Für ein einzelnes Bastelprojekt ist es Überbau, für ein Produkt mit drei Hardwareständen die einzige saubere Lösung.

Flashen und Debuggen

WegWerkzeugDebuggenAnmerkung
SWD / JTAGST-Link, J-Link, CMSIS-DAP, Raspberry Pi Debug ProbejaDer Standardweg: Breakpoints, Speicheransicht, Live-Variablen → Debuggen & Messen
USB-DFUdfu-utilneinBootloader im ROM, kein Adapter nötig
UART-Bootloaderstm32flash, esptoolneinBei ESP32 der Normalfall; Boot-Pins beim Start beachten
MassenspeicherDatei auf ein Laufwerk kopierenneinPico im BOOTSEL-Modus, viele mbed-Boards
OpenOCD / probe-rsquelloffenjaSehr viele Adapter und Ziele; probe-rs ist deutlich einfacher einzurichten

Compiler- und Linker-Schalter

SchalterWirkungWann
-OgOptimierung, die Debuggen erlaubtWährend der Entwicklung
-O2StandardoptimierungFür die Auslieferung
-OsAuf Größe optimierenWenn der Flash knapp wird
-Wall -WextraWarnungen einschaltenImmer
-ffunction-sections -fdata-sectionsJede Funktion in eine eigene SektionZusammen mit -Wl,--gc-sections: entfernt ungenutzten Code
-g3Volle Debug-InformationImmer — sie landet in der ELF-Datei, nicht im Flash
-Wl,-Map=firmware.mapErzeugt die Speicherkarte des LinkersWenn Flash oder RAM knapp werden: zeigt, welches Objekt wie viel belegt
--specs=nano.specsKleine C-Bibliothek (newlib-nano)Spart mehrere Kilobyte gegenüber der vollen printf-Implementierung
Optimierungsstufe ändert Verhalten — aber nie korrekten Code. Wenn ein Programm mit -O0 läuft und mit -O2 nicht, liegt fast immer ein echter Fehler vor: eine Variable, die eine ISR ändert, ohne volatile; ein Zugriff über einen Zeiger falschen Typs; eine Warteschleife ohne Nebenwirkung. Details unter C-Praxis & Fallstricke.
Quellen

04 / 11Peripherie: GPIO, Timer, DMA

Die Peripherie ist der eigentliche Grund, einen Mikrocontroller zu nehmen. Timer, DMA und Wandler arbeiten unabhängig von der CPU weiter — richtig eingesetzt erledigt die Hardware Aufgaben, für die Software zu langsam und zu ungenau wäre.

GPIO richtig konfigurieren

EinstellungOptionenWann was
RichtungEingang, Ausgang, Alternate Function, AnalogAnalog schaltet die Digitalstufe ab und spart Strom — für ADC-Pins immer
AusgangstypPush-Pull, Open-DrainOpen-Drain für I²C, Wired-AND, Pegelanpassung nach oben
Pull-WiderstandPull-up, Pull-down, keinerEingänge nie floaten lassen. Interne Pull-ups liegen bei 20–50 kΩ
FlankensteilheitLow, Medium, High SpeedSo langsam wie möglich — steile Flanken erzeugen Störungen und Überschwingen
Treiberstärke2–20 mA je nach ControllerGesamtstrom über alle Pins beachten, nicht nur den je Pin
Der Strom durch einen GPIO-Pin ist begrenzt — der über die ganze Portgruppe auch. Ein STM32 verträgt oft 20 mA je Pin, aber nur 80–120 mA über den gesamten Baustein. Acht LEDs mit je 20 mA direkt am Controller sind bereits jenseits der Spezifikation. Ab wenigen Milliampere gehört ein Transistor oder Treiberbaustein dazwischen.

Timer: der vielseitigste Block

Zeitbasis
Zähler plus Vorteiler erzeugen einen periodischen Interrupt. Der Systemtick für alles Zeitgesteuerte.
PWM (Compare)
Der Ausgang wechselt bei einem Vergleichswert. Auflösung und Frequenz teilen sich denselben Zähler: bei 100 MHz Takt sind volle 16 Bit nur bis ≈1,5 kHz möglich, bei 100 kHz PWM bleiben rund 10 Bit.
Input Capture
Der Zählerstand wird bei einer Flanke am Eingang festgehalten — ohne Interrupt-Jitter. Für Frequenz-, Pulsweiten- und Drehzahlmessung deutlich genauer als Software.
Encoder-Modus
Der Timer wertet zwei Quadratursignale direkt aus und zählt vor und zurück. Kein Interrupt, keine verlorenen Schritte — für Drehgeber die einzige richtige Lösung.
Komplementäre Ausgänge mit Totzeit
Advanced-Timer erzeugen zwei zueinander invertierte PWM-Signale mit einstellbarer Totzeit — Voraussetzung für jede Halbbrücke. Dazu ein Break-Eingang, der bei Überstrom die Ausgänge in Hardware sofort abschaltet.
Verkettung
Ein Timer triggert den nächsten oder den ADC. Damit lässt sich zum Beispiel exakt in der Mitte der PWM-Einschaltphase messen — der Standardtrick bei Motorstromregelung.

DMA — Daten bewegen ohne CPU

Der DMA-Controller kopiert selbstständig zwischen Peripherie und Speicher. Der Prozessor wird erst am Ende (oder zur Hälfte) benachrichtigt.

AnwendungOhne DMAMit DMA
ADC mit 100 kSa/s100 000 Interrupts pro Sekunde — die CPU macht nichts anderes mehrEin Interrupt je gefülltem Puffer
Display über SPIJedes Byte einzeln geschrieben, Bildaufbau spürbar langsamGanzer Rahmen im Hintergrund, CPU rechnet weiter
UART-EmpfangBei hoher Baudrate gehen Zeichen verlorenRingpuffer im Hintergrund, mit Idle-Interrupt für Paketende
WS2812-LEDsBit-Banging blockiert allesPWM oder SPI mit DMA, exaktes Timing ohne CPU-Last
Die drei DMA-Fallen: (1) Der Puffer muss dort liegen, wo der DMA-Controller hinkommt — bei manchen Controllern erreicht er das schnelle CCM-RAM nicht. (2) Bei aktivem Daten-Cache (Cortex-M7) muss vor dem Lesen invalidiert und nach dem Schreiben zurückgeschrieben werden, sonst sieht die CPU alte Daten. (3) Ein Puffer auf dem Stack ist ungültig, sobald die Funktion verlassen wird — DMA-Puffer gehören nach static oder in einen globalen Bereich.

ADC in der Praxis

Abtastzeit lang genug wählen

Der interne Sample-and-Hold-Kondensator braucht Zeit zum Laden. Bei hochohmiger Quelle die längste Abtastzeit einstellen — sonst hängt der Messwert vom vorher gemessenen Kanal ab.

Referenz klären

Wird gegen V_DDA gemessen, wandert der Messwert mit der Versorgung. Für absolute Messungen eine externe Referenz oder die interne Referenzspannung mit dem Werkskalibrierwert nutzen.

Über mehrere Werte mitteln

Oversampling mit 16 Werten bringt zwei zusätzliche effektive Bits. Viele Controller können das in Hardware.

Zeitpunkt festlegen

Bei PWM-getriebenen Lasten in der Mitte der Ein- oder Ausschaltphase messen, per Timer getriggert. Zufällig gemessene Werte sind bei geschalteten Lasten wertlos.

Interne Kanäle nutzen

Temperatursensor, Referenzspannung und Versorgungsüberwachung sind meist eingebaut — nützlich für Selbstdiagnose ohne Zusatzbauteile.

Weitere Blöcke, die oft übersehen werden

BlockWas er leistetWofür
WatchdogLöst Reset aus, wenn er nicht regelmäßig zurückgesetzt wirdPflicht in jedem Gerät, das unbeaufsichtigt läuft. Fenster-Watchdog erkennt auch zu häufiges Zurücksetzen
RTCUhrzeit mit Batteriepufferung, WeckfunktionZeitstempel, geplantes Aufwachen aus dem Tiefschlaf
KomparatorAnaloge Schwelle in Hardware, ohne CPUÜberstromabschaltung in Nanosekunden, Nulldurchgangserkennung
CRC-EinheitPrüfsumme in HardwareFirmware-Integrität, Protokolle — deutlich schneller als Software
TRNGEchter ZufallsgeneratorSchlüssel, Nonces, eindeutige Kennungen
Eindeutige ID96–128 Bit Seriennummer im ChipGeräteidentifikation, MAC-Adresse ableiten
PIO (nur RP2040/RP2350)Kleine, frei programmierbare Zustandsautomaten an den PinsBeliebige Protokolle in Hardware — der interessanteste Peripherieblock der letzten Jahre
Quellen
  • Referenzhandbuch des jeweiligen Controllers — Timer- und DMA-Kapitel sind gerätespezifisch und lassen sich nicht verallgemeinern.
  • STMicroelectronics AN4776 (Timer-Übersicht) und AN4031 (DMA-Nutzung).
  • RP2350 Datasheet, Kapitel PIO — mit Beispielprogrammen.
  • Interrupt (Memfault) — Artikel zu DMA, Cache-Kohärenz und Watchdog-Strategien.

05 / 11Interrupts & Nebenläufigkeit

Ein Interrupt unterbricht das laufende Programm — genau dort, wo man es am wenigsten erwartet. Deshalb sind fast alle sporadischen, nicht reproduzierbaren Fehler in Firmware Interrupt-Probleme: geteilte Daten ohne Schutz, zu lange Routinen oder eine Prioritätsvergabe, die niemand durchdacht hat.

Der Ablauf

Ereignis tritt auf

Peripherie setzt ein Flag und meldet es dem Interrupt-Controller (NVIC bei Cortex-M).

Prozessor sichert den Kontext

Bei Cortex-M werden acht Register automatisch auf den Stack gelegt — deshalb ist die Latenz deterministisch (12 Takte bei M3/M4/M7, 15 beim M0+) und es braucht keinen Assembler-Vorspann.

Handler läuft

Der Vektor zeigt auf die Funktion. Sie sollte kurz sein und keine blockierenden Aufrufe enthalten.

Flag löschen

Sonst wird die Routine sofort erneut aufgerufen. Bei manchen Controllern muss nach dem Löschen zurückgelesen werden, damit der Schreibvorgang die Peripherie erreicht hat, bevor der Handler endet.

Kontext wiederherstellen

Rücksprung ins unterbrochene Programm. Bei Tail-Chaining wird direkt der nächste anstehende Interrupt bedient, ohne zwischendurch zurückzukehren.

Prioritäten

EbeneBeispielRegel
HöchsteÜberstromabschaltung, Not-Halt, Motor-BreakMuss immer durchkommen, Routine extrem kurz
HochRegelkreis-Tick, Encoder, ADC-AbschlussFeste Periode, harte Frist
MittelKommunikation: UART, SPI, CAN, DMAPuffer füllen und leeren, keine Verarbeitung
NiedrigTastenabfrage, Display, ProtokollierungKann warten; gehört meist ohnehin ins Hauptprogramm
Bei ARM Cortex-M ist die niedrigere Zahl die höhere Priorität. Priorität 0 ist die höchste. Und: Nur die oberen Bits des Prioritätsregisters sind implementiert — bei vier Bit sind das 16 Stufen, die Werte 0, 16, 32 usw. Wer 0, 1, 2 schreibt, bekommt möglicherweise dieselbe Stufe. Bei Verwendung eines RTOS gibt es zusätzlich eine Grenze, unterhalb derer keine RTOS-Aufrufe erlaubt sind (configMAX_SYSCALL_INTERRUPT_PRIORITY bei FreeRTOS).

Geteilte Daten sicher übergeben

Was volatile leistet
Es verhindert, dass der Compiler Zugriffe wegoptimiert oder in Register zwischenspeichert. Ohne volatile kann eine Schleife, die auf ein von der ISR gesetztes Flag wartet, endlos laufen, weil der Compiler das Flag nur einmal liest.
Was volatile nicht leistet
Es macht Zugriffe nicht atomar. Ein volatile uint32_t auf einem 8-Bit-Controller wird in vier Schritten gelesen — der Interrupt kann mittendrin zuschlagen. Und es ordnet keine Speicherzugriffe über mehrere Variablen hinweg.
MusterWannUmsetzung
Einzelnes FlagISR meldet ein Ereignisvolatile bool; in der Hauptschleife prüfen und zurücksetzen
Kritischer AbschnittMehrere zusammengehörige WerteInterrupts kurz sperren (__disable_irq() / BASEPRI), Daten kopieren, wieder freigeben. So kurz wie irgend möglich
RingpufferDatenstrom von der ISR ins HauptprogrammEin Schreiber, ein Leser, Größe als Zweierpotenz. Dann sind ohne jede Sperre nur zwei Indizes nötig
DoppelpufferGanze Datensätze, z. B. vom ADC per DMAISR füllt Puffer A, Hauptprogramm liest Puffer B, dann Tausch
Atomare OperationZähler erhöhenBei Cortex-M3 aufwärts LDREX/STREX bzw. die C11-Atomics; alternativ Interrupts kurz sperren
RTOS-QueueISR an TaskxQueueSendFromISR() — nie die normale Variante aus einer ISR aufrufen

Der Ringpuffer — das Muster, das man einmal richtig baut

#define RB_SIZE 256                 /* Zweierpotenz! */
static volatile uint8_t  rb[RB_SIZE];
static volatile uint16_t rb_w = 0, rb_r = 0;

/* nur aus der ISR aufgerufen */
static inline void rb_put(uint8_t c)
{
    uint16_t next = (rb_w + 1u) & (RB_SIZE - 1u);
    if (next != rb_r) {            /* voll -> Zeichen verwerfen */
        rb[rb_w] = c;
        rb_w = next;
    }
}

/* nur aus dem Hauptprogramm aufgerufen */
static inline int rb_get(void)
{
    if (rb_r == rb_w) return -1;    /* leer */
    uint8_t c = rb[rb_r];
    rb_r = (rb_r + 1u) & (RB_SIZE - 1u);
    return c;
}

Warum das ohne Sperren funktioniert: Es gibt genau einen Schreiber für rb_w (die ISR) und genau einen für rb_r (das Hauptprogramm). Jeder liest nur den Index des anderen. Solange die Indizes einzeln atomar geschrieben werden — bei 16 Bit auf einem 32-Bit-Controller gegeben —, kann nichts durcheinandergeraten. Die Zweierpotenz macht aus dem Modulo eine UND-Verknüpfung.

Typische Fehler und ihre Symptome

SymptomWahrscheinliche UrsachePrüfung
Programm hängt in der ISR festInterrupt-Flag nicht gelöschtDebugger anhalten, Rücksprungadresse ansehen
Werte springen sporadischMehrbyte-Variable ohne Schutz geteiltKritischen Abschnitt einbauen und beobachten, ob es verschwindet
Alles wird langsam, wenn Daten ankommenVerarbeitung in der ISR statt im HauptprogrammGPIO-Pin am Anfang und Ende der ISR umschalten, am Oszilloskop messen
Hard Fault nach längerer LaufzeitStacküberlauf — ISR verschachteln sich tiefer als vorgesehenStack mit Muster vorbelegen und prüfen, wie weit es überschrieben wurde
Eine Aufgabe kommt nie dranEine hochpriore Quelle löst dauernd ausInterruptzähler je Quelle mitzählen und ausgeben
Fehler nur mit OptimierungFehlendes volatileMit -O0 gegentesten; verschwindet der Fehler, ist es fast sicher das
Das nützlichste Messwerkzeug: Am Anfang der ISR einen GPIO-Pin auf High setzen, am Ende auf Low. Am Oszilloskop oder Logikanalysator sieht man dann sofort, wie lange die Routine läuft, wie oft sie aufgerufen wird und ob sich Interrupts verschachteln. Diese zwei Zeilen Code beantworten mehr Fragen als jede Simulation.
Quellen

06 / 11Bussysteme auf dem Board

Vier Busse decken fast alles ab: I²C für viele langsame Sensoren an zwei Leitungen, SPI für hohe Datenraten auf kurzer Strecke, UART für Punkt-zu-Punkt und Debug-Ausgaben, CAN für störfeste Verbindungen über mehrere Meter. Die Wahl folgt aus Distanz, Anzahl der Teilnehmer und Datenrate.

Der Vergleich

BusLeitungenTeilnehmerRateDistanzStärkeSchwäche
I²C2 (SDA, SCL) + GNDviele, per Adresse100 k / 400 k / 1 M / 3,4 M< 1 mWenige Pins, Adressierung eingebautEmpfindlich, blockierbar, Adresskonflikte
SPI4 (MOSI, MISO, SCK, CS)je Teilnehmer ein CSbis 100 MHz< 30 cmSehr schnell, einfach, VollduplexViele Pins, keine Rückmeldung, keine Adressierung
UART2 (TX, RX)genau 2bis ~12 Mbit/s< 5 m (mit RS-485 bis 1200 m)Denkbar einfach, überall vorhandenNur Punkt zu Punkt, Baudraten müssen passen
CAN / CAN FD2 differentiell + GNDbis 1101 Mbit/s (FD bis 8 Mbit/s Daten)bis 40 m bei 1 Mbit/sSehr störfest, Arbitrierung ohne Kollisionsverlust, Fehlererkennung eingebautTransceiver nötig, Abschlusswiderstände, mehr Einarbeitung
RS-4852 differentiellbis 32 (mehr mit modernen Treibern)bis 10 Mbit/sbis 1200 mSehr robust, große ReichweiteHalbduplex, Richtungsumschaltung, Protokoll (meist Modbus) selbst
USB4 (D+, D−, V_BUS, GND); SuperSpeed zusätzlich zwei PaareHost zu Geräten12 M / 480 M / 5 G< 5 mStromversorgung inklusive, am PC überallAufwendiger Protokollstapel, Host nötig
1-Wire1 (+ GND)viele15 kbit/s (Overdrive 125 k)bis 100 mMinimale Verdrahtung, Speisung über die Datenleitung möglichSehr langsam, striktes Timing

Welcher Bus wofür

Bus DatenrateReichweiteStörfestTeilnehmerWenig PinsEinfach
I²C2 Leitungen, Adressen 2 von 5 1 von 5 1 von 5 4 von 5 5 von 5 4 von 5
StarkZwei Leitungen für viele Sensoren, Adressierung eingebaut, jede Bibliothek kann es. SchwachUnter einem Meter, blockierbar, Adresskonflikte; Pull-ups wollen dimensioniert sein. Nimm es fürSensoren, Displays, EEPROMs auf derselben Platine.
SPI4 Leitungen, CS je Gerät 5 von 5 1 von 5 2 von 5 3 von 5 2 von 5 5 von 5
StarkSehr schnell, Vollduplex, trivial zu implementieren. SchwachEin Chip Select je Teilnehmer, keine Rückmeldung, nur kurze Leitungen. Nimm es fürDisplays, Flash, ADCs, alles mit hoher Datenrate auf dem Board.
UARTTX, RX 3 von 5 3 von 5 2 von 5 1 von 5 4 von 5 5 von 5
StarkDenkbar einfach, überall vorhanden, mit RS-485-Treiber weit. SchwachNur Punkt zu Punkt, Baudraten müssen passen, keine Fehlerprüfung. Nimm es fürDebug-Ausgabe, GPS, Funkmodule, zwei Controller miteinander.
CAN / CAN FDdifferentiell, Arbitrierung 3 von 5 4 von 5 5 von 5 5 von 5 4 von 5 2 von 5
StarkSehr störfest, Arbitrierung ohne Kollisionsverlust, Fehlererkennung in Hardware. SchwachTransceiver und Abschlusswiderstände nötig; das Protokoll darüber entwirft man selbst. Nimm es fürMehrere Baugruppen über Meter: Roboterarm, Fahrzeug, Maschine.
RS-485differentiell, halbduplex 3 von 5 5 von 5 5 von 5 3 von 5 4 von 5 3 von 5
StarkBis 1200 m, robust, günstige Transceiver. SchwachHalbduplex mit Richtungsumschaltung; Protokoll (meist Modbus) selbst. Nimm es fürGebäude, Feld, Sensoren über lange Leitungen.

Punkte sind Einordnungen typischer Vertreter (5 = sehr gut), keine Messwerte. Sie helfen bei der Vorauswahl — entschieden wird am Datenblatt.

I²C — worauf es ankommt

Pull-ups richtig dimensionieren

Beide Leitungen brauchen einen Pull-up nach V_CC — einmal im ganzen Bus, nicht je Modul. 4,7 kΩ bei 100 kHz, 2,2 kΩ oder weniger bei 400 kHz. Zu viele Module mit eigenen Pull-ups ergeben zusammen einen zu kleinen Widerstand und überlasten die Ausgänge.

Adresskonflikte prüfen

7-Bit-Adressen, oft nur teilweise einstellbar. Zwei gleiche Sensoren am selben Bus brauchen eine Adressbrücke oder einen I²C-Multiplexer (TCA9548A). Ein Scan-Programm, das alle 128 Adressen durchprobiert, gehört in jedes Projekt.

Kabellänge begrenzen

Die Buskapazität darf 400 pF nicht überschreiten. Bei längeren Strecken wird die Flanke zu flach — dann hilft ein Buspuffer (P82B715) oder ein Wechsel auf einen anderen Bus.

Blockierten Bus lösen

Wenn ein Teilnehmer mitten in einer Übertragung zurückgesetzt wird, kann er SDA dauerhaft auf Low halten. Standardlösung: neun Takte auf SCL erzeugen und dann eine Stopp-Bedingung senden. Diese Routine gehört in die Initialisierung.

Pegel beachten

3,3-V- und 5-V-Geräte am selben Bus brauchen einen Pegelwandler mit MOSFET (BSS138-Schaltung) — normale Pufferbausteine funktionieren bei Open-Drain nicht.

SPI — worauf es ankommt

PunktDetail
Modus (CPOL/CPHA)Vier Kombinationen. Steht im Datenblatt des Bausteins; falscher Modus liefert scheinbar zufällige Daten. Modus 0 ist am verbreitetsten
BitreihenfolgeMeist MSB zuerst, manche Bausteine LSB
Chip SelectJe Teilnehmer eine eigene Leitung, aktiv Low. Manche Bausteine brauchen zwischen zwei Übertragungen zwingend eine steigende Flanke
TaktfrequenzDatenblattgrenze beachten, bei langen Leitungen deutlich darunter bleiben
Terminierung22–47 Ω in Reihe zum Takt reduzieren Überschwingen erheblich
BezeichnungenMOSI/MISO heißen in neueren Datenblättern SDO/SDI oder COPI/CIPO. Immer prüfen, aus wessen Sicht benannt wurde

CAN — der unterschätzte Bus

Warum er so robust ist
Differentiell übertragen, Arbitrierung ohne Datenverlust (die niedrigere Kennung gewinnt und sendet ohne Wiederholung weiter), CRC-15 in Hardware, automatische Fehlererkennung mit Abschaltung defekter Knoten. Ein CAN-Netz läuft in Umgebungen weiter, in denen I²C längst aufgegeben hat.
Was man braucht
Einen CAN-Controller (in vielen Controllern eingebaut) plus einen Transceiver (MCP2551, TJA1051, SN65HVD230) und 120 Ω an beiden Enden der Leitung — nicht in der Mitte, nicht nur an einem Ende.
CAN FD und CAN XL
CAN FD erhöht die Nutzdaten von 8 auf 64 Byte und die Datenphase auf bis zu 8 Mbit/s. CAN XL geht weiter auf bis zu 2048 Byte und 20 Mbit/s und ist auf Koexistenz mit Ethernet ausgelegt.
Für eigene Projekte
Sobald mehrere Baugruppen über Meter verteilt sind — Roboterarm, Fahrzeug, Maschine — ist CAN die deutlich bessere Wahl als ein verlängerter I²C-Bus. Der Mehraufwand ist ein Transceiver je Knoten.

Fehlersuche an Bussen

Logikanalysator anschließen

Ein 8-Kanal-Gerät für unter 20 € mit PulseView dekodiert I²C, SPI, UART, CAN und 1-Wire fertig. Das ist bei Busproblemen das mit Abstand wirksamste Werkzeug — es zeigt, was wirklich auf der Leitung liegt, statt was man vermutet.

Erst die Pegel messen

Erreicht High wirklich V_CC? Ist die Flanke steil genug? Ein Oszilloskop zeigt zu schwache Pull-ups sofort als abgerundete Anstiege.

Adresse und Modus prüfen

Bei I²C: Scan durchführen. Bei SPI: CPOL/CPHA gegen das Datenblatt prüfen. Zwei Drittel aller Probleme sind hier erledigt.

Masse prüfen

Zwei Baugruppen ohne gemeinsame Masse kommunizieren nicht — oder scheinbar doch, mit sporadischen Fehlern. Die Masseleitung ist keine Nebensache, sie ist der Rückweg jedes Signals.

Auf das Minimum reduzieren

Einen einzigen Teilnehmer, kurze Leitung, niedrigste Geschwindigkeit. Läuft das, schrittweise erweitern — die Stelle, an der es kippt, ist die Antwort.
Quellen

07 / 11RTOS: FreeRTOS & Zephyr

Ein RTOS löst genau ein Problem: mehrere Aufgaben, die unabhängig voneinander warten müssen. Es löst kein Problem mit Rechenleistung, Timing-Genauigkeit oder Codequalität — und es fügt eine Fehlerklasse hinzu, die es vorher nicht gab.

Brauche ich überhaupt eines?

Ja, wenn …

  • mehrere Aufgaben blockierend warten (Netzwerk, Benutzer, langsame Peripherie)
  • Aufgaben sehr unterschiedliche Zeitanforderungen haben
  • ein Netzwerkstack oder USB-Stack eingebunden wird, der ohnehin Tasks erwartet
  • das Projekt über Jahre wächst und mehrere Personen daran arbeiten

Nein, wenn …

  • zwei oder drei periodische Aufgaben genügen — dann reicht ein Zeitscheiben-Hauptprogramm
  • alles interruptgetrieben und kurz ist
  • der RAM knapp ist: jede Task braucht einen eigenen Stack
  • harte Zeitgarantien im Mikrosekundenbereich nötig sind — dafür ist der Interrupt zuständig, nicht der Scheduler

Die Alternative: kooperativer Zeitscheiben-Ablauf

while (1) {
    uint32_t now = tick_ms();
    if (now - t1 >= 1)    { t1 = now; regelung_tick(); }   /* 1 kHz  */
    if (now - t10 >= 10)  { t10 = now; tasten_tick(); }    /* 100 Hz */
    if (now - t100 >= 100){ t100 = now; display_tick(); }  /* 10 Hz  */
    kommunikation_tick();                                  /* so oft es geht */
}

Voraussetzung: Jede _tick()-Funktion kehrt sofort zurück und ist als Zustandsautomat gebaut. Damit lassen sich erstaunlich komplexe Geräte bauen — deterministisch, ohne Stackprobleme, mit einem einzigen Stack. Die Differenzbildung now - t1 ist überlaufsicher, solange beide Werte denselben vorzeichenlosen Typ haben.

Die Bausteine eines RTOS

BausteinWofürFallstrick
TaskEigenständiger Ablauf mit eigenem Stack und PrioritätStackgröße wird fast immer zu klein geschätzt
QueueDaten zwischen Tasks übergeben, blockierendKopiert die Daten — bei großen Strukturen besser Zeiger übergeben
Semaphore (binär)Signalisierung, typisch von ISR an TaskKein Ersatz für einen Mutex: keine Prioritätsvererbung
MutexGegenseitiger Ausschluss beim Zugriff auf eine RessourceNie aus einer ISR verwenden. Prioritätsvererbung aktivieren
Zählender SemaphorBegrenzte Anzahl gleichartiger Ressourcen
Event Group / FlagsAuf mehrere Bedingungen gleichzeitig wartenSparsamer als mehrere Semaphore
Software-TimerVerzögerte oder periodische AufrufeLäuft in einer eigenen Task — deren Priorität und Stack beachten
Task NotificationDirekte, sehr schnelle Signalisierung an eine TaskBei FreeRTOS deutlich schneller und sparsamer als ein Semaphor

FreeRTOS und Zephyr

MerkmalFreeRTOSZephyr
UmfangScheduler plus Synchronisierung, sonst nichtsVollständiges Betriebssystem mit Treibermodell, Netzwerkstack, Dateisystem, Bluetooth
Größe5–10 kB Flashab ~10 kB, realistisch 50–200 kB mit Konnektivität
KonfigurationEine Header-DateiKconfig plus Devicetree
Hardware-Abstraktionkeine — man nutzt das Hersteller-SDKeinheitliche Treiber-APIs über alle Boards
Einstiegan einem Nachmittagmehrere Tage bis Wochen
Wann sinnvollMan hat bereits ein SDK und braucht nur den SchedulerMehrere Plattformen, Funk, langfristige Pflege, professionelles Umfeld

FreeRTOS ist in ESP-IDF fest eingebaut — wer mit ESP32 arbeitet, benutzt es ohnehin. Das nRF Connect SDK baut auf Zephyr auf. Beide sind quelloffen; Zephyr steht unter Apache 2.0, FreeRTOS unter MIT.

Stackgrößen bestimmen

Großzügig anfangen

1–2 kB je Task als Startwert. Zu klein ist der häufigste Grund für unerklärliche Abstürze.

Muster hineinschreiben

FreeRTOS füllt neue Stacks mit einem Füllwert. uxTaskGetStackHighWaterMark() liefert, wie viel nie benutzt wurde.

Nach realer Last messen

Nicht im Leerlauf, sondern unter Volllast mit allen Fehlerpfaden — gerade Fehlerbehandlung mit printf braucht überraschend viel.

Reserve lassen

Auf den gemessenen Spitzenwert 30–50 % aufschlagen. Bei FreeRTOS auf Cortex-M laufen ISRs auf einem eigenen Stack (MSP) — dessen Größe getrennt festlegen und prüfen.

Überlauf erkennbar machen

configCHECK_FOR_STACK_OVERFLOW = 2 und einen Hook implementieren, der den Task-Namen ausgibt. Ohne das sucht man den Fehler blind. Wo eine MPU vorhanden ist, schützt sie den Stack in Hardware.
Vorsicht mit printf und Fließkomma in Tasks: Eine printf-Implementierung mit Fließkomma-Unterstützung braucht schnell 1–2 kB Stack. Wer in fünf Tasks je 512 Byte veranschlagt hat, bekommt einen Absturz, der erst unter bestimmten Bedingungen auftritt. Für Diagnoseausgaben lohnt sich eine schlanke Alternative wie tinyprintf oder ein binäres Protokoll.

Aufteilung, die sich bewährt hat

TaskPrioritätAufgabe
RegelunghochFeste Periode über einen Timer, kurze Rechenzeit, keine blockierenden Aufrufe
SensorikmittelLiest Sensoren, füllt einen gemeinsamen Zustand, filtert
KommunikationmittelVerarbeitet Protokolle, blockiert an Queues
Bedienung / AnzeigeniedrigDarf jederzeit unterbrochen werden
Diagnose / ProtokollniedrigsteSchreibt in den Flash, wenn sonst nichts zu tun ist
IdleLegt den Controller schlafen (Tickless Idle) — hier entsteht die Ersparnis bei Batteriebetrieb
Quellen

08 / 11Speicherbild, Linker & Bootloader

Zwischen dem Einschalten und der ersten Zeile in main() passiert mehr, als die meisten vermuten: Vektortabelle, Takt, Kopieren initialisierter Variablen, Nullen des BSS-Bereichs, Konstruktoren. Wer diesen Weg kennt, versteht Linker-Fehler, Bootloader und Updates ohne Zauberei.

Der Speicher beim Start

FLASH — bleibt erhalten RAM — beim Reset undefiniert .isr_vector .text — Code .rodata — const Anfangswerte .data .data — kopiert .bss — genullt Heap frei Stack Startup kopiert niedrig hoch wachsen aufeinander zu
SektionInhaltLiegt imBeim Start
.isr_vectorVektortabelle: Anfangs-Stackzeiger und Adressen aller HandlerFlash, ganz vornDer Prozessor liest daraus die ersten beiden Worte
.textProgrammcodeFlashWird direkt von dort ausgeführt
.rodataKonstanten, Zeichenketten, TabellenFlashBleibt dort
.dataInitialisierte globale VariablenWerte im Flash, Variablen im RAMStartup-Code kopiert vom Flash ins RAM
.bssGlobale Variablen ohne AnfangswertRAMStartup-Code nullt den Bereich
HeapDynamisch angeforderter SpeicherRAMWächst zu höheren Adressen
StackLokale Variablen, Rücksprungadressen, ISR-KontextRAMWächst zu niedrigeren Adressen
Stack und Heap wachsen aufeinander zu. Treffen sie sich, überschreibt der eine den anderen — ohne Fehlermeldung, mit beliebig verzögerten Folgen. Deshalb gilt: kein malloc nach der Initialisierung, besser gar keins. Statische Puffer fester Größe sind vorhersagbar, und der Linker meldet schon beim Bauen, wenn der Speicher nicht reicht.

Der Startup-Ablauf

Reset

Der Prozessor lädt den Stackzeiger aus dem ersten Wort der Vektortabelle und springt an die Adresse im zweiten (Reset-Handler).

Takt einstellen

Interner Oszillator, PLL, externer Quarz. Bei ESP32 und ähnlichen SoCs erledigt das ein ROM-Bootloader vorab.

.data kopieren, .bss nullen

Zwei Schleifen im Startup-Code. Ab hier stimmen die Anfangswerte globaler Variablen.

Laufzeitumgebung starten

Bei C++ die Konstruktoren globaler Objekte, bei C die newlib-Initialisierung.

main() aufrufen

Ab hier läuft der eigene Code. main() darf nie zurückkehren — der Startup-Code endet in einer Endlosschleife.

Deshalb ist eine globale Variable mit Anfangswert „teuer“: Ihr Wert belegt Flash und RAM. Eine große Nachschlagetabelle gehört nach const, damit sie in .rodata bleibt und gar nicht erst kopiert wird.

Das Linker-Skript

MEMORY
Beschreibt die physischen Bereiche: Flash-Anfang und -Größe, RAM-Anfang und -Größe, gegebenenfalls weitere wie CCM-RAM oder externes SDRAM.
SECTIONS
Legt fest, welche Sektion in welchen Bereich kommt und welche Symbole der Startup-Code dafür braucht — Anfang und Ende von .data und .bss, Obergrenze des Stacks.
Anpassen, wenn …Was zu tun ist
ein Bootloader davor liegtDie Anwendung beginnt mit Versatz statt bei Flash-Anfang, und die Vektortabelle muss zur Laufzeit umgesetzt werden: bei Cortex-M3/M4/M7/M33 über das VTOR-Register. Cortex-M0 hat kein VTOR — dort spiegelt man die Tabelle ins RAM oder nutzt ein herstellereigenes Remap-Register
ein Bereich den Reset überleben sollEtwa für einen Absturzbericht: eigene Sektion anlegen, die der Startup-Code weder kopiert noch nullt — in .bss wird sie gelöscht
Code aus dem RAM laufen sollZeitkritische Funktionen sind dort schneller; beim Schreiben in den Flash darf ohnehin kein Code aus dem Flash ausgeführt werden

Bootloader und Updates

VariantePrinzipBewertung
ROM-BootloaderFest im Chip, meist über UART, USB-DFU oder I²C erreichbarKostet nichts, ist nicht anpassbar, aber als Rettungsanker immer da
Eigener Bootloader, ein BereichLöscht die Anwendung und schreibt die neueEinfach — ein Abbruch macht das Gerät unbrauchbar
A/B-VerfahrenZwei vollständige Anwendungsbereiche im WechselDer sichere Standard: Bei Fehlschlag wird der alte Stand wieder aktiviert. Kostet doppelten Flash
Externer Flash als ZwischenspeicherUpdate wird dorthin geladen und dann kopiertSpart internen Flash, braucht einen zusätzlichen Baustein
MCUbootFertiger, quelloffener Bootloader mit Signaturprüfung und A/BIn Zephyr und im nRF Connect SDK integriert; für ernsthafte Projekte die naheliegende Wahl

Ein Update, das nicht scheitert

Vollständig übertragen, dann prüfen

Erst das ganze Abbild in den inaktiven Bereich schreiben, danach Prüfsumme und Signatur verifizieren. Nie während der Übertragung aktivieren.

Umschalten atomar gestalten

Ein einziges Flag oder ein Metadatenblock entscheidet, welcher Bereich startet. Dieser Schreibvorgang muss so klein sein, dass er nicht halb passieren kann.

Bestätigung durch die Anwendung

Die neue Firmware bestätigt sich nach dem ersten erfolgreichen Start selbst. Bleibt die Bestätigung aus, fällt der Bootloader beim nächsten Reset auf den alten Stand zurück.

Rollback-Schutz

Ein Zähler in geschütztem Speicher verhindert, dass eine alte Version mit bekannter Lücke eingespielt wird → Security & Kryptografie.

Versorgung absichern

Beim Schreiben in den Flash braucht der Controller stabile Spannung: Brownout-Erkennung aktivieren, bei Batteriebetrieb vorher den Ladezustand prüfen → Stromsparen.
Quellen

09 / 11Debuggen & Messen

Vier Werkzeuge decken praktisch alles ab: Debug-Adapter für Breakpoints und Speicheransicht, serielle Diagnoseausgabe, Logikanalysator für Busse, GPIO-Pin am Oszilloskop für Zeitmessungen. Das allgemeine Vorgehen — halbieren, messen, eine Sache auf einmal — steht unter Lernpfad → Debugging & Fehlersuche.

Debug-Schnittstellen

SchnittstellePinsLeitungenKann
SWD2SWDIO, SWCLK (+ GND, möglichst nRESET)Anhalten, Einzelschritt, Speicher lesen und schreiben, Breakpoints, Flashen — der Standard bei Cortex-M
JTAG4TCK, TMS, TDI, TDO (+ GND, optional nTRST)Wie SWD, zusätzlich Boundary Scan und Verkettung mehrerer Bausteine
SWO / ITM1SWO, zusätzlich zu SWDDiagnoseausgabe mit sehr geringer Rückwirkung, deutlich schneller als UART
ETM / Trace5TRACECLK + bis zu 4 DatenleitungenLückenloser Befehlsverlauf. Nur bei größeren Kernen und mit teurerem Adapter
RTT (SEGGER)0keine zusätzliche — läuft über SWD/JTAGZwei Ringpuffer im RAM, im laufenden Betrieb vom Debugger gelesen. Schnell, kaum Rückwirkung
Vier Pins reservieren, immer: SWDIO, SWCLK, GND und Reset auf einer Stiftleiste oder als Tag-Connect-Pads. Der Platz ist minimal, der Nutzen enorm — am fertigen Gerät lassen sie sich nicht nachrüsten. Wer die Pins anderweitig belegt, sollte wissen, dass viele Controller SWD nach dem Umkonfigurieren erst nach einem Reset wieder freigeben.

Hard Fault analysieren

Ein Hard Fault ist kein Zufall, sondern eine präzise Fehlermeldung — man muss sie nur lesen.

1 · Fault-Register auswerten

Bei Cortex-M sagt das CFSR, welche Art von Fehler vorlag; MMFAR und BFAR enthalten die betroffene Adresse.

2 · Gesicherten Kontext finden

Der Prozessor legt beim Fault acht Register auf den Stack (R0–R3, R12, LR, PC, xPSR). Der PC zeigt auf den auslösenden Befehl.

3 · Adresse in Code übersetzen

arm-none-eabi-addr2line -e firmware.elf 0x0800xxxx liefert Datei und Zeile.

4 · Handler nachrüsten

Ein Hard-Fault-Handler, der die Register über die serielle Schnittstelle ausgibt oder in einen reset-festen Bereich schreibt, macht auch Fehler im Feld auswertbar → Speicherbild.
Fault-TypHäufige Ursache
Bus Fault (PRECISERR)Zugriff auf eine ungültige Adresse — Nullzeiger oder Peripherie ohne aktivierten Takt
Usage Fault (UNALIGNED)32-Bit-Zugriff auf eine ungerade Adresse, oft durch Zeiger-Umdeutung auf einen Bytepuffer
Usage Fault (INVSTATE)Sprung an eine Adresse ohne gesetztes Thumb-Bit — typischerweise ein kaputter Funktionszeiger
Memory Manage FaultMPU-Verletzung, häufig Stacküberlauf bei geschütztem Stack
Escalated Hard FaultEin Fault innerhalb eines Fault-Handlers oder ein abgeschalteter Handler

Logging, das im Betrieb funktioniert

RegelBegründung
Log-Stufen ERROR/WARN/INFO/DEBUGZur Übersetzungszeit abschaltbar, damit der Auslieferungsstand weder Flash noch Rechenzeit verbraucht
Zeitstempel in jeder ZeileMillisekunden seit dem Start. Ohne Zeitbezug lassen sich Abläufe nicht rekonstruieren
Nicht blockierend ausgebenBlockierendes printf über UART mit 115 200 Bd braucht für 80 Zeichen rund 7 ms — genug, um einen Regelkreis zu ruinieren. Ringpuffer plus DMA oder RTT verwenden
Nichts aus der ISR ausgebenFormatieren und Warten gehören nicht in einen Interrupt: Ereignis in den Puffer legen, im Hauptprogramm ausgeben → Interrupts
Absturzberichte speichernFault-Register und die letzten Zeilen in einen reset-festen Bereich schreiben und beim nächsten Start ausgeben. Bei Fehlern alle drei Tage die einzige Chance

Messen statt vermuten

FrageWerkzeugVorgehen
Wie lange läuft diese Funktion?GPIO-Pin plus OszilloskopPin am Anfang setzen, am Ende löschen. Zeigt auch Jitter und Verschachtelung
Wie oft löst der Interrupt aus?Zähler plus AusgabeHäufiger als erwartet ist der typische Befund
Was liegt wirklich auf dem Bus?Logikanalysator mit PulseViewDekodiert I²C, SPI, UART, CAN direkt → Bussysteme
Warum bricht die Spannung ein?Oszilloskop, AC-gekoppeltAm Versorgungspin des Controllers messen, nicht am Netzteil
Wo bleibt der Speicher?arm-none-eabi-size, nm --size-sort, Map-DateiZeigt die größten Symbole und Objekte → Toolchain
Wie viel Stack ist übrig?Füllmuster prüfenBeim Start Muster schreiben, im Betrieb zählen, wie viel davon überschrieben wurde
Was macht der Compiler daraus?objdump -d oder Compiler ExplorerZeigt sofort, ob eine Optimierung greift oder eine Variable wegoptimiert wurde

Sporadische Fehler in Firmware

Reset-Ursache auslesen

Fast jeder Controller speichert sie in einem Register: Watchdog, Brownout, Software-Reset, Pin. Diese Information beim Start ausgeben — sie beantwortet die halbe Frage.

Bedingung erzwingen

Ein Fehler, der einmal pro Woche auftritt, ist nicht zu debuggen: Last erhöhen, Temperatur ändern, Kommunikation fluten, Takt verstellen.

Nebenläufigkeit verdächtigen

Sporadisch heißt in Firmware meist: gemeinsame Variable ohne Schutz, fehlendes volatile, zu kleiner ISR-Stack, Prioritätsumkehr → Interrupts & Nebenläufigkeit.

Hardware ausschließen

Versorgung am Oszilloskop ansehen, Verbindungen nachmessen, zweites Exemplar probieren. Erstaunlich viele „Softwarefehler“ sind kalte Lötstellen oder Spannungseinbrüche.
Quellen

10 / 11Stromsparen & Batteriebetrieb

Bei Batteriebetrieb entscheidet nicht der aktive Strom, sondern der Ruhestrom. Ein Gerät, das 99,9 % der Zeit schläft, wird vom Schlafstrom bestimmt — 10 µA statt 1 mA bedeuten Faktor 100 in der Laufzeit. Die erste Frage lautet deshalb immer: Wie oft muss es überhaupt wach sein?

Die Betriebsarten

ModusTypischer StromWas bleibt anAufwachzeit
Aktiv (Run)2–100 mAalles
Sleep / WFI0,5–10 mAPeripherie und RAM, nur die CPU stehtwenige Takte
Stop / Deep Sleep2–50 µARAM erhalten, wenige Aufwachquellen10 µs – 1 ms
Standby0,3–3 µAnur RTC und wenige Pins; RAM verloren außer im Backup-Bereichwie ein Neustart
Shutdownunter 100 nAfast nichtsNeustart
Datenblattwerte gelten unter Laborbedingungen. Dazu kommen in der Praxis: Ruhestrom des Reglers, Pull-Widerstände, LEDs, Sensoren im Dauerbetrieb und Leckströme über falsch konfigurierte Pins. Ein Controller mit 2 µA Standby nützt nichts, wenn der Regler daneben 5 mA zieht.

Wo der Strom tatsächlich hingeht

VerbraucherTypischer BeitragGegenmittel
SpannungsreglerAMS1117 rund 5 mA · MCP1700 1,6 µA · TPS7A02 25 nARuhestrom I_q im Datenblatt zum Auswahlkriterium machen. Bei sehr kleiner Last ist ein LDO oft sparsamer als ein Schaltregler → Spannungsversorgung
Pull-Widerstände4,7 kΩ an 3,3 V dauerhaft Low ≈ 700 µAIm Schlaf abschalten, hochohmiger auslegen oder den Ruhepegel umdrehen
Floatende Eingängeeinige µA je Pin, stark streuendEin offener CMOS-Eingang lässt beide Transistoren teilweise leiten: ungenutzte Pins auf Analog oder mit Pull konfigurieren
Sensorenim Betrieb einige hundert µA, im Schlaf unter 1 µASchlafmodus aktiv setzen — oder die Versorgung über einen MOSFET ganz abschalten, dann entfällt auch der Leckstrom
FunkWLAN-Sendespitze 100–350 mA, BLE 5–15 mANicht die Spitze zählt, sondern die Gesamtzeit im Funkbetrieb. Ein fehlgeschlagener Verbindungsaufbau kostet mehr als hundert erfolgreiche Sendungen
Status-LED5 mA dauerhaftMehr als alles andere zusammen: nur blitzen lassen — 10 ms alle 5 s sind sichtbar und kosten fast nichts
Ungenutzte Peripherieje Block einige zehn µA bis mATakt der nicht benutzten Baugruppen abschalten; ADC, Komparator und Debug-Einheit werden gern vergessen

Laufzeit rechnen

Batterielaufzeit abschätzen

I_mittel = (I_aktiv · t_aktiv + I_schlaf · t_schlaf) / (t_aktiv + t_schlaf) Beispiel: 20 mA für 50 ms alle 60 s, dazwischen 5 µA → I_mittel = (20 · 0,05 + 0,005 · 59,95)/60 = 21,7 µA. Aus einer 2000-mAh-Zelle ergibt das rechnerisch über zehn Jahre — begrenzt dann durch die Selbstentladung, nicht durch die Schaltung.

Messen, nicht schätzen

MethodeBereichBewertung
Multimeter in ReihemAFür Mikroampere zu ungenau; der Bürdenwiderstand verfälscht und kann beim Aufwachen sogar einen Reset auslösen
Shunt plus OszilloskopµA bis AZeigt den zeitlichen Verlauf — genau das, was man braucht. 1 Ω und ein Differenztastkopf reichen oft
Spezialgerät (Nordic PPK2, Joulescope, Otii)nA bis A, dynamischDer richtige Weg: löst nA bis hunderte mA in einem Durchgang auf und integriert die Energie automatisch
Langzeittest mit KondensatorMittelwerteBekannte Kapazität laden, Spannungsabfall über Stunden messen. Billig und erstaunlich brauchbar
Strommesszangeab einigen mAFür die kleinen Ströme im Schlafbetrieb unbrauchbar
Der typische Befund beim ersten Messen: Das Gerät wacht viel häufiger auf als geplant — ein nicht abgeschalteter Timer, ein ständig auslösender Sensor-Interrupt oder der RTOS-Tick, der den Controller tausendmal pro Sekunde weckt. Genau dagegen gibt es configUSE_TICKLESS_IDLERTOS.

Batterietypen

TypSpannungKapazitätSpitzenstromSelbstentladungAnmerkung
CR2032 (Lithium-Knopfzelle)3,0 V210–240 mAhnur wenige mArund 1 % pro JahrBei Funkspitzen bricht die Spannung ein — Pufferkondensator (10–100 µF) zwingend
AA Alkaline1,5 V2000–2800 mAheinige hundert mA2–3 % pro JahrSpannung fällt kontinuierlich; Aufwärtswandler nötig
AA Lithium (LiFeS₂)1,5 V3000 mAhhochunter 1 % pro JahrKältefest bis −40 °C, erste Wahl für Langzeitgeräte
Li-Ion 186503,0–4,2 V2500–3500 mAhmehrere A2–3 % pro MonatBraucht Schutzschaltung und Ladeelektronik; nicht unter 0 °C laden
LiFePO₄2,5–3,6 Vje nach Bauformhochwenige % pro MonatSehr viele Zyklen, thermisch gutmütig, geringere Energiedichte
NiMH (LSD-Typ)1,2 V1900–2500 mAhhochklassisch bis 20 % pro Monat, LSD rund 1 %Nur Low-Self-Discharge-Zellen taugen für Geräte, die lange liegen
Supercap2,5–5,5 Vgeringsehr hochTage bis WochenFür Überbrückung und Stromspitzen, nicht für Dauerbetrieb
Bei zweistelligen Laufzeiten in Jahren rechnet man gegen die Chemie, nicht gegen die Schaltung: Eine Zelle mit 1 % Selbstentladung pro Jahr ist nach zehn Jahren rechnerisch bei 90 % — jede weitere Optimierung des Schlafstroms bringt dann nichts mehr. Ebenso wichtig: Kapazitätsangaben gelten für kleine Entladeströme und Raumtemperatur; bei Kälte und Stromspitzen bleibt deutlich weniger übrig.
Quellen
  • Nordic Semiconductor, Power Profiler Kit II — Messgerät und lesenswerte Anwendungshinweise.
  • Texas Instruments, Achieving Ultra-Low Power in MCU Applications und die MSP430-Anwendungsberichte.
  • STMicroelectronics AN4991 und AN5001 — Low-Power-Modi bei STM32.
  • Datenblätter von Energizer, Panasonic und Varta mit Entladekurven und Innenwiderstand über Temperatur.
  • Regler, Wandler und ihre Ruheströme: Elektrotechnik → Spannungsversorgung.

11 / 11C-Praxis & Fallstricke

C ist in Embedded-Systemen deshalb Standard, weil es nichts versteckt. Genau das ist auch das Problem: Der Compiler darf unter bestimmten Annahmen alles wegoptimieren, was nach diesen Annahmen unmöglich ist. Wer die Annahmen kennt, schreibt Code, der auch mit -O2 noch tut, was er soll.

Die Schlüsselwörter, die zählen

SchlüsselwortBedeutungWann verwenden
volatileJeder Zugriff muss wirklich stattfindenHardwareregister, von ISR geänderte Variablen. Nicht als Ersatz für Atomarität
constWird nicht verändertNachschlagetabellen — landet dann in .rodata statt im RAM
static (Dateiebene)Nur in dieser Übersetzungseinheit sichtbarStandard für alles, was nicht ausdrücklich exportiert wird. Erlaubt dem Compiler bessere Optimierung
static (in Funktion)Behält den Wert zwischen Aufrufen, liegt nicht auf dem StackDMA-Puffer, Zustandsvariablen; Vorsicht bei Wiedereintritt
inline / static inlineVorschlag zum EinbettenKurze Zugriffsfunktionen in Headern
restrictZusicherung, dass zwei Zeiger nicht auf dasselbe zeigenIn Rechenschleifen — kann erhebliche Beschleunigung bringen
_Atomic / stdatomic.hGarantiert unteilbare OperationenZähler, die von ISR und Hauptprogramm verändert werden

Feste Breiten und Typen

Immer stdint.h
uint8_t, int16_t, uint32_t statt char, short, int. Die Breite von int ist plattformabhängig — auf AVR 16 Bit, auf ARM 32 Bit. Code, der davon abhängt, bricht beim Portieren.
bool aus stdbool.h
Klarer als int mit 0 und 1, und der Compiler kann besser prüfen.
size_t für Größen und Indizes
Vorzeichenlos und passend zur Plattform. Vergleiche mit vorzeichenbehafteten Typen vermeiden.
Aufzählungen für Zustände
typedef enum statt magischer Zahlen. Der Compiler warnt bei switch, wenn ein Fall fehlt.

Hardwareregister ansprechen

/* Falsch: der Compiler darf den Zugriff wegoptimieren */
uint32_t *reg = (uint32_t *)0x40020014;

/* Richtig: volatile, damit jeder Zugriff wirklich erfolgt */
#define GPIOA_ODR (*(volatile uint32_t *)0x40020014u)

/* Bit setzen, ohne die anderen zu stören */
GPIOA_ODR |=  (1u << 5);
GPIOA_ODR &= ~(1u << 5);

/* Bitfeld schreiben: erst löschen, dann setzen */
#define MODE_MASK  (3u << 10)
#define MODE_OUT   (1u << 10)
GPIOA_MODER = (GPIOA_MODER & ~MODE_MASK) | MODE_OUT;
Read-Modify-Write ist nicht atomar. reg |= bit; besteht aus drei Schritten. Unterbricht ein Interrupt dazwischen und ändert dasselbe Register, geht die Änderung verloren. Viele Controller bieten dafür Set/Reset-Register (BSRR bei STM32) oder Bit-Banding (nur Cortex-M3/M4) — beides erledigt die Operation in einem einzigen Schreibzugriff und ist damit unterbrechungsfest.

Fallen, die immer wieder zuschlagen

FalleWas passiertRichtig
Zeiger auf lokale Variable zurückgebenDer Speicher ist nach dem Rücksprung ungültigstatic, Puffer vom Aufrufer, oder Wert kopieren
strcpy ohne LängenprüfungPufferüberlaufsnprintf mit Größenangabe; strncpy terminiert nicht garantiert
sizeof auf einen ZeigerLiefert die Zeigergröße, nicht die ArraygrößeLänge separat übergeben; sizeof(arr)/sizeof(arr[0]) nur im Gültigkeitsbereich des Arrays
Ganzzahldivision mit Rundungserwartung7/2 ist 3Vorher multiplizieren, Festkomma nutzen oder bewusst runden
Vergleich signed mit unsigned−1 wird zu einer riesigen positiven ZahlGleiche Vorzeichenklasse; -Wsign-compare aktivieren
Zeitvergleich mit >Bricht beim Überlauf des Millisekundenzählers(uint32_t)(now - start) >= dauer
Fehlendes break im switchDurchfallen in den nächsten FallAbsicht mit /* fallthrough */ kennzeichnen; -Wimplicit-fallthrough aktivieren
Zeiger-Umdeutung auf PufferNicht ausgerichteter Zugriff oder Verletzung der Aliasing-Regelnmemcpy in eine passende Struktur — moderne Compiler optimieren das weg
Rückgabewert ignoriertFehler bleiben unbemerktJeden Rückgabewert prüfen oder bewusst mit (void) verwerfen
malloc im laufenden BetriebFragmentierung, unbestimmte Laufzeit, irgendwann kein Speicher mehrStatische Puffer oder ein Pool fester Blockgröße

Muster, die sich bewährt haben

Modul mit klarer Schnittstelle
Eine .h-Datei mit den öffentlichen Funktionen, alles andere static in der .c-Datei. Kein Zugriff auf interne Variablen von außen.
Init, Tick, Handler
Jedes Modul bekommt dieselbe Struktur: xyz_init(), xyz_tick() (zyklisch, kehrt sofort zurück) und gegebenenfalls einen ISR-Anteil. Sehr leicht zu verstehen und zu testen.
Fehlercodes statt stiller Fehler
Ein einheitlicher Aufzählungstyp als Rückgabewert. Ein zentraler Fehlerzähler mit letztem Code erleichtert die Diagnose erheblich.
Hardware hinter einer dünnen Schicht
Nicht wegen Portabilität, sondern damit sich Logik ohne Hardware testen lässt — siehe Digitaltechnik → Software.
Konfiguration an einer Stelle
Pinbelegung, Zeitkonstanten und Grenzwerte in einer config.h. Spart Suchen und macht Hardwarevarianten beherrschbar.
Statische Zusicherungen
_Static_assert(sizeof(frame_t) == 16, "Rahmengröße"); — prüft Annahmen zur Übersetzungszeit statt im Feld.

C++ auf dem Mikrocontroller

Nützlich

  • constexpr — Berechnungen zur Übersetzungszeit, kostet zur Laufzeit nichts
  • Aufzählungsklassen (enum class) — typsicher
  • Referenzen statt Zeiger, wo kein Nullwert vorkommen kann
  • Templates für typsichere Puffer und Einheiten
  • RAII für Sperren und Ressourcen

Vorsicht

  • Ausnahmen (exceptions) — meist abgeschaltet, kosten Flash und Determinismus
  • RTTI und dynamic_cast — dasselbe
  • std::string, std::vector — dynamische Speicheranforderung
  • Virtuelle Funktionen — vtable im RAM, indirekter Aufruf; in engen ISRs vermeiden
  • iostreams — sehr groß, für Diagnoseausgaben ungeeignet
Quellen
  • Kernighan & Ritchie, The C Programming Language — immer noch die knappste Referenz.
  • MISRA C:2023 — Regelwerk für sicherheitskritische Software; viele Regeln lohnen sich generell.
  • CERT C Coding Standard (SEI) — frei zugänglich, mit Begründungen und Beispielen.
  • Michael Barr, Embedded C Coding Standard — kostenlos als PDF, kurz und praxisnah.
  • Dan Saks, Vorträge zu volatile, const und C++ in eingebetteten Systemen.