3 min read
On this page

Embedded Fundamentals

What Is an Embedded System?

An embedded system is a computer designed to perform a dedicated function within a larger system, typically with real-time constraints and limited resources. Unlike general-purpose computers, embedded systems are optimized for specific tasks with strict requirements on power, size, cost, and reliability.

Key Characteristics

| Property | Embedded System | General-Purpose Computer | |---|---|---| | Purpose | Single/few dedicated tasks | Broad, user-defined tasks | | OS | Bare-metal or RTOS | Full OS (Linux, Windows) | | Resources | KB-MB RAM, KB-MB flash | GB RAM, TB storage | | Power | uW to low W | Tens to hundreds of W | | Boot time | Milliseconds | Seconds to minutes | | Reliability | Must run indefinitely | Acceptable to restart |

Microcontrollers vs Microprocessors

Microcontroller (MCU)

A self-contained system-on-chip with CPU, memory, and peripherals integrated:

  • CPU core (ARM Cortex-M, RISC-V, AVR, etc.)
  • Flash memory (program storage, typically 16 KB - 2 MB)
  • SRAM (data memory, typically 2 KB - 512 KB)
  • Built-in peripherals: GPIO, UART, SPI, I2C, ADC, timers
  • Clock generation (internal oscillators, PLL)

Examples: STM32F4, ESP32, ATmega328P, nRF52840.

Microprocessor (MPU)

A CPU that requires external memory and peripherals:

  • Higher clock speeds (hundreds of MHz to GHz)
  • MMU for virtual memory support
  • Typically runs Linux or another full OS
  • Requires external RAM (DDR3/4), storage (eMMC, SD)

Examples: Broadcom BCM2711 (Raspberry Pi 4), i.MX8, AM335x.

System-on-Chip (SoC)

An SoC integrates a microprocessor with peripherals, GPU, DSP, and connectivity on one die. Modern SoCs blur the MCU/MPU line -- the ESP32 is technically an SoC with dual cores, Wi-Fi, and Bluetooth.

Arduino (ATmega328P / ARM-based)

  • Beginner-friendly, massive ecosystem
  • Arduino framework abstracts hardware details
  • Limited for production use, great for prototyping

STM32 (ARM Cortex-M)

  • Professional-grade, wide range (M0 to M7)
  • STM32CubeIDE, HAL libraries
  • Strong Rust support via stm32-hal and PAC crates
  • Nucleo and Discovery boards for development

ESP32 (Xtensa / RISC-V)

  • Built-in Wi-Fi and Bluetooth
  • Dual-core, up to 240 MHz
  • ESP-IDF framework, also supports Arduino and Rust
  • Ideal for IoT applications

Raspberry Pi Pico (RP2040)

  • Dual-core ARM Cortex-M0+ at 133 MHz
  • 264 KB SRAM, 2 MB flash
  • Programmable I/O (PIO) state machines
  • Excellent Rust support via embassy-rp

Cross-Compilation

Embedded targets differ from the host machine architecture. Cross-compilation builds code on a host (x86_64) for a target (ARM Cortex-M).

Rust Cross-Compilation Toolchain

# Install the target for ARM Cortex-M4F (e.g., STM32F4)
rustup target add thumbv7em-none-eabihf

# Build for the target
cargo build --target thumbv7em-none-eabihf --release

# Flash to the board (using probe-rs)
cargo flash --target thumbv7em-none-eabihf --release --chip STM32F411CEUx

Common Rust embedded targets:

| Target Triple | Architecture | FPU | |---|---|---| | thumbv6m-none-eabi | Cortex-M0/M0+ | No | | thumbv7m-none-eabi | Cortex-M3 | No | | thumbv7em-none-eabi | Cortex-M4/M7 | No (soft float) | | thumbv7em-none-eabihf | Cortex-M4F/M7F | Yes (hard float) | | thumbv8m.main-none-eabihf | Cortex-M33 | Yes |

Bare-Metal Programming

Bare-metal means running code directly on hardware without an operating system. The program has full control of the hardware and is responsible for all initialization.

Minimal Bare-Metal Rust Program

// No standard library -- bare-metal environment
// No standard entry point

ENTRY PROCEDURE MAIN()    // never returns
    // Hardware initialization here

    LOOP FOREVER
        // Main application loop -- must never return

The no_std Environment

When #![no_std] is declared, the Rust standard library (std) is unavailable. You get:

  • core -- language primitives, iterators, Option, Result, traits, math
  • alloc (optional) -- heap allocation (Vec, Box, String) if you provide an allocator

What you lose without std:

  • File I/O, networking, threads, println!
  • HashMap (requires alloc + a hasher)
  • The default panic handler (must provide your own)

Essential Embedded Rust Crates

[dependencies]
cortex-m = "0.7"          # Low-level Cortex-M access
cortex-m-rt = "0.7"       # Runtime (startup code, vector table)
panic-halt = "0.2"        # Panic handler that halts
embedded-hal = "1.0"      # Hardware abstraction traits
defmt = "0.3"             # Efficient logging for embedded
defmt-rtt = "0.4"         # RTT transport for defmt

# Async embedded framework
embassy-executor = "0.7"  # Async executor
embassy-time = "0.4"      # Async timers and delays
embassy-stm32 = "0.3"     # STM32 HAL with async support

Development Workflow

 Write Code (Rust / C)
       |
 Cross-Compile (cargo build --target ...)
       |
 Flash to MCU (probe-rs / OpenOCD / ST-Link)
       |
 Debug (GDB + probe-rs / JTAG / SWD)
       |
 Monitor Output (RTT / UART / defmt)

Flashing and Debugging with probe-rs

# Flash firmware
probe-rs run --chip STM32F411CEUx target/thumbv7em-none-eabihf/release/app

# Attach GDB for debugging
probe-rs gdb --chip STM32F411CEUx &
arm-none-eabi-gdb -ex "target remote :1337" target/thumbv7em-none-eabihf/release/app

Memory Constraints and Optimization

Embedded systems require careful memory management:

// Stack-allocated fixed-size buffer (no heap needed)
buffer ← array of 64 zero bytes

// Use fixed-capacity collections instead of dynamic allocation
data ← FIXED_VEC(max_capacity ← 32)   // Max 32 elements, stack-allocated
PUSH(data, 42)

// Use fixed-capacity string instead of heap-allocated string
s ← FIXED_STRING(max_capacity ← 64)
FORMAT_WRITE(s, "temp: {}C", 23)

Build Size Optimization

# Cargo.toml
[profile.release]
opt-level = "z"    # Optimize for size
lto = true         # Link-time optimization
codegen-units = 1  # Single codegen unit for better optimization
strip = true       # Strip debug symbols

Key Takeaways

  • Embedded systems trade generality for efficiency, reliability, and determinism.
  • Microcontrollers integrate CPU, memory, and peripherals; microprocessors need external components.
  • Rust's no_std environment provides memory safety on bare metal without runtime overhead.
  • Cross-compilation, flashing, and on-target debugging form the core development loop.
  • Every byte of RAM and flash matters -- use stack allocation and fixed-size data structures.