mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-30 13:50:38 +00:00
Track async GPIOs in memory (#2625)
* Track async GPIOs in memory * Add an example * Deduplicate interrupt handling * Changelog * Add gpio_bank_1 symbol * Derive EnumCount * Try to fix issues around manual listen calls and multi-core * Fix test * Update esp-hal/src/gpio/mod.rs Co-authored-by: Dominic Fischer <14130965+Dominaezzz@users.noreply.github.com> * Do not prevent pending interrupt from being handled * Remove unnecessary unpin * Add a note about interrupt status flags --------- Co-authored-by: Dominic Fischer <14130965+Dominaezzz@users.noreply.github.com>
This commit is contained in:
parent
891a12e13f
commit
a6a83d3bb5
@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Added `I8080::apply_config`, `DPI::apply_config` and `Camera::apply_config` (#2610)
|
- Added `I8080::apply_config`, `DPI::apply_config` and `Camera::apply_config` (#2610)
|
||||||
- Introduced the `unstable` feature which will be used to restrict stable APIs to a subset of esp-hal. (#2628)
|
- Introduced the `unstable` feature which will be used to restrict stable APIs to a subset of esp-hal. (#2628)
|
||||||
- HAL configuration structs now implement the Builder Lite pattern (#2645)
|
- HAL configuration structs now implement the Builder Lite pattern (#2645)
|
||||||
|
- Added `OutputOpenDrain::unlisten` (#2625)
|
||||||
|
- Added `{Input, Flex}::wait_for` (#2625)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@ -50,10 +52,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Interrupt handling related functions are only provided for Blocking UART. (#2610)
|
- Interrupt handling related functions are only provided for Blocking UART. (#2610)
|
||||||
- Changed how `Spi`, (split or unsplit) `Uart`, `LpUart`, `I8080`, `Camera`, `DPI` and `I2C` drivers are constructed (#2610)
|
- Changed how `Spi`, (split or unsplit) `Uart`, `LpUart`, `I8080`, `Camera`, `DPI` and `I2C` drivers are constructed (#2610)
|
||||||
- I8080, camera, DPI: The various standalone configuration options have been merged into `Config` (#2610)
|
- I8080, camera, DPI: The various standalone configuration options have been merged into `Config` (#2610)
|
||||||
|
- Dropped GPIO futures stop listening for interrupts (#2625)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Xtensa devices now correctly enable the `esp-hal-procmacros/rtc-slow` feature (#2594)
|
- Xtensa devices now correctly enable the `esp-hal-procmacros/rtc-slow` feature (#2594)
|
||||||
|
- User-bound GPIO interrupt handlers should no longer interfere with async pins. (#2625)
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
@ -55,8 +55,9 @@
|
|||||||
//! [embedded-hal-async]: https://docs.rs/embedded-hal-async/latest/embedded_hal_async/index.html
|
//! [embedded-hal-async]: https://docs.rs/embedded-hal-async/latest/embedded_hal_async/index.html
|
||||||
//! [Inverting TX and RX Pins]: crate::uart#inverting-rx-and-tx-pins
|
//! [Inverting TX and RX Pins]: crate::uart#inverting-rx-and-tx-pins
|
||||||
|
|
||||||
use portable_atomic::{AtomicPtr, Ordering};
|
use portable_atomic::{AtomicPtr, AtomicU32, Ordering};
|
||||||
use procmacros::ram;
|
use procmacros::ram;
|
||||||
|
use strum::EnumCount;
|
||||||
|
|
||||||
#[cfg(any(lp_io, rtc_cntl))]
|
#[cfg(any(lp_io, rtc_cntl))]
|
||||||
use crate::peripherals::gpio::{handle_rtcio, handle_rtcio_with_resistors};
|
use crate::peripherals::gpio::{handle_rtcio, handle_rtcio_with_resistors};
|
||||||
@ -454,7 +455,7 @@ pub trait OutputPin: Pin + Into<AnyPin> + 'static {
|
|||||||
gpio.func_out_sel_cfg(self.number() as usize)
|
gpio.func_out_sel_cfg(self.number() as usize)
|
||||||
.modify(|_, w| unsafe { w.out_sel().bits(OutputSignal::GPIO as OutputSignalType) });
|
.modify(|_, w| unsafe { w.out_sel().bits(OutputSignal::GPIO as OutputSignalType) });
|
||||||
|
|
||||||
#[cfg(any(esp32c3, esp32s3))]
|
#[cfg(usb_device)]
|
||||||
disable_usb_pads(self.number());
|
disable_usb_pads(self.number());
|
||||||
|
|
||||||
io_mux_reg(self.number()).modify(|_, w| unsafe {
|
io_mux_reg(self.number()).modify(|_, w| unsafe {
|
||||||
@ -546,16 +547,16 @@ pub trait TouchPin: Pin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, EnumCount)]
|
||||||
pub enum GpioRegisterAccess {
|
pub enum GpioRegisterAccess {
|
||||||
Bank0,
|
Bank0,
|
||||||
#[cfg(any(esp32, esp32s2, esp32s3))]
|
#[cfg(gpio_bank_1)]
|
||||||
Bank1,
|
Bank1,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<usize> for GpioRegisterAccess {
|
impl From<usize> for GpioRegisterAccess {
|
||||||
fn from(_gpio_num: usize) -> Self {
|
fn from(_gpio_num: usize) -> Self {
|
||||||
#[cfg(any(esp32, esp32s2, esp32s3))]
|
#[cfg(gpio_bank_1)]
|
||||||
if _gpio_num >= 32 {
|
if _gpio_num >= 32 {
|
||||||
return GpioRegisterAccess::Bank1;
|
return GpioRegisterAccess::Bank1;
|
||||||
}
|
}
|
||||||
@ -565,6 +566,21 @@ impl From<usize> for GpioRegisterAccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GpioRegisterAccess {
|
impl GpioRegisterAccess {
|
||||||
|
fn async_operations(self) -> &'static AtomicU32 {
|
||||||
|
static FLAGS: [AtomicU32; GpioRegisterAccess::COUNT] =
|
||||||
|
[const { AtomicU32::new(0) }; GpioRegisterAccess::COUNT];
|
||||||
|
|
||||||
|
&FLAGS[self as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offset(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::Bank0 => 0,
|
||||||
|
#[cfg(gpio_bank_1)]
|
||||||
|
Self::Bank1 => 32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn write_out_en(self, word: u32, enable: bool) {
|
fn write_out_en(self, word: u32, enable: bool) {
|
||||||
if enable {
|
if enable {
|
||||||
self.write_out_en_set(word);
|
self.write_out_en_set(word);
|
||||||
@ -576,7 +592,7 @@ impl GpioRegisterAccess {
|
|||||||
fn write_out_en_clear(self, word: u32) {
|
fn write_out_en_clear(self, word: u32) {
|
||||||
match self {
|
match self {
|
||||||
Self::Bank0 => Bank0GpioRegisterAccess::write_out_en_clear(word),
|
Self::Bank0 => Bank0GpioRegisterAccess::write_out_en_clear(word),
|
||||||
#[cfg(any(esp32, esp32s2, esp32s3))]
|
#[cfg(gpio_bank_1)]
|
||||||
Self::Bank1 => Bank1GpioRegisterAccess::write_out_en_clear(word),
|
Self::Bank1 => Bank1GpioRegisterAccess::write_out_en_clear(word),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -584,7 +600,7 @@ impl GpioRegisterAccess {
|
|||||||
fn write_out_en_set(self, word: u32) {
|
fn write_out_en_set(self, word: u32) {
|
||||||
match self {
|
match self {
|
||||||
Self::Bank0 => Bank0GpioRegisterAccess::write_out_en_set(word),
|
Self::Bank0 => Bank0GpioRegisterAccess::write_out_en_set(word),
|
||||||
#[cfg(any(esp32, esp32s2, esp32s3))]
|
#[cfg(gpio_bank_1)]
|
||||||
Self::Bank1 => Bank1GpioRegisterAccess::write_out_en_set(word),
|
Self::Bank1 => Bank1GpioRegisterAccess::write_out_en_set(word),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -592,7 +608,7 @@ impl GpioRegisterAccess {
|
|||||||
fn read_input(self) -> u32 {
|
fn read_input(self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
Self::Bank0 => Bank0GpioRegisterAccess::read_input(),
|
Self::Bank0 => Bank0GpioRegisterAccess::read_input(),
|
||||||
#[cfg(any(esp32, esp32s2, esp32s3))]
|
#[cfg(gpio_bank_1)]
|
||||||
Self::Bank1 => Bank1GpioRegisterAccess::read_input(),
|
Self::Bank1 => Bank1GpioRegisterAccess::read_input(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -600,7 +616,7 @@ impl GpioRegisterAccess {
|
|||||||
fn read_output(self) -> u32 {
|
fn read_output(self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
Self::Bank0 => Bank0GpioRegisterAccess::read_output(),
|
Self::Bank0 => Bank0GpioRegisterAccess::read_output(),
|
||||||
#[cfg(any(esp32, esp32s2, esp32s3))]
|
#[cfg(gpio_bank_1)]
|
||||||
Self::Bank1 => Bank1GpioRegisterAccess::read_output(),
|
Self::Bank1 => Bank1GpioRegisterAccess::read_output(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -608,7 +624,7 @@ impl GpioRegisterAccess {
|
|||||||
fn read_interrupt_status(self) -> u32 {
|
fn read_interrupt_status(self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
Self::Bank0 => Bank0GpioRegisterAccess::read_interrupt_status(),
|
Self::Bank0 => Bank0GpioRegisterAccess::read_interrupt_status(),
|
||||||
#[cfg(any(esp32, esp32s2, esp32s3))]
|
#[cfg(gpio_bank_1)]
|
||||||
Self::Bank1 => Bank1GpioRegisterAccess::read_interrupt_status(),
|
Self::Bank1 => Bank1GpioRegisterAccess::read_interrupt_status(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -616,7 +632,7 @@ impl GpioRegisterAccess {
|
|||||||
fn write_interrupt_status_clear(self, word: u32) {
|
fn write_interrupt_status_clear(self, word: u32) {
|
||||||
match self {
|
match self {
|
||||||
Self::Bank0 => Bank0GpioRegisterAccess::write_interrupt_status_clear(word),
|
Self::Bank0 => Bank0GpioRegisterAccess::write_interrupt_status_clear(word),
|
||||||
#[cfg(any(esp32, esp32s2, esp32s3))]
|
#[cfg(gpio_bank_1)]
|
||||||
Self::Bank1 => Bank1GpioRegisterAccess::write_interrupt_status_clear(word),
|
Self::Bank1 => Bank1GpioRegisterAccess::write_interrupt_status_clear(word),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -632,7 +648,7 @@ impl GpioRegisterAccess {
|
|||||||
fn write_output_set(self, word: u32) {
|
fn write_output_set(self, word: u32) {
|
||||||
match self {
|
match self {
|
||||||
Self::Bank0 => Bank0GpioRegisterAccess::write_output_set(word),
|
Self::Bank0 => Bank0GpioRegisterAccess::write_output_set(word),
|
||||||
#[cfg(any(esp32, esp32s2, esp32s3))]
|
#[cfg(gpio_bank_1)]
|
||||||
Self::Bank1 => Bank1GpioRegisterAccess::write_output_set(word),
|
Self::Bank1 => Bank1GpioRegisterAccess::write_output_set(word),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -640,7 +656,7 @@ impl GpioRegisterAccess {
|
|||||||
fn write_output_clear(self, word: u32) {
|
fn write_output_clear(self, word: u32) {
|
||||||
match self {
|
match self {
|
||||||
Self::Bank0 => Bank0GpioRegisterAccess::write_output_clear(word),
|
Self::Bank0 => Bank0GpioRegisterAccess::write_output_clear(word),
|
||||||
#[cfg(any(esp32, esp32s2, esp32s3))]
|
#[cfg(gpio_bank_1)]
|
||||||
Self::Bank1 => Bank1GpioRegisterAccess::write_output_clear(word),
|
Self::Bank1 => Bank1GpioRegisterAccess::write_output_clear(word),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -692,10 +708,10 @@ impl Bank0GpioRegisterAccess {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(esp32, esp32s2, esp32s3))]
|
#[cfg(gpio_bank_1)]
|
||||||
struct Bank1GpioRegisterAccess;
|
struct Bank1GpioRegisterAccess;
|
||||||
|
|
||||||
#[cfg(any(esp32, esp32s2, esp32s3))]
|
#[cfg(gpio_bank_1)]
|
||||||
impl Bank1GpioRegisterAccess {
|
impl Bank1GpioRegisterAccess {
|
||||||
fn write_out_en_clear(word: u32) {
|
fn write_out_en_clear(word: u32) {
|
||||||
unsafe { GPIO::steal() }
|
unsafe { GPIO::steal() }
|
||||||
@ -863,14 +879,10 @@ impl InterruptConfigurable for Io {
|
|||||||
/// Install the given interrupt handler replacing any previously set
|
/// Install the given interrupt handler replacing any previously set
|
||||||
/// handler.
|
/// handler.
|
||||||
///
|
///
|
||||||
/// ⚠️ Be careful when using this together with the async API:
|
/// Note that when using interrupt handlers registered by this function,
|
||||||
///
|
/// we clear the interrupt status register for you. This is NOT the case
|
||||||
/// - The async driver will disable any interrupts whose status is not
|
/// if you register the interrupt handler directly, by defining a
|
||||||
/// cleared by the user handler.
|
/// `#[no_mangle] unsafe extern "C" fn GPIO()` function.
|
||||||
/// - Clearing the interrupt status in the user handler will prevent the
|
|
||||||
/// async driver from detecting the interrupt, silently disabling the
|
|
||||||
/// corresponding pin's async API.
|
|
||||||
/// - You will not be notified if you make a mistake.
|
|
||||||
fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
|
fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
|
||||||
for core in crate::Cpu::other() {
|
for core in crate::Cpu::other() {
|
||||||
crate::interrupt::disable(core, Interrupt::GPIO);
|
crate::interrupt::disable(core, Interrupt::GPIO);
|
||||||
@ -883,22 +895,46 @@ impl InterruptConfigurable for Io {
|
|||||||
|
|
||||||
#[ram]
|
#[ram]
|
||||||
extern "C" fn user_gpio_interrupt_handler() {
|
extern "C" fn user_gpio_interrupt_handler() {
|
||||||
USER_INTERRUPT_HANDLER.call();
|
handle_pin_interrupts(|| USER_INTERRUPT_HANDLER.call());
|
||||||
|
|
||||||
default_gpio_interrupt_handler();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ram]
|
#[ram]
|
||||||
extern "C" fn default_gpio_interrupt_handler() {
|
extern "C" fn default_gpio_interrupt_handler() {
|
||||||
handle_pin_interrupts(on_pin_irq);
|
handle_pin_interrupts(|| ());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ram]
|
#[ram]
|
||||||
fn on_pin_irq(pin_nr: u8) {
|
fn handle_pin_interrupts(user_handler: fn()) {
|
||||||
// FIXME: async handlers signal completion by disabling the interrupt, but this
|
let intrs_bank0 = InterruptStatusRegisterAccess::Bank0.interrupt_status_read();
|
||||||
// conflicts with user handlers.
|
|
||||||
set_int_enable(pin_nr, 0, 0, false);
|
#[cfg(gpio_bank_1)]
|
||||||
asynch::PIN_WAKERS[pin_nr as usize].wake(); // wake task
|
let intrs_bank1 = InterruptStatusRegisterAccess::Bank1.interrupt_status_read();
|
||||||
|
|
||||||
|
user_handler();
|
||||||
|
|
||||||
|
let banks = [
|
||||||
|
(GpioRegisterAccess::Bank0, intrs_bank0),
|
||||||
|
#[cfg(gpio_bank_1)]
|
||||||
|
(GpioRegisterAccess::Bank1, intrs_bank1),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (bank, intrs) in banks {
|
||||||
|
// Get the mask of active async pins and also unmark them in the same go.
|
||||||
|
let async_pins = bank.async_operations().fetch_and(!intrs, Ordering::Relaxed);
|
||||||
|
|
||||||
|
// Wake up the tasks
|
||||||
|
let mut intr_bits = intrs & async_pins;
|
||||||
|
while intr_bits != 0 {
|
||||||
|
let pin_pos = intr_bits.trailing_zeros();
|
||||||
|
intr_bits -= 1 << pin_pos;
|
||||||
|
|
||||||
|
let pin_nr = pin_pos as u8 + bank.offset();
|
||||||
|
asynch::PIN_WAKERS[pin_nr as usize].wake();
|
||||||
|
set_int_enable(pin_nr, Some(0), 0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bank.write_interrupt_status_clear(intrs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
@ -1445,7 +1481,74 @@ where
|
|||||||
self.pin.level()
|
self.pin.level()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Listen for interrupts
|
/// Listen for interrupts.
|
||||||
|
///
|
||||||
|
/// The interrupts will be handled by the handler set using
|
||||||
|
/// [`Io::set_interrupt_handler`]. All GPIO pins share the same
|
||||||
|
/// interrupt handler.
|
||||||
|
///
|
||||||
|
/// Note that [`Event::LowLevel`] and [`Event::HighLevel`] are fired
|
||||||
|
/// continuously when the pin is low or high, respectively. You must use
|
||||||
|
/// a custom interrupt handler to stop listening for these events,
|
||||||
|
/// otherwise your program will be stuck in a loop as long as the pin is
|
||||||
|
/// reading the corresponding level.
|
||||||
|
///
|
||||||
|
/// ## Example: print something when a button is pressed.
|
||||||
|
///
|
||||||
|
/// ```rust, no_run
|
||||||
|
#[doc = crate::before_snippet!()]
|
||||||
|
/// use esp_hal::gpio::{Event, Input, Pull, Io};
|
||||||
|
///
|
||||||
|
/// let mut io = Io::new(peripherals.IO_MUX);
|
||||||
|
/// io.set_interrupt_handler(handler);
|
||||||
|
///
|
||||||
|
/// // Set up the input and store it in the static variable.
|
||||||
|
/// // This example uses a push button that is high when not
|
||||||
|
/// // pressed and low when pressed.
|
||||||
|
/// let mut button = Input::new(peripherals.GPIO5, Pull::Up);
|
||||||
|
///
|
||||||
|
/// critical_section::with(|cs| {
|
||||||
|
/// // Here we are listening for a low level to demonstrate
|
||||||
|
/// // that you need to stop listening for level interrupts,
|
||||||
|
/// // but usually you'd probably use `FallingEdge`.
|
||||||
|
/// button.listen(Event::LowLevel);
|
||||||
|
/// BUTTON.borrow_ref_mut(cs).replace(button);
|
||||||
|
/// });
|
||||||
|
/// # }
|
||||||
|
///
|
||||||
|
/// // Outside of your `main` function:
|
||||||
|
///
|
||||||
|
/// # use esp_hal::gpio::Input;
|
||||||
|
/// use core::cell::RefCell;
|
||||||
|
/// use critical_section::Mutex;
|
||||||
|
///
|
||||||
|
/// // You will need to store the `Input` object in a static variable so
|
||||||
|
/// // that the interrupt handler can access it.
|
||||||
|
/// static BUTTON: Mutex<RefCell<Option<Input>>> =
|
||||||
|
/// Mutex::new(RefCell::new(None));
|
||||||
|
///
|
||||||
|
/// #[handler]
|
||||||
|
/// fn handler() {
|
||||||
|
/// critical_section::with(|cs| {
|
||||||
|
/// let mut button = BUTTON.borrow_ref_mut(cs);
|
||||||
|
/// let Some(button) = button.as_mut() else {
|
||||||
|
/// // Some other interrupt has occurred
|
||||||
|
/// // before the button was set up.
|
||||||
|
/// return;
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// if button.is_interrupt_set() {
|
||||||
|
/// print!("Button pressed");
|
||||||
|
///
|
||||||
|
/// // If you want to stop listening for interrupts, you need to
|
||||||
|
/// // call `unlisten` here. If you comment this line, the
|
||||||
|
/// // interrupt will fire continuously while the button
|
||||||
|
/// // is pressed.
|
||||||
|
/// button.unlisten();
|
||||||
|
/// }
|
||||||
|
/// });
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn listen(&mut self, event: Event) {
|
pub fn listen(&mut self, event: Event) {
|
||||||
self.pin.listen(event);
|
self.pin.listen(event);
|
||||||
@ -1660,12 +1763,20 @@ where
|
|||||||
self.pin.level()
|
self.pin.level()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Listen for interrupts
|
/// Listen for interrupts.
|
||||||
|
///
|
||||||
|
/// See [`Input::listen`] for more information and an example.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn listen(&mut self, event: Event) {
|
pub fn listen(&mut self, event: Event) {
|
||||||
self.pin.listen(event);
|
self.pin.listen(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stop listening for interrupts.
|
||||||
|
#[inline]
|
||||||
|
pub fn unlisten(&mut self) {
|
||||||
|
self.pin.unlisten();
|
||||||
|
}
|
||||||
|
|
||||||
/// Clear the interrupt status bit for this Pin
|
/// Clear the interrupt status bit for this Pin
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn clear_interrupt(&mut self) {
|
pub fn clear_interrupt(&mut self) {
|
||||||
@ -1815,21 +1926,30 @@ where
|
|||||||
|
|
||||||
set_int_enable(
|
set_int_enable(
|
||||||
self.pin.number(),
|
self.pin.number(),
|
||||||
gpio_intr_enable(int_enable, nmi_enable),
|
Some(gpio_intr_enable(int_enable, nmi_enable)),
|
||||||
event as u8,
|
event as u8,
|
||||||
wake_up_from_light_sleep,
|
wake_up_from_light_sleep,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Listen for interrupts
|
/// Listen for interrupts.
|
||||||
|
///
|
||||||
|
/// See [`Input::listen`] for more information and an example.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn listen(&mut self, event: Event) {
|
pub fn listen(&mut self, event: Event) {
|
||||||
self.listen_with_options(event, true, false, false)
|
self.listen_with_options(event, true, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop listening for interrupts
|
/// Stop listening for interrupts.
|
||||||
|
#[inline]
|
||||||
pub fn unlisten(&mut self) {
|
pub fn unlisten(&mut self) {
|
||||||
set_int_enable(self.pin.number(), 0, 0, false);
|
set_int_enable(self.pin.number(), Some(0), 0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the pin is listening for interrupts.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_listening(&self) -> bool {
|
||||||
|
is_int_enabled(self.pin.number())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear the interrupt status bit for this Pin
|
/// Clear the interrupt status bit for this Pin
|
||||||
@ -2151,56 +2271,39 @@ pub(crate) mod internal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_listening(pin_num: u8) -> bool {
|
/// Set GPIO event listening.
|
||||||
let bits = unsafe { GPIO::steal() }
|
///
|
||||||
.pin(pin_num as usize)
|
/// - `gpio_num`: the pin to configure
|
||||||
.read()
|
/// - `int_ena`: maskable and non-maskable CPU interrupt bits. None to leave
|
||||||
.int_ena()
|
/// unchanged.
|
||||||
.bits();
|
/// - `int_type`: interrupt type, see [Event] (or 0 to disable)
|
||||||
bits != 0
|
/// - `wake_up_from_light_sleep`: whether to wake up from light sleep
|
||||||
}
|
fn set_int_enable(gpio_num: u8, int_ena: Option<u8>, int_type: u8, wake_up_from_light_sleep: bool) {
|
||||||
|
|
||||||
fn set_int_enable(gpio_num: u8, int_ena: u8, int_type: u8, wake_up_from_light_sleep: bool) {
|
|
||||||
unsafe { GPIO::steal() }
|
unsafe { GPIO::steal() }
|
||||||
.pin(gpio_num as usize)
|
.pin(gpio_num as usize)
|
||||||
.modify(|_, w| unsafe {
|
.modify(|_, w| unsafe {
|
||||||
w.int_ena().bits(int_ena);
|
if let Some(int_ena) = int_ena {
|
||||||
|
w.int_ena().bits(int_ena);
|
||||||
|
}
|
||||||
w.int_type().bits(int_type);
|
w.int_type().bits(int_type);
|
||||||
w.wakeup_enable().bit(wake_up_from_light_sleep)
|
w.wakeup_enable().bit(wake_up_from_light_sleep)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ram]
|
fn is_int_enabled(gpio_num: u8) -> bool {
|
||||||
fn handle_pin_interrupts(handle: impl Fn(u8)) {
|
unsafe { GPIO::steal() }
|
||||||
let intrs_bank0 = InterruptStatusRegisterAccess::Bank0.interrupt_status_read();
|
.pin(gpio_num as usize)
|
||||||
|
.read()
|
||||||
#[cfg(any(esp32, esp32s2, esp32s3))]
|
.int_ena()
|
||||||
let intrs_bank1 = InterruptStatusRegisterAccess::Bank1.interrupt_status_read();
|
.bits()
|
||||||
|
!= 0
|
||||||
let mut intr_bits = intrs_bank0;
|
|
||||||
while intr_bits != 0 {
|
|
||||||
let pin_nr = intr_bits.trailing_zeros();
|
|
||||||
handle(pin_nr as u8);
|
|
||||||
intr_bits -= 1 << pin_nr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear interrupt bits
|
|
||||||
Bank0GpioRegisterAccess::write_interrupt_status_clear(intrs_bank0);
|
|
||||||
|
|
||||||
#[cfg(any(esp32, esp32s2, esp32s3))]
|
|
||||||
{
|
|
||||||
let mut intr_bits = intrs_bank1;
|
|
||||||
while intr_bits != 0 {
|
|
||||||
let pin_nr = intr_bits.trailing_zeros();
|
|
||||||
handle(pin_nr as u8 + 32);
|
|
||||||
intr_bits -= 1 << pin_nr;
|
|
||||||
}
|
|
||||||
Bank1GpioRegisterAccess::write_interrupt_status_clear(intrs_bank1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod asynch {
|
mod asynch {
|
||||||
use core::task::{Context, Poll};
|
use core::{
|
||||||
|
future::poll_fn,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::asynch::AtomicWaker;
|
use crate::asynch::AtomicWaker;
|
||||||
@ -2213,34 +2316,85 @@ mod asynch {
|
|||||||
where
|
where
|
||||||
P: InputPin,
|
P: InputPin,
|
||||||
{
|
{
|
||||||
async fn wait_for(&mut self, event: Event) {
|
/// Wait until the pin experiences a particular [`Event`].
|
||||||
self.listen(event);
|
///
|
||||||
PinFuture::new(self.pin.number()).await
|
/// The GPIO driver will disable listening for the event once it occurs,
|
||||||
|
/// or if the `Future` is dropped.
|
||||||
|
///
|
||||||
|
/// Note that calling this function will overwrite previous
|
||||||
|
/// [`listen`][Self::listen] operations for this pin.
|
||||||
|
#[inline]
|
||||||
|
pub async fn wait_for(&mut self, event: Event) {
|
||||||
|
let mut future = PinFuture {
|
||||||
|
pin: unsafe { self.clone_unchecked() },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make sure this pin is not being processed by an interrupt handler.
|
||||||
|
if future.pin.is_listening() {
|
||||||
|
set_int_enable(
|
||||||
|
future.pin.number(),
|
||||||
|
None, // Do not disable handling pending interrupts.
|
||||||
|
0, // Disable generating new events
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
poll_fn(|cx| {
|
||||||
|
if future.pin.is_interrupt_set() {
|
||||||
|
cx.waker().wake_by_ref();
|
||||||
|
Poll::Pending
|
||||||
|
} else {
|
||||||
|
Poll::Ready(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point the pin is no longer listening, we can safely
|
||||||
|
// do our setup.
|
||||||
|
|
||||||
|
// Mark pin as async.
|
||||||
|
future
|
||||||
|
.pin
|
||||||
|
.gpio_bank(private::Internal)
|
||||||
|
.async_operations()
|
||||||
|
.fetch_or(future.pin_mask(), Ordering::Relaxed);
|
||||||
|
|
||||||
|
future.pin.listen(event);
|
||||||
|
|
||||||
|
future.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait until the pin is high. If it is already high, return
|
/// Wait until the pin is high.
|
||||||
/// immediately.
|
///
|
||||||
|
/// See [Self::wait_for] for more information.
|
||||||
pub async fn wait_for_high(&mut self) {
|
pub async fn wait_for_high(&mut self) {
|
||||||
self.wait_for(Event::HighLevel).await
|
self.wait_for(Event::HighLevel).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait until the pin is low. If it is already low, return immediately.
|
/// Wait until the pin is low.
|
||||||
|
///
|
||||||
|
/// See [Self::wait_for] for more information.
|
||||||
pub async fn wait_for_low(&mut self) {
|
pub async fn wait_for_low(&mut self) {
|
||||||
self.wait_for(Event::LowLevel).await
|
self.wait_for(Event::LowLevel).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for the pin to undergo a transition from low to high.
|
/// Wait for the pin to undergo a transition from low to high.
|
||||||
|
///
|
||||||
|
/// See [Self::wait_for] for more information.
|
||||||
pub async fn wait_for_rising_edge(&mut self) {
|
pub async fn wait_for_rising_edge(&mut self) {
|
||||||
self.wait_for(Event::RisingEdge).await
|
self.wait_for(Event::RisingEdge).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for the pin to undergo a transition from high to low.
|
/// Wait for the pin to undergo a transition from high to low.
|
||||||
|
///
|
||||||
|
/// See [Self::wait_for] for more information.
|
||||||
pub async fn wait_for_falling_edge(&mut self) {
|
pub async fn wait_for_falling_edge(&mut self) {
|
||||||
self.wait_for(Event::FallingEdge).await
|
self.wait_for(Event::FallingEdge).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for the pin to undergo any transition, i.e low to high OR high
|
/// Wait for the pin to undergo any transition, i.e low to high OR high
|
||||||
/// to low.
|
/// to low.
|
||||||
|
///
|
||||||
|
/// See [Self::wait_for] for more information.
|
||||||
pub async fn wait_for_any_edge(&mut self) {
|
pub async fn wait_for_any_edge(&mut self) {
|
||||||
self.wait_for(Event::AnyEdge).await
|
self.wait_for(Event::AnyEdge).await
|
||||||
}
|
}
|
||||||
@ -2250,60 +2404,117 @@ mod asynch {
|
|||||||
where
|
where
|
||||||
P: InputPin,
|
P: InputPin,
|
||||||
{
|
{
|
||||||
/// Wait until the pin is high. If it is already high, return
|
/// Wait until the pin experiences a particular [`Event`].
|
||||||
/// immediately.
|
///
|
||||||
|
/// The GPIO driver will disable listening for the event once it occurs,
|
||||||
|
/// or if the `Future` is dropped.
|
||||||
|
///
|
||||||
|
/// Note that calling this function will overwrite previous
|
||||||
|
/// [`listen`][Self::listen] operations for this pin.
|
||||||
|
#[inline]
|
||||||
|
pub async fn wait_for(&mut self, event: Event) {
|
||||||
|
self.pin.wait_for(event).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait until the pin is high.
|
||||||
|
///
|
||||||
|
/// See [Self::wait_for] for more information.
|
||||||
pub async fn wait_for_high(&mut self) {
|
pub async fn wait_for_high(&mut self) {
|
||||||
self.pin.wait_for_high().await
|
self.pin.wait_for_high().await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait until the pin is low. If it is already low, return immediately.
|
/// Wait until the pin is low.
|
||||||
|
///
|
||||||
|
/// See [Self::wait_for] for more information.
|
||||||
pub async fn wait_for_low(&mut self) {
|
pub async fn wait_for_low(&mut self) {
|
||||||
self.pin.wait_for_low().await
|
self.pin.wait_for_low().await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for the pin to undergo a transition from low to high.
|
/// Wait for the pin to undergo a transition from low to high.
|
||||||
|
///
|
||||||
|
/// See [Self::wait_for] for more information.
|
||||||
pub async fn wait_for_rising_edge(&mut self) {
|
pub async fn wait_for_rising_edge(&mut self) {
|
||||||
self.pin.wait_for_rising_edge().await
|
self.pin.wait_for_rising_edge().await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for the pin to undergo a transition from high to low.
|
/// Wait for the pin to undergo a transition from high to low.
|
||||||
|
///
|
||||||
|
/// See [Self::wait_for] for more information.
|
||||||
pub async fn wait_for_falling_edge(&mut self) {
|
pub async fn wait_for_falling_edge(&mut self) {
|
||||||
self.pin.wait_for_falling_edge().await
|
self.pin.wait_for_falling_edge().await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for the pin to undergo any transition, i.e low to high OR high
|
/// Wait for the pin to undergo any transition, i.e low to high OR high
|
||||||
/// to low.
|
/// to low.
|
||||||
|
///
|
||||||
|
/// See [Self::wait_for] for more information.
|
||||||
pub async fn wait_for_any_edge(&mut self) {
|
pub async fn wait_for_any_edge(&mut self) {
|
||||||
self.pin.wait_for_any_edge().await
|
self.pin.wait_for_any_edge().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||||
struct PinFuture {
|
struct PinFuture<'d, P: InputPin> {
|
||||||
pin_num: u8,
|
pin: Flex<'d, P>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PinFuture {
|
impl<'d, P: InputPin> PinFuture<'d, P> {
|
||||||
fn new(pin_num: u8) -> Self {
|
fn pin_mask(&self) -> u32 {
|
||||||
Self { pin_num }
|
let bank = self.pin.gpio_bank(private::Internal);
|
||||||
|
1 << (self.pin.number() - bank.offset())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_done(&self) -> bool {
|
||||||
|
// Only the interrupt handler should clear the async bit, and only if the
|
||||||
|
// specific pin is handling an interrupt.
|
||||||
|
self.pin
|
||||||
|
.gpio_bank(private::Internal)
|
||||||
|
.async_operations()
|
||||||
|
.load(Ordering::Acquire)
|
||||||
|
& self.pin_mask()
|
||||||
|
== 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::future::Future for PinFuture {
|
impl<P: InputPin> core::future::Future for PinFuture<'_, P> {
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn poll(self: core::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: core::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
PIN_WAKERS[self.pin_num as usize].register(cx.waker());
|
PIN_WAKERS[self.pin.number() as usize].register(cx.waker());
|
||||||
|
|
||||||
// if pin is no longer listening its been triggered
|
if self.is_done() {
|
||||||
// therefore the future has resolved
|
|
||||||
if !is_listening(self.pin_num) {
|
|
||||||
Poll::Ready(())
|
Poll::Ready(())
|
||||||
} else {
|
} else {
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<P: InputPin> Drop for PinFuture<'_, P> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// If the pin isn't listening, the future has either been dropped before setup,
|
||||||
|
// or the interrupt has already been handled.
|
||||||
|
if self.pin.is_listening() {
|
||||||
|
// Make sure the future isn't dropped while the interrupt is being handled.
|
||||||
|
// This prevents tricky drop-and-relisten scenarios.
|
||||||
|
|
||||||
|
set_int_enable(
|
||||||
|
self.pin.number(),
|
||||||
|
None, // Do not disable handling pending interrupts.
|
||||||
|
0, // Disable generating new events
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
while self.pin.is_interrupt_set() {}
|
||||||
|
|
||||||
|
// Unmark pin as async
|
||||||
|
self.pin
|
||||||
|
.gpio_bank(private::Internal)
|
||||||
|
.async_operations()
|
||||||
|
.fetch_and(!self.pin_mask(), Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod embedded_hal_impls {
|
mod embedded_hal_impls {
|
||||||
|
@ -787,14 +787,8 @@ pub(crate) enum InterruptStatusRegisterAccess {
|
|||||||
impl InterruptStatusRegisterAccess {
|
impl InterruptStatusRegisterAccess {
|
||||||
pub(crate) fn interrupt_status_read(self) -> u32 {
|
pub(crate) fn interrupt_status_read(self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
Self::Bank0 => match Cpu::current() {
|
Self::Bank0 => unsafe { GPIO::steal() }.status().read().bits(),
|
||||||
Cpu::ProCpu => unsafe { GPIO::steal() }.pcpu_int().read().bits(),
|
Self::Bank1 => unsafe { GPIO::steal() }.status1().read().bits(),
|
||||||
Cpu::AppCpu => unsafe { GPIO::steal() }.acpu_int().read().bits(),
|
|
||||||
},
|
|
||||||
Self::Bank1 => match Cpu::current() {
|
|
||||||
Cpu::ProCpu => unsafe { GPIO::steal() }.pcpu_int1().read().bits(),
|
|
||||||
Cpu::AppCpu => unsafe { GPIO::steal() }.acpu_int1().read().bits(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Introduced the `wifi6` symbol (#2612)
|
- Introduced the `wifi6` symbol (#2612)
|
||||||
|
- Introduced the `gpio_bank_1` symbol (#2625)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@ symbols = [
|
|||||||
"timg_timer1",
|
"timg_timer1",
|
||||||
"touch",
|
"touch",
|
||||||
"large_intr_status",
|
"large_intr_status",
|
||||||
|
"gpio_bank_1",
|
||||||
|
|
||||||
# ROM capabilities
|
# ROM capabilities
|
||||||
"rom_crc_le",
|
"rom_crc_le",
|
||||||
|
@ -95,4 +95,5 @@ symbols = [
|
|||||||
# Additional peripherals defined by us (the developers):
|
# Additional peripherals defined by us (the developers):
|
||||||
"clic",
|
"clic",
|
||||||
"very_large_intr_status",
|
"very_large_intr_status",
|
||||||
|
"gpio_bank_1",
|
||||||
]
|
]
|
||||||
|
@ -58,6 +58,7 @@ symbols = [
|
|||||||
"ulp_riscv_core",
|
"ulp_riscv_core",
|
||||||
"timg_timer1",
|
"timg_timer1",
|
||||||
"large_intr_status",
|
"large_intr_status",
|
||||||
|
"gpio_bank_1",
|
||||||
|
|
||||||
# ROM capabilities
|
# ROM capabilities
|
||||||
"rom_crc_le",
|
"rom_crc_le",
|
||||||
|
@ -72,6 +72,7 @@ symbols = [
|
|||||||
"ulp_riscv_core",
|
"ulp_riscv_core",
|
||||||
"timg_timer1",
|
"timg_timer1",
|
||||||
"very_large_intr_status",
|
"very_large_intr_status",
|
||||||
|
"gpio_bank_1",
|
||||||
|
|
||||||
# ROM capabilities
|
# ROM capabilities
|
||||||
"rom_crc_le",
|
"rom_crc_le",
|
||||||
|
@ -119,6 +119,14 @@ mod tests {
|
|||||||
assert_eq!(test_gpio1.is_high(), false);
|
assert_eq!(test_gpio1.is_high(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
async fn waiting_for_level_does_not_hang(ctx: Context) {
|
||||||
|
let mut test_gpio1 = Input::new(ctx.test_gpio1, Pull::Down);
|
||||||
|
let _test_gpio2 = Output::new(ctx.test_gpio2, Level::High);
|
||||||
|
|
||||||
|
test_gpio1.wait_for_high().await;
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gpio_output(ctx: Context) {
|
fn gpio_output(ctx: Context) {
|
||||||
let mut test_gpio2 = Output::new(ctx.test_gpio2, Level::Low);
|
let mut test_gpio2 = Output::new(ctx.test_gpio2, Level::Low);
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
use embassy_time::{Duration, Timer};
|
use embassy_time::{Duration, Timer};
|
||||||
use esp_hal::{
|
use esp_hal::{
|
||||||
gpio::{AnyPin, Input, Io, Level, Output, Pull},
|
gpio::{AnyPin, Flex, Input, Io, Level, Output, Pull},
|
||||||
interrupt::InterruptConfigurable,
|
interrupt::InterruptConfigurable,
|
||||||
macros::handler,
|
macros::handler,
|
||||||
timer::timg::TimerGroup,
|
timer::timg::TimerGroup,
|
||||||
@ -23,7 +23,16 @@ use portable_atomic::{AtomicUsize, Ordering};
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
unsafe extern "C" fn GPIO() {
|
unsafe extern "C" fn GPIO() {
|
||||||
// do nothing, prevents binding the default handler
|
// Prevents binding the default handler, but we need to clear the GPIO
|
||||||
|
// interrupts by hand.
|
||||||
|
let peripherals = esp_hal::peripherals::Peripherals::steal();
|
||||||
|
|
||||||
|
let (gpio1, _) = hil_test::common_test_pins!(peripherals);
|
||||||
|
|
||||||
|
// Using flex will not mutate the pin.
|
||||||
|
let mut gpio1 = Flex::new(gpio1);
|
||||||
|
|
||||||
|
gpio1.clear_interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[handler]
|
#[handler]
|
||||||
@ -71,6 +80,14 @@ mod tests {
|
|||||||
let timg0 = TimerGroup::new(peripherals.TIMG0);
|
let timg0 = TimerGroup::new(peripherals.TIMG0);
|
||||||
esp_hal_embassy::init(timg0.timer0);
|
esp_hal_embassy::init(timg0.timer0);
|
||||||
|
|
||||||
|
// We need to enable the GPIO interrupt, otherwise the async Future's
|
||||||
|
// setup or Drop implementation hangs.
|
||||||
|
esp_hal::interrupt::enable(
|
||||||
|
esp_hal::peripherals::Interrupt::GPIO,
|
||||||
|
esp_hal::interrupt::Priority::Priority1,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let counter = drive_pins(gpio1, gpio2).await;
|
let counter = drive_pins(gpio1, gpio2).await;
|
||||||
|
|
||||||
// GPIO is bound to something else, so we don't expect the async API to work.
|
// GPIO is bound to something else, so we don't expect the async API to work.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user