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

* ADC: Add async support for oneshot reads for esp32c3 and esp32c6

* ADC: change interrupt waking logic

- fix migrating document
- add ADC2 reading qa example and fix sensor reading

* ADC: run `cargo xtask fmt-packages`

* ADC: remove TODO comment
This commit is contained in:
Davo 2025-01-31 01:52:47 -06:00 committed by GitHub
parent 6aa17819a5
commit 9a28bdfdbd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 330 additions and 8 deletions

View File

@ -106,6 +106,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- ESP32-S2: Made Wi-Fi peripheral non virtual. (#2942)
- `UartRx::check_for_errors`, `Uart::check_for_rx_errors`, `{Uart, UartRx}::read_buffered_bytes` (#2935)
- Added `i2c` interrupt API (#2944)
- Async support for ADC oneshot reads for ESP32C3 and ESP32C6 (#2925)
### Changed

View File

@ -207,4 +207,14 @@ All async functions now include the `_async` postfix. Additionally the non-async
```diff
- let result = i2c.write_read(0x77, &[0xaa], &mut data).await;
+ let result = i2c.write_read_async(0x77, &[0xaa], &mut data).await;
## 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>
```

View File

@ -1,3 +1,5 @@
use core::marker::PhantomData;
use super::{AdcConfig, Attenuation};
use crate::{
peripheral::PeripheralRef,
@ -198,13 +200,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,
{
@ -280,6 +283,7 @@ where
_adc: adc_instance.into_ref(),
attenuations: config.attenuations,
active_channel: None,
_phantom: PhantomData,
}
}
@ -329,7 +333,7 @@ where
}
}
impl<ADC1> Adc<'_, ADC1> {
impl<ADC1> Adc<'_, ADC1, crate::Blocking> {
/// Enable the Hall sensor
pub fn enable_hall_sensor() {
RTC_IO::regs()

View File

@ -1,3 +1,10 @@
use core::marker::PhantomData;
#[cfg(esp32c3)]
use Interrupt::APB_ADC as InterruptSource;
#[cfg(esp32c6)]
use Interrupt::APB_SARADC as InterruptSource;
#[cfg(not(esp32h2))]
pub use self::calibration::*;
use super::{AdcCalSource, AdcConfig, Attenuation};
@ -5,10 +12,18 @@ use super::{AdcCalSource, AdcConfig, Attenuation};
use crate::clock::clocks_ll::regi2c_write_mask;
#[cfg(any(esp32c2, esp32c3, esp32c6))]
use crate::efuse::Efuse;
#[cfg(any(esp32c3, esp32c6))]
use crate::{
analog::adc::asynch::AdcFuture,
interrupt::{InterruptConfigurable, InterruptHandler},
peripherals::Interrupt,
Async,
};
use crate::{
peripheral::PeripheralRef,
peripherals::APB_SARADC,
system::{GenericPeripheralGuard, Peripheral},
Blocking,
};
mod calibration;
@ -384,14 +399,15 @@ 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,
{
@ -415,6 +431,26 @@ 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);
// Reset interrupt flags and disable oneshot reading to normalize state before
// entering async mode, otherwise there can be '0' readings, happening initially
// using ADC2
ADCI::reset();
Adc {
_adc: self._adc,
attenuations: self.attenuations,
active_channel: self.active_channel,
_guard: self._guard,
_phantom: PhantomData,
}
}
@ -493,6 +529,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> {
@ -582,3 +634,201 @@ 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.
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);
}
// 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
let adc_ready_future = AdcFuture::new(self);
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))]
/// Async functionality
pub(crate) mod asynch {
use core::{
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
};
use procmacros::handler;
use crate::{asynch::AtomicWaker, peripherals::APB_SARADC, Async};
#[handler]
pub(crate) fn adc_interrupt_handler() {
let saradc = APB_SARADC::regs();
let interrupt_status = saradc.int_st().read();
if interrupt_status.adc1_done().bit_is_set() {
handle_async(crate::peripherals::ADC1)
}
#[cfg(esp32c3)]
if interrupt_status.adc2_done().bit_is_set() {
handle_async(crate::peripherals::ADC2)
}
}
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;
}
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(esp32c3)]
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();
}
}
}

View File

@ -1,3 +1,5 @@
use core::marker::PhantomData;
#[cfg(esp32s3)]
pub use self::calibration::*;
use super::{AdcCalScheme, AdcCalSource, AdcChannel, AdcConfig, AdcPin, Attenuation};
@ -380,14 +382,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<u8>,
last_init_code: u16,
_guard: GenericPeripheralGuard<{ Peripheral::ApbSarAdc as u8 }>,
_phantom: PhantomData<Dm>,
}
impl<'d, ADCI> Adc<'d, ADCI>
impl<'d, ADCI> Adc<'d, ADCI, crate::Blocking>
where
ADCI: RegisterAccess,
{
@ -467,6 +470,7 @@ where
active_channel: None,
last_init_code: 0,
_guard: guard,
_phantom: PhantomData,
}
}

View File

@ -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::<ADC1>::new(peripherals.ADC1, adc1_config);
//! let mut adc1 = Adc::<ADC1, Blocking>::new(peripherals.ADC1, adc1_config);
//! let pin_value: u16 = nb::block!(adc1.read_oneshot(&mut adc1_pin))?;
//! rng.read(&mut buf);
//! true_rand = rng.random();

View File

@ -0,0 +1,52 @@
//! 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_pin1 = peripherals.GPIO4;
let mut pin1 = adc1_config.enable_pin(analog_pin1, Attenuation::_11dB);
let mut adc1 = Adc::new(peripherals.ADC1, adc1_config).into_async();
cfg_if::cfg_if! {
if #[cfg(feature = "esp32c3")] {
let mut adc2_config = AdcConfig::new();
let analog_pin2 = peripherals.GPIO5;
let mut pin2 = adc2_config.enable_pin(analog_pin2, Attenuation::_11dB);
let mut adc2 = Adc::new(peripherals.ADC2, adc2_config).into_async();
}
}
let delay = Delay::new();
loop {
let adc1_value: u16 = adc1.read_oneshot(&mut pin1).await;
println!("ADC1 value: {}", adc1_value);
cfg_if::cfg_if! {
if #[cfg(feature = "esp32c3")] {
let adc2_value: u16 = adc2.read_oneshot(&mut pin2).await;
println!("ADC2 value: {}", adc2_value);
}
}
delay.delay_millis(1000);
}
}