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 /// Coefficients for the eFuse calibration version on this chip.
38 fn curves_coeffs() -> CurvesCoeffs {
39 Self::CURVES_COEFFS
40 }
41}
42
43/// Curve fitting ADC calibration scheme
44///
45/// This scheme implements polynomial error correction using predefined
46/// coefficient sets for each attenuation. It returns readings in mV.
47///
48/// This scheme also includes basic calibration ([`super::AdcCalBasic`]) and
49/// line fitting ([`AdcCalLine`]).
50#[derive(Clone, Copy)]
51pub struct AdcCalCurve<ADCX> {
52 line: AdcCalLine<ADCX>,
53
54 /// Coefficients of the error estimation polynomial.
55 ///
56 /// The constant coefficient comes first; the error polynomial is
57 /// `coeff[0] + coeff[1] * x + ... + coeff[n] * x^n`.
58 ///
59 /// This calibration works by first applying linear calibration. Then
60 /// the error polynomial is applied to the output of linear calibration.
61 /// The output of the polynomial is our estimate of the error; it gets
62 /// subtracted from linear calibration's output to get the final reading.
63 coeff: &'static [CurveCoeff],
64
65 _phantom: PhantomData<ADCX>,
66}
67
68impl<ADCX> crate::private::Sealed for AdcCalCurve<ADCX> {}
69
70impl<ADCX> AdcCalScheme<ADCX> for AdcCalCurve<ADCX>
71where
72 ADCX: AdcCalEfuse + AdcHasLineCal + AdcHasCurveCal + CalibrationAccess,
73{
74 fn new_cal(atten: Attenuation) -> Self {
75 Self::new_cal_with_channel(atten, 0)
76 }
77
78 fn new_cal_with_channel(atten: Attenuation, channel: u8) -> Self {
79 let line = AdcCalLine::<ADCX>::new_cal_with_channel(atten, channel);
80
81 let coeff = ADCX::curves_coeffs()
82 .iter()
83 .find(|item| item.atten == atten)
84 .expect("No curve coefficients for given attenuation")
85 .coeff;
86
87 Self {
88 line,
89 coeff,
90 _phantom: PhantomData,
91 }
92 }
93
94 fn adc_cal(&self) -> u16 {
95 self.line.adc_cal()
96 }
97
98 fn adc_val(&self, val: u16) -> u16 {
99 let val = self.line.adc_val(val);
100
101 // Calculate polynomial error using Horner's method to prevent overflow.
102 // Horner's evaluates: err = coeff[0] + val*(coeff[1] + val*(coeff[2] + ...))
103 // This avoids computing val^n which causes overflow when multiplied by coefficients.
104 let err = if val == 0 || self.coeff.is_empty() {
105 0
106 } else {
107 let val_i64 = val as i64;
108 let mut poly = 0i64;
109
110 // Iterate coefficients in reverse order for Horner's method
111 for &coeff in self.coeff.iter().rev() {
112 poly = poly * val_i64 + coeff;
113 }
114
115 (poly / COEFF_MUL) as i32
116 };
117
118 (val as i32 - err) as u16
119 }
120}
121
122macro_rules! coeff_tables {
123 ($($(#[$($meta:meta)*])* $name:ident [ $($att:ident => [ $($val:literal,)* ],)* ];)*) => {
124 $(
125 $(#[$($meta)*])*
126 const $name: CurvesCoeffs = &[
127 $(CurveCoeffs {
128 atten: Attenuation::$att,
129 coeff: &[
130 $(($val as f64 * COEFF_MUL as f64) as CurveCoeff,)*
131 ],
132 },)*
133 ];
134 )*
135 };
136}
137
138#[cfg(any(esp32c3, esp32c5, esp32c6, esp32c61, esp32h2, esp32p4, esp32s3))]
139mod impls {
140 use super::*;
141
142 impl AdcHasCurveCal for crate::peripherals::ADC1<'_> {
143 const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS1;
144
145 #[cfg(esp32c6)]
146 fn curves_coeffs() -> CurvesCoeffs {
147 if crate::efuse::rtc_calib_version() == 2 {
148 CURVES_COEFFS1_V2
149 } else {
150 CURVES_COEFFS1
151 }
152 }
153 }
154
155 #[cfg(any(esp32c3, esp32p4, esp32s3))]
156 impl AdcHasCurveCal for crate::peripherals::ADC2<'_> {
157 const CURVES_COEFFS: CurvesCoeffs = cfg_select! {
158 esp32c3 => CURVES_COEFFS1,
159 esp32p4 => CURVES_COEFFS2,
160 esp32s3 => CURVES_COEFFS2,
161 };
162 }
163
164 coeff_tables! {
165 /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32c3/curve_fitting_coefficients.c>
166 #[cfg(esp32c3)]
167 CURVES_COEFFS1 [
168 _0dB => [
169 -0.225966470500043,
170 -0.0007265418501948,
171 0.0000109410402681,
172 ],
173 _2p5dB => [
174 0.4229623392600516,
175 -0.0000731527490903,
176 0.0000088166562521,
177 ],
178 _6dB => [
179 -1.017859239236435,
180 -0.0097159265299153,
181 0.0000149794028038,
182 ],
183 _11dB => [
184 -1.4912262772850453,
185 -0.0228549975564099,
186 0.0000356391935717,
187 -0.0000000179964582,
188 0.0000000000042046,
189 ],
190 ];
191
192 /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/c602e55/components/esp_adc/esp32c5/curve_fitting_coefficients.c>
193 #[cfg(esp32c5)]
194 CURVES_COEFFS1 [
195 _0dB => [
196 -0.2941017829027464,
197 0.0007368674918527,
198 0.0,
199 ],
200 _2p5dB => [
201 -0.3224276125615327,
202 0.0005325658467636,
203 0.0,
204 ],
205 _6dB => [
206 -0.3307554632960901,
207 0.000409244304226,
208 0.0,
209 ],
210 _11dB => [
211 1.463642578413965,
212 -0.003349642363147,
213 0.0000011676836451,
214 ],
215 ];
216
217 /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/027613140/components/esp_adc/esp32c6/curve_fitting_coefficients.c#L29-L35>
218 #[cfg(esp32c6)]
219 CURVES_COEFFS1 [
220 _0dB => [
221 -0.0487166399931449,
222 0.0006436483033201,
223 0.0000030410131806,
224 ],
225 _2p5dB => [
226 -0.8665498165817785,
227 0.0015239070452946,
228 0.0000013818878844,
229 ],
230 _6dB => [
231 -1.2277821756674387,
232 0.0022275554717885,
233 0.0000005924302667,
234 ],
235 _11dB => [
236 -0.3801417550380255,
237 -0.0006020352420772,
238 0.0000012442478488,
239 ],
240 ];
241
242 /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/1e76669a8b940f5dc25adc35065cb53de3c71423/components/esp_adc/esp32c61/curve_fitting_coefficients.c>
243 #[cfg(esp32c61)]
244 CURVES_COEFFS1 [
245 _0dB => [
246 -0.8668885650149671,
247 0.0015630376830615,
248 ],
249 _2p5dB => [
250 -0.1090569589734153,
251 0.0013859487941542,
252 ],
253 _6dB => [
254 -1.4231790752153335,
255 0.00122016745867,
256 ],
257 _11dB => [
258 -1.3204544579940347,
259 -0.0011762579610906,
260 0.0000007639928529,
261 ],
262 ];
263
264 /// Error curve coefficients for calibration version 2 derived from <https://github.com/espressif/esp-idf/blob/027613140/components/esp_adc/esp32c6/curve_fitting_coefficients.c#L36-L42>
265 ///
266 /// Version 2 does not apply a second-step polynomial at 0 dB and 2.5 dB.
267 #[cfg(esp32c6)]
268 CURVES_COEFFS1_V2 [
269 _0dB => [],
270 _2p5dB => [],
271 _6dB => [
272 -1.2217864764388775,
273 -0.0001954123107752,
274 0.0000006409679727,
275 ],
276 _11dB => [
277 -0.3915910437042445,
278 -0.0031536470857564,
279 0.0000012493873014,
280 ],
281 ];
282
283 /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/465b159cd8771ffab6be70c7675ecf6705b62649/components/esp_adc/esp32h2/curve_fitting_coefficients.c>
284 #[cfg(esp32h2)]
285 CURVES_COEFFS1 [
286 _0dB => [
287 -0.5081991760658888,
288 0.0000007858995319,
289 0,
290 ],
291 _2p5dB => [
292 -0.8359230818901277,
293 0.0000009025419089,
294 0,
295 ],
296 _6dB => [
297 -1.165668771581976,
298 0.0000008294679249,
299 0,
300 ],
301 _11dB => [
302 -0.3637329628677273,
303 -0.0000196072597389,
304 0.0000007871689227,
305 ],
306 ];
307
308 /// Error curve coefficients for ADC1, derived from <https://github.com/espressif/esp-idf/blob/08e0d30a74a/components/esp_adc/esp32p4/curve_fitting_coefficients.c>
309 #[cfg(esp32p4)]
310 CURVES_COEFFS1 [
311 _0dB => [
312 -0.7170501832480995,
313 0.0010598497992115,
314 ],
315 _2p5dB => [
316 -0.9960085535084866,
317 0.0015840076608145,
318 ],
319 _6dB => [
320 -1.4711053224678996,
321 0.00001594266424857,
322 ],
323 _11dB => [
324 -2.8811493455181565,
325 0.0010082311568625,
326 ],
327 ];
328
329 /// Error curve coefficients for ADC2, derived from <https://github.com/espressif/esp-idf/blob/08e0d30a74a/components/esp_adc/esp32p4/curve_fitting_coefficients.c>
330 #[cfg(esp32p4)]
331 CURVES_COEFFS2 [
332 _0dB => [
333 -0.4900967548489932,
334 0.0005037402667913,
335 ],
336 _2p5dB => [
337 -0.7296214814536025,
338 0.0011021577596635,
339 ],
340 _6dB => [
341 -1.0991620450220592,
342 0.0011623930881896,
343 ],
344 _11dB => [
345 -2.442140102462673,
346 0.0009458501263393,
347 ],
348 ];
349
350 /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32s3/curve_fitting_coefficients.c>
351 #[cfg(esp32s3)]
352 CURVES_COEFFS1 [
353 _0dB => [
354 -2.7856531419538344,
355 -0.0050871540569528,
356 0.0000097982495890,
357 ],
358 _2p5dB => [
359 -2.9831022915028695,
360 -0.0049393185868806,
361 0.0000101379430548,
362 ],
363 _6dB => [
364 -2.3285545746296417,
365 -0.0147640181047414,
366 0.0000208385525314,
367 ],
368 _11dB => [
369 -0.644403418269478,
370 -0.0644334888647536,
371 0.0001297891447611,
372 -0.0000000707697180,
373 0.0000000000135150,
374 ],
375 ];
376
377 /// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32s3/curve_fitting_coefficients.c>
378 #[cfg(esp32s3)]
379 CURVES_COEFFS2 [
380 _0dB => [
381 -2.5668651654328927,
382 0.0001353548869615,
383 0.0000036615265189,
384 ],
385 _2p5dB => [
386 -2.3690184690298404,
387 -0.0066319894226185,
388 0.0000118964995959,
389 ],
390 _6dB => [
391 -0.9452499397020617,
392 -0.0200996773954387,
393 0.00000259011467956,
394 ],
395 _11dB => [
396 1.2247719764336924,
397 -0.0755717904943462,
398 0.0001478791187119,
399 -0.0000000796725280,
400 0.0000000000150380,
401 ],
402 ];
403 }
404}