1600  Low-Power Design Strategies

1600.1 Learning Objectives

By the end of this chapter, you will be able to:

  • Implement effective sleep mode strategies for microcontrollers
  • Design duty-cycling schemes for optimal power efficiency
  • Apply voltage and frequency scaling techniques
  • Manage peripheral power consumption effectively
  • Configure and use wake-up sources appropriately
  • Optimize firmware for minimal energy consumption

1600.2 Low-Power Design Strategies

Achieving multi-year battery life requires systematic application of low-power design techniques at every level—hardware selection, circuit design, and firmware implementation.

1600.2.1 The Fundamental Principle: IoT Transceivers Are “Mostly Off”

ImportantThe Sleep-First Design Philosophy

A well-designed battery-powered IoT device spends 99%+ of its time in deep sleep. The goal is to:

  1. Minimize wake time - Complete all tasks as quickly as possible
  2. Maximize sleep depth - Use the lowest power mode that meets requirements
  3. Reduce wake frequency - Only wake when necessary

Rule of thumb: If your device is awake more than 1% of the time, you have optimization opportunities.

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#2C3E50', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#7F8C8D'}}}%%
pie title Time Distribution for 5-Year Battery Life
    "Deep Sleep (99.7%)" : 99.7
    "Active (0.3%)" : 0.3

Figure 1600.1: For 5-year battery life, devices typically spend over 99% of time in deep sleep

1600.2.2 Sleep Mode Hierarchy

Different sleep modes offer trade-offs between power savings and wake-up time:

Mode Typical Current Wake-up Time What’s Retained Wake Sources
Run 10-100 mA N/A Everything N/A
Idle 1-10 mA Immediate Everything Any interrupt
Light Sleep 100µA-1mA 10-100 µs RAM, registers Fast GPIO, timer
Deep Sleep 1-100 µA 100-500 µs RTC, wake logic RTC, GPIO, touch
Hibernate 0.1-10 µA 1-10 ms Wake logic only Limited GPIO
Shutdown 0.01-1 µA Full reboot Nothing Power button

1600.2.3 ESP32 Sleep Mode Example

#include "esp_sleep.h"

// Configure wake-up source (timer or GPIO)
esp_sleep_enable_timer_wakeup(3600 * 1000000ULL);  // 1 hour
esp_sleep_enable_ext0_wakeup(GPIO_NUM_33, 1);       // GPIO33 high

// Optional: Configure GPIO hold during sleep
gpio_hold_en(GPIO_NUM_LED);

// Enter deep sleep - does not return!
esp_deep_sleep_start();

// After wake-up, execution starts from beginning
// Check wake cause:
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
switch (cause) {
    case ESP_SLEEP_WAKEUP_TIMER:
        // Timer-triggered wake
        break;
    case ESP_SLEEP_WAKEUP_EXT0:
        // GPIO-triggered wake
        break;
    default:
        // First power-on or reset
        break;
}

1600.2.4 STM32 Low-Power Modes

#include "stm32l4xx_hal.h"

// Enter STOP2 mode (1.1 µA typical)
HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);

// Enter STANDBY mode (0.3 µA typical)
HAL_PWR_EnterSTANDBYMode();

// Enter SHUTDOWN mode (30 nA typical!)
HAL_PWREx_EnterSHUTDOWNMode();

// Wake-up configuration for STANDBY
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WUF1);

1600.3 Duty Cycling Strategies

1600.3.1 Time-Based Duty Cycling

Regular interval wake-ups for periodic sensing:

void duty_cycle_periodic(void) {
    while (1) {
        // Wake actions
        read_sensors();
        process_data();
        if (should_transmit()) {
            transmit_data();
        }

        // Calculate next wake time
        uint32_t sleep_us = INTERVAL_SECONDS * 1000000;

        // Enter deep sleep
        esp_sleep_enable_timer_wakeup(sleep_us);
        esp_deep_sleep_start();
    }
}

1600.3.2 Event-Driven Duty Cycling

Wake only when events occur:

void duty_cycle_event_driven(void) {
    // Configure accelerometer for motion detection
    config_accel_interrupt(MOTION_THRESHOLD);

    // Configure GPIO wake-up
    esp_sleep_enable_ext0_wakeup(ACCEL_INT_PIN, HIGH);

    while (1) {
        // Handle motion event
        read_motion_data();
        transmit_alert();

        // Clear interrupt and sleep
        clear_accel_interrupt();
        esp_deep_sleep_start();
    }
}

1600.3.3 Adaptive Duty Cycling

Adjust interval based on conditions:

uint32_t calculate_adaptive_interval(sensor_data_t* data) {
    static uint32_t base_interval = 60000;  // 60 seconds
    static float last_value = 0;

    float change = fabs(data->value - last_value);
    last_value = data->value;

    // High change rate: sample more frequently
    if (change > HIGH_CHANGE_THRESHOLD) {
        return base_interval / 4;  // 15 seconds
    }
    // Moderate change: standard interval
    else if (change > LOW_CHANGE_THRESHOLD) {
        return base_interval;      // 60 seconds
    }
    // Stable: extend interval
    else {
        return base_interval * 4;  // 4 minutes
    }
}

