Files
RTS10_verslagen/report-3/rust_report.md
2025-10-30 13:46:46 +01:00

13 KiB

sub_title, class_code, toc, auther
sub_title class_code toc auther
Real Time Systems 10 ELERTS10 true
name email name_short
Finley van Reenen (0964590) mail@lailatheelf.nl E.L.F. van Reenen

Rust Verslag

Opdracht 8.2

Maak een toestandsmachine volgens de State Pattern methode zoals beschreven in het boek. Er zijn drie toestanden: rood, groen en oranje. Tussen de toestanden wordt geschakeld op basis van tijd: respectievelijk $4s&, &3s& en &1s&.

De State Pattern methode in rust maakt gebuik van structs en traits. Om dit mognelijk te maken moet er een struct zijn die de leds kan aansturen. Hiervoor is het de Pin struct van de stm32f4xx-hal nodig in in struct. Om mijn implementatie makkelijker onderhoudbaar te maken worden de Pin structs doorgegeven aan de state machine struct. Hiervoor is een triad nodig die functions voor het aanpassen van de led status beschijft. De toggle functie die gebruikt wordt in het voorbeeld is niet geimplementeerd via een trait. Deze kan dus niet gebruikt worden zonder de hal aan te passen. In de documentatie van de pin struct implementeerd die de OutputPin trait van de embedded_hal crate^[https://docs.rs/stm32f4xx-hal/0.22.0/stm32f4xx_hal/gpio/struct.Pin.html#impl-OutputPin-for-Pin%3CP,+N,+Output%3CMODE%3E%3E-1]. Dus deze crate is toegevoed aan het project zodat hier gebruik van gemaakt kan worden.

Om te testen of dit werkt is er een stuct aangemaakt met een simple toggle functie en de embeded_hal geimporteerd. Dit is gedaan met de volgende code:

use embedded_hal::digital::{ErrorType as DigitalErrorType, OutputPin, PinState};

struct Leds<'a, Red: OutputPin, Green: OutputPin, Blue: OutputPin> {
    red: &'a mut Red,
    red_state: PinState,
    green: &'a mut Green,
    green_state: PinState,
    blue: &'a mut Blue,
    blue_state: PinState,
}
impl<'a, Red: OutputPin, Green: OutputPin, Blue: OutputPin> Leds<'a, Red, Green, Blue> {
    pub fn new(red: &'a mut Red, green: &'a mut Green, blue: &'a mut Blue) -> Self {
        let _  = red.set_low();
        let _  = green.set_low();
        let _  = blue.set_low();
        Self {
            red,
            red_state: PinState::Low,
            green,
            green_state: PinState::Low,
            blue,
            blue_state: PinState::Low,
        }
    }

    pub fn red_toggle(&mut self) -> Result<(), <Red as DigitalErrorType>::Error> {
        if self.red_state == PinState::Low {
            self.red_state = PinState::High;
        } else {
            self.red_state = PinState::Low;
        }
        self.red.set_state(self.red_state)
    }
}

Daarnaast is ook de main functie aangepast naar het volgende:

#[entry]
fn main() -> ! {
    if let (Some(dp), Some(cp)) = (
        pac::Peripherals::take(),
        cortex_m::peripheral::Peripherals::take(),
    ) {       
        //GPIOD ophalen
        let gpiod = dp.GPIOD.split();
        // create structs for pins
        let mut green = gpiod.pd12.into_push_pull_output();
        let mut red = gpiod.pd14.into_push_pull_output();
        let mut blue = gpiod.pd15.into_push_pull_output();
        // create leds struct
        let mut leds = Leds::new(&mut red, &mut green, &mut blue);

        //Klok instellen
        let rcc = dp.RCC.constrain();
        let clocks = rcc.cfgr.sysclk(48.MHz()).freeze();

        // Create a delay abstraction based on SysTick
        let mut delay = cp.SYST.delay(&clocks);
        let mut status:bool = false;
        loop {
            // On for 1s, off for 1s.
            let _ = leds.red_toggle();
            status ^= true;
            delay.delay_ms(1000_u32);

            //dit verschijnt in een van de open terminals in vscode
            hprintln!("Led {:?}", status.then(|| "aan!").unwrap_or("uit!"));
        }
    }

    loop {}
}

state machine

De state machine is geïmplementeerd in zijn eigen struct. In the rust boek wordt er gebruikt gemaakt van Box van de std library, maar deze zit niet in de core library. Ik heb dit vervangen met een enum. Het nadeel is dat functies niet meer dynamisch aangeroepen kunnen worden. Om hier toch makkelijk mee om te gaan is de volgende functie aangemaakt die het juiste object van de enum pakt en diens functie aanroept.

De state machine struct is als volgt geïmplementeerd in het bestand state_machine.rs.

use embedded_hal::digital::{OutputPin, PinState};

use crate::leds::{Leds, Toggle};

enum States {
    Red(StateRed),
    Blue(StateBlue),
    Green(StateGreen),
}

