diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 00ff45a27..555bc96b0 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implemented `embedded_io::ReadReady` for `Uart` and `UartRx` (#3423) - Implemented `embedded_io::WriteReady` for `Uart` and `UartTx` (#3423) - ESP32-H2: Support for ADC calibration (#3414) +- Expose ADC asynchrounous functionalities where applicable (#3443) - Added `UartInterrupt::RxTimeout` support (#3493) - UART: Added HW and SW flow control config option (#3435) diff --git a/esp-hal/src/analog/adc/riscv.rs b/esp-hal/src/analog/adc/riscv.rs index e7d37278d..121cdebf1 100644 --- a/esp-hal/src/analog/adc/riscv.rs +++ b/esp-hal/src/analog/adc/riscv.rs @@ -8,6 +8,16 @@ cfg_if::cfg_if! { } } +use core::{ + pin::Pin, + task::{Context, Poll}, +}; + +// We only have to count on devices that have multiple ADCs sharing the same interrupt +#[cfg(all(adc1, adc2))] +use portable_atomic::{AtomicU32, Ordering}; +use procmacros::handler; + pub use self::calibration::*; use super::{AdcCalSource, AdcConfig, Attenuation}; #[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2))] @@ -15,7 +25,7 @@ use crate::efuse::Efuse; use crate::{ Async, Blocking, - analog::adc::asynch::AdcFuture, + asynch::AtomicWaker, interrupt::{InterruptConfigurable, InterruptHandler}, peripherals::{APB_SARADC, Interrupt}, soc::regi2c, @@ -302,8 +312,8 @@ where /// Reconfigures the ADC driver to operate in asynchronous mode. pub fn into_async(mut self) -> Adc<'d, ADCI, Async> { - asynch::acquire_async_adc(); - self.set_interrupt_handler(asynch::adc_interrupt_handler); + acquire_async_adc(); + self.set_interrupt_handler(adc_interrupt_handler); // Reset interrupt flags and disable oneshot reading to normalize state before // entering async mode, otherwise there can be '0' readings, happening initially @@ -505,7 +515,7 @@ where { /// Create a new instance in [crate::Blocking] mode. pub fn into_blocking(self) -> Adc<'d, ADCI, Blocking> { - if asynch::release_async_adc() { + if release_async_adc() { // Disable ADC interrupt on all cores if the last async ADC instance is disabled for cpu in crate::system::Cpu::all() { crate::interrupt::disable(cpu, InterruptSource); @@ -527,7 +537,7 @@ where /// underlies the pin. pub async fn read_oneshot(&mut self, pin: &mut super::AdcPin) -> u16 where - ADCI: asynch::AsyncAccess, + ADCI: Instance, PIN: super::AdcChannel, CS: super::AdcCalScheme, { @@ -565,160 +575,144 @@ where } } -/// Async functionality -pub(crate) mod asynch { - use core::{ - marker::PhantomData, - pin::Pin, - task::{Context, Poll}, - }; +#[cfg(all(adc1, adc2))] +static ASYNC_ADC_COUNT: AtomicU32 = AtomicU32::new(0); - // We only have to count on devices that have multiple ADCs sharing the same interrupt +pub(super) fn acquire_async_adc() { #[cfg(all(adc1, adc2))] - use portable_atomic::{AtomicU32, Ordering}; - use procmacros::handler; + ASYNC_ADC_COUNT.fetch_add(1, Ordering::Relaxed); +} - use crate::{Async, asynch::AtomicWaker, peripherals::APB_SARADC}; - - #[cfg(all(adc1, adc2))] - static ASYNC_ADC_COUNT: AtomicU32 = AtomicU32::new(0); - - pub(super) fn acquire_async_adc() { - #[cfg(all(adc1, adc2))] - ASYNC_ADC_COUNT.fetch_add(1, Ordering::Relaxed); - } - - pub(super) fn release_async_adc() -> bool { - cfg_if::cfg_if! { - if #[cfg(all(adc1, adc2))] { - ASYNC_ADC_COUNT.fetch_sub(1, Ordering::Relaxed) == 1 - } else { - true - } - } - } - - #[handler] - pub(crate) fn adc_interrupt_handler() { - let saradc = APB_SARADC::regs(); - let interrupt_status = saradc.int_st().read(); - - #[cfg(adc1)] - if interrupt_status.adc1_done().bit_is_set() { - unsafe { handle_async(crate::peripherals::ADC1::steal()) } - } - - #[cfg(adc2)] - if interrupt_status.adc2_done().bit_is_set() { - unsafe { handle_async(crate::peripherals::ADC2::steal()) } - } - } - - fn handle_async(_instance: ADCI) { - ADCI::waker().wake(); - ADCI::disable_interrupt(); - } - - #[doc(hidden)] - pub trait AsyncAccess { - /// Enable the ADC interrupt - fn enable_interrupt(); - - /// Disable the ADC interrupt - fn disable_interrupt(); - - /// Clear the ADC interrupt - fn clear_interrupt(); - - /// Obtain the waker for the ADC interrupt - fn waker() -> &'static AtomicWaker; - } - - #[cfg(adc1)] - impl AsyncAccess for crate::peripherals::ADC1<'_> { - fn enable_interrupt() { - APB_SARADC::regs() - .int_ena() - .modify(|_, w| w.adc1_done().set_bit()); - } - - fn disable_interrupt() { - APB_SARADC::regs() - .int_ena() - .modify(|_, w| w.adc1_done().clear_bit()); - } - - fn clear_interrupt() { - APB_SARADC::regs() - .int_clr() - .write(|w| w.adc1_done().clear_bit_by_one()); - } - - fn waker() -> &'static AtomicWaker { - static WAKER: AtomicWaker = AtomicWaker::new(); - - &WAKER - } - } - - #[cfg(adc2)] - impl AsyncAccess for crate::peripherals::ADC2<'_> { - fn enable_interrupt() { - APB_SARADC::regs() - .int_ena() - .modify(|_, w| w.adc2_done().set_bit()); - } - - fn disable_interrupt() { - APB_SARADC::regs() - .int_ena() - .modify(|_, w| w.adc2_done().clear_bit()); - } - - fn clear_interrupt() { - APB_SARADC::regs() - .int_clr() - .write(|w| w.adc2_done().clear_bit_by_one()); - } - - fn waker() -> &'static AtomicWaker { - static WAKER: AtomicWaker = AtomicWaker::new(); - - &WAKER - } - } - - #[must_use = "futures do nothing unless you `.await` or poll them"] - pub(crate) struct AdcFuture { - phantom: PhantomData, - } - - impl AdcFuture { - pub fn new(_self: &super::Adc<'_, ADCI, Async>) -> Self { - Self { - phantom: PhantomData, - } - } - } - - impl core::future::Future for AdcFuture { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if ADCI::is_done() { - ADCI::clear_interrupt(); - Poll::Ready(()) - } else { - ADCI::waker().register(cx.waker()); - ADCI::enable_interrupt(); - Poll::Pending - } - } - } - - impl Drop for AdcFuture { - fn drop(&mut self) { - ADCI::disable_interrupt(); +pub(super) fn release_async_adc() -> bool { + cfg_if::cfg_if! { + if #[cfg(all(adc1, adc2))] { + ASYNC_ADC_COUNT.fetch_sub(1, Ordering::Relaxed) == 1 + } else { + true } } } + +#[handler] +pub(crate) fn adc_interrupt_handler() { + let saradc = APB_SARADC::regs(); + let interrupt_status = saradc.int_st().read(); + + #[cfg(adc1)] + if interrupt_status.adc1_done().bit_is_set() { + unsafe { handle_async(crate::peripherals::ADC1::steal()) } + } + + #[cfg(adc2)] + if interrupt_status.adc2_done().bit_is_set() { + unsafe { handle_async(crate::peripherals::ADC2::steal()) } + } +} + +fn handle_async(_instance: ADCI) { + ADCI::waker().wake(); + ADCI::disable_interrupt(); +} + +/// Enable asynchronous access. +pub trait Instance: crate::private::Sealed { + /// Enable the ADC interrupt + fn enable_interrupt(); + + /// Disable the ADC interrupt + fn disable_interrupt(); + + /// Clear the ADC interrupt + fn clear_interrupt(); + + /// Obtain the waker for the ADC interrupt + fn waker() -> &'static AtomicWaker; +} + +#[cfg(adc1)] +impl Instance for crate::peripherals::ADC1<'_> { + fn enable_interrupt() { + APB_SARADC::regs() + .int_ena() + .modify(|_, w| w.adc1_done().set_bit()); + } + + fn disable_interrupt() { + APB_SARADC::regs() + .int_ena() + .modify(|_, w| w.adc1_done().clear_bit()); + } + + fn clear_interrupt() { + APB_SARADC::regs() + .int_clr() + .write(|w| w.adc1_done().clear_bit_by_one()); + } + + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + + &WAKER + } +} + +#[cfg(adc2)] +impl Instance for crate::peripherals::ADC2<'_> { + fn enable_interrupt() { + APB_SARADC::regs() + .int_ena() + .modify(|_, w| w.adc2_done().set_bit()); + } + + fn disable_interrupt() { + APB_SARADC::regs() + .int_ena() + .modify(|_, w| w.adc2_done().clear_bit()); + } + + fn clear_interrupt() { + APB_SARADC::regs() + .int_clr() + .write(|w| w.adc2_done().clear_bit_by_one()); + } + + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + + &WAKER + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub(crate) struct AdcFuture { + phantom: PhantomData, +} + +impl AdcFuture { + pub fn new(_self: &super::Adc<'_, ADCI, Async>) -> Self { + Self { + phantom: PhantomData, + } + } +} + +impl core::future::Future for AdcFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if ADCI::is_done() { + ADCI::clear_interrupt(); + Poll::Ready(()) + } else { + ADCI::waker().register(cx.waker()); + ADCI::enable_interrupt(); + Poll::Pending + } + } +} + +impl Drop for AdcFuture { + fn drop(&mut self) { + ADCI::disable_interrupt(); + } +} diff --git a/qa-test/src/bin/embassy_adc_async_abstraction.rs b/qa-test/src/bin/embassy_adc_async_abstraction.rs new file mode 100644 index 000000000..815afd1cd --- /dev/null +++ b/qa-test/src/bin/embassy_adc_async_abstraction.rs @@ -0,0 +1,99 @@ +//! This shows how to asynchronously read ADC data and offers an abstraction to +//! convert the raw value to a type-specific interpretation. +//! +//! PINS +//! GPIO4 for ADC1 + +//% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2 + +#![no_std] +#![no_main] + +use core::marker::PhantomData; + +use embassy_executor::Spawner; +use esp_backtrace as _; +use esp_hal::{ + Async, + analog::adc::{ + Adc, + AdcCalScheme, + AdcChannel, + AdcConfig, + AdcPin, + Attenuation, + Instance, + RegisterAccess, + }, + delay::Delay, + timer::timg::TimerGroup, +}; +use esp_println::println; + +trait Sensor { + async fn measure(&mut self) -> u16; +} + +trait Converter { + /// Converts the raw ADC value to a valid metering. + fn raw_to_metering(raw_value: u16) -> u16; +} + +pub struct AdcSensor<'d, ADCI, PIN, CS, MC> { + adc: Adc<'d, ADCI, Async>, + pin: AdcPin, + _phantom: PhantomData, +} + +impl<'d, ADCI, PIN, CS, MC> AdcSensor<'d, ADCI, PIN, CS, MC> { + pub fn new(adc: Adc<'d, ADCI, Async>, pin: AdcPin) -> Self { + let _phantom = PhantomData:: {}; + Self { adc, pin, _phantom } + } +} + +impl<'d, ADCI, PIN, CS, MC> Sensor for AdcSensor<'d, ADCI, PIN, CS, MC> +where + ADCI: RegisterAccess + Instance + 'd, + PIN: AdcChannel, + CS: AdcCalScheme, + MC: Converter, +{ + async fn measure(&mut self) -> u16 { + let raw_value = self.adc.read_oneshot(&mut self.pin).await; + + MC::raw_to_metering(raw_value) + } +} + +/// Returns the provided raw value as is. +pub struct Identity; + +impl Converter for Identity { + fn raw_to_metering(raw_value: u16) -> u16 { + raw_value + } +} + +#[esp_hal_embassy::main] +async fn main(_spawner: Spawner) { + esp_println::logger::init_logger_from_env(); + let peripherals = esp_hal::init(esp_hal::Config::default()); + + let timg0 = TimerGroup::new(peripherals.TIMG0); + esp_hal_embassy::init(timg0.timer0); + + let mut adc1_config = AdcConfig::new(); + let analog_pin1 = peripherals.GPIO4; + let pin1 = adc1_config.enable_pin(analog_pin1, Attenuation::_11dB); + let adc1 = Adc::new(peripherals.ADC1, adc1_config).into_async(); + let mut id = AdcSensor::<_, _, _, Identity>::new(adc1, pin1); + + let delay = Delay::new(); + + loop { + let id_value = id.measure().await; + println!("id value: {}", id_value); + delay.delay_millis(1000); + } +}