CodeBlanks Logo Icon CodeBlanks

Embedded

Program microcontrollers with embedded-hal.

Embedded

Hardware access in embedded Rust is layered: vendor register definitions (PAC), a chip HAL, and on top of those the embedded-hal traits. Drivers (sensors, displays) are written against these traits, so the same driver runs on different microcontrollers:

// embedded-hal
pub trait OutputPin {
    type Error;
    fn set_high(&mut self) -> Result<(), Self::Error>;
    fn set_low(&mut self) -> Result<(), Self::Error>;
}

fn yak_sondur<P: OutputPin>(led: &mut P) -> Result<(), P::Error> {
    led.set_high()?;
    led.set_low()
}

For testing on a desktop, a mock pin implementing the same trait is written.

A mock pin

A mock pin recording state changes verifies driver logic without real hardware:

struct SahtePin {
    kayit: String,
}

impl OutputPin for SahtePin {
    type Error = core::convert::Infallible;
    fn set_high(&mut self) -> Result<(), Self::Error> {
        self.kayit.push('H');
        Ok(())
    }
    fn set_low(&mut self) -> Result<(), Self::Error> {
        self.kayit.push('L');
        Ok(())
    }
}

Infallible is an empty error type saying "this operation never fails".

Practice Blink an LED

Write the OutputPin trait above and the SahtePin type. Blink the mock pin three times with fn yak_sondur<P: OutputPin>(led: &mut P) and print the record.

Starter code

main.rs
fn main() {
    // buraya yaz
}

Sample solution

main.rs
pub trait OutputPin {
    type Error;
    fn set_high(&mut self) -> Result<(), Self::Error>;
    fn set_low(&mut self) -> Result<(), Self::Error>;
}

struct SahtePin {
    kayit: String,
}

impl OutputPin for SahtePin {
    type Error = core::convert::Infallible;
    fn set_high(&mut self) -> Result<(), Self::Error> {
        self.kayit.push('H');
        Ok(())
    }
    fn set_low(&mut self) -> Result<(), Self::Error> {
        self.kayit.push('L');
        Ok(())
    }
}

fn yak_sondur<P: OutputPin>(led: &mut P) -> Result<(), P::Error> {
    led.set_high()?;
    led.set_low()
}

fn main() {
    let mut pin = SahtePin { kayit: String::new() };
    for _ in 0..3 {
        yak_sondur(&mut pin).unwrap();
    }
    println!("{}", pin.kayit);
}

Expected output

HLHLHL

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 What is the benefit of writing a driver against a trait instead of a concrete chip type?

  • The same driver works on any compatible hardware and in desktop tests Correct answer
  • It's faster
  • Less memory
  • No benefit

Explanation: Since it's generic there's no runtime cost; the compiler generates specialized code for each chip.

2 Never fails

Question
impl OutputPin for SahtePin {
    type Error = core::convert::____;
    ..
}
Answer
impl OutputPin for SahtePin {
    type Error = core::convert::Infallible;
    ..
}

Explanation: Infallible is a type with no values, saying failure is impossible.

3 Layer and role

Term Meaning
PAC Register definitions
HAL Chip abstraction
embedded-hal Common traits
Driver Written against traits

All lessons in Expert