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, 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/903af13e8/components/esp_adc/esp32c6/curve_fitting_coefficients.c>
176        #[cfg(esp32c6)]
177        CURVES_COEFFS1 [
178            _0dB => [
179                -0.0487166399931449,
180                0.0006436483033201,
181                0.0000030410131806,
182            ],
183            _2p5dB => [
184                -0.8665498165817785,
185                0.0015239070452946,
186                0.0000013818878844,
187            ],
188            _6dB => [
189                -1.2277821756674387,
190                0.0022275554717885,
191                0.0000005924302667,
192            ],
193            _11dB => [
194                -0.3801417550380255,
195                -0.0006020352420772,
196                0.0000012442478488,
197            ],
198        ];
199
200        /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/465b159cd8771ffab6be70c7675ecf6705b62649/components/esp_adc/esp32h2/curve_fitting_coefficients.c>
201        #[cfg(esp32h2)]
202        CURVES_COEFFS1 [
203            _0dB => [
204                -0.5081991760658888,
205                0.0000007858995319,
206                0,
207            ],
208            _2p5dB => [
209                -0.8359230818901277,
210                0.0000009025419089,
211                0,
212            ],
213            _6dB => [
214                -1.165668771581976,
215                0.0000008294679249,
216                0,
217            ],
218            _11dB => [
219                -0.3637329628677273,
220                -0.0000196072597389,
221                0.0000007871689227,
222            ],
223        ];
224
225        /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32s3/curve_fitting_coefficients.c>
226        #[cfg(esp32s3)]
227        CURVES_COEFFS1 [
228            _0dB => [
229                -2.7856531419538344,
230                -0.0050871540569528,
231                0.0000097982495890,
232            ],
233            _2p5dB => [
234                -2.9831022915028695,
235                -0.0049393185868806,
236                0.0000101379430548,
237            ],
238            _6dB => [
239                -2.3285545746296417,
240                -0.0147640181047414,
241                0.0000208385525314,
242            ],
243            _11dB => [
244                -0.644403418269478,
245                -0.0644334888647536,
246                0.0001297891447611,
247                -0.0000000707697180,
248                0.0000000000135150,
249            ],
250        ];
251
252        /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32s3/curve_fitting_coefficients.c>
253        #[cfg(esp32s3)]
254        CURVES_COEFFS2 [
255            _0dB => [
256                -2.5668651654328927,
257                0.0001353548869615,
258                0.0000036615265189,
259            ],
260            _2p5dB => [
261                -2.3690184690298404,
262                -0.0066319894226185,
263                0.0000118964995959,
264            ],
265            _6dB => [
266                -0.9452499397020617,
267                -0.0200996773954387,
268                0.00000259011467956,
269            ],
270            _11dB => [
271                1.2247719764336924,
272                -0.0755717904943462,
273                0.0001478791187119,
274                -0.0000000796725280,
275                0.0000000000150380,
276            ],
277        ];
278    }
279}