One of the most elegant features of modern sensor modules is the ability to stack many of them on just two wires. If you want to run multiple temperature sensors, pressure gauges, and display modules simultaneously without consuming a dozen Arduino pins, the Arduino I2C multiple sensors technique is exactly what you need. I2C (Inter-Integrated Circuit) was designed by Philips in the 1980s precisely for this scenario — a shared bus where dozens of devices communicate through addressing rather than dedicated select lines. This deep dive covers the protocol, addressing, clock stretching, multi-master considerations, and real code for common sensors.
- I2C Protocol Basics
- I2C Pins on Arduino Boards
- Device Addressing and Address Conflicts
- Pull-up Resistors: The Critical Detail
- The Wire Library in Depth
- I2C Bus Scanner: Find All Devices
- Connecting Multiple Sensors: Real Examples
- Troubleshooting I2C Problems
- Frequently Asked Questions
I2C Protocol Basics
I2C (also written I²C or IIC) uses only two bidirectional lines:
- SDA (Serial Data): Carries data bits between master and slaves.
- SCL (Serial Clock): Clock line driven by the master.
Both lines are open-drain (open-collector) — devices can only pull lines low; pull-up resistors bring lines high when no device is actively driving them. This architecture means multiple devices share the same lines without short-circuit risk.
A typical I2C transaction looks like this:
- START condition: Master pulls SDA low while SCL is high — this is the unique start signal.
- Address frame: Master sends 7-bit device address followed by a R/W bit (0 = write, 1 = read).
- ACK bit: The addressed slave pulls SDA low to acknowledge.
- Data frames: One or more 8-bit data bytes, each followed by an ACK from the receiver.
- STOP condition: Master releases SDA high while SCL is high — signals end of transaction.
Standard I2C runs at 100 kHz (Standard Mode) or 400 kHz (Fast Mode). Some devices support 1 MHz (Fast-Mode Plus) or 3.4 MHz (High-Speed), but most Arduino-compatible breakouts operate at 100 or 400 kHz.
I2C Pins on Arduino Boards
| Board | SDA | SCL | Notes |
|---|---|---|---|
| Uno / Nano | A4 | A5 | Also on pin headers near USB on newer Uno R3 |
| Mega 2560 | D20 | D21 | Separate I2C header also available |
| Leonardo | D2 | D3 | A4/A5 are analog-only on Leonardo |
| Nano 33 IoT | A4 | A5 | 3.3V logic only |
On Uno Rev3 and later, there are dedicated SDA/SCL pins near the AREF pin (top of the board). These are electrically identical to A4/A5 — don’t connect both simultaneously.
Device Addressing and Address Conflicts
Standard I2C uses 7-bit addresses, giving 128 possible addresses (16 are reserved, leaving 112 usable). Each device on the bus must have a unique address. Most common sensors have fixed addresses or allow one or two address bits to be configured via solder jumpers or address pins.
Common sensor default addresses:
| Sensor | Default Address | Alt. Addresses |
|---|---|---|
| BMP280 | 0x76 | 0x77 (SDO high) |
| BME280 | 0x76 | 0x77 |
| DHT20 | 0x38 | Fixed |
| DS3231 (RTC) | 0x68 | Fixed |
| SSD1306 OLED | 0x3C | 0x3D |
| PCA9685 (PWM) | 0x40 | 0x41–0x7F (A0-A5) |
When you need two identical sensors (e.g., two BMP280 modules measuring pressure at different points), you have options:
- Use the alternate address: BMP280 has SDO pin — tie it high on one module, low on the other. Now you have 0x76 and 0x77 — problem solved.
- Use an I2C multiplexer: TCA9548A is an 8-channel I2C mux. It connects to your Arduino as a single I2C device (0x70), and you switch which sub-bus is active by writing a register. Each sub-bus can have duplicate addresses.
- Bit-bang a second I2C bus: Use a software I2C library on different pins to create a second independent bus. Speed is limited to ~50 kHz but works for slow sensors.
Pull-up Resistors: The Critical Detail
I2C is perhaps the most pull-up-sensitive protocol you’ll encounter. The pull-ups form an RC low-pass filter with the bus capacitance. Too high a value and the rising edges slow down to the point where data is corrupted at 400 kHz. Too low and devices can’t pull the bus down reliably.
General guidance:
- 100 kHz, short bus (<30cm): 4.7 kΩ to 10 kΩ
- 400 kHz, short bus: 2.2 kΩ to 4.7 kΩ
- 400 kHz, longer bus or many devices: 1 kΩ to 2.2 kΩ
Most breakout boards include onboard pull-up resistors (often 4.7 kΩ). When you connect multiple modules, these pull-ups combine in parallel, reducing the effective resistance. With 4 modules each having 4.7 kΩ, the effective pull-up is ~1.175 kΩ — which is actually fine for 400 kHz. But with 10 modules the resistance drops to ~470 Ω, which can exceed the drive strength of slave devices trying to pull SDA low, causing ACK failures. In that case, remove the pull-up resistors from all but one module, or use a single external pull-up with an I2C bus driver.
Also: if mixing 5V Arduino with 3.3V sensors, use 3.3V pull-ups (to 3.3V, not 5V). Never pull I2C lines to 5V when 3.3V-only devices are on the bus — it will damage them.
The Wire Library in Depth
Arduino’s Wire.h library wraps the TWI (Two Wire Interface) hardware peripheral. Here’s a complete reference for multi-sensor operation:
#include <Wire.h>
void setup() {
Wire.begin(); // Join bus as master
Wire.setClock(400000); // Set 400 kHz Fast Mode
Serial.begin(9600);
}
// Write a byte to a device register
void i2cWrite(uint8_t addr, uint8_t reg, uint8_t value) {
Wire.beginTransmission(addr);
Wire.write(reg);
Wire.write(value);
uint8_t err = Wire.endTransmission();
if (err != 0) {
Serial.print("I2C write error: ");
Serial.println(err);
}
}
// Read bytes from a device register
void i2cRead(uint8_t addr, uint8_t reg, uint8_t* buf, uint8_t len) {
Wire.beginTransmission(addr);
Wire.write(reg);
Wire.endTransmission(false); // false = repeated START (no STOP)
Wire.requestFrom(addr, len);
for (uint8_t i = 0; i < len; i++) {
buf[i] = Wire.available() ? Wire.read() : 0;
}
}
The Wire.endTransmission(false) call is critical when reading from devices that require a register pointer write followed immediately by a read without a STOP in between. Sending a STOP between the write and read (the default behaviour) causes many sensors to reset their internal read pointer, returning wrong data.
Wire.endTransmission() returns an error code: 0 = success, 1 = buffer overflow, 2 = NACK on address, 3 = NACK on data, 4 = other error. Always check this in production code.
I2C Bus Scanner: Find All Devices
Before writing sensor-specific code, always scan your bus to confirm which addresses are responding. Here’s a complete scanner:
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(9600);
while (!Serial); // Wait for Serial on native USB boards
Serial.println("I2C Scanner");
Serial.println("Scanning...");
uint8_t count = 0;
for (uint8_t addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
uint8_t err = Wire.endTransmission();
if (err == 0) {
Serial.print("Device found at 0x");
if (addr < 16) Serial.print("0");
Serial.println(addr, HEX);
count++;
} else if (err == 4) {
Serial.print("Unknown error at 0x");
if (addr < 16) Serial.print("0");
Serial.println(addr, HEX);
}
}
Serial.print(count);
Serial.println(" device(s) found.");
}
void loop() {}
Run this whenever you add a new device to verify the address before writing driver code. If a device doesn’t appear in the scan, the issue is almost always wiring (SDA/SCL swapped, missing pull-ups, or wrong voltage level) rather than software.
Connecting Multiple Sensors: Real Examples
Let’s build a practical multi-sensor weather station reading temperature/humidity from a DHT20 (I2C, 0x38) and pressure/altitude from a BMP280 (I2C, 0x76) on the same bus.
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP280.h>
#include <AHTxx.h> // DHT20 uses AHT20 protocol
Adafruit_BMP280 bmp;
AHTxx aht(AHTXX_ADDRESS_X38, AHT2x_SENSOR);
void setup() {
Serial.begin(9600);
Wire.begin();
Wire.setClock(400000);
if (!bmp.begin(0x76)) {
Serial.println("BMP280 not found!");
while (1);
}
bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,
Adafruit_BMP280::SAMPLING_X2,
Adafruit_BMP280::SAMPLING_X16,
Adafruit_BMP280::FILTER_X16,
Adafruit_BMP280::STANDBY_MS_500);
if (aht.begin() != true) {
Serial.println("DHT20 not found!");
while (1);
}
}
void loop() {
float temp_bmp = bmp.readTemperature();
float pressure = bmp.readPressure() / 100.0F; // hPa
float altitude = bmp.readAltitude(1013.25);
float temp_dht = aht.readTemperature();
float humidity = aht.readHumidity();
Serial.print("BMP280 Temp: "); Serial.print(temp_bmp); Serial.println(" C");
Serial.print("Pressure: "); Serial.print(pressure); Serial.println(" hPa");
Serial.print("Altitude: "); Serial.print(altitude); Serial.println(" m");
Serial.print("DHT20 Temp: "); Serial.print(temp_dht); Serial.println(" C");
Serial.print("Humidity: "); Serial.print(humidity); Serial.println(" %");
Serial.println("---");
delay(2000);
}
Both sensors sit on the same SDA/SCL lines with a single pair of 4.7 kΩ pull-ups to 3.3V. The Wire library handles arbitration — you simply call each sensor’s library in sequence and both respond to their own addresses.
Troubleshooting I2C Problems
Device not found in scan
- Check SDA and SCL are not swapped
- Verify power (3.3V vs 5V) matches the module
- Confirm pull-up resistors are present (test with 4.7 kΩ from SDA to VCC and SCL to VCC)
- Some modules need an initial delay after power-on before responding
Intermittent data errors
- Bus capacitance too high — remove duplicate pull-ups from modules, use lower resistance external pull-ups
- Wire length over 1 metre — reduce clock to 100 kHz and add stronger pull-ups
- Voltage mismatch — 5V pull-ups on 3.3V devices
Bus lockup (Wire.endTransmission never returns)
- SDA stuck low — a device crashed mid-transaction. Power cycle all devices.
- Add a clock recovery function: manually toggle SCL 9 times to release the bus
- The Wire library on older Arduino cores has a known timeout bug — update to the latest Arduino core
Frequently Asked Questions
How many I2C devices can I connect to one Arduino?
Up to 112 devices at unique 7-bit addresses (128 minus 16 reserved). In practice, bus capacitance is the real limit — standard I2C specifies a maximum of 400 pF bus capacitance. Each device and each centimetre of wire adds capacitance. With typical module wiring you can reliably connect 10–20 devices; beyond that you need I2C bus extenders or repeaters.
Can I use I2C and SPI at the same time?
Yes, they use separate hardware peripherals on all AVR-based Arduino boards. I2C uses TWI (A4/A5 on Uno) and SPI uses the SPI peripheral (D11/D12/D13). They operate completely independently and can run simultaneously in interrupt-driven code.
What happens if two I2C devices have the same address?
Both devices will respond to the same address frame simultaneously, causing a bus collision. The data becomes corrupted and both responses conflict. The master cannot distinguish between them. Solutions: use alternate address configuration, an I2C multiplexer (TCA9548A), or a software I2C bus on different pins.
How do I speed up I2C reads?
Call Wire.setClock(400000) for 400 kHz Fast Mode. Verify your devices support Fast Mode (most modern modules do). Also use Wire.endTransmission(false) with repeated START instead of STOP between write and read — this eliminates bus turnaround time. For very fast requirements, consider SPI alternatives of the same sensor.
Why does my I2C work on a breadboard but fail on long jumper wires?
Longer wires add capacitance and resistance. Each 30 cm of wire adds roughly 50–100 pF to the bus. Reduce clock speed to 100 kHz and/or strengthen pull-ups (lower resistance) for longer runs. For distances over 50 cm, add a P82B96 I2C bus extender to maintain signal integrity.
Ready to build your multi-sensor I2C system? Shop our collection of Arduino boards and sensor modules at Zbotic — we stock the Nano 33 IoT, BMP280, DHT20, BME280 and dozens more I2C-compatible components shipped across India.
Add comment