8000
Skip to content

[adc] Fix autorange negative coefficient bug causing incorrect voltage readings - #10549

Merged
jesserockz merged 11 commits into
esphome:devfrom
edwardtfn:adc-troubleshoot-10522
Sep 9, 2025
Merged

[adc] Fix autorange negative coefficient bug causing incorrect voltage readings#10549
jesserockz merged 11 commits into
esphome:devfrom
edwardtfn:adc-troubleshoot-10522

Conversation

@edwardtfn
@edwardtfn edwardtfn commented Sep 3, 2025
Copy link
Copy Markdown
Contributor

What does this implement/fix?

Fixes a critical bug in the ESP32 ADC autorange implementation where negative coefficient calculations wrap around when assigned to unsigned integers, causing extremely incorrect voltage readings (millions of volts instead of expected 1-3V range).

The Problem

The autorange algorithm calculates weighting coefficients to combine readings from different attenuation levels:

uint32_t c6 = adc_half - std::abs(raw6 - adc_half);

When the raw ADC reading exceeds adc_half + adc_half (i.e., > 4096), this calculation becomes negative. Since the coefficient is stored as uint32_t, the negative value wraps around to ~4.3 billion, completely breaking the weighted average calculation.

Example with user's data:

  • raw6 = 6157, adc_half = 2048
  • 2048 - abs(6157 - 2048) = 2048 - 4109 = -2061
  • As uint32_t: -20614,294,965,235 (negative converted to huge positive)
  • Final coefficient sum becomes ~4.3 billion instead of reasonable values

The Solution

Use signed arithmetic for the calculation and clamp negative results to zero:

int32_t c6_signed = adc_half - std::abs(raw6 - adc_half);
uint32_t c6 = std::max(0, c6_signed);

This ensures that unreliable readings (far from the optimal range) get zero weight instead of maximum weight.

Root Cause

This bug was introduced during the ESP-IDF v5 migration (PR #9021) when the ADC implementation was rewritten to use the new oneshot API. The previous legacy implementation didn't have this issue.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Code quality improvements to existing code or addition of tests
  • Other

Related issue or feature (if applicable):

Pull request in esphome-docs with documentation (if applicable): N/A

Test Environment

  • ESP32
  • ESP32 IDF
  • ESP8266
  • RP2040
  • BK72xx
  • RTL87xx
  • nRF52840

Example entry for config.yaml: N/A

Checklist:

  • The code change is tested and works locally.
  • Tests have been added to verify that the new code works (under tests/ folder).

If user exposed functionality or configuration variables are added/changed:

@github-actions
github-actions Bot commented Sep 3, 2025
Copy link
Copy Markdown
Contributor

To use the changes from this PR as an external component, add the following to your ESPHome configuration YAML file:

external_components:
  - source: github://pr#10549
    components: [adc]
    refresh: 1h

(Added by the PR bot)

@codecov-commenter
codecov-commenter commented Sep 3, 2025
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.00%. Comparing base (e5bba00) to head (598272b).

Additional details and impacted files
@@           Coverage Diff           @@
##              dev   #10549   +/-   ##
=======================================
  Coverage   61.00%   61.00%           
=======================================
  Files          52       52           
  Lines       10504    10504           
  Branches     1400     1400           
=======================================
  Hits         6408     6408           
  Misses       3738     3738           
  Partials      358      358           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

pre-commit-ci-lite Bot and others added 2 commits September 3, 2025 17:05
…e readings

## What does this implement/fix?

Fixes a critical bug in the ESP32 ADC autorange implementation where negative coefficient calculations wrap around when assigned to unsigned integers, causing extremely incorrect voltage readings (millions of volts instead of expected 1-3V range).

## The Problem

The autorange algorithm calculates weighting coefficients to combine readings from different attenuation levels:

```cpp
uint32_t c6 = adc_half - std::abs(raw6 - adc_half);
```

When the raw ADC reading exceeds `adc_half + adc_half` (i.e., > 4096), this calculation becomes negative. Since the coefficient is stored as `uint32_t`, the negative value wraps around to ~4.3 billion, completely breaking the weighted average calculation.

**Example with user's data:**
- `raw6 = 6157`, `adc_half = 2048`
- `2048 - abs(6157 - 2048) = 2048 - 4109 = -2061`
- As `uint32_t`: `-2061` → `4,294,965,235` (negative converted to huge positive)
- Final coefficient sum becomes ~4.3 billion instead of reasonable values

## The Solution

Use signed arithmetic for the calculation and clamp negative results to zero:

```cpp
int32_t c6_signed = adc_half - std::abs(raw6 - adc_half);
uint32_t c6 = std::max(0, c6_signed);
```

This ensures that unreliable readings (far from the optimal range) get zero weight instead of maximum weight.

## Root Cause

This bug was introduced during the ESP-IDF v5 migration (PR esphome#9021) when the ADC implementation was rewritten to use the new oneshot API. The previous legacy implementation didn't have this issue.

## Testing

- ✅ Fixes the reported case where autorange returned 30+ million volts
- ✅ Fixed attenuation (6db) works correctly: ~1.036V
- ✅ Raw ADC readings are normal: 3252/4095
- ✅ After fix, autorange should return values close to fixed attenuation readings

**Related issue:** esphome#10522
@esphome esphome Bot removed the small-pr PR < 30 lines label Sep 6, 2025
@edwardtfn edwardtfn changed the title [adc] Add temporary detailed logs on autorange engine to troubleshoot [adc] Fix autorange negative coefficient bug causing incorrect voltage readings Sep 6, 2025
@edwardtfn
edwardtfn marked this pull request as ready for review September 8, 2025 15:19
@edwardtfn
edwardtfn requested a review from a team as a code owner September 8, 2025 15:19
@jesserockz
jesserockz merged commit 90c2fdd into esphome:dev Sep 9, 2025
25 checks passed
@jesserockz jesserockz mentioned this pull request Sep 10, 2025
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 11, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2025.8.x on esp32s2: adc delivers bizarre values

3 participants

0