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_inputinto_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.
}