-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
gpio: add ErasedPin and PartiallyErasedPin (#19)
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
1 parent
9510815
commit 5118c26
Showing
4 changed files
with
352 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |