diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 3f7dd605b..314c19050 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `esp_hal::i2s::master::AnyI2s` has been moved to `esp_hal::i2s::AnyI2s` (#3226) - `esp_hal::i2c::master::AnyI2c` has been moved to `esp_hal::i2c::AnyI2c` (#3226) - `SpiDmaBus` no longer adjusts the DMA buffer length for each transfer (#3263) +- `SpiDma` now uses the SPI interrupt (instead of DMA) to wait for completion (#3303) - `gpio::interconnect` types now have a lifetime associated with them (#3302) diff --git a/esp-hal/src/spi/master.rs b/esp-hal/src/spi/master.rs index f0c8886d2..b1379b3bf 100644 --- a/esp-hal/src/spi/master.rs +++ b/esp-hal/src/spi/master.rs @@ -1277,7 +1277,8 @@ mod dma { impl<'d> SpiDma<'d, Blocking> { /// Converts the SPI instance into async mode. #[instability::unstable] - pub fn into_async(self) -> SpiDma<'d, Async> { + pub fn into_async(mut self) -> SpiDma<'d, Async> { + self.set_interrupt_handler(self.spi.handler()); SpiDma { spi: self.spi, channel: self.channel.into_async(), @@ -1354,12 +1355,42 @@ mod dma { pub fn clear_interrupts(&mut self, interrupts: impl Into>) { self.driver().clear_interrupts(interrupts.into()); } + + #[cfg_attr( + not(multi_core), + doc = "Registers an interrupt handler for the peripheral." + )] + #[cfg_attr( + multi_core, + doc = "Registers an interrupt handler for the peripheral on the current core." + )] + #[doc = ""] + /// Note that this will replace any previously registered interrupt + /// handlers. + /// + /// You can restore the default/unhandled interrupt handler by using + /// [crate::interrupt::DEFAULT_INTERRUPT_HANDLER] + /// + /// # Panics + /// + /// Panics if passed interrupt handler is invalid (e.g. has priority + /// `None`) + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + let interrupt = self.driver().info.interrupt; + for core in Cpu::other() { + crate::interrupt::disable(core, interrupt); + } + unsafe { crate::interrupt::bind_interrupt(interrupt, handler.handler()) }; + unwrap!(crate::interrupt::enable(interrupt, handler.priority())); + } } impl<'d> SpiDma<'d, Async> { /// Converts the SPI instance into async mode. #[instability::unstable] pub fn into_blocking(self) -> SpiDma<'d, Blocking> { + crate::interrupt::disable(Cpu::current(), self.driver().info.interrupt); SpiDma { spi: self.spi, channel: self.channel.into_blocking(), @@ -1448,23 +1479,44 @@ mod dma { } async fn wait_for_idle_async(&mut self) { - // As a future enhancement, setup Spi Future in here as well. - if self.rx_transfer_in_progress { _ = DmaRxFuture::new(&mut self.channel.rx).await; self.rx_transfer_in_progress = false; } - core::future::poll_fn(|cx| { - use core::task::Poll; - if self.is_done() { - Poll::Ready(()) - } else { - cx.waker().wake_by_ref(); + struct Fut(Driver); + impl Future for Fut { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.0.interrupts().contains(SpiInterrupt::TransferDone) { + #[cfg(esp32)] + // Need to poll for done-ness even after interrupt fires. + if self.0.busy() { + cx.waker().wake_by_ref(); + return Poll::Pending; + } + + self.0.clear_interrupts(SpiInterrupt::TransferDone.into()); + return Poll::Ready(()); + } + + self.0.state.waker.register(cx.waker()); + self.0 + .enable_listen(SpiInterrupt::TransferDone.into(), true); Poll::Pending } - }) - .await; + } + impl Drop for Fut { + fn drop(&mut self) { + self.0 + .enable_listen(SpiInterrupt::TransferDone.into(), false); + } + } + + if !self.is_done() { + Fut(self.driver()).await; + } if self.tx_transfer_in_progress { // In case DMA TX buffer is bigger than what the SPI consumes, stop the DMA. @@ -3735,8 +3787,8 @@ fn handle_async(instance: I) { let driver = Driver { info, state }; if driver.interrupts().contains(SpiInterrupt::TransferDone) { - state.waker.wake(); driver.enable_listen(SpiInterrupt::TransferDone.into(), false); + state.waker.wake(); } }