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}