diff --git a/examples/pin_probe.rs b/examples/pin_probe.rs new file mode 100644 index 00000000..bcac7fea --- /dev/null +++ b/examples/pin_probe.rs @@ -0,0 +1,183 @@ +//! Example for experimenting with pinouts by shorting outputs to A0 +//! +//! Also an example for using erased pins to have a single array containing most pins on stm32. +//! +//! The example iterates through each pin, setting it high then testing if A0 went high as well, +//! then resetting the tested pin to low. +//! This allows for any pin to be quickly discovered by shorting A0 and the pin in question. +//! If multiple pins connect to the same output, it will only report the first discovered pin in the array. + +#![deny(warnings)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use stm32h7xx_hal::gpio::{erased::EPin, Floating, Input}; +use stm32h7xx_hal::hal::digital::v2::InputPin; +use stm32h7xx_hal::{delay::Delay, pac, prelude::*}; + +#[macro_use] +mod utilities; + +use log::info; + +#[entry] +fn main() -> ! { + utilities::logger::init(); + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = pac::Peripherals::take().unwrap(); + + // Constrain and Freeze power + let pwr = dp.PWR.constrain(); + let pwrcfg = example_power!(pwr).freeze(); + + // Constrain and Freeze clock + let rcc = dp.RCC.constrain(); + let ccdr = rcc.sys_ck(200.mhz()).freeze(pwrcfg, &dp.SYSCFG); + + let mut delay = Delay::new(cp.SYST, ccdr.clocks); + + let gpioa = dp.GPIOA.split(ccdr.peripheral.GPIOA); + let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB); + let gpioc = dp.GPIOC.split(ccdr.peripheral.GPIOC); + let gpiod = dp.GPIOD.split(ccdr.peripheral.GPIOD); + let gpioe = dp.GPIOE.split(ccdr.peripheral.GPIOE); + let gpiof = dp.GPIOF.split(ccdr.peripheral.GPIOF); + let gpiog = dp.GPIOG.split(ccdr.peripheral.GPIOG); + + let mut most_pins = [ + gpioa.pa0.into_floating_input().erase(), // Used as probe pin + gpioa.pa1.into_floating_input().erase(), + gpioa.pa2.into_floating_input().erase(), + gpioa.pa3.into_floating_input().erase(), + gpioa.pa4.into_floating_input().erase(), + gpioa.pa5.into_floating_input().erase(), + gpioa.pa6.into_floating_input().erase(), + gpioa.pa7.into_floating_input().erase(), + gpioa.pa8.into_floating_input().erase(), + gpioa.pa9.into_floating_input().erase(), + gpioa.pa10.into_floating_input().erase(), + gpioa.pa11.into_floating_input().erase(), + gpioa.pa12.into_floating_input().erase(), + gpioa.pa13.into_floating_input().erase(), + gpioa.pa14.into_floating_input().erase(), + gpioa.pa15.into_floating_input().erase(), + gpiob.pb0.into_floating_input().erase(), + gpiob.pb1.into_floating_input().erase(), + gpiob.pb2.into_floating_input().erase(), + gpiob.pb3.into_floating_input().erase(), + gpiob.pb4.into_floating_input().erase(), + gpiob.pb5.into_floating_input().erase(), + gpiob.pb6.into_floating_input().erase(), + gpiob.pb7.into_floating_input().erase(), + gpiob.pb8.into_floating_input().erase(), + gpiob.pb9.into_floating_input().erase(), + gpiob.pb10.into_floating_input().erase(), + gpiob.pb11.into_floating_input().erase(), + gpiob.pb12.into_floating_input().erase(), + gpiob.pb13.into_floating_input().erase(), + gpiob.pb14.into_floating_input().erase(), + gpiob.pb15.into_floating_input().erase(), + gpioc.pc0.into_floating_input().erase(), + gpioc.pc1.into_floating_input().erase(), + gpioc.pc2.into_floating_input().erase(), + gpioc.pc3.into_floating_input().erase(), + gpioc.pc4.into_floating_input().erase(), + gpioc.pc5.into_floating_input().erase(), + gpioc.pc6.into_floating_input().erase(), + gpioc.pc7.into_floating_input().erase(), + gpioc.pc8.into_floating_input().erase(), + gpioc.pc9.into_floating_input().erase(), + gpioc.pc10.into_floating_input().erase(), + gpioc.pc11.into_floating_input().erase(), + gpioc.pc12.into_floating_input().erase(), + gpioc.pc13.into_floating_input().erase(), + gpioc.pc14.into_floating_input().erase(), + gpioc.pc15.into_floating_input().erase(), + gpiod.pd0.into_floating_input().erase(), + gpiod.pd1.into_floating_input().erase(), + gpiod.pd2.into_floating_input().erase(), + gpiod.pd3.into_floating_input().erase(), + gpiod.pd4.into_floating_input().erase(), + gpiod.pd5.into_floating_input().erase(), + gpiod.pd6.into_floating_input().erase(), + gpiod.pd7.into_floating_input().erase(), + gpiod.pd8.into_floating_input().erase(), + gpiod.pd9.into_floating_input().erase(), + gpiod.pd10.into_floating_input().erase(), + gpiod.pd11.into_floating_input().erase(), + gpiod.pd12.into_floating_input().erase(), + gpiod.pd13.into_floating_input().erase(), + gpiod.pd14.into_floating_input().erase(), + gpiod.pd15.into_floating_input().erase(), + gpioe.pe0.into_floating_input().erase(), + gpioe.pe1.into_floating_input().erase(), + gpioe.pe2.into_floating_input().erase(), + gpioe.pe3.into_floating_input().erase(), + gpioe.pe4.into_floating_input().erase(), + gpioe.pe5.into_floating_input().erase(), + gpioe.pe6.into_floating_input().erase(), + gpioe.pe7.into_floating_input().erase(), + gpioe.pe8.into_floating_input().erase(), + gpioe.pe9.into_floating_input().erase(), + gpioe.pe10.into_floating_input().erase(), + gpioe.pe11.into_floating_input().erase(), + gpioe.pe12.into_floating_input().erase(), + gpioe.pe13.into_floating_input().erase(), + gpioe.pe14.into_floating_input().erase(), + gpioe.pe15.into_floating_input().erase(), + gpiof.pf0.into_floating_input().erase(), + gpiof.pf1.into_floating_input().erase(), + gpiof.pf2.into_floating_input().erase(), + gpiof.pf3.into_floating_input().erase(), + gpiof.pf4.into_floating_input().erase(), + gpiof.pf5.into_floating_input().erase(), + gpiof.pf6.into_floating_input().erase(), + gpiof.pf7.into_floating_input().erase(), + gpiof.pf8.into_floating_input().erase(), + gpiof.pf9.into_floating_input().erase(), + gpiof.pf10.into_floating_input().erase(), + gpiof.pf11.into_floating_input().erase(), + gpiof.pf12.into_floating_input().erase(), + gpiof.pf13.into_floating_input().erase(), + gpiof.pf14.into_floating_input().erase(), + gpiof.pf15.into_floating_input().erase(), + gpiog.pg0.into_floating_input().erase(), + gpiog.pg1.into_floating_input().erase(), + gpiog.pg2.into_floating_input().erase(), + gpiog.pg3.into_floating_input().erase(), + gpiog.pg4.into_floating_input().erase(), + gpiog.pg5.into_floating_input().erase(), + gpiog.pg6.into_floating_input().erase(), + gpiog.pg7.into_floating_input().erase(), + gpiog.pg8.into_floating_input().erase(), + gpiog.pg9.into_floating_input().erase(), + gpiog.pg10.into_floating_input().erase(), + gpiog.pg11.into_floating_input().erase(), + gpiog.pg12.into_floating_input().erase(), + gpiog.pg13.into_floating_input().erase(), + gpiog.pg14.into_floating_input().erase(), + gpiog.pg15.into_floating_input().erase(), + ]; + + loop { + probe_pins(&mut most_pins); + delay.delay_ms(100_u16); + } +} + +fn probe_pins<'a>(pins: &'a mut [EPin>]) -> Option { + for pin in pins.iter_mut() { + info!( + "Pin {:?}: {}", + pin, + if pin.is_high().unwrap() { + "HIGH" + } else { + "LOW" + } + ) + } + + None +} diff --git a/src/gpio.rs b/src/gpio.rs index 7a2c9aa9..b18226e2 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -145,8 +145,8 @@ pub trait ExtiPin { macro_rules! gpio { ($GPIOX:ident, $gpiox:ident, $gpio_doc:expr, $Rec:ident, $PXx:ident, $extigpionr:expr, [ - $($PXi:ident: ($pxi:ident, $i:expr, $MODE:ty, $exticri:ident),)+ - ]) => { + $($PXi:ident: ($pxi:ident, $i:expr, $MODE:ty, $exticri:ident),)+ + ]) => { #[doc=$gpio_doc] pub mod $gpiox { use core::marker::PhantomData; @@ -161,7 +161,7 @@ macro_rules! gpio { Alternate, Floating, GpioExt, Input, OpenDrain, Output, Speed, PullDown, PullUp, PushPull, AF0, AF1, AF2, AF3, AF4, AF5, AF6, AF7, AF8, AF9, AF10, AF11, - AF12, AF13, AF14, AF15, Analog, Edge, ExtiPin, }; + AF12, AF13, AF14, AF15, Analog, Edge, ExtiPin, erased::ErasedPin, }; use crate::Never; @@ -542,7 +542,7 @@ macro_rules! gpio { (*$GPIOX::ptr()).moder.modify(|r, w| { w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset)) } - )}; + )}; $PXi { _mode: PhantomData } } @@ -598,7 +598,7 @@ macro_rules! gpio { (*$GPIOX::ptr()).moder.modify(|r, w| { w.bits((r.bits() & !(0b11 << offset)) | (0b11 << offset)) } - )}; + )}; $PXi { _mode: PhantomData } } @@ -676,6 +676,19 @@ macro_rules! gpio { } impl $PXi { + /// Erases both the pin number and 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($extigpionr, $i) + // ErasedPin { + // pin_port: $extigpionr << 4 | $i, + // _mode: self._mode, + // } + } + /// Erases the pin number from the type /// /// This is useful when you want to collect the @@ -768,7 +781,7 @@ macro_rules! gpio { } impl IoPin - for $PXi> + for $PXi> { type Error = Never; fn into_input_pin(self) -> Result { @@ -1090,3 +1103,280 @@ gpio!(GPIOK, gpiok, "Port K", Gpiok, PK, 10, [ PK14: (pk14, 14, Analog, exticr4), PK15: (pk15, 15, Analog, exticr4), ]); + +pub mod erased { + use super::{ + Alternate, Analog, Floating, Input, OpenDrain, Output, PullDown, + PullUp, PushPull, + }; + use crate::Never; + use core::fmt; + use core::marker::PhantomData; + use embedded_hal::digital::v2::{ + toggleable, InputPin, OutputPin, StatefulOutputPin, + }; + 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 ErasedPin { + pub(crate) fn new(port: u8, pin: u8) -> Self { + Self { + pin_port: port << 4 | pin, + _mode: PhantomData, + } + } + + #[inline(always)] + fn pin_id(&self) -> u8 { + self.pin_port & 0x0f + } + #[inline(always)] + fn port_id(&self) -> u8 { + self.pin_port >> 4 + } + + #[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; + unsafe { &*crate::pac::GPIOA::ptr().add(offset) } + } + + /// Configures the pin to operate as a floating + /// input pin + pub fn into_floating_input(self) -> EPin> { + let offset = 2 * self.pin_id(); + let block = self.block(); + + unsafe { + block.pupdr.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset)) + }); + block.moder.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset)) + }) + }; + + EPin { + pin_port: self.pin_port, + _mode: PhantomData, + } + } + + /// Configures the pin to operate as a pulled down + /// input pin + pub fn into_pull_down_input(self) -> EPin> { + let offset = 2 * self.pin_id(); + let block = self.block(); + + unsafe { + block.pupdr.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b10 << offset)) + }); + block.moder.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset)) + }) + }; + + EPin { + pin_port: self.pin_port, + _mode: PhantomData, + } + } + + /// Configures the pin to operate as a pulled up + /// input pin + pub fn into_pull_up_input(self) -> EPin> { + let offset = 2 * self.pin_id(); + let block = self.block(); + + unsafe { + block.pupdr.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b01 << offset)) + }); + block.moder.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset)) + }) + }; + + EPin { + pin_port: self.pin_port, + _mode: PhantomData, + } + } + + /// Configures the pin to operate as an open drain + /// output pin + pub fn into_open_drain_output(self) -> EPin> { + let offset = 2 * self.pin_id(); + let block = self.block(); + + unsafe { + block.pupdr.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset)) + }); + block + .otyper + .modify(|r, w| w.bits(r.bits() | (0b1 << self.pin_id()))); + block.moder.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b01 << offset)) + }) + }; + + EPin { + pin_port: self.pin_port, + _mode: PhantomData, + } + } + + /// Configures the pin to operate as an push pull + /// output pin + pub fn into_push_pull_output(self) -> EPin> { + let offset = 2 * self.pin_id(); + let block = self.block(); + + unsafe { + block.pupdr.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset)) + }); + block + .otyper + .modify(|r, w| w.bits(r.bits() & !(0b1 << self.pin_id()))); + block.moder.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b01 << offset)) + }) + }; + + EPin { + pin_port: self.pin_port, + _mode: PhantomData, + } + } + + /// Configures the pin to operate as an analog + /// input pin + pub fn into_analog(self) -> EPin { + let offset = 2 * self.pin_id(); + let block = self.block(); + + unsafe { + block.pupdr.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b00 << offset)) + }); + block.moder.modify(|r, w| { + w.bits((r.bits() & !(0b11 << offset)) | (0b11 << offset)) + }) + }; + + EPin { + pin_port: self.pin_port, + _mode: PhantomData, + } + } + } + + impl OutputPin for ErasedPin> { + type Error = Never; + + #[inline(always)] + fn set_high(&mut self) -> Result<(), Never> { + // NOTE(unsafe) atomic write to a stateless register + unsafe { self.block().bsrr.write(|w| w.bits(1 << self.pin_id())) }; + Ok(()) + } + + #[inline(always)] + fn set_low(&mut self) -> Result<(), Never> { + // NOTE(unsafe) atomic write to a stateless register + unsafe { + self.block() + .bsrr + .write(|w| w.bits(1 << (self.pin_id() + 16))) + }; + Ok(()) + } + } + + impl StatefulOutputPin for ErasedPin> { + #[inline(always)] + fn is_set_high(&self) -> Result { + self.is_set_low().map(|x| !x) + } + + #[inline(always)] + fn is_set_low(&self) -> Result { + Ok(self.block().odr.read().bits() & (1 << self.pin_id()) == 0) + } + } + + impl toggleable::Default for ErasedPin> {} + impl InputPin for ErasedPin> { + type Error = Never; + + #[inline(always)] + fn is_high(&self) -> Result { + self.is_low().map(|x| !x) + } + + #[inline(always)] + fn is_low(&self) -> Result { + Ok(self.block().idr.read().bits() & (1 << self.pin_id()) == 0) + } + } + + impl InputPin for ErasedPin> { + type Error = Never; + + #[inline(always)] + fn is_high(&self) -> Result { + self.is_low().map(|x| !x) + } + + #[inline(always)] + fn is_low(&self) -> Result { + Ok(self.block().idr.read().bits() & (1 << self.pin_id()) == 0) + } + } + + impl InputPin for ErasedPin> { + type Error = Never; + + #[inline(always)] + fn is_high(&self) -> Result { + self.is_low().map(|x| !x) + } + + #[inline(always)] + fn is_low(&self) -> Result { + Ok(self.block().idr.read().bits() & (1 << self.pin_id()) == 0) + } + } + + impl fmt::Debug for ErasedPin { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_fmt(format_args!( + "P({}{})", + char::from(self.port_id() + b'A'), + self.pin_id() + )) + } + } +}