The Embedded HAL and its implementations
These things are different
- STM32F030 I²C Driver
- nRF52840 I²C Driver
- But I want to write a library which is generic!
- e.g. a Sensor Driver
How does Rust allow generic behaviour?
- Generics!
where T: SomeTrait
Traits
An example:
#![allow(unused)] fn main() { pub trait I2c { type Error; fn write_read( &mut self, address: u8, write: &[u8], read: &mut [u8], ) -> Result<(), Self::Error>; } }
My Library
struct Co2Sensor<T> {
i2c_bus: T,
...
}
impl<T> Co2Sensor<T> where T: I2c {
fn new(i2c_bus: T) -> Co2Sensor<T> { ... }
fn read_sensor(&mut self) -> Result<f32, Error> { ... }
}
Note how Co2Sensor
owns the value whose type implements the I2c
trait.
My Application
let i2c = stm32f0xx_hal::i2c::i2c1(...);
let sensor = sensor_lib::Co2Sensor::new(i2c);
let Ok(reading) = sensor.read_sensor() else {
// did you unplug it?
};
My Application (2)
let i2c = nrf52840_hal::twim::Twim::new(...);
let sensor = sensor_lib::Co2Sensor::new(i2c);
let Ok(reading) = sensor.read_sensor() else {
// did you unplug it?
};
How do we agree on the traits?
- The Rust Embedded Working Group has developed some traits
- They are called the Embedded HAL
- See https://docs.rs/embedded-hal
- All HAL implementations should implement these traits
Blocking vs Non-blocking
- Should a trait API stall your CPU until the data is ready?
- Or should it return early, saying "not yet ready"
- So you can go and do something else in the mean time?
- Or sleep?
- Or should it be an
async fn
Blocking vs Non-blocking
- https://crates.io/crates/embedded-hal
- https://crates.io/crates/embedded-hal-nb
- https://crates.io/crates/embedded-hal-async
Trade-offs
- Some MCUs have more features than others
- The trait design has an inherent trade-off
- Flexibility/Performance vs Portability