Skip to main content

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

1use core::marker::PhantomData;
2
3use super::AdcHasLineCal;
4use crate::{
5    analog::adc::{AdcCalScheme, Attenuation},
6    efuse,
7};
8
9const COEFF_A_SCALE: u32 = 65536;
10const COEFF_A_ROUND: u32 = COEFF_A_SCALE / 2;
11
12const VREF_MASK: u32 = 0x1F;
13const VREF_STEP_SIZE: i32 = 7;
14const VREF_OFFSET: i32 = 1100;
15
16const TP_LOW1_OFFSET: i32 = 278;
17const TP_LOW2_OFFSET: i32 = 421;
18const TP_LOW_MASK: u32 = 0x7F;
19const TP_LOW_VOLTAGE: u32 = 150;
20const TP_HIGH1_OFFSET: i32 = 3265;
21const TP_HIGH2_OFFSET: i32 = 3406;
22const TP_HIGH_MASK: u32 = 0x1FF;
23const TP_HIGH_VOLTAGE: u32 = 850;
24const TP_STEP_SIZE: i32 = 4;
25
26const LUT_VREF_LOW: i32 = 1000;
27const LUT_VREF_HIGH: i32 = 1200;
28const LUT_ADC_STEP_SIZE: u32 = 64;
29const LUT_POINTS: usize = 20;
30const LUT_LOW_THRESH: u32 = 2880;
31const LUT_HIGH_THRESH: u32 = LUT_LOW_THRESH + LUT_ADC_STEP_SIZE;
32const ADC_12_BIT_RES: u32 = 4096;
33
34const ADC1_TP_ATTEN_SCALE: [u32; 4] = [65504, 86975, 120389, 224310];
35const ADC2_TP_ATTEN_SCALE: [u32; 4] = [65467, 86861, 120416, 224708];
36const ADC1_TP_ATTEN_OFFSET: [u32; 4] = [0, 1, 27, 54];
37const ADC2_TP_ATTEN_OFFSET: [u32; 4] = [0, 9, 26, 66];
38
39const ADC1_VREF_ATTEN_SCALE: [u32; 4] = [57431, 76236, 105481, 196602];
40const ADC2_VREF_ATTEN_SCALE: [u32; 4] = [57236, 76175, 105678, 197170];
41const ADC1_VREF_ATTEN_OFFSET: [u32; 4] = [75, 78, 107, 142];
42const ADC2_VREF_ATTEN_OFFSET: [u32; 4] = [63, 66, 89, 128];
43
44const LUT_ADC1_LOW: [u32; LUT_POINTS] = [
45    2240, 2297, 2352, 2405, 2457, 2512, 2564, 2616, 2664, 2709, 2754, 2795, 2832, 2868, 2903, 2937,
46    2969, 3000, 3030, 3060,
47];
48const LUT_ADC1_HIGH: [u32; LUT_POINTS] = [
49    2667, 2706, 2745, 2780, 2813, 2844, 2873, 2901, 2928, 2956, 2982, 3006, 3032, 3059, 3084, 3110,
50    3135, 3160, 3184, 3209,
51];
52const LUT_ADC2_LOW: [u32; LUT_POINTS] = [
53    2238, 2293, 2347, 2399, 2451, 2507, 2561, 2613, 2662, 2710, 2754, 2792, 2831, 2869, 2904, 2937,
54    2968, 2999, 3029, 3059,
55];
56const LUT_ADC2_HIGH: [u32; LUT_POINTS] = [
57    2657, 2698, 2738, 2774, 2807, 2838, 2867, 2894, 2921, 2946, 2971, 2996, 3020, 3043, 3067, 3092,
58    3116, 3139, 3162, 3185,
59];
60
61/// Line fitting ADC calibration scheme
62///
63/// ESP32 uses two-point or Vref characterization from eFuse, and a lookup table
64/// at 11 dB for the non-linear region. Readings are in mV.
65///
66/// See <https://github.com/espressif/esp-idf/blob/027613140/components/esp_adc/esp32/adc_cali_line_fitting.c#L155>
67#[derive(Clone, Copy)]
68pub struct AdcCalLine<ADCX> {
69    coeff_a: u32,
70    coeff_b: u32,
71    vref: u32,
72    use_lut: bool,
73    unit: u8,
74    _phantom: PhantomData<ADCX>,
75}
76
77fn decode_bits(bits: u32, mask: u32, twos_compl: bool) -> i32 {
78    let sign_bit = !(mask >> 1) & mask;
79    if bits & sign_bit != 0 {
80        if twos_compl {
81            -(((!bits).wrapping_add(1) & (mask >> 1)) as i32)
82        } else {
83            -((bits & (mask >> 1)) as i32)
84        }
85    } else {
86        (bits & (mask >> 1)) as i32
87    }
88}
89
90fn read_efuse_vref() -> u32 {
91    let bits = efuse::read_field_le::<u32>(efuse::ADC_VREF);
92    (VREF_OFFSET + decode_bits(bits, VREF_MASK, false) * VREF_STEP_SIZE) as u32
93}
94
95fn check_efuse_vref() -> bool {
96    efuse::read_field_le::<u32>(efuse::ADC_VREF) != 0
97}
98
99fn check_efuse_tp() -> bool {
100    if !efuse::read_bit(efuse::BLK3_PART_RESERVE) {
101        return false;
102    }
103    efuse::read_field_le::<u32>(efuse::ADC1_TP_LOW) != 0
104        && efuse::read_field_le::<u32>(efuse::ADC2_TP_LOW) != 0
105        && efuse::read_field_le::<u32>(efuse::ADC1_TP_HIGH) != 0
106        && efuse::read_field_le::<u32>(efuse::ADC2_TP_HIGH) != 0
107}
108
109fn read_efuse_tp_low(unit: u8) -> u32 {
110    let (high_offset, efuse) = if unit == 0 {
111        (TP_LOW1_OFFSET, efuse::ADC1_TP_LOW)
112    } else {
113        (TP_LOW2_OFFSET, efuse::ADC2_TP_LOW)
114    };
115
116    let efuse_value = decode_bits(efuse::read_field_le::<u32>(efuse), TP_LOW_MASK, true);
117    (high_offset + efuse_value * TP_STEP_SIZE) as u32
118}
119
120fn read_efuse_tp_high(unit: u8) -> u32 {
121    let (high_offset, efuse) = if unit == 0 {
122        (TP_HIGH1_OFFSET, efuse::ADC1_TP_HIGH)
123    } else {
124        (TP_HIGH2_OFFSET, efuse::ADC2_TP_HIGH)
125    };
126
127    let efuse_value = decode_bits(efuse::read_field_le::<u32>(efuse), TP_HIGH_MASK, true);
128    (high_offset + efuse_value * TP_STEP_SIZE) as u32
129}
130
131fn characterize_using_two_point(unit: u8, atten: usize, high: u32, low: u32) -> (u32, u32) {
132    let (scales, offsets) = if unit == 0 {
133        (&ADC1_TP_ATTEN_SCALE, &ADC1_TP_ATTEN_OFFSET)
134    } else {
135        (&ADC2_TP_ATTEN_SCALE, &ADC2_TP_ATTEN_OFFSET)
136    };
137    let delta_x = high - low;
138    let delta_v = TP_HIGH_VOLTAGE - TP_LOW_VOLTAGE;
139    let coeff_a = (delta_v * scales[atten] + (delta_x / 2)) / delta_x;
140    let coeff_b = TP_HIGH_VOLTAGE - ((delta_v * high + (delta_x / 2)) / delta_x) + offsets[atten];
141    (coeff_a, coeff_b)
142}
143
144fn characterize_using_vref(unit: u8, atten: usize, vref: u32) -> (u32, u32) {
145    let (scales, offsets) = if unit == 0 {
146        (&ADC1_VREF_ATTEN_SCALE, &ADC1_VREF_ATTEN_OFFSET)
147    } else {
148        (&ADC2_VREF_ATTEN_SCALE, &ADC2_VREF_ATTEN_OFFSET)
149    };
150    let coeff_a = (vref * scales[atten]) / ADC_12_BIT_RES;
151    (coeff_a, offsets[atten])
152}
153
154fn calculate_voltage_linear(adc: u32, coeff_a: u32, coeff_b: u32) -> u32 {
155    ((coeff_a * adc + COEFF_A_ROUND) / COEFF_A_SCALE) + coeff_b
156}
157
158fn interpolate_two_points(y1: u32, y2: u32, x_step: u32, x: u32) -> u32 {
159    ((y1 * x_step) + (y2 * x) - (y1 * x) + (x_step / 2)) / x_step
160}
161
162fn calculate_voltage_lut(
163    adc: u32,
164    vref: u32,
165    low: &[u32; LUT_POINTS],
166    high: &[u32; LUT_POINTS],
167) -> u32 {
168    let i = ((adc - LUT_LOW_THRESH) / LUT_ADC_STEP_SIZE) as usize;
169    let i = i.min(LUT_POINTS - 2);
170
171    let x2dist = LUT_VREF_HIGH - vref as i32;
172    let x1dist = vref as i32 - LUT_VREF_LOW;
173    let y2dist = ((i as u32 + 1) * LUT_ADC_STEP_SIZE + LUT_LOW_THRESH) as i32 - adc as i32;
174    let y1dist = adc as i32 - (i as u32 * LUT_ADC_STEP_SIZE + LUT_LOW_THRESH) as i32;
175
176    let q11 = low[i] as i32;
177    let q12 = low[i + 1] as i32;
178    let q21 = high[i] as i32;
179    let q22 = high[i + 1] as i32;
180
181    let mut voltage = (q11 * x2dist * y2dist)
182        + (q21 * x1dist * y2dist)
183        + (q12 * x2dist * y1dist)
184        + (q22 * x1dist * y1dist);
185    voltage += ((LUT_VREF_HIGH - LUT_VREF_LOW) * LUT_ADC_STEP_SIZE as i32) / 2;
186    voltage /= (LUT_VREF_HIGH - LUT_VREF_LOW) * LUT_ADC_STEP_SIZE as i32;
187    voltage as u32
188}
189
190impl<ADCX> crate::private::Sealed for AdcCalLine<ADCX> {}
191
192impl<ADCX> AdcCalScheme<ADCX> for AdcCalLine<ADCX>
193where
194    ADCX: AdcHasLineCal,
195{
196    fn new_cal(atten: Attenuation) -> Self {
197        let unit = ADCX::UNIT;
198        let atten_idx = atten as usize;
199
200        let vref = if check_efuse_vref() {
201            read_efuse_vref()
202        } else {
203            1100
204        };
205
206        let (coeff_a, coeff_b) = if check_efuse_tp() {
207            let high = read_efuse_tp_high(unit);
208            let low = read_efuse_tp_low(unit);
209            characterize_using_two_point(unit, atten_idx, high, low)
210        } else {
211            characterize_using_vref(unit, atten_idx, vref)
212        };
213
214        Self {
215            coeff_a,
216            coeff_b,
217            vref,
218            use_lut: atten == Attenuation::_11dB,
219            unit,
220            _phantom: PhantomData,
221        }
222    }
223
224    fn adc_val(&self, val: u16) -> u16 {
225        let raw = val as u32;
226
227        if self.use_lut && raw >= LUT_LOW_THRESH {
228            let (low, high) = if self.unit == 0 {
229                (&LUT_ADC1_LOW, &LUT_ADC1_HIGH)
230            } else {
231                (&LUT_ADC2_LOW, &LUT_ADC2_HIGH)
232            };
233            let lut_voltage = calculate_voltage_lut(raw, self.vref, low, high);
234            if raw <= LUT_HIGH_THRESH {
235                let linear = calculate_voltage_linear(raw, self.coeff_a, self.coeff_b);
236                interpolate_two_points(linear, lut_voltage, LUT_ADC_STEP_SIZE, raw - LUT_LOW_THRESH)
237                    as u16
238            } else {
239                lut_voltage as u16
240            }
241        } else {
242            calculate_voltage_linear(raw, self.coeff_a, self.coeff_b) as u16
243        }
244    }
245}