From 9a28bdfdbdd43afacde20fc6a7d574fd12d2a907 Mon Sep 17 00:00:00 2001 From: Davo Date: Fri, 31 Jan 2025 01:52:47 -0600 Subject: [PATCH] ADC: Add async support for oneshot reads for esp32c3 and esp32c6 (#2925) * ADC: Add async support for oneshot reads for esp32c3 and esp32c6 * ADC: change interrupt waking logic - fix migrating document - add ADC2 reading qa example and fix sensor reading * ADC: run `cargo xtask fmt-packages` * ADC: remove TODO comment --- esp-hal/CHANGELOG.md | 1 + esp-hal/MIGRATING-0.23.md | 10 ++ esp-hal/src/analog/adc/esp32.rs | 10 +- esp-hal/src/analog/adc/riscv.rs | 254 ++++++++++++++++++++++++++++++- esp-hal/src/analog/adc/xtensa.rs | 8 +- esp-hal/src/rng.rs | 3 +- qa-test/src/bin/embassy_adc.rs | 52 +++++++ 7 files changed, 330 insertions(+), 8 deletions(-) create mode 100644 qa-test/src/bin/embassy_adc.rs diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 2fd8cdf44..d4f643acd 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -106,6 +106,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ESP32-S2: Made Wi-Fi peripheral non virtual. (#2942) - `UartRx::check_for_errors`, `Uart::check_for_rx_errors`, `{Uart, UartRx}::read_buffered_bytes` (#2935) - Added `i2c` interrupt API (#2944) +- Async support for ADC oneshot reads for ESP32C3 and ESP32C6 (#2925) ### Changed diff --git a/esp-hal/MIGRATING-0.23.md b/esp-hal/MIGRATING-0.23.md index 554025bbd..8517a1811 100644 --- a/esp-hal/MIGRATING-0.23.md +++ b/esp-hal/MIGRATING-0.23.md @@ -207,4 +207,14 @@ All async functions now include the `_async` postfix. Additionally the non-async ```diff - let result = i2c.write_read(0x77, &[0xaa], &mut data).await; + let result = i2c.write_read_async(0x77, &[0xaa], &mut data).await; + +## ADC Changes + +The ADC driver has gained a new `Async`/`Blocking` mode parameter. +NOTE: Async support is only supported in ESP32C3 and ESP32C6 for now + + +```diff +- Adc<'d, ADC> ++ Adc<'d, ADC, Blocking> ``` diff --git a/esp-hal/src/analog/adc/esp32.rs b/esp-hal/src/analog/adc/esp32.rs index 6d12411d5..767a4dcff 100644 --- a/esp-hal/src/analog/adc/esp32.rs +++ b/esp-hal/src/analog/adc/esp32.rs @@ -1,3 +1,5 @@ +use core::marker::PhantomData; + use super::{AdcConfig, Attenuation}; use crate::{ peripheral::PeripheralRef, @@ -198,13 +200,14 @@ impl RegisterAccess for ADC2 { } /// Analog-to-Digital Converter peripheral driver. -pub struct Adc<'d, ADC> { +pub struct Adc<'d, ADC, Dm: crate::DriverMode> { _adc: PeripheralRef<'d, ADC>, attenuations: [Option; NUM_ATTENS], active_channel: Option, + _phantom: PhantomData, } -impl<'d, ADCI> Adc<'d, ADCI> +impl<'d, ADCI> Adc<'d, ADCI, crate::Blocking> where ADCI: RegisterAccess, { @@ -280,6 +283,7 @@ where _adc: adc_instance.into_ref(), attenuations: config.attenuations, active_channel: None, + _phantom: PhantomData, } } @@ -329,7 +333,7 @@ where } } -impl Adc<'_, ADC1> { +impl Adc<'_, ADC1, crate::Blocking> { /// Enable the Hall sensor pub fn enable_hall_sensor() { RTC_IO::regs() diff --git a/esp-hal/src/analog/adc/riscv.rs b/esp-hal/src/analog/adc/riscv.rs index eb1d472ad..c8924dcaf 100644 --- a/esp-hal/src/analog/adc/riscv.rs +++ b/esp-hal/src/analog/adc/riscv.rs @@ -1,3 +1,10 @@ +use core::marker::PhantomData; + +#[cfg(esp32c3)] +use Interrupt::APB_ADC as InterruptSource; +#[cfg(esp32c6)] +use Interrupt::APB_SARADC as InterruptSource; + #[cfg(not(esp32h2))] pub use self::calibration::*; use super::{AdcCalSource, AdcConfig, Attenuation}; @@ -5,10 +12,18 @@ use super::{AdcCalSource, AdcConfig, Attenuation}; use crate::clock::clocks_ll::regi2c_write_mask; #[cfg(any(esp32c2, esp32c3, esp32c6))] use crate::efuse::Efuse; +#[cfg(any(esp32c3, esp32c6))] +use crate::{ + analog::adc::asynch::AdcFuture, + interrupt::{InterruptConfigurable, InterruptHandler}, + peripherals::Interrupt, + Async, +}; use crate::{ peripheral::PeripheralRef, peripherals::APB_SARADC, system::{GenericPeripheralGuard, Peripheral}, + Blocking, }; mod calibration; @@ -384,14 +399,15 @@ impl super::CalibrationAccess for crate::peripherals::ADC2 { } /// Analog-to-Digital Converter peripheral driver. -pub struct Adc<'d, ADCI> { +pub struct Adc<'d, ADCI, Dm: crate::DriverMode> { _adc: PeripheralRef<'d, ADCI>, attenuations: [Option; NUM_ATTENS], active_channel: Option, _guard: GenericPeripheralGuard<{ Peripheral::ApbSarAdc as u8 }>, + _phantom: PhantomData, } -impl<'d, ADCI> Adc<'d, ADCI> +impl<'d, ADCI> Adc<'d, ADCI, Blocking> where ADCI: RegisterAccess + 'd, { @@ -415,6 +431,26 @@ where attenuations: config.attenuations, active_channel: None, _guard: guard, + _phantom: PhantomData, + } + } + + #[cfg(any(esp32c3, esp32c6))] + /// Reconfigures the ADC driver to operate in asynchronous mode. + pub fn into_async(mut self) -> Adc<'d, ADCI, Async> { + self.set_interrupt_handler(asynch::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 + // using ADC2 + ADCI::reset(); + + Adc { + _adc: self._adc, + attenuations: self.attenuations, + active_channel: self.active_channel, + _guard: self._guard, + _phantom: PhantomData, } } @@ -493,6 +529,22 @@ where } } +impl crate::private::Sealed for Adc<'_, ADCI, Blocking> {} + +#[cfg(any(esp32c3, esp32c6))] +impl InterruptConfigurable for Adc<'_, ADCI, Blocking> { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + for core in crate::Cpu::other() { + crate::interrupt::disable(core, InterruptSource); + } + unsafe { crate::interrupt::bind_interrupt(InterruptSource, handler.handler()) }; + unwrap!(crate::interrupt::enable( + InterruptSource, + handler.priority() + )); + } +} + #[cfg(any(esp32c2, esp32c3, esp32c6))] impl super::AdcCalEfuse for crate::peripherals::ADC1 { fn init_code(atten: Attenuation) -> Option { @@ -582,3 +634,201 @@ mod adc_implementation { ] } } + +#[cfg(any(esp32c3, esp32c6))] +impl<'d, ADCI> Adc<'d, ADCI, Async> +where + ADCI: RegisterAccess + 'd, +{ + /// Create a new instance in [crate::Blocking] mode. + pub fn into_blocking(self) -> Adc<'d, ADCI, Blocking> { + crate::interrupt::disable(crate::Cpu::current(), InterruptSource); + Adc { + _adc: self._adc, + attenuations: self.attenuations, + active_channel: self.active_channel, + _guard: self._guard, + _phantom: PhantomData, + } + } + + /// Request that the ADC begin a conversion on the specified pin + /// + /// This method takes an [AdcPin](super::AdcPin) reference, as it is + /// expected that the ADC will be able to sample whatever channel + /// underlies the pin. + pub async fn read_oneshot(&mut self, pin: &mut super::AdcPin) -> u16 + where + ADCI: asynch::AsyncAccess, + PIN: super::AdcChannel, + CS: super::AdcCalScheme, + { + let channel = PIN::CHANNEL; + if self.attenuations[channel as usize].is_none() { + panic!("Channel {} is not configured reading!", channel); + } + + // Set ADC unit calibration according used scheme for pin + ADCI::set_init_code(pin.cal_scheme.adc_cal()); + + let attenuation = self.attenuations[channel as usize].unwrap() as u8; + ADCI::config_onetime_sample(channel, attenuation); + ADCI::start_onetime_sample(); + + // Wait for ADC to finish conversion and get value + let adc_ready_future = AdcFuture::new(self); + adc_ready_future.await; + let converted_value = ADCI::read_data(); + + // There is a hardware limitation. If the APB clock frequency is high, the step + // of this reg signal: ``onetime_start`` may not be captured by the + // ADC digital controller (when its clock frequency is too slow). A rough + // estimate for this step should be at least 3 ADC digital controller + // clock cycle. + // + // This limitation will be removed in hardware future versions. + // We reset ``onetime_start`` in `reset` and assume enough time has passed until + // the next sample is requested. + + ADCI::reset(); + + // Postprocess converted value according to calibration scheme used for pin + pin.cal_scheme.adc_val(converted_value) + } +} + +#[cfg(any(esp32c3, esp32c6))] +/// Async functionality +pub(crate) mod asynch { + use core::{ + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, + }; + + use procmacros::handler; + + use crate::{asynch::AtomicWaker, peripherals::APB_SARADC, Async}; + + #[handler] + pub(crate) fn adc_interrupt_handler() { + let saradc = APB_SARADC::regs(); + let interrupt_status = saradc.int_st().read(); + + if interrupt_status.adc1_done().bit_is_set() { + handle_async(crate::peripherals::ADC1) + } + + #[cfg(esp32c3)] + if interrupt_status.adc2_done().bit_is_set() { + handle_async(crate::peripherals::ADC2) + } + } + + 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; + } + + 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(esp32c3)] + 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(); + } + } +} diff --git a/esp-hal/src/analog/adc/xtensa.rs b/esp-hal/src/analog/adc/xtensa.rs index 65b1376c9..c385f05e5 100644 --- a/esp-hal/src/analog/adc/xtensa.rs +++ b/esp-hal/src/analog/adc/xtensa.rs @@ -1,3 +1,5 @@ +use core::marker::PhantomData; + #[cfg(esp32s3)] pub use self::calibration::*; use super::{AdcCalScheme, AdcCalSource, AdcChannel, AdcConfig, AdcPin, Attenuation}; @@ -380,14 +382,15 @@ impl super::CalibrationAccess for crate::peripherals::ADC2 { } /// Analog-to-Digital Converter peripheral driver. -pub struct Adc<'d, ADC> { +pub struct Adc<'d, ADC, Dm: crate::DriverMode> { _adc: PeripheralRef<'d, ADC>, active_channel: Option, last_init_code: u16, _guard: GenericPeripheralGuard<{ Peripheral::ApbSarAdc as u8 }>, + _phantom: PhantomData, } -impl<'d, ADCI> Adc<'d, ADCI> +impl<'d, ADCI> Adc<'d, ADCI, crate::Blocking> where ADCI: RegisterAccess, { @@ -467,6 +470,7 @@ where active_channel: None, last_init_code: 0, _guard: guard, + _phantom: PhantomData, } } diff --git a/esp-hal/src/rng.rs b/esp-hal/src/rng.rs index d943e9ba1..0f4cd2176 100644 --- a/esp-hal/src/rng.rs +++ b/esp-hal/src/rng.rs @@ -60,6 +60,7 @@ //! ### TRNG operation //! ```rust, no_run #![doc = crate::before_snippet!()] +//! # use esp_hal::Blocking; //! # use esp_hal::rng::Trng; //! # use esp_hal::peripherals::Peripherals; //! # use esp_hal::peripherals::ADC1; @@ -80,7 +81,7 @@ //! analog_pin, //! Attenuation::_11dB //! ); -//! let mut adc1 = Adc::::new(peripherals.ADC1, adc1_config); +//! let mut adc1 = Adc::::new(peripherals.ADC1, adc1_config); //! let pin_value: u16 = nb::block!(adc1.read_oneshot(&mut adc1_pin))?; //! rng.read(&mut buf); //! true_rand = rng.random(); diff --git a/qa-test/src/bin/embassy_adc.rs b/qa-test/src/bin/embassy_adc.rs new file mode 100644 index 000000000..cb3fdb8fa --- /dev/null +++ b/qa-test/src/bin/embassy_adc.rs @@ -0,0 +1,52 @@ +//! This shows how to asynchronously read ADC data + +//% CHIPS: esp32c6 esp32c3 + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use esp_backtrace as _; +use esp_hal::{ + analog::adc::{Adc, AdcConfig, Attenuation}, + delay::Delay, + timer::timg::TimerGroup, +}; +use esp_println::println; + +#[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 mut pin1 = adc1_config.enable_pin(analog_pin1, Attenuation::_11dB); + let mut adc1 = Adc::new(peripherals.ADC1, adc1_config).into_async(); + + cfg_if::cfg_if! { + if #[cfg(feature = "esp32c3")] { + let mut adc2_config = AdcConfig::new(); + let analog_pin2 = peripherals.GPIO5; + let mut pin2 = adc2_config.enable_pin(analog_pin2, Attenuation::_11dB); + let mut adc2 = Adc::new(peripherals.ADC2, adc2_config).into_async(); + } + } + + let delay = Delay::new(); + + loop { + let adc1_value: u16 = adc1.read_oneshot(&mut pin1).await; + println!("ADC1 value: {}", adc1_value); + cfg_if::cfg_if! { + if #[cfg(feature = "esp32c3")] { + let adc2_value: u16 = adc2.read_oneshot(&mut pin2).await; + println!("ADC2 value: {}", adc2_value); + } + } + delay.delay_millis(1000); + } +}