I2C (Inter-Integrated Circuit) is an open-drain bus. This single physical reality dictates everything about how you wire, scan, and debug it. Because devices can only pull the signal lines low (to ground) and cannot drive them high, the bus relies entirely on external pull-up resistors to return the lines to VCC. If you skip the physical layer details, your i2c scanner sketch will return nothing but timeouts and phantom addresses.

The Physical Layer: Wiring and Pull-Up Requirements

Before writing a single line of code, you must establish the bus physics. The SDA (data) and SCL (clock) lines must be pulled up to the logic high voltage (usually 3.3V or 5V) via resistors. The NXP I2C-bus specification (UM10204) dictates a maximum bus capacitance of 400 pF. Long wires and multiple devices add parasitic capacitance, which slows the rise time of the signal when the pull-up resistor tries to bring the line high.

Resistor Selection Rule of Thumb:
  • 4.7 kΩ: Standard for 100 kHz and 400 kHz buses with short wires (<30 cm) and 3.3V or 5V logic.
  • 2.2 kΩ: Required for 1 MHz (Fast-mode Plus) or when bus capacitance approaches 300 pF.
  • 10 kΩ: Only use for ultra-low power battery applications at 100 kHz; rise times will fail at 400 kHz.

Many breakout boards (like the Adafruit BME280 or SparkFun MPU6050) include onboard 10 kΩ pull-ups. If you daisy-chain three of these, you have 3.3 kΩ in parallel, which is usually fine. But if you are wiring raw ICs or level shifters, you must add discrete 4.7 kΩ resistors from SDA to VCC and SCL to VCC.

I2C Bus Mechanics and Protocol Alternatives

Understanding where I2C fits in the embedded ecosystem prevents architectural mistakes. Use I2C for low-speed sensor networks on a single PCB or short breadboard runs. Switch protocols when your constraints change.

I2C Bus Mechanics Specification
ParameterStandard ModeFast ModeFast+ / High Speed
Wires Required2 (SDA, SCL) + Common GND
Clock Speed100 kHz400 kHz1 MHz / 3.4 MHz
Addressing7-bit (112 usable) or 10-bit
Max Distance~1 meter~30 cm<10 cm
TopologyMulti-master, Multi-slave (Bus)

Which Protocol Fits Your Constraints?

  • Choose I2C when: You need to connect 5+ low-speed sensors (temp, humidity, IMUs) using only 2 microcontroller pins, and distance is under 1 meter.
  • Choose SPI when: You need high throughput (e.g., SD cards, TFT displays, high-sample-rate ADCs) and can afford 4+ wires per device.
  • Choose UART/RS485 when: You are communicating over long distances (meters to kilometers) or need point-to-point async communication without a shared clock.

The Minimal I2C Scanner Sketch and Wiring

A scanner sketch sweeps the 7-bit address space (0x01 to 0x7F), sends a START condition, and listens for an ACKnowledge (ACK) bit. Below is the physical wiring map followed by a robust, copy-pasteable scanner that includes a minimal working exchange to verify data readability.

Pin Mapping: Microcontroller to Generic I2C Sensor (e.g., BME280)
MicrocontrollerSDA PinSCL PinLogic Level
ESP32 DevKit V1GPIO 21GPIO 223.3V
Arduino Uno R3A4A55V
Raspberry Pi PicoGP4 (SDA0)GP5 (SCL0)3.3V
ESP32 Pin Warning: Never use GPIO 34, 35, 36, or 39 for I2C on the original ESP32. These are input-only pins and lack the internal pull-up structures required for open-drain bus operation.
#include <Wire.h>

// Explicit pin definitions prevent cross-board compilation errors
const int SDA_PIN = 21;
const int SCL_PIN = 22;
const uint32_t I2C_FREQ = 400000; // 400kHz Fast Mode

void setup() {
  Serial.begin(115200);
  while (!Serial) { delay(10); }
  
  Serial.println("\n--- I2C Scanner Sketch Initialized ---");
  
  // Initialize bus with explicit pins and frequency
  Wire.begin(SDA_PIN, SCL_PIN);
  Wire.setClock(I2C_FREQ);
  
  // ESP32 specific: Increase timeout to prevent WDT resets on noisy buses
  #if defined(ARDUINO_ARCH_ESP32)
    Wire.setTimeOut(50); // 50ms timeout
  #endif
}

void loop() {
  byte error, address;
  int deviceCount = 0;

  Serial.println("Scanning 7-bit address space (0x01 - 0x7F)...");

  for (address = 1; address < 127; address++ ) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("Device found at 0x");
      if (address < 16) Serial.print("0");
      Serial.println(address, HEX);
      deviceCount++;
      
      // Minimal working exchange: Read 1 byte to verify bus read capability
      testMinimalExchange(address);
    }
    else if (error == 4) {
      Serial.print("Unknown error at address 0x");
      if (address < 16) Serial.print("0");
      Serial.println(address, HEX);
    }
  }
  
  if (deviceCount == 0) Serial.println("No I2C devices found. Check pull-ups and wiring.");
  Serial.println("--- Scan Complete ---\n");
  
  delay(5000);
}

