ESP32 battery sensor: from 4 days to about a year on one 18650. Real numbers, real mistakes
Dev boards lie to you in deep sleep. Measure first, optimise second
Contents
My uncle has a coconut farm near Pollachi. He wanted soil moisture and temperature from 6 spots, every 10 minutes, on his phone. Easy, I said. ESP32, WiFi to the pump shed, MQTT, done. First version battery died in 4 days. This post is how it became about a year, with numbers, because "use deep sleep" is not an answer.
Step 0: measure, don't guess
You need two tools:
- A multimeter in series for the sleep current (µA range)
- Something that can see the wake-up current over time. I use a Nordic Power Profiler Kit II. A cheaper way is a 1Ω shunt and a scope
Battery percentage on a dashboard is not measurement. It is astrology.
Version 1: DevKit board, 4 days
ESP32 DevKitC, capacitive soil sensor, one 18650 from the local market, deep sleep 10 minutes between readings. Measured:
- Deep sleep current: 9.8 mA. Not µA. mA
- Each wake: ~5 seconds at ~150 mA average (DHCP, DNS, TLS to MQTT broker)
Why 9.8 mA in "deep sleep"? The ESP32 chip itself was sleeping fine at ~10 µA. But the board also has an AMS1117 regulator (quiescent current a few mA), a USB-UART chip that is always powered, and a power LED. The sensor was also powered all the time, another ~5 mA.
And the battery: label said 9900 mAh. I measured the real capacity with a cheap tester: about 1100 mAh. Tamil Nadu market special 😄. Math: 9.8 mA × 24 h = 235 mAh/day just sleeping, plus 144 wakes × (5 s × 150 mA) = 30 mAh/day. 1100 / 265 ≈ 4 days. Matches.
Version 2: hardware fixes
- Bare ESP32-WROOM-32E module on my own small board. No USB chip, no power LED. Programming header only
- LDO with low quiescent current that can still give 500 mA+. First I used MCP1700 (1.6 µA quiescent, beautiful) but it is only 250 mA, and WiFi TX peaks are more than that. Result: brownout reset in the middle of connecting, every time, battery drains in retries. Now RT9080 (2 µA, 600 mA) with 470 µF + 10 µF + 100 nF near the module
- Sensor power from a P-MOSFET switched by a GPIO. Off in sleep
- Battery voltage divider 1M + 1M with 100 nF across the bottom resistor. 100k + 100k draws 18 µA all the time, that alone is more than the whole sleeping ESP32
- Genuine 18650, 2600 mAh, bought from an authorised dealer, tested at ~2450
Deep sleep now: 14 µA for the whole board.
Version 2: firmware fixes
Wake time is the other half. 5 seconds awake is very expensive. What helped:
- Static IP. DHCP alone was ~1 second sometimes
- Save WiFi channel and BSSID in RTC memory and pass them to WiFi.begin. Skips the scan
- No TLS on the ESP32. The handshake was ~1.2 s by itself. The sensors talk plain MQTT on the farm's own WiFi to a Pi in the pump shed, and the Pi does TLS to the outside. Not perfect, acceptable for soil data
- Batch: read every 10 minutes, but send every 30 minutes (3 readings stored in RTC memory). Reading the sensor needs the CPU for ~60 ms. The radio is the expensive part
#include <WiFi.h>
RTC_DATA_ATTR uint8_t savedBssid[6];
RTC_DATA_ATTR int32_t savedChannel = 0;
RTC_DATA_ATTR uint16_t readings[3];
RTC_DATA_ATTR uint8_t count = 0;
IPAddress ip(192, 168, 4, 60), gw(192, 168, 4, 1), mask(255, 255, 255, 0);
void sleepNow() {
esp_sleep_enable_timer_wakeup(10ULL * 60 * 1000000ULL); // 10 minutes
esp_deep_sleep_start();
}
void setup() {
pinMode(SENSOR_EN, OUTPUT);
digitalWrite(SENSOR_EN, HIGH);
delay(50); // sensor settle
readings[count++] = analogReadMilliVolts(SENSOR_PIN);
digitalWrite(SENSOR_EN, LOW);
if (count < 3) sleepNow(); // radio never turns on
WiFi.mode(WIFI_STA);
WiFi.config(ip, gw, mask);
if (savedChannel) WiFi.begin(SSID, PASS, savedChannel, savedBssid, true);
else WiFi.begin(SSID, PASS);
uint32_t t0 = millis();
while (WiFi.status() != WL_CONNECTED) {
if (millis() - t0 > 3000) { // give up, try fresh scan next time
savedChannel = 0;
count = 0; // drop readings. it is soil, not ICU
sleepNow();
}
delay(10);
}
savedChannel = WiFi.channel();
memcpy(savedBssid, WiFi.BSSID(), 6);
publishReadings(readings, 3); // plain MQTT to the shed Pi
count = 0;
sleepNow();
}
void loop() {}
Wake with radio now: ~0.6 s at ~120 mA average.
The math now
- Sleep: 14 µA × 24 h = 0.34 mAh/day
- Sensor reads: 144 × 60 ms × 25 mA = 0.06 mAh/day
- Radio wakes: 48 × 0.6 s × 120 mA = 0.96 mAh/day
- Total on paper: ~1.4 mAh/day. 2450 mAh / 1.4 = about 4.8 years
Field reality: from the battery voltage curve over 4 months, the real number is ~5-6 mAh/day. Why? The far sensors see the AP at -82 dBm, so connects fail and retry. The battery box sits in sun, Li-ion self discharge goes up with heat. And my uncle presses the "wake now" button to show his friends. So: about a year. Paper says 4 years, field says 1 year. Field wins always only.
This video has the RTC memory trick and few more things that are useful if you are starting:
Ask questions in the comments. And please, measure with a proper meter, not the percentage on your dashboard.
Written by
Karthikeyan S
Embedded engineer, Coimbatore. STM32, ESP32, motor controllers for pumps (Kovai is pump city da). C, RTOS, oscilloscope, filter coffee. எம்பெடட் தான் உண்மையான கோடிங் 😄
Get the best of #raspberry_pi_lab weekly
One email each Monday with the most useful posts. No spam; unsubscribe in one click.
Comments
Sign in to join the conversation.
Sign in to comment