I2C at 400kHz works out of the box. At 1MHz Fast-mode Plus, signal integrity becomes critical. Let’s analyze the electrical requirements and optimize for reliable high-speed communication.

Rise Time Requirements

I2C rise time must meet spec for the target frequency:

// I2C timing requirements (I2C specification)
// Standard Mode (100kHz):  tr_max = 1000ns
// Fast Mode (400kHz):      tr_max = 300ns  
// Fast Mode Plus (1MHz):   tr_max = 120ns

#define VDD             3.3f        // Supply voltage
#define VOL             0.4f        // Low output voltage
#define VIH             0.7f * VDD  // Input high threshold (2.31V)
#define VIL             0.3f * VDD  // Input low threshold (0.99V)

float calculate_rise_time(float r_pullup, float c_bus) {
    // Rise time from VOL to VIH (10% to 70% of VDD)
    // t_r = -RC * ln((VDD - VIH) / (VDD - VOL))
    return -r_pullup * c_bus * logf((VDD - VIH) / (VDD - VOL));
}

float calculate_optimal_pullup(float c_bus, float tr_target) {
    // Solve for R given target rise time
    return -tr_target / (c_bus * logf((VDD - VIH) / (VDD - VOL)));
}
📊

Pull-up Resistor Selection for Different Bus Capacitances

Bus Capacitance100kHz R_pu400kHz R_pu1MHz R_pu
50pF 10kΩ 4.7kΩ 1.5kΩ
100pF 6.8kΩ 2.7kΩ 1kΩ
200pF 4.7kΩ 1.8kΩ 680Ω
400pF (max) 2.4kΩ 1kΩ N/A
Note: Calculated for 3.3V, meeting spec rise times with margin
⚠️ Current Limits

Lower pull-up resistance increases IOL sink current. I2C spec limits IOL to 3mA (Standard) or 20mA (Fast Mode Plus). With 680Ω at 3.3V: I = 3.3V/680Ω = 4.85mA—verify your device supports this.

Bus Capacitance Measurement

// Measure bus capacitance using RC time constant
void measure_bus_capacitance(void) {
    // Configure GPIO as output, drive low
    gpio_set_direction(SDA_PIN, GPIO_MODE_OUTPUT);
    gpio_set_level(SDA_PIN, 0);
    delay_us(10);  // Discharge bus
    
    // Switch to input (high-Z), measure rise time
    uint32_t start = get_timer_us();
    gpio_set_direction(SDA_PIN, GPIO_MODE_INPUT);
    
    // Wait for line to reach VIH
    while (!gpio_get_level(SDA_PIN) && (get_timer_us() - start) < 1000);
    uint32_t rise_time_us = get_timer_us() - start;
    
    // C = -t_r / (R * ln((VDD - VIH) / VDD))
    // With known pull-up R, calculate C
    float c_bus_pf = (-rise_time_us * 1e-6f) / 
                     (KNOWN_PULLUP * logf((3.3f - 2.31f) / 3.3f)) * 1e12f;
    
    printf("Measured rise time: %lu us\n", rise_time_us);
    printf("Estimated bus capacitance: %.0f pF\n", c_bus_pf);
}

Noise Immunity Techniques

1. Schmitt Trigger Inputs

Most modern MCUs have Schmitt trigger inputs on I2C pins. Verify hysteresis:

// Check I2C pin configuration for Schmitt trigger
void configure_i2c_gpio(void) {
    // STM32 example: Enable Schmitt trigger
    GPIOB->MODER |= (GPIO_MODER_MODE6_1 | GPIO_MODER_MODE7_1);  // AF mode
    GPIOB->OTYPER |= (GPIO_OTYPER_OT6 | GPIO_OTYPER_OT7);       // Open-drain
    
    // Schmitt trigger is default on STM32 I2C pins
    // Typical hysteresis: 200-400mV
}

2. Digital Filtering

Enable peripheral-level digital filters:

// STM32 I2C analog and digital filters
void configure_i2c_filters(I2C_TypeDef* i2c) {
    // Enable analog filter (default on)
    i2c->CR1 &= ~I2C_CR1_ANFOFF;
    
    // Configure digital filter (0-15 clock cycles)
    // Higher values = more noise immunity, but added latency
    i2c->CR1 &= ~I2C_CR1_DNF_Msk;
    i2c->CR1 |= (4 << I2C_CR1_DNF_Pos);  // 4 clock cycles
    
    // Digital filter adds latency: t_filter = DNF * t_I2CCLK
    // At 48MHz I2CCLK, DNF=4: 83ns filter delay
}

3. Ground Plane Design

PCB Layout Checklist for I2C:
✓ Continuous ground plane under I2C traces
✓ Pull-ups close to master device
✓ Minimize trace length between devices
✓ Keep SDA/SCL traces parallel, same length
✓ Avoid crossing high-speed digital signals
✓ Add 100nF decoupling at each device

Clock Stretching Handling

Slaves may stretch the clock. Handle robustly:

// I2C clock stretching timeout
#define CLOCK_STRETCH_TIMEOUT_US    1000

bool wait_for_clock_release(void) {
    uint32_t start = get_timer_us();
    
    while (!gpio_get_level(SCL_PIN)) {
        if (get_timer_us() - start > CLOCK_STRETCH_TIMEOUT_US) {
            // Clock stuck low - bus error
            i2c_bus_reset();
            return false;
        }
    }
    return true;
}

void i2c_bus_reset(void) {
    // Generate 9 clock pulses to release stuck slave
    gpio_set_direction(SDA_PIN, GPIO_MODE_INPUT);  // Release SDA
    
    for (int i = 0; i < 9; i++) {
        gpio_set_direction(SCL_PIN, GPIO_MODE_OUTPUT);
        gpio_set_level(SCL_PIN, 0);
        delay_us(5);
        gpio_set_direction(SCL_PIN, GPIO_MODE_INPUT);  // Release high
        delay_us(5);
        
        if (gpio_get_level(SDA_PIN)) {
            break;  // SDA released
        }
    }
    
    // Generate STOP condition
    generate_stop_condition();
}

I2C Throughput vs Speed Mode

(KB/s)
Standard (100kHz)
10 KB/s
Fast (400kHz)
40 KB/s
Fast Plus (1MHz)
100 KB/s
High Speed (3.4MHz)
340 KB/s

Practical Test Setup

// I2C signal quality validation
void validate_i2c_signals(void) {
    // Test 1: Rise time measurement
    float tr = measure_rise_time();
    printf("Rise time: %.0f ns (max 120ns for FM+)\n", tr * 1e9);
    
    // Test 2: Data transfer test
    uint8_t test_data[256];
    for (int i = 0; i < 256; i++) test_data[i] = i;
    
    uint32_t errors = 0;
    for (int iter = 0; iter < 1000; iter++) {
        i2c_write(SLAVE_ADDR, test_data, 256);
        
        uint8_t read_back[256];
        i2c_read(SLAVE_ADDR, read_back, 256);
        
        if (memcmp(test_data, read_back, 256) != 0) {
            errors++;
        }
    }
    
    printf("Transfer test: %lu errors in 1000 iterations\n", errors);
    printf("Error rate: %.2f%%\n", errors / 10.0f);
}

Conclusion

Reliable 1MHz I2C requires:

  1. Correct pull-up sizing: Calculate for your bus capacitance
  2. Signal integrity: Good ground plane, short traces
  3. Filtering: Enable analog and digital filters
  4. Robust error handling: Timeout and bus reset logic

Start at 400kHz, validate signals with oscilloscope, then increase to 1MHz if margins allow.