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