To successfully run an Arduino scan I2C routine, you need more than just a copy-paste sketch. The I2C (Inter-Integrated Circuit) bus relies on an open-drain physical layer, meaning it will completely fail to find devices if your pull-up resistors are missing or incorrectly sized. Before uploading any scanner code, you must verify your SDA and SCL lines are pulled high to VCC (usually 3.3V or 5V) with 4.7kΩ resistors, and that your board variant's default I2C pins are correctly wired.

I2C Bus Mechanics and Physical Layer Specs

I2C is a synchronous, multi-master, multi-slave serial communication bus. It uses only two bidirectional open-drain lines: Serial Data (SDA) and Serial Clock (SCL). Because the lines are open-drain, devices can only pull the bus low; they cannot drive it high. This is why external pull-up resistors are mandatory to return the bus to a logic HIGH state when idle.

Below is the data-dense specification sheet for the I2C physical layer, based on the NXP UM10204 I2C-bus specification. Note the strict 400 pF capacitance limit, which directly dictates your maximum wire length and device count.

Table 1: I2C Bus Mechanics and Hardware Specifications
Parameter Standard Mode Fast Mode Fast Mode Plus
Bus Speed 100 kbps 400 kbps 1 Mbps
Required Pull-Up 4.7kΩ - 10kΩ 2.2kΩ - 4.7kΩ 1kΩ - 2.2kΩ
Max Bus Capacitance 400 pF 400 pF 550 pF
Addressing Scheme 7-bit (112 usable) or 10-bit (1024 usable)
Max Practical Distance ~1 meter (unshielded) ~0.5 meter ~0.25 meter

Which Protocol Fits Your Distance, Speed, and Device Count?

Makers often default to I2C without considering physical constraints. Use this matrix to decide if I2C is actually the right tool for your project, or if you should pivot to SPI or RS-485.

Table 2: Embedded Protocol Selection Matrix
Protocol Max Speed Practical Distance Device Count Topology Best Use Case
I2C 100k - 400k < 1 meter Multi-drop (up to 112 on 7-bit) On-board sensors, OLEDs, EEPROMs
SPI 10M - 50M+ < 0.5 meter Point-to-multipoint (1 CS per device) High-speed ADCs, SD cards, TFT displays
UART 115k - 921k < 15 meters (at 9600) Strictly Point-to-Point (1 TX/RX pair) GPS modules, cellular modems, debug consoles
RS-485 100k - 10M Up to 1200 meters Multi-drop differential (up to 32/256 nodes) Industrial control, long-distance telemetry

Wiring the Bus and Pull-Up Resistor Rules

The most common reason an Arduino I2C scanner returns zero devices is a physical layer failure. Here is the exact wiring protocol for the most common microcontrollers:

  • Arduino Uno / Nano (ATmega328P): SDA is A4, SCL is A5. (Also available on dedicated headers near the AREF pin).
  • Arduino Mega 2560: SDA is Pin 20, SCL is Pin 21.
  • ESP32 DevKit V1: Default SDA is GPIO 21, Default SCL is GPIO 22. (The ESP32 allows matrix remapping, but stick to defaults to utilize the hardware I2C peripheral rather than software bit-banging).
  • Raspberry Pi Pico (RP2040): I2C0 defaults to GP4 (SDA) and GP5 (SCL). I2C1 defaults to GP2 (SDA) and GP3 (SCL).
⚠️ The Classic Failure: Missing or Weak Pull-Ups
Many cheap breakout boards (like the GY-BME280 or MPU6050) include 10kΩ onboard pull-up resistors. While 10kΩ might barely work at 100kHz on a short breadboard jumper, it will cause signal rise-time failures at 400kHz or when bus capacitance increases. The fix: Add external 4.7kΩ resistors from SDA to VCC and SCL to VCC. If your module already has 10kΩ, adding a 4.7kΩ in parallel yields ~3.2kΩ, which is perfect for Fast Mode (400kHz) operation.

