To run I2C on a classic Arduino Nano (ATmega328P), connect your SDA line to analog pin A4 and your SCL line to A5. You must use 4.7kΩ pull-up resistors tied to 5V for standard 100 kHz operation, initialize the bus with Wire.begin(), and verify slave addresses using an I2C scanner before writing your main logic. If you are using a 3.3V Nano clone or the newer Nano 33 IoT, the voltage rails and pinouts change—more on that below.

The Physical Layer: Wiring and Pull-Up Requirements

I2C (Inter-Integrated Circuit) uses an open-drain (or open-collector) architecture. This means devices on the bus can only pull the signal line to ground (logic LOW); they cannot actively drive it HIGH. To achieve a HIGH state, the line relies on external pull-up resistors connected to the positive supply rail. This design prevents bus contention—if two devices try to talk at once, one pulling LOW and one pulling HIGH, you don't get a short circuit; the bus simply reads LOW, which is the foundation of I2C arbitration.

Hardware Gotcha: Nano Variants
The classic Arduino Nano V3 (ATmega328P) runs at 5V logic. SDA is A4, SCL is A5. However, the Nano Every (ATmega4809) uses different pins (SDA on A4, SCL on A5, but mapped differently internally), and the Nano 33 IoT (SAMD21) runs at 3.3V logic with SDA/SCL broken out on dedicated pins. Always check your specific board's schematic before applying 5V pull-ups to a 3.3V microcontroller, or you will fry the GPIO pins.

Sizing Your Pull-Up Resistors

The I2C specification mandates a minimum sink current of 3 mA for standard mode. Using Ohm's Law (R = V / I), the absolute minimum resistor value for a 5V bus is 5V / 0.003A = 1,666Ω. However, lower resistance means higher current draw and slower rise times when combined with bus capacitance.

  • 100 kHz (Standard Mode): Use 4.7kΩ resistors.
  • 400 kHz (Fast Mode): Use 2.2kΩ resistors to overcome the RC time constant and ensure the signal rises fast enough before the next clock edge.
  • 3.3V Systems: Use 2.2kΩ to 3.3kΩ resistors to maintain adequate rise times at the lower voltage.

Many sensor breakout boards (like Adafruit or SparkFun modules) include 10kΩ pull-ups onboard. If you daisy-chain three of these boards, the parallel resistance drops to ~3.3kΩ, which is usually fine. But if you add more, or use long wires, the bus capacitance will exceed the 400pF I2C limit, rounding off your square waves into useless triangles. Keep your I2C traces under 1 meter.

I2C Bus Mechanics and Protocol Limits

Before writing code, you need to understand the hard limits of the protocol. I2C is designed for short-distance, intra-board communication, not for running wires across a room.

I2C Specification Sheet (NXP UM10204)
Parameter Standard Mode Fast Mode Fast Mode Plus
Wires Required 2 (SDA, SCL) + Ground
Max Speed 100 kbit/s 400 kbit/s 1 Mbit/s
Addressing 7-bit (128 addresses, ~16 reserved) or 10-bit
Max Capacitance 400 pF (limits physical distance and node count)
Practical Distance ~1 meter ~0.5 meter ~0.25 meter

For deeper protocol timing diagrams and electrical characteristics, refer to the official NXP I2C-bus specification and user manual (UM10204).

Minimal Working Exchange: Master to Sensor

Let's read the WHO_AM_I register from an MPU6050 accelerometer/gyroscope. This is the ultimate I2C sanity check: if the chip responds with its hardcoded ID (0x68), your physical layer and addressing are correct.

Wiring Diagram

Arduino Nano Pin MPU6050 Pin Notes
5V VCC Power
GND GND Common ground is mandatory
A4 (SDA) SDA Add 4.7kΩ pull-up to 5V
A5 (SCL) SCL Add 4.7kΩ pull-up to 5V

The Code

This sketch uses the native Arduino Wire library. Notice the error handling on Wire.endTransmission()—most beginner tutorials ignore this, leaving you blind when the bus NACKs.

#include <Wire.h>

const uint8_t MPU_ADDR = 0x68; // 7-bit I2C address
const uint8_t WHO_AM_I_REG = 0x75;

