EthicsComputerCourses
Join training +91 90532 10052

Power Management & Battery Projects

IoT & Arduino · Lesson 27 of 29 Advanced 8 min read Updated

By the end of this lesson you will be able to estimate how long a battery will run your project, cut power use dramatically with ESP32 deep sleep, measure a battery's voltage from your sketch, and choose safe batteries, regulators and charging modules. This is what separates a desk demo from a device that runs for months in a garden.

A plant sensor that needs its batteries changed every two days will be abandoned within a week. Good power design is invisible: the device just keeps working.

Capacity, current and the power budget

Battery capacity is given in milliamp-hours (mAh). A 2,000 mAh battery could, in theory, supply 2,000 mA for one hour, or 100 mA for 20 hours, or 1 mA for 2,000 hours.

battery life in hours ≈ capacity in mAh ÷ average current in mA

In practice you get less: batteries lose capacity in heat and cold, with age, and at high currents, and your circuit stops working before the battery is completely empty. A common rule of thumb is to plan with about 80% of the label capacity.

A power budget lists every part and its current, like a monthly household budget lists rent, groceries and the electricity bill:

PartTypical currentNotes
ESP32 with Wi-Fi transmittingOften 80 to 250 mA in burstsThe biggest cost; keep Wi-Fi time short
ESP32 in deep sleep (chip alone)MicroampsOnly a few parts stay awake
Dev board extras (USB chip, power LED, regulator)Can be several mA, always onOften more than the sleeping chip itself
Capacitive soil sensorA few mACan be switched off between readings
OLED displayVaries with how many pixels are litTurn it off when nobody is looking

Check your own parts' datasheets; these figures are only typical ranges.

Step 1: Estimate battery life

The secret of long battery life is duty cycling: be awake for a short time, then sleep for a long time. The average current is then dominated by the sleep current. This Python calculator compares three designs that send one reading every 10 minutes, each awake for 2 seconds at 80 mA, using a 2,000 mAh battery:

Python
# Estimate battery life for a device that wakes, sends, then sleeps
def average_ma(awake_ma, awake_s, sleep_ma, period_s):
    sleep_s = period_s - awake_s
    return (awake_ma * awake_s + sleep_ma * sleep_s) / period_s

def life_days(capacity_mah, avg_ma, usable=0.8):
    return capacity_mah * usable / avg_ma / 24

always_on = 80
bare_chip = average_ma(80, 2, 0.15, 600)     # good low-power board asleep
dev_board = average_ma(80, 2, 10, 600)       # dev board: USB chip and LED stay on

for name, ma in [("Always awake", always_on), ("Sleeps, low-power board", bare_chip),
                 ("Sleeps, typical dev board", dev_board)]:
    print(f"{name:26} {ma:7.2f} mA  ->  {life_days(2000, ma):6.1f} days")
Output
Always awake                 80.00 mA  ->     0.8 days
Sleeps, low-power board       0.42 mA  ->   160.2 days
Sleeps, typical dev board    10.23 mA  ->     6.5 days

Three lessons from three lines. Staying awake drains the battery in under a day. Sleeping makes a huge difference. And the board matters as much as the code: the extra 10 mA of a typical dev board's always-on parts cuts 160 days down to under a week. Boards sold as "low power" remove or switch off those parts.

Step 2: ESP32 deep sleep

In deep sleep, the ESP32 switches off its processors, Wi-Fi and most RAM. A small, low-power part keeps a timer running and wakes the chip later. Waking from deep sleep is like a full restart: setup() runs again from the top and loop() is never reached.

Normal variables are lost in deep sleep. A variable marked RTC_DATA_ATTR is kept in the small memory that stays powered, so it survives:

C++
const unsigned long long SLEEP_US = 10ULL * 1000000ULL;   // 10 seconds in microseconds

RTC_DATA_ATTR int bootCount = 0;    // survives deep sleep

void setup() {
  Serial.begin(115200);
  delay(500);
  bootCount++;
  Serial.printf("Boot number %d\n", bootCount);

  // ... read sensors and send the data here, quickly ...

  Serial.println("Going to sleep for 10 s");
  Serial.flush();                   // finish printing before sleeping
  esp_sleep_enable_timer_wakeup(SLEEP_US);
  esp_deep_sleep_start();           // nothing after this line runs
}

void loop() {
  // never reached
}

