diff --git a/esp-hal-common/src/gpio.rs b/esp-hal-common/src/gpio.rs index 5f2c4ca28..4ce26a0e1 100644 --- a/esp-hal-common/src/gpio.rs +++ b/esp-hal-common/src/gpio.rs @@ -1376,6 +1376,140 @@ where } } +impl embedded_hal::digital::v2::InputPin for AnyPin> { + type Error = core::convert::Infallible; + + fn is_high(&self) -> Result { + let inner = &self.inner; + handle_gpio_input!(inner, target, { target.is_high() }) + } + + fn is_low(&self) -> Result { + let inner = &self.inner; + handle_gpio_input!(inner, target, { target.is_low() }) + } +} + +#[cfg(feature = "eh1")] +impl embedded_hal_1::digital::ErrorType for AnyPin> { + type Error = Infallible; +} + +#[cfg(feature = "eh1")] +impl embedded_hal_1::digital::InputPin for AnyPin> { + fn is_high(&self) -> Result { + let inner = &self.inner; + handle_gpio_input!(inner, target, { target.is_high() }) + } + + fn is_low(&self) -> Result { + let inner = &self.inner; + handle_gpio_input!(inner, target, { target.is_low() }) + } +} + +impl embedded_hal::digital::v2::OutputPin for AnyPin> { + type Error = Infallible; + + fn set_low(&mut self) -> Result<(), Self::Error> { + let inner = &mut self.inner; + handle_gpio_output!(inner, target, { target.set_low() }) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + let inner = &mut self.inner; + handle_gpio_output!(inner, target, { target.set_high() }) + } +} + +impl embedded_hal::digital::v2::StatefulOutputPin for AnyPin> { + fn is_set_high(&self) -> Result { + let inner = &self.inner; + handle_gpio_output!(inner, target, { target.is_set_high() }) + } + + fn is_set_low(&self) -> Result { + let inner = &self.inner; + handle_gpio_output!(inner, target, { target.is_set_low() }) + } +} + +impl embedded_hal::digital::v2::ToggleableOutputPin for AnyPin> { + type Error = Infallible; + + fn toggle(&mut self) -> Result<(), Self::Error> { + let inner = &mut self.inner; + handle_gpio_output!(inner, target, { target.toggle() }) + } +} + +#[cfg(feature = "eh1")] +impl embedded_hal_1::digital::ErrorType for AnyPin> { + type Error = Infallible; +} + +#[cfg(feature = "eh1")] +impl embedded_hal_1::digital::OutputPin for AnyPin> { + fn set_low(&mut self) -> Result<(), Self::Error> { + let inner = &mut self.inner; + handle_gpio_output!(inner, target, { target.set_low() }) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + let inner = &mut self.inner; + handle_gpio_output!(inner, target, { target.set_high() }) + } +} + +#[cfg(feature = "eh1")] +impl embedded_hal_1::digital::StatefulOutputPin for AnyPin> { + fn is_set_high(&self) -> Result { + let inner = &self.inner; + handle_gpio_output!(inner, target, { target.is_set_high() }) + } + + fn is_set_low(&self) -> Result { + let inner = &self.inner; + handle_gpio_output!(inner, target, { target.is_set_low() }) + } +} + +#[cfg(feature = "eh1")] +impl embedded_hal_1::digital::ToggleableOutputPin for AnyPin> { + fn toggle(&mut self) -> Result<(), Self::Error> { + let inner = &mut self.inner; + handle_gpio_output!(inner, target, { target.toggle() }) + } +} + +#[cfg(feature = "async")] +impl embedded_hal_async::digital::Wait for AnyPin> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + let inner = &mut self.inner; + handle_gpio_input!(inner, target, { target.wait_for_high().await }) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + let inner = &mut self.inner; + handle_gpio_input!(inner, target, { target.wait_for_low().await }) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + let inner = &mut self.inner; + handle_gpio_input!(inner, target, { target.wait_for_rising_edge().await }) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + let inner = &mut self.inner; + handle_gpio_input!(inner, target, { target.wait_for_falling_edge().await }) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + let inner = &mut self.inner; + handle_gpio_input!(inner, target, { target.wait_for_any_edge().await }) + } +} + pub struct IO { _io_mux: IO_MUX, pub pins: types::Pins, @@ -1471,6 +1605,65 @@ macro_rules! gpio { $( pub type [] = GpioPin], $crate::gpio::[< $cores CoreInteruptStatusRegisterAccessBank $bank >], [< $type PinType >], [], $gpionum>; )+ + + pub(crate) enum ErasedPin { + $( + []([]), + )+ + } + + pub struct AnyPin { + pub(crate) inner: ErasedPin + } + + $( + impl From< [] > for AnyPin { + fn from(value: []) -> Self { + AnyPin { + inner: ErasedPin::[](value) + } + } + } + + impl [] { + pub fn degrade(self) -> AnyPin { + AnyPin { + inner: ErasedPin::[](self) + } + } + } + + impl TryInto<[]> for AnyPin { + type Error = (); + + fn try_into(self) -> Result<[], Self::Error> { + match self.inner { + ErasedPin::[](gpio) => Ok(gpio), + _ => Err(()), + } + } + } + )+ + + procmacros::make_gpio_enum_dispatch_macro!( + handle_gpio_output + { InputOutputAnalog, InputOutput, } + { + $( + $type,$gpionum + )+ + } + ); + + procmacros::make_gpio_enum_dispatch_macro!( + handle_gpio_input + { InputOutputAnalog, InputOutput, InputOnlyAnalog } + { + $( + $type,$gpionum + )+ + } + ); } }; } @@ -1665,7 +1858,7 @@ mod asynch { for GpioPin, RA, IRA, PINTYPE, SIG, GPIONUM> where RA: BankGpioRegisterAccess, - PINTYPE: IsOutputPin, + PINTYPE: IsInputPin, IRA: InteruptStatusRegisterAccess, SIG: GpioSignal, { diff --git a/esp-hal-procmacros/src/lib.rs b/esp-hal-procmacros/src/lib.rs index 68ba571ae..25c3f5d10 100644 --- a/esp-hal-procmacros/src/lib.rs +++ b/esp-hal-procmacros/src/lib.rs @@ -15,7 +15,11 @@ use syn::{ Type, Visibility, }; -use syn::{parse_macro_input, AttributeArgs}; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, + AttributeArgs, +}; #[derive(Debug, Default, FromMeta)] #[darling(default)] @@ -306,3 +310,99 @@ fn extract_cfgs(attrs: Vec) -> (Vec, Vec) { (cfgs, not_cfgs) } + +#[derive(Debug)] +struct MakeGpioEnumDispatchMacro { + name: String, + filter: Vec, + elements: Vec<(String, usize)>, +} + +impl Parse for MakeGpioEnumDispatchMacro { + fn parse(input: ParseStream) -> syn::parse::Result { + let name = input.parse::()?.to_string(); + let filter = input + .parse::()? + .stream() + .into_iter() + .map(|v| match v { + proc_macro2::TokenTree::Group(_) => String::new(), + proc_macro2::TokenTree::Ident(ident) => ident.to_string(), + proc_macro2::TokenTree::Punct(_) => String::new(), + proc_macro2::TokenTree::Literal(_) => String::new(), + }) + .filter(|p| !p.is_empty()) + .collect(); + + let mut stream = input.parse::()?.stream().into_iter(); + + let mut elements = vec![]; + + let mut element_name = String::new(); + loop { + match stream.next() { + Some(v) => match v { + proc_macro2::TokenTree::Ident(ident) => { + element_name = ident.to_string(); + } + proc_macro2::TokenTree::Literal(lit) => { + let index = lit.to_string().parse().unwrap(); + elements.push((element_name.clone(), index)); + } + _ => (), + }, + None => break, + } + } + + Ok(MakeGpioEnumDispatchMacro { + name, + filter, + elements, + }) + } +} + +#[proc_macro] +pub fn make_gpio_enum_dispatch_macro(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as MakeGpioEnumDispatchMacro); + + let mut arms = Vec::new(); + + for (gpio_type, num) in input.elements { + let enum_name = quote::format_ident!("ErasedPin"); + let variant_name = quote::format_ident!("Gpio{}", num); + + if input.filter.contains(&gpio_type) { + let arm = { + quote! { #enum_name::#variant_name($target) => $body } + }; + arms.push(arm); + } else { + let arm = { + quote! { + #[allow(unused)] + #enum_name::#variant_name($target) => { panic!("Unsupported") } + } + }; + arms.push(arm); + } + } + + let macro_name = quote::format_ident!("{}", input.name); + + quote! { + #[doc(hidden)] + #[macro_export] + macro_rules! #macro_name { + ($m:ident, $target:ident, $body:block) => { + match $m { + #(#arms)* + } + } + } + + pub(crate) use #macro_name; + } + .into() +} diff --git a/esp32-hal/examples/blinky_erased_pins.rs b/esp32-hal/examples/blinky_erased_pins.rs new file mode 100644 index 000000000..b77434999 --- /dev/null +++ b/esp32-hal/examples/blinky_erased_pins.rs @@ -0,0 +1,62 @@ +//! Blinks three LEDs +//! +//! This assumes that LEDs are connected to GPIO25, 26 and 27. + +#![no_std] +#![no_main] + +use esp32_hal::{ + clock::ClockControl, + gpio::{AnyPin, Input, Output, PullDown, PushPull, IO}, + peripherals::Peripherals, + prelude::*, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use xtensa_lx_rt::entry; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let system = peripherals.DPORT.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let mut wdt = timer_group0.wdt; + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + + // Disable MWDT and RWDT (Watchdog) flash boot protection + wdt.disable(); + rtc.rwdt.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let led1 = io.pins.gpio25.into_push_pull_output(); + let led2 = io.pins.gpio26.into_push_pull_output(); + let led3 = io.pins.gpio27.into_push_pull_output(); + + let button = io.pins.gpio0.into_pull_down_input().degrade(); + + // you can use `into` or `degrade` + let mut pins = [led1.into(), led2.into(), led3.degrade()]; + + // Initialize the Delay peripheral, and use it to toggle the LED state in a + // loop. + let mut delay = Delay::new(&clocks); + + loop { + toggle_pins(&mut pins, &button); + delay.delay_ms(500u32); + } +} + +fn toggle_pins(leds: &mut [AnyPin>], button: &AnyPin>) { + for pin in leds.iter_mut() { + pin.toggle().unwrap(); + } + + if button.is_low().unwrap() { + esp_println::println!("Button"); + } +} diff --git a/esp32c2-hal/examples/blinky_erased_pins.rs b/esp32c2-hal/examples/blinky_erased_pins.rs new file mode 100644 index 000000000..925f6125e --- /dev/null +++ b/esp32c2-hal/examples/blinky_erased_pins.rs @@ -0,0 +1,64 @@ +//! Blinks an LED +//! +//! This assumes that LEDs are connected to GPIO3, 4 and 5. + +#![no_std] +#![no_main] + +use esp32c2_hal::{ + clock::ClockControl, + gpio::{AnyPin, Input, Output, PullDown, PushPull, IO}, + peripherals::Peripherals, + prelude::*, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use esp_riscv_rt::entry; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + // Disable the watchdog timers. For the ESP32-C3, this includes the Super WDT, + // the RTC WDT, and the TIMG WDTs. + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let mut wdt0 = timer_group0.wdt; + + rtc.swd.disable(); + rtc.rwdt.disable(); + wdt0.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let led1 = io.pins.gpio3.into_push_pull_output(); + let led2 = io.pins.gpio4.into_push_pull_output(); + let led3 = io.pins.gpio5.into_push_pull_output(); + + let button = io.pins.gpio9.into_pull_down_input().degrade(); + + // you can use `into` or `degrade` + let mut pins = [led1.into(), led2.into(), led3.degrade()]; + + // Initialize the Delay peripheral, and use it to toggle the LED state in a + // loop. + let mut delay = Delay::new(&clocks); + + loop { + toggle_pins(&mut pins, &button); + delay.delay_ms(500u32); + } +} + +fn toggle_pins(leds: &mut [AnyPin>], button: &AnyPin>) { + for pin in leds.iter_mut() { + pin.toggle().unwrap(); + } + + if button.is_low().unwrap() { + esp_println::println!("Button"); + } +} diff --git a/esp32c3-hal/examples/blinky_erased_pins.rs b/esp32c3-hal/examples/blinky_erased_pins.rs new file mode 100644 index 000000000..e09420cae --- /dev/null +++ b/esp32c3-hal/examples/blinky_erased_pins.rs @@ -0,0 +1,67 @@ +//! Blinks an LED +//! +//! This assumes that LEDs are connected to GPIO3, 4 and 5. + +#![no_std] +#![no_main] + +use esp32c3_hal::{ + clock::ClockControl, + gpio::{AnyPin, Input, Output, PullDown, PushPull, IO}, + peripherals::Peripherals, + prelude::*, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use esp_riscv_rt::entry; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + // Disable the watchdog timers. For the ESP32-C3, this includes the Super WDT, + // the RTC WDT, and the TIMG WDTs. + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let mut wdt0 = timer_group0.wdt; + let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks); + let mut wdt1 = timer_group1.wdt; + + rtc.swd.disable(); + rtc.rwdt.disable(); + wdt0.disable(); + wdt1.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let led1 = io.pins.gpio3.into_push_pull_output(); + let led2 = io.pins.gpio4.into_push_pull_output(); + let led3 = io.pins.gpio5.into_push_pull_output(); + + let button = io.pins.gpio9.into_pull_down_input().degrade(); + + // you can use `into` or `degrade` + let mut pins = [led1.into(), led2.into(), led3.degrade()]; + + // Initialize the Delay peripheral, and use it to toggle the LED state in a + // loop. + let mut delay = Delay::new(&clocks); + + loop { + toggle_pins(&mut pins, &button); + delay.delay_ms(500u32); + } +} + +fn toggle_pins(leds: &mut [AnyPin>], button: &AnyPin>) { + for pin in leds.iter_mut() { + pin.toggle().unwrap(); + } + + if button.is_low().unwrap() { + esp_println::println!("Button"); + } +} diff --git a/esp32s2-hal/examples/blinky_erased_pins.rs b/esp32s2-hal/examples/blinky_erased_pins.rs new file mode 100644 index 000000000..eee987b43 --- /dev/null +++ b/esp32s2-hal/examples/blinky_erased_pins.rs @@ -0,0 +1,63 @@ +//! Blinks an LED +//! +//! This assumes that LEDs are connected to GPIO3, 4 and 5. + +#![no_std] +#![no_main] + +use esp32s2_hal::{ + clock::ClockControl, + gpio::{AnyPin, Input, Output, PullDown, PushPull, IO}, + peripherals::Peripherals, + prelude::*, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use xtensa_lx_rt::entry; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let mut wdt = timer_group0.wdt; + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + + // Disable MWDT and RWDT (Watchdog) flash boot protection + wdt.disable(); + rtc.rwdt.disable(); + + // Set GPIO4 as an output, and set its state high initially. + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let led1 = io.pins.gpio3.into_push_pull_output(); + let led2 = io.pins.gpio4.into_push_pull_output(); + let led3 = io.pins.gpio5.into_push_pull_output(); + + let button = io.pins.gpio0.into_pull_down_input().degrade(); + + // you can use `into` or `degrade` + let mut pins = [led1.into(), led2.into(), led3.degrade()]; + + // Initialize the Delay peripheral, and use it to toggle the LED state in a + // loop. + let mut delay = Delay::new(&clocks); + + loop { + toggle_pins(&mut pins, &button); + delay.delay_ms(500u32); + } +} + +fn toggle_pins(leds: &mut [AnyPin>], button: &AnyPin>) { + for pin in leds.iter_mut() { + pin.toggle().unwrap(); + } + + if button.is_low().unwrap() { + esp_println::println!("Button"); + } +} diff --git a/esp32s3-hal/examples/blinky_erased_pins.rs b/esp32s3-hal/examples/blinky_erased_pins.rs new file mode 100644 index 000000000..91f609403 --- /dev/null +++ b/esp32s3-hal/examples/blinky_erased_pins.rs @@ -0,0 +1,63 @@ +//! Blinks an LED +//! +//! This assumes that LEDs are connected to GPIO3, 4 and 5. + +#![no_std] +#![no_main] + +use esp32s3_hal::{ + clock::ClockControl, + gpio::{AnyPin, Input, Output, PullDown, PushPull, IO}, + peripherals::Peripherals, + prelude::*, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use xtensa_lx_rt::entry; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let mut wdt = timer_group0.wdt; + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + + // Disable MWDT and RWDT (Watchdog) flash boot protection + wdt.disable(); + rtc.rwdt.disable(); + + // Set GPIO4 as an output, and set its state high initially. + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let led1 = io.pins.gpio3.into_push_pull_output(); + let led2 = io.pins.gpio4.into_push_pull_output(); + let led3 = io.pins.gpio5.into_push_pull_output(); + + let button = io.pins.gpio0.into_pull_down_input().degrade(); + + // you can use `into` or `degrade` + let mut pins = [led1.into(), led2.into(), led3.degrade()]; + + // Initialize the Delay peripheral, and use it to toggle the LED state in a + // loop. + let mut delay = Delay::new(&clocks); + + loop { + toggle_pins(&mut pins, &button); + delay.delay_ms(500u32); + } +} + +fn toggle_pins(leds: &mut [AnyPin>], button: &AnyPin>) { + for pin in leds.iter_mut() { + pin.toggle().unwrap(); + } + + if button.is_low().unwrap() { + esp_println::println!("Button"); + } +}