From 7fe658a220c5dbed4079543453eef5af0959d046 Mon Sep 17 00:00:00 2001 From: astapleton Date: Wed, 31 Jul 2024 15:19:57 -0700 Subject: [PATCH] gpio: add ErasedPin and PartiallyErasedPin --- src/gpio.rs | 44 ++++++++++ src/gpio/erased.rs | 156 +++++++++++++++++++++++++++++++++++ src/gpio/gpio_def.rs | 3 + src/gpio/partially_erased.rs | 149 +++++++++++++++++++++++++++++++++ 4 files changed, 352 insertions(+) create mode 100644 src/gpio/erased.rs create mode 100644 src/gpio/partially_erased.rs diff --git a/src/gpio.rs b/src/gpio.rs index 901f22c..131fd0a 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -44,7 +44,9 @@ //! 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. +mod erased; mod gpio_def; +mod partially_erased; use core::{fmt, marker::PhantomData}; @@ -52,7 +54,9 @@ pub use embedded_hal::digital::PinState; use crate::rcc::ResetEnable; +pub use erased::{EPin, ErasedPin}; pub use gpio_def::*; +pub use partially_erased::{PEPin, PartiallyErasedPin}; /// A filler pin type #[derive(Debug)] @@ -356,6 +360,46 @@ where } } +impl Pin { + /// Erases the pin number from the type + /// + /// This is useful when you want to collect the pins into an array where you + /// need all the elements to have the same type + pub fn erase_number(self) -> PartiallyErasedPin { + PartiallyErasedPin::new(N) + } + + /// Erases the pin number and the port from the type + /// + /// This is useful when you want to collect the pins into an array where you + /// need all the elements to have the same type + pub fn erase(self) -> ErasedPin { + ErasedPin::new(P as u8 - b'A', N) + } +} + +impl From> + for PartiallyErasedPin +{ + /// Pin-to-partially erased pin conversion using the [`From`] trait. + /// + /// Note that [`From`] is the reciprocal of [`Into`]. + fn from(p: Pin) -> Self { + p.erase_number() + } +} + +impl From> + for ErasedPin +{ + /// Pin-to-erased pin conversion using the [`From`] trait. + /// + /// Note that [`From`] is the reciprocal of [`Into`]. + fn from(p: Pin) -> Self { + p.erase() + } +} + impl Pin { /// Set the output of the pin regardless of its mode. /// Primarily used to set the output value of the pin diff --git a/src/gpio/erased.rs b/src/gpio/erased.rs new file mode 100644 index 0000000..a59dfe5 --- /dev/null +++ b/src/gpio/erased.rs @@ -0,0 +1,156 @@ +use super::*; + +pub type EPin = ErasedPin; + +/// Fully erased pin +/// +/// `MODE` is one of the pin modes (see [Modes](crate::gpio#modes) section). +pub struct ErasedPin { + // Bits 0-3: Pin, Bits 4-7: Port + pin_port: u8, + _mode: PhantomData, +} + +impl fmt::Debug for ErasedPin { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_fmt(format_args!( + "P({}{})<{}>", + self.port_id(), + self.pin_id(), + crate::stripped_type_name::() + )) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for ErasedPin { + fn format(&self, f: defmt::Formatter) { + defmt::write!( + f, + "P({}{})<{}>", + self.port_id(), + self.pin_id(), + crate::stripped_type_name::() + ); + } +} + +impl PinExt for ErasedPin { + type Mode = MODE; + + #[inline(always)] + fn pin_id(&self) -> u8 { + self.pin_port & 0x0f + } + #[inline(always)] + fn port_id(&self) -> u8 { + self.pin_port >> 4 + } +} + +impl ErasedPin { + pub(crate) fn new(port: u8, pin: u8) -> Self { + Self { + pin_port: port << 4 | pin, + _mode: PhantomData, + } + } + + #[inline] + fn block(&self) -> &crate::pac::gpioa::RegisterBlock { + // This function uses pointer arithmetic instead of branching to be more efficient + + // The logic relies on the following assumptions: + // - GPIOA register is available on all chips + // - all gpio register blocks have the same layout + // - consecutive gpio register blocks have the same offset between them, namely 0x0400 + // - ErasedPin::new was called with a valid port + + // FIXME could be calculated after const_raw_ptr_to_usize_cast stabilization #51910 + const GPIO_REGISTER_OFFSET: usize = 0x0400; + + let offset = GPIO_REGISTER_OFFSET * self.port_id() as usize; + let block_ptr = (crate::pac::GPIOA::ptr() as usize + offset) + as *const crate::pac::gpioa::RegisterBlock; + + unsafe { &*block_ptr } + } +} + +impl ErasedPin> { + /// Drives the pin high + #[inline(always)] + pub fn set_high(&mut self) { + // NOTE(unsafe) atomic write to a stateless register + unsafe { self.block().bsrr().write(|w| w.bits(1 << self.pin_id())) }; + } + + /// Drives the pin low + #[inline(always)] + pub fn set_low(&mut self) { + // NOTE(unsafe) atomic write to a stateless register + unsafe { + self.block() + .bsrr() + .write(|w| w.bits(1 << (self.pin_id() + 16))) + }; + } + + /// Is the pin in drive high or low mode? + #[inline(always)] + pub fn get_state(&mut self) -> PinState { + if self.is_set_low() { + PinState::Low + } else { + PinState::High + } + } + + /// Drives the pin high or low depending on the provided value + #[inline(always)] + pub fn set_state(&mut self, state: PinState) { + match state { + PinState::Low => self.set_low(), + PinState::High => self.set_high(), + } + } + + /// Is the pin in drive high mode? + #[inline(always)] + pub fn is_set_high(&mut self) -> bool { + !self.is_set_low() + } + + /// Is the pin in drive low mode? + #[inline(always)] + pub fn is_set_low(&mut self) -> bool { + self.block().odr().read().bits() & (1 << self.pin_id()) == 0 + } + + /// Toggle pin output + #[inline(always)] + pub fn toggle(&mut self) { + if self.is_set_low() { + self.set_high() + } else { + self.set_low() + } + } +} + +impl ErasedPin +where + MODE: marker::Readable, +{ + /// Is the input pin high? + #[inline(always)] + pub fn is_high(&mut self) -> bool { + !self.is_low() + } + + /// Is the input pin low? + #[inline(always)] + pub fn is_low(&mut self) -> bool { + self.block().idr().read().bits() & (1 << self.pin_id()) == 0 + } +} diff --git a/src/gpio/gpio_def.rs b/src/gpio/gpio_def.rs index 32245d8..9ef04e7 100644 --- a/src/gpio/gpio_def.rs +++ b/src/gpio/gpio_def.rs @@ -42,6 +42,9 @@ macro_rules! gpio { } } + #[doc=concat!("Common type for GPIO", $port_id, " related pins")] + pub type $PXn = crate::gpio::PartiallyErasedPin<$port_id, MODE>; + $( #[doc=concat!("P", $port_id, $i, " pin")] pub type $PXi = crate::gpio::Pin<$port_id, $i, MODE>; diff --git a/src/gpio/partially_erased.rs b/src/gpio/partially_erased.rs new file mode 100644 index 0000000..4e48417 --- /dev/null +++ b/src/gpio/partially_erased.rs @@ -0,0 +1,149 @@ +use super::*; + +pub type PEPin = PartiallyErasedPin; + +/// Partially erased pin +/// +/// - `MODE` is one of the pin modes (see [Modes](crate::gpio#modes) section). +/// - `P` is port name: `A` for GPIOA, `B` for GPIOB, etc. +pub struct PartiallyErasedPin { + i: u8, + _mode: PhantomData, +} + +impl PartiallyErasedPin { + pub(crate) fn new(i: u8) -> Self { + Self { + i, + _mode: PhantomData, + } + } +} + +impl fmt::Debug for PartiallyErasedPin { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_fmt(format_args!( + "P{}({})<{}>", + P, + self.i, + crate::stripped_type_name::() + )) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for PartiallyErasedPin { + fn format(&self, f: defmt::Formatter) { + defmt::write!( + f, + "P{}({})<{}>", + P, + self.i, + crate::stripped_type_name::() + ); + } +} + +impl PinExt for PartiallyErasedPin { + type Mode = MODE; + + #[inline(always)] + fn pin_id(&self) -> u8 { + self.i + } + #[inline(always)] + fn port_id(&self) -> u8 { + P as u8 - b'A' + } +} + +impl PartiallyErasedPin> { + /// Drives the pin high + #[inline(always)] + pub fn set_high(&mut self) { + // NOTE(unsafe) atomic write to a stateless register + unsafe { (*Gpio::

