mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-30 13:50:38 +00:00
ADC raw values calibration (#555)
* adc_cal: c2: Add efuse functions for reading calibration * adc_cal: c3: Add efuse functions for reading calibration * adc_cal: c6: Add efuse functions for reading calibration * adc_cal: Add extra traits to support calibration - `AdcCalScheme<ADCI>` implemented for each calibration scheme (basic, linear, curved) - `AdcCalEfuse` implemented for each ADC unit to get calibration data from efuse bits * adc_cal: Add basic ADC calibration scheme Basic calibration is related to setting some initial bias value to ADC unit. Such values usually is stored in efuse bit fields but also can be measured in runtime by connecting ADC input to ground internally. * adc_cal: Add line fitting ADC calibration scheme This scheme also includes basic calibration and implements gain correction based on reference point. Reference point is a pair of reference voltage and corresponding mean raw ADC value. Such raw values usually is stored in efuse bit fields for each supported attenuation. Possibly it also can be measured in runtime by connecting ADC to reference voltage internally. * adc_cal: Add curve fitting ADC calibration scheme This scheme also includes basic and linear and implements final polynomial error correction. * adc_cal: riscv: Add ADC calibration implementation for riscv chips * adc_cal: c2: Add calibrated ADC reading example This example uses line fitting calibration scheme by default. It periodically prints both raw measured value and computed millivolts. * adc_cal: c3: Add calibrated ADC reading example This example uses curve fitting calibration scheme by default. It periodically prints both raw measured value and computed millivolts. * adc_cal: c6: Add calibrated ADC reading example This example uses curve fitting calibration scheme by default. It periodically prints both raw measured value and computed millivolts. * adc_cal: riscv: Add changelog entry for ADC calibration
This commit is contained in:
parent
996da27f30
commit
74438fcec5
@ -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
|
||||
|
||||
|
40
esp-hal-common/src/analog/adc/cal_basic.rs
Normal file
40
esp-hal-common/src/analog/adc/cal_basic.rs
Normal file
@ -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<ADCI> {
|
||||
/// Calibration value to set to ADC unit
|
||||
cal_val: u16,
|
||||
|
||||
_phantom: PhantomData<ADCI>,
|
||||
}
|
||||
|
||||
impl<ADCI> AdcCalScheme<ADCI> for AdcCalBasic<ADCI>
|
||||
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::<ADCI>::adc_calibrate(atten, AdcCalSource::Gnd)
|
||||
});
|
||||
|
||||
Self {
|
||||
cal_val,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn adc_cal(&self) -> u16 {
|
||||
self.cal_val
|
||||
}
|
||||
}
|
240
esp-hal-common/src/analog/adc/cal_curve.rs
Normal file
240
esp-hal-common/src/analog/adc/cal_curve.rs
Normal file
@ -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<ADCI> {
|
||||
line: AdcCalLine<ADCI>,
|
||||
|
||||
/// Coefficients for each term (3..=5)
|
||||
coeff: &'static [CurveCoeff],
|
||||
|
||||
_phantom: PhantomData<ADCI>,
|
||||
}
|
||||
|
||||
impl<ADCI> AdcCalScheme<ADCI> for AdcCalCurve<ADCI>
|
||||
where
|
||||
ADCI: AdcCalEfuse + AdcHasLineCal + AdcHasCurveCal + RegisterAccess,
|
||||
{
|
||||
fn new_cal(atten: Attenuation) -> Self {
|
||||
let line = AdcCalLine::<ADCI>::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,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
100
esp-hal-common/src/analog/adc/cal_line.rs
Normal file
100
esp-hal-common/src/analog/adc/cal_line.rs
Normal file
@ -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<ADCI> {
|
||||
basic: AdcCalBasic<ADCI>,
|
||||
|
||||
/// Gain of ADC-value
|
||||
gain: u32,
|
||||
|
||||
_phantom: PhantomData<ADCI>,
|
||||
}
|
||||
|
||||
impl<ADCI> AdcCalScheme<ADCI> for AdcCalLine<ADCI>
|
||||
where
|
||||
ADCI: AdcCalEfuse + AdcHasLineCal + RegisterAccess,
|
||||
{
|
||||
fn new_cal(atten: Attenuation) -> Self {
|
||||
let basic = AdcCalBasic::<ADCI>::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::<ADCI>::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 {}
|
@ -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<PIN, ADCI> {
|
||||
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<PIN, ADCI, CS = ()> {
|
||||
pub pin: PIN,
|
||||
pub cal_scheme: CS,
|
||||
_phantom: PhantomData<ADCI>,
|
||||
}
|
||||
|
||||
impl<PIN: Channel<ADCI, ID = u8>, ADCI> Channel<ADCI> for AdcPin<PIN, ADCI> {
|
||||
impl<PIN: Channel<ADCI, ID = u8>, ADCI, CS> Channel<ADCI> for AdcPin<PIN, ADCI, CS> {
|
||||
type ID = u8;
|
||||
|
||||
fn channel() -> Self::ID {
|
||||
@ -57,14 +192,69 @@ where
|
||||
&mut self,
|
||||
pin: PIN,
|
||||
attenuation: Attenuation,
|
||||
) -> AdcPin<PIN, ADCI> {
|
||||
) -> AdcPin<PIN, ADCI, ()> {
|
||||
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<PIN: Channel<ADCI, ID = u8>, CS: AdcCalScheme<ADCI>>(
|
||||
&mut self,
|
||||
pin: PIN,
|
||||
attenuation: Attenuation,
|
||||
) -> AdcPin<PIN, ADCI, CS> {
|
||||
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<ADCI> Default for AdcConfig<ADCI> {
|
||||
@ -77,19 +267,40 @@ impl<ADCI> Default for AdcConfig<ADCI> {
|
||||
}
|
||||
}
|
||||
|
||||
#[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<ADCI, WORD, AdcPin<PIN, ADCI>> for ADC<'d, ADCI>
|
||||
#[cfg(any(esp32c2, esp32c3, esp32c6))]
|
||||
impl AdcCalEfuse for ADC1 {
|
||||
fn get_init_code(atten: Attenuation) -> Option<u16> {
|
||||
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<u16> {
|
||||
Efuse::get_rtc_calib_cal_code(1, atten)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(esp32c3)]
|
||||
impl AdcCalEfuse for ADC2 {
|
||||
fn get_init_code(atten: Attenuation) -> Option<u16> {
|
||||
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<u16> {
|
||||
Efuse::get_rtc_calib_cal_code(2, atten)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, ADCI, WORD, PIN, CS> OneShot<ADCI, WORD, AdcPin<PIN, ADCI, CS>> for ADC<'d, ADCI>
|
||||
where
|
||||
WORD: From<u16>,
|
||||
PIN: Channel<ADCI, ID = u8>,
|
||||
ADCI: RegisterAccess,
|
||||
CS: AdcCalScheme<ADCI>,
|
||||
{
|
||||
type Error = ();
|
||||
|
||||
fn read(&mut self, _pin: &mut AdcPin<PIN, ADCI>) -> nb::Result<WORD, Self::Error> {
|
||||
fn read(&mut self, pin: &mut AdcPin<PIN, ADCI, CS>) -> nb::Result<WORD, Self::Error> {
|
||||
if self.attenuations[AdcPin::<PIN, ADCI>::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::<PIN, ADCI>::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
|
||||
|
@ -5,6 +5,46 @@ pub mod adc;
|
||||
#[cfg(dac)]
|
||||
pub mod dac;
|
||||
|
||||
/// A helper trait to do calibrated samples fitting
|
||||
pub trait AdcCalScheme<ADCI>: 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<ADCI> AdcCalScheme<ADCI> 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<u16>;
|
||||
|
||||
/// 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<u16>;
|
||||
}
|
||||
|
||||
pub struct ADC1 {
|
||||
_private: (),
|
||||
}
|
||||
|
@ -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::<u8>(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::<u8>(BLK_VERSION_MAJOR),
|
||||
Self::read_field_le::<u8>(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<u16> {
|
||||
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<u16> {
|
||||
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)]
|
||||
|
@ -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::<u8>(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::<u8>(BLK_VERSION_MAJOR),
|
||||
Self::read_field_le::<u8>(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<u16> {
|
||||
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<u16> {
|
||||
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)]
|
||||
|
@ -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::<u8>(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::<u8>(BLK_VERSION_MAJOR),
|
||||
Self::read_field_le::<u8>(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<u16> {
|
||||
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<u16> {
|
||||
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)]
|
||||
|
73
esp32c2-hal/examples/adc_cal.rs
Normal file
73
esp32c2-hal/examples/adc_cal.rs
Normal file
@ -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<ADC1>;
|
||||
type AdcCal = adc::AdcCalLine<ADC1>;
|
||||
|
||||
let mut pin = adc1_config.enable_pin_with_cal::<_, AdcCal>(io.pins.gpio2.into_analog(), atten);
|
||||
|
||||
let mut adc1 = ADC::<ADC1>::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);
|
||||
}
|
||||
}
|
81
esp32c3-hal/examples/adc_cal.rs
Normal file
81
esp32c3-hal/examples/adc_cal.rs
Normal file
@ -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<ADC1>;
|
||||
// type AdcCal = adc::AdcCalLine<ADC1>;
|
||||
type AdcCal = adc::AdcCalCurve<ADC1>;
|
||||
|
||||
let mut pin = adc1_config.enable_pin_with_cal::<_, AdcCal>(io.pins.gpio2.into_analog(), atten);
|
||||
|
||||
let mut adc1 = ADC::<ADC1>::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);
|
||||
}
|
||||
}
|
81
esp32c6-hal/examples/adc_cal.rs
Normal file
81
esp32c6-hal/examples/adc_cal.rs
Normal file
@ -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<ADC1>;
|
||||
// type AdcCal = adc::AdcCalLine<ADC1>;
|
||||
type AdcCal = adc::AdcCalCurve<ADC1>;
|
||||
|
||||
let mut pin = adc1_config.enable_pin_with_cal::<_, AdcCal>(io.pins.gpio2.into_analog(), atten);
|
||||
|
||||
let mut adc1 = ADC::<ADC1>::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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user