1600.3.4 Worked Example: Duty Cycle Optimization for 5-Year Battery Life

Scenario: You need to achieve 5-year battery life with a 2,400 mAh battery. Calculate the maximum duty cycle allowed.

Given:

  • Battery capacity: 2,400 mAh
  • Target lifetime: 5 years = 43,800 hours
  • Active current: 80 mA (sensor read + LoRa TX)
  • Sleep current: 10 µA

Step 1: Calculate maximum average current

\[I_{avg,max} = \frac{2400 \text{ mAh}}{43800 \text{ h}} = 0.0548 \text{ mA} = 54.8 \text{ µA}\]

Step 2: Calculate duty cycle

Using the duty cycle formula:

\[I_{avg} = I_{active} \times D + I_{sleep} \times (1 - D)\]

Where D is duty cycle (fraction of time active):

\[54.8 = 80000 \times D + 10 \times (1 - D)\] \[54.8 = 80000D + 10 - 10D\] \[44.8 = 79990D\] \[D = 0.00056 = 0.056\%\]

Step 3: Calculate active time per hour

\[T_{active} = 3600 \text{ s} \times 0.00056 = 2.02 \text{ seconds per hour}\]

Conclusion: To achieve 5-year battery life, the device can be active for only ~2 seconds per hour. This means:

  • Sensor reading must complete in <1 second
  • LoRa transmission must complete in <1 second
  • No room for Wi-Fi (connection alone takes 2-5 seconds)

Optimization Strategies:

  1. Use hot-start GPS (1s vs 30s cold start)
  2. Pre-compute LoRa packet while sensor is reading
  3. Use spread factor SF7 (fastest) if range permits
  4. Transmit only deltas/changes, not every reading

1600.4 Peripheral Power Management

1600.4.1 GPIO Power Consumption

WarningHidden GPIO Power Drains

Unused or misconfigured GPIOs can consume significant power:

Configuration Current Draw
Floating input 0-500 µA (oscillates)
Input with 10kΩ pull-up @ 3.3V 330 µA
Input with 100kΩ pull-up @ 3.3V 33 µA
Output low (no load) ~0 µA
Output high driving LED @ 20mA 20 mA
Internal pull-up enabled 10-50 µA

Best practice: Configure unused pins as output low or use internal pull-downs.

1600.4.2 Sensor Power Gating

Use GPIO or load switch to completely power off sensors:

#define SENSOR_POWER_PIN 25

void power_on_sensors(void) {
    gpio_set_level(SENSOR_POWER_PIN, HIGH);
    delay_ms(10);  // Sensor startup time
}

void power_off_sensors(void) {
    gpio_set_level(SENSOR_POWER_PIN, LOW);
}

void read_with_power_gating(void) {
    power_on_sensors();
    float temp = read_temperature();
    float humidity = read_humidity();
    power_off_sensors();  // Back to zero current!

    process_and_transmit(temp, humidity);
}

1600.4.3 Load Switch Selection

Parameter Low-Side Switch High-Side Switch Integrated Load Switch
Control MCU GPIO Level shifter needed Direct MCU GPIO
Quiescent Current 0 (MOSFET) 0 (MOSFET) 1-10 µA
Turn-on Time 1-10 µs 1-10 µs 10-100 µs
Inrush Limiting No No Often included
Example Parts 2N7002 Si2301 TPS22917, SIP32431
Cost $0.02 $0.05 $0.20-0.50

1600.4.4 Clock and Voltage Scaling

Dynamic Voltage and Frequency Scaling (DVFS) reduces power:

\[P_{dynamic} \propto C \times V^2 \times f\]

  • Halving frequency: 2× power reduction
  • Halving voltage: 4× power reduction
  • Halving both: 8× power reduction!
// ESP32 frequency scaling
#include "esp_pm.h"

// Configure power management
esp_pm_config_esp32_t pm_config = {
    .max_freq_mhz = 240,
    .min_freq_mhz = 80,    // Scale down when idle
    .light_sleep_enable = true
};
esp_pm_configure(&pm_config);

// STM32L4 low-power run mode (down to 2 MHz)
HAL_RCCEx_EnableLSECSS();
HAL_PWREx_EnableLowPowerRunMode();

1600.5 Wake-Up Source Configuration

1600.5.1 Timer Wake-Up

Most common for periodic sensing:

// ESP32: Wake after 1 hour
esp_sleep_enable_timer_wakeup(3600 * 1000000ULL);

// STM32: Wake using RTC alarm
RTC_AlarmTypeDef alarm = {0};
alarm.AlarmTime.Hours = 1;
alarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY;
HAL_RTC_SetAlarm_IT(&hrtc, &alarm, RTC_FORMAT_BIN);

1600.5.2 External GPIO Wake-Up

For event-driven wake:

// ESP32: Wake on GPIO 33 going HIGH
esp_sleep_enable_ext0_wakeup(GPIO_NUM_33, 1);

// ESP32: Wake on ANY of multiple pins
uint64_t mask = (1ULL << GPIO_NUM_33) | (1ULL << GPIO_NUM_34);
esp_sleep_enable_ext1_wakeup(mask, ESP_EXT1_WAKEUP_ANY_HIGH);

1600.5.3 Touch Wake-Up (ESP32)

// Configure touch pin for wake-up
touch_pad_config(TOUCH_PAD_NUM8, TOUCH_THRESHOLD);
esp_sleep_enable_touchpad_wakeup();

1600.6 Communication Protocol Optimization

1600.6.1 Minimize Transmissions

// BAD: Transmit every reading
void bad_transmit_pattern(void) {
    while (1) {
        float temp = read_temperature();
        transmit(temp);  // Every 60 seconds
        sleep(60);
    }
}

// GOOD: Transmit only on change or periodically
void good_transmit_pattern(void) {
    static float last_sent = 0;
    static int readings_since_tx = 0;

    while (1) {
        float temp = read_temperature();
        readings_since_tx++;

        bool significant_change = fabs(temp - last_sent) > 0.5;
        bool time_for_heartbeat = readings_since_tx >= 60;  // 1 hour

        if (significant_change || time_for_heartbeat) {
            transmit(temp);
            last_sent = temp;
            readings_since_tx = 0;
        }

        sleep(60);
    }
}

1600.6.2 Batch Transmissions

#define BATCH_SIZE 10
#define READING_INTERVAL 60  // seconds

float readings[BATCH_SIZE];
int reading_index = 0;

void batched_transmit_pattern(void) {
    // Read sensor
    readings[reading_index++] = read_temperature();

    // Transmit when batch is full
    if (reading_index >= BATCH_SIZE) {
        // One transmission for 10 readings!
        transmit_batch(readings, BATCH_SIZE);
        reading_index = 0;
    }

    sleep(READING_INTERVAL);
}

1600.6.3 Protocol Selection Guide

Requirement Best Protocol Reason
Ultra-low power, short range BLE 10-50 µJ per packet
Low power, long range LoRa Best range/power ratio
High bandwidth, power available Wi-Fi Fastest data transfer
Global coverage, no gateway Cellular (LTE-M/NB-IoT) Direct cloud connection
Mesh networking Thread/Zigbee Self-healing network

1600.7 Firmware Optimization Techniques

1600.7.1 Efficient Interrupt Handling

// BAD: Long interrupt handler
void IRAM_ATTR bad_isr(void) {
    read_sensor();           // DON'T: Slow I2C in ISR
    process_data();          // DON'T: Computation in ISR
    transmit_wireless();     // DON'T: Blocking TX in ISR
}

// GOOD: Minimal interrupt, deferred processing
volatile bool sensor_ready = false;

void IRAM_ATTR good_isr(void) {
    sensor_ready = true;     // Just set flag
}

void main_loop(void) {
    while (1) {
        if (sensor_ready) {
            sensor_ready = false;
            read_sensor();
            process_data();
            transmit_if_needed();
        }
        enter_light_sleep();
    }
}

1600.7.2 Optimize Memory Access Patterns

// BAD: Random access (cache misses)
for (int i = 0; i < 1000; i++) {
    result += data[random_index[i]];
}

// GOOD: Sequential access (cache friendly)
for (int i = 0; i < 1000; i++) {
    result += data[i];
}

1600.7.3 Avoid Busy Waiting

// BAD: Busy wait (wastes power)
while (!sensor_ready) {
    // CPU runs at full power doing nothing!
}

// BETTER: Sleep between checks
while (!sensor_ready) {
    delay_ms(10);  // May still waste power
}

// BEST: Interrupt-driven
enable_sensor_interrupt();
enter_sleep();  // Zero power until interrupt

1600.8 Knowledge Check

Question 1: Which THREE energy optimization techniques provide the GREATEST battery life improvement for IoT devices? (Select all that apply)

Deep sleep provides the largest improvement - reducing from 80mA idle to 10µA is an 8,000× reduction. This single change can improve battery life from days to years. Peripheral management and duty cycling provide the next largest improvements (10-100×). Compiler flags and frequency changes provide modest improvements (1.1-2×).

Question 2: For a device requiring 5-year battery life on a 2,400 mAh battery with 80mA active current and 10µA sleep current, what is the maximum duty cycle?

Maximum average current = 2400mAh / 43800h = 54.8µA. Using the duty cycle equation: 54.8 = 80000×D + 10×(1-D), solving gives D = 0.056%. This translates to about 2 seconds of active time per hour, which is extremely tight - requiring careful optimization of all active operations.

1600.9 Summary

Key low-power design strategies:

  1. Sleep First: Design for 99%+ time in deep sleep
  2. Match Sleep Mode to Requirements: Use deepest mode that still allows required wake sources
  3. Power Gate Peripherals: Completely shut off sensors when not in use
  4. Minimize Wake Time: Optimize every active operation for speed
  5. Event-Driven Design: Wake on interrupts rather than polling
  6. Batch and Filter: Reduce transmission frequency and payload size
  7. Choose Protocols Wisely: BLE for low power, LoRa for range, avoid Wi-Fi when possible

1600.10 What’s Next

Continue to Energy Measurement and Profiling to learn how to measure and validate your power optimization efforts.