Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Empfehlungen für GPIO-Schnittstellen

Pin-Typen haben standardmäßig die Größe Null (C-ZST-PIN)

Die von der HAL bereitgestellten GPIO-Schnittstellen sollten für jeden Pin jeder Schnittstelle oder jedes Ports einen dedizierten Datentyp der Größe Null bereitstellen. Dies führt zu einer kostenlosen GPIO-Abstraktion, wenn alle Pinbelegungen statisch bekannt sind.

Jede GPIO-Schnittstelle oder jeder Port sollte eine split-Methode implementieren, die eine Struktur mit allen Pins zurückgibt.

Beispiel:

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,
    // ...
}

Pin-Typen stellen Methoden zum Löschen von Pins und Ports bereit (C-ERASED-PIN)

Pins sollten Methoden zur Typeliminierung (Type Erasure) bereitstellen, die ihre Eigenschaften von der Kompilierzeit in die Laufzeit verlagern und so mehr Flexibilität in Anwendungen ermöglichen.

Beispiel:

/// 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,
    // (Diese Felder koennen gepackt werden, um den Speicherbedarf zu 
    // verringern.)
}

enum Port {
    A,
    B,
    C,
    D,
}

Der Pin-Zustand sollte als Typparameter kodiert werden (C-PIN-STATE)

Je nach Chip oder Chipfamilie können Pins als Eingang oder Ausgang mit unterschiedlichen Eigenschaften konfiguriert werden. Dieser Zustand sollte im Typsystem abgebildet werden, um eine Verwendung der Pins in einem falschen Zustand zu verhindern.

Auch zusätzliche, chip-spezifische Zustände (z. B. die Treiberstärke) lassen sich auf diese Weise mittels zusätzlicher Typparameter kodieren.

Methoden zur Änderung des Pin-Zustands sollten als into_input und into_output bereitgestellt werden.

Zusätzlich sollten Methoden wie with_{input,output}_state angeboten werden, die einen Pin vorübergehend in einen anderen Zustand versetzen, ohne ihn zu verschieben (d. h. ohne Eigentumsübergang).

Für jeden Pin-Typ sollten die folgenden Methoden bereitgestellt werden (das heißt, sowohl „erased“ als auch „non-erased” Pin-Typen müssen dieselbe API bieten):

  • 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

Der Pin-Zustand sollte durch „Sealed Traits“ eingeschränkt sein. Nutzer des HAL sollten keinen eigenen Zustand hinzufügen müssen. Die Traits können HAL-spezifische Methoden bereitstellen, die für die Implementierung der Pin-Zustands-API erforderlich sind.

Beispiel:

# 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!()
    }
}

// Dasselbe gilt fuer `PA` und `Pin` sowie andere Pin-Typen.