diff --git a/CHANGELOG.md b/CHANGELOG.md index f6e78481f5a..5136628ff9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add MD5 functions from ESP ROM (#618) - Add embassy async `read` support for `uart` (#620) - Add bare-bones support to run code on ULP-RISCV / LP core (#631) +- Add ADC calibration implementation for a riscv chips (#555) ### Changed diff --git a/esp-hal-common/src/analog/adc/cal_basic.rs b/esp-hal-common/src/analog/adc/cal_basic.rs new file mode 100644 index 00000000000..3dc279fe050 --- /dev/null +++ b/esp-hal-common/src/analog/adc/cal_basic.rs @@ -0,0 +1,40 @@ +use core::marker::PhantomData; + +use crate::adc::{AdcCalEfuse, AdcCalScheme, AdcCalSource, AdcConfig, Attenuation, RegisterAccess}; + +/// Basic ADC calibration scheme +/// +/// Basic calibration is related to setting some initial bias value in ADC. +/// Such values usually is stored in efuse bit fields but also can be measured +/// in runtime by connecting ADC input to ground internally a fallback when +/// it is not available. +#[derive(Clone, Copy)] +pub struct AdcCalBasic { + /// Calibration value to set to ADC unit + cal_val: u16, + + _phantom: PhantomData, +} + +impl AdcCalScheme for AdcCalBasic +where + ADCI: AdcCalEfuse + RegisterAccess, +{ + fn new_cal(atten: Attenuation) -> Self { + // Try to get init code (Dout0) from efuse + // Dout0 means mean raw ADC value when zero voltage applied to input. + let cal_val = ADCI::get_init_code(atten).unwrap_or_else(|| { + // As a fallback try to calibrate via connecting input to ground internally. + AdcConfig::::adc_calibrate(atten, AdcCalSource::Gnd) + }); + + Self { + cal_val, + _phantom: PhantomData, + } + } + + fn adc_cal(&self) -> u16 { + self.cal_val + } +} diff --git a/esp-hal-common/src/analog/adc/cal_curve.rs b/esp-hal-common/src/analog/adc/cal_curve.rs new file mode 100644 index 00000000000..2e923a5e597 --- /dev/null +++ b/esp-hal-common/src/analog/adc/cal_curve.rs @@ -0,0 +1,240 @@ +use core::marker::PhantomData; + +use crate::adc::{ + AdcCalEfuse, + AdcCalLine, + AdcCalScheme, + AdcHasLineCal, + Attenuation, + RegisterAccess, +}; + +const COEFF_MUL: i64 = 1 << 52; + +type CurveCoeff = i64; + +/// Polynomial coefficients for specified attenuation. +pub struct CurveCoeffs { + /// Attenuation + atten: Attenuation, + /// Polynomial coefficients + coeff: &'static [CurveCoeff], +} + +type CurvesCoeffs = &'static [CurveCoeffs]; + +/// Marker trait for ADC which support curve futting +/// +/// See also [`AdcCalCurve`]. +pub trait AdcHasCurveCal { + /// Coefficients for calculating the reading voltage error. + /// + /// A sets of coefficients for each attenuation. + const CURVES_COEFFS: CurvesCoeffs; +} + +/// Curve fitting ADC calibration scheme +/// +/// This scheme implements final polynomial error correction using predefined +/// coefficient sets for each attenuation. +/// +/// This scheme also includes basic calibration ([`AdcCalBasic`]) and line +/// fitting ([`AdcCalLine`]). +#[derive(Clone, Copy)] +pub struct AdcCalCurve { + line: AdcCalLine, + + /// Coefficients for each term (3..=5) + coeff: &'static [CurveCoeff], + + _phantom: PhantomData, +} + +impl AdcCalScheme for AdcCalCurve +where + ADCI: AdcCalEfuse + AdcHasLineCal + AdcHasCurveCal + RegisterAccess, +{ + fn new_cal(atten: Attenuation) -> Self { + let line = AdcCalLine::::new_cal(atten); + + let coeff = ADCI::CURVES_COEFFS + .iter() + .find(|item| item.atten == atten) + .expect("No curve coefficients for given attenuation") + .coeff; + + Self { + line, + coeff, + _phantom: PhantomData, + } + } + + fn adc_cal(&self) -> u16 { + self.line.adc_cal() + } + + fn adc_val(&self, val: u16) -> u16 { + let val = self.line.adc_val(val); + + let err = if val == 0 { + 0 + } else { + // err = coeff[0] + coeff[1] * val + coeff[2] * val^2 + ... + coeff[n] * val^n + let mut var = 1i64; + let mut err = (var * self.coeff[0] as i64 / COEFF_MUL) as i32; + + for coeff in &self.coeff[1..] { + var = var * val as i64; + err += (var * *coeff as i64 / COEFF_MUL) as i32; + } + + err + }; + + (val as i32 - err) as u16 + } +} + +macro_rules! coeff_tables { + ($($(#[$($meta:meta)*])* $name:ident [ $($att:ident => [ $($val:literal,)* ],)* ];)*) => { + $( + $(#[$($meta)*])* + const $name: CurvesCoeffs = &[ + $(CurveCoeffs { + atten: Attenuation::$att, + coeff: &[ + $(($val as f64 * COEFF_MUL as f64 * 4096f64 / Attenuation::$att.ref_mv() as f64) as CurveCoeff,)* + ], + },)* + ]; + )* + }; +} + +#[cfg(any(esp32c3, esp32c6, esp32s3))] +mod impls { + use super::*; + + impl AdcHasCurveCal for crate::adc::ADC1 { + const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS1; + } + + #[cfg(esp32c3)] + impl AdcHasCurveCal for crate::adc::ADC2 { + const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS1; + } + + #[cfg(esp32s3)] + impl AdcHasCurveCal for crate::adc::ADC2 { + const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS2; + } + + coeff_tables! { + /// Error curve coefficients derived from https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32c3/curve_fitting_coefficients.c + #[cfg(esp32c3)] + CURVES_COEFFS1 [ + Attenuation0dB => [ + -0.2259664705000430, + -0.0007265418501948, + 0.0000109410402681, + ], + Attenuation2p5dB => [ + 0.4229623392600516, + -0.0000731527490903, + 0.0000088166562521, + ], + Attenuation6dB => [ + -1.0178592392364350, + -0.0097159265299153, + 0.0000149794028038, + ], + Attenuation11dB => [ + -1.4912262772850453, + -0.0228549975564099, + 0.0000356391935717, + -0.0000000179964582, + 0.0000000000042046, + ], + ]; + + /// Error curve coefficients derived from https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32c6/curve_fitting_coefficients.c + #[cfg(esp32c6)] + CURVES_COEFFS1 [ + Attenuation0dB => [ + -0.0487166399931449, + 0.0006436483033201, + 0.0000030410131806, + ], + Attenuation2p5dB => [ + -0.8665498165817785, + 0.0015239070452946, + 0.0000013818878844, + ], + Attenuation6dB => [ + -1.2277821756674387, + 0.0022275554717885, + 0.0000005924302667, + ], + Attenuation11dB => [ + -0.3801417550380255, + -0.0006020352420772, + 0.0000012442478488, + ], + ]; + + /// Error curve coefficients derived from https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32s3/curve_fitting_coefficients.c + #[cfg(esp32s3)] + CURVES_COEFFS1 [ + Attenuation0dB => [ + -2.7856531419538344, + -0.0050871540569528, + 0.0000097982495890, + ], + Attenuation2p5dB => [ + -2.9831022915028695, + -0.0049393185868806, + 0.0000101379430548, + ], + Attenuation6dB => [ + -2.3285545746296417, + -0.0147640181047414, + 0.0000208385525314, + ], + Attenuation11dB => [ + -0.6444034182694780, + -0.0644334888647536, + 0.0001297891447611, + -0.0000000707697180, + 0.0000000000135150, + ], + ]; + + /// Error curve coefficients derived from https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32s3/curve_fitting_coefficients.c + #[cfg(esp32s3)] + CURVES_COEFFS2 [ + Attenuation0dB => [ + -2.5668651654328927, + 0.0001353548869615, + 0.0000036615265189, + ], + Attenuation2p5dB => [ + -2.3690184690298404, + -0.0066319894226185, + 0.0000118964995959, + ], + Attenuation6dB => [ + -0.9452499397020617, + -0.0200996773954387, + 0.00000259011467956, + ], + Attenuation11dB => [ + 1.2247719764336924, + -0.0755717904943462, + 0.0001478791187119, + -0.0000000796725280, + 0.0000000000150380, + ], + ]; + } +} diff --git a/esp-hal-common/src/analog/adc/cal_line.rs b/esp-hal-common/src/analog/adc/cal_line.rs new file mode 100644 index 00000000000..73d72455895 --- /dev/null +++ b/esp-hal-common/src/analog/adc/cal_line.rs @@ -0,0 +1,100 @@ +use core::marker::PhantomData; + +use crate::adc::{ + AdcCalBasic, + AdcCalEfuse, + AdcCalScheme, + AdcCalSource, + AdcConfig, + Attenuation, + RegisterAccess, +}; + +/// Marker trait for ADC units which support line fitting +/// +/// Usually it means that reference points are stored in efuse. +/// See also [`AdcCalLine`]. +pub trait AdcHasLineCal {} + +/// Coefficients is actually a fixed-point numbers. +/// It is scaled to put them into integer. +const GAIN_SCALE: u32 = 1 << 16; + +/// Line fitting ADC calibration scheme +/// +/// This scheme implements gain correction based on reference points. +/// +/// A reference point is a pair of a reference voltage and the corresponding +/// mean raw digital ADC value. Such values are usually stored in efuse bit +/// fields for each supported attenuation. +/// +/// Also it can be measured in runtime by connecting ADC to reference voltage +/// internally but this method is not so good because actual reference voltage +/// may varies in range 1.0..=1.2 V. Currently this method is used as a fallback +/// (with 1.1 V by default) when calibration data is missing. +/// +/// This scheme also includes basic calibration ([`AdcCalBasic`]). +#[derive(Clone, Copy)] +pub struct AdcCalLine { + basic: AdcCalBasic, + + /// Gain of ADC-value + gain: u32, + + _phantom: PhantomData, +} + +impl AdcCalScheme for AdcCalLine +where + ADCI: AdcCalEfuse + AdcHasLineCal + RegisterAccess, +{ + fn new_cal(atten: Attenuation) -> Self { + let basic = AdcCalBasic::::new_cal(atten); + + // Try get the reference point (Dout, Vin) from efuse + // Dout means mean raw ADC value when specified Vin applied to input. + let (code, mv) = ADCI::get_cal_code(atten) + .map(|code| (code, ADCI::get_cal_mv(atten))) + .unwrap_or_else(|| { + // As a fallback try to calibrate using reference voltage source. + // This methos is no to good because actual reference voltage may varies + // in range 1000..=1200 mV and this value currently cannot be given from efuse. + ( + AdcConfig::::adc_calibrate(atten, AdcCalSource::Ref), + 1100, // use 1100 mV as a middle of typical reference voltage range + ) + }); + + // Estimate the (assumed) linear relationship between the measured raw value and + // the voltage with the previously done measurement when the chip was + // manufactured. + // + // Rounding formula: R = (OP(A * 2) + 1) / 2 + // where R - result, A - argument, O - operation + let gain = + ((mv as u32 * GAIN_SCALE * 2 / code as u32 + 1) * 4096 / atten.ref_mv() as u32 + 1) / 2; + + Self { + basic, + gain, + _phantom: PhantomData, + } + } + + fn adc_cal(&self) -> u16 { + self.basic.adc_cal() + } + + fn adc_val(&self, val: u16) -> u16 { + let val = self.basic.adc_val(val); + + // pointers are checked in the upper layer + (val as u32 * self.gain / GAIN_SCALE) as u16 + } +} + +#[cfg(any(esp32c2, esp32c3, esp32c6))] +impl AdcHasLineCal for crate::adc::ADC1 {} + +#[cfg(esp32c3)] +impl AdcHasLineCal for crate::adc::ADC2 {} diff --git a/esp-hal-common/src/analog/adc/riscv.rs b/esp-hal-common/src/analog/adc/riscv.rs index 23b13f1be87..e6b702ed36f 100644 --- a/esp-hal-common/src/analog/adc/riscv.rs +++ b/esp-hal-common/src/analog/adc/riscv.rs @@ -4,6 +4,10 @@ use embedded_hal::adc::{Channel, OneShot}; #[cfg(esp32c3)] use crate::analog::ADC2; +#[cfg(any(esp32c6, esp32h2))] +use crate::clock::clocks_ll::regi2c_write_mask; +#[cfg(any(esp32c2, esp32c3, esp32c6))] +use crate::efuse::Efuse; use crate::{ analog::ADC1, peripheral::PeripheralRef, @@ -11,6 +15,99 @@ use crate::{ system::{Peripheral, PeripheralClockControl}, }; +#[cfg(any(esp32c2, esp32c3, esp32c6))] +mod cal_basic; +#[cfg(any(esp32c3, esp32c6))] +mod cal_curve; +#[cfg(any(esp32c2, esp32c3, esp32c6))] +mod cal_line; + +#[cfg(any(esp32c2, esp32c3, esp32c6))] +pub use cal_basic::AdcCalBasic; +#[cfg(any(esp32c3, esp32c6))] +pub use cal_curve::{AdcCalCurve, AdcHasCurveCal}; +#[cfg(any(esp32c2, esp32c3, esp32c6))] +pub use cal_line::{AdcCalLine, AdcHasLineCal}; + +pub use crate::analog::{AdcCalEfuse, AdcCalScheme}; + +// polyfill for c2 and c3 +#[cfg(any(esp32c2, esp32c3))] +#[inline(always)] +fn regi2c_write_mask(block: u8, host_id: u8, reg_add: u8, msb: u8, lsb: u8, data: u8) { + unsafe { + crate::rom::rom_i2c_writeReg_Mask( + block as _, + host_id as _, + reg_add as _, + msb as _, + lsb as _, + data as _, + ); + } +} + +// Constants taken from: +// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32c2/include/soc/regi2c_saradc.h +// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32c3/include/soc/regi2c_saradc.h +// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32c6/include/soc/regi2c_saradc.h +// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32h2/include/soc/regi2c_saradc.h +// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32h4/include/soc/regi2c_saradc.h +cfg_if::cfg_if! { + if #[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2))] { + const I2C_SAR_ADC: u8 = 0x69; + const I2C_SAR_ADC_HOSTID: u8 = 0; + + const ADC_VAL_MASK: u16 = 0xfff; + const ADC_CAL_CNT_MAX: u16 = 32; + const ADC_CAL_CHANNEL: u32 = 0xf; + + const ADC_SAR1_ENCAL_GND_ADDR: u8 = 0x7; + const ADC_SAR1_ENCAL_GND_ADDR_MSB: u8 = 5; + const ADC_SAR1_ENCAL_GND_ADDR_LSB: u8 = 5; + + const ADC_SAR1_INITIAL_CODE_HIGH_ADDR: u8 = 0x1; + const ADC_SAR1_INITIAL_CODE_HIGH_ADDR_MSB: u8 = 0x3; + const ADC_SAR1_INITIAL_CODE_HIGH_ADDR_LSB: u8 = 0x0; + + const ADC_SAR1_INITIAL_CODE_LOW_ADDR: u8 = 0x0; + const ADC_SAR1_INITIAL_CODE_LOW_ADDR_MSB: u8 = 0x7; + const ADC_SAR1_INITIAL_CODE_LOW_ADDR_LSB: u8 = 0x0; + + const ADC_SAR1_DREF_ADDR: u8 = 0x2; + const ADC_SAR1_DREF_ADDR_MSB: u8 = 0x6; + const ADC_SAR1_DREF_ADDR_LSB: u8 = 0x4; + + const ADC_SARADC1_ENCAL_REF_ADDR: u8 = 0x7; + const ADC_SARADC1_ENCAL_REF_ADDR_MSB: u8 = 4; + const ADC_SARADC1_ENCAL_REF_ADDR_LSB: u8 = 4; + } +} + +cfg_if::cfg_if! { + if #[cfg(esp32c3)] { + const ADC_SAR2_ENCAL_GND_ADDR: u8 = 0x7; + const ADC_SAR2_ENCAL_GND_ADDR_MSB: u8 = 7; + const ADC_SAR2_ENCAL_GND_ADDR_LSB: u8 = 7; + + const ADC_SAR2_INITIAL_CODE_HIGH_ADDR: u8 = 0x4; + const ADC_SAR2_INITIAL_CODE_HIGH_ADDR_MSB: u8 = 0x3; + const ADC_SAR2_INITIAL_CODE_HIGH_ADDR_LSB: u8 = 0x0; + + const ADC_SAR2_INITIAL_CODE_LOW_ADDR: u8 = 0x3; + const ADC_SAR2_INITIAL_CODE_LOW_ADDR_MSB: u8 = 0x7; + const ADC_SAR2_INITIAL_CODE_LOW_ADDR_LSB: u8 = 0x0; + + const ADC_SAR2_DREF_ADDR: u8 = 0x5; + const ADC_SAR2_DREF_ADDR_MSB: u8 = 0x6; + const ADC_SAR2_DREF_ADDR_LSB: u8 = 0x4; + + const ADC_SARADC2_ENCAL_REF_ADDR: u8 = 0x7; + const ADC_SARADC2_ENCAL_REF_ADDR_MSB: u8 = 6; + const ADC_SARADC2_ENCAL_REF_ADDR_LSB: u8 = 6; + } +} + /// The sampling/readout resolution of the ADC #[derive(PartialEq, Eq, Clone, Copy)] pub enum Resolution { @@ -20,18 +117,56 @@ pub enum Resolution { /// The attenuation of the ADC pin #[derive(PartialEq, Eq, Clone, Copy)] pub enum Attenuation { + /// 0 dB attenuation, measurement range: 0 - 800 mV Attenuation0dB = 0b00, + /// 2.5 dB attenuation, measurement range: 0 - 1100 mV + #[cfg(not(esp32c2))] Attenuation2p5dB = 0b01, + /// 6 dB attenuation, measurement range: 0 - 1350 mV + #[cfg(not(esp32c2))] Attenuation6dB = 0b10, + /// 11 dB attenuation, measurement range: 0 - 2600 mV Attenuation11dB = 0b11, } -pub struct AdcPin { +impl Attenuation { + /// List of all supported attenuations + pub const ALL: &'static [Attenuation] = &[ + Attenuation::Attenuation0dB, + #[cfg(not(esp32c2))] + Attenuation::Attenuation2p5dB, + #[cfg(not(esp32c2))] + Attenuation::Attenuation6dB, + Attenuation::Attenuation11dB, + ]; + + /// Reference voltage in millivolts + /// + /// Vref = 10 ^ (Att / 20) * Vref0 + /// where Vref0 = 1.1 V, Att - attenuation in dB + /// + /// To colvert raw value to millivolts use folmula: + /// V = D * Vref / 2 ^ R + /// where D - raw ADC value, R - resolution in bits + pub const fn ref_mv(&self) -> u16 { + match self { + Attenuation::Attenuation0dB => 1100, + #[cfg(not(esp32c2))] + Attenuation::Attenuation2p5dB => 1467, + #[cfg(not(esp32c2))] + Attenuation::Attenuation6dB => 2195, + Attenuation::Attenuation11dB => 3903, + } + } +} + +pub struct AdcPin { pub pin: PIN, + pub cal_scheme: CS, _phantom: PhantomData, } -impl, ADCI> Channel for AdcPin { +impl, ADCI, CS> Channel for AdcPin { type ID = u8; fn channel() -> Self::ID { @@ -57,14 +192,69 @@ where &mut self, pin: PIN, attenuation: Attenuation, - ) -> AdcPin { + ) -> AdcPin { + self.attenuations[PIN::channel() as usize] = Some(attenuation); + + AdcPin { + pin, + cal_scheme: AdcCalScheme::<()>::new_cal(attenuation), + _phantom: PhantomData::default(), + } + } + + pub fn enable_pin_with_cal, CS: AdcCalScheme>( + &mut self, + pin: PIN, + attenuation: Attenuation, + ) -> AdcPin { self.attenuations[PIN::channel() as usize] = Some(attenuation); AdcPin { pin, + cal_scheme: CS::new_cal(attenuation), _phantom: PhantomData::default(), } } + + /// Calibrate ADC with specified attenuation and voltage source + pub fn adc_calibrate(atten: Attenuation, source: AdcCalSource) -> u16 { + let mut adc_max: u16 = 0; + let mut adc_min: u16 = u16::MAX; + let mut adc_sum: u32 = 0; + + ADCI::enable_vdef(true); + + // Start sampling + ADCI::config_onetime_sample(ADC_CAL_CHANNEL as u8, atten as u8); + + // Connect calibration source + ADCI::connect_cal(source, true); + + for _ in 0..ADC_CAL_CNT_MAX { + ADCI::set_init_code(0); + + // Trigger ADC sampling + ADCI::start_onetime_sample(); + + // Wait until ADC1 sampling is done + while !ADCI::is_done() {} + + let adc = ADCI::read_data() & ADC_VAL_MASK; + + ADCI::reset(); + + adc_sum += adc as u32; + adc_max = adc.max(adc_max); + adc_min = adc.min(adc_min); + } + + let cal_val = (adc_sum - adc_max as u32 - adc_min as u32) as u16 / (ADC_CAL_CNT_MAX - 2); + + // Disconnect calibration source + ADCI::connect_cal(source, false); + + cal_val + } } impl Default for AdcConfig { @@ -77,19 +267,40 @@ impl Default for AdcConfig { } } +#[derive(Clone, Copy)] +pub enum AdcCalSource { + Gnd, + Ref, +} + #[doc(hidden)] pub trait RegisterAccess { - fn start_onetime_sample(channel: u8, attenuation: u8); + /// Configure onetime sampling parameters + fn config_onetime_sample(channel: u8, attenuation: u8); + /// Start onetime sampling + fn start_onetime_sample(); + + /// Check if sampling is done fn is_done() -> bool; + /// Read sample data fn read_data() -> u16; + /// Reset flags fn reset(); + + fn enable_vdef(enable: bool); + + /// Enable internal connect GND (for calibration) + fn connect_cal(source: AdcCalSource, enable: bool); + + /// Set calibration parameter to ADC hardware + fn set_init_code(data: u16); } impl RegisterAccess for ADC1 { - fn start_onetime_sample(channel: u8, attenuation: u8) { + fn config_onetime_sample(channel: u8, attenuation: u8) { let sar_adc = unsafe { &*APB_SARADC::PTR }; sar_adc.onetime_sample.modify(|_, w| unsafe { @@ -99,11 +310,17 @@ impl RegisterAccess for ADC1 { .bits(channel) .saradc_onetime_atten() .bits(attenuation) - .saradc_onetime_start() - .set_bit() }); } + fn start_onetime_sample() { + let sar_adc = unsafe { &*APB_SARADC::PTR }; + + sar_adc + .onetime_sample + .modify(|_, w| w.saradc_onetime_start().set_bit()); + } + fn is_done() -> bool { let sar_adc = unsafe { &*APB_SARADC::PTR }; @@ -119,19 +336,76 @@ impl RegisterAccess for ADC1 { fn reset() { let sar_adc = unsafe { &*APB_SARADC::PTR }; + // Clear ADC1 sampling done interrupt bit sar_adc .int_clr .write(|w| w.apb_saradc1_done_int_clr().set_bit()); + // Disable ADC sampling sar_adc .onetime_sample .modify(|_, w| w.saradc_onetime_start().clear_bit()); } + + fn enable_vdef(enable: bool) { + let value = enable as _; + regi2c_write_mask( + I2C_SAR_ADC, + I2C_SAR_ADC_HOSTID, + ADC_SAR1_DREF_ADDR, + ADC_SAR1_DREF_ADDR_MSB, + ADC_SAR1_DREF_ADDR_LSB, + value, + ); + } + + fn connect_cal(source: AdcCalSource, enable: bool) { + let value = enable as _; + match source { + AdcCalSource::Gnd => regi2c_write_mask( + I2C_SAR_ADC, + I2C_SAR_ADC_HOSTID, + ADC_SAR1_ENCAL_GND_ADDR, + ADC_SAR1_ENCAL_GND_ADDR_MSB, + ADC_SAR1_ENCAL_GND_ADDR_LSB, + value, + ), + AdcCalSource::Ref => regi2c_write_mask( + I2C_SAR_ADC, + I2C_SAR_ADC_HOSTID, + ADC_SARADC1_ENCAL_REF_ADDR, + ADC_SARADC1_ENCAL_REF_ADDR_MSB, + ADC_SARADC1_ENCAL_REF_ADDR_LSB, + value, + ), + } + } + + fn set_init_code(data: u16) { + let [msb, lsb] = data.to_be_bytes(); + + regi2c_write_mask( + I2C_SAR_ADC, + I2C_SAR_ADC_HOSTID, + ADC_SAR1_INITIAL_CODE_HIGH_ADDR, + ADC_SAR1_INITIAL_CODE_HIGH_ADDR_MSB, + ADC_SAR1_INITIAL_CODE_HIGH_ADDR_LSB, + msb as _, + ); + regi2c_write_mask( + I2C_SAR_ADC, + I2C_SAR_ADC_HOSTID, + ADC_SAR1_INITIAL_CODE_LOW_ADDR, + ADC_SAR1_INITIAL_CODE_LOW_ADDR_MSB, + ADC_SAR1_INITIAL_CODE_LOW_ADDR_LSB, + lsb as _, + ); + } } #[cfg(esp32c3)] impl RegisterAccess for ADC2 { - fn start_onetime_sample(channel: u8, attenuation: u8) { + fn config_onetime_sample(channel: u8, attenuation: u8) { let sar_adc = unsafe { &*APB_SARADC::PTR }; sar_adc.onetime_sample.modify(|_, w| unsafe { @@ -141,11 +415,17 @@ impl RegisterAccess for ADC2 { .bits(channel) .saradc_onetime_atten() .bits(attenuation) - .saradc_onetime_start() - .set_bit() }); } + fn start_onetime_sample() { + let sar_adc = unsafe { &*APB_SARADC::PTR }; + + sar_adc + .onetime_sample + .modify(|_, w| w.saradc_onetime_start().set_bit()); + } + fn is_done() -> bool { let sar_adc = unsafe { &*APB_SARADC::PTR }; @@ -169,6 +449,61 @@ impl RegisterAccess for ADC2 { .onetime_sample .modify(|_, w| w.saradc_onetime_start().clear_bit()); } + + fn enable_vdef(enable: bool) { + let value = enable as _; + regi2c_write_mask( + I2C_SAR_ADC, + I2C_SAR_ADC_HOSTID, + ADC_SAR2_DREF_ADDR, + ADC_SAR2_DREF_ADDR_MSB, + ADC_SAR2_DREF_ADDR_LSB, + value, + ); + } + + fn connect_cal(source: AdcCalSource, enable: bool) { + let value = enable as _; + match source { + AdcCalSource::Gnd => regi2c_write_mask( + I2C_SAR_ADC, + I2C_SAR_ADC_HOSTID, + ADC_SAR2_ENCAL_GND_ADDR, + ADC_SAR2_ENCAL_GND_ADDR_MSB, + ADC_SAR2_ENCAL_GND_ADDR_LSB, + value, + ), + AdcCalSource::Ref => regi2c_write_mask( + I2C_SAR_ADC, + I2C_SAR_ADC_HOSTID, + ADC_SARADC2_ENCAL_REF_ADDR, + ADC_SARADC2_ENCAL_REF_ADDR_MSB, + ADC_SARADC2_ENCAL_REF_ADDR_LSB, + value, + ), + } + } + + fn set_init_code(data: u16) { + let [msb, lsb] = data.to_be_bytes(); + + regi2c_write_mask( + I2C_SAR_ADC, + I2C_SAR_ADC_HOSTID, + ADC_SAR2_INITIAL_CODE_HIGH_ADDR, + ADC_SAR2_INITIAL_CODE_HIGH_ADDR_MSB, + ADC_SAR2_INITIAL_CODE_HIGH_ADDR_LSB, + msb as _, + ); + regi2c_write_mask( + I2C_SAR_ADC, + I2C_SAR_ADC_HOSTID, + ADC_SAR2_INITIAL_CODE_LOW_ADDR, + ADC_SAR2_INITIAL_CODE_LOW_ADDR_MSB, + ADC_SAR2_INITIAL_CODE_LOW_ADDR_LSB, + lsb as _, + ); + } } pub struct ADC<'d, ADCI> { @@ -179,7 +514,7 @@ pub struct ADC<'d, ADCI> { impl<'d, ADCI> ADC<'d, ADCI> where - ADCI: RegisterAccess, + ADCI: RegisterAccess + 'd, { pub fn adc( peripheral_clock_controller: &mut PeripheralClockControl, @@ -209,15 +544,46 @@ where } } -impl<'d, ADCI, WORD, PIN> OneShot> for ADC<'d, ADCI> +#[cfg(any(esp32c2, esp32c3, esp32c6))] +impl AdcCalEfuse for ADC1 { + fn get_init_code(atten: Attenuation) -> Option { + Efuse::get_rtc_calib_init_code(1, atten) + } + + fn get_cal_mv(atten: Attenuation) -> u16 { + Efuse::get_rtc_calib_cal_mv(1, atten) + } + + fn get_cal_code(atten: Attenuation) -> Option { + Efuse::get_rtc_calib_cal_code(1, atten) + } +} + +#[cfg(esp32c3)] +impl AdcCalEfuse for ADC2 { + fn get_init_code(atten: Attenuation) -> Option { + Efuse::get_rtc_calib_init_code(2, atten) + } + + fn get_cal_mv(atten: Attenuation) -> u16 { + Efuse::get_rtc_calib_cal_mv(2, atten) + } + + fn get_cal_code(atten: Attenuation) -> Option { + Efuse::get_rtc_calib_cal_code(2, atten) + } +} + +impl<'d, ADCI, WORD, PIN, CS> OneShot> for ADC<'d, ADCI> where WORD: From, PIN: Channel, ADCI: RegisterAccess, + CS: AdcCalScheme, { type Error = (); - fn read(&mut self, _pin: &mut AdcPin) -> nb::Result { + fn read(&mut self, pin: &mut AdcPin) -> nb::Result { if self.attenuations[AdcPin::::channel() as usize] == None { panic!( "Channel {} is not configured reading!", @@ -236,9 +602,13 @@ where // If no conversions are in progress, start a new one for given channel self.active_channel = Some(AdcPin::::channel()); + // Set ADC unit calibration according used scheme for pin + ADCI::set_init_code(pin.cal_scheme.adc_cal()); + let channel = self.active_channel.unwrap(); let attenuation = self.attenuations[channel as usize].unwrap() as u8; - ADCI::start_onetime_sample(channel, attenuation); + ADCI::config_onetime_sample(channel, attenuation); + ADCI::start_onetime_sample(); } // Wait for ADC to finish conversion @@ -251,6 +621,9 @@ where let converted_value = ADCI::read_data(); ADCI::reset(); + // Postprocess converted value according to calibration scheme used for pin + let converted_value = pin.cal_scheme.adc_val(converted_value); + // 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 diff --git a/esp-hal-common/src/analog/mod.rs b/esp-hal-common/src/analog/mod.rs index 663a8fd04ce..18690cb816a 100644 --- a/esp-hal-common/src/analog/mod.rs +++ b/esp-hal-common/src/analog/mod.rs @@ -5,6 +5,46 @@ pub mod adc; #[cfg(dac)] pub mod dac; +/// A helper trait to do calibrated samples fitting +pub trait AdcCalScheme: Sized { + /// Instantiate scheme + fn new_cal(atten: adc::Attenuation) -> Self; + + /// Get ADC calibration value to set to ADC unit + fn adc_cal(&self) -> u16 { + 0 + } + + /// Convert ADC value + fn adc_val(&self, val: u16) -> u16 { + val + } +} + +impl AdcCalScheme for () { + fn new_cal(_atten: adc::Attenuation) -> Self { + () + } +} + +/// A helper trait to get access to ADC calibration efuses +pub trait AdcCalEfuse { + /// Get ADC calibration init code + /// + /// Returns digital value for zero voltage for a given attenuation + fn get_init_code(atten: adc::Attenuation) -> Option; + + /// Get ADC calibration reference point voltage + /// + /// Returns reference voltage (millivolts) for a given attenuation + fn get_cal_mv(atten: adc::Attenuation) -> u16; + + /// Get ADC calibration reference point digital value + /// + /// Returns digital value for reference voltage for a given attenuation + fn get_cal_code(atten: adc::Attenuation) -> Option; +} + pub struct ADC1 { _private: (), } diff --git a/esp-hal-common/src/soc/esp32c2/efuse.rs b/esp-hal-common/src/soc/esp32c2/efuse.rs index eaf5a9949e9..7519566c940 100644 --- a/esp-hal-common/src/soc/esp32c2/efuse.rs +++ b/esp-hal-common/src/soc/esp32c2/efuse.rs @@ -1,7 +1,7 @@ //! Reading of eFuses -use crate::peripherals::EFUSE; pub use crate::soc::efuse_field::*; +use crate::{adc::Attenuation, peripherals::EFUSE}; pub struct Efuse; @@ -36,6 +36,102 @@ impl Efuse { pub fn get_rwdt_multiplier() -> u8 { Self::read_field_le::(WDT_DELAY_SEL) } + + /// Get efuse block version + /// + /// see https://github.com/espressif/esp-idf/blob/dc016f5987/components/hal/efuse_hal.c#L27-L30 + pub fn get_block_version() -> (u8, u8) { + // see https://github.com/espressif/esp-idf/blob/dc016f5987/components/hal/esp32c2/include/hal/efuse_ll.h#L65-L73 + // https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_table.csv#L90-L91 + ( + Self::read_field_le::(BLK_VERSION_MAJOR), + Self::read_field_le::(BLK_VERSION_MINOR), + ) + } + + /// Get version of RTC calibration block + /// + /// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_rtc_calib.c#L14 + pub fn get_rtc_calib_version() -> u8 { + let (major, _minor) = Self::get_block_version(); + if major == 0 { + 1 + } else { + 0 + } + } + + /// Get ADC initial code for specified attenuation from efuse + /// + /// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_rtc_calib.c#L27 + pub fn get_rtc_calib_init_code(_unit: u8, atten: Attenuation) -> Option { + let version = Self::get_rtc_calib_version(); + + if version != 1 { + return None; + } + + // see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_table.csv#L94 + let diff_code0: u16 = Self::read_field_le(ADC1_INIT_CODE_ATTEN0); + let code0 = if diff_code0 & (1 << 7) != 0 { + 2160 - (diff_code0 & 0x7f) + } else { + 2160 + diff_code0 + }; + + if matches!(atten, Attenuation::Attenuation0dB) { + return Some(code0); + } + + // see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_table.csv#L95 + let diff_code11: u16 = Self::read_field_le(ADC1_INIT_CODE_ATTEN3); + let code11 = code0 + diff_code11; + + Some(code11) + } + + /// Get ADC reference point voltage for specified attenuation in millivolts + /// + /// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_rtc_calib.c#L65 + pub fn get_rtc_calib_cal_mv(_unit: u8, atten: Attenuation) -> u16 { + match atten { + Attenuation::Attenuation0dB => 400, + Attenuation::Attenuation11dB => 1370, + } + } + + /// Get ADC reference point digital code for specified attenuation + /// + /// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_rtc_calib.c#L65 + pub fn get_rtc_calib_cal_code(_unit: u8, atten: Attenuation) -> Option { + let version = Self::get_rtc_calib_version(); + + if version != 1 { + return None; + } + + // see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_table.csv#L96 + let diff_code0: u16 = Self::read_field_le(ADC1_CAL_VOL_ATTEN0); + let code0 = if diff_code0 & (1 << 7) != 0 { + 1540 - (diff_code0 & 0x7f) + } else { + 1540 + diff_code0 + }; + + if matches!(atten, Attenuation::Attenuation0dB) { + return Some(code0); + } + + // see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_table.csv#L97 + let diff_code11: u16 = Self::read_field_le(ADC1_CAL_VOL_ATTEN3); + let code11 = if diff_code0 & (1 << 5) != 0 { + code0 - (diff_code11 & 0x1f) + } else { + code0 + diff_code11 + } - 123; + + Some(code11) + } } #[derive(Copy, Clone)] diff --git a/esp-hal-common/src/soc/esp32c3/efuse.rs b/esp-hal-common/src/soc/esp32c3/efuse.rs index 300dff12555..25a4b61f98a 100644 --- a/esp-hal-common/src/soc/esp32c3/efuse.rs +++ b/esp-hal-common/src/soc/esp32c3/efuse.rs @@ -1,7 +1,7 @@ //! Reading of eFuses -use crate::peripherals::EFUSE; pub use crate::soc::efuse_field::*; +use crate::{adc::Attenuation, peripherals::EFUSE}; pub struct Efuse; @@ -36,6 +36,91 @@ impl Efuse { pub fn get_rwdt_multiplier() -> u8 { Self::read_field_le::(WDT_DELAY_SEL) } + + /// Get efuse block version + /// + /// see https://github.com/espressif/esp-idf/blob/dc016f5987/components/hal/efuse_hal.c#L27-L30 + pub fn get_block_version() -> (u8, u8) { + // see https://github.com/espressif/esp-idf/blob/dc016f5987/components/hal/esp32c3/include/hal/efuse_ll.h#L70-L78 + // https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_table.csv#L163 + // https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_table.csv#L173 + ( + Self::read_field_le::(BLK_VERSION_MAJOR), + Self::read_field_le::(BLK_VERSION_MINOR), + ) + } + + /// Get version of RTC calibration block + /// + /// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_rtc_calib.c#L12 + pub fn get_rtc_calib_version() -> u8 { + let (major, _minor) = Self::get_block_version(); + if major == 1 { + 1 + } else { + 0 + } + } + + /// Get ADC initial code for specified attenuation from efuse + /// + /// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_rtc_calib.c#L25 + pub fn get_rtc_calib_init_code(_unit: u8, atten: Attenuation) -> Option { + let version = Self::get_rtc_calib_version(); + + if version != 1 { + return None; + } + + // See https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_table.csv#L176-L179 + let init_code: u16 = Self::read_field_le(match atten { + Attenuation::Attenuation0dB => ADC1_INIT_CODE_ATTEN0, + Attenuation::Attenuation2p5dB => ADC1_INIT_CODE_ATTEN1, + Attenuation::Attenuation6dB => ADC1_INIT_CODE_ATTEN2, + Attenuation::Attenuation11dB => ADC1_INIT_CODE_ATTEN3, + }); + + Some(init_code + 1000) // version 1 logic + } + + /// Get ADC reference point voltage for specified attenuation in millivolts + /// + /// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_rtc_calib.c#L49 + pub fn get_rtc_calib_cal_mv(_unit: u8, atten: Attenuation) -> u16 { + match atten { + Attenuation::Attenuation0dB => 400, + Attenuation::Attenuation2p5dB => 550, + Attenuation::Attenuation6dB => 750, + Attenuation::Attenuation11dB => 1370, + } + } + + /// Get ADC reference point digital code for specified attenuation + /// + /// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_rtc_calib.c#L49 + pub fn get_rtc_calib_cal_code(_unit: u8, atten: Attenuation) -> Option { + let version = Self::get_rtc_calib_version(); + + if version != 1 { + return None; + } + + // See https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_table.csv#L180-L183 + let cal_code: u16 = Self::read_field_le(match atten { + Attenuation::Attenuation0dB => ADC1_CAL_VOL_ATTEN0, + Attenuation::Attenuation2p5dB => ADC1_CAL_VOL_ATTEN1, + Attenuation::Attenuation6dB => ADC1_CAL_VOL_ATTEN2, + Attenuation::Attenuation11dB => ADC1_CAL_VOL_ATTEN3, + }); + + let cal_code = if cal_code & (1 << 9) != 0 { + 2000 - (cal_code & !(1 << 9)) + } else { + 2000 + cal_code + }; + + Some(cal_code) + } } #[derive(Copy, Clone)] diff --git a/esp-hal-common/src/soc/esp32c6/efuse.rs b/esp-hal-common/src/soc/esp32c6/efuse.rs index 300dff12555..bb502c822b5 100644 --- a/esp-hal-common/src/soc/esp32c6/efuse.rs +++ b/esp-hal-common/src/soc/esp32c6/efuse.rs @@ -1,7 +1,7 @@ //! Reading of eFuses -use crate::peripherals::EFUSE; pub use crate::soc::efuse_field::*; +use crate::{adc::Attenuation, peripherals::EFUSE}; pub struct Efuse; @@ -36,6 +36,90 @@ impl Efuse { pub fn get_rwdt_multiplier() -> u8 { Self::read_field_le::(WDT_DELAY_SEL) } + + /// Get efuse block version + /// + /// see https://github.com/espressif/esp-idf/blob/dc016f5987/components/hal/efuse_hal.c#L27-L30 + pub fn get_block_version() -> (u8, u8) { + // see https://github.com/espressif/esp-idf/blob/dc016f5987/components/hal/esp32c6/include/hal/efuse_ll.h#L65-L73 + // https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c6/esp_efuse_table.csv#L156 + ( + Self::read_field_le::(BLK_VERSION_MAJOR), + Self::read_field_le::(BLK_VERSION_MINOR), + ) + } + + /// Get version of RTC calibration block + /// + /// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c6/esp_efuse_rtc_calib.c#L20 + pub fn get_rtc_calib_version() -> u8 { + let (_major, minor) = Self::get_block_version(); + if minor >= 1 { + 1 + } else { + 0 + } + } + + /// Get ADC initial code for specified attenuation from efuse + /// + /// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c6/esp_efuse_rtc_calib.c#L32 + pub fn get_rtc_calib_init_code(_unit: u8, atten: Attenuation) -> Option { + let version = Self::get_rtc_calib_version(); + + if version != 1 { + return None; + } + + // See https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c6/esp_efuse_table.csv#L147-L152 + let init_code: u16 = Self::read_field_le(match atten { + Attenuation::Attenuation0dB => ADC1_INIT_CODE_ATTEN0, + Attenuation::Attenuation2p5dB => ADC1_INIT_CODE_ATTEN1, + Attenuation::Attenuation6dB => ADC1_INIT_CODE_ATTEN2, + Attenuation::Attenuation11dB => ADC1_INIT_CODE_ATTEN3, + }); + + Some(init_code + 1600) // version 1 logic + } + + /// Get ADC reference point voltage for specified attenuation in millivolts + /// + /// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c6/esp_efuse_rtc_calib.c#L42 + pub fn get_rtc_calib_cal_mv(_unit: u8, atten: Attenuation) -> u16 { + match atten { + Attenuation::Attenuation0dB => 400, + Attenuation::Attenuation2p5dB => 550, + Attenuation::Attenuation6dB => 750, + Attenuation::Attenuation11dB => 1370, + } + } + + /// Get ADC reference point digital code for specified attenuation + /// + /// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c6/esp_efuse_rtc_calib.c#L42 + pub fn get_rtc_calib_cal_code(_unit: u8, atten: Attenuation) -> Option { + let version = Self::get_rtc_calib_version(); + + if version != 1 { + return None; + } + + // See https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c6/esp_efuse_table.csv#L153-L156 + let cal_code: u16 = Self::read_field_le(match atten { + Attenuation::Attenuation0dB => ADC1_CAL_VOL_ATTEN0, + Attenuation::Attenuation2p5dB => ADC1_CAL_VOL_ATTEN1, + Attenuation::Attenuation6dB => ADC1_CAL_VOL_ATTEN2, + Attenuation::Attenuation11dB => ADC1_CAL_VOL_ATTEN3, + }); + + let cal_code = if cal_code & (1 << 9) != 0 { + 1500 - (cal_code & !(1 << 9)) + } else { + 1500 + cal_code + }; + + Some(cal_code) + } } #[derive(Copy, Clone)] diff --git a/esp32c2-hal/examples/adc_cal.rs b/esp32c2-hal/examples/adc_cal.rs new file mode 100644 index 00000000000..dce2d67b84b --- /dev/null +++ b/esp32c2-hal/examples/adc_cal.rs @@ -0,0 +1,73 @@ +//! Connect a potentiometer to PIN2 and see the read values change when +//! rotating the shaft. Alternatively you could also connect the PIN to GND or +//! 3V3 to see the maximum and minimum raw values read. + +#![no_std] +#![no_main] + +use esp32c2_hal::{ + adc, + adc::{AdcConfig, Attenuation, ADC, ADC1}, + clock::ClockControl, + gpio::IO, + peripherals::Peripherals, + prelude::*, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use esp_println::println; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let mut system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + // Disable the watchdog timers. For the ESP32-C2, this includes the Super WDT, + // the RTC WDT, and the TIMG WDTs. + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + let timer_group0 = TimerGroup::new( + peripherals.TIMG0, + &clocks, + &mut system.peripheral_clock_control, + ); + let mut wdt0 = timer_group0.wdt; + + rtc.swd.disable(); + rtc.rwdt.disable(); + wdt0.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + + // Create ADC instances + let analog = peripherals.APB_SARADC.split(); + + let mut adc1_config = AdcConfig::new(); + + let atten = Attenuation::Attenuation11dB; + + // You can try any of the following calibration methods by uncommenting them + // type AdcCal = (); + // type AdcCal = adc::AdcCalBasic; + type AdcCal = adc::AdcCalLine; + + let mut pin = adc1_config.enable_pin_with_cal::<_, AdcCal>(io.pins.gpio2.into_analog(), atten); + + let mut adc1 = ADC::::adc( + &mut system.peripheral_clock_control, + analog.adc1, + adc1_config, + ) + .unwrap(); + + let mut delay = Delay::new(&clocks); + + loop { + let pin_value: u16 = nb::block!(adc1.read(&mut pin)).unwrap(); + let pin_value_mv = pin_value as u32 * atten.ref_mv() as u32 / 4096; + println!("PIN2 ADC reading = {pin_value} ({pin_value_mv} mV)"); + delay.delay_ms(1500u32); + } +} diff --git a/esp32c3-hal/examples/adc_cal.rs b/esp32c3-hal/examples/adc_cal.rs new file mode 100644 index 00000000000..223f241de6d --- /dev/null +++ b/esp32c3-hal/examples/adc_cal.rs @@ -0,0 +1,81 @@ +//! Connect a potentiometer to PIN2 and see the read values change when +//! rotating the shaft. Alternatively you could also connect the PIN to GND or +//! 3V3 to see the maximum and minimum raw values read. + +#![no_std] +#![no_main] + +use esp32c3_hal::{ + adc, + adc::{AdcConfig, Attenuation, ADC, ADC1}, + clock::ClockControl, + gpio::IO, + peripherals::Peripherals, + prelude::*, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use esp_println::println; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let mut system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + // Disable the watchdog timers. For the ESP32-C3, this includes the Super WDT, + // the RTC WDT, and the TIMG WDTs. + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + let timer_group0 = TimerGroup::new( + peripherals.TIMG0, + &clocks, + &mut system.peripheral_clock_control, + ); + let mut wdt0 = timer_group0.wdt; + let timer_group1 = TimerGroup::new( + peripherals.TIMG1, + &clocks, + &mut system.peripheral_clock_control, + ); + let mut wdt1 = timer_group1.wdt; + + rtc.swd.disable(); + rtc.rwdt.disable(); + wdt0.disable(); + wdt1.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + + // Create ADC instances + let analog = peripherals.APB_SARADC.split(); + + let mut adc1_config = AdcConfig::new(); + + let atten = Attenuation::Attenuation11dB; + + // You can try any of the following calibration methods by uncommenting them + // type AdcCal = (); + // type AdcCal = adc::AdcCalBasic; + // type AdcCal = adc::AdcCalLine; + type AdcCal = adc::AdcCalCurve; + + let mut pin = adc1_config.enable_pin_with_cal::<_, AdcCal>(io.pins.gpio2.into_analog(), atten); + + let mut adc1 = ADC::::adc( + &mut system.peripheral_clock_control, + analog.adc1, + adc1_config, + ) + .unwrap(); + + let mut delay = Delay::new(&clocks); + + loop { + let pin_value: u16 = nb::block!(adc1.read(&mut pin)).unwrap(); + let pin_value_mv = pin_value as u32 * atten.ref_mv() as u32 / 4096; + println!("PIN2 ADC reading = {pin_value} ({pin_value_mv} mV)"); + delay.delay_ms(1500u32); + } +} diff --git a/esp32c6-hal/examples/adc_cal.rs b/esp32c6-hal/examples/adc_cal.rs new file mode 100644 index 00000000000..f789f1f5981 --- /dev/null +++ b/esp32c6-hal/examples/adc_cal.rs @@ -0,0 +1,81 @@ +//! Connect a potentiometer to PIN2 and see the read values change when +//! rotating the shaft. Alternatively you could also connect the PIN to GND or +//! 3V3 to see the maximum and minimum raw values read. + +#![no_std] +#![no_main] + +use esp32c6_hal::{ + adc, + adc::{AdcConfig, Attenuation, ADC, ADC1}, + clock::ClockControl, + gpio::IO, + peripherals::Peripherals, + prelude::*, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use esp_println::println; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let mut system = peripherals.PCR.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + // Disable the watchdog timers. For the ESP32-C6, this includes the Super WDT, + // and the TIMG WDTs. + let mut rtc = Rtc::new(peripherals.LP_CLKRST); + let timer_group0 = TimerGroup::new( + peripherals.TIMG0, + &clocks, + &mut system.peripheral_clock_control, + ); + let mut wdt0 = timer_group0.wdt; + let timer_group1 = TimerGroup::new( + peripherals.TIMG1, + &clocks, + &mut system.peripheral_clock_control, + ); + let mut wdt1 = timer_group1.wdt; + + rtc.swd.disable(); + rtc.rwdt.disable(); + wdt0.disable(); + wdt1.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + + // Create ADC instances + let analog = peripherals.APB_SARADC.split(); + + let mut adc1_config = AdcConfig::new(); + + let atten = Attenuation::Attenuation11dB; + + // You can try any of the following calibration methods by uncommenting them + // type AdcCal = (); + // type AdcCal = adc::AdcCalBasic; + // type AdcCal = adc::AdcCalLine; + type AdcCal = adc::AdcCalCurve; + + let mut pin = adc1_config.enable_pin_with_cal::<_, AdcCal>(io.pins.gpio2.into_analog(), atten); + + let mut adc1 = ADC::::adc( + &mut system.peripheral_clock_control, + analog.adc1, + adc1_config, + ) + .unwrap(); + + let mut delay = Delay::new(&clocks); + + loop { + let pin_value: u16 = nb::block!(adc1.read(&mut pin)).unwrap(); + let pin_value_mv = pin_value as u32 * atten.ref_mv() as u32 / 4096; + println!("PIN2 ADC reading = {pin_value} ({pin_value_mv} mV)"); + delay.delay_ms(1500u32); + } +}