Einleitung: Die Ära der Hardware wechselt die Sprache
\n\nWenn Sie jemals eine Firmware für einen Mikrocontroller in C geschrieben haben, kennen Sie dieses Gefühl. Das Gefühl, wenn Sie nach drei Stunden Debugging einen Hänger finden – der Zeiger ist ins Nirwana geflogen, und der Watchdog hat alles ertragen. Embedded-Entwicklung war lange Zeit das Terrain von C und Assembler. Aber in den letzten Jahren ist ein neuer Spieler auf die Bühne getreten – Rust. Und es ist nicht nur "eine weitere Sprache", sondern ein echter Durchbruch für eingebettete Systeme.
\n\nIn diesem Artikel werden wir analysieren, warum Embedded-Entwicklung mit Rust kein Hype, sondern eine Notwendigkeit ist. Wir sehen uns reale Codebeispiele an, vergleichen mit C und verstehen, wo man anfängt. Wenn Sie für STM32, ESP32 oder ARM Cortex-M programmieren, sind Sie hier genau richtig.
\n\nWarum Rust für Embedded die ideale Verbindung ist
\n\nEingebettete Systeme sind eine Welt der Einschränkungen. Wenig Speicher, kein Betriebssystem, kein Heap, keine Ausnahmen. Ein Compiler-Fehler ist gut, ein Laufzeitfehler ist schlecht. Rust wurde genau für solche Bedingungen entwickelt.
\n\nSpeichersicherheit ohne Garbage Collector
\n\nDer größte Schmerz eines Embedded-Entwicklers in C ist Use-after-free, doppeltes Freigeben von Speicher und Pufferüberläufe. Rust löst dies auf Typebene. Das Ownership-Modell und das Borrowing funktionieren sogar in einer no_std-Umgebung – ohne Allokator, ohne Betriebssystem, ohne GC.
Stellen Sie sich vor, Sie schreiben einen Treiber für eine LED. In C würden Sie schreiben:
\n\n// C code\nvoid led_on(GPIO_TypeDef *gpio, uint16_t pin) {\n gpio->BSRR = pin;\n}\n\nAlles gut, bis jemand einen Nullzeiger übergibt. Rust lässt Sie das nicht tun:
\n\n// Rust code\nfn led_on(gpio: &mut GpioRegisters, pin: u16) {\n gpio.bsrr.write(|w| w.bits(pin.into()));\n}\n\nHier ist &mut GpioRegisters die Garantie, dass die Referenz gültig und der Zugriff auf die Register exklusiv ist. Der Compiler lässt gefährlichen Code einfach nicht durch.
Nullkosten-Abstraktionen
\n\nEine der Hauptängste von Embedded-Entwicklern ist, dass eine höhere Sprache Taktzyklen "frisst". Rust folgt dem Prinzip der Zero-Cost Abstractions: Sie zahlen nur für das, was Sie nutzen. Iteratoren, Closures, Traits – all das wird in ebenso effizienten Maschinencode kompiliert wie manuelles Entrollen von Schleifen in C.
\n\nDarüber hinaus gibt es im Embedded-Ökosystem von Rust Crates, die auf der Ebene einzelner Instruktionen arbeiten. Zum Beispiel generiert cortex-m-rt die Interrupt-Vektor-Tabelle und den Startup-Code ohne eine einzige überflüssige Instruktion.
Reales Projekt: LED blinken mit Rust (STM32)
\n\nLassen Sie uns ein einfaches Programm für das STM32F3Discovery-Board schreiben. Wir verwenden cortex-m-quickstart und das HAL der embedded rust-Community.
Schritt 1: Projekt einrichten
\n\nStellen Sie sicher, dass Rust und das Toolchain für ARM Cortex-M installiert sind:
\n\nrustup target add thumbv7em-none-eabihf\ncargo install cargo-generate\n\nErstellen Sie ein Projekt aus der Vorlage:
\n\ncargo generate --git https://github.com/rust-embedded/cortex-m-quickstart\n\nSchritt 2: Code schreiben
\n\nÖffnen Sie src/main.rs und schreiben Sie den klassischen Blinky:
#![no_std]\n#![no_main]\n\nuse cortex_m_rt::entry;\nuse panic_halt as _;\nuse stm32f3xx_hal::{pac, prelude::*};\n\n#[entry]\nfn main() -> ! {\n // Zugriff auf die Peripherie erhalten\n let dp = pac::Peripherals::take().unwrap();\n let mut rcc = dp.RCC.constrain();\n let mut gpioe = dp.GPIOE.split(&mut rcc.ahb);\n\n // Pin PE9 als Ausgang konfigurieren (LED LD1)\n let mut led = gpioe\n .pe9\n .into_push_pull_output(&mut gpioe.moder, &mut gpioe.otyper);\n\n loop {\n led.set_high().unwrap();\n cortex_m::asm::delay(8_000_000);\n led.set_low().unwrap();\n cortex_m::asm::delay(8_000_000);\n }\n}\n\nWas passiert hier?
\n- \n
#![no_std]— wir be