Power Management
Power management is critical for battery-powered products. This guide covers ESP32 power consumption, sleep modes, battery selection, and optimization techniques.
ESP32 Power Consumption Overview
Current Draw by Mode
| Mode | Current | Description |
|---|---|---|
| Active (WiFi TX) | 160-260 mA | Transmitting WiFi data |
| Active (WiFi RX) | 80-95 mA | Receiving WiFi data |
| Active (BLE TX) | 27-40 mA | Transmitting BLE data |
| Active (CPU only) | 20-40 mA | No wireless, CPU running |
| Modem Sleep | 3-20 mA | WiFi disabled, CPU active |
| Light Sleep | 0.8 mA | CPU paused, RAM retained |
| Deep Sleep | 10 µA | RTC memory only |
| Hibernation | 2.5 µA | RTC timer only |
Measuring Power Consumption
For accurate measurements:
Equipment needed:
- Multimeter with µA range
- Current sense resistor (0.1-1Ω)
- Oscilloscope (for transients)
- USB power meter (basic measurements)
Important: Development boards have LEDs, voltage regulators, and USB bridges that consume 10-30mA. For accurate measurements, use the bare module.
Sleep Modes
Light Sleep
CPU paused, fast wake-up, RAM retained.
#include "esp_sleep.h"
void enterLightSleep(int seconds) {
// Configure wake-up source
esp_sleep_enable_timer_wakeup(seconds * 1000000ULL); // microseconds
Serial.println("Entering light sleep...");
Serial.flush();
// Enter light sleep
esp_light_sleep_start();
// Code continues here after wake-up
Serial.println("Woke up from light sleep!");
}
void setup() {
Serial.begin(115200);
delay(1000);
enterLightSleep(5); // Sleep for 5 seconds
}
void loop() {
Serial.println("Running...");
delay(2000);
enterLightSleep(5);
}
Deep Sleep
Lowest power with timer/GPIO wake-up. RTC memory preserved.
#include "esp_sleep.h"
// Store data in RTC memory (survives deep sleep)
RTC_DATA_ATTR int bootCount = 0;
void setup() {
Serial.begin(115200);
delay(1000);
bootCount++;
Serial.printf("Boot count: %d\n", bootCount);
// Print wake-up reason
printWakeupReason();
// Do your work here
Serial.println("Doing some work...");
delay(2000);
// Configure wake-up
esp_sleep_enable_timer_wakeup(10 * 1000000ULL); // 10 seconds
Serial.println("Going to deep sleep...");
Serial.flush();
esp_deep_sleep_start();
// Code after this never executes - ESP32 resets on wake
}
void printWakeupReason() {
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
switch (wakeup_reason) {
case ESP_SLEEP_WAKEUP_EXT0:
Serial.println("Wakeup: External signal (RTC_IO)");
break;
case ESP_SLEEP_WAKEUP_EXT1:
Serial.println("Wakeup: External signal (RTC_CNTL)");
break;
case ESP_SLEEP_WAKEUP_TIMER:
Serial.println("Wakeup: Timer");
break;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
Serial.println("Wakeup: Touchpad");
break;
case ESP_SLEEP_WAKEUP_ULP:
Serial.println("Wakeup: ULP program");
break;
default:
Serial.printf("Wakeup: Other (%d)\n", wakeup_reason);
break;
}
}
void loop() {
// Never reached in deep sleep pattern
}
GPIO Wake-up (Deep Sleep)
#include "esp_sleep.h"
#define WAKEUP_PIN GPIO_NUM_33 // Must be RTC GPIO
RTC_DATA_ATTR int bootCount = 0;
void setup() {
Serial.begin(115200);
bootCount++;
Serial.printf("Boot count: %d\n", bootCount);
// Configure GPIO wake-up
// ESP_EXT1_WAKEUP_ANY_HIGH: Wake when ANY pin goes HIGH
// ESP_EXT1_WAKEUP_ALL_LOW: Wake when ALL pins go LOW
// Single pin wake-up (ext0)
esp_sleep_enable_ext0_wakeup(WAKEUP_PIN, 1); // 1 = HIGH, 0 = LOW
// Or multiple pins (ext1)
// uint64_t mask = (1ULL << GPIO_NUM_33) | (1ULL << GPIO_NUM_32);
// esp_sleep_enable_ext1_wakeup(mask, ESP_EXT1_WAKEUP_ANY_HIGH);
Serial.println("Going to sleep. Press button to wake...");
Serial.flush();
esp_deep_sleep_start();
}
void loop() {}
Touch Pad Wake-up
#include "esp_sleep.h"
#define TOUCH_PIN T0 // GPIO 4
#define TOUCH_THRESHOLD 40
RTC_DATA_ATTR int bootCount = 0;
void callback() {
// Touch detected
}
void setup() {
Serial.begin(115200);
bootCount++;
Serial.printf("Boot count: %d\n", bootCount);
// Configure touch wake-up
touchAttachInterrupt(TOUCH_PIN, callback, TOUCH_THRESHOLD);
esp_sleep_enable_touchpad_wakeup();
Serial.println("Going to sleep. Touch pad to wake...");
Serial.flush();
esp_deep_sleep_start();
}
void loop() {}
Hibernation Mode
Absolute minimum power. Only RTC timer can wake.
#include "esp_sleep.h"
void setup() {
Serial.begin(115200);
Serial.println("Preparing for hibernation...");
// Only timer wake-up in hibernation
esp_sleep_enable_timer_wakeup(60 * 1000000ULL); // 60 seconds
// Disable all RTC peripherals for minimum power
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF);
Serial.println("Entering hibernation...");
Serial.flush();
esp_deep_sleep_start();
}
void loop() {}
Power Optimization Strategies
WiFi Power Optimization
#include <WiFi.h>
void optimizedWiFiConnect() {
// Disable WiFi power saving initially for fast connect
WiFi.setSleep(false);
WiFi.mode(WIFI_STA);
WiFi.begin("ssid", "password");
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
// Enable power saving after connection
WiFi.setSleep(true); // Modem sleep between beacon intervals
}
// For maximum power saving
void maxPowerSaving() {
// Configure WiFi for power saving
esp_wifi_set_ps(WIFI_PS_MAX_MODEM);
// Reduce TX power (less range, less power)
WiFi.setTxPower(WIFI_POWER_MINUS_1dBm); // Options: 19.5, 18.5, ... -1 dBm
}
CPU Frequency Scaling
void setup() {
Serial.begin(115200);
// Get current frequency
Serial.printf("CPU Frequency: %d MHz\n", getCpuFrequencyMhz());
// Set lower frequency for power saving
setCpuFrequencyMhz(80); // Options: 240, 160, 80, 40, 20, 10
Serial.printf("New CPU Frequency: %d MHz\n", getCpuFrequencyMhz());
}
Disabling Unused Peripherals
#include "driver/adc.h"
#include "esp_wifi.h"
#include "esp_bt.h"
void disableUnusedPeripherals() {
// Disable WiFi
esp_wifi_stop();
esp_wifi_deinit();
// Disable Bluetooth
esp_bt_controller_disable();
esp_bt_controller_deinit();
// Disable ADC
adc_power_release();
}
Efficient Data Transmission Pattern
#include <WiFi.h>
#include <HTTPClient.h>
#include "esp_sleep.h"
RTC_DATA_ATTR int readingCount = 0;
RTC_DATA_ATTR float readings[10];
void setup() {
Serial.begin(115200);
// Take reading
readings[readingCount++] = readSensor();
// Only transmit every 10 readings
if (readingCount >= 10) {
connectAndSendData();
readingCount = 0;
}
// Deep sleep for 5 minutes
esp_sleep_enable_timer_wakeup(5 * 60 * 1000000ULL);
esp_deep_sleep_start();
}
float readSensor() {
// Simulated sensor reading
return analogRead(34) * 0.001;
}
void connectAndSendData() {
WiFi.begin("ssid", "password");
while (WiFi.status() != WL_CONNECTED) delay(100);
HTTPClient http;
http.begin("http://your-server.com/data");
http.addHeader("Content-Type", "application/json");
// Build JSON with all readings
String json = "[";
for (int i = 0; i < 10; i++) {
if (i > 0) json += ",";
json += String(readings[i]);
}
json += "]";
http.POST(json);
http.end();
WiFi.disconnect(true);
}
void loop() {}
Battery Selection and Management
Battery Types Comparison
| Type | Voltage | Energy Density | Self-Discharge | Temp Range | Cost |
|---|---|---|---|---|---|
| LiPo (3.7V) | 3.0-4.2V | High | 5%/month | -20 to 60°C | Medium |
| Li-Ion (18650) | 2.5-4.2V | High | 2-3%/month | -20 to 60°C | Low |
| LiFePO4 | 2.5-3.65V | Medium | 3%/month | -20 to 70°C | Medium |
| AA Alkaline | 1.5V (×2=3V) | Medium | 2%/year | -18 to 55°C | Very Low |
| AA Lithium | 1.5V (×2=3V) | High | <1%/year | -40 to 60°C | Medium |
| CR123A | 3V | Medium | <1%/year | -40 to 60°C | Medium |
Battery Capacity Calculation
Runtime = Battery Capacity (mAh) / Average Current (mA)
Example:
- 2000mAh LiPo battery
- Deep sleep: 10µA for 59 minutes
- Active WiFi: 150mA for 1 minute (each hour)
Hourly consumption:
- Sleep: 0.01mA × 59min = 0.59mAh
- Active: 150mA × 1min = 2.5mAh
- Total: ~3.1mAh per hour
Runtime: 2000mAh / 3.1mAh = ~645 hours = ~27 days
LiPo Battery Circuit
┌──────────────┐
│ TP4056 │
USB 5V ────────────►│ Charging │
│ Module │
└──────┬───────┘
│
┌──────┴───────┐
│ LiPo │
│ Battery │
│ 3.7V │
└──────┬───────┘
│
┌──────┴───────┐
│ HT7333 │──────► 3.3V to ESP32
│ or AMS1117 │
└──────────────┘
Battery Voltage Monitoring
#define VBAT_PIN 35 // ADC pin
#define VBAT_DIVIDER_RATIO 2.0 // If using voltage divider
// Calibration factor (adjust based on measurement)
#define ADC_CALIBRATION 1.073
float readBatteryVoltage() {
int raw = 0;
// Average multiple readings
for (int i = 0; i < 10; i++) {
raw += analogRead(VBAT_PIN);
delay(10);
}
raw /= 10;
// Convert to voltage
float voltage = (raw / 4095.0) * 3.3 * VBAT_DIVIDER_RATIO * ADC_CALIBRATION;
return voltage;
}
int getBatteryPercentage(float voltage) {
// LiPo discharge curve (approximate)
// 4.2V = 100%, 3.0V = 0%
if (voltage >= 4.2) return 100;
if (voltage <= 3.0) return 0;
// Non-linear mapping
if (voltage >= 4.0) return map(voltage * 100, 400, 420, 80, 100);
if (voltage >= 3.7) return map(voltage * 100, 370, 400, 30, 80);
return map(voltage * 100, 300, 370, 0, 30);
}
void setup() {
Serial.begin(115200);
// Configure ADC
analogReadResolution(12);
analogSetAttenuation(ADC_11db);
}
void loop() {
float voltage = readBatteryVoltage();
int percent = getBatteryPercentage(voltage);
Serial.printf("Battery: %.2fV (%d%%)\n", voltage, percent);
// Low battery warning
if (voltage < 3.3) {
Serial.println("WARNING: Low battery!");
// Take action: send alert, deep sleep, etc.
}
delay(5000);
}
Voltage Divider for Battery Monitoring
VBAT (3.0-4.2V)
│
├── 100kΩ ──┬── To ADC (GPIO 35)
│ │
└─────────── 100kΩ
│
GND
Divider ratio: 2:1
ADC sees: 1.5V - 2.1V
Important: Use high-value resistors (100kΩ+) to minimize battery drain.
Power Supply Design
LDO vs Switching Regulator
| Feature | LDO | Switching (Buck) |
|---|---|---|
| Efficiency | 60-80% | 85-95% |
| Noise | Very low | Higher |
| Heat | More | Less |
| Size | Smaller | Larger |
| Cost | Lower | Higher |
| Use When | Low current, clean power | Battery-powered, high current |
Recommended Regulators
LDO (Linear):
- AMS1117-3.3: 1A, dropout 1.2V
- HT7333: 250mA, dropout 0.1V, low quiescent current
- AP2112K-3.3: 600mA, low dropout, low noise
Switching (Buck):
- MP2359: 1.2A, 92% efficient
- TPS62200: 300mA, 95% efficient, tiny
- RT8059: 600mA, very low quiescent
Decoupling Capacitors
3.3V ──┬── 10µF ──┬── ESP32 VDD
│ │
GND 100nF
│
GND
Place 100nF as close to ESP32 as possible.
10µF can be slightly further away.
Solar Power Integration
Basic Solar Setup
┌──────────────┐
Solar Panel ───────►│ CN3065 │
(5-6V, 1W+) │ or TP4056 │
│ with solar │
│ input │
└──────┬───────┘
│
┌──────┴───────┐
│ LiPo │
│ Battery │
└──────┬───────┘
│
┌──────┴───────┐
│ 3.3V LDO │──────► ESP32
└──────────────┘
Solar Sizing
Daily energy needed:
- Deep sleep 23 hours: 0.01mA × 23h = 0.23mAh
- Active 1 hour total: 100mA × 1h = 100mAh
- Total: ~100mAh/day @ 3.3V = 330mWh
Solar panel sizing (assuming 4 peak sun hours):
- Account for 50% system losses
- Required: 330mWh / 4h / 0.5 = 165mW panel minimum
- Recommended: 250-500mW panel for margin
Solar Power Code
#include "esp_sleep.h"
#define SOLAR_PIN 34
#define VBAT_PIN 35
RTC_DATA_ATTR int bootCount = 0;
bool hasSufficientPower() {
float batteryVoltage = readBatteryVoltage();
float solarVoltage = analogRead(SOLAR_PIN) * (3.3 / 4095.0) * 2; // With divider
Serial.printf("Battery: %.2fV, Solar: %.2fV\n", batteryVoltage, solarVoltage);
// Need battery above 3.5V or active solar charging
return (batteryVoltage > 3.5) || (solarVoltage > 4.5);
}
void setup() {
Serial.begin(115200);
bootCount++;
if (hasSufficientPower()) {
// Do work
doWork();
// Short sleep when solar available
esp_sleep_enable_timer_wakeup(5 * 60 * 1000000ULL); // 5 minutes
} else {
// Long sleep to conserve battery
Serial.println("Low power - extended sleep");
esp_sleep_enable_timer_wakeup(30 * 60 * 1000000ULL); // 30 minutes
}
esp_deep_sleep_start();
}
void doWork() {
// Connect, send data, etc.
}
float readBatteryVoltage() {
return analogRead(VBAT_PIN) * (3.3 / 4095.0) * 2; // With voltage divider
}
void loop() {}
Power Budget Template
Use this template to estimate battery life:
┌─────────────────────────────────────────────────────────────┐
│ POWER BUDGET │
├──────────────────┬──────────┬──────────┬───────────────────┤
│ Mode │ Current │ Duration │ Energy (mAh) │
├──────────────────┼──────────┼──────────┼───────────────────┤
│ Deep Sleep │ 10 µA │ 58 min │ 0.01 │
│ Wake + Sensor │ 30 mA │ 100 ms │ 0.0008 │
│ WiFi Connect │ 150 mA │ 3 sec │ 0.125 │
│ WiFi TX/RX │ 180 mA │ 2 sec │ 0.1 │
│ WiFi Disconnect │ 50 mA │ 100 ms │ 0.0014 │
├──────────────────┼──────────┼──────────┼───────────────────┤
│ Total per cycle │ │ ~1 hour │ 0.237 mAh │
│ Daily (24 cycles)│ │ 24 hours │ 5.7 mAh │
├──────────────────┴──────────┴──────────┴───────────────────┤
│ Battery: 2000 mAh LiPo │
│ Estimated Runtime: 2000 / 5.7 = 350 days │
│ With 80% usable capacity: 280 days │
└─────────────────────────────────────────────────────────────┘
Production Power Considerations
Development vs Production Current
| Component | DevKit Current | Production Module |
|---|---|---|
| USB-UART chip | 15-20 mA | Not present |
| Power LED | 2-5 mA | Remove or change |
| LDO quiescent | 5-10 mA | 1-5 µA (better LDO) |
| Pull-ups/downs | Variable | Optimize values |
Checklist for Low-Power Products
- Use bare module, not DevKit for final power measurements
- Choose low-quiescent LDO (HT7333, AP2112)
- Remove or control indicator LEDs
- Use high-value pull resistors (100kΩ+)
- Disable unused peripherals in code
- Optimize WiFi connection time (static IP, channel hints)
- Batch data transmissions instead of frequent small ones
- Use ESP32-C3 or H2 for lowest power WiFi/BLE