diff --git a/src/gpio.rs b/src/gpio.rs index cd374b2..4f5eb94 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -43,7 +43,19 @@ //! If you need a more temporary mode change, and can not use the `into_` functions for //! ownership reasons, you can use the closure based `with_` functions to temporarily change the pin type, do //! some output or input, and then have it change back once done. +//! +//! ### Dynamic Mode Change +//! The above mode change methods guarantee that you can only call input functions when the pin is +//! in input mode, and output when in output modes, but can lead to some issues. Therefore, there +//! is also a mode where the state is kept track of at runtime, allowing you to change the mode +//! often, and without problems with ownership, or references, at the cost of some performance and +//! the risk of runtime errors. +//! +//! To make a pin dynamic, use the `into_dynamic` function, and then use the `make_` functions to +//! change the mode +mod convert; +mod dynamic; mod erased; mod exti; mod gpio_def; @@ -51,10 +63,12 @@ mod partially_erased; use core::{fmt, marker::PhantomData}; -pub use embedded_hal::digital::PinState; - use crate::rcc::ResetEnable; +pub use convert::PinMode; +pub use dynamic::{Dynamic, DynamicPin}; +pub use embedded_hal::digital::PinState; + pub use erased::{EPin, ErasedPin}; pub use exti::ExtiPin; pub use gpio_def::*; @@ -166,10 +180,8 @@ mod marker { /// Marker trait for active pin modes pub trait Active {} /// Marker trait for all pin modes except alternate - #[allow(dead_code)] // TODO: Remove when alternate function conversion is implemented pub trait NotAlt {} /// Marker trait for pins with alternate function `A` mapping - #[allow(dead_code)] // TODO: Remove when alternate function conversion is implemented pub trait IntoAf {} } diff --git a/src/gpio/convert.rs b/src/gpio/convert.rs new file mode 100644 index 0000000..aecd510 --- /dev/null +++ b/src/gpio/convert.rs @@ -0,0 +1,442 @@ +use super::*; + +impl + Pin> +{ + /// Turns pin alternate configuration pin into open drain + pub fn set_open_drain(self) -> Pin> { + self.into_mode() + } +} + +impl< + const P: char, + const N: u8, + MODE: PinMode + marker::NotAlt, + const A: u8, + Otype, + > From> for Pin> +where + Alternate: PinMode, + Self: marker::IntoAf, +{ + #[inline(always)] + fn from(f: Pin) -> Self { + f.into_mode() + } +} + +impl + From>> + for Pin> +where + Self: marker::IntoAf, +{ + #[inline(always)] + fn from(f: Pin>) -> Self { + f.into_mode() + } +} + +impl From>> + for Pin +where + Output: PinMode, +{ + #[inline(always)] + fn from(f: Pin>) -> Self { + f.into_mode() + } +} + +impl From> for Pin { + #[inline(always)] + fn from(f: Pin) -> Self { + f.into_mode() + } +} + +impl + From>> for Pin +where + Alternate: PinMode, + MODE: PinMode + marker::NotAlt, +{ + #[inline(always)] + fn from(f: Pin>) -> Self { + f.into_mode() + } +} + +impl From> + for Pin> +where + Output: PinMode, +{ + #[inline(always)] + fn from(f: Pin) -> Self { + f.into_mode() + } +} + +impl From> + for Pin> +where + Output: PinMode, +{ + #[inline(always)] + fn from(f: Pin) -> Self { + f.into_mode() + } +} + +impl From>> + for Pin> +{ + #[inline(always)] + fn from(f: Pin>) -> Self { + f.into_mode() + } +} + +impl From>> + for Pin> +{ + #[inline(always)] + fn from(f: Pin>) -> Self { + f.into_mode() + } +} + +impl From> for Pin { + #[inline(always)] + fn from(f: Pin) -> Self { + f.into_mode() + } +} + +impl From>> + for Pin +where + Output: PinMode, +{ + #[inline(always)] + fn from(f: Pin>) -> Self { + f.into_mode() + } +} + +impl Pin { + /// Configures the pin to operate alternate mode + pub fn into_alternate( + self, + ) -> Pin> + where + Self: marker::IntoAf, + { + self.into_mode() + } + + /// Configures the pin to operate in alternate open drain mode + #[allow(path_statements)] + pub fn into_alternate_open_drain( + self, + ) -> Pin> + where + Self: marker::IntoAf, + { + self.into_mode() + } + + /// Configures the pin to operate as a input pin + pub fn into_input(self) -> Pin { + self.into_mode() + } + + /// Configures the pin to operate as a floating input pin + pub fn into_floating_input(self) -> Pin { + self.into_mode().internal_resistor(Pull::None) + } + + /// Configures the pin to operate as a pulled down input pin + pub fn into_pull_down_input(self) -> Pin { + self.into_mode().internal_resistor(Pull::Down) + } + + /// Configures the pin to operate as a pulled up input pin + pub fn into_pull_up_input(self) -> Pin { + self.into_mode().internal_resistor(Pull::Up) + } + + /// Configures the pin to operate as an open drain output pin + /// Initial state will be low. + pub fn into_open_drain_output(self) -> Pin> { + self.into_mode() + } + + /// Configures the pin to operate as an open-drain output pin. + /// `initial_state` specifies whether the pin should be initially high or low. + pub fn into_open_drain_output_in_state( + mut self, + initial_state: PinState, + ) -> Pin> { + self._set_state(initial_state); + self.into_mode() + } + + /// Configures the pin to operate as an push pull output pin + /// Initial state will be low. + pub fn into_push_pull_output(mut self) -> Pin> { + self._set_low(); + self.into_mode() + } + + /// Configures the pin to operate as an push-pull output pin. + /// `initial_state` specifies whether the pin should be initially high or low. + pub fn into_push_pull_output_in_state( + mut self, + initial_state: PinState, + ) -> Pin> { + self._set_state(initial_state); + self.into_mode() + } + + /// Configures the pin to operate as an analog input pin + pub fn into_analog(self) -> Pin { + self.into_mode() + } + + /// Configures the pin as a pin that can change between input + /// and output without changing the type. It starts out + /// as a floating input + pub fn into_dynamic(self) -> DynamicPin { + self.into_floating_input(); + DynamicPin::new(Dynamic::InputFloating) + } + + /// Puts `self` into mode `M`. + /// + /// This violates the type state constraints from `MODE`, so callers must + /// ensure they use this properly. + #[inline(always)] + pub(super) fn mode(&mut self) { + let offset = 2 * N; + unsafe { + if MODE::OTYPER != M::OTYPER { + if let Some(otyper) = M::OTYPER { + (*Gpio::