Over the first three wake-ups, the Serial Monitor shows (with the ESP32's own boot messages in between, which you can ignore):

Output
Boot number 1
Going to sleep for 10 s
Boot number 2
Going to sleep for 10 s
Boot number 3
Going to sleep for 10 s

Real projects sleep for minutes, not seconds. ULL makes the numbers 64-bit (unsigned long long), because the wake-up time is given in microseconds and 10 minutes is 600,000,000 µs. Keep the awake part short: connect to Wi-Fi, send, sleep.

Step 3: Measure the battery voltage

A lithium-ion cell is about 4.2 V when full and is considered empty around 3.0 to 3.3 V, depending on the cell and your circuit. That is above the ESP32's 3.3 V pin limit, so use a voltage divider with two equal resistors (for example 100 kΩ and 100 kΩ) to halve it. High values waste very little current.

PartPinConnects to
100 kΩ resistor (R1)Leg 1Battery + (after the protection board)
100 kΩ resistor (R1)Leg 2ESP32 GPIO 34 (ADC1)
100 kΩ resistor (R2)Leg 1ESP32 GPIO 34
100 kΩ resistor (R2)Leg 2GND

On the ESP32, analogReadMilliVolts(pin) returns a calibrated voltage in millivolts. Multiply by 2 to undo the divider, then estimate the charge level. Test the estimate on fixed values first:

C++
int batteryPercent(long millivolts) {
  long p = map(millivolts, 3300, 4200, 0, 100);   // rough straight-line estimate
  return constrain(p, 0, 100);
}

void setup() {
  Serial.begin(115200);
  delay(500);
  const long tests[] = {4200, 3750, 3300, 3000};
  for (int i = 0; i < 4; i++) {
    Serial.printf("%ld mV -> %d %%\n", tests[i], batteryPercent(tests[i]));
  }
  // live: long mv = analogReadMilliVolts(34) * 2;
}

void loop() {
}
Output
4200 mV -> 100 %
3750 mV -> 50 %
3300 mV -> 0 %
3000 mV -> 0 %

A lithium cell's voltage does not fall in a straight line, so this percentage is only a rough guide. It is still useful for a "low battery" warning sent before the device dies.

Choosing batteries and regulators

Power sourceGood forWatch out for
USB power bankEasy portable projects at 5 VMany switch off automatically when the current is very low, such as during deep sleep
3 or 4 AA cellsSimple, safe, easy to buy4 fresh alkaline cells can exceed 6 V; check what your parts accept
Single Li-ion cell (18650 or pouch) with protectionLong-life IoT nodesNeeds a proper charger module and protection circuit
9 V PP3 batteryVery littleLow capacity; most of it is wasted as heat in the regulator
Small solar panel + chargerOutdoor nodesPanel output drops sharply in shade, cloud and the monsoon

A voltage regulator turns a higher, varying battery voltage into a steady 3.3 V or 5 V. A linear regulator simply burns the extra voltage as heat; a switching (buck) regulator converts it much more efficiently. For battery projects, choose an efficient regulator with a low quiescent current (the current it uses itself, even with no load).

For a single lithium cell, use a charger module made for it, such as the common TP4056-based boards with a protection circuit (often labelled with a DW01 protection chip). These stop overcharging, over-discharging and short circuits. Follow the rules from Electronics Safety First: never charge damaged or swollen cells, never leave them charging unattended, and keep them out of direct sun.

Other ways to save power

  • Switch sensors off between readings by powering them through a transistor or MOSFET switch.
  • Turn off displays, or show the reading only when a button is pressed.
  • Send less often. Every 15 minutes is plenty for soil moisture; it changes slowly.
  • Send less data. Short messages mean shorter Wi-Fi time.
  • Remove or cut the power LED on boards you own, if you are comfortable soldering.

Common mistakes

  • Trusting the dev board for low power. Its USB chip and LED may use more than everything else combined.
  • Forgetting that deep sleep restarts the sketch. Code in loop() never runs; put the work in setup().
  • Connecting a 4.2 V battery straight to an ESP32 ADC pin. Use a divider.
  • Unprotected lithium cells and improvised charging. Use protected cells and a real charger module.
  • Power banks that switch off. Test that yours stays on at very low current.

Key points

  • Battery life in hours ≈ usable mAh ÷ average mA; plan with about 80% of the label capacity.
  • Duty cycling (short awake, long sleep) makes the sleep current the most important number.
  • ESP32 deep sleep restarts setup() on wake; RTC_DATA_ATTR variables survive.
  • Measure a lithium cell with a high-value divider and analogReadMilliVolts(); percentages are rough.
  • Use efficient regulators, protected lithium cells and proper charger modules.

Exercise

Ananya's weather node wakes every 15 minutes, stays awake 3 seconds at 120 mA, and sleeps at 0.2 mA on a low-power board. She has a 3,000 mAh lithium cell. Use the Python functions above to calculate the average current and the battery life in days with 80% usable capacity.

Show solution
Python
def average_ma(awake_ma, awake_s, sleep_ma, period_s):
    sleep_s = period_s - awake_s
    return (awake_ma * awake_s + sleep_ma * sleep_s) / period_s

def life_days(capacity_mah, avg_ma, usable=0.8):
    return capacity_mah * usable / avg_ma / 24

avg = average_ma(120, 3, 0.2, 15 * 60)
print(f"Average current: {avg:.3f} mA")
print(f"Battery life: {life_days(3000, avg):.0f} days")
Output
Average current: 0.599 mA
Battery life: 167 days

The awake part (120 × 3 = 360 mA-seconds) and the sleep part (0.2 × 897 ≈ 179 mA-seconds) are similar, so making either one smaller would noticeably extend the life.

Quick Quiz

1. A device averages 5 mA from a 2,000 mAh battery with 80% usable. About how long will it run?

2. What happens when an ESP32 wakes from deep sleep?

3. Which variable keeps its value through deep sleep?

4. Why does a typical dev board give poor battery life even with deep sleep?

Learn IoT & Arduino with a mentor

Free lessons get you started. Our IoT & Embedded Systems training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call