Выбрать главу

Ok(())

}

pub fn get_input_status(&self) -> Result<bool, ()> {

if self.periph.read().enable().bit_is_clear() {

// Must be enabled to get status

return Err(());

}

if self.periph.read().direction().bit_is_set() {

// Direction must be input

return Err(());

}

Ok(self.periph.read().input_status().bit_is_set())

}

}

Because we need to enforce the restrictions on the hardware, we end up doing a lot of runtime checking which wastes time and resources, and this code will be much less pleasant for the developer to use.

But what if instead, we used Rust's type system to enforce the state transition rules? Take this example:

/// GPIO interface

struct GpioConfig<ENABLED, DIRECTION, MODE> {

/// GPIO Configuration structure generated by svd2rust

periph: GPIO_CONFIG,

enabled: ENABLED,

direction: DIRECTION,

mode: MODE,

}

// Type states for MODE in GpioConfig

struct Disabled;

struct Enabled;

struct Output;

struct Input;

struct PulledLow;

struct PulledHigh;

struct HighZ;

struct DontCare;

/// These functions may be used on any GPIO Pin

impl<EN, DIR, IN_MODE> GpioConfig<EN, DIR, IN_MODE> {

pub fn into_disabled(self) -> GpioConfig<Disabled, DontCare, DontCare> {

self.periph.modify(|_r, w| w.enable.disabled());

GpioConfig {

periph: self.periph,

enabled: Disabled,

direction: DontCare,

mode: DontCare,

}

}

pub fn into_enabled_input(self) -> GpioConfig<Enabled, Input, HighZ> {

self.periph.modify(|_r, w| {

w.enable.enabled()

.direction.input()

.input_mode.high_z()

});

GpioConfig {

periph: self.periph,

enabled: Enabled,

direction: Input,

mode: HighZ,

}

}

pub fn into_enabled_output(self) -> GpioConfig<Enabled, Output, DontCare> {

self.periph.modify(|_r, w| {

w.enable.enabled()

.direction.output()

.input_mode.set_high()

});

GpioConfig {

periph: self.periph,

enabled: Enabled,

direction: Output,

mode: DontCare,

}

}

}

/// This function may be used on an Output Pin

impl GpioConfig<Enabled, Output, DontCare> {

pub fn set_bit(&mut self, set_high: bool) {

self.periph.modify(|_r, w| w.output_mode.set_bit(set_high));

}

}

/// These methods may be used on any enabled input GPIO

impl<IN_MODE> GpioConfig<Enabled, Input, IN_MODE> {

pub fn bit_is_set(&self) -> bool {

self.periph.read().input_status.bit_is_set()

}

pub fn into_input_high_z(self) -> GpioConfig<Enabled, Input, HighZ> {

self.periph.modify(|_r, w| w.input_mode().high_z());

GpioConfig {

periph: self.periph,

enabled: Enabled,

direction: Input,

mode: HighZ,

}

}

pub fn into_input_pull_down(self) -> GpioConfig<Enabled, Input, PulledLow> {

self.periph.modify(|_r, w| w.input_mode().pull_low());

GpioConfig {

periph: self.periph,

enabled: Enabled,

direction: Input,

mode: PulledLow,

}

}

pub fn into_input_pull_up(self) -> GpioConfig<Enabled, Input, PulledHigh> {

self.periph.modify(|_r, w| w.input_mode().pull_high());

GpioConfig {

periph: self.periph,

enabled: Enabled,

direction: Input,

mode: PulledHigh,

}

}

}

Now let's see what the code using this would look like:

/*

* Example 1: Unconfigured to High-Z input

*/

let pin: GpioConfig<Disabled, _, _> = get_gpio();

// Can't do this, pin isn't enabled!

// pin.into_input_pull_down();

// Now turn the pin from unconfigured to a high-z input

let input_pin = pin.into_enabled_input();

// Read from the pin

let pin_state = input_pin.bit_is_set();

// Can't do this, input pins don't have this interface!

// input_pin.set_bit(true);

/*

* Example 2: High-Z input to Pulled Low input

*/

let pulled_low = input_pin.into_input_pull_down();

let pin_state = pulled_low.bit_is_set();

/*

* Example 3: Pulled Low input to Output, set high

*/

let output_pin = pulled_low.into_enabled_output();

output_pin.set_bit(true);

// Can't do this, output pins don't have this interface!

// output_pin.into_input_pull_down();

This is definitely a convenient way to store the state of the pin, but why do it this way? Why is this better than storing the state as an enum inside of our GpioConfig structure?