The Arduino Scan I2C Sketch and Minimal Exchange

Once your physical layer is verified, upload this scanner sketch. It sweeps the 7-bit address space (0x08 to 0x77) and reports any device that acknowledges (ACKs) its address. This code relies on the standard Arduino Wire library.

#include <Wire.h>

void setup() {
  Serial.begin(115200);
  // Initialize I2C (SDA, SCL) 
  // For ESP32, use: Wire.begin(21, 22);
  Wire.begin(); 
  // Optional: Drop to 100kHz if you suspect clock-stretching issues
  Wire.setClock(100000); 
  Serial.println("\nI2C Scanner Ready");
}

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

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

    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address < 16) Serial.print("0");
      Serial.println(address, HEX);
      deviceCount++;
    }
  }
  
  if (deviceCount == 0) Serial.println("No I2C devices found. Check pull-ups!");
  delay(5000);
}

Minimal Working Exchange Example

Finding the address is only step one. To prove the bus is stable, perform a minimal register read. For a BME280 sensor (typically at 0x76 or 0x77), reading the WHO_AM_I register (0xD0) should return 0x60.

Wire.beginTransmission(0x76);
Wire.write(0xD0); // Point to WHO_AM_I register
Wire.endTransmission(false); // Repeated start condition
Wire.requestFrom(0x76, 1);  // Request 1 byte
if (Wire.available()) {
  byte chipID = Wire.read();
  Serial.print("Chip ID: 0x"); Serial.println(chipID, HEX);
}

Debugging Classic I2C Failures and Sniffing the Bus

When the scanner hangs, returns garbage addresses, or finds the device but data reads fail, you are dealing with one of three classic I2C edge cases. Here is how to diagnose them, referencing Texas Instruments application note SLVA704 on I2C bus troubleshooting.

1. Address Clashes

Symptom: You wire two identical sensors (e.g., two BME280s), but the scanner only shows one address, or the bus locks up.
Cause: Both devices default to the same 7-bit address (e.g., 0x76). I2C has no native routing; if two slaves answer to the same name, data collides.
Fix: Check the datasheet for an address-select pin. On the BME280, tying the SDO pin to GND sets the address to 0x76, while tying it to VCC sets it to 0x77. If the chip has no hardware address pins, you must use an I2C multiplexer like the TCA9548A to isolate the buses.

2. Baud Mismatch and Clock Stretching

Symptom: The scanner finds the device, but Wire.requestFrom() hangs the microcontroller indefinitely.
Cause: Clock stretching. A slow I2C slave (like an ATtiny running a software I2C library, or a high-resolution ADC mid-conversion) holds the SCL line LOW to force the master to wait. If your master (ESP32/Arduino) doesn't support hardware clock stretching properly, or times out, it crashes.
Fix: Lower the bus speed using Wire.setClock(50000); to give the slave more time to process between bytes. Add a timeout wrapper to your Wire calls if your core library supports it.

3. How to Sniff and Debug the Physical Bus

When code and multimeters fail, you must look at the actual waveforms. You don't need a $400 oscilloscope; a $15 USB logic analyzer (like a Saleae clone running Sigrok/PulseView) is sufficient.

  • Hookup: Connect the logic analyzer ground to your circuit GND. Connect Channel 0 to SDA and Channel 1 to SCL.
  • Decode: In PulseView, add the I2C protocol decoder. Assign SDA and SCL to the correct channels and set the address format to 7-bit.
  • What to look for: Watch the 9th clock pulse (the ACK bit). After the master sends 8 bits, it releases SDA. The slave must pull SDA LOW on the 9th pulse to ACKnowledge. If SDA stays HIGH (NACK), the slave is missing, unpowered, or the address is wrong. If SCL stays LOW for milliseconds, you are witnessing clock stretching.

By combining strict adherence to pull-up resistor physics with a systematic debugging approach, you can eliminate I2C bus ghosts and build robust multi-sensor embedded systems.