briefings // Embedded Engineering: Wenn dein Code direkt mit der Welt spricht
Embedded Engineering: Wenn dein Code direkt mit der Welt spricht
Web-Apps und Desktop-Tools hat jedes IT'wesen schon geschrieben. Aber was, wenn dein Code direkt LEDs, Motoren oder Sensoren steuert? In diesem kurzen Vortrag tauchen wir in die Welt der Embedded-Systeme ein: kleine Computer ohne Betriebssystem, direkt auf der Hardware, aber mit moderner Sprache und solider Architektur.
Mit einem Mix aus Coding, Praxisbeispielen und technischen Einblicken zeige ich dir, wie Embedded-Entwicklung mit Rust nicht nur sicher, sondern auch richtig spannend wird.
Embedded Engineering
Wenn dein Code direkt mit der Welt spricht
04.04.2026
RoboCup war mein Gateway Drug

- Roboter sind “Software mit Konsequenzen”
- Du merkst sofort, ob du Mist gebaut hast: er fährt, kippt, brennt, verliert
- Embedded ist der Teil, der die Physik trifft
Fun Fact: 2027 RoboCup WM in Nürnberg
Embedded ist überall
- “Computer” ist nicht immer Laptop
- Oft ist es ein Chip + ein Job
- pro Tag hunderte um dich
Viele dieser “unsichtbaren” Rechner sind Cortex-M-Klasse: klein, billig, zuverlässig
Embedded = Probleme in kleinen Lösungsraum
- Wenig RAM/Flash → plane Datenflüsse, statt “wir skalieren später”
- Echtzeit/IO → Nebenläufigkeit ist nicht optional
- Updates sind teuer → Debugbarkeit ist Teil des Produkts
- Hardware ist “API”, aber mit Lieferzeiten
Unser Mini-Labor: Blue Pill
- Board: STM32F103 (“Blue Pill”)
- Debug: SWD (z.B. ST-Link / probe-rs fähig)
- IO-Experimente:
- LED (digital / PWM)
- Button (digital)
- “Sensor” (Potentiometer → ADC)
Sichtbare Physik als Feedback-Loop
Hello World ohne Display: Blinky
- Kein Terminal
- Kein Browser
- Nur Strom + ein Pin
Wenn das blinkt, lebt die Kette:
Toolchain → Flash → CPU → GPIO → Elektronen
Wenn’s schiefgeht, gibt’s Debug-Ausgabe…

Mini-Glossar
- MCU: CPU + RAM/Flash + Peripherie auf einem Chip
- GPIO: Pin als Ein/Aus
- ADC: Spannung → Zahl
- PWM: “Analog tun” mit Digital-Pins (Duty Cycle)
- Interrupt: Hardware weckt Task
- HAL: Treiber-Schicht: “Pin A9” wird “LED”
Pinout

Analog vs. Digital
- Die Welt ist analog
- Dein Code ist digital
- Embedded heißt: Übersetzen bzw. Abschätzen
ADC/DAC sind am Ende nur Methoden, Spannung und Zeit zu formen
PWM: Zeit als Stellschraube
- Du hast nur “0/1” (Digital)
- Du willst aber dimmbar / Servo / Motor
- Lösung: Duty Cycle
PWM ist kein echtes Analog. Aber oft reicht’s nd es ist billig
UART: Bits + Timing + Agreement
- Kein “Netzwerk”
- Kein “Stack”
- Nur: Startbit, Datenbits, Stopbit
- Praktisch: Logs, GPS, Debug-Konsole
- I²C/SPI sind die gleichen Grundideen – nur andere Verträge
MCU: CPU + Peripherie + Bus
Das “Programm” ist nur ein Teil. Der Rest sind Timer, DMA, UART, ADC, GPIO, Interrupts.

