Skip to content

Arduino - Inter Integrated Circuit

The Inter-Integrated Circuit (I2C) protocol is a popular serial communication bus used for connecting microcontrollers to sensors, memory modules, and other peripherals. It’s a two-wire system, making it ideal for projects requiring multiple devices without using many GPIO pins.

I2C communication involves two roles:

  • Master: The device that initiates communication, generates the clock signal, and addresses slaves. An Arduino often acts as the master.
  • Slave: The device that listens for its address and responds to requests from the master. Examples include sensors, displays, and I/O expanders.

The two wires used are:

  • SDA (Serial Data): The line for transmitting and receiving data.
  • SCL (Serial Clock): The line that carries the clock signal, synchronizing data transfer.

A single master can communicate with up to 112 slave devices on the same bus, as long as each has a unique address. The bus requires pull-up resistors on both the SDA and SCL lines (typically 4.7kΩ), although many breakout boards for sensors include these already.

The I2C pins vary by Arduino board. Here are the common ones:

  • Uno, Nano, Pro Mini: A4 (SDA), A5 (SCL)
  • Mega, Due: 20 (SDA), 21 (SCL)
  • Leonardo, Micro: 2 (SDA), 3 (SCL)

Arduino’s built-in Wire library simplifies I2C communication. The core functions are:

  • Wire.begin(): Initializes the library and joins the I2C bus. A master calls it without an address. A slave calls it with its 7-bit address, e.g., Wire.begin(0x08).
  • Wire.beginTransmission(address): Starts a transmission to the slave at the specified address.
  • Wire.write(data): Sends data bytes to the slave.
  • Wire.endTransmission(): Ends the transmission. Crucially, it returns a status code that you should check for errors.
  • Wire.requestFrom(address, byteCount): Master requests a specific number of bytes from a slave.
  • Wire.available(): Returns the number of bytes received and available to read.
  • Wire.read(): Reads a byte that was received.

Practical Example: Master Writer / Slave Receiver

Section titled “Practical Example: Master Writer / Slave Receiver”

In this example, a Master Arduino sends a command to a Slave Arduino to control its onboard LED.

#include <Wire.h>
#define SLAVE_ADDRESS 0x08 // Define the slave's address
void setup() {
Wire.begin(); // Join I2C bus as master
Serial.begin(9600);
Serial.println("I2C Master Writer Initialized");
}
void loop() {
// --- Turn LED ON ---
Serial.println("Sending ON command...");
Wire.beginTransmission(SLAVE_ADDRESS);
Wire.write('1'); // Send a byte to turn the LED on
byte error = Wire.endTransmission(); // Check for errors
if (error == 0) {
Serial.println("Command sent successfully.");
} else {
Serial.print("Error sending command: ");
Serial.println(error);
}
delay(2000);
// --- Turn LED OFF ---
Serial.println("Sending OFF command...");
Wire.beginTransmission(SLAVE_ADDRESS);
Wire.write('0'); // Send a byte to turn the LED off
error = Wire.endTransmission();
if (error == 0) {
Serial.println("Command sent successfully.");
} else {
Serial.print("Error sending command: ");
Serial.println(error);
}
delay(2000);
}
#include <Wire.h>
#define SLAVE_ADDRESS 0x08 // This slave's address
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW); // Start with LED off
Wire.begin(SLAVE_ADDRESS); // Join I2C bus with address
Wire.onReceive(receiveEvent); // Register event handler
Serial.begin(9600);
Serial.println("I2C Slave Receiver Initialized");
}
void loop() {
// The main loop can be used for other tasks.
// All I2C logic is handled by the event.
delay(100);
}
// This function is called whenever data is received from the master.
void receiveEvent(int byteCount) {
while (Wire.available()) { // Loop through all received bytes
char c = Wire.read(); // Receive byte as a character
Serial.print("Command received: ");
Serial.println(c);
if (c == '1') {
digitalWrite(LED_BUILTIN, HIGH);
} else if (c == '0') {
digitalWrite(LED_BUILTIN, LOW);
}
}
}
  • Wrong Address: The most common issue. Use an “I2C Scanner” sketch to scan the bus and find the addresses of connected devices.
  • Missing Pull-up Resistors: If your sensor module doesn’t have them, the bus won’t work. You need one 4.7kΩ resistor from SDA to 5V and another from SCL to 5V.
  • Wiring Errors: Double-check that SDA goes to SDA and SCL to SCL. Swapping them will prevent communication.
  • Power Issues: Ensure all devices on the bus share a common ground (GND).

Understanding endTransmission() Return Codes

Section titled “Understanding endTransmission() Return Codes”

Always check the return value of Wire.endTransmission() to build robust systems:

  • 0: Success.
  • 1: Data too long to fit in transmit buffer.
  • 2: Received NACK (Negative Acknowledge) on transmit of address. This means no slave responded at that address.
  • 3: Received NACK on transmit of data. The slave is there but didn’t accept the data.
  • 4: Other error.