mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-28 04:40:52 +00:00
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:
parent
692b7dddc2
commit
631b4aab41
@ -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)
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
99
qa-test/src/bin/embassy_adc_async_abstraction.rs
Normal file
99
qa-test/src/bin/embassy_adc_async_abstraction.rs
Normal 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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user