Course contentsShow
← Rust
Lesson 1824 of 1,91016. Specialized Runtime TargetsFree lesson

Embedded-HAL Abstractions

Write portable drivers by programming against hardware abstraction layer traits instead of specific silicon registers.

In this lesson, you will learn how to decouple your driver logic from specific silicon by programming against embedded-hal traits instead of raw Peripheral Access Crates (PACs).

Beyond the Silicon

When you write a driver by interacting directly with a PAC—for example, toggling a specific register bit in an STM32's GPIOA_ODR—you create "silicon-locked" code. This code is impossible to port to an nRF or RP2040 chip without a complete rewrite. The embedded-hal crate solves this by defining standard interface traits for common hardware operations, such as OutputPin, I2c, or Spi.

Programming Against Traits

Instead of requiring a concrete type like stm32f4::pac::GPIOA, your driver accepts any type that implements the trait. This creates a contract: your driver doesn't care how a pin is toggled, only that the provided object satisfies the OutputPin trait's set_low() and set_high() methods.

Consider this sensor driver structure:

use embedded_hal::digital::OutputPin;

pub struct SensorDriver<P> {
    pin: P,
}

impl<P: OutputPin> SensorDriver<P> {
    pub fn new(pin: P) -> Self {
        Self { pin }
    }

    pub fn trigger(&mut self) -> Result<(), P::Error> {
        self.pin.set_high()?;
        // ... internal delay logic
        self.pin.set_low()
    }
}

The Power of Generic Drivers

Because SensorDriver is generic over P, it is completely platform-agnostic. You can instantiate it with an STM32 GPIO pin, an I2C expander pin, or even a dummy pin used during unit testing on your host machine.

This approach leverages Rust's monomorphization; the compiler will generate a specialized version of trigger for each specific pin type used in your final application. Because these traits are designed as zero-cost abstractions, the generated assembly for set_high() usually boils down to the exact same bitwise register manipulation you would have written manually, but with the added benefit of being reusable across any hardware that supports the embedded-hal.

By targeting these traits, you ensure your drivers remain portable and maintainable as you rotate through different microcontrollers.

Key Takeaway: By coding against embedded-hal traits rather than hardware registers, you transform vendor-specific logic into portable, reusable, and testable drivers.