I2C (Inter-Integrated Circuit) is one of the most popular serial communication protocols in the electronics world. With just two wires, you can connect multiple sensors, displays, and other peripherals to your Arduino and communicate with all of them. This tutorial covers everything you need to know about Arduino I2C — from the underlying concept to reading multiple sensors simultaneously.
Table of Contents
- What Is I2C and How Does It Work?
- I2C Pins on Arduino Boards
- Using the Wire Library
- I2C Address Scanner Sketch
- Reading I2C Sensors: BMP280 and DHT20 Examples
- Connecting Multiple I2C Devices
- Troubleshooting I2C Issues
- Frequently Asked Questions
What Is I2C and How Does It Work?
I2C was invented by Philips (now NXP) in 1982 as a way for a master device (like an Arduino) to communicate with multiple slave devices (like sensors) using only two signal wires:
- SDA (Serial Data Line): Carries the actual data bits
- SCL (Serial Clock Line): Carries the clock signal that synchronizes data transfer
The I2C protocol works like this:
- The master sends a START condition (pulls SDA LOW while SCL is HIGH)
- The master transmits the 7-bit address of the target slave device plus a Read/Write bit
- The addressed slave acknowledges (pulls SDA LOW during the 9th clock pulse)
- Data bytes are transferred, with the receiver acknowledging each byte
- The master sends a STOP condition to end the transmission
Each device on the I2C bus has a unique 7-bit address (0–127). In practice, addresses 0–7 and 120–127 are reserved, leaving addresses 8–119 for user devices. Some devices have configurable addresses (via pins or internal registers), allowing you to use multiple identical chips on the same bus.
Standard I2C speeds:
- Standard Mode: 100 kHz (most common for hobbyist use)
- Fast Mode: 400 kHz
- Fast Mode Plus: 1 MHz
- High Speed Mode: 3.4 MHz (rarely used with Arduino)
Both SDA and SCL lines require pull-up resistors (typically 4.7kΩ for 100 kHz or 2.2kΩ for 400 kHz) connected to VCC. Many breakout modules include these resistors on board, but if you connect bare chips you must add them externally.
I2C Pins on Arduino Boards
The I2C pins vary by board. Here are the most common ones:
| Board | SDA Pin | SCL Pin | Logic Level |
|---|---|---|---|
| Arduino Uno R3 | A4 (18) | A5 (19) | 5V |
| Arduino Nano | A4 | A5 | 5V |
| Arduino Mega 2560 | 20 | 21 | 5V |
| Arduino Leonardo | 2 | 3 | 5V |
| Arduino Nano 33 IoT | A4 | A5 | 3.3V |
| Arduino Nano RP2040 | A4 | A5 | 3.3V |
Important voltage note: The Arduino Uno, Nano, and Mega operate at 5V logic. Many modern sensors (BMP280, BME280, DHT20) use 3.3V. Most breakout boards include a level shifter or voltage regulator, but always verify the module’s operating voltage before connecting. Connecting a 3.3V sensor directly to a 5V I2C bus without level shifting can damage the sensor.
Using the Wire Library
Arduino’s built-in Wire library handles all the low-level I2C signaling. You never need to manually generate START/STOP conditions or manage acknowledgements. Here are the essential functions:
Initializing I2C
#include <Wire.h>
void setup() {
Wire.begin(); // Initialize as master (no address argument)
Wire.setClock(400000); // Optional: set to 400kHz Fast Mode
}
Writing Data to a Device
// Write a value to a register in a slave device
void writeRegister(uint8_t deviceAddr, uint8_t regAddr, uint8_t value) {
Wire.beginTransmission(deviceAddr); // Start transmission to device
Wire.write(regAddr); // Send register address
Wire.write(value); // Send value to write
Wire.endTransmission(); // End transmission, send STOP
}
Reading Data from a Device
// Read a byte from a register in a slave device
uint8_t readRegister(uint8_t deviceAddr, uint8_t regAddr) {
Wire.beginTransmission(deviceAddr); // Select device
Wire.write(regAddr); // Tell device which register to read
Wire.endTransmission(false); // Repeated START (false = no STOP)
Wire.requestFrom(deviceAddr, 1); // Request 1 byte
if (Wire.available()) {
return Wire.read(); // Read and return the byte
}
return 0;
}
I2C Address Scanner Sketch
Before writing device-specific code, it is essential to know the I2C address of your device. The I2C scanner sketch tries every address from 1 to 126 and reports which ones respond:
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(9600);
while (!Serial); // Wait for Serial on Leonardo/Nano 33
Serial.println("I2C Scanner — scanning...");
Serial.println();
int deviceCount = 0;
for (byte address = 1; address < 127; address++) {
Wire.beginTransmission(address);
byte error = Wire.endTransmission();
if (error == 0) {
Serial.print("Device found at address 0x");
if (address < 16) Serial.print("0"); // Leading zero
Serial.println(address, HEX);
deviceCount++;
} else if (error == 4) {
Serial.print("Unknown error at address 0x");
if (address < 16) Serial.print("0");
Serial.println(address, HEX);
}
}
Serial.println();
if (deviceCount == 0) {
Serial.println("No I2C devices found.");
Serial.println("Check wiring and pull-up resistors.");
} else {
Serial.print(deviceCount);
Serial.println(" device(s) found.");
}
}
void loop() {
// Scanner only runs once in setup
}
Upload this sketch, open Serial Monitor at 9600 baud, and you will see the addresses of all connected I2C devices printed in hexadecimal. Common addresses you will encounter:
- 0x3C or 0x3D: OLED display (SSD1306)
- 0x76 or 0x77: BMP280 / BME280 pressure sensor
- 0x38: DHT20 temperature and humidity sensor
- 0x48: ADS1115 analog-to-digital converter
- 0x68 or 0x69: MPU-6050 gyroscope/accelerometer
Reading I2C Sensors: BMP280 and DHT20 Examples
Reading the BMP280 Pressure Sensor
The BMP280 measures barometric pressure and temperature. It communicates via I2C at address 0x76 (or 0x77 if the SDO pin is tied to VCC).
Install the Adafruit BMP280 library via the Library Manager, then use this code:
#include <Wire.h>
#include <Adafruit_BMP280.h>
Adafruit_BMP280 bmp;
void setup() {
Serial.begin(9600);
if (!bmp.begin(0x76)) { // Use 0x77 if 0x76 doesn't work
Serial.println("BMP280 not found! Check wiring.");
while (1);
}
// Recommended settings for indoor navigation
bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,
Adafruit_BMP280::SAMPLING_X2,
Adafruit_BMP280::SAMPLING_X16,
Adafruit_BMP280::FILTER_X16,
Adafruit_BMP280::STANDBY_MS_500);
}
void loop() {
Serial.print("Temperature: ");
Serial.print(bmp.readTemperature());
Serial.println(" *C");
Serial.print("Pressure: ");
Serial.print(bmp.readPressure() / 100.0F);
Serial.println(" hPa");
Serial.print("Altitude: ");
Serial.print(bmp.readAltitude(1013.25)); // Standard sea level pressure
Serial.println(" m");
Serial.println();
delay(2000);
}
Reading the DHT20 Sensor
Unlike the older DHT11/DHT22 (which use a single-wire protocol), the DHT20 uses I2C at address 0x38. Install the DFRobot DHT20 or Aosong DHT20 library and use:
#include <Wire.h>
#include <AHTxx.h> // AHT20/DHT20 compatible library
AHTxx dht20(AHTXX_ADDRESS_X38, AHT2x_SENSOR);
void setup() {
Serial.begin(9600);
Wire.begin();
if (!dht20.begin()) {
Serial.println("DHT20 init failed!");
while (1);
}
}
void loop() {
float temperature = dht20.readTemperature();
float humidity = dht20.readHumidity();
Serial.print("Temperature: ");
Serial.print(temperature);
Serial.println(" *C");
Serial.print("Humidity: ");
Serial.print(humidity);
Serial.println(" %");
delay(2000);
}
Connecting Multiple I2C Devices
The real power of I2C is connecting many devices on the same two-wire bus. Since each device has a unique address, the master can talk to each one independently.
Wiring Multiple Devices
Simply connect all SDA pins together and all SCL pins together. Connect the shared SDA and SCL lines to the Arduino’s SDA and SCL pins. Use a single pair of pull-up resistors (4.7kΩ to VCC) — you do NOT need separate pull-ups for each device. Adding too many pull-up resistors lowers the effective resistance and can cause signal integrity issues.
Reading Multiple Sensors in One Sketch
#include <Wire.h>
#include <Adafruit_BMP280.h>
#include <AHTxx.h>
Adafruit_BMP280 bmp;
AHTxx dht20(AHTXX_ADDRESS_X38, AHT2x_SENSOR);
void setup() {
Serial.begin(9600);
Wire.begin();
// Initialize BMP280 (address 0x76)
if (!bmp.begin(0x76)) {
Serial.println("BMP280 not found!");
}
// Initialize DHT20 (address 0x38)
if (!dht20.begin()) {
Serial.println("DHT20 not found!");
}
}
void loop() {
// Read from BMP280
float pressure = bmp.readPressure() / 100.0F;
float bmpTemp = bmp.readTemperature();
// Read from DHT20
float temperature = dht20.readTemperature();
float humidity = dht20.readHumidity();
Serial.println("--- Sensor Readings ---");
Serial.print("BMP280 Temp: "); Serial.print(bmpTemp); Serial.println(" *C");
Serial.print("BMP280 Pressure: ");Serial.print(pressure); Serial.println(" hPa");
Serial.print("DHT20 Temp: "); Serial.print(temperature);Serial.println(" *C");
Serial.print("DHT20 Humidity: "); Serial.print(humidity); Serial.println(" %");
Serial.println();
delay(3000);
}
Handling Address Conflicts
If two identical devices have the same fixed address, you have two options:
- Use address pins: Many chips have ADDR or SDO pins you can tie HIGH or LOW to select between two available addresses. For example, BMP280 can be 0x76 or 0x77.
- Use an I2C multiplexer (TCA9548A): This chip lets you switch between 8 separate I2C buses, each with its own set of devices, allowing many identical chips on one Arduino.
Troubleshooting I2C Issues
Problem: I2C scanner finds no devices
- Check SDA and SCL connections — swapping these is a common mistake
- Ensure pull-up resistors are present (4.7kΩ to VCC)
- Confirm VCC voltage matches the device’s operating voltage
- Some devices need a few milliseconds after power-up before responding — add
delay(100)before scanning
Problem: Garbage data from sensor
- Double-check the I2C address — run the scanner sketch to confirm
- Verify you are using the correct library for your specific sensor variant
- Check the SCL frequency — some sensors max out at 100 kHz; lower speed with
Wire.setClock(100000)
Problem: I2C works, then stops randomly
- Check for bus lockup — if power is cut mid-transaction, a slave device may hold SDA LOW permanently. Toggle SCL 9 times to release it, or power cycle
- Insufficient pull-up resistor values for the bus capacitance. With long wires or many devices, try 2.2kΩ instead of 4.7kΩ
- Loose connections on a breadboard — I2C is sensitive to intermittent contacts
Frequently Asked Questions
How many I2C devices can I connect to one Arduino?
Theoretically, up to 112 devices with unique 7-bit addresses can share one I2C bus. In practice, the limit is set by bus capacitance (maximum 400 pF). Long wires and many devices add capacitance, slowing rise times and causing communication errors. For most hobbyist projects, you can comfortably connect 5–10 modules without issues. Use shorter wires and lower clock speeds for large numbers of devices.
What is the difference between I2C and SPI?
I2C uses 2 wires and supports multiple masters and up to 112 devices on one bus, but is slower (up to 3.4 MHz max, typically 100–400 kHz). SPI uses 4 wires (MOSI, MISO, SCK, and one CS pin per device), supports full-duplex (simultaneous send and receive), and is much faster (up to tens of MHz). SPI is preferred for high-speed data transfer (like TFT displays), while I2C is better for sensor networks where pin count matters.
Why does my 5V Arduino damage 3.3V I2C sensors?
Many modern sensors use 3.3V logic and have I/O pins rated for maximum 3.6V. When a 5V Arduino drives SDA or SCL HIGH at 5V, it exceeds this limit and can permanently damage the sensor. Most breakout modules (like the ones from Adafruit or DFRobot) include onboard level shifters or use I2C-safe open-drain outputs. Check the module’s datasheet, and use a bidirectional I2C level shifter (like the TXB0108 or BSS138-based modules) for bare chips.
Can the Arduino act as an I2C slave?
Yes. The Wire library supports slave mode. Call Wire.begin(address) with a slave address (1–127), then register callbacks with Wire.onReceive() and Wire.onRequest(). This is useful for multi-Arduino projects where a Nano 33 IoT collects sensor data and forwards it over I2C to an Arduino Mega acting as the master controller.
How do I speed up I2C for a faster data rate?
Call Wire.setClock(400000) in setup for 400 kHz Fast Mode. Make sure all devices on your bus support 400 kHz (check their datasheets). Use shorter wires and smaller pull-up resistors (2.2kΩ instead of 4.7kΩ) to support faster signal rise times. For speeds above 400 kHz, you need Fast Mode Plus (1 MHz) capable hardware — not all ATmega Arduinos support this without external hardware.
Build Your I2C Sensor Network Today
I2C is the backbone of modern Arduino sensor projects. With just two pins and the Wire library, you can connect pressure sensors, temperature sensors, OLED displays, gyroscopes, and much more — all at the same time. The I2C scanner sketch should be the first thing you upload whenever you add a new device to your project.
Explore the complete range of Arduino-compatible boards and I2C sensors at Zbotic.in. From the BMP280 and DHT20 to Arduino Nano 33 IoT, we stock everything you need to build your next sensor project in India.
Add comment