Skip to content

Commit

Permalink
gpio: add ErasedPin and PartiallyErasedPin (#19)
Browse files Browse the repository at this point in the history
Add pin type erasure so that pins can be used interchangeably as the
same type. Pin port and number are stored in the struct instead of being
encoded in the type.

This is taken directly from the implementation in
[stm32h7xx-hal](https://github.com/stm32-rs/stm32h7xx-hal).
  • Loading branch information
astapleton authored Oct 10, 2024
1 parent 9510815 commit 5118c26
Show file tree
Hide file tree
Showing 4 changed files with 352 additions and 0 deletions.
44 changes: 44 additions & 0 deletions src/gpio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,21 @@
//! ownership reasons, you can use the closure based `with_<mode>` functions to temporarily change the pin type, do
//! some output or input, and then have it change back once done.
mod erased;
mod exti;
mod gpio_def;
mod partially_erased;

use core::{fmt, marker::PhantomData};

pub use embedded_hal::digital::PinState;

use crate::rcc::ResetEnable;

pub use erased::{EPin, ErasedPin};
pub use exti::ExtiPin;
pub use gpio_def::*;
pub use partially_erased::{PEPin, PartiallyErasedPin};

/// A filler pin type
#[derive(Debug)]
Expand Down Expand Up @@ -357,6 +361,46 @@ where
}
}

impl<const P: char, const N: u8, MODE> Pin<P, N, MODE> {
/// 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<P, MODE> {
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<MODE> {
ErasedPin::new(P as u8 - b'A', N)
}
}

impl<const P: char, const N: u8, MODE> From<Pin<P, N, MODE>>
for PartiallyErasedPin<P, MODE>
{
/// Pin-to-partially erased pin conversion using the [`From`] trait.
///
/// Note that [`From`] is the reciprocal of [`Into`].
fn from(p: Pin<P, N, MODE>) -> Self {
p.erase_number()
}
}

impl<const P: char, const N: u8, MODE> From<Pin<P, N, MODE>>
for ErasedPin<MODE>
{
/// Pin-to-erased pin conversion using the [`From`] trait.
///
/// Note that [`From`] is the reciprocal of [`Into`].
fn from(p: Pin<P, N, MODE>) -> Self {
p.erase()
}
}

impl<const P: char, const N: u8, MODE> Pin<P, N, MODE> {
/// Set the output of the pin regardless of its mode.
/// Primarily used to set the output value of the pin
Expand Down
156 changes: 156 additions & 0 deletions src/gpio/erased.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
use super::*;

pub type EPin<MODE> = ErasedPin<MODE>;

/// Fully erased pin
///
/// `MODE` is one of the pin modes (see [Modes](crate::gpio#modes) section).
pub struct ErasedPin<MODE> {
// Bits 0-3: Pin, Bits 4-7: Port
pin_port: u8,
_mode: PhantomData<MODE>,
}

impl<MODE> fmt::Debug for ErasedPin<MODE> {
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::<MODE>()
))
}
}

#[cfg(feature = "defmt")]
impl<MODE> defmt::Format for ErasedPin<MODE> {
fn format(&self, f: defmt::Formatter) {
defmt::write!(
f,
"P({}{})<{}>",
self.port_id(),
self.pin_id(),
crate::stripped_type_name::<MODE>()
);
}
}

impl<MODE> PinExt for ErasedPin<MODE> {
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<MODE> ErasedPin<MODE> {
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<MODE> ErasedPin<Output<MODE>> {
/// 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<MODE> ErasedPin<MODE>
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
}
}
3 changes: 3 additions & 0 deletions src/gpio/gpio_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ macro_rules! gpio {
}
}

#[doc=concat!("Common type for GPIO", $port_id, " related pins")]
pub type $PXn<MODE> = crate::gpio::PartiallyErasedPin<$port_id, MODE>;

$(
#[doc=concat!("P", $port_id, $i, " pin")]
pub type $PXi<MODE = crate::gpio::Analog> = crate::gpio::Pin<$port_id, $i, MODE>;
Expand Down
149 changes: 149 additions & 0 deletions src/gpio/partially_erased.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
use super::*;

pub type PEPin<const P: char, MODE> = PartiallyErasedPin<P, MODE>;

/// 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<const P: char, MODE> {
i: u8,
_mode: PhantomData<MODE>,
}

impl<const P: char, MODE> PartiallyErasedPin<P, MODE> {
pub(crate) fn new(i: u8) -> Self {
Self {
i,
_mode: PhantomData,
}
}
}

impl<const P: char, MODE> fmt::Debug for PartiallyErasedPin<P, MODE> {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_fmt(format_args!(
"P{}({})<{}>",
P,
self.i,
crate::stripped_type_name::<MODE>()
))
}
}

#[cfg(feature = "defmt")]
impl<const P: char, MODE> defmt::Format for PartiallyErasedPin<P, MODE> {
fn format(&self, f: defmt::Formatter) {
defmt::write!(
f,
"P{}({})<{}>",
P,
self.i,
crate::stripped_type_name::<MODE>()
);
}
}

impl<const P: char, MODE> PinExt for PartiallyErasedPin<P, MODE> {
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<const P: char, MODE> PartiallyErasedPin<P, Output<MODE>> {
/// Drives the pin high
#[inline(always)]
pub fn set_high(&mut self) {
// NOTE(unsafe) atomic write to a stateless register
unsafe { (*Gpio::<P>::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::<P>::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::<P>::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<const P: char, MODE> PartiallyErasedPin<P, MODE>
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::<P>::ptr()).idr().read().bits() & (1 << self.i) == 0 }
}
}

impl<const P: char, MODE> From<PartiallyErasedPin<P, MODE>>
for ErasedPin<MODE>
{
/// Partially erased pin-to-erased pin conversion using the [`From`] trait.
///
/// Note that [`From`] is the reciprocal of [`Into`].
fn from(p: PartiallyErasedPin<P, MODE>) -> Self {
ErasedPin::new(P as u8 - b'A', p.i)
}
}

0 comments on commit 5118c26

Please sign in to comment.