The Embedded HAL and its implementations
These things are different
- STM32F030 UART Driver
- nRF52840 UART Driver
- But I want to write a library which is generic!
- e.g. an AT Command Parser
How does Rust allow generic behaviour?
- Generics!
where T: SomeTrait
Traits
An example:
#![allow(unused)] fn main() { trait GenericSerial { type Error; fn read(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error>; fn write(&mut self, buffer: &[u8]) -> Result<usize, Self::Error>; } }
My Library
struct AtCommandParser<T> {
uart: T,
...
}
impl<T> AtCommandParser<T> where T: GenericSerial {
fn new(uart: T) -> AtCommandParser<T> { ... }
fn get_command(&mut self) -> Result<Option<AtCommand>, Error> { ... }
}
Note how AtCommandParser
owns the object which meets the GenericSerial
trait.
My Application
let uart = stm32_hal::Uart::new(...);
let at_parser = at_library::AtCommandParser::new(uart);
while let Some(cmd) = at_parser.get_command().unwrap() {
...
}
My Application (2)
let uart = nrf52_hal::Uart::new(...);
let at_parser = at_library::AtCommandParser::new(uart);
while let Some(cmd) = at_parser.get_command().unwrap() {
...
}
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 an do something else in the mean time?
- Or sleep?
embedded_hal::blocking::serial::Write
, vsembedded_hal::serial::Write
Trade-offs
- Some MCUs have more features than others
- The trait design has an inherent trade-off
- Flexibility/Performance vs Portability