Skip to main content

esp_hal/analog/adc/calibration/
curve.rs

1use core::marker::PhantomData;
2
3use crate::analog::adc::{
4    AdcCalEfuse,
5    AdcCalLine,
6    AdcCalScheme,
7    AdcHasLineCal,
8    Attenuation,
9    CalibrationAccess,
10};
11
12const COEFF_MUL: i64 = 1 << 52;
13
14/// Integer type for the error polynomial's coefficients. Despite
15/// the type, this is a fixed-point number with 52 fractional bits.
16type CurveCoeff = i64;
17
18/// Polynomial coefficients for specified attenuation.
19pub struct CurveCoeffs {
20    /// Attenuation
21    atten: Attenuation,
22    /// Polynomial coefficients
23    coeff: &'static [CurveCoeff],
24}
25
26type CurvesCoeffs = &'static [CurveCoeffs];
27
28/// Marker trait for ADC which support curve fitting
29///
30/// See also [`AdcCalCurve`].
31pub trait AdcHasCurveCal {
32    /// Coefficients for calculating the reading voltage error.
33    ///
34    /// A sets of coefficients for each attenuation.
35    const CURVES_COEFFS: CurvesCoeffs;
36}
37
38/// Curve fitting ADC calibration scheme
39///
40/// This scheme implements polynomial error correction using predefined
41/// coefficient sets for each attenuation. It returns readings in mV.
42///
43/// This scheme also includes basic calibration ([`super::AdcCalBasic`]) and
44/// line fitting ([`AdcCalLine`]).
45#[derive(Clone, Copy)]
46pub struct AdcCalCurve<ADCI> {
47    line: AdcCalLine<ADCI>,
48
49    /// Coefficients of the error estimation polynomial.
50    ///
51    /// The constant coefficient comes first; the error polynomial is
52    /// `coeff[0] + coeff[1] * x + ... + coeff[n] * x^n`.
53    ///
54    /// This calibration works by first applying linear calibration. Then
55    /// the error polynomial is applied to the output of linear calibration.
56    /// The output of the polynomial is our estimate of the error; it gets
57    /// subtracted from linear calibration's output to get the final reading.
58    coeff: &'static [CurveCoeff],
59
60    _phantom: PhantomData<ADCI>,
61}
62
63impl<ADCI> crate::private::Sealed for AdcCalCurve<ADCI> {}
64
65impl<ADCI> AdcCalScheme<ADCI> for AdcCalCurve<ADCI>
66where
67    ADCI: AdcCalEfuse + AdcHasLineCal + AdcHasCurveCal + CalibrationAccess,
68{
69    fn new_cal(atten: Attenuation) -> Self {
70        let line = AdcCalLine::<ADCI>::new_cal(atten);
71
72        let coeff = ADCI::CURVES_COEFFS
73            .iter()
74            .find(|item| item.atten == atten)
75            .expect("No curve coefficients for given attenuation")
76            .coeff;
77
78        Self {
79            line,
80            coeff,
81            _phantom: PhantomData,
82        }
83    }
84
85    fn adc_cal(&self) -> u16 {
86        self.line.adc_cal()
87    }
88
89    fn adc_val(&self, val: u16) -> u16 {
90        let val = self.line.adc_val(val);
91
92        // Calculate polynomial error using Horner's method to prevent overflow.
93        // Horner's evaluates: err = coeff[0] + val*(coeff[1] + val*(coeff[2] + ...))
94        // This avoids computing val^n which causes overflow when multiplied by coefficients.
95        let err = if val == 0 || self.coeff.is_empty() {
96            0
97        } else {
98            let val_i64 = val as i64;
99            let mut poly = 0i64;
100
101            // Iterate coefficients in reverse order for Horner's method
102            for &coeff in self.coeff.iter().rev() {
103                poly = poly * val_i64 + coeff;
104            }
105
106            (poly / COEFF_MUL) as i32
107        };
108
109        (val as i32 - err) as u16
110    }
111}
112
113macro_rules! coeff_tables {
114    ($($(#[$($meta:meta)*])* $name:ident [ $($att:ident => [ $($val:literal,)* ],)* ];)*) => {
115        $(
116            $(#[$($meta)*])*
117            const $name: CurvesCoeffs = &[
118                $(CurveCoeffs {
119                    atten: Attenuation::$att,
120                    coeff: &[
121                        $(($val as f64 * COEFF_MUL as f64) as CurveCoeff,)*
122                    ],
123                },)*
124            ];
125        )*
126    };
127}
128
129#[cfg(any(esp32c3, esp32c5, esp32c6, esp32h2, esp32s3))]
130mod impls {
131    use super::*;
132
133    impl AdcHasCurveCal for crate::peripherals::ADC1<'_> {
134        const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS1;
135    }
136
137    #[cfg(esp32c3)]
138    impl AdcHasCurveCal for crate::peripherals::ADC2<'_> {
139        const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS1;
140    }
141
142    #[cfg(esp32s3)]
143    impl AdcHasCurveCal for crate::peripherals::ADC2<'_> {
144        const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS2;
145    }
146
147    coeff_tables! {
148        /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32c3/curve_fitting_coefficients.c>
149        #[cfg(esp32c3)]
150        CURVES_COEFFS1 [
151            _0dB => [
152                -0.225966470500043,
153                -0.0007265418501948,
154                0.0000109410402681,
155            ],
156            _2p5dB => [
157                0.4229623392600516,
158                -0.0000731527490903,
159                0.0000088166562521,
160            ],
161            _6dB => [
162                -1.017859239236435,
163                -0.0097159265299153,
164                0.0000149794028038,
165            ],
166            _11dB => [
167                -1.4912262772850453,
168                -0.0228549975564099,
169                0.0000356391935717,
170                -0.0000000179964582,
171                0.0000000000042046,
172            ],
173        ];
174
175        /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/c602e55/components/esp_adc/esp32c5/curve_fitting_coefficients.c>
176        #[cfg(esp32c5)]
177        CURVES_COEFFS1 [
178            _0dB => [
179                -0.2941017829027464,
180                0.0007368674918527,
181                0.0,
182            ],
183            _2p5dB => [
184                -0.3224276125615327,
185                0.0005325658467636,
186                0.0,
187            ],
188            _6dB => [
189                -0.3307554632960901,
190                0.000409244304226,
191                0.0,
192            ],
193            _11dB => [
194                1.463642578413965,
195                -0.003349642363147,
196                0.0000011676836451,
197            ],
198        ];
199
200
201        /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32c6/curve_fitting_coefficients.c>
202        #[cfg(esp32c6)]
203        CURVES_COEFFS1 [
204            _0dB => [
205                -0.0487166399931449,
206                0.0006436483033201,
207                0.0000030410131806,
208            ],
209            _2p5dB => [
210                -0.8665498165817785,
211                0.0015239070452946,
212                0.0000013818878844,
213            ],
214            _6dB => [
215                -1.2277821756674387,
216                0.0022275554717885,
217                0.0000005924302667,
218            ],
219            _11dB => [
220                -0.3801417550380255,
221                -0.0006020352420772,
222                0.0000012442478488,
223            ],
224        ];
225
226        /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/465b159cd8771ffab6be70c7675ecf6705b62649/components/esp_adc/esp32h2/curve_fitting_coefficients.c>
227        #[cfg(esp32h2)]
228        CURVES_COEFFS1 [
229            _0dB => [
230                -0.5081991760658888,
231                0.0000007858995319,
232                0,
233            ],
234            _2p5dB => [
235                -0.8359230818901277,
236                0.0000009025419089,
237                0,
238            ],
239            _6dB => [
240                -1.165668771581976,
241                0.0000008294679249,
242                0,
243            ],
244            _11dB => [
245                -0.3637329628677273,
246                -0.0000196072597389,
247                0.0000007871689227,
248            ],
249        ];
250
251        /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32s3/curve_fitting_coefficients.c>
252        #[cfg(esp32s3)]
253        CURVES_COEFFS1 [
254            _0dB => [
255                -2.7856531419538344,
256                -0.0050871540569528,
257                0.0000097982495890,
258            ],
259            _2p5dB => [
260                -2.9831022915028695,
261                -0.0049393185868806,
262                0.0000101379430548,
263            ],
264            _6dB => [
265                -2.3285545746296417,
266                -0.0147640181047414,
267                0.0000208385525314,
268            ],
269            _11dB => [
270                -0.644403418269478,
271                -0.0644334888647536,
272                0.0001297891447611,
273                -0.0000000707697180,
274                0.0000000000135150,
275            ],
276        ];
277
278        /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32s3/curve_fitting_coefficients.c>
279        #[cfg(esp32s3)]
280        CURVES_COEFFS2 [
281            _0dB => [
282                -2.5668651654328927,
283                0.0001353548869615,
284                0.0000036615265189,
285            ],
286            _2p5dB => [
287                -2.3690184690298404,
288                -0.0066319894226185,
289                0.0000118964995959,
290            ],
291            _6dB => [
292                -0.9452499397020617,
293                -0.0200996773954387,
294                0.00000259011467956,
295            ],
296            _11dB => [
297                1.2247719764336924,
298                -0.0755717904943462,
299                0.0001478791187119,
300                -0.0000000796725280,
301                0.0000000000150380,
302            ],
303        ];
304    }
305}