::ptr()).otyper().modify(|r, w| { + w.bits(r.bits() & !(0b1 << N) | (otyper << N)) + }); + } + } + + if MODE::AFR != M::AFR { + if let Some(afr) = M::AFR { + if N < 8 { + let offset2 = 4 * { N }; + (*Gpio::

::ptr()).afrl().modify(|r, w| { + w.bits( + (r.bits() & !(0b1111 << offset2)) + | (afr << offset2), + ) + }); + } else { + let offset2 = 4 * { N - 8 }; + (*Gpio::

::ptr()).afrh().modify(|r, w| { + w.bits( + (r.bits() & !(0b1111 << offset2)) + | (afr << offset2), + ) + }); + } + } + } + + if MODE::MODER != M::MODER { + (*Gpio::

::ptr()).moder().modify(|r, w| { + w.bits( + (r.bits() & !(0b11 << offset)) | (M::MODER << offset), + ) + }); + } + } + } + + #[inline(always)] + /// Converts pin into specified mode + pub fn into_mode(mut self) -> Pin { + self.mode::(); + Pin::new() + } +} + +impl Pin +where + MODE: PinMode, +{ + fn with_mode(&mut self, f: F) -> R + where + M: PinMode, + F: FnOnce(&mut Pin) -> R, + { + self.mode::(); // change physical mode, without changing typestate + + // This will reset the pin back to the original mode when dropped. + // (so either when `with_mode` returns or when `f` unwinds) + let mut resetti = ResetMode::::new(); + + f(&mut resetti.pin) + } + + /// Temporarily configures this pin as a input. + /// + /// The closure `f` is called with the reconfigured pin. After it returns, + /// the pin will be configured back. + pub fn with_input( + &mut self, + f: impl FnOnce(&mut Pin) -> R, + ) -> R { + self.with_mode(f) + } + + /// Temporarily configures this pin as an analog pin. + /// + /// The closure `f` is called with the reconfigured pin. After it returns, + /// the pin will be configured back. + pub fn with_analog( + &mut self, + f: impl FnOnce(&mut Pin) -> R, + ) -> R { + self.with_mode(f) + } + + /// Temporarily configures this pin as an open drain output. + /// + /// The closure `f` is called with the reconfigured pin. After it returns, + /// the pin will be configured back. + /// The value of the pin after conversion is undefined. If you + /// want to control it, use `with_open_drain_output_in_state` + pub fn with_open_drain_output( + &mut self, + f: impl FnOnce(&mut Pin>) -> R, + ) -> R { + self.with_mode(f) + } + + /// Temporarily configures this pin as an open drain output . + /// + /// The closure `f` is called with the reconfigured pin. After it returns, + /// the pin will be configured back. + /// Note that the new state is set slightly before conversion + /// happens. This can cause a short output glitch if switching + /// between output modes + pub fn with_open_drain_output_in_state( + &mut self, + state: PinState, + f: impl FnOnce(&mut Pin>) -> R, + ) -> R { + self._set_state(state); + self.with_mode(f) + } + + /// Temporarily configures this pin as a push-pull output. + /// + /// The closure `f` is called with the reconfigured pin. After it returns, + /// the pin will be configured back. + /// The value of the pin after conversion is undefined. If you + /// want to control it, use `with_push_pull_output_in_state` + pub fn with_push_pull_output( + &mut self, + f: impl FnOnce(&mut Pin>) -> R, + ) -> R { + self.with_mode(f) + } + + /// Temporarily configures this pin as a push-pull output. + /// + /// The closure `f` is called with the reconfigured pin. After it returns, + /// the pin will be configured back. + /// Note that the new state is set slightly before conversion + /// happens. This can cause a short output glitch if switching + /// between output modes + pub fn with_push_pull_output_in_state( + &mut self, + state: PinState, + f: impl FnOnce(&mut Pin>) -> R, + ) -> R { + self._set_state(state); + self.with_mode(f) + } +} + +/// Wrapper around a pin that transitions the pin to mode ORIG when dropped +struct ResetMode { + pub pin: Pin, + _mode: PhantomData, +} +impl + ResetMode +{ + fn new() -> Self { + Self { + pin: Pin::new(), + _mode: PhantomData, + } + } +} +impl Drop + for ResetMode +{ + fn drop(&mut self) { + self.pin.mode::(); + } +} + +/// Marker trait for valid pin modes (type state). +/// +/// This trait is sealed and cannot be implemented by outside types +pub trait PinMode: crate::Sealed { + // These constants are used to implement the pin configuration code. + // They are not part of public API. + + #[doc(hidden)] + const MODER: u32 = u32::MAX; + #[doc(hidden)] + const OTYPER: Option = None; + #[doc(hidden)] + const AFR: Option = None; +} + +impl crate::Sealed for Input {} +impl PinMode for Input { + const MODER: u32 = 0b00; +} + +impl crate::Sealed for Analog {} +impl PinMode for Analog { + const MODER: u32 = 0b11; +} + +impl crate::Sealed for Output {} +impl PinMode for Output { + const MODER: u32 = 0b01; + const OTYPER: Option = Some(0b1); +} + +impl PinMode for Output { + const MODER: u32 = 0b01; + const OTYPER: Option = Some(0b0); +} + +impl crate::Sealed for Alternate {} +impl PinMode for Alternate { + const MODER: u32 = 0b10; + const OTYPER: Option = Some(0b1); + const AFR: Option = Some(A as _); +} + +impl PinMode for Alternate { + const MODER: u32 = 0b10; + const OTYPER: Option = Some(0b0); + const AFR: Option = Some(A as _); +} diff --git a/src/gpio/dynamic.rs b/src/gpio/dynamic.rs new file mode 100644 index 0000000..968a2f3 --- /dev/null +++ b/src/gpio/dynamic.rs @@ -0,0 +1,149 @@ +use super::*; + +/// Pin type with dynamic mode +/// +/// - `P` is port name: `A` for GPIOA, `B` for GPIOB, etc. +/// - `N` is pin number: from `0` to `15`. +pub struct DynamicPin { + /// Current pin mode + pub(crate) mode: Dynamic, +} + +/// Tracks the current pin state for dynamic pins +pub enum Dynamic { + /// Floating input mode + InputFloating, + /// Pull-up input mode + InputPullUp, + /// Pull-down input mode + InputPullDown, + /// Push-pull output mode + OutputPushPull, + /// Open-drain output mode + OutputOpenDrain, +} + +/// Error for [DynamicPin] +#[derive(Debug, PartialEq, Eq)] +pub enum PinModeError { + /// For operations unsupported in current mode + IncorrectMode, +} + +impl Dynamic { + /// Is pin in readable mode + pub fn is_input(&self) -> bool { + use Dynamic::*; + match self { + InputFloating | InputPullUp | InputPullDown | OutputOpenDrain => { + true + } + OutputPushPull => false, + } + } + + /// Is pin in writable mode + pub fn is_output(&self) -> bool { + use Dynamic::*; + match self { + InputFloating | InputPullUp | InputPullDown => false, + OutputPushPull | OutputOpenDrain => true, + } + } +} + +// For convertion simplify +struct Unknown; + +impl crate::Sealed for Unknown {} +impl PinMode for Unknown {} + +impl DynamicPin { + pub(super) const fn new(mode: Dynamic) -> Self { + Self { mode } + } + + /// Switch pin into pull-up input + #[inline] + pub fn make_pull_up_input(&mut self) { + // NOTE(unsafe), we have a mutable reference to the current pin + Pin::::new().into_pull_up_input(); + self.mode = Dynamic::InputPullUp; + } + /// Switch pin into pull-down input + #[inline] + pub fn make_pull_down_input(&mut self) { + // NOTE(unsafe), we have a mutable reference to the current pin + Pin::::new().into_pull_down_input(); + self.mode = Dynamic::InputPullDown; + } + /// Switch pin into floating input + #[inline] + pub fn make_floating_input(&mut self) { + // NOTE(unsafe), we have a mutable reference to the current pin + Pin::::new().into_floating_input(); + self.mode = Dynamic::InputFloating; + } + /// Switch pin into push-pull output + #[inline] + pub fn make_push_pull_output(&mut self) { + // NOTE(unsafe), we have a mutable reference to the current pin + Pin::::new().into_push_pull_output(); + self.mode = Dynamic::OutputPushPull; + } + /// Switch pin into push-pull output with required voltage state + #[inline] + pub fn make_push_pull_output_in_state(&mut self, state: PinState) { + // NOTE(unsafe), we have a mutable reference to the current pin + Pin::::new().into_push_pull_output_in_state(state); + self.mode = Dynamic::OutputPushPull; + } + /// Switch pin into open-drain output + #[inline] + pub fn make_open_drain_output(&mut self) { + // NOTE(unsafe), we have a mutable reference to the current pin + Pin::::new().into_open_drain_output(); + self.mode = Dynamic::OutputOpenDrain; + } + /// Switch pin into open-drain output with required voltage state + #[inline] + pub fn make_open_drain_output_in_state(&mut self, state: PinState) { + // NOTE(unsafe), we have a mutable reference to the current pin + Pin::::new().into_open_drain_output_in_state(state); + self.mode = Dynamic::OutputOpenDrain; + } + + /// Drives the pin high + pub fn set_high(&mut self) -> Result<(), PinModeError> { + if self.mode.is_output() { + Pin::::new()._set_state(PinState::High); + Ok(()) + } else { + Err(PinModeError::IncorrectMode) + } + } + + /// Drives the pin low + pub fn set_low(&mut self) -> Result<(), PinModeError> { + if self.mode.is_output() { + Pin::::new()._set_state(PinState::Low); + Ok(()) + } else { + Err(PinModeError::IncorrectMode) + } + } + + /// Is the input pin high? + pub fn is_high(&self) -> Result { + self.is_low().map(|b| !b) + } + + /// Is the input pin low? + pub fn is_low(&self) -> Result { + if self.mode.is_input() { + Ok(Pin::::new()._is_low()) + } else { + Err(PinModeError::IncorrectMode) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 1905f2c..e106df7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,6 +55,14 @@ pub mod gpio; #[cfg(feature = "device-selected")] pub mod icache; +#[cfg(feature = "device-selected")] +mod sealed { + pub trait Sealed {} +} + +#[cfg(feature = "device-selected")] +pub(crate) use sealed::Sealed; + /// Get the name of the type without the module prefix(es) fn stripped_type_name() -> &'static str { let s = core::any::type_name::();