Skip to content

Commit

Permalink
Adc oneshot driver (#302)
Browse files Browse the repository at this point in the history
* esp-idf v5 Adc oneshot driver

* cargo fmt

* added example & cargo fmt

* fix clippy issues

* more clippy's

* even more clippy's

* handle most review comments

* AdcChannelDriver: remove ADC annotation - use T::Adc
AdcChannelDriver: remove Sync implementation
examples: update adc_oneshot

* cargo fmt
remove AdcChannelDriver::channel

* implement e-hal 0.2 Adc OneShot trait
  • Loading branch information
liebman authored Sep 18, 2023
1 parent 34be575 commit 95dfc2f
Show file tree
Hide file tree
Showing 2 changed files with 364 additions and 0 deletions.
48 changes: 48 additions & 0 deletions examples/adc_oneshot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//! ADC oneshot example, reading a value form a pin and printing it on the terminal
//! requires ESP-IDF v5.0 or newer
use std::thread;
use std::time::Duration;

#[cfg(not(esp_idf_version_major = "4"))]
fn main() -> anyhow::Result<()> {
use esp_idf_hal::adc::attenuation::DB_11;
use esp_idf_hal::adc::oneshot::config::AdcChannelConfig;
use esp_idf_hal::adc::oneshot::*;
use esp_idf_hal::peripherals::Peripherals;

let peripherals = Peripherals::take().unwrap();

#[cfg(not(esp32))]
let adc = AdcDriver::new(peripherals.adc1)?;

#[cfg(esp32)]
let adc = AdcDriver::new(peripherals.adc2)?;

// configuring pin to analog read, you can regulate the adc input voltage range depending on your need
// for this example we use the attenuation of 11db which sets the input voltage range to around 0-3.6V
let config = AdcChannelConfig {
attenuation: DB_11,
calibration: true,
..Default::default()
};

#[cfg(not(esp32))]
let mut adc_pin = AdcChannelDriver::new(&adc, peripherals.pins.gpio2, config)?;

#[cfg(esp32)]
let mut adc_pin = AdcChannelDriver::new(&adc, peripherals.pins.gpio12, config)?;

loop {
// you can change the sleep duration depending on how often you want to sample
thread::sleep(Duration::from_millis(100));
println!("ADC value: {}", adc.read(&mut adc_pin).unwrap());
}
}
#[cfg(esp_idf_version_major = "4")]
fn main() -> anyhow::Result<()> {
println!("This example requires ESP-IDF v5.0 or newer");
loop {
thread::sleep(Duration::from_millis(1000));
}
}
316 changes: 316 additions & 0 deletions src/adc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,322 @@ impl_adc!(ADC1: adc_unit_t_ADC_UNIT_1);
#[cfg(not(any(esp32c2, esp32h2, esp32c5, esp32c6, esp32p4)))] // TODO: Check for esp32c5 and esp32p4
impl_adc!(ADC2: adc_unit_t_ADC_UNIT_2);

#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp_idf_version_major = "4")))]
pub mod oneshot {
use core::borrow::Borrow;

use esp_idf_sys::*;

use crate::gpio::ADCPin;
use crate::peripheral::Peripheral;
use crate::peripheral::PeripheralRef;

use super::attenuation::adc_atten_t;
use super::config::Resolution;
use super::to_nb_err;
use super::Adc;

pub mod config {
use super::adc_atten_t;
use super::Resolution;

#[derive(Debug, Copy, Clone, Default)]
pub struct AdcChannelConfig {
pub attenuation: adc_atten_t,
pub resolution: Resolution,
#[cfg(any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled))]
pub calibration: bool,
}
}

pub struct AdcChannelDriver<'d, T, M>
where
T: ADCPin,
M: Borrow<AdcDriver<'d, T::Adc>>,
{
adc: M,
_pin: PeripheralRef<'d, T>,
calibration: Option<adc_cali_handle_t>,
}

impl<'d, T, M> AdcChannelDriver<'d, T, M>
where
T: ADCPin,
M: Borrow<AdcDriver<'d, T::Adc>>,
{
pub fn new(
adc: M,
pin: impl Peripheral<P = T> + 'd,
config: config::AdcChannelConfig,
) -> Result<Self, EspError> {
crate::into_ref!(pin);

unsafe {
crate::gpio::rtc_reset_pin(pin.pin())?;
}

let chan_config = adc_oneshot_chan_cfg_t {
atten: config.attenuation,
bitwidth: config.resolution.into(),
};

unsafe {
esp!(adc_oneshot_config_channel(
adc.borrow().handle,
pin.adc_channel(),
&chan_config
))?
};

let mut calibration = Self::get_curve_calibration_handle(
T::Adc::unit() as u8,
pin.adc_channel(),
config.attenuation,
config.resolution.into(),
);
if calibration.is_none() {
calibration = Self::get_line_calibration_handle(
T::Adc::unit() as u8,
config.attenuation,
config.resolution.into(),
);
}
Ok(Self {
adc,
_pin: pin,
calibration,
})
}

#[allow(unused_variables)]
fn get_curve_calibration_handle(
unit_id: u8,
chan: adc_channel_t,
atten: adc_atten_t,
bitwidth: adc_bits_width_t,
) -> Option<adc_cali_handle_t> {
// it would be nice if esp-idf-sys could export some cfg values to replicate these two defines
// from esp-idf:
// ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
// ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
// then we wouuld not need the uglyness for the esp32c6
#[cfg(any(
esp32c3,
all(
esp32c6,
not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")),
not(esp_idf_version_full = "5.1.0")
),
esp32s3,
))]
{
let cal_config = adc_cali_curve_fitting_config_t {
unit_id: unit_id as u32,
chan,
atten,
bitwidth,
};
let mut cal_handle: adc_cali_handle_t = core::ptr::null_mut();
if let Err(_err) = unsafe {
esp!(esp_idf_sys::adc_cali_create_scheme_curve_fitting(
&cal_config,
&mut cal_handle
))
} {
// I'd log a warning but the log crate is not available here
None
} else {
Some(cal_handle)
}
}
#[cfg(not(any(
esp32c3,
all(
esp32c6,
not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")),
not(esp_idf_version_full = "5.1.0")
),
esp32s3,
)))]
None
}