void testMinimalExchange(byte addr) {
  // Attempt to read 1 byte. A true working device will NACK or return data.
  Wire.requestFrom(addr, (byte)1);
  if (Wire.available()) {
    byte data = Wire.read();
    Serial.print("  -> Read successful. First byte: 0x");
    if (data < 16) Serial.print("0");
    Serial.println(data, HEX);
  }
}

Debugging the Classic I2C Failures

When your i2c scanner sketch fails, the issue is almost always physical or architectural. Here is how to diagnose the big three.

1. Missing or Incorrect Pull-Up Resistors

Symptom: Scanner finds no devices, or finds every address from 0x01 to 0x7F (phantom devices).
Diagnosis: Measure SDA and SCL with a multimeter. Both should read VCC (3.3V or 5V) when idle. If they read 0V or float around 1.5V, your pull-ups are missing or a device is holding the bus low (a crashed slave).
Fix: Add 4.7 kΩ resistors to VCC. If a slave is stuck holding SDA low, power-cycle the slave device to reset its internal state machine.

2. Address Clashes

Symptom: You wire two identical sensors (e.g., two cheap I2C LCD backpacks or two BME280s) and the scanner only shows one address.
Diagnosis: Check the datasheet. Many sensors have a hardwired address (like 0x27 for PCF8574 LCD backpacks) or only offer one alternative via a solder jumper.
Fix: Use an I2C multiplexer like the TI TCA9548A. It allows you to route the master SDA/SCL to 8 isolated channels, letting you use identical addresses on different channels.

3. Baud Mismatch and Clock Stretching Bugs

Symptom: Scanner hangs indefinitely or triggers a Watchdog Timer (WDT) reset on the ESP32.
Diagnosis: Some slow sensors use "clock stretching" (holding SCL low to buy processing time). The original ESP32 (Rev 0 and Rev 1 silicon) has a known hardware bug where it fails to handle clock stretching properly at 400 kHz, causing the I2C peripheral to lock up.
Fix: Drop the bus speed to 100 kHz (`Wire.setClock(100000);`), or upgrade to an ESP32-S3 or ESP32-C3, which feature fixed I2C peripherals. See the Espressif I2C API documentation for silicon-specific errata.

How to Sniff and Debug the Bus

When software fails, look at the physics. Connect a logic analyzer (like a Saleae Logic Pro 8 or a budget DSLogic Plus) to SDA, SCL, and GND.
Setup: Set the sample rate to at least 24 MHz (for a 400 kHz bus, you need 50x oversampling to catch glitches).
Trigger: Set a complex trigger on the START condition (SDA transitions High-to-Low while SCL is High).
Decode: Use the I2C protocol decoder. Look for NACK (Not Acknowledge) bits. If the master sends an address and the 9th clock cycle shows SDA High, the slave is missing, unpowered, or at the wrong address.

I2C Scanner Sketch Troubleshooting FAQ

Why does my I2C scanner sketch find no devices?

The most common causes are missing pull-up resistors, swapped SDA/SCL wires, or a logic level mismatch (e.g., connecting a 5V Arduino directly to a 3.3V sensor without a level shifter like the BSS138, which can damage the sensor or prevent the 3.3V device from pulling the 5V line low enough to register as a logical '0'). Verify idle voltages with a multimeter first.

Can I use an I2C scanner sketch on a Raspberry Pi?

You do not need an Arduino-style sketch for a Raspberry Pi running Linux. The OS has built-in I2C tools. Enable I2C via sudo raspi-config, install the tools with sudo apt install i2c-tools, and run i2cdetect -y 1 in the terminal. This will output a grid of detected addresses on bus 1 (the default header pins).

How do I change the I2C address if the scanner shows a clash?

Address changing depends entirely on the silicon. Some ICs (like the PCA9548A multiplexer or certain ADC chips) have physical A0, A1, A2 pins that you tie to VCC or GND to alter the address. Others (like modern digital sensors) have a one-time programmable (OTP) EEPROM or require a specific hex command sequence sent over the bus to write a new address to volatile memory. Always consult the specific component datasheet.

Why does the scanner hang or crash the ESP32 mid-scan?

If the ESP32 crashes with a "Task Watchdog got triggered" error during an I2C scan, the bus is likely locked up due to a missing slave, extreme noise, or the ESP32 Rev 1 clock-stretching silicon bug mentioned above. Add Wire.setTimeOut(50); before your scan loop to force the Wire library to abandon stuck transactions, and ensure your pull-up resistors are correctly sized for the bus capacitance.