OXRecorder::OXBatteryManager
ADC battery / power-rail sensing on GPIO3 with a drop-curve percentage estimate
Overview
OXBatteryManager reads an ADC input, reconstructs the
original pre-divider voltage, and maps it to a percentage through a
configurable drop curve. The default wiring matches the current board:
GPIO3 at the midpoint of two 1k
resistors, with the top resistor on the raw single-cell LiPo
VCC and the bottom resistor on GND.
- Default hardware target: GPIO3 + 1k/1k divider.
- Averages multiple ADC samples per read to smooth noise.
- Returns raw ADC, midpoint millivolts, reconstructed source millivolts, and percent.
- Uses an injected ADC backend so the divider math and curve mapping are host-testable.
Declared in include/OXBatteryManager.h; portable core in
src/managers/OXBatteryManager.cpp; ESP32-S3 ADC backend in
src/gpio/EspAdcInput.cpp.
Important wiring note
The divider must sense the raw LiPo cell before any boost converter or regulator. A regulated 5 V rail stays nearly constant until shutdown and cannot represent remaining battery charge. With the 1k/1k divider, a full 4.20 V cell appears as about 2.10 V on GPIO3.
BatteryConfig
| Field | Type | Default | Meaning |
|---|---|---|---|
adcPin | int | 3 | ADC pin connected to the divider midpoint. |
sampleCount | uint8_t | 8 | How many raw + millivolt reads to average per sample. |
dividerTopOhms | uint32_t | 1000 | Top resistor from the sensed rail to GPIO3. |
dividerBottomOhms | uint32_t | 1000 | Bottom resistor from GPIO3 to GND. |
curve | const BatteryCurvePoint* | nullptr | Voltage-to-percent mapping. If omitted, the manager uses its built-in 1-cell LiPo curve (4.20 V = 100%, 3.30 V = 0%). |
curveCount | size_t | 0 | Number of points in curve. |
BatteryReading
| Field | Type | Meaning |
|---|---|---|
raw | uint16_t | Average raw ADC reading. |
adcMilliVolts | uint16_t | Average midpoint voltage seen directly on GPIO3. |
sourceMilliVolts | uint16_t | Reconstructed pre-divider voltage. |
percent | uint8_t | Interpolated estimate from the active curve. |
valid | bool | False when no ADC backend is available. |
API reference
bool begin(const BatteryConfig &cfg = BatteryConfig{})
Configures the ADC pin and stores the divider/curve settings. Returns false if no ADC backend is available.
BatteryReading sample()
Takes a fresh averaged sample now and caches it in lastReading().
uint16_t readAdcMilliVolts()
uint16_t readSourceMilliVolts()
uint8_t readPercent()
Convenience wrappers that call sample() and return one field from the resulting reading.
static uint16_t sourceMilliVoltsFromAdc(uint16_t adcMilliVolts, uint32_t dividerTopOhms, uint32_t dividerBottomOhms)
Reconstructs the pre-divider voltage using Vsource = Vadc × (Rtop + Rbottom) / Rbottom.
static uint8_t percentForMilliVolts(uint16_t milliVolts, const BatteryCurvePoint *curve, size_t curveCount)
Maps voltage to percent with linear interpolation between curve points. Curves must be ordered highest voltage first.
static const BatteryCurvePoint *defaultBoost5vCurve(size_t *count = nullptr)
static const BatteryCurvePoint *defaultLiPoCurve(size_t *count = nullptr)
Return the optional 5 V rail curve or the default raw single-cell LiPo curve.
Examples
1. Current board wiring: raw LiPo VCC / GND on a 1k/1k divider
#include "OXBatteryManager.h"
#include "OXLog.h"
using namespace OXRecorder;
OXBatteryManager battery;
void setup() {
OXLog::begin(115200);
battery.begin();
BatteryReading r = battery.sample();
OXLog::info("battery estimate %u%% (%u mV source, %u mV ADC)",
r.percent, r.sourceMilliVolts, r.adcMilliVolts);
}
2. Explicitly select the raw LiPo curve
BatteryConfig cfg;
size_t curveCount = 0;
cfg.curve = OXBatteryManager::defaultLiPoCurve(&curveCount);
cfg.curveCount = curveCount;
battery.begin(cfg);
3. Custom resistor values
BatteryConfig cfg;
cfg.dividerTopOhms = 150000;
cfg.dividerBottomOhms = 100000;
battery.begin(cfg);
Notes
- Default meaning. The built-in default curve is for a raw 1-cell LiPo: 4.20 V is 100% and 3.30 V is 0%. Voltage-only state of charge remains an estimate, especially under load.
- ADC headroom. A 1k/1k divider puts a fully charged 4.20 V cell at about 2.10 V on GPIO3.
- On-demand API. There is no polling loop; each read method takes a fresh sample immediately.