Embedded C Grundlagen: Wichtige Fragen zur Mikrocontroller-Programmierung
Mikrocontroller-Programmierer müssen die Besonderheiten von C unter knappen Ressourcen meistern. Hier ist eine kuratierte Liste praktischer Fragen zu Kompilierung, Speicher und Optimierung. Diese Themen tauchen häufig in Vorstellungsgesprächen bei Embedded-Unternehmen auf.
Wichtige Keywords und ihre Verwendung
Static beschränkt die Sichtbarkeit einer Variable auf die aktuelle Datei oder Funktion und erhält Werte zwischen Aufrufen bei lokalen statischen Variablen. Volatile verhindert, dass der Compiler Variablen optimiert, die extern verändert werden (Register, Speicher, Interrupts).
Beispiel für problematischen Code:
int square(volatile int *ptr) {
return *ptr * *ptr;
}
Hier gilt volatile nur für die erste Dereferenzierung; die zweite könnte optimiert wegfallen. Lösung: (ptr) (*ptr).
Const volatile eignet sich für schreibgeschützte externe Daten wie Statusregister. Register schlägt vor, eine Variable in einem CPU-Register zu speichern (veraltet seit C11). Restrict signalisiert dem Compiler, dass Pointer nicht aliasen, und ermöglicht Vektorisierung.
Speicherverwaltung und Strukturen
Globale const-Variablen landen im Flash-Speicher. Für ein 32-KB-Array mit Konstanten 0xFF nutzen Sie eine Linker-Skript-Sektion mit fill(0xFF).
Bitoperationen: Bit prüfen mit n & (1 << k), löschen mit n & ~(1 << k). Float-Vergleich: fabs(a - b) < EPSILON.
Strukturgrößen hängen von der Ausrichtung ab. Für
struct Foo {
int iiii;
char c;
};
beträgt die Größe 8 Bytes (4 Bytes Polsterung). GCC-Packing: __attribute__((packed)).
Beispiele für String-Deklarationen:
char str5[]={'s','t','r','i','n','g'}; // Größe:7 Länge:6
char *str1="string"; // Größe:4 Länge:6 (Pointer)
char str3[]="string"; // Größe:7 Länge:6
char str4[10]="string"; // Größe:10 Länge:6
Build-Systeme und Toolchains
Build-Systeme automatisieren Kompilierung, Verknüpfung und Abhängigkeiten. Make reagiert auf Dateiänderungen, Ninja ist dank Parallelität und Einfachheit schneller.
VPATH in GNU Make sucht Quellen in angegebenen Verzeichnissen.
Code-Pipeline: .c → .i (Präprozessor) → .s (Assembler) → .o → .elf → .bin/.hex.
GCC-Warnung deaktivieren: #pragma GCC diagnostic ignored "-Wrestrict".
Firmware verkleinern: -ffunction-sections -fdata-sections, ld --gc-sections und -Os-Optimierung.
Build-Artefakte
.o— Objektdateien.elf— Ausführbare Datei mit Symbolen.hex/.bin— zum Flashen (hex enthält Adressen)
Binutils: objdump (Disassembler), objcopy (Konverter), readelf (ELF-Analyse).
Build überprüfen: make V=1 oder -n (Trockenlauf).
Datenstrukturen und Algorithmen
Ringpuffer — Ringarray mit Kopf-/Schwanzindizes, im Gegensatz zu FIFO, das Elemente verschiebt.
Linked-List-Entfernung: prev->next = node->next (braucht prev) oder node->next->prev = node->prev (doppelt verkettet).
Polymorphie in C: Funktionszeiger in Strukturen (vtable-ähnlich).
Ohne if/switch:
void print_num(int n) {
const char *nums[] = {"","Eins","Zwei"};
printf("%s", nums[n]);
}
do-while(0) für Makros — eine Iteration mit mehreren Anweisungen.
Interrupts und RTOS
Interrupt — Hardwaremechanismus, der bei einem Ereignis zur ISR springt. Vektortabelle enthält ISR-Adressen. Cortex-M4: 12–16 Zyklen für Eintritt (Kontextspeicherung).
Ablauf: Hardware speichert PC/LR, lädt Vektor, springt zur ISR, kehrt via POP zurück.
Reentrant-Funktion — sicher für gleichzeitige Aufrufe (keine statischen Variablen/lokalen ohne Synchronisation).
In RTOS:
- Thread — Ausführungseinheit mit eigenem Stack
- Mutex — gegenseitiger Ausschluss
- Semaphore — Ereigniszähler
- Spinlock — busy-wait-Sperre
Code-Problem: Deadlock durch Lock-Reihenfolge (TaskA: ma→mb, TaskB: mb→mc→ma).
Priority Inversion: Hochprioritärer Task wartet auf Niedrigprioritätigen. Lösung: Priority Inheritance.
Atomare Operationen
Beispiel: __atomic_exchange_n(&x, &y, __ATOMIC_SEQ_CST).
Bit-Banding — direkter Bitzugriff ohne Masken (Cortex-M3/4).
Toolchain und Debugging
ABI — binäre Schnittstelle (Aufrufkonvention).
Segmente: .text (Flash-Code), .data (RAM initialisiert), .bss (RAM null), .rodata (Flash-Konstanten).
Präprozessor ausdehnen: gcc -E file.c > expanded.i.
Wichtige Dokumente: Datenblatt, Referenzhandbuch, Programmierhandbuch, Schaltpläne.
Zusammenfassung der Schlüsselpunkte
- Volatile ist essenziell für Peripherieregister
- Packed-Strukturen sparen RAM, aber verlangsamen Zugriffe
- RTOS erfordert thread-sicheren Code und Schutz kritischer Abschnitte
- Build-Optimierung: Sections + gc-sections
- Interrupts nutzen den Stack des aktuellen Threads
Für 0xFF-Polsterung in Strukturen: Linker-Skript mit fill oder __attribute__((packed)) + manuelle Füllung.
— Editorial Team
Noch keine Kommentare.