Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADC: Add async support for oneshot reads for esp32c3 and esp32c6 #2925

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 9 additions & 0 deletions esp-hal/MIGRATING-0.22.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 6 additions & 3 deletions esp-hal/src/analog/adc/esp32.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use core::marker::PhantomData;
use super::{AdcConfig, Attenuation};
use crate::{
peripheral::PeripheralRef,
Expand Down Expand Up @@ -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<Attenuation>; NUM_ATTENS],
active_channel: Option<u8>,
_phantom: PhantomData<Dm>,
}

impl<'d, ADCI> Adc<'d, ADCI>
impl<'d, ADCI> Adc<'d, ADCI, crate::Blocking>
where
ADCI: RegisterAccess,
{
Expand Down Expand Up @@ -280,6 +282,7 @@ where
_adc: adc_instance.into_ref(),
attenuations: config.attenuations,
active_channel: None,
_phantom: PhantomData,
}
}

Expand Down Expand Up @@ -329,7 +332,7 @@ where
}
}

impl<ADC1> Adc<'_, ADC1> {
impl<ADC1> Adc<'_, ADC1, crate::Blocking> {
/// Enable the Hall sensor
pub fn enable_hall_sensor() {
unsafe { &*RTC_IO::ptr() }
Expand Down
278 changes: 272 additions & 6 deletions esp-hal/src/analog/adc/riscv.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use core::marker::PhantomData;
#[cfg(not(esp32h2))]
pub use self::calibration::*;
use super::{AdcCalSource, AdcConfig, Attenuation};
Expand All @@ -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
Expand Down Expand Up @@ -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<Attenuation>; NUM_ATTENS],
active_channel: Option<u8>,
_guard: GenericPeripheralGuard<{ Peripheral::ApbSarAdc as u8 }>,
_phantom: PhantomData<Dm>
}

impl<'d, ADCI> Adc<'d, ADCI>
impl<'d, ADCI> Adc<'d, ADCI, Blocking>
where
ADCI: RegisterAccess + 'd,
{
Expand All @@ -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();
Expand All @@ -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
}
}

Expand Down Expand Up @@ -503,6 +532,22 @@ where
}
}

impl<ADCI> crate::private::Sealed for Adc<'_, ADCI, Blocking> {}

#[cfg(any(esp32c3, esp32c6))]
impl<ADCI> 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<u16> {
Expand Down Expand Up @@ -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<PIN, CS>(
&mut self,
pin: &mut super::AdcPin<PIN, ADCI, CS>,
) -> u16
where
ADCI: asynch::AsyncAccess,
PIN: super::AdcChannel,
CS: super::AdcCalScheme<ADCI>,
{
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<ADCI: AsyncAccess> {
_phantom: PhantomData<ADCI>,
}

impl<ADCI: AsyncAccess> AdcFuture<ADCI> {
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<ADCI: AsyncAccess> core::future::Future for AdcFuture<ADCI> {
type Output = ();

fn poll(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> core::task::Poll<Self::Output> {
ADCI::waker().register(cx.waker());

if ADCI::is_data_ready() {
Poll::Ready(())
} else {
Poll::Pending
}
}
}

impl<ADCI: AsyncAccess> Drop for AdcFuture<ADCI> {
fn drop(&mut self) {
ADCI::disable_interrupt();
}
}

}
Loading
Loading