Skip to content

Commit

Permalink
gpio: add ErasedPin and PartiallyErasedPin
Browse files Browse the repository at this point in the history
  • Loading branch information
astapleton committed Oct 10, 2024
1 parent 9510815 commit c7bce2e
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 c7bce2e

Please sign in to comment.