Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calibration calc overflow #3090

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

yuri-ncs
Copy link

@yuri-ncs yuri-ncs commented Feb 3, 2025

Submission Checklist 📝

  • I have updated existing examples or added new ones (if applicable).
  • I have used cargo xtask fmt-packages command to ensure that all changed code is formatted correctly.
  • My changes were added to the CHANGELOG.md in the proper section.
  • I have added necessary changes to user code to the Migration Guide.
  • My changes are in accordance with the esp-rs API guidelines.

Extra:

Pull Request Details 📖

Description

The current implementation evaluates polynomials using the standard form:

[
P(x) = a_n x^n + a_{n-1} x^{n-1} + ... + a_1 x + a_0
]

This method requires multiple multiplications and additions, leading to overflow. Instead, we transition to Horner’s method, which restructures the polynomial to minimize operations:

[
P(x) = (...((a_n x + a_{n-1})x + a_{n-2})x + ...)x + a_0
]

This transformation significantly reduces the number of required operations from (O(n^2)) in some naive implementations to O(n), improving efficiency and numerical stability.

Solution

  • Replaced the standard polynomial evaluation with Horner’s method.
  • Reduced the number of multiplications and additions, leading to better performance, especially for higher-degree polynomials.
  • Ensured that the transition maintains the correctness of results.

Tests

  • Tested the Horners method with this code
use std::fs::File;
use std::io::{BufWriter, Write};

// Fixed-point scaling factor.
const COEFF_MUL: i64 = 1 << 52;

/// Naive method: Computes the error correction by calculating each term,
/// dividing immediately (thus truncating per term), and summing.
fn calc_error_naive(val: u16, coeff: &[i64]) -> i32 {
    if val == 0 {
        0
    } else {
        let mut power = 1i64;
        let mut err = 0i32;
        for &c in coeff {
            err += (power as i128 * (c as i128) / COEFF_MUL as i128) as i32;
            power *= val as i64;
        }
        err
    }
}

/// Horner method: Evaluates the entire polynomial first and performs one division at the end.
/// This approach may differ in rounding since the division is deferred.
fn calc_error_horner(val: u16, coeff: &[i64]) -> i32 {
    if val == 0 {
        0
    } else {
        let x = val as i64;
        let mut poly = 0i64;
        for &c in coeff.iter().rev() {
            poly = poly * x + c;
        }
        (poly / COEFF_MUL) as i32
    }
}

fn main() -> std::io::Result<()> {
    // 11 dB attenuation coefficients from ESP-IDF (as f64) converted to fixed-point (i64).
    let coeff: Vec<i64> = vec![
        (-0.644403418269478_f64 * (COEFF_MUL as f64)) as i64,
        (-0.0644334888647536_f64 * (COEFF_MUL as f64)) as i64,
        (0.0001297891447611_f64 * (COEFF_MUL as f64)) as i64,
        (-0.0000000707697180_f64 * (COEFF_MUL as f64)) as i64,
        (0.0000000000135150_f64 * (COEFF_MUL as f64)) as i64,
    ];

    // Create (or overwrite) the log file.
    let log_file_path = "adc_comparison.log";
    let file = File::create(log_file_path)?;
    let mut writer = BufWriter::new(file);

    // Write header for CSV-like log.
    writeln!(writer, "ADC,Naive,Horner,Difference")?;

    let mut sum_abs_diff: i64 = 0;
    let mut max_abs_diff: i32 = 0;
    let mut count: u32 = 0;

    // Loop over all ADC values from 0 to 4096.
    for val in 0u16..=4096 {
        let naive = calc_error_naive(val, &coeff);
        let horner = calc_error_horner(val, &coeff);
        let diff = naive - horner;
        sum_abs_diff += diff.abs() as i64;
        if diff.abs() > max_abs_diff {
            max_abs_diff = diff.abs();
        }
        count += 1;
        // Write one line per ADC value.
        writeln!(writer, "{},{},{},{}", val, naive, horner, diff)?;
    }

    let avg_diff = sum_abs_diff as f64 / count as f64;

    // Write summary data at the end of the file.
    writeln!(writer, "\nSummary:")?;
    writeln!(writer, "Total ADC values tested: {}", count)?;
    writeln!(writer, "Maximum absolute difference: {}", max_abs_diff)?;
    writeln!(writer, "Average absolute difference: {:.3}", avg_diff)?;

    writer.flush()?;
    println!("Log file created: {}", log_file_path);

    Ok(())
}

This is the generated output adc_comparison.log

  • Maximum absolute difference: 3
  • Average Abolute difference: 1.005

  • I also created a new test under qa_test, it will test every adc calibration method with every attenuation, the overflow check needs to be on, and will continuosly read and print the output set.

resolve #3061

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ADC conversion overflow using curve calibration
1 participant