Memory-Mapped IO
- Peripherie ist Speicherbereich
- Register sind Adressen
- Lesen/Schreiben → Hardware reagiert
”Transistoren” plötzlich wie API
Bare Metal in Rust
no_std ist Absicht
#![no_std]
#![no_main]
use cortex_m_rt::entry;
#[entry]
fn main() -> ! {
loop {
// your code hits physics here
}
}
- Kein OS → kein
std - verlinke nur, was du wirklich brauchst (
core, optionalalloc) - “Komfort” kostet in Embedded oft: Flash, RAM, Determinismus
Ownership & Borrowing
der Compiler ist dein zweites Paar Augen
- Kein GC → trotzdem Memory Safety
- Werte werden besessen (move), nicht “irgendwo referenced”
- Du leihst Daten (borrow) statt sie “mal kurz zu teilen”
- Ergebnis: viele Fehler verhindert, bevor du überhaupt flashst
Ownership: Copy vs Borrow
let eins: i32 = 1;
let mut zwei: i32 = 2;
zwei = 3; // ok: nur neue Zuweisung
let drei = eins; // ok: Copy, eins bleibt gültig
let vier = &eins; // ok: Borrow (immutable reference)
println!("{}", eins); // ok: eins ist noch da
println!("{}", drei); // ok
println!("{}", vier); // ok: deref implizit bei Display
Der Debug-Loop muss banal sein

cargo embed= build → flash → reset → RTT → GDB- Logs früh aktivieren (RTT/defmt), sonst LED-Morsecode
- Ziel: eine Änderung → sofort Feedback
- wenn das frustet, verlierst du 80% deiner Zeit/Energie
Weniger Handarbeit: Datasheet → SVD → API
- CMSIS-SVD beschreibt Register/Bitfelder als XML
- Generatoren bauen daraus typsichere Register-APIs
- Ergebnis: weniger Copy-Paste, weniger “falscher Bitshift”
- Rust verliert keine Hardware-Tiefe (nur Tippfehler)
Minimal bare-metal blinky
#![no_std]
#![no_main]
use cortex_m::asm::delay;
use cortex_m_rt::entry;
use panic_halt as _;
use stm32f1::stm32f103;
#[entry]
fn main() -> ! {
let dp = stm32f103::Peripherals::take().unwrap();
// Clock für GPIOC
dp.RCC.apb2enr.modify(|r, w| unsafe {
w.bits(r.bits() | (1 << 4))
});
// PC13 als Output Push-Pull @ 2 MHz
dp.GPIOC.crh.modify(|r, w| unsafe {
let mut bits = r.bits();
bits &= !(0xF << 20); // PC13-Feld (4 Bits) löschen
bits |= (0x2 << 20); // MODE13=10, CNF13=00 setzen
w.bits(bits)
});
const BLINK_DELAY_CYCLES: u32 = 2_000_000; // Cycles! not ms
loop {
// PC13 LOW (reset) -> LED oft AN
dp.GPIOC.bsrr.write(|w| unsafe { w.bits(1 << (13 + 16)) });
delay(BLINK_DELAY_CYCLES);
// PC13 HIGH (set) -> LED AUS
dp.GPIOC.bsrr.write(|w| unsafe { w.bits(1 << 13) });
delay(BLINK_DELAY_CYCLES);
}
}
Problem: Tasks nacheinander ausführen
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use panic_halt as _;
use stm32f1xx_hal::{gpio::PinState, pac, prelude::*};
#[entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
let mut flash = dp.FLASH.constrain();
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.freeze(&mut flash.acr);
let mut delay = cp.SYST.delay(&clocks);
let mut gpioa = dp.GPIOA.split();
let mut gpioc = dp.GPIOC.split();
let mut led = gpioc.pc13.into_push_pull_output_with_state(&mut gpioc.crh, PinState::High);
let button = gpioa.pa0.into_pull_up_input(&mut gpioa.crl);
let mut wait_ms: u16 = 300;
let mut was_up = true;
loop {
let _ = led.toggle();
if was_up && button.is_low() {
if wait_ms > 1 {
wait_ms /= 2;
}
was_up = false;
} else if button.is_high() {
was_up = true;
}
delay.delay_ms(wait_ms);
}
}
Nebenläufigkeit ist kein Feature.
Sie ist Vorraussetzung für vieles
Async ist kein OS
Es ist ein fairer Scheduler

