Course contentsShow
← Rust
Lesson 1508 of 1,91012. Unsafe Rust and Low-Level AccessFree lesson

Hardware Abstraction Layers

You will design modular hardware abstraction layers for portability across different microcontrollers.

In this lesson, you will learn how to design modular Hardware Abstraction Layers (HALs) to decouple your application logic from specific microcontroller registers, ensuring your code remains portable across different hardware platforms.

The Strategy of Decoupling

When writing low-level Rust, hardcoding memory-mapped I/O addresses or specific register offsets makes your codebase fragile. If you switch from an ARM Cortex-M0 to a RISC-V controller, your firmware logic—such as a sensor driver—should not need to change.

To achieve this, we use the Trait-based abstraction pattern. We define a common interface (a trait) that describes what the hardware can do, while the HAL implementation defines how the hardware does it.

Implementing a Portable Interface

Imagine we want to toggle an LED. Instead of writing directly to a volatile pointer inside our business logic, we define a trait:

pub trait Led {
    fn on(&mut self);
    fn off(&mut self);
}

The application logic now accepts any type T that implements Led:

pub fn blink_twice<T: Led>(led: &mut T) {
    led.on();
    led.off();
    led.on();
    led.off();
}

Bridging to Hardware

The hardware-specific crate (the HAL implementation) performs the actual register manipulation. It defines a struct, such as GpioLed, and implements the trait:

pub struct GpioLed { /* register pointers */ }

impl Led for GpioLed {
    fn on(&mut self) {
        // Unsafe block for memory-mapped I/O access
        unsafe { self.regs.set.write(1); }
    }
    fn off(&mut self) {
        unsafe { self.regs.clear.write(1); }
    }
}

By separating the Trait definition (the contract) from the HAL implementation (the hardware access), you enable your high-level application to remain completely agnostic of the underlying register map. This modularity allows you to unit test logic using "Mock" implementations of these traits on your host machine while linking to real peripheral implementations on embedded targets.

Key Takeaway: Design HALs using traits to define the behavioral interface of peripherals, allowing application code to stay portable by interacting with abstractions rather than raw hardware registers.