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/MIGRATING-0.22.md b/esp-hal/MIGRATING-0.22.md index 42b6fc4c46..357f0d07d1 100644 --- a/esp-hal/MIGRATING-0.22.md +++ b/esp-hal/MIGRATING-0.22.md @@ -498,6 +498,15 @@ The GPIO drive strength variants are renamed from e.g. `I5mA` to `_5mA`. ## 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; +``` + The ADC attenuation variants are renamed from e.g. `Attenuation0dB` to `_0dB`. ```diff diff --git a/esp-hal/src/analog/adc/esp32.rs b/esp-hal/src/analog/adc/esp32.rs index 93e003a0c7..2d5dd7c569 100644 --- a/esp-hal/src/analog/adc/esp32.rs +++ b/esp-hal/src/analog/adc/esp32.rs @@ -1,3 +1,4 @@ +use core::marker::PhantomData; use super::{AdcConfig, Attenuation}; use crate::{ peripheral::PeripheralRef, @@ -198,13 +199,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 +282,7 @@ where _adc: adc_instance.into_ref(), attenuations: config.attenuations, active_channel: None, + _phantom: PhantomData, } } @@ -329,7 +332,7 @@ where } } -impl Adc<'_, ADC1> { +impl Adc<'_, ADC1, crate::Blocking> { /// Enable the Hall sensor pub fn enable_hall_sensor() { unsafe { &*RTC_IO::ptr() } diff --git a/esp-hal/src/analog/adc/riscv.rs b/esp-hal/src/analog/adc/riscv.rs index f4968cc5bb..b8b3c8d193 100644 --- a/esp-hal/src/analog/adc/riscv.rs +++ b/esp-hal/src/analog/adc/riscv.rs @@ -1,3 +1,4 @@ +use core::marker::PhantomData; #[cfg(not(esp32h2))] pub use self::calibration::*; use super::{AdcCalSource, AdcConfig, Attenuation}; @@ -6,11 +7,22 @@ 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}, + peripheral::PeripheralRef, peripherals::APB_SARADC, system::{GenericPeripheralGuard, Peripheral}, Blocking }; +#[cfg(any(esp32c3, esp32c6))] +use crate::{ + analog::adc::asynch::AdcFuture, + peripherals::Interrupt, + interrupt::{InterruptConfigurable, InterruptHandler}, + Async, +}; + +#[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 +405,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 +427,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 +440,20 @@ 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); + Adc { + _adc: self._adc, + attenuations: self.attenuations, + active_channel: self.active_channel, + _guard: self._guard, + _phantom: PhantomData } } @@ -503,6 +532,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 +637,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(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. + /// + /// TODO: This method does not handle concurrent reads to multiple channels yet + 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); + } + + 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/esp-hal/src/analog/adc/xtensa.rs b/esp-hal/src/analog/adc/xtensa.rs index 069bc502d1..8f509c3f23 100644 --- a/esp-hal/src/analog/adc/xtensa.rs +++ b/esp-hal/src/analog/adc/xtensa.rs @@ -1,3 +1,4 @@ +use core::marker::PhantomData; #[cfg(esp32s3)] pub use self::calibration::*; use super::{AdcCalScheme, AdcCalSource, AdcChannel, AdcConfig, AdcPin, Attenuation}; @@ -387,14 +388,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, { @@ -474,6 +476,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 ba918497b6..bde192219e 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)).unwrap(); /// 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 0000000000..756bd6f9f1 --- /dev/null +++ b/qa-test/src/bin/embassy_adc.rs @@ -0,0 +1,37 @@ +//! 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_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); + } +}