Embassy (embedded + async)
- Tasks sind statisch allokiert (kein Heap nötig)
- CPU schläft, wenn nix los ist (Interrupts wecken)
- Fairness: ein Task kann nicht “alles” belegen
- Du bekommst “blink + button + sensor” ohne Thread-Tetris
Embassy Async Beispiel
#![no_std]
#![no_main]
use core::sync::atomic::{AtomicU32, Ordering};
use embassy_executor::Spawner;
use embassy_stm32::{
exti::ExtiInput,
gpio::{Level, Output, Pull, Speed},
};
use embassy_time::Timer;
use panic_probe as _;
static BLINK_DELAY_MS: AtomicU32 = AtomicU32::new(3000);
#[embassy_executor::task]
async fn led_task(mut led: Output<'static, embassy_stm32::peripherals::PC13>) {
loop {
led.toggle();
let d = BLINK_DELAY_MS.load(Ordering::Relaxed) as u64;
Timer::after_millis(d).await;
}
}
#[embassy_executor::task]
async fn button_task(mut button: ExtiInput<'static, embassy_stm32::peripherals::PA0>) {
loop {
button.wait_for_falling_edge().await;
let d = (BLINK_DELAY_MS.load(Ordering::Relaxed) / 2);
BLINK_DELAY_MS.store(d, Ordering::Relaxed);
button.wait_for_rising_edge().await;
}
}
#[embassy_executor::main]
async fn main(spawner: Spawner) -> ! {
let p = embassy_stm32::init(Default::default());
let led = Output::new(p.PC13, Level::High, Speed::Low);
let button = ExtiInput::new(p.PA0, p.EXTI0, Pull::Up);
spawner.spawn(led_task(led)).unwrap();
spawner.spawn(button_task(button)).unwrap();
core::future::pending::<()>().await;
unreachable!()
}
Memory Safety ist Key-Faktor

- Android 2025: Memory-Safety-Vulns fallen erstmals unter 20%
- In deren Auswertung: Rust ~1000× geringere Memory-Safety-Bug-Dichte als C/C++
- Aber:
unsafeexistiert! Wichtig ist Kapselung + Review-Disziplin
Playbook: Erste Stunde Embedded Rust
- Board + Debugger: Strom + SWD steht
- Blinky: GPIO + Timer/Ticks
- Logs an: RTT/defmt
- Ein Input: Button oder ADC (Poti)
- Ein Output: PWM (LED dimmen)
- Dann erst: UART / I²C / SPI
Erst Feedback, dann Komplexität.
Decision Matrix: Rust vs C/C++ vs MicroPython
Rust
- (+) Guardrails (Ownership/Types)
- (+) Einheitliches Tooling (Cargo/Docs/Tests)
- (–) Setup/Compile-Zeit
- (–)
unsafebraucht Regeln
C/C++ / MicroPython
- C/C++: (+) Kontrolle, (–) Footguns
- MicroPython: (+) REPL/Tempo, (–) Grenzen bei Echtzeit/Footprint
- Beides: Ökosystem stark, DX weniger “einheitlich”
Zusammenfassend
Embedded mit Rust ist “weniger Stress”, nicht “weniger nah dran”
- Du arbeitest weiter mit Timern, Interrupts, Registern
- mit besseren Verträgen zwischen Modulen (Traits/HAL)
- Ziel: schneller iterieren, Fehler vornherein Verhindern
Mehr?
Auf der Easterhegg23
“The Bunny is a Lie, but Rust is the Truth” 2026-04-04 15:15–16:15, Test Chamber 01
“Consent Theater & Interface Oper”, 2026-04-05 10:15–10:45, The Rabbit Hole
“Arduino For Total Newbies”, 2026-04-05 13:45–17:15, Solder Enrichment Center
Repo-Links, Bücher, Tools
END OF BRIEFING
MAIL:
robert.jeutter@wieerwill.dev
MASTODON:
https://chaos.social/@wieerwill
LINKEDIN:
https://www.linkedin.com/in/wieerwill