trait State<
    RP: OutputPin, RT: Toggle<RP>,
    GP: OutputPin, GT: Toggle<GP>,
    BP: OutputPin, BT: Toggle<BP>
> {
    fn new(leds: &mut Leds<RP, RT, GP, GT, BP, BT>) -> Self;
    fn second_passed(&self, leds: &mut Leds<RP, RT, GP, GT, BP, BT>) -> States;
}

pub struct StateMachine<'a,
    RP: OutputPin, RT: Toggle<RP>,
    GP: OutputPin, GT: Toggle<GP>,
    BP: OutputPin, BT: Toggle<BP>
> {
    state: States,
    leds: &'a mut Leds<'a, RP, RT, GP, GT, BP, BT>,
}
impl<'a,
    RP: OutputPin, RT: Toggle<RP>,
    GP: OutputPin, GT: Toggle<GP>,
    BP: OutputPin, BT: Toggle<BP>
> StateMachine<'a, RP, RT, GP, GT, BP, BT> 
{
    pub fn new(leds: &'a mut Leds<'a, RP, RT, GP, GT, BP, BT>) -> Self {
        Self {
            state: States::Red(StateRed::new(leds)),
            leds,
        }
    }
    pub fn second_passed(&mut self) {
        self.state = match &self.state {
            States::Red(state) => state.second_passed(self.leds),
            States::Blue(state) => state.second_passed(self.leds),
            States::Green(state) => state.second_passed(self.leds),
        }
    }
}

// ...

Leds en Toggle

Als te zien is in de state machine code is er gebruikgemaakt van een struct Leds die andere generics gebruikt als eerder. Dit is een geupdaten variant hiervan. De vorige versie had 1 trait met herhalende function voor elke led. Deze functies zijn nu elke gedefinieerd in de trait Toggle. De toggle functie waar het concept is getest is eigenlijk niet nodig voor deze opdracht, maar ik vond het een leuke uitdaging om dit er in te houden, gezien Rust beveiligingen heeft die ervoor zorgt dat een pin niet gebruikt kan worden zonder toegang tot een specifiek object. Dit zorgt er voor dat dat object kan bijhouden wat de laatste status van de pin is en dus de toggle altijd werkt zonder de status van de pin uit te lezen.

De toggle functie is implemented in leds.rs

use embedded_hal::digital::{OutputPin, PinState};

pub trait Toggle<T: OutputPin> {
    fn toggle(&mut self) -> Result<(), T::Error>;
    fn set(&mut self, state: PinState) -> Result<(), T::Error>;
    fn get(&self) -> PinState;

    fn high(&mut self) -> Result<(), T::Error> {
        self.set(PinState::High)
    }
    fn low(&mut self) -> Result<(), T::Error> {
        self.set(PinState::Low)
    }
}

pub struct ToggleLed<'a, T: OutputPin> {
    pin: &'a mut T,
    state: PinState
}
impl<'a, T: OutputPin> ToggleLed<'a, T> {
    pub fn new(pin: &'a mut T) -> Result<Self, T::Error> {
        match pin.set_low() {
            Ok(_) => Ok(Self { pin, state: PinState::Low }),
            Err(e) => Err(e)
        }
    }
}
impl<'a, T: OutputPin> Toggle<T> for ToggleLed<'a, T> {
    fn toggle(&mut self) -> Result<(), T::Error> {
        if self.state == PinState::High {
            self.state = PinState::Low;
        } else {
            self.state = PinState::Low;
        }
        self.pin.set_state(self.state)
    }
    fn set(&mut self, state: PinState) -> Result<(), T::Error> {
        if state != self.state {
            self.state = state;
            self.pin.set_state(self.state)
        } else {
            Ok(())
        }
    }
    fn get(&self) -> PinState {
        self.state
    }
}

// ...

De struct Leds was wat lastiger wegens een bug in Rust^[github.com/rust-lang/rust/issues/60214]. De Leds struct gebruikt een generic voor de trait Toggl<T>. Maar deze trait heeft ook een generic. Deze kan alleen ingevuld worden door met een struct, dus T: Toggle<DigitalPin> heeft een error. Een oplossing hiervoor is een extra generic (P: OutputPin, T: Toggle<P>), echter de generic P wordt verder nergens gebruikt. De compiler detecteert het gebruik niet in Toggle<P>. en geeft een error hiervoor. Een workaround voor dit is door een extra element toe te voegen aan de struct met met type PhantomData<P>.

Met deze workaround toegepast de struct Leds is als volgt geïmplementeerd in leds.rs

// ...

pub struct Leds<'a,
    RP: OutputPin, RT: Toggle<RP>,
    GP: OutputPin, GT: Toggle<GP>,
    BP: OutputPin, BT: Toggle<BP>
> {
    pub red: &'a mut RT,
    pub green: &'a mut GT,
    pub blue: &'a mut BT,

    // phantom data to convince rustc the generics RP, GP and BP are used.
    _r: PhantomData<RP>,
    _g: PhantomData<GP>,
    _b: PhantomData<BP>,
}
impl<'a,
    RP: OutputPin, RT: Toggle<RP>,
    GP: OutputPin, GT: Toggle<GP>,
    BP: OutputPin, BT: Toggle<BP>
