# Embedded Abstractions — The Layer Cake Embedded Rust is built in layers. Understanding these layers is the single most important thing you'll learn today — because once you see the pattern, you can navigate *any* crate in the ecosystem. ## The Five Layers From lowest (closest to hardware) to highest (most convenient): ``` ┌─────────────────────────────────┐ │ BSP │ Board Support Package │ (board-specific config) │ e.g., uferris-bsp ├─────────────────────────────────┤ │ Driver Crates │ Hardware-agnostic drivers │ (sensor/device drivers) │ e.g., icm42670, bmp180 ├─────────────────────────────────┤ │ embedded-hal Traits │ The portable interface │ (the "contract") │ e.g., InputPin, I2c ├─────────────────────────────────┤ │ HAL │ Hardware Abstraction Layer │ (chip-specific safe API) │ e.g., esp-hal ├─────────────────────────────────┤ │ PAC │ Peripheral Access Crate │ (raw register access) │ e.g., esp32c3 (auto-generated) └─────────────────────────────────┘ ↓ Hardware ↓ ``` ### PAC — Peripheral Access Crate The lowest layer. Auto-generated from SVD (System View Description) files — basically, a Rust representation of every register in the chip. You *can* use it directly, but you almost never need to. ```rust // PAC-level: writing directly to a register // You don't want to do this. But it's good to know it exists. peripherals.GPIO.out_w1ts.write(|w| unsafe { w.bits(1 << 5) }); ``` ### HAL — Hardware Abstraction Layer **This is where we'll spend most of our time.** The HAL provides safe, ergonomic Rust APIs on top of the PAC. For ESP32 chips, this is `esp-hal`. ```rust // HAL-level: safe, readable, type-checked let mut led = Output::new(peripherals.GPIO5, Level::Low, OutputConfig::default()); led.set_high(); ``` ### embedded-hal Traits **The key to portability.** These traits define a *contract* — a standard interface that any HAL can implement. If your code uses `embedded_hal::digital::OutputPin` instead of `esp_hal::gpio::Output` directly, it can work on *any* chip. ```rust // This function works on ANY microcontroller that implements OutputPin fn blink(pin: &mut impl OutputPin, delay: &mut impl DelayNs) { pin.set_high().unwrap(); delay.delay_ms(500); pin.set_low().unwrap(); delay.delay_ms(500); } ``` ### Driver Crates Built on top of `embedded-hal` traits. A driver crate provides a high-level API for a specific sensor or device — and it works on *any* chip because it only depends on the traits, not on any specific HAL. ```rust // This driver doesn't know about ESP32. It just needs something that implements I2c. let mut imu = Icm42670::new(i2c, Address::Primary); let accel = imu.accel_norm().unwrap(); ``` ### BSP — Board Support Package The highest layer. A BSP pre-configures everything for a specific board — pin assignments, peripheral setup, default configurations. Instead of remembering pin numbers, you use meaningful names. ```rust // Without BSP: which pin is the LED again? let led = Output::new(peripherals.GPIO5, Level::Low, OutputConfig::default()); // With BSP: the board knows let led = board.led(); ``` --- ## How the Layers Connect The power of this architecture: 1. **esp-hal** implements `embedded-hal` traits for ESP32-C3 hardware 2. **Driver crates** are written against `embedded-hal` traits 3. So any driver crate works with any HAL — no glue code needed This is why you can take an IMU driver written by someone who's never touched an ESP32, plug it into `esp-hal`'s I2C, and it just works. The traits are the contract that makes this possible. In the next section, we'll see how this maps to a pattern you'll use for *every* peripheral.
The Embedded Rustacean · Rust Week 2026