Skip to content

Commit 9a28bdf

Browse files
authored
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
1 parent 6aa1781 commit 9a28bdf

7 files changed

Lines changed: 330 additions & 8 deletions

File tree

esp-hal/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
106106
- ESP32-S2: Made Wi-Fi peripheral non virtual. (#2942)
107107
- `UartRx::check_for_errors`, `Uart::check_for_rx_errors`, `{Uart, UartRx}::read_buffered_bytes` (#2935)
108108
- Added `i2c` interrupt API (#2944)
109+
- Async support for ADC oneshot reads for ESP32C3 and ESP32C6 (#2925)
109110

110111
### Changed
111112

esp-hal/MIGRATING-0.23.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,4 +207,14 @@ All async functions now include the `_async` postfix. Additionally the non-async
207207
```diff
208208
- let result = i2c.write_read(0x77, &[0xaa], &mut data).await;
209209
+ let result = i2c.write_read_async(0x77, &[0xaa], &mut data).await;
210+
211+
## ADC Changes
212+
213+
The ADC driver has gained a new `Async`/`Blocking` mode parameter.
214+
NOTE: Async support is only supported in ESP32C3 and ESP32C6 for now
215+
216+
217+
```diff
218+
- Adc<'d, ADC>
219+
+ Adc<'d, ADC, Blocking>
210220
```

esp-hal/src/analog/adc/esp32.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use core::marker::PhantomData;
2+
13
use super::{AdcConfig, Attenuation};
24
use crate::{
35
peripheral::PeripheralRef,
@@ -198,13 +200,14 @@ impl RegisterAccess for ADC2 {
198200
}
199201

200202
/// Analog-to-Digital Converter peripheral driver.
201-
pub struct Adc<'d, ADC> {
203+
pub struct Adc<'d, ADC, Dm: crate::DriverMode> {
202204
_adc: PeripheralRef<'d, ADC>,
203205
attenuations: [Option<Attenuation>; NUM_ATTENS],
204206
active_channel: Option<u8>,
207+
_phantom: PhantomData<Dm>,
205208
}
206209

207-
impl<'d, ADCI> Adc<'d, ADCI>
210+
impl<'d, ADCI> Adc<'d, ADCI, crate::Blocking>
208211
where
209212
ADCI: RegisterAccess,
210213
{
@@ -280,6 +283,7 @@ where
280283
_adc: adc_instance.into_ref(),
281284
attenuations: config.attenuations,
282285
active_channel: None,
286+
_phantom: PhantomData,
283287
}
284288
}
285289

@@ -329,7 +333,7 @@ where
329333
}
330334
}
331335

332-
impl<ADC1> Adc<'_, ADC1> {
336+
impl<ADC1> Adc<'_, ADC1, crate::Blocking> {
333337
/// Enable the Hall sensor
334338
pub fn enable_hall_sensor() {
335339
RTC_IO::regs()

esp-hal/src/analog/adc/riscv.rs

Lines changed: 252 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,29 @@
1+
use core::marker::PhantomData;
2+
3+
#[cfg(esp32c3)]
4+
use Interrupt::APB_ADC as InterruptSource;
5+
#[cfg(esp32c6)]
6+
use Interrupt::APB_SARADC as InterruptSource;
7+
18
#[cfg(not(esp32h2))]
29
pub use self::calibration::*;
310
use super::{AdcCalSource, AdcConfig, Attenuation};
411
#[cfg(any(esp32c6, esp32h2))]
512
use crate::clock::clocks_ll::regi2c_write_mask;
613
#[cfg(any(esp32c2, esp32c3, esp32c6))]
714
use crate::efuse::Efuse;
15+
#[cfg(any(esp32c3, esp32c6))]
16+
use crate::{
17+
analog::adc::asynch::AdcFuture,
18+
interrupt::{InterruptConfigurable, InterruptHandler},
19+
peripherals::Interrupt,
20+
Async,
21+
};
822
use crate::{
923
peripheral::PeripheralRef,
1024
peripherals::APB_SARADC,
1125
system::{GenericPeripheralGuard, Peripheral},
26+
Blocking,
1227
};
1328

1429
mod calibration;
@@ -384,14 +399,15 @@ impl super::CalibrationAccess for crate::peripherals::ADC2 {
384399
}
385400

386401
/// Analog-to-Digital Converter peripheral driver.
387-
pub struct Adc<'d, ADCI> {
402+
pub struct Adc<'d, ADCI, Dm: crate::DriverMode> {
388403
_adc: PeripheralRef<'d, ADCI>,
389404
attenuations: [Option<Attenuation>; NUM_ATTENS],
390405
active_channel: Option<u8>,
391406
_guard: GenericPeripheralGuard<{ Peripheral::ApbSarAdc as u8 }>,
407+
_phantom: PhantomData<Dm>,
392408
}
393409

394-
impl<'d, ADCI> Adc<'d, ADCI>
410+
impl<'d, ADCI> Adc<'d, ADCI, Blocking>
395411
where
396412
ADCI: RegisterAccess + 'd,
397413
{
@@ -415,6 +431,26 @@ where
415431
attenuations: config.attenuations,
416432
active_channel: None,
417433
_guard: guard,
434+
_phantom: PhantomData,
435+
}
436+
}
437+
438+
#[cfg(any(esp32c3, esp32c6))]
439+
/// Reconfigures the ADC driver to operate in asynchronous mode.
440+
pub fn into_async(mut self) -> Adc<'d, ADCI, Async> {
441+
self.set_interrupt_handler(asynch::adc_interrupt_handler);
442+
443+
// Reset interrupt flags and disable oneshot reading to normalize state before
444+
// entering async mode, otherwise there can be '0' readings, happening initially
445+
// using ADC2
446+
ADCI::reset();
447+
448+
Adc {
449+
_adc: self._adc,
450+
attenuations: self.attenuations,
451+
active_channel: self.active_channel,
452+
_guard: self._guard,
453+
_phantom: PhantomData,
418454
}
419455
}
420456

@@ -493,6 +529,22 @@ where
493529
}
494530
}
495531

532+
impl<ADCI> crate::private::Sealed for Adc<'_, ADCI, Blocking> {}
533+
534+
#[cfg(any(esp32c3, esp32c6))]
535+
impl<ADCI> InterruptConfigurable for Adc<'_, ADCI, Blocking> {
536+
fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
537+
for core in crate::Cpu::other() {
538+
crate::interrupt::disable(core, InterruptSource);
539+
}
540+
unsafe { crate::interrupt::bind_interrupt(InterruptSource, handler.handler()) };
541+
unwrap!(crate::interrupt::enable(
542+
InterruptSource,
543+
handler.priority()
544+
));
545+
}
546+
}
547+
496548
#[cfg(any(esp32c2, esp32c3, esp32c6))]
497549
impl super::AdcCalEfuse for crate::peripherals::ADC1 {
498550
fn init_code(atten: Attenuation) -> Option<u16> {
@@ -582,3 +634,201 @@ mod adc_implementation {
582634
]
583635
}
584636
}
637+
638+
#[cfg(any(esp32c3, esp32c6))]
639+
impl<'d, ADCI> Adc<'d, ADCI, Async>
640+
where
641+
ADCI: RegisterAccess + 'd,
642+
{
643+
/// Create a new instance in [crate::Blocking] mode.
644+
pub fn into_blocking(self) -> Adc<'d, ADCI, Blocking> {
645+
crate::interrupt::disable(crate::Cpu::current(), InterruptSource);
646+
Adc {
647+
_adc: self._adc,
648+
attenuations: self.attenuations,
649+
active_channel: self.active_channel,
650+
_guard: self._guard,
651+
_phantom: PhantomData,
652+
}
653+
}
654+
655+
/// Request that the ADC begin a conversion on the specified pin
656+
///
657+
/// This method takes an [AdcPin](super::AdcPin) reference, as it is
658+
/// expected that the ADC will be able to sample whatever channel
659+
/// underlies the pin.
660+
pub async fn read_oneshot<PIN, CS>(&mut self, pin: &mut super::AdcPin<PIN, ADCI, CS>) -> u16
661+
where
662+
ADCI: asynch::AsyncAccess,
663+
PIN: super::AdcChannel,
664+
CS: super::AdcCalScheme<ADCI>,
665+
{
666+
let channel = PIN::CHANNEL;
667+
if self.attenuations[channel as usize].is_none() {
668+
panic!("Channel {} is not configured reading!", channel);
669+
}
670+
671+
// Set ADC unit calibration according used scheme for pin
672+
ADCI::set_init_code(pin.cal_scheme.adc_cal());
673+
674+
let attenuation = self.attenuations[channel as usize].unwrap() as u8;
675+
ADCI::config_onetime_sample(channel, attenuation);
676+
ADCI::start_onetime_sample();
677+
678+
// Wait for ADC to finish conversion and get value
679+
let adc_ready_future = AdcFuture::new(self);
680+
adc_ready_future.await;
681+
let converted_value = ADCI::read_data();
682+
683+
// There is a hardware limitation. If the APB clock frequency is high, the step
684+
// of this reg signal: ``onetime_start`` may not be captured by the
685+
// ADC digital controller (when its clock frequency is too slow). A rough
686+
// estimate for this step should be at least 3 ADC digital controller
687+
// clock cycle.
688+
//
689+
// This limitation will be removed in hardware future versions.
690+
// We reset ``onetime_start`` in `reset` and assume enough time has passed until
691+
// the next sample is requested.
692+
693+
ADCI::reset();
694+
695+
// Postprocess converted value according to calibration scheme used for pin
696+
pin.cal_scheme.adc_val(converted_value)
697+
}
698+
}
699+
700+
#[cfg(any(esp32c3, esp32c6))]
701+
/// Async functionality
702+
pub(crate) mod asynch {
703+
use core::{
704+
marker::PhantomData,
705+
pin::Pin,
706+
task::{Context, Poll},
707+
};
708+
709+
use procmacros::handler;
710+
711+
use crate::{asynch::AtomicWaker, peripherals::APB_SARADC, Async};
712+
713+
#[handler]
714+
pub(crate) fn adc_interrupt_handler() {
715+
let saradc = APB_SARADC::regs();
716+
let interrupt_status = saradc.int_st().read();
717+
718+
if interrupt_status.adc1_done().bit_is_set() {
719+
handle_async(crate::peripherals::ADC1)
720+
}
721+
722+
#[cfg(esp32c3)]
723+
if interrupt_status.adc2_done().bit_is_set() {
724+
handle_async(crate::peripherals::ADC2)
725+
}
726+
}
727+
728+
fn handle_async<ADCI: AsyncAccess>(_instance: ADCI) {
729+
ADCI::waker().wake();
730+
ADCI::disable_interrupt();
731+
}
732+
733+
#[doc(hidden)]
734+
pub trait AsyncAccess {
735+
/// Enable the ADC interrupt
736+
fn enable_interrupt();
737+
738+
/// Disable the ADC interrupt
739+
fn disable_interrupt();
740+
741+
/// Clear the ADC interrupt
742+
fn clear_interrupt();
743+
744+
/// Obtain the waker for the ADC interrupt
745+
fn waker() -> &'static AtomicWaker;
746+
}
747+
748+
impl AsyncAccess for crate::peripherals::ADC1 {
749+
fn enable_interrupt() {
750+
APB_SARADC::regs()
751+
.int_ena()
752+
.modify(|_, w| w.adc1_done().set_bit());
753+
}
754+
755+
fn disable_interrupt() {
756+
APB_SARADC::regs()
757+
.int_ena()
758+
.modify(|_, w| w.adc1_done().clear_bit());
759+
}
760+
761+
fn clear_interrupt() {
762+
APB_SARADC::regs()
763+
.int_clr()
764+
.write(|w| w.adc1_done().clear_bit_by_one());
765+
}
766+
767+
fn waker() -> &'static AtomicWaker {
768+
static WAKER: AtomicWaker = AtomicWaker::new();
769+
770+
&WAKER
771+
}
772+
}
773+
774+
#[cfg(esp32c3)]
775+
impl AsyncAccess for crate::peripherals::ADC2 {
776+
fn enable_interrupt() {
777+
APB_SARADC::regs()
778+
.int_ena()
779+
.modify(|_, w| w.adc2_done().set_bit());
780+
}
781+
782+
fn disable_interrupt() {
783+
APB_SARADC::regs()
784+
.int_ena()
785+
.modify(|_, w| w.adc2_done().clear_bit());
786+
}
787+
788+
fn clear_interrupt() {
789+
APB_SARADC::regs()
790+
.int_clr()
791+
.write(|w| w.adc2_done().clear_bit_by_one());
792+
}
793+
794+
fn waker() -> &'static AtomicWaker {
795+
static WAKER: AtomicWaker = AtomicWaker::new();
796+
797+
&WAKER
798+
}
799+
}
800+
801+
#[must_use = "futures do nothing unless you `.await` or poll them"]
802+
pub(crate) struct AdcFuture<ADCI: AsyncAccess> {
803+
phantom: PhantomData<ADCI>,
804+
}
805+
806+
impl<ADCI: AsyncAccess> AdcFuture<ADCI> {
807+
pub fn new(_self: &super::Adc<'_, ADCI, Async>) -> Self {
808+
Self {
809+
phantom: PhantomData,
810+
}
811+
}
812+
}
813+
814+
impl<ADCI: AsyncAccess + super::RegisterAccess> core::future::Future for AdcFuture<ADCI> {
815+
type Output = ();
816+
817+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
818+
if ADCI::is_done() {
819+
ADCI::clear_interrupt();
820+
Poll::Ready(())
821+
} else {
822+
ADCI::waker().register(cx.waker());
823+
ADCI::enable_interrupt();
824+
Poll::Pending
825+
}
826+
}
827+
}
828+
829+
impl<ADCI: AsyncAccess> Drop for AdcFuture<ADCI> {
830+
fn drop(&mut self) {
831+
ADCI::disable_interrupt();
832+
}
833+
}
834+
}

esp-hal/src/analog/adc/xtensa.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use core::marker::PhantomData;
2+
13
#[cfg(esp32s3)]
24
pub use self::calibration::*;
35
use super::{AdcCalScheme, AdcCalSource, AdcChannel, AdcConfig, AdcPin, Attenuation};
@@ -380,14 +382,15 @@ impl super::CalibrationAccess for crate::peripherals::ADC2 {
380382
}
381383

382384
/// Analog-to-Digital Converter peripheral driver.
383-
pub struct Adc<'d, ADC> {
385+
pub struct Adc<'d, ADC, Dm: crate::DriverMode> {
384386
_adc: PeripheralRef<'d, ADC>,
385387
active_channel: Option<u8>,
386388
last_init_code: u16,
387389
_guard: GenericPeripheralGuard<{ Peripheral::ApbSarAdc as u8 }>,
390+
_phantom: PhantomData<Dm>,
388391
}
389392

390-
impl<'d, ADCI> Adc<'d, ADCI>
393+
impl<'d, ADCI> Adc<'d, ADCI, crate::Blocking>
391394
where
392395
ADCI: RegisterAccess,
393396
{
@@ -467,6 +470,7 @@ where
467470
active_channel: None,
468471
last_init_code: 0,
469472
_guard: guard,
473+
_phantom: PhantomData,
470474
}
471475
}
472476

0 commit comments

Comments
 (0)