Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adc oneshot driver #302

Merged
merged 10 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions examples/adc_oneshot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//! ADC oneshot example, reading a value form a pin and printing it on the terminal
//! requires ESP-IDF v5.0 or newer

use esp_idf_sys::{self as _}; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
liebman marked this conversation as resolved.
Show resolved Hide resolved

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 mut adc = AdcDriver::new(peripherals.adc1)?;

#[cfg(esp32)]
let mut 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(10));
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(100));
}
}
238 changes: 238 additions & 0 deletions src/adc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,244 @@ 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::marker::PhantomData;

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::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: ADCPin> {
pin: PeripheralRef<'d, T>,
calibration: Option<adc_cali_handle_t>,
}

impl<'d, T: ADCPin> AdcChannelDriver<'d, T> {
pub fn new(
adc: &AdcDriver<'d>,
pin: impl Peripheral<P = T> + 'd,
config: config::AdcChannelConfig,
) -> Result<AdcChannelDriver<'d, T>, EspError> {
liebman marked this conversation as resolved.
Show resolved Hide resolved
crate::into_ref!(pin);
assert_eq!(adc.adc, T::Adc::unit() as u8); // TODO: is there a better way?
liebman marked this conversation as resolved.
Show resolved Hide resolved

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(
liebman marked this conversation as resolved.
Show resolved Hide resolved
adc.handle,
pin.adc_channel(),
&chan_config
))?
};

let mut calibration = Self::get_curve_calibration_handle(
adc.adc,
pin.adc_channel(),
config.attenuation,
config.resolution.into(),
);
if calibration.is_none() {
calibration = Self::get_line_calibration_handle(
adc.adc,
config.attenuation,
config.resolution.into(),
);
}
Ok(Self { 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
}

fn pin(&mut self) -> &mut PeripheralRef<'d, T> {
&mut self.pin
}
}

pub struct AdcDriver<'d> {
handle: adc_oneshot_unit_handle_t,
adc: u8,
_ref: PhantomData<&'d ()>,
}

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

pub fn read<T>(&mut self, channel: &mut AdcChannelDriver<'_, T>) -> Result<u16, EspError>
liebman marked this conversation as resolved.
Show resolved Hide resolved
where
T: ADCPin,
{
let raw = self.read_raw(channel)?;
self.raw_to_cal(raw, channel)
}

#[inline(always)]
pub fn read_raw<T>(
&mut self,
channel: &mut AdcChannelDriver<'_, T>,
) -> Result<u16, EspError>
where
T: ADCPin,
{
let mut measurement = 0;
unsafe {
esp!(adc_oneshot_read(
self.handle,
channel.pin().adc_channel(),
&mut measurement
))
}?;
Ok(measurement as u16)
}

#[inline(always)]
pub fn raw_to_cal<T>(
&mut self,
raw: u16,
channel: &AdcChannelDriver<'_, T>,
) -> Result<u16, EspError>
where
T: ADCPin,
{
let value = if let Some(calibration) = &channel.calibration {
let mut mv = 0i32;
unsafe {
esp!(adc_cali_raw_to_voltage(*calibration, raw as i32, &mut mv))?;
};
mv as u16
} else {
raw
};
Ok(value)
}
}
}

liebman marked this conversation as resolved.
Show resolved Hide resolved
liebman marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp_idf_version_major = "4")))]
pub mod continuous {
use core::ffi::c_void;
Expand Down