Chapter 6: Power Optimization

Measuring Before Optimizing

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:

  1. Full-speed, all peripherals on
  2. After reducing CPU frequency
  3. After disabling unused peripherals
  4. During sleep
  5. Total average current over one sleep/wake cycle

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.


CPU Clock Scaling

Arduino (AVR)

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.

ESP32

#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.


Disabling Unused Peripherals

Arduino (AVR)

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.

ESP32

// 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 Power Optimization (ESP32)

WiFi is by far the largest power consumer on the ESP32. Strategies to minimize its impact:

Reduce Transmission Time

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.

Reduce TX Power

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

Use MQTT or UDP Instead of HTTP

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.

Modem Sleep Between Transmissions

If you need to send data every few seconds without full deep sleep:

WiFi.setSleep(true);
// ESP32 automatically enables modem sleep

Reducing Active Time

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:


External Hardware Power Control

For 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.


Pull-Up and Pull-Down Resistors

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);

Summary: Power Budget Checklist


Previous: Power Modes Next: Practical Examples Home