Skip to content
article

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

Karthikeyan S@karthik_embedded

June 16, 2026 · 5 min read

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

12

Sign in to join the conversation.

Sign in to comment
Peter Hughes@amiga_pete3 rep in Pi Lab
"Measure first, optimise second" is music to an old engineer's ears. Lovely write-up, Karthik. Cheers, Pete
5 net votes
Matt O'Brien@pi_tinker_matt#1 in Pi Lab
This is the post I'll be linking to forever. The fake 18650 bit, mate, I've got a whole drawer of "9900 mAh" cells that are lucky to do a thousand
4 net votes
Leo Chen 陈立@szhw_leo#8 in Pi Lab
RT9080 good choice. Also check: module antenna must be at board edge, no copper under. Many small sensor board I see, antenna on top of ground pour, then connect take long time because weak signal. Weak signal = long wake = battery.
0 net votes
Karthikeyan S@karthik_embedded#2 in Pi Lab
Very correct Leo. The farthest node had exactly this, first board had ground fill under antenna. Respin fixed ~8 dB.
5 net votes
Gwen Albright@gwenprints#4 in Pi Lab
The battery box in the sun is the part I'd fix next (I know, I know, I'm the enclosure lady). A white or light grey box, a little roof over it (even a printed one (in ASA)), and a vent at the bottom can drop the inside temperature a lot. Your cells will thank you.
4 net votes
Daan Visser@daan_fpga4 rep in Pi Lab
The PPK2 is the best 100 euro I ever spent. Small addition: with the PPK2 in source meter mode you can also simulate the battery getting empty and see at which voltage the board starts to brownout. That is how I found my LDO dropout problem.
4 net votes
Hamish Turner@hamish_on_the_farm#3 in Pi Lab
this is exactly why I went LoRa for the troughs. WiFi on a farm is a battery killer, the connect bit is the expensive bit like you say. great numbers mate
4 net votes
Mehmet Aydoğan@malloc_mehmet#9 in Pi Lab
Good post. For once Arduino code without delay(1000) in loop. You could go lower with the ULP coprocessor reading the ADC without waking main CPU, but for this, no need. Correct engineering decision.
5 net votes
Ryan Goh@ai_maxi_ryan3 rep in Pi Lab
or just put a small solar panel on it and stop counting microamps lol 🚀
3 net votes
Karthikeyan S@karthik_embedded#2 in Pi Lab
Solar panel. In a coconut farm. Under coconut trees. Shade da. Please think for one second before typing.
3 net votes
Anoop Menon@anoop_flutter1 rep in Pi Lab
machane coconut farm photo please, then only I will believe Pollachi coconut is better than Kerala coconut 😂😂
7 net votes
Karthikeyan S@karthik_embedded#2 in Pi Lab
Kerala coconut is good for chutney only da. Pollachi coconut runs on ESP32 😄
3 net votes