GPIO 接口的建议
默认情况下,引脚类型为零大小(C-ZST-PIN)
HAL 公开的 GPIO 接口应为每个接口或端口上的每个引脚提供专用的零大小类型,当所有引脚分配在静态已知时,这将导致零成本 GPIO 抽象。
每个 GPIO 接口或端口应实现一个 split
方法,该方法返回一个包含每个引脚的结构体。
示例
#![allow(unused)] fn main() { 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, // ... } }
引脚类型提供方法来擦除引脚和端口(C-ERASED-PIN)
引脚应提供类型擦除方法,这些方法将它们的属性从编译时移动到运行时,并允许应用程序具有更大的灵活性。
示例
#![allow(unused)] fn main() { /// 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, } }
引脚状态应编码为类型参数(C-PIN-STATE)
引脚可以配置为输入或输出,具有取决于芯片或系列的不同特性。此状态应编码在类型系统中,以防止在不正确状态下使用引脚。
其他特定于芯片的状态(例如驱动强度)也可以通过这种方式编码,使用额外的类型参数。
用于更改引脚状态的方法应作为 into_input
和 into_output
方法提供。
此外,应提供 with_{input,output}_state
方法,这些方法暂时以不同的状态重新配置引脚,而不会移动它。
以下方法应为每种引脚类型提供(即,擦除和未擦除的引脚类型都应提供相同的 API)
pub fn into_input<N: InputState>(self, input: N) -> Pin<N>
pub fn into_output<N: OutputState>(self, output: N) -> Pin<N>
-
pub fn with_input_state<N: InputState, R>( &mut self, input: N, f: impl FnOnce(&mut PA1<N>) -> R, ) -> R
-
pub fn with_output_state<N: OutputState, R>( &mut self, output: N, f: impl FnOnce(&mut PA1<N>) -> R, ) -> R
引脚状态应受密封特征约束。HAL 的用户无需添加自己的状态。这些特征可以提供实现引脚状态 API 所需的特定于 HAL 的方法。
示例
#![allow(unused)] fn main() { 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. }