Skip to main content

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

1use core::marker::PhantomData;
2
3use super::AdcHasLineCal;
4use crate::analog::adc::{AdcCalBasic, AdcCalEfuse, AdcCalScheme, Attenuation, CalibrationAccess};
5
6const COEFF_A_SCALING: i64 = 65536;
7const COEFF_B_SCALING: i64 = 1024;
8const V_HIGH: [i64; 4] = [600, 800, 1000, 2000];
9const V_LOW: i64 = 250;
10
11/// Line fitting ADC calibration scheme
12///
13/// ESP32-S2 uses two-point characterization (eFuse calibration v1) or
14/// one-point characterization (v2). Readings are in mV.
15///
16/// This scheme also includes basic calibration ([`AdcCalBasic`]).
17///
18/// See <https://github.com/espressif/esp-idf/blob/027613140/components/esp_adc/esp32s2/adc_cali_line_fitting.c#L87>
19#[derive(Clone, Copy)]
20pub struct AdcCalLine<ADCX> {
21    basic: AdcCalBasic<ADCX>,
22    coeff_a: u32,
23    coeff_b: i32,
24    _phantom: PhantomData<ADCX>,
25}
26
27fn characterize_two_point(atten: Attenuation, high: i64, low: i64) -> (u32, i32) {
28    let v_high = V_HIGH[atten as usize];
29    let denom = (high - low).max(1);
30    let coeff_a = (COEFF_A_SCALING * (v_high - V_LOW) / denom) as u32;
31    let coeff_b = (COEFF_B_SCALING * (V_LOW * high - v_high * low) / denom) as i32;
32    (coeff_a, coeff_b)
33}
34
35impl<ADCX> crate::private::Sealed for AdcCalLine<ADCX> {}
36
37impl<ADCX> AdcCalScheme<ADCX> for AdcCalLine<ADCX>
38where
39    ADCX: AdcCalEfuse + AdcHasLineCal + CalibrationAccess,
40{
41    fn new_cal(atten: Attenuation) -> Self {
42        Self::new_cal_with_channel(atten, 0)
43    }
44
45    fn new_cal_with_channel(atten: Attenuation, channel: u8) -> Self {
46        let basic = AdcCalBasic::<ADCX>::new_cal_with_channel(atten, channel);
47        let version = crate::efuse::rtc_calib_version();
48
49        let (coeff_a, coeff_b) = match version {
50            1 => {
51                let low = crate::efuse::rtc_calib_reading(
52                    version,
53                    ADCX::UNIT,
54                    atten,
55                    crate::efuse::RtcCalibParam::Vlow,
56                );
57                let high = crate::efuse::rtc_calib_reading(
58                    version,
59                    ADCX::UNIT,
60                    atten,
61                    crate::efuse::RtcCalibParam::Vhigh,
62                );
63                characterize_two_point(atten, high as i64, low as i64)
64            }
65            2 => {
66                let high = ADCX::cal_code(atten).unwrap_or(1).max(1) as i64;
67                let mv = ADCX::cal_mv(atten) as i64;
68                (((COEFF_A_SCALING * mv) / high) as u32, 0)
69            }
70            _ => {
71                let low = crate::efuse::rtc_calib_reading_inner(
72                    1,
73                    ADCX::UNIT,
74                    atten,
75                    crate::efuse::RtcCalibParam::Vlow,
76                    true,
77                );
78                let high = crate::efuse::rtc_calib_reading_inner(
79                    1,
80                    ADCX::UNIT,
81                    atten,
82                    crate::efuse::RtcCalibParam::Vhigh,
83                    true,
84                );
85                characterize_two_point(atten, high as i64, low as i64)
86            }
87        };
88
89        Self {
90            basic,
91            coeff_a,
92            coeff_b,
93            _phantom: PhantomData,
94        }
95    }
96
97    fn adc_cal(&self) -> u16 {
98        self.basic.adc_cal()
99    }
100
101    fn adc_val(&self, val: u16) -> u16 {
102        let val = self.basic.adc_val(val) as i64;
103        let voltage = (val * self.coeff_a as i64 / (COEFF_A_SCALING / COEFF_B_SCALING)
104            + self.coeff_b as i64)
105            / COEFF_B_SCALING;
106        voltage.clamp(0, u16::MAX as i64) as u16
107    }
108}