Battery level, on the screen
Bridge one solder pad and the board can read its own cell on GPIO 2. Then three conversions in a row — halve, count, double back — and a calibration constant that is honest only if it is yours.
Bridge the pad first
Nothing in this article works until the solder jumper marked BATTERY MONITOR
and IO2 on the expansion base is bridged. It ships open on purpose — the
divider sits across the cell and draws from it once connected — so this is a
decision, not a step you skipped.
Bridging it also spends GPIO 2. That pin is one of the five with an ADC behind it, and from here on it belongs to the battery.
Halve, count, double back
Three conversions, and each of them is a place to be wrong by a factor of two:
- The divider halves the cell. Two equal resistors, so the pin sees about 2.05 V when the cell is at 4.1 V. That is deliberate — a full lithium cell is 4.2 V and the ADC's input range tops out below that, so measuring it directly would clip.
- The ADC turns that into a number.
analogReadMilliVolts()is the call to use rather thananalogRead(), because it applies the per-chip calibration that Espressif burns into the efuses at manufacture and hands you millivolts instead of counts out of 4095. It removes most of the error before you write any arithmetic of your own. - You double it back. Multiply by the divider ratio and you have the cell.
The calibration constant is yours
Sketches for this board circulate with a hard-coded correction factor in them — 0.911 is the common one. It is a real measurement: somebody put a meter on their cell, compared it to what their board printed, and divided. It corrects their board's ADC.
Applied to yours it is a guess with a decimal point on it. Start at 1.0,
measure the cell across its own terminals, divide by what the sketch printed, and
use that. If the two agree to a few hundredths, leave it at 1.0 and stop.
What the number is worth
A lithium cell's voltage is a poor gauge and a good alarm. Most of its capacity lives between about 3.9 and 3.6 V, which is a range narrow enough that ±0.05 V of measurement error moves your "percentage remaining" by a quarter. Then it falls off a cliff below 3.5 V.
So print volts rather than a percentage, and use it for the thing it is genuinely good at: noticing 3.4 V and shutting something down before the cell's own protection circuit does it for you, less gracefully.
The code
Reads the divider on GPIO 2, averages the noise out, and prints the result on the screen and over serial. Only works once the BATTERY MONITOR pad on the expansion base is bridged.
/* Lonely Binary ESP32-C3 OLED — battery meter
Tools > Board: ESP32C3 Dev Module
Tools > USB CDC On Boot: Enabled
Needs: BATTERY MONITOR pad on the base bridged */
#include <U8g2lib.h>
U8G2_SH1106_72X40_WISE_F_SW_I2C u8g2(U8G2_R0, 6, 5);
const int ADC_PIN = 2; // the pad routes the divider here
const float DIVIDER = 2.0; // two equal resistors: half the cell
const float CAL = 1.0; // your board's correction. See the note.
const int SAMPLES = 16;
void setup() {
Serial.begin(115200);
u8g2.begin();
u8g2.setFont(u8g2_font_6x10_tf);
analogSetAttenuation(ADC_11db); // full 0-3.3 V input range
}
float readBattery() {
uint32_t sum = 0;
for (int i = 0; i < SAMPLES; i++) {
sum += analogReadMilliVolts(ADC_PIN); // calibrated mV, not counts
delay(5);
}
return (sum / SAMPLES) / 1000.0f * DIVIDER * CAL;
}
void loop() {
float v = readBattery();
char line[16];
snprintf(line, sizeof line, "%.2f V", v);
u8g2.clearBuffer();
u8g2.drawStr(0, 12, "Battery");
u8g2.drawStr(0, 28, line);
u8g2.sendBuffer();
Serial.printf("%.3f V\n", v);
delay(1000);
}CAL is the number to change. 1.0 is the honest starting point — run the sketch, measure the cell with a meter, and set CAL to (what the meter says) divided by (what the sketch printed). Anyone else's value is a correction for their board's ADC, not yours.
When it does not work
The pad is not bridged. It ships open, so GPIO 2 is floating until you make the connection, and a floating ADC input reads whatever it feels like — often near zero. Look for the BATTERY MONITOR block beside the connector and check the solder joint under a light.
The DIVIDER constant is missing or set to 1. The pad connects GPIO 2 to the midpoint of two equal resistors, so the pin only ever sees half the cell voltage and the sketch has to double it back.
That is what CAL is for, and it is per-board. Measure the cell across its own terminals with a meter, divide that by what the sketch prints, and put the result in CAL. Copying a factor from someone else's sketch corrects their ADC's error using your ADC, which just moves the error.
Increase SAMPLES, and take the readings while the radio is quiet. Averaging sixteen samples costs 80 ms and removes most of it; a reading taken during a Wi-Fi transmission is measuring the supply sagging as much as the cell.
It is not wrong, it is charging. The charger holds the cell at up to 4.2 V while USB is connected, so any reading taken then tells you about the charger rather than the cell. Take battery readings on battery.
The last thing between this board and a project: an antenna a few millimetres long, and a radio that will happily overdrive it.
Wi-Fi the antenna can carry →Edit this page — content/boards/c3-oled/battery-level-on-the-screen.mdx
Questions about this product
See what other owners have asked, and read their solutions.
ESP32-C3 OLED Development Board with Expansion Base
Loading discussions…
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.