Expose ADC asynchronous functionalities where applicable (#3443)

From a user's perspective, logic based on asynchronous ADC
functionalities using esp-hal v0.23 is no longer possible with
v1.0.0-beta.0. This is due to the fact that asynchronous traits used to
implement the latter are gatekept behind a module, called `asynch`,
accessible only to the esp-hal crate itself. As a result, users cannot
write generic logic that requires the implementation of such traits as
they are currently private.

See https://github.com/esp-rs/esp-hal/discussions/3441 for a concrete
example.

Co-authored-by: Juraj Sadel <juraj.sadel@espressif.com>
This commit is contained in:
Renken 2025-05-27 11:19:14 +02:00 committed by GitHub
parent 692b7dddc2
commit 631b4aab41
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 250 additions and 156 deletions

View File

@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Implemented `embedded_io::ReadReady` for `Uart` and `UartRx` (#3423)
- Implemented `embedded_io::WriteReady` for `Uart` and `UartTx` (#3423)
- ESP32-H2: Support for ADC calibration (#3414)
- Expose ADC asynchrounous functionalities where applicable (#3443)
- Added `UartInterrupt::RxTimeout` support (#3493)
- UART: Added HW and SW flow control config option (#3435)

View File

@ -8,6 +8,16 @@ cfg_if::cfg_if! {
}
}
use core::{
pin::Pin,
task::{Context, Poll},
};
// We only have to count on devices that have multiple ADCs sharing the same interrupt
#[cfg(all(adc1, adc2))]
use portable_atomic::{AtomicU32, Ordering};
use procmacros::handler;
pub use self::calibration::*;
use super::{AdcCalSource, AdcConfig, Attenuation};
#[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2))]
@ -15,7 +25,7 @@ use crate::efuse::Efuse;
use crate::{
Async,
Blocking,
analog::adc::asynch::AdcFuture,
asynch::AtomicWaker,
interrupt::{InterruptConfigurable, InterruptHandler},
peripherals::{APB_SARADC, Interrupt},
soc::regi2c,
@ -302,8 +312,8 @@ where
/// Reconfigures the ADC driver to operate in asynchronous mode.
pub fn into_async(mut self) -> Adc<'d, ADCI, Async> {
asynch::acquire_async_adc();
self.set_interrupt_handler(asynch::adc_interrupt_handler);
acquire_async_adc();
self.set_interrupt_handler(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
@ -505,7 +515,7 @@ where
{
/// Create a new instance in [crate::Blocking] mode.
pub fn into_blocking(self) -> Adc<'d, ADCI, Blocking> {
if asynch::release_async_adc() {
if release_async_adc() {
// Disable ADC interrupt on all cores if the last async ADC instance is disabled
for cpu in crate::system::Cpu::all() {
crate::interrupt::disable(cpu, InterruptSource);
@ -527,7 +537,7 @@ where
/// underlies the pin.
pub async fn read_oneshot<PIN, CS>(&mut self, pin: &mut super::AdcPin<PIN, ADCI, CS>) -> u16
where
ADCI: asynch::AsyncAccess,
ADCI: Instance,
PIN: super::AdcChannel,
CS: super::AdcCalScheme<ADCI>,
{
@ -565,160 +575,144 @@ where
}
}
/// Async functionality
pub(crate) mod asynch {
use core::{
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
};
#[cfg(all(adc1, adc2))]
static ASYNC_ADC_COUNT: AtomicU32 = AtomicU32::new(0);
// We only have to count on devices that have multiple ADCs sharing the same interrupt
pub(super) fn acquire_async_adc() {
#[cfg(all(adc1, adc2))]
use portable_atomic::{AtomicU32, Ordering};
use procmacros::handler;
ASYNC_ADC_COUNT.fetch_add(1, Ordering::Relaxed);
}
use crate::{Async, asynch::AtomicWaker, peripherals::APB_SARADC};
#[cfg(all(adc1, adc2))]
static ASYNC_ADC_COUNT: AtomicU32 = AtomicU32::new(0);
pub(super) fn acquire_async_adc() {
#[cfg(all(adc1, adc2))]
ASYNC_ADC_COUNT.fetch_add(1, Ordering::Relaxed);
}
pub(super) fn release_async_adc() -> bool {
cfg_if::cfg_if! {
if #[cfg(all(adc1, adc2))] {
ASYNC_ADC_COUNT.fetch_sub(1, Ordering::Relaxed) == 1
} else {
true
}
}
}
#[handler]
pub(crate) fn adc_interrupt_handler() {
let saradc = APB_SARADC::regs();
let interrupt_status = saradc.int_st().read();
#[cfg(adc1)]
if interrupt_status.adc1_done().bit_is_set() {
unsafe { handle_async(crate::peripherals::ADC1::steal()) }
}
#[cfg(adc2)]
if interrupt_status.adc2_done().bit_is_set() {
unsafe { handle_async(crate::peripherals::ADC2::steal()) }
}
}
fn handle_async<ADCI: AsyncAccess>(_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;
}
#[cfg(adc1)]
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(adc2)]
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<ADCI: AsyncAccess> {
phantom: PhantomData<ADCI>,
}
impl<ADCI: AsyncAccess> AdcFuture<ADCI> {
pub fn new(_self: &super::Adc<'_, ADCI, Async>) -> Self {
Self {
phantom: PhantomData,
}
}
}
impl<ADCI: AsyncAccess + super::RegisterAccess> core::future::Future for AdcFuture<ADCI> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if ADCI::is_done() {
ADCI::clear_interrupt();
Poll::Ready(())
} else {
ADCI::waker().register(cx.waker());
ADCI::enable_interrupt();
Poll::Pending
}
}
}
impl<ADCI: AsyncAccess> Drop for AdcFuture<ADCI> {
fn drop(&mut self) {
ADCI::disable_interrupt();
pub(super) fn release_async_adc() -> bool {
cfg_if::cfg_if! {
if #[cfg(all(adc1, adc2))] {
ASYNC_ADC_COUNT.fetch_sub(1, Ordering::Relaxed) == 1
} else {
true
}
}
}
#[handler]
pub(crate) fn adc_interrupt_handler() {
let saradc = APB_SARADC::regs();
let interrupt_status = saradc.int_st().read();
#[cfg(adc1)]
if interrupt_status.adc1_done().bit_is_set() {
unsafe { handle_async(crate::peripherals::ADC1::steal()) }
}
#[cfg(adc2)]
if interrupt_status.adc2_done().bit_is_set() {
unsafe { handle_async(crate::peripherals::ADC2::steal()) }
}
}
fn handle_async<ADCI: Instance>(_instance: ADCI) {
ADCI::waker().wake();
ADCI::disable_interrupt();
}
/// Enable asynchronous access.
pub trait Instance: crate::private::Sealed {
/// 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;
}
#[cfg(adc1)]
impl Instance 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(adc2)]
impl Instance 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<ADCI: Instance> {
phantom: PhantomData<ADCI>,
}
impl<ADCI: Instance> AdcFuture<ADCI> {
pub fn new(_self: &super::Adc<'_, ADCI, Async>) -> Self {
Self {
phantom: PhantomData,
}
}
}
impl<ADCI: Instance + super::RegisterAccess> core::future::Future for AdcFuture<ADCI> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if ADCI::is_done() {
ADCI::clear_interrupt();
Poll::Ready(())
} else {
ADCI::waker().register(cx.waker());
ADCI::enable_interrupt();
Poll::Pending
}
}
}
impl<ADCI: Instance> Drop for AdcFuture<ADCI> {
fn drop(&mut self) {
ADCI::disable_interrupt();
}
}

View File

@ -0,0 +1,99 @@
//! This shows how to asynchronously read ADC data and offers an abstraction to
//! convert the raw value to a type-specific interpretation.
//!
//! PINS
//! GPIO4 for ADC1
//% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2
#![no_std]
#![no_main]
use core::marker::PhantomData;
use embassy_executor::Spawner;
use esp_backtrace as _;
use esp_hal::{
Async,
analog::adc::{
Adc,
AdcCalScheme,
AdcChannel,
AdcConfig,
AdcPin,
Attenuation,
Instance,
RegisterAccess,
},
delay::Delay,
timer::timg::TimerGroup,
};
use esp_println::println;
trait Sensor {
async fn measure(&mut self) -> u16;
}
trait Converter {
/// Converts the raw ADC value to a valid metering.
fn raw_to_metering(raw_value: u16) -> u16;
}
pub struct AdcSensor<'d, ADCI, PIN, CS, MC> {
adc: Adc<'d, ADCI, Async>,
pin: AdcPin<PIN, ADCI, CS>,
_phantom: PhantomData<MC>,
}
impl<'d, ADCI, PIN, CS, MC> AdcSensor<'d, ADCI, PIN, CS, MC> {
pub fn new(adc: Adc<'d, ADCI, Async>, pin: AdcPin<PIN, ADCI, CS>) -> Self {
let _phantom = PhantomData::<MC> {};
Self { adc, pin, _phantom }
}
}
impl<'d, ADCI, PIN, CS, MC> Sensor for AdcSensor<'d, ADCI, PIN, CS, MC>
where
ADCI: RegisterAccess + Instance + 'd,
PIN: AdcChannel,
CS: AdcCalScheme<ADCI>,
MC: Converter,
{
async fn measure(&mut self) -> u16 {
let raw_value = self.adc.read_oneshot(&mut self.pin).await;
MC::raw_to_metering(raw_value)
}
}
/// Returns the provided raw value as is.
pub struct Identity;
impl Converter for Identity {
fn raw_to_metering(raw_value: u16) -> u16 {
raw_value
}
}
#[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 pin1 = adc1_config.enable_pin(analog_pin1, Attenuation::_11dB);
let adc1 = Adc::new(peripherals.ADC1, adc1_config).into_async();
let mut id = AdcSensor::<_, _, _, Identity>::new(adc1, pin1);
let delay = Delay::new();
loop {
let id_value = id.measure().await;
println!("id value: {}", id_value);
delay.delay_millis(1000);
}
}