Skip to main content

esp_hal/analog/adc/calibration/line/
one_point.rs

1use core::marker::PhantomData;
2
3use super::AdcHasLineCal;
4use crate::analog::adc::{
5    AdcCalBasic,
6    AdcCalEfuse,
7    AdcCalScheme,
8    Attenuation,
9    CalibrationAccess,
10    Error,
11};
12
13/// Gain is stored as a `u32`, but is really a fixed-point number.
14const GAIN_SCALE: u32 = 1 << 16;
15
16/// Line fitting ADC calibration scheme.
17///
18/// This scheme implements gain correction based on reference points, and
19/// returns readings in mV.
20///
21/// A reference point is a pair of a reference voltage and the corresponding
22/// mean raw digital ADC value. Those values are stored in eFuse bit fields for each supported
23/// attenuation, and there is no way to establish them at runtime: the ADC can only be switched to
24/// internal ground, which gives the zero-voltage offset rather than a second point to derive a
25/// gain from.
26///
27/// This scheme also includes basic calibration ([`AdcCalBasic`]).
28///
29/// # Errors
30///
31/// Returns [`Error::UnsupportedAttenuation`] if the attenuation has no
32/// reference point on this chip, or [`Error::MissingCalibrationData`] if the
33/// chip carries no line-fitting calibration data or the stored reference
34/// point is zero.
35#[derive(Clone, Copy)]
36pub struct AdcCalLine<ADCX> {
37    basic: AdcCalBasic<ADCX>,
38
39    /// ADC gain.
40    ///
41    /// After being de-biased by the basic calibration, the reading is
42    /// multiplied by this value. Despite the type, it is a fixed-point
43    /// number with 16 fractional bits.
44    gain: u32,
45
46    _phantom: PhantomData<ADCX>,
47}
48
49impl<ADCX> crate::private::Sealed for AdcCalLine<ADCX> {}
50
51impl<ADCX> AdcCalScheme<ADCX> for AdcCalLine<ADCX>
52where
53    ADCX: AdcCalEfuse + AdcHasLineCal + CalibrationAccess,
54{
55    fn new_cal(atten: Attenuation) -> Result<Self, Error> {
56        Self::new_cal_with_channel(atten, 0)
57    }
58
59    fn new_cal_with_channel(atten: Attenuation, channel: u8) -> Result<Self, Error> {
60        let basic = AdcCalBasic::<ADCX>::new_cal_with_channel(atten, channel)?;
61
62        // Get the reference point (Dout, Vin) from efuse. Dout means mean raw ADC value when
63        // specified Vin applied to input.
64        // `cal_mv` goes first: it rejects attenuations without a reference point on the ESP32-C2.
65        let Some(mv) = ADCX::cal_mv(atten) else {
66            return Err(Error::UnsupportedAttenuation(atten));
67        };
68        let Some(code) = ADCX::cal_code(atten) else {
69            return Err(Error::MissingCalibrationData);
70        };
71
72        // A zero reference point would divide by zero below; it means the eFuse
73        // field was never burnt.
74        if code == 0 {
75            return Err(Error::MissingCalibrationData);
76        }
77
78        // Estimate the (assumed) linear relationship between the measured raw value and
79        // the voltage with the previously done measurement when the chip was
80        // manufactured.
81        //
82        // Note that the constant term is zero because the driver programs the
83        // zero-voltage bias for every conversion.
84        let gain = mv as u32 * GAIN_SCALE / code as u32;
85
86        Ok(Self {
87            basic,
88            gain,
89            _phantom: PhantomData,
90        })
91    }
92
93    fn adc_val(&self, val: u16) -> u16 {
94        let val = self.basic.adc_val(val);
95
96        (val as u32 * self.gain / GAIN_SCALE) as u16
97    }
98}