| # Recommendations for GPIO Interfaces |
| |
| <a id="c-zst-pin"></a> |
| ## Pin types are zero-sized by default (C-ZST-PIN) |
| |
| GPIO Interfaces exposed by the HAL should provide dedicated zero-sized types for |
| each pin on every interface or port, resulting in a zero-cost GPIO abstraction |
| when all pin assignments are statically known. |
| |
| Each GPIO Interface or Port should implement a `split` method returning a |
| struct with every pin. |
| |
| Example: |
| |
| ```rust |
| pub struct PA0; |
| pub struct PA1; |
| // ... |
| |
| pub struct PortA; |
| |
| impl PortA { |
| pub fn split(self) -> PortAPins { |
| PortAPins { |
| pa0: PA0, |
| pa1: PA1, |
| // ... |
| } |
| } |
| } |
| |
| pub struct PortAPins { |
| pub pa0: PA0, |
| pub pa1: PA1, |
| // ... |
| } |
| ``` |
| |
| <a id="c-erased-pin"></a> |
| ## Pin types provide methods to erase pin and port (C-ERASED-PIN) |
| |
| Pins should provide type erasure methods that move their properties from |
| compile time to runtime, and allow more flexibility in applications. |
| |
| Example: |
| |
| ```rust |
| /// Port A, pin 0. |
| pub struct PA0; |
| |
| impl PA0 { |
| pub fn erase_pin(self) -> PA { |
| PA { pin: 0 } |
| } |
| } |
| |
| /// A pin on port A. |
| pub struct PA { |
| /// The pin number. |
| pin: u8, |
| } |
| |
| impl PA { |
| pub fn erase_port(self) -> Pin { |
| Pin { |
| port: Port::A, |
| pin: self.pin, |
| } |
| } |
| } |
| |
| pub struct Pin { |
| port: Port, |
| pin: u8, |
| // (these fields can be packed to reduce the memory footprint) |
| } |
| |
| enum Port { |
| A, |
| B, |
| C, |
| D, |
| } |
| ``` |
| |
| <a id="c-pin-state"></a> |
| ## Pin state should be encoded as type parameters (C-PIN-STATE) |
| |
| Pins may be configured as input or output with different characteristics |
| depending on the chip or family. This state should be encoded in the type system |
| to prevent use of pins in incorrect states. |
| |
| Additional, chip-specific state (eg. drive strength) may also be encoded in this |
| way, using additional type parameters. |
| |
| Methods for changing the pin state should be provided as `into_input` and |
| `into_output` methods. |
| |
| Additionally, `with_{input,output}_state` methods should be provided that |
| temporarily reconfigure a pin in a different state without moving it. |
| |
| The following methods should be provided for every pin type (that is, both |
| erased and non-erased pin types should provide the same API): |
| |
| * `pub fn into_input<N: InputState>(self, input: N) -> Pin<N>` |
| * `pub fn into_output<N: OutputState>(self, output: N) -> Pin<N>` |
| * ```ignore |
| pub fn with_input_state<N: InputState, R>( |
| &mut self, |
| input: N, |
| f: impl FnOnce(&mut PA1<N>) -> R, |
| ) -> R |
| ``` |
| * ```ignore |
| pub fn with_output_state<N: OutputState, R>( |
| &mut self, |
| output: N, |
| f: impl FnOnce(&mut PA1<N>) -> R, |
| ) -> R |
| ``` |
| |
| |
| Pin state should be bounded by sealed traits. Users of the HAL should have no |
| need to add their own state. The traits can provide HAL-specific methods |
| required to implement the pin state API. |
| |
| Example: |
| |
| ```rust |
| # use std::marker::PhantomData; |
| mod sealed { |
| pub trait Sealed {} |
| } |
| |
| pub trait PinState: sealed::Sealed {} |
| pub trait OutputState: sealed::Sealed {} |
| pub trait InputState: sealed::Sealed { |
| // ... |
| } |
| |
| pub struct Output<S: OutputState> { |
| _p: PhantomData<S>, |
| } |
| |
| impl<S: OutputState> PinState for Output<S> {} |
| impl<S: OutputState> sealed::Sealed for Output<S> {} |
| |
| pub struct PushPull; |
| pub struct OpenDrain; |
| |
| impl OutputState for PushPull {} |
| impl OutputState for OpenDrain {} |
| impl sealed::Sealed for PushPull {} |
| impl sealed::Sealed for OpenDrain {} |
| |
| pub struct Input<S: InputState> { |
| _p: PhantomData<S>, |
| } |
| |
| impl<S: InputState> PinState for Input<S> {} |
| impl<S: InputState> sealed::Sealed for Input<S> {} |
| |
| pub struct Floating; |
| pub struct PullUp; |
| pub struct PullDown; |
| |
| impl InputState for Floating {} |
| impl InputState for PullUp {} |
| impl InputState for PullDown {} |
| impl sealed::Sealed for Floating {} |
| impl sealed::Sealed for PullUp {} |
| impl sealed::Sealed for PullDown {} |
| |
| pub struct PA1<S: PinState> { |
| _p: PhantomData<S>, |
| } |
| |
| impl<S: PinState> PA1<S> { |
| pub fn into_input<N: InputState>(self, input: N) -> PA1<Input<N>> { |
| todo!() |
| } |
| |
| pub fn into_output<N: OutputState>(self, output: N) -> PA1<Output<N>> { |
| todo!() |
| } |
| |
| pub fn with_input_state<N: InputState, R>( |
| &mut self, |
| input: N, |
| f: impl FnOnce(&mut PA1<N>) -> R, |
| ) -> R { |
| todo!() |
| } |
| |
| pub fn with_output_state<N: OutputState, R>( |
| &mut self, |
| output: N, |
| f: impl FnOnce(&mut PA1<N>) -> R, |
| ) -> R { |
| todo!() |
| } |
| } |
| |
| // Same for `PA` and `Pin`, and other pin types. |
| ``` |