Before applying any optimization, measure current draw at each stage of your application. A cheap INA219 module on the power rail gives real-time current and power readings over I2C. A bench power supply with current display is even better.
Typical baseline measurements to take:
Average current over one cycle determines battery life:
Average current = (I_active × t_active + I_sleep × t_sleep) / (t_active + t_sleep)
For a 10-second cycle with 500 ms active at 80 mA and 9.5 s sleep at 50 µA:
I_avg = (80000 µA × 0.5 + 50 µA × 9.5) / 10 = (40000 + 475) / 10 ≈ 4047 µA ≈ 4 mA
A 2000 mAh battery would last: 2000 mAh / 4 mA = 500 hours ≈ 20 days.
The ATmega328P runs at 16 MHz on Arduino Uno (8 MHz on 3.3 V boards like Pro Mini). You can divide the clock by prescaling:
#include <avr/power.h>
clock_prescale_set(clock_div_8); // run at 2 MHz (16/8)
// ... do slow work ...
clock_prescale_set(clock_div_1); // restore 16 MHz
Note: at lower clock frequencies, UART baud rate calculations must be recomputed. Lower the CPU clock only when serial communication is not needed.
#include "esp32-hal-cpu.h"
setCpuFrequencyMhz(80); // 240, 160, 80, 40, 20, 10 MHz
Serial.printf("CPU: %d MHz\n", getCpuFrequencyMhz());
80 MHz is a good balance for most work. Drop to 40 MHz for simple sensor reads with no radio. The minimum stable frequency with WiFi is 80 MHz.
Each peripheral draws a small standby current. Disable unused ones:
#include <avr/power.h>
// Disable SPI, I2C, UART, ADC, Timer1, Timer2
power_spi_disable();
power_twi_disable();
power_usart0_disable();
power_adc_disable();
power_timer1_disable();
power_timer2_disable();
Re-enable before use: power_spi_enable(), etc.
// Disable WiFi and Bluetooth when not needed
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
btStop();
// Disable specific clocks via esp_pm
// (advanced: use menuconfig CONFIG_PM_* options)
Turning off WiFi saves ~60–80 mA when it was idle, and ~200 mA when transmitting.
WiFi is by far the largest power consumer on the ESP32. Strategies to minimize its impact:
Connect, send data, disconnect as fast as possible. Avoid waiting for slow DHCP or DNS if you can use static IP:
IPAddress ip(192, 168, 1, 50);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
WiFi.config(ip, gateway, subnet);
WiFi.begin(ssid, password);
Static IP skips DHCP negotiation, saving ~200 ms of radio-on time.
The default TX power is 20 dBm (100 mW). If your device is close to the router, reduce it:
WiFi.setTxPower(WIFI_POWER_11dBm); // options: 2dBm to 20dBm
HTTP requests involve multiple round-trips (TCP connect, TLS handshake if HTTPS). A single MQTT publish or UDP packet takes less time with the radio on.
If you need to send data every few seconds without full deep sleep:
WiFi.setSleep(true);
// ESP32 automatically enables modem sleep
The faster your code runs, the sooner you can go to sleep. Profile your loop() or task function:
unsigned long t0 = micros();
doWork();
Serial.printf("Work took %lu µs\n", micros() - t0);
Common slow operations to optimize:
Serial.print() — buffer and send in bulk, or disable in productiondelay() — replace with sleep when possibleFor sensors or modules that consume significant idle current, control their power via a GPIO and a MOSFET or load switch:
ESP32 GPIO → N-MOSFET gate → sensor VCC
sensor GND
Turn the sensor on only for the duration of a reading:
#define SENSOR_PWR_PIN 25
void takeMeasurement() {
digitalWrite(SENSOR_PWR_PIN, HIGH); // power on sensor
delay(100); // wait for stabilization
int val = analogRead(34);
digitalWrite(SENSOR_PWR_PIN, LOW); // power off sensor
}
This is especially effective for components like GPS modules (40–80 mA) or cellular modems.
Floating inputs cause the input buffer to oscillate, consuming extra current. Always define a state for unused pins:
// On AVR: enable internal pull-ups on unused pins
DDRB &= ~0xFF; // all B pins as input
PORTB |= 0xFF; // enable pull-ups
// On ESP32
gpio_set_pull_mode(GPIO_NUM_X, GPIO_PULLDOWN_ONLY);
| Previous: Power Modes | Next: Practical Examples | Home |