How OXBatteryManager Works

A walkthrough of the ADC seam, divider math, and voltage-curve interpolation

What this page is

The API reference tells you what the battery manager does. This page explains the injected ADC backend, the resistor-divider reconstruction, and the voltage-curve mapping to percent.

The injected ADC input

The manager depends only on IAdcInput: configure(pin), readRaw(pin), and readMilliVolts(pin). The ESP32 backend configures the ADC in device builds; host tests inject a fake and script exact readings.

OXBatteryManager  (portable: averaging, divider math, curve mapping)
   │  one injected IAdcInput
   ▼
IAdcInput         configure(pin) · readRaw(pin) · readMilliVolts(pin)
   ├─ EspAdcInput    src/gpio/EspAdcInput.cpp   (device)
   └─ FakeAdcInput   test/...                    (host)

Divider reconstruction

GPIO3 only sees the midpoint voltage. The manager reconstructs the original sensed rail with the standard divider equation:

Vsource = Vadc × (Rtop + Rbottom) / Rbottom

With the current 1k/1k divider, that simplifies to 2 × Vadc. So a 2100 mV ADC reading means the LiPo cell is about 4200 mV.

Averaging and cached samples

Every call to sample() performs sampleCount raw reads and calibrated millivolt reads, averages them, and stores the result in lastReading(). There is no background task or polling loop; the API is deliberately on-demand.

Curve mapping

After reconstructing the source voltage, the manager maps it to a percent using a piecewise-linear curve of { millivolts, percent } points ordered highest voltage first. Values above the first point clamp high; values below the last point clamp low; values between two points are linearly interpolated.

Sense the raw cell, not a regulated rail

The divider top resistor connects directly to the raw one-cell LiPo VCC, before any boost converter or regulator. The default curve maps 4.20 V to 100% and 3.30 V to 0%. Sensing a regulated 5 V rail instead would remain near 5 V until shutdown and incorrectly report a high percentage.

A 1k/1k divider puts a full 4.20 V cell at about 2.10 V on GPIO3, within the ESP32-S3 ADC range. Add a small capacitor, such as 100 nF, from the divider midpoint to GND if readings are noisy. A 0 mV reading is treated as an unavailable sense signal, not a 0% battery: inspect the GPIO3 midpoint, both resistor connections, and the shared battery ground.

Where it fits

OXBatteryManager is only the sensing layer. It does not own charging logic, LED policy, or shutdown behavior. It converts one ADC pin into a structured reading that the rest of the firmware can log, expose, or threshold later.