::ptr()).bsrr().write(|w| w.bits(1 << self.i)) } + } + + /// Drives the pin low + #[inline(always)] + pub fn set_low(&mut self) { + // NOTE(unsafe) atomic write to a stateless register + unsafe { + (*Gpio::

::ptr()) + .bsrr() + .write(|w| w.bits(1 << (self.i + 16))) + } + } + + /// Is the pin in drive high or low mode? + #[inline(always)] + pub fn get_state(&mut self) -> PinState { + if self.is_set_low() { + PinState::Low + } else { + PinState::High + } + } + + /// Drives the pin high or low depending on the provided value + #[inline(always)] + pub fn set_state(&mut self, state: PinState) { + match state { + PinState::Low => self.set_low(), + PinState::High => self.set_high(), + } + } + + /// Is the pin in drive high mode? + #[inline(always)] + pub fn is_set_high(&mut self) -> bool { + !self.is_set_low() + } + + /// Is the pin in drive low mode? + #[inline(always)] + pub fn is_set_low(&mut self) -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*Gpio::

::ptr()).odr().read().bits() & (1 << self.i) == 0 } + } + + /// Toggle pin output + #[inline(always)] + pub fn toggle(&mut self) { + if self.is_set_low() { + self.set_high() + } else { + self.set_low() + } + } +} + +impl PartiallyErasedPin +where + MODE: marker::Readable, +{ + /// Is the input pin high? + #[inline(always)] + pub fn is_high(&self) -> bool { + !self.is_low() + } + + /// Is the input pin low? + #[inline(always)] + pub fn is_low(&self) -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { (*Gpio::

::ptr()).idr().read().bits() & (1 << self.i) == 0 } + } +} + +impl From> + for ErasedPin +{ + /// Partially erased pin-to-erased pin conversion using the [`From`] trait. + /// + /// Note that [`From`] is the reciprocal of [`Into`]. + fn from(p: PartiallyErasedPin) -> Self { + ErasedPin::new(P as u8 - b'A', p.i) + } +}