void setup() {
  Serial.begin(115200);
  Wire.begin(); // Join I2C bus as master
  Wire.setClock(100000); // Explicitly set 100kHz standard mode
  
  Serial.println("Checking MPU6050 WHO_AM_I register...");
  
  // Step 1: Point to the register we want to read
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(WHO_AM_I_REG);
  uint8_t error = Wire.endTransmission(false); // 'false' sends a repeated start condition
  
  if (error != 0) {
    Serial.print("Transmission failed with error code: ");
    Serial.println(error); // 2 = NACK on address, 3 = NACK on data
    while(1); // Halt execution
  }
  
  // Step 2: Request 1 byte from the sensor
  Wire.requestFrom(MPU_ADDR, (uint8_t)1);
  
  if (Wire.available()) {
    uint8_t chipID = Wire.read();
    Serial.print("WHO_AM_I returned: 0x");
    Serial.println(chipID, HEX);
    if (chipID == 0x68) {
      Serial.println("Success! Sensor is online.");
    }
  } else {
    Serial.println("No data received from sensor.");
  }
}

void loop() {
  // Main sensor reading logic goes here
}

Debugging the Classic I2C Failures

When I2C fails, it rarely fails silently. Here is how to diagnose the three most common bench headaches.

1. The Missing or Weak Pull-Up

Symptom: The bus reads erratic values, or Wire.endTransmission() returns error code 2 (Address NACK) intermittently. If you measure SDA/SCL with a multimeter, the voltage floats around 1.5V instead of sitting solidly at 5V when idle.
Fix: Add 4.7kΩ pull-ups. If you are using a logic analyzer, look at the waveform: the falling edges will be sharp (actively driven low), but the rising edges will look like slow, rounded hills. That RC curve means your pull-up is too weak for the bus capacitance. Drop to 2.2kΩ.

2. Address Clashes and Unknown Devices

Symptom: You wired a new sensor, but it won't respond. Many modules have hardcoded addresses (e.g., multiple BMP280s might both default to 0x76), or you simply guessed the 7-bit address wrong.
Fix: Run an I2C Scanner sketch (available in the Arduino IDE under File > Examples > Wire > i2c_scanner). It sweeps addresses 1 through 127 and prints any that ACK. If two devices share an address, look for an "ADDR" or "CSB" pad on the PCB to solder-jump the secondary address.

3. Clock Stretching and Bus Lockups

Symptom: The Nano freezes completely during a Wire.requestFrom() call.
Fix: Some sensors (like certain SHT humidity chips) use "clock stretching"—they hold the SCL line LOW to force the master to wait while they process data. The ATmega328P hardware I2C peripheral handles this automatically, but if the sensor crashes while holding SCL low, the bus locks forever. Implement a watchdog timer in your code, or power-cycle the sensor via a MOSFET if it fails to release the line within 50ms.

Sniffing the Bus

If the code and wiring look right but data is corrupted, you need to see the bits. Buy a $12 24MHz logic analyzer clone (based on the Cypress CY7C68013A chip) and use the open-source PulseView / Sigrok software. Hook the ground clip to your Nano GND, and probe SDA and SCL. Set the decoder to I2C. You will instantly see if the master is sending the wrong 7-bit address (remember, PulseView often displays the 8-bit value including the R/W bit, so 0x68 becomes 0xD0 for a write).

Protocol Decision Tree: I2C vs. SPI vs. UART

Don't default to I2C just because it only uses two wires. Use this decision matrix to pick the right protocol for your specific hardware constraint.

Constraint / Requirement I2C SPI UART
Wire Count 2 (shared bus) 4+ (CS, MOSI, MISO, SCK) 2 (TX, RX) point-to-point
Speed / Throughput Low (100k - 400k) Very High (10MHz - 50MHz+) Medium (9600 - 115200 baud typical)
Distance Limit < 1 meter < 0.5 meter Up to 15m (RS-232/485)
Multi-Master Support Yes (Hardware arbitration) No (Single master typically) No (Requires complex collision detection)

The Final Verdict

Stop guessing and follow this hard rule:

  • Choose I2C when: You are connecting multiple low-speed environmental sensors (temperature, humidity, light, IMUs) on the same PCB or inside the same project enclosure. Default Pick: I2C with 4.7kΩ pull-ups at 100kHz.
  • Choose SPI when: You need to move bulk data. SD cards, TFT LCD displays, and high-sample-rate ADCs will choke the I2C bus. Use SPI.
  • Choose UART when: You are communicating between two separate microcontrollers, sending data to a PC via USB-Serial, or wiring a GPS module. UART is asynchronous and doesn't require a shared clock line, making it vastly superior for off-board wiring.

For 90% of Arduino Nano sensor projects, I2C is the correct, most efficient choice—provided you respect the pull-up resistors and keep your wires short.