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.