diff --git a/CHANGELOG.md b/CHANGELOG.md index faa9cd21c84..d3a1bb78e96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Implemented calibrated ADC API for ESP32-S3 (#641) + ### Changed - Update `embedded-hal-*` alpha packages to their latest versions (#640) diff --git a/esp-hal-common/src/analog/adc/cal_basic.rs b/esp-hal-common/src/analog/adc/cal_basic.rs index 3dc279fe050..cfa3508fc63 100644 --- a/esp-hal-common/src/analog/adc/cal_basic.rs +++ b/esp-hal-common/src/analog/adc/cal_basic.rs @@ -1,6 +1,13 @@ use core::marker::PhantomData; -use crate::adc::{AdcCalEfuse, AdcCalScheme, AdcCalSource, AdcConfig, Attenuation, RegisterAccess}; +use crate::adc::{ + AdcCalEfuse, + AdcCalScheme, + AdcCalSource, + AdcConfig, + Attenuation, + CalibrationAccess, +}; /// Basic ADC calibration scheme /// @@ -18,7 +25,7 @@ pub struct AdcCalBasic { impl AdcCalScheme for AdcCalBasic where - ADCI: AdcCalEfuse + RegisterAccess, + ADCI: AdcCalEfuse + CalibrationAccess, { fn new_cal(atten: Attenuation) -> Self { // Try to get init code (Dout0) from efuse diff --git a/esp-hal-common/src/analog/adc/cal_curve.rs b/esp-hal-common/src/analog/adc/cal_curve.rs index 1beaa542a82..75bfe4d5603 100644 --- a/esp-hal-common/src/analog/adc/cal_curve.rs +++ b/esp-hal-common/src/analog/adc/cal_curve.rs @@ -6,7 +6,7 @@ use crate::adc::{ AdcCalScheme, AdcHasLineCal, Attenuation, - RegisterAccess, + CalibrationAccess, }; const COEFF_MUL: i64 = 1 << 52; @@ -52,7 +52,7 @@ pub struct AdcCalCurve { impl AdcCalScheme for AdcCalCurve where - ADCI: AdcCalEfuse + AdcHasLineCal + AdcHasCurveCal + RegisterAccess, + ADCI: AdcCalEfuse + AdcHasLineCal + AdcHasCurveCal + CalibrationAccess, { fn new_cal(atten: Attenuation) -> Self { let line = AdcCalLine::::new_cal(atten); diff --git a/esp-hal-common/src/analog/adc/cal_line.rs b/esp-hal-common/src/analog/adc/cal_line.rs index 73d72455895..e0bae18a972 100644 --- a/esp-hal-common/src/analog/adc/cal_line.rs +++ b/esp-hal-common/src/analog/adc/cal_line.rs @@ -7,7 +7,7 @@ use crate::adc::{ AdcCalSource, AdcConfig, Attenuation, - RegisterAccess, + CalibrationAccess, }; /// Marker trait for ADC units which support line fitting @@ -46,7 +46,7 @@ pub struct AdcCalLine { impl AdcCalScheme for AdcCalLine where - ADCI: AdcCalEfuse + AdcHasLineCal + RegisterAccess, + ADCI: AdcCalEfuse + AdcHasLineCal + CalibrationAccess, { fn new_cal(atten: Attenuation) -> Self { let basic = AdcCalBasic::::new_cal(atten); @@ -93,8 +93,8 @@ where } } -#[cfg(any(esp32c2, esp32c3, esp32c6))] +#[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))] impl AdcHasLineCal for crate::adc::ADC1 {} -#[cfg(esp32c3)] +#[cfg(any(esp32c3, esp32s3))] 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 43d3ab75845..89f21b4f944 100644 --- a/esp-hal-common/src/analog/adc/riscv.rs +++ b/esp-hal-common/src/analog/adc/riscv.rs @@ -60,7 +60,7 @@ cfg_if::cfg_if! { const ADC_VAL_MASK: u16 = 0xfff; const ADC_CAL_CNT_MAX: u16 = 32; - const ADC_CAL_CHANNEL: u32 = 0xf; + const ADC_CAL_CHANNEL: u16 = 15; const ADC_SAR1_ENCAL_GND_ADDR: u8 = 0x7; const ADC_SAR1_ENCAL_GND_ADDR_MSB: u8 = 5; @@ -192,7 +192,7 @@ where &mut self, pin: PIN, attenuation: Attenuation, - ) -> AdcPin { + ) -> AdcPin { self.attenuations[PIN::channel() as usize] = Some(attenuation); AdcPin { @@ -206,7 +206,10 @@ where &mut self, pin: PIN, attenuation: Attenuation, - ) -> AdcPin { + ) -> AdcPin + where + ADCI: CalibrationAccess, + { self.attenuations[PIN::channel() as usize] = Some(attenuation); AdcPin { @@ -217,7 +220,10 @@ where } /// Calibrate ADC with specified attenuation and voltage source - pub fn adc_calibrate(atten: Attenuation, source: AdcCalSource) -> u16 { + pub fn adc_calibrate(atten: Attenuation, source: AdcCalSource) -> u16 + where + ADCI: CalibrationAccess, + { let mut adc_max: u16 = 0; let mut adc_min: u16 = u16::MAX; let mut adc_sum: u32 = 0; @@ -290,13 +296,19 @@ pub trait RegisterAccess { /// Reset flags fn reset(); + /// Set calibration parameter to ADC hardware + fn set_init_code(data: u16); +} + +pub trait CalibrationAccess: RegisterAccess { + const ADC_CAL_CNT_MAX: u16; + const ADC_CAL_CHANNEL: u16; + const ADC_VAL_MASK: u16; + fn enable_vdef(enable: bool); - /// Enable internal connect GND (for calibration) + /// Enable internal calibration voltage source fn connect_cal(source: AdcCalSource, enable: bool); - - /// Set calibration parameter to ADC hardware - fn set_init_code(data: u16); } impl RegisterAccess for ADC1 { @@ -347,6 +359,33 @@ impl RegisterAccess for ADC1 { .modify(|_, w| w.saradc_onetime_start().clear_bit()); } + 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 _, + ); + } +} + +impl CalibrationAccess for ADC1 { + const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX; + const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL; + const ADC_VAL_MASK: u16 = ADC_VAL_MASK; + fn enable_vdef(enable: bool) { let value = enable as _; regi2c_write_mask( @@ -380,27 +419,6 @@ impl RegisterAccess for ADC1 { ), } } - - 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)] @@ -450,6 +468,34 @@ impl RegisterAccess for ADC2 { .modify(|_, w| w.saradc_onetime_start().clear_bit()); } + 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 _, + ); + } +} + +#[cfg(esp32c3)] +impl CalibrationAccess for ADC2 { + const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX; + const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL; + const ADC_VAL_MASK: u16 = ADC_VAL_MASK; + fn enable_vdef(enable: bool) { let value = enable as _; regi2c_write_mask( @@ -483,27 +529,6 @@ impl RegisterAccess for ADC2 { ), } } - - 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> { diff --git a/esp-hal-common/src/analog/adc/xtensa.rs b/esp-hal-common/src/analog/adc/xtensa.rs index 53d67d16c4c..7b8f873a2b5 100644 --- a/esp-hal-common/src/analog/adc/xtensa.rs +++ b/esp-hal-common/src/analog/adc/xtensa.rs @@ -2,12 +2,88 @@ use core::marker::PhantomData; use embedded_hal::adc::{Channel, OneShot}; +#[cfg(esp32s3)] +use crate::efuse::Efuse; use crate::{ analog::{ADC1, ADC2}, peripheral::PeripheralRef, peripherals::{APB_SARADC, SENS}, }; +#[cfg(esp32s3)] +mod cal_basic; +#[cfg(esp32s3)] +mod cal_curve; +#[cfg(esp32s3)] +mod cal_line; + +#[cfg(esp32s3)] +pub use cal_basic::AdcCalBasic; +#[cfg(esp32s3)] +pub use cal_curve::{AdcCalCurve, AdcHasCurveCal}; +#[cfg(esp32s3)] +pub use cal_line::{AdcCalLine, AdcHasLineCal}; + +pub use crate::analog::{AdcCalEfuse, AdcCalScheme}; + +// Constants taken from: +// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32s2/include/soc/regi2c_saradc.h +// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32s3/include/soc/regi2c_saradc.h +cfg_if::cfg_if! { + if #[cfg(any(esp32s2, esp32s3))] { + const I2C_SAR_ADC: u8 = 0x69; + const I2C_SAR_ADC_HOSTID: u8 = 1; + + const ADC_VAL_MASK: u16 = 0xfff; + const ADC_CAL_CNT_MAX: u16 = 32; + const ADC_CAL_CHANNEL: u16 = 15; + + 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(any(esp32s2, esp32s3))] { + const ADC_SAR2_ENCAL_GND_ADDR: u8 = 0x7; + const ADC_SAR2_ENCAL_GND_ADDR_MSB: u8 = 5; + const ADC_SAR2_ENCAL_GND_ADDR_LSB: u8 = 5; + + 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 = 4; + const ADC_SARADC2_ENCAL_REF_ADDR_LSB: u8 = 4; + } +} + /// The sampling/readout resolution of the ADC #[derive(PartialEq, Eq, Clone, Copy)] pub enum Resolution { @@ -17,18 +93,50 @@ 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 Attenuation2p5dB = 0b01, + /// 6 dB attenuation, measurement range: 0 - 1350 mV 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, + Attenuation::Attenuation2p5dB, + Attenuation::Attenuation6dB, + Attenuation::Attenuation11dB, + ]; + + /// Reference voltage in millivolts + /// + /// Vref = 10 ^ (Att / 20) * Vref0 + /// where Vref0 = 1.1 V, Att - attenuation in dB + /// + /// To convert raw value to millivolts use formula: + /// 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, + Attenuation::Attenuation2p5dB => 1467, + 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 { @@ -59,9 +167,72 @@ where 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 + where + ADCI: CalibrationAccess, + { + 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 + where + ADCI: CalibrationAccess, + { + 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::adc_samplecfg(ADCI::ADC_CAL_CHANNEL); + ADCI::set_attenuation(ADCI::ADC_CAL_CHANNEL as usize, atten as u8); + + // Connect calibration source + ADCI::connect_cal(source, true); + + for _ in 0..ADCI::ADC_CAL_CNT_MAX { + ADCI::set_init_code(0); + + // Trigger ADC sampling + ADCI::start_sample(); + + // Wait until ADC1 sampling is done + while !ADCI::is_done() {} + + let adc = ADCI::read_data() & ADCI::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 / (ADCI::ADC_CAL_CNT_MAX - 2); + + // Disconnect calibration source + ADCI::connect_cal(source, false); + + cal_val + } } impl Default for AdcConfig { @@ -74,11 +245,15 @@ impl Default for AdcConfig { } } +#[derive(Clone, Copy)] +pub enum AdcCalSource { + Gnd, + Ref, +} + #[doc(hidden)] pub trait RegisterAccess { - fn set_bit_width(resolution: u8); - - fn set_sample_bit(resolution: u8); + fn adc_samplecfg(channel: u16); fn set_attenuation(channel: usize, attenuation: u8); @@ -90,22 +265,52 @@ pub trait RegisterAccess { fn set_en_pad(channel: u8); - fn clear_start_sar(); + fn clear_start_sample(); + + fn start_sample(); + + /// Check if sampling is done + fn is_done() -> bool; - fn set_start_sar(); + /// Read sample data + fn read_data() -> u16; - fn read_done_sar() -> bool; + /// Set calibration parameter to ADC hardware + fn set_init_code(data: u16); - fn read_data_sar() -> u16; + /// Reset flags + fn reset(); +} + +pub trait CalibrationAccess: RegisterAccess { + const ADC_CAL_CNT_MAX: u16; + const ADC_CAL_CHANNEL: u16; + const ADC_VAL_MASK: u16; + + fn enable_vdef(enable: bool); + + /// Enable internal calibration voltage source + fn connect_cal(source: AdcCalSource, enable: bool); } impl RegisterAccess for ADC1 { - fn set_bit_width(_resolution: u8) { - // no-op - } + fn adc_samplecfg(channel: u16) { + let sensors = unsafe { &*SENS::ptr() }; - fn set_sample_bit(_resolution: u8) { - // no-op + // Configure for RTC control + sensors.sar_meas1_mux.modify(|_r, w| { + w.sar1_dig_force().clear_bit() // 1: Select digital control; + // 0: Select RTC control. + }); + sensors.sar_meas1_ctrl2.modify(|_r, w| { + w.meas1_start_force() + .set_bit() // 1: SW control RTC ADC start; 0: ULP control RTC ADC start. + .sar1_en_pad_force() + .set_bit() // 1: SW control RTC ADC bit map; 0: ULP control RTC ADC bit map; + // Enable internal connect GND (for calibration). + .sar1_en_pad() + .variant(channel) // only one channel is selected. + }); } fn set_attenuation(channel: usize, attenuation: u8) { @@ -146,38 +351,93 @@ impl RegisterAccess for ADC1 { .modify(|_, w| unsafe { w.sar1_en_pad().bits(1 << channel) }); } - fn clear_start_sar() { + fn clear_start_sample() { let sensors = unsafe { &*SENS::ptr() }; sensors .sar_meas1_ctrl2 .modify(|_, w| w.meas1_start_sar().clear_bit()); } - fn set_start_sar() { + fn start_sample() { let sensors = unsafe { &*SENS::ptr() }; sensors .sar_meas1_ctrl2 .modify(|_, w| w.meas1_start_sar().set_bit()); } - fn read_done_sar() -> bool { + fn is_done() -> bool { let sensors = unsafe { &*SENS::ptr() }; sensors.sar_meas1_ctrl2.read().meas1_done_sar().bit_is_set() } - fn read_data_sar() -> u16 { + fn read_data() -> u16 { let sensors = unsafe { &*SENS::ptr() }; sensors.sar_meas1_ctrl2.read().meas1_data_sar().bits() as u16 } + + fn set_init_code(data: u16) { + let [msb, lsb] = data.to_be_bytes(); + + crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR1_INITIAL_CODE_HIGH_ADDR, msb as u32); + crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR1_INITIAL_CODE_LOW_ADDR, lsb as u32); + } + + fn reset() { + let adc = unsafe { &*APB_SARADC::ptr() }; + let sensors = unsafe { &*SENS::ptr() }; + + cfg_if::cfg_if! { + if #[cfg(esp32s2)] { + adc.int_clr + .write(|w| w.adc1_done_int_clr().set_bit()); + } else { + adc.int_clr + .write(|w| w.apb_saradc1_done_int_clr().set_bit()); + } + } + + sensors + .sar_meas1_ctrl2 + .modify(|_, w| w.meas1_start_sar().clear_bit()); + } } -impl RegisterAccess for ADC2 { - fn set_bit_width(_resolution: u8) { - // no-op +#[cfg(esp32s3)] +impl CalibrationAccess for ADC1 { + const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX; + const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL; + const ADC_VAL_MASK: u16 = ADC_VAL_MASK; + + fn enable_vdef(enable: bool) { + crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR1_DREF_ADDR, enable as u8); + } + + fn connect_cal(source: AdcCalSource, enable: bool) { + match source { + AdcCalSource::Gnd => { + crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR1_ENCAL_GND_ADDR, enable as u8); + } + AdcCalSource::Ref => { + crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SARADC1_ENCAL_REF_ADDR, enable as u8); + } + } } +} - fn set_sample_bit(_resolution: u8) { - // no-op +impl RegisterAccess for ADC2 { + fn adc_samplecfg(channel: u16) { + let sensors = unsafe { &*SENS::ptr() }; + + // Configure for RTC control + sensors.sar_meas2_ctrl2.modify(|_r, w| { + w.meas2_start_force() + .set_bit() // 1: SW control RTC ADC start; 0: ULP control RTC ADC start. + .sar2_en_pad_force() + .set_bit() // 1: SW control RTC ADC bit map; 0: ULP control RTC ADC bit map; + // Enable internal connect GND (for calibration). + .sar2_en_pad() + .variant(channel) // only one channel is selected. + }); } fn set_attenuation(channel: usize, attenuation: u8) { @@ -223,29 +483,77 @@ impl RegisterAccess for ADC2 { .modify(|_, w| unsafe { w.sar2_en_pad().bits(1 << channel) }); } - fn clear_start_sar() { + fn clear_start_sample() { let sensors = unsafe { &*SENS::ptr() }; sensors .sar_meas2_ctrl2 .modify(|_, w| w.meas2_start_sar().clear_bit()); } - fn set_start_sar() { + fn start_sample() { let sensors = unsafe { &*SENS::ptr() }; sensors .sar_meas2_ctrl2 .modify(|_, w| w.meas2_start_sar().set_bit()); } - fn read_done_sar() -> bool { + fn is_done() -> bool { let sensors = unsafe { &*SENS::ptr() }; sensors.sar_meas2_ctrl2.read().meas2_done_sar().bit_is_set() } - fn read_data_sar() -> u16 { + fn read_data() -> u16 { let sensors = unsafe { &*SENS::ptr() }; sensors.sar_meas2_ctrl2.read().meas2_data_sar().bits() as u16 } + + fn set_init_code(data: u16) { + let [msb, lsb] = data.to_be_bytes(); + + crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR2_INITIAL_CODE_HIGH_ADDR, msb as u32); + crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR2_INITIAL_CODE_LOW_ADDR, lsb as u32); + } + + fn reset() { + let adc = unsafe { &*APB_SARADC::ptr() }; + let sensors = unsafe { &*SENS::ptr() }; + + cfg_if::cfg_if! { + if #[cfg(esp32s2)] { + adc.int_clr + .write(|w| w.adc2_done_int_clr().set_bit()); + } else { + adc.int_clr + .write(|w| w.apb_saradc2_done_int_clr().set_bit()); + } + } + + sensors + .sar_meas2_ctrl2 + .modify(|_, w| w.meas2_start_sar().clear_bit()); + } +} + +#[cfg(esp32s3)] +impl CalibrationAccess for ADC2 { + const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX; + const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL; + const ADC_VAL_MASK: u16 = ADC_VAL_MASK; + + fn enable_vdef(enable: bool) { + crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR2_DREF_ADDR, enable as u8); + } + + fn connect_cal(source: AdcCalSource, enable: bool) { + match source { + AdcCalSource::Gnd => { + crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR2_ENCAL_GND_ADDR, enable as u8); + } + AdcCalSource::Ref => { + crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SARADC2_ENCAL_REF_ADDR, enable as u8); + } + } + } } pub struct ADC<'d, ADC> { @@ -264,12 +572,6 @@ where ) -> Result { let sensors = unsafe { &*SENS::ptr() }; - // Set reading and sampling resolution - let resolution: u8 = config.resolution as u8; - - ADCI::set_bit_width(resolution); - ADCI::set_sample_bit(resolution); - // Set attenuation for pins let attenuations = config.attenuations; @@ -342,15 +644,46 @@ where } } -impl<'d, ADCI, WORD, PIN> OneShot> for ADC<'d, ADCI> +#[cfg(esp32s3)] +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(esp32s3)] +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!", @@ -369,20 +702,24 @@ 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()); + ADCI::set_en_pad(AdcPin::::channel() as u8); - ADCI::clear_start_sar(); - ADCI::set_start_sar(); + ADCI::clear_start_sample(); + ADCI::start_sample(); } // Wait for ADC to finish conversion - let conversion_finished = ADCI::read_done_sar(); + let conversion_finished = ADCI::is_done(); if !conversion_finished { return Err(nb::Error::WouldBlock); } // Get converted value - let converted_value = ADCI::read_data_sar(); + let converted_value = ADCI::read_data(); + ADCI::reset(); // Mark that no conversions are currently in progress self.active_channel = None; diff --git a/esp-hal-common/src/clock/clocks_ll/esp32c2.rs b/esp-hal-common/src/clock/clocks_ll/esp32c2.rs index fed446f9743..f0eaa1e3965 100644 --- a/esp-hal-common/src/clock/clocks_ll/esp32c2.rs +++ b/esp-hal-common/src/clock/clocks_ll/esp32c2.rs @@ -2,7 +2,6 @@ use crate::{ clock::{ApbClock, Clock, CpuClock, PllClock, XtalClock}, regi2c_write, regi2c_write_mask, - rom::{rom_i2c_writeReg, rom_i2c_writeReg_Mask}, }; extern "C" { diff --git a/esp-hal-common/src/clock/clocks_ll/esp32c3.rs b/esp-hal-common/src/clock/clocks_ll/esp32c3.rs index e9243b9465b..2ace16e0a7f 100644 --- a/esp-hal-common/src/clock/clocks_ll/esp32c3.rs +++ b/esp-hal-common/src/clock/clocks_ll/esp32c3.rs @@ -2,7 +2,6 @@ use crate::{ clock::{ApbClock, Clock, CpuClock, PllClock, XtalClock}, regi2c_write, regi2c_write_mask, - rom::{rom_i2c_writeReg, rom_i2c_writeReg_Mask}, }; extern "C" { diff --git a/esp-hal-common/src/rom/mod.rs b/esp-hal-common/src/rom/mod.rs index c38e7ee5fe0..c87c3cecbe1 100644 --- a/esp-hal-common/src/rom/mod.rs +++ b/esp-hal-common/src/rom/mod.rs @@ -25,11 +25,14 @@ extern "C" { macro_rules! regi2c_write { ( $block: ident, $reg_add: ident, $indata: expr ) => { paste::paste! { - rom_i2c_writeReg($block, - [<$block _HOSTID>], - $reg_add, - $indata - ); + unsafe { + crate::rom::rom_i2c_writeReg( + $block as u32, + [<$block _HOSTID>] as u32, + $reg_add as u32, + $indata as u32 + ) + } } }; } @@ -39,13 +42,16 @@ macro_rules! regi2c_write { macro_rules! regi2c_write_mask { ( $block: ident, $reg_add: ident, $indata: expr ) => { paste::paste! { - rom_i2c_writeReg_Mask($block, - [<$block _HOSTID>], - $reg_add, - [<$reg_add _MSB>], - [<$reg_add _LSB>], - $indata - ); + unsafe { + crate::rom::rom_i2c_writeReg_Mask( + $block as u32, + [<$block _HOSTID>] as u32, + $reg_add as u32, + [<$reg_add _MSB>] as u32, + [<$reg_add _LSB>] as u32, + $indata as u32 + ) + } } }; } diff --git a/esp-hal-common/src/rtc_cntl/rtc/esp32c2.rs b/esp-hal-common/src/rtc_cntl/rtc/esp32c2.rs index e764a651885..578f0b21fb0 100644 --- a/esp-hal-common/src/rtc_cntl/rtc/esp32c2.rs +++ b/esp-hal-common/src/rtc_cntl/rtc/esp32c2.rs @@ -4,7 +4,6 @@ use crate::{ clock::XtalClock, peripherals::{APB_CTRL, EXTMEM, RTC_CNTL, SPI0, SPI1, SYSTEM}, regi2c_write_mask, - rom::rom_i2c_writeReg_Mask, rtc_cntl::{RtcCalSel, RtcClock, RtcFastClock, RtcSlowClock}, }; diff --git a/esp-hal-common/src/rtc_cntl/rtc/esp32c3.rs b/esp-hal-common/src/rtc_cntl/rtc/esp32c3.rs index 84ab372b797..374e61ea453 100644 --- a/esp-hal-common/src/rtc_cntl/rtc/esp32c3.rs +++ b/esp-hal-common/src/rtc_cntl/rtc/esp32c3.rs @@ -4,7 +4,6 @@ use crate::{ clock::XtalClock, peripherals::{APB_CTRL, EXTMEM, RTC_CNTL, SPI0, SPI1, SYSTEM}, regi2c_write_mask, - rom::rom_i2c_writeReg_Mask, rtc_cntl::{RtcCalSel, RtcClock, RtcFastClock, RtcSlowClock}, }; diff --git a/esp-hal-common/src/soc/esp32s3/efuse.rs b/esp-hal-common/src/soc/esp32s3/efuse.rs index 300dff12555..9ced37d8ef8 100644 --- a/esp-hal-common/src/soc/esp32s3/efuse.rs +++ b/esp-hal-common/src/soc/esp32s3/efuse.rs @@ -1,7 +1,7 @@ //! Reading of eFuses -use crate::peripherals::EFUSE; pub use crate::soc::efuse_field::*; +use crate::{analog::adc::Attenuation, peripherals::EFUSE}; pub struct Efuse; @@ -36,6 +36,135 @@ 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/esp32s3/include/hal/efuse_ll.h#L65-L73 + // https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32s3/esp_efuse_table.csv#L196 + ( + 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/esp32s3/esp_efuse_rtc_calib.c#L15 + 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/esp32s3/esp_efuse_rtc_calib.c#L28 + pub fn get_rtc_calib_init_code(unit: u8, atten: Attenuation) -> Option { + let version = Self::get_rtc_calib_version(); + + if version != 1 { + return None; + } + + let adc_icode_diff: [u16; 4] = if unit == 0 { + [ + Self::read_field_le(ADC1_INIT_CODE_ATTEN0), + Self::read_field_le(ADC1_INIT_CODE_ATTEN1), + Self::read_field_le(ADC1_INIT_CODE_ATTEN2), + Self::read_field_le(ADC1_INIT_CODE_ATTEN3), + ] + } else { + [ + Self::read_field_le(ADC2_INIT_CODE_ATTEN0), + Self::read_field_le(ADC2_INIT_CODE_ATTEN1), + Self::read_field_le(ADC2_INIT_CODE_ATTEN2), + Self::read_field_le(ADC2_INIT_CODE_ATTEN3), + ] + }; + + // Version 1 logic for calculating ADC ICode based on EFUSE burnt value + + let mut adc_icode = [0; 4]; + if unit == 0 { + adc_icode[0] = adc_icode_diff[0] + 1850; + adc_icode[1] = adc_icode_diff[1] + adc_icode[0] + 90; + adc_icode[2] = adc_icode_diff[2] + adc_icode[1]; + adc_icode[3] = adc_icode_diff[3] + adc_icode[2] + 70; + } else { + adc_icode[0] = adc_icode_diff[0] + 2020; + adc_icode[1] = adc_icode_diff[1] + adc_icode[0]; + adc_icode[2] = adc_icode_diff[2] + adc_icode[1]; + adc_icode[3] = adc_icode_diff[3] + adc_icode[2]; + } + + Some( + adc_icode[match atten { + Attenuation::Attenuation0dB => 0, + Attenuation::Attenuation2p5dB => 1, + Attenuation::Attenuation6dB => 2, + Attenuation::Attenuation11dB => 3, + }], + ) + } + + /// Get ADC reference point voltage for specified attenuation in millivolts + /// + /// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32s3/esp_efuse_rtc_calib.c#L63 + pub fn get_rtc_calib_cal_mv(_unit: u8, _atten: Attenuation) -> u16 { + 850 + } + + /// Get ADC reference point digital code for specified attenuation + /// + /// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32s3/esp_efuse_rtc_calib.c#L63 + pub fn get_rtc_calib_cal_code(unit: u8, atten: Attenuation) -> Option { + let version = Self::get_rtc_calib_version(); + + if version != 1 { + return None; + } + + let adc_vol_diff: [u16; 8] = [ + Self::read_field_le(ADC1_CAL_VOL_ATTEN0), + Self::read_field_le(ADC1_CAL_VOL_ATTEN1), + Self::read_field_le(ADC1_CAL_VOL_ATTEN2), + Self::read_field_le(ADC1_CAL_VOL_ATTEN3), + Self::read_field_le(ADC2_CAL_VOL_ATTEN0), + Self::read_field_le(ADC2_CAL_VOL_ATTEN1), + Self::read_field_le(ADC2_CAL_VOL_ATTEN2), + Self::read_field_le(ADC2_CAL_VOL_ATTEN3), + ]; + + let mut adc1_vol = [0; 4]; + let mut adc2_vol = [0; 4]; + adc1_vol[3] = adc_vol_diff[3] + 900; + adc1_vol[2] = adc_vol_diff[2] + adc1_vol[3] + 800; + adc1_vol[1] = adc_vol_diff[1] + adc1_vol[2] + 700; + adc1_vol[0] = adc_vol_diff[0] + adc1_vol[1] + 800; + adc2_vol[3] = adc1_vol[3] - adc_vol_diff[7] + 15; + adc2_vol[2] = adc1_vol[2] - adc_vol_diff[6] + 20; + adc2_vol[1] = adc1_vol[1] - adc_vol_diff[5] + 10; + adc2_vol[0] = adc1_vol[0] - adc_vol_diff[4] + 40; + + let atten = match atten { + Attenuation::Attenuation0dB => 0, + Attenuation::Attenuation2p5dB => 1, + Attenuation::Attenuation6dB => 2, + Attenuation::Attenuation11dB => 3, + }; + + Some(if unit == 0 { + adc1_vol[atten] + } else { + adc2_vol[atten] + }) + } } #[derive(Copy, Clone)] diff --git a/esp32s3-hal/examples/adc_cal.rs b/esp32s3-hal/examples/adc_cal.rs new file mode 100644 index 00000000000..6df866081c8 --- /dev/null +++ b/esp32s3-hal/examples/adc_cal.rs @@ -0,0 +1,66 @@ +//! Connect a potentiometer to PIN3 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 esp32s3_hal::{ + adc::{self, 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(); + + let timer_group0 = TimerGroup::new( + peripherals.TIMG0, + &clocks, + &mut system.peripheral_clock_control, + ); + let mut wdt = timer_group0.wdt; + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + + // Disable MWDT and RWDT (Watchdog) flash boot protection + wdt.disable(); + rtc.rwdt.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + + // Create ADC instances + let analog = peripherals.SENS.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.gpio3.into_analog(), atten); + + let mut adc1 = ADC::::adc(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); + } +}