> Leds<'a, RP, RT, GP, GT, BP, BT> {
    pub fn new(red: &'a mut RT, green: &'a mut GT, blue: &'a mut BT) -> Self {
        Self {
            red,
            green,
            blue,

            _r: PhantomData,
            _g: PhantomData,
            _b: PhantomData,
        }
    }

    pub fn set_all(&mut self, red: PinState, green: PinState, blue: PinState) {
        let _ = self.red.set(red);
        let _ = self.blue.set(blue);
        let _ = self.green.set(green);
    }
}

states

De states zijn geimplementeerd in state_machine.rs

// ...

// ####################################################
// ## RED #############################################
// ####################################################

struct StateRed {
    time_passed: u32
}
impl<
    RP: OutputPin, RT: Toggle<RP>,
    GP: OutputPin, GT: Toggle<GP>,
    BP: OutputPin, BT: Toggle<BP>
> State<RP, RT, GP, GT, BP, BT> for StateRed {
    fn new(leds: &mut Leds<RP, RT, GP, GT, BP, BT>) -> Self {
        leds.set_all(PinState::High, PinState::Low, PinState::Low);
        Self {
            time_passed: 0
        }
    }
    fn second_passed(&self, leds: &mut Leds<RP, RT, GP, GT, BP, BT>) -> States {
        if self.time_passed + 1 >= 4 {
            States::Green(StateGreen::new(leds))
        } else {
            States::Red(Self {
                time_passed: self.time_passed + 1
            })
        }
    }
}

// ####################################################
// ## GREEN ###########################################
// ####################################################

struct StateGreen {
    time_passed: u32
}
impl<
    RP: OutputPin, RT: Toggle<RP>,
    GP: OutputPin, GT: Toggle<GP>,
    BP: OutputPin, BT: Toggle<BP>
> State<RP, RT, GP, GT, BP, BT> for StateGreen {
    fn new(leds: &mut Leds<RP, RT, GP, GT, BP, BT>) -> Self {
        leds.set_all(PinState::Low, PinState::High, PinState::Low);
        Self {
            time_passed: 0
        }
    }
    fn second_passed(&self, leds: &mut Leds<RP, RT, GP, GT, BP, BT>) -> States {
        if self.time_passed + 1 >= 3 {
            States::Blue(StateBlue::new(leds))
        } else {
            States::Green(Self {
                time_passed: self.time_passed + 1
            })
        }
    }
}

// ####################################################
// ## BLUE ############################################
// ####################################################

struct StateBlue {}
impl<
    RP: OutputPin, RT: Toggle<RP>,
    GP: OutputPin, GT: Toggle<GP>,
    BP: OutputPin, BT: Toggle<BP>
> State<RP, RT, GP, GT, BP, BT> for StateBlue {
    fn new(leds: &mut Leds<RP, RT, GP, GT, BP, BT>) -> Self {
        leds.set_all(PinState::Low, PinState::Low, PinState::High);
        Self {}
    }
    fn second_passed(&self, leds: &mut Leds<RP, RT, GP, GT, BP, BT>) -> States {
        States::Red(StateRed::new(leds))
    }
}

main.rs

#![deny(unsafe_code)]
#![allow(clippy::empty_loop)]
#![no_main]
#![no_std]

// Halt on panic
use panic_halt as _; // panic handler

use cortex_m_rt::entry;
use stm32f4xx_hal::{self as hal};

use crate::hal::{pac, prelude::*};

mod leds;
use crate::leds::{Leds, ToggleLed};
mod state_machine;
use crate::state_machine::StateMachine;

#[entry]
fn main() -> ! {
    if let (Some(dp), Some(cp)) = (
        pac::Peripherals::take(),
        cortex_m::peripheral::Peripherals::take(),
    ) {
        //GPIOD ophalen
        let gpiod = dp.GPIOD.split();
        //led pinnen uphalen
        let mut green_pin = &mut gpiod.pd12.into_push_pull_output();
        let mut red_pin = &mut gpiod.pd14.into_push_pull_output();
        let mut blue_pin = &mut gpiod.pd15.into_push_pull_output();
        let mut green = ToggleLed::new(&mut green_pin).expect("faild to set green led");
        let mut red = ToggleLed::new(&mut red_pin).expect("faild to set red led");
        let mut blue = ToggleLed::new(&mut blue_pin).expect("faild to set blue led");
        let mut leds = Leds::new(&mut red, &mut green, &mut blue);
        //leds struct en state machine maken
        let mut state_machine = StateMachine::new(&mut leds);

        //Klok instellen
        let rcc = dp.RCC.constrain();
        let clocks = rcc.cfgr.sysclk(48.MHz()).freeze();

        // Create a delay abstraction based on SysTick
        let mut delay = cp.SYST.delay(&clocks);
        loop {
            let _ = state_machine.second_passed();
            delay.delay_ms(1000_u32);
        }
    }

    loop {}
}