Skip to content

ESP32-C6: ADC calibration ignores efuse calibration scheme V2, readings ~9% low #6145

Description

@matze

Bug description

The ESP32-C6 is burned with one of two ADC calibration schemes, selected by the efuse block version. esp_hal::efuse::rtc_calib_version collapses every block version above zero into scheme 1:

// esp-hal/src/efuse/esp32c6/mod.rs
pub fn rtc_calib_version() -> u8 {
    let (_major, minor) = block_version();
    if minor >= 1 { 1 } else { 0 }
}

ESP-IDF on the other hand picks the scheme from major * 100 + minor. Everything from 2 upwards is scheme 2 (esp_efuse_rtc_calib.c):

uint32_t blk_ver = efuse_hal_blk_version();          // major * 100 + minor
if (blk_ver == 1)      cali_version = ESP_EFUSE_ADC_CALIB_VER1;
else if (blk_ver >= 2) cali_version = ESP_EFUSE_ADC_CALIB_VER2;

The two schemes disagree on the reference point for AdcCalLine:

V1 V2
reference voltage, 12 dB 1370 mV 2800 mV
digital code base 1500 2850 (2900 at 6 dB)

rtc_calib_cal_mv returns only the V1 column and rtc_calib_cal_code only the V1 base, so on a V2 chip the gain comes out as 1370 / (1500 + d) where it should be 2800 / (2850 + d). AdcCalCurve then applies the V1 error polynomial on top. IDF has a separate V2 coefficient table for attenuations 2 and 3.

Measured on my board, ADC1 channel 0 at Attenuation::_11dB, same 2049 raw counts through both paths:

mV
esp-hal AdcCalCurve 1791
ESP-IDF V2 constants 1970

This is about 9% low. Through a battery gauge scaled 3.0 to 4.12 V that showed a full cell as 52% where Espressif's own Arduino example on the same board reported 98%.

Two smaller things in the same area, neither covered by #5589:

  • SOC_ADC_CALIB_CHAN_COMPENS_SUPPORTED is set for the C6, and IDF subtracts esp_efuse_rtc_calib_get_chan_compens() from the raw count in adc_cali_curve_fitting.c. esp-hal applies chan_compens only on the C5.
  • AdcCalScheme is sealed, so a downstream crate cannot supply a corrected scheme. The only workaround is AdcCalBasic plus doing the conversion by hand.

Already known

The symptom is listed as future work in #5589, deliberately out of that PR's scope. #5589 says the wrong offsets and coefficient table are used, but neither its body nor its diff mentions the efuse block version

Related: #1203 reports calibrated readings being off on the C6, but every board tested there is v0.0, which is scheme V1 and unaffected by this.

To Reproduce

On any C6 with efuse block revision ≥ v0.2 (the bootloader prints it as efuse block revision: v0.3):

let mut config = AdcConfig::new();
let mut pin = config.enable_pin_with_cal::<_, AdcCalCurve<ADC1<'static>>>(
    peripherals.GPIO0,
    Attenuation::_11dB,
);
let mut adc = Adc::new(peripherals.ADC1, config);

println!("block version {:?}", esp_hal::efuse::block_version());   // (0, 3)
println!("calib version {}", esp_hal::efuse::rtc_calib_version()); // 1, want 2
println!("{} mV", nb::block!(adc.read_oneshot(&mut pin)).unwrap());

Expected behavior

Millivolts within the ADC's stated accuracy of the input, matching ESP-IDF, on chips burned with either calibration scheme.

Environment

  • Target device: ESP32-C6 (revision v0.2, efuse block revision v0.3), Waveshare ESP32-C6-ePaper-1.54
  • Crate name and version: esp-hal 1.1.2; rtc_calib_version and the C6 curve coefficient table are unchanged on main

Disclaimer

Opus 5 was used to find the root cause for the misread battery level.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingchip:esp32c6Issue related to ESP32-C6 chipperipheral:adcADC peripheral

Type

No type

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions