The Power Cost of Fast vs. Slow I2C

Battery-powered Internet of Things (IoT) devices spend most of their operating lives in deep sleep modes, waking up periodically to read sensors and broadcast telemetry. Minimizing the energy consumed during these active windows directly extends battery life. While developers often focus on optimizing wireless transmission power, the peripheral bus configuration also impacts the system energy budget. This article isolates the electrical impact of Inter-Integrated Circuit (I2C) clock frequency configurations on the Espressif ESP32-C6 DevKitC, specifically comparing Standard-mode (100 kHz) against Fast-mode (400 kHz) during intensive data transmissions.

Why I2C Speed Matters

In low-power firmware design, the fundamental objective is to minimize CPU uptime. While an I2C transaction is executing, the ESP32-C6 is held hostage. It cannot enter a low-power sleep state and must keep its high-speed internal oscillators, CPU, and peripheral clock trees fully powered.

Increasing your bus speed from 100 kHz to 400 kHz cuts the physical time spent driving the wire by roughly 75%. This is a classic “race to sleep” strategy: by running the peripheral bus at its performance limit, you clear the transmission hardware constraints faster, allowing the RTOS kernel to instantly scale down clock gates and drop the core back into a low-power sleep state.

However, to realize these macro-level power savings in code and prevent the driver from stalling mid-burst, your firmware should also implement the following optimizations:

  1. Continuous Buffering: Replace repetitive individual byte-probing loops with a single, continuous multi-byte hardware transmission to eliminate software function-call overhead.
  2. Power Management Locking: Acquire a strict CPU frequency lock during the burst to prevent the FreeRTOS scheduler from attempting mid-transaction sleep context switches as the hardware FIFO transitions.
  3. Dedicated Clock Sources: Explicitly isolate the peripheral clock tree from the system bus by tracking a stable internal oscillator source.

Test Firmware

The Espressif ESP-IDF framework manages peripheral configuration through structured driver handles. It offers the i2c_master_bus_config_t and i2c_device_config_t structures within the ESP-IDF I2C Master Driver that allow you to instantiate the bus and define operating parameters.

To accurately benchmark the true electrical performance of the bus speed, you should not just test one I2C transaction, but a bundle (e.g. 100). This way you can magnify the microsecond differences between 100 and 400 kHz to millisecond scale, which are much easier to visualize.

The following code example initializes the I2C master peripheral on an ESP32-C6 DevKitC using a stable clock source (I2C_CLK_SRC_RC_FAST) and transmits an unbroken 100-byte hardware block. To keep power usage low, it uses the optimizations described in the previous section, including continuous buffering and power management locking. With this code and the Nordic Power Profiler Kit II (PPK2), you can see for yourself how much power your board uses at different I2C speeds.

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c_master.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_pm.h" 
#include "esp_err.h"

// Hardware Layout Definitions
#define I2C_MASTER_SDA_IO           18    
#define I2C_MASTER_SCL_IO           19    
#define I2C_MASTER_NUM              0     

// Bus Configuration Parameters 
// Note: Adjust I2C_TARGET_SPEED_HZ to 400000 to benchmark Fast Mode profiling.
#define I2C_TARGET_SPEED_HZ         100000  
#define I2C_TARGET_ADDR             0x48    
#define I2C_POWER_TIMEOUT_MS        5

// Global handle for the power management lock
esp_pm_lock_handle_t cpu_lock;

// Initialize the system power management configuration and instantiate
// the CPU frequency lock to enforce clock stability during bursts.
void init_power_management(void) {
    // Configure the power management algorithm to allow automatic light sleep
    esp_pm_config_t pm_config = {
        .max_freq_mhz = 160,
        .min_freq_mhz = 40,
        // Allows the core to enter light sleep during idle tasks
        .light_sleep_enable = true 
    };
    ESP_ERROR_CHECK(esp_pm_configure(&pm_config));

    // Create a CPU frequency lock that keeps the system clock pinned at max frequency when acquired
    ESP_ERROR_CHECK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "i2c_burst_lock", &cpu_lock));
}

// Main application entry point. Initializes the GPIO matrix, sets up the
// I2C master driver architecture, and hosts the continuous benchmarking loop.
void app_main(void)
{
    // Clear and reset previous pin matrix hardware states
    gpio_reset_pin(I2C_MASTER_SDA_IO);
    gpio_reset_pin(I2C_MASTER_SCL_IO);
    
    // Enforce explicit Open-Drain configuration with input capabilities for the I2C physical lines
    gpio_set_direction(I2C_MASTER_SDA_IO, GPIO_MODE_INPUT_OUTPUT_OD);
    gpio_set_direction(I2C_MASTER_SCL_IO, GPIO_MODE_INPUT_OUTPUT_OD);

    // Establish the configuration parameters for the parent I2C Master Bus controller
    i2c_master_bus_config_t bus_config = {
        // Forced stable clock source for power transitions
        .clk_source = I2C_CLK_SRC_RC_FAST, 
        .i2c_port = I2C_MASTER_NUM,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .glitch_ignore_cnt = 7,
        // Enabled to stabilize voltage planes during low-power state wake transitions
        .flags.enable_internal_pullup = true,  
    };

    // Allocate and register the main master bus handle context
    i2c_master_bus_handle_t bus_handle;
    ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &bus_handle));

    // Define the structural device properties of the hardware target endpoint
    i2c_device_config_t dev_config = {
        .dev_addr_length = I2C_ADDR_BIT_LEN_7,
        .device_address = I2C_TARGET_ADDR,
        .scl_speed_hz = I2C_TARGET_SPEED_HZ,
    };

    // Register the target device logical handle into the parent master bus container
    i2c_master_dev_handle_t nordic_target_handle;
    ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_config, &nordic_target_handle));

    // Initialize power management and instantiate our PM lock
    init_power_management();

    // Allocate a single array containing all 100 bytes at once to stream via hardware
    uint8_t burst_buffer[100];
    memset(burst_buffer, 0x00, sizeof(burst_buffer));

    // Infinite execution loop handling periodic high-speed data burst tracking.
    // Transitions aggressively between deep processing state locks and automatic low-power sleep mode.
    while (1) {
        // 1ms stabilization window cushion after waking up before hammering the bus interface
        vTaskDelay(pdMS_TO_TICKS(1));
        
        // Acquire the clock lock to force the CPU frequency to 160MHz and prevent mid-burst sleep
        esp_pm_lock_acquire(cpu_lock);

        // Transmit all 100 bytes in a single hardware transaction.
        // This eliminates the 100x function call overhead entirely.
        i2c_master_transmit(nordic_target_handle, burst_buffer, 100, I2C_POWER_TIMEOUT_MS);

        // Release the frequency lock to permit clock tree scaling and power-saving sleep gates
        esp_pm_lock_release(cpu_lock);
        
        // Yield to the scheduler for 4 seconds, allowing the idle task to drop the core into Light Sleep
        vTaskDelay(pdMS_TO_TICKS(4000));
    }
}

Benchmarking Results

Switching from 100 kHz to 400 kHz fundamentally alters the balance between a device’s active current draw and its transaction duration. While a higher clock frequency requires the internal oscillators and peripheral clock trees to run faster, compressing the time window spent driving the bus prevents the CPU from being held hostage in a high-power state. The result is that at 400 kHz, you should see your device draw less current for less time, ultimately using less power.

Standard Mode (100 kHz) Profile

If you run the code above, you may see a power curve like the following.

PPK2 power trace showing I2C transaction at 100 kHz Standard Mode with 8.5ms active window

Fast Mode (400 kHz) Profile

If you run the same code with the I2C_TARGET_SPEED_HZ macro adjusted to 400 kHz, you may see a power curve like the following.

PPK2 power trace showing I2C transaction at 400 kHz Fast Mode with 4.5ms active window

By leveraging a single continuous hardware transaction and maintaining a stable clock lock, you should see Fast Mode collapse your active window energy footprint by 52 percent.

Bus ConfigurationPhysical Peak WidthTotal Electrical Charge
Standard Mode (100 kHz)8.5 ms165.22 µC
Fast Mode (400 kHz)4.5 ms79.50 µC

Deploying to Production

Deploying fast I2C into production requires moving beyond standard IDE desktop tests and validating the physical limits of the circuit board layout. To ensure long-term stability and optimal battery runtime in production, follow the three guidelines:

  • Validate the Physical RC Time Constant: Ensure production pull-up resistors are sized correctly for your total bus capacitance (factoring in PCB traces, connector vias, and the number of target nodes). If the lines rise too slowly, your firmware will experience random driver timeouts or corrupted data frames at 400 kHz.
  • Lock the Power Gates Automatically: Embed power management locks explicitly around multi-byte peripheral transactions. This shields your communication routines from RTOS sleep interventions mid-burst, ensuring your communication finishes cleanly and predictably.
  • Maximize Streaming Efficiency: Always bundle data payloads into continuous hardware-driven arrays rather than executing byte-by-byte function loops. Eliminating master-side software context-switching enables a true “race to sleep” strategy, allowing your microcontroller to shut down its clock trees and maximize its operating lifespan in deep sleep mode.

Just changing your I2C bus speed can cut your power consumption by more than 50% on the ESP32-C6. As massive satellite and cellular IoT networks continue to roll out over the next few years, squeezing every microcoulomb out of these sensors is going to be the secret to keeping remote gear running on the same battery for a decade.


Ready to connect your devices anywhere on Earth? Get started with Hubble Network for free →