mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-26 20:00:32 +00:00
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:
parent
6aa17819a5
commit
9a28bdfdbd
@ -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
|
||||
|
||||
|
@ -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>
|
||||
```
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
52
qa-test/src/bin/embassy_adc.rs
Normal file
52
qa-test/src/bin/embassy_adc.rs
Normal 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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user