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    AdcCalSource,
9    AdcConfig,
10    Attenuation,
11    CalibrationAccess,
12};
13
14/// We store the gain as a u32, but it's really a fixed-point number.
15const GAIN_SCALE: u32 = 1 << 16;
16
17/// Line fitting ADC calibration scheme
18///
19/// This scheme implements gain correction based on reference points, and
20/// returns readings in mV.
21///
22/// A reference point is a pair of a reference voltage and the corresponding
23/// mean raw digital ADC value. Such values are usually stored in efuse bit
24/// fields for each supported attenuation.
25///
26/// Also it can be measured in runtime by connecting ADC to reference voltage
27/// internally but this method is not so good because actual reference voltage
28/// may varies in range 1.0..=1.2 V. Currently this method is used as a fallback
29/// (with 1.1 V by default) when calibration data is missing.
30///
31/// This scheme also includes basic calibration ([`AdcCalBasic`]).
32#[derive(Clone, Copy)]
33pub struct AdcCalLine<ADCX> {
34    basic: AdcCalBasic<ADCX>,
35
36    /// ADC gain.
37    ///
38    /// After being de-biased by the basic calibration, the reading is
39    /// multiplied by this value. Despite the type, it is a fixed-point
40    /// number with 16 fractional bits.
41    gain: u32,
42
43    _phantom: PhantomData<ADCX>,
44}
45
46impl<ADCX> crate::private::Sealed for AdcCalLine<ADCX> {}
47
48impl<ADCX> AdcCalScheme<ADCX> for AdcCalLine<ADCX>
49where
50    ADCX: AdcCalEfuse + AdcHasLineCal + CalibrationAccess,
51{
52    fn new_cal(atten: Attenuation) -> Self {
53        Self::new_cal_with_channel(atten, 0)
54    }
55
56    fn new_cal_with_channel(atten: Attenuation, channel: u8) -> Self {
57        let basic = AdcCalBasic::<ADCX>::new_cal_with_channel(atten, channel);
58
59        // Try get the reference point (Dout, Vin) from efuse
60        // Dout means mean raw ADC value when specified Vin applied to input.
61        let (code, mv) = ADCX::cal_code(atten)
62            .map(|code| (code, ADCX::cal_mv(atten)))
63            .unwrap_or_else(|| {
64                // As a fallback try to calibrate using reference voltage source.
65                // This method is not too good because actual reference voltage may varies
66                // in range 1000..=1200 mV and this value currently cannot be read from efuse.
67                (
68                    AdcConfig::<ADCX>::adc_calibrate(atten, AdcCalSource::Ref),
69                    1100, // use 1100 mV as a middle of typical reference voltage range
70                )
71            });
72
73        // Estimate the (assumed) linear relationship between the measured raw value and
74        // the voltage with the previously done measurement when the chip was
75        // manufactured.
76        //
77        // Note that the constant term is zero because the basic calibration takes care
78        // of it already.
79        let gain = mv as u32 * GAIN_SCALE / code as u32;
80
81        Self {
82            basic,
83            gain,
84            _phantom: PhantomData,
85        }
86    }
87
88    fn adc_cal(&self) -> u16 {
89        self.basic.adc_cal()
90    }
91
92    fn adc_val(&self, val: u16) -> u16 {
93        let val = self.basic.adc_val(val);
94
95        (val as u32 * self.gain / GAIN_SCALE) as u16
96    }
97}