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}