Skip to content

Commit

Permalink
ADC: Add async support for oneshot reads for esp32c3 and esp32c6
Browse files Browse the repository at this point in the history
  • Loading branch information
davoclavo committed Jan 9, 2025
1 parent 040c0fd commit 638d9b6
Show file tree
Hide file tree
Showing 3 changed files with 309 additions and 9 deletions.
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
277 changes: 268 additions & 9 deletions esp-hal/src/analog/adc/riscv.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
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},
};
use crate::{peripheral::PeripheralRef, peripherals::{APB_SARADC, Interrupt}, system::{GenericPeripheralGuard, Peripheral}, Async, Blocking, Cpu};
use crate::analog::adc::asynch::AdcFuture;
use crate::interrupt::{InterruptConfigurable, InterruptHandler};

#[cfg(esp32c3)]
use Interrupt::APB_ADC as InterruptSource;
#[cfg(esp32c6)]
use Interrupt::APB_SARADC as InterruptSource;

mod calibration;

Expand Down Expand Up @@ -393,15 +397,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 +419,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 +432,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
}
}

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

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

#[cfg(any(esp32c3, esp32c6))]
impl<'d, ADCI> InterruptConfigurable for Adc<'d, 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 +628,226 @@ 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<PIN, CS>(
&mut self,
pin: &mut AdcPin<PIN, ADCI, CS>,
) -> u16
where
ADCI: asynch::AsyncAccess,
PIN: AdcChannel,
CS: 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();

ADCI::reset();

// Postprocess converted value according to calibration scheme used for pin
let converted_value = pin.cal_scheme.adc_val(converted_value);

// 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.

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<'d, ADCI: AsyncAccess> AdcFuture<ADCI> {
pub fn new(_instance: &Adc<'d, 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();
}
}

}
40 changes: 40 additions & 0 deletions qa-test/src/bin/embassy_adc.rs
Original file line number Diff line number Diff line change
@@ -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::Attenuation11dB,
);
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);
}
}

0 comments on commit 638d9b6

Please sign in to comment.