From 36c698e5603eb41ccba863f0d39ddc61fd0096ea Mon Sep 17 00:00:00 2001 From: David Gu Date: Thu, 9 Jan 2025 15:08:53 -0600 Subject: [PATCH] ADC: Add async support for oneshot reads for esp32c3 and esp32c6 --- esp-hal/CHANGELOG.md | 1 + esp-hal/src/analog/adc/riscv.rs | 275 +++++++++++++++++++++++++++++++- qa-test/src/bin/embassy_adc.rs | 40 +++++ 3 files changed, 308 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 d15d57c3ed..b9c1372b8d 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -56,6 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `rtc_cntl::{RtcFastClock, RtcSlowClock, RtcCalSel}` now implement `PartialEq`, `Eq`, `Hash` and `defmt::Format` (#2840) - Added `tsens::TemperatureSensor` peripheral for ESP32C6 and ESP32C3 (#2875) - Added `with_rx()` and `with_tx()` methods to Uart, UartRx, and UartTx () +- Async support for ADC oneshot reads for ESP32C3 and ESP32C6 (#2925) ### Changed diff --git a/esp-hal/src/analog/adc/riscv.rs b/esp-hal/src/analog/adc/riscv.rs index f4968cc5bb..b60510e102 100644 --- a/esp-hal/src/analog/adc/riscv.rs +++ b/esp-hal/src/analog/adc/riscv.rs @@ -1,16 +1,22 @@ +use core::marker::PhantomData; #[cfg(not(esp32h2))] pub use self::calibration::*; -use super::{AdcCalSource, AdcConfig, Attenuation}; +use super::{AdcCalScheme, AdcCalSource, AdcChannel, AdcConfig, AdcPin, Attenuation}; #[cfg(any(esp32c6, esp32h2))] use crate::clock::clocks_ll::regi2c_write_mask; #[cfg(any(esp32c2, esp32c3, esp32c6))] -use crate::efuse::Efuse; use crate::{ - peripheral::PeripheralRef, - peripherals::APB_SARADC, - system::{GenericPeripheralGuard, Peripheral}, + efuse::Efuse, + analog::adc::asynch::AdcFuture, + peripheral::PeripheralRef, peripherals::{APB_SARADC, Interrupt}, system::{GenericPeripheralGuard, Peripheral}, Async, Blocking, Cpu, + interrupt::{InterruptConfigurable, InterruptHandler}, }; +#[cfg(esp32c3)] +use Interrupt::APB_ADC as InterruptSource; +#[cfg(esp32c6)] +use Interrupt::APB_SARADC as InterruptSource; + mod calibration; // polyfill for c2 and c3 @@ -393,15 +399,17 @@ 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, { @@ -413,7 +421,8 @@ where ) -> Self { let guard = GenericPeripheralGuard::new(); - unsafe { &*APB_SARADC::PTR }.ctrl().modify(|_, w| unsafe { + let saradc = unsafe { &*APB_SARADC::PTR }; + saradc.ctrl().modify(|_, w| unsafe { w.start_force().set_bit(); w.start().set_bit(); w.sar_clk_gated().set_bit(); @@ -425,6 +434,19 @@ where attenuations: config.attenuations, active_channel: None, _guard: guard, + _phantom: PhantomData + } + } + + /// 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); + Adc { + _adc: self._adc, + attenuations: self.attenuations, + active_channel: self.active_channel, + _guard: self._guard, + _phantom: PhantomData } } @@ -503,6 +525,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 { @@ -592,3 +630,224 @@ 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(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. + /// + /// TODO: This method does not handle concurrent reads to multiple channels yet + pub async fn read_oneshot( + &mut self, + pin: &mut AdcPin, + ) -> u16 + where + ADCI: asynch::AsyncAccess, + PIN: AdcChannel, + CS: AdcCalScheme, + { + let channel = PIN::CHANNEL; + if self.attenuations[channel as usize].is_none() { + panic!("Channel {} is not configured reading!", channel); + } + + let adc_ready_future = AdcFuture::new(self); + + // 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 + 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))] +#[instability::unstable] +/// Async functionality +pub(crate) mod asynch { + use core::{ + marker::PhantomData, + sync::atomic::Ordering, + task::Poll + }; + use portable_atomic::AtomicBool; + use procmacros::handler; + use crate::{ + peripherals::APB_SARADC, + asynch::AtomicWaker, + analog::adc::Adc, + Async + }; + + static ADC1_DONE_WAKER: AtomicWaker = AtomicWaker::new(); + static ADC1_DONE_SIGNAL: AtomicBool = AtomicBool::new(false); + #[cfg(esp32c3)] + static ADC2_DONE_WAKER: AtomicWaker = AtomicWaker::new(); + #[cfg(esp32c3)] + static ADC2_DONE_SIGNAL: AtomicBool = AtomicBool::new(false); + + #[handler] + pub(crate) fn adc_interrupt_handler() { + let saradc = unsafe { &*APB_SARADC::PTR }; + let interrupt_status = saradc.int_st().read(); + + if interrupt_status.adc1_done().bit_is_set() { + ADC1_DONE_SIGNAL.store(true, Ordering::Relaxed); + saradc.int_clr().write(|w| { + w.adc1_done().clear_bit_by_one() + }); + ADC1_DONE_WAKER.wake(); + } + + #[cfg(esp32c3)] + if interrupt_status.adc2_done().bit_is_set() { + ADC2_DONE_SIGNAL.store(true, Ordering::Relaxed); + saradc.int_clr().write(|w| { + w.adc2_done().clear_bit_by_one() + }); + ADC2_DONE_WAKER.wake(); + } + } + + #[must_use = "futures do nothing unless you `.await` or poll them"] + pub(crate) struct AdcFuture { + _phantom: PhantomData, + } + + impl AdcFuture { + pub fn new(_instance: &Adc<'_, ADCI, Async>) -> Self { + ADCI::signal().store(false, Ordering::Relaxed); + ADCI::enable_interrupt(); + Self { _phantom: PhantomData } + } + } + + + #[doc(hidden)] + pub trait AsyncAccess { + /// Enable the ADC interrupt + fn enable_interrupt(); + + /// Disable the ADC interrupt + fn disable_interrupt(); + + /// Obtain the waker for the ADC interrupt + fn waker() -> &'static AtomicWaker; + + /// Obtain the signal for the ADC interrupt + fn signal() -> &'static AtomicBool; + + /// Check if the ADC data is ready + fn is_data_ready() -> bool; + } + + impl AsyncAccess for crate::peripherals::ADC1 { + fn enable_interrupt() { + let sar_adc = unsafe { &*APB_SARADC::PTR }; + sar_adc.int_ena().modify(|_, w| w.adc1_done().set_bit()); + } + + fn disable_interrupt() { + let sar_adc = unsafe { &*APB_SARADC::PTR }; + sar_adc.int_ena().modify(|_, w| w.adc1_done().clear_bit()); + } + + fn waker() -> &'static AtomicWaker { + &ADC1_DONE_WAKER + } + + fn signal() -> &'static AtomicBool { + &ADC1_DONE_SIGNAL + } + + fn is_data_ready() -> bool { + Self::signal().load(Ordering::Acquire) + } + } + + #[cfg(esp32c3)] + impl AsyncAccess for crate::peripherals::ADC2 { + fn enable_interrupt() { + let sar_adc = unsafe { &*APB_SARADC::PTR }; + sar_adc.int_ena().modify(|_, w| w.adc2_done().set_bit()); + } + + fn disable_interrupt() { + let sar_adc = unsafe { &*APB_SARADC::PTR }; + sar_adc.int_ena().modify(|_, w| w.adc2_done().clear_bit()); + } + + fn waker() -> &'static AtomicWaker { + &ADC2_DONE_WAKER + } + + fn signal() -> &'static AtomicBool { + &ADC2_DONE_SIGNAL + } + + fn is_data_ready() -> bool { + Self::signal().load(Ordering::Acquire) + } + } + + impl core::future::Future for AdcFuture { + type Output = (); + + fn poll(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> core::task::Poll { + ADCI::waker().register(cx.waker()); + + if ADCI::is_data_ready() { + Poll::Ready(()) + } else { + Poll::Pending + } + } + } + + impl Drop for AdcFuture { + fn drop(&mut self) { + ADCI::disable_interrupt(); + } + } + +} diff --git a/qa-test/src/bin/embassy_adc.rs b/qa-test/src/bin/embassy_adc.rs new file mode 100644 index 0000000000..6b98ac89af --- /dev/null +++ b/qa-test/src/bin/embassy_adc.rs @@ -0,0 +1,40 @@ +//! This shows how to asynchronously read ADC data + +//% CHIPS: esp32c6 esp32c3 + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{ + delay::Delay, + timer::timg::TimerGroup, +}; +use esp_println::println; +use esp_hal::analog::adc::{Adc, AdcConfig, Attenuation}; +use embassy_executor::Spawner; + +#[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_pin = peripherals.GPIO5; + let mut pin = adc1_config.enable_pin( + analog_pin, + Attenuation::_11dB, + ); + let mut adc1 = Adc::new(peripherals.ADC1, adc1_config).into_async(); + + let delay = Delay::new(); + + loop { + let adc_value: u16 = adc1.read_oneshot(&mut pin).await; + println!("ADC value: {}", adc_value); + delay.delay_millis(1000); + } +}