#[allow(unused_variables)]
fn get_line_calibration_handle(
unit_id: u8,
atten: adc_atten_t,
bitwidth: adc_bits_width_t,
) -> Option<adc_cali_handle_t> {
#[cfg(any(esp32, esp32c2, esp32s2))]
{
// esp32 has an additional field that the exanple defalts
// to using fuse values for vref. Maybe we should expose
// this as a config option?
#[allow(clippy::needless_update)]
let cal_config = adc_cali_line_fitting_config_t {
unit_id: unit_id as u32,
atten,
bitwidth,
..Default::default()
};
let mut cal_handle: adc_cali_handle_t = core::ptr::null_mut();
if let Err(_err) = unsafe {
esp!(esp_idf_sys::adc_cali_create_scheme_line_fitting(
&cal_config,
&mut cal_handle
))
} {
// I'd log a warning but the log crate is not available here
None
} else {
Some(cal_handle)
}
}
#[cfg(not(any(esp32, esp32c2, esp32s2)))]
None
}

#[inline(always)]
pub fn read(&mut self) -> Result<u16, EspError> {
let raw = self.read_raw()?;
self.raw_to_cal(raw)
}

#[inline(always)]
pub fn read_raw(&mut self) -> Result<u16, EspError> {
let channel = T::CHANNEL;
self.adc.borrow().read_raw_internal(channel)
}

#[inline(always)]
pub fn raw_to_cal(&self, raw: u16) -> Result<u16, EspError> {
if let Some(calibration) = &self.calibration {
self.adc.borrow().raw_to_cal_internal(*calibration, raw)
} else {
Ok(raw)
}
}
}

impl<'d, T, M> embedded_hal_0_2::adc::Channel<T::Adc> for AdcChannelDriver<'d, T, M>
where
T: ADCPin,
M: Borrow<AdcDriver<'d, T::Adc>>,
{
type ID = adc_channel_t;

fn channel() -> Self::ID {
T::CHANNEL
}
}

unsafe impl<'d, T, M> Send for AdcChannelDriver<'d, T, M>
where
T: ADCPin,
M: Borrow<AdcDriver<'d, T::Adc>>,
{
}

pub struct AdcDriver<'d, ADC: Adc> {
handle: adc_oneshot_unit_handle_t,
_adc: PeripheralRef<'d, ADC>,
}

impl<'d, ADC: Adc> AdcDriver<'d, ADC> {
pub fn new(adc: impl Peripheral<P = ADC> + 'd) -> Result<Self, EspError> {
crate::into_ref!(adc);
let config = adc_oneshot_unit_init_cfg_t {
unit_id: ADC::unit(),
..Default::default()
};
let mut handle: adc_oneshot_unit_handle_t = core::ptr::null_mut();
unsafe { esp!(adc_oneshot_new_unit(&config, &mut handle))? };
Ok(Self { handle, _adc: adc })
}

#[inline(always)]
pub fn read<T, M>(&self, channel: &mut AdcChannelDriver<'d, T, M>) -> Result<u16, EspError>
where
T: ADCPin,
M: Borrow<AdcDriver<'d, T::Adc>>,
{
let raw = self.read_raw(channel)?;
self.raw_to_cal(channel, raw)
}

#[inline(always)]
pub fn read_raw<T, M>(
&self,
_channel: &mut AdcChannelDriver<'d, T, M>,
) -> Result<u16, EspError>
where
T: ADCPin,
M: Borrow<AdcDriver<'d, T::Adc>>,
{
self.read_raw_internal(T::CHANNEL)
}

#[inline(always)]
fn read_raw_internal(&self, channel: adc_channel_t) -> Result<u16, EspError> {
let mut measurement = 0;
unsafe { esp!(adc_oneshot_read(self.handle, channel, &mut measurement)) }?;
Ok(measurement as u16)
}

#[inline(always)]
pub fn raw_to_cal<T, M>(
&self,
channel: &AdcChannelDriver<'d, T, M>,
raw: u16,
) -> Result<u16, EspError>
where
T: ADCPin,
M: Borrow<AdcDriver<'d, T::Adc>>,
{
if let Some(calibration) = &channel.calibration {
self.raw_to_cal_internal(*calibration, raw)
} else {
Ok(raw)
}
}

#[inline(always)]
fn raw_to_cal_internal(
&self,
calibration: adc_cali_handle_t,
raw: u16,
) -> Result<u16, EspError> {
let mut mv = 0i32;
unsafe {
esp!(adc_cali_raw_to_voltage(calibration, raw as i32, &mut mv))?;
};
Ok(mv as u16)
}
}

impl<'d, ADC: Adc> Drop for AdcDriver<'d, ADC> {
fn drop(&mut self) {
unsafe { esp!(adc_oneshot_del_unit(self.handle)) }.unwrap();
}
}

impl<'d, T, M> embedded_hal_0_2::adc::OneShot<T::Adc, u16, AdcChannelDriver<'d, T, M>>
for AdcDriver<'d, T::Adc>
where
T: ADCPin,
M: Borrow<AdcDriver<'d, T::Adc>>,
{
type Error = EspError;

fn read(&mut self, pin: &mut AdcChannelDriver<'d, T, M>) -> nb::Result<u16, Self::Error> {
AdcDriver::read(self, pin).map_err(to_nb_err)
}
}

unsafe impl<'d, ADC: Adc> Send for AdcDriver<'d, ADC> {}
unsafe impl<'d, ADC: Adc> Sync for AdcDriver<'d, ADC> {}
}

#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp_idf_version_major = "4")))]
pub mod continuous {
use core::ffi::c_void;
Expand Down

0 comments on commit 95dfc2f

Please sign in to comment.