The I2C bus is widely used to retrieve measurements or control equipment on Arduino, ESP32, ESP8266 and Raspberry Pi projects using the standard Wire.h library. The I2C bus is a serial communication bus developed by Philips from 1982 that allows two (or more) devices to communicate. Specifically, it is a half-duplex bidirectional synchronous serial data bus . The devices connected to the bus can act as the main device – that is to say the device that “controls” the bus – or secondary.
In this article, we will discuss the operating principle of the Wire.h library, which allows Arduino to communicate easily with each other. However, we will not discuss the technical operation of the I2C bus which is already very well explained on this Wikipedia page .
Quick overview of the I2C bus
The I2C bus is therefore an improvement in serial communication intended to communicate home automation accessories developed from the 1980s by Philipps. Most of the time, the I2C bus consists of a main device and one or more secondary devices, but the standard allows more complex organizations.
The I2C bus only requires two communication lines (wires) to connect devices to each other:
- SDA (Serial Data Line), bidirectional data line,
- SCL (Serial Clock Line), bidirectional synchronization clock line. The signal is generated only by the main equipment
From an electrical point of view, all equipment must be connected to the same GND electrical ground .
The two lines must be connected to a reference voltage (Vdd) via pull-up resistors.
Here is an example of an I2C network. An Arduino Uno is the main equipment of the network. Two secondary devices are connected to the I2C bus. A BME280 sensor is connected to the address 0x76 . At address 0x5A is a light intensity sensor BH1750FVI .
The maximum number of devices is limited by the number of available addresses. As peripheral addressing is made up of a 7-bit word, up to 128 peripherals (27) can be addressed . The number of I2C devices also depends on the quality of the circuit (its capacity) as well as the desired transmission speed.
Five transmission speeds are possible:
- Standard mode (Sm) ≤ 100 kbit/s
- Fast mode (Fm) ≤ 400 kbit/s
- Fast plus mode (Fm+) ≤ 1 Mbit/s
- High-speed mode (Hs-mode) ≤ 3,4 Mbit/s
- Ultra-fast mode (UFm) ≤ 5 Mbit/s, unidirectional only.
The maximum speed is imposed by the manufacturer. Most of the time, the speed is less than 1 Mbit/s, in general it is between 100 and 400 kbit/s for the sensors.
I2C addresses
Each secondary device therefore has a unique address which takes the form of a hexadecimal string, for example 0x69 .
This address is assigned by the component manufacturer. There is no standard, but manufacturers organize themselves not to use an address already used by an existing component. Usually a secondary I2C address is available. It can be assigned by programming (rarely) or by modifying the circuit (using a solder jumper or by applying a pre-defined voltage).
Here are two examples
If in doubt about the breakout’s I2C address, you can consult this list of the most common modules for Arduino, ESP32, ESP8266 and Raspberry Pi projects
Most common development boards default i2c pins
It is possible to manually assign by programming (we will see how in the next paragraph) the pins of the I2C bus. Most development board manufacturers (Arduino, ESP32, ESP8266, STM32, Raspberry Pi …) expose the standard I2C pins.
Here is the list of the default pins of the most common development boards
SDA | SCL | SDA1 | SCL1 | |
Main bus | Secondary bus | |||
One* | A4 | A5 | ||
Ethernet | A4 | A5 | ||
Mega2560 | 20 | 21 | ||
Leonardo | 2 | 3 | ||
Due | 20 | 21 | SDA1 | SCL1 |
ESP8266 | D1 | D2 | ||
ESP32 DevkitC v4 | 21 | 22 | User | User |
ESP32 (older generations) | IO21 | IO22 | User | User |
STM32 | PB7 or PB9 | PB6 or PB8 | PB11 | PB10 |
Raspberry Pi (any generation) | 3 | 5 |
(*) Do not use the pins marked SDA and SCL on the Arduino Uno.
Presentation of the Wire.h library for Arduino, ESP32 and ESP8266
The i2c bus is supported by the Wire.h library which has also been ported to ESP32 and ESP8266.
It is a standard library which does not require any additional installation. The version dedicated to ESP32 and ESP8266 is installed at the same time as the SDK.
To use the I2C bus in an Arduino project, all you have to do is declare the wire library at the start of the code.
#include <Wire.h>
The i2c bus is initialized using the Wire.begin (address) method.
On Arduino, the pins are not specified, the library uses the default pins for the targeted development board. The address corresponds to the address that one wishes to assign to the secondary equipment. This parameter will be used for example when developing an I2C network between several Arduinos. We will see two examples right after.
Wire.begin();
Commercial I2C modules have a library which takes care of recovering data which greatly facilitates development work since we do not have to deal with decoding the packets sent.
However, it is possible to use the Wire.h library to develop our own communication protocol between two (or more) Arduino (or any other microcontroller for that matter)
To put it simply, the Wire.h library has 4 main methods. All the methods are detailed here .
Wire.beginTransmission(i2c_address) is used to start sending data to the device whose address is passed as a parameter.
Wire.write(data_to_send) allows data to be sent to the device specified previously. Several data formats are supported by the library
Wire.write(value) a value to send on a byte
Wire.write(string) a string to send as a series of bytes. The conversion of a character string into byte is managed by the library
Wire.write(data, length)
- data an array of bytes
- length the number of bytes to transmit
Wire.endTransmission() is used to close communication with the equipment
We will see other useful methods in the following examples
Limitations of the Wire.h library for Arduino boards
For Arduino boards, the Wire.begin() method allows you to manually specify the address of a device. However, the SDA and SCL pins cannot be manually specified.
Wire.begin(0x09);
Limitations of the Wire.h library for ESP32 and ESP8266
Adapting the Wire.h library for ESP32 and ESP8266 development boards does not allow assigning an address to secondary equipment. We cannot therefore create an I2C network to communicate several ESP32 or ESP8266 together.
However, the SDA and SCL pins can be specified. Here, for example, pins 21 (SDA) and 22 (SCL) are manually assigned to the I2C bus by calling the method like this Wire.begin(pin_sda, pin_scl) .
Wire.begin(21,22);
Summary
Arduino | ESP32 / ESP8266 | |
Assign an address to a secondary device on the I2C network | Yes | No |
Create an I2C network | Yes | No |
Assign SDA and SCL pins | No | Yes |
Control and recover data from I2C equipment | Yes | Yes |
Arduino, ESP32 and ESP8266 compatible I2C scanner
Most of the time, we will use the I2C bus to communicate with ready-to-use sensors (breakout).
The I2C address of the most common modules are listed in this article
If you can’t find your component’s I2C address or are having communication issues, here is the source code for an Arduino, ESP32, and ESP8266 compatible I2C scanner.
#include <Arduino.h>
#include <Wire.h>
#define DISPLAY_ERROR false
#define LOOP_DELAY 10000
#define USER_PIN false
// Customize I2C bus pins for ESP8266 or ESP32
const int PIN_SCL = D1;
const int PIN_SDA = D2;
String I2Ctest() {
byte error, address;
int nDevices;
String s;
s="Scanning...\n";
nDevices = 0;
for(address = 1; address < 127; address++ ) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0) {
s+="I2C device found at 0x";
if (address<16)
s+="0";
s+=String(address,HEX);
s+="\n";
nDevices++;
} else if ( error > 0 ) {
if ( DISPLAY_ERROR ) {
s+="Unknow error at 0x";
if (address<16)
s+="0";
s+=String(address,HEX);
s+="\n";
}
}
}
if (nDevices == 0)
s+="No I2C devices found\n";
else
s+="done\n";
return s;
}
void setup() {
Serial.begin(115200);
Serial.println("I2C scanner");
#if USER_PIN
Wire.begin(PIN_SDA, PIN_SCL);
#else
Wire.begin();
#endif
}
void loop() {
// put your main code here, to run repeatedly:
Serial.println(I2Ctest());
delay(LOOP_DELAY);
}
Example 1, how to make two Arduino communicate via the I2C bus
To understand the basics of the wire.h library, we are going to make two Arduino communicate via an I2C bus.
Here is the wiring diagram.
Code to upload to the Arduino Principal
The main Arduino sends an integer to address 0x9 every 500ms.
void loop() {
Wire.beginTransmission(0x09); // transmit to device #9
Wire.write(x); // sends x value
Wire.endTransmission(); // stop transmitting
x++; // Increment x
delay(500); // delay of 500 milli seconde between two send / delay de 500 milli seconde entre 2 envoi sur le bus I2C
}
Create a first sketch on the Arduino IDE and paste the following code.
Then connect the main Arduino to the computer and upload the project.
/*Arduino 1 code
Example of code for use I2C communication between two Arduino :
Arduino 1 Secondary Arduino
A4 A4
A5 A4
GND GND
*/
// Include the required Wire library for I2C / integrer la librairie Wire pour utiliser l I2C
#include <Wire.h>
int x = 0; // creation of the variable x for stockage the state of the led on the secondary arduino / creation d une variable x qui stocke l etat de la led sur l arduino secondaire
void setup() {
// Start the I2C Bus / innitialisation du bus I2C
Wire.begin();
}
void loop() {
Wire.beginTransmission(0x09); // transmit to device #9 / transmission sur l arduino secondaire a l adresse 0x09 (=9 en decimale)
Wire.write(x); // sends x / envoi de la valeur de x
Wire.endTransmission(); // stop transmitting / arret de la transmission
x++; // Increment x / incremente x
delay(500); // delay of 500 milli seconde between two send / delay de 500 milli seconde entre 2 envoi sur le bus I2C
}
Code to install on the secondary Arduino
Create a second sketch and paste the following code. Connect the second Arduino, select the port on which it is connected and upload the project.
/*
Secondary arduino code
Example of code for use I2C communication between two Arduino :
Main Arduino (Uno) Secondary Arduino (Uno)
A4 A4
A5 A4
GND GND
*/
// Include the required Wire library for I2C
#include <Wire.h>
int LED = 13;
int x = 0;
void setup() {
pinMode (LED, OUTPUT); // Define the LED pin as Output
Wire.begin(9); // Start the I2C Bus with address 9 in decimal (= 0x09 in hexadecimal)
Wire.onReceive(receiveEvent); // Attach a function to trigger when something is received
}
void receiveEvent() {
x = Wire.read(); // read one character from the I2C
}
void loop() {
//If value received is a multiple of 2 turn on the led
if (x % 2 == 0) {
digitalWrite(LED, HIGH);
}
//Else turn off the led
else {
digitalWrite(LED, LOW);
}
}
How does this code work?
We specify the address of the device, here 0x9 or 9, both writes work.
Wire.begin(9);
As soon as a new message is received by the device, the receiveEvent() method is executed.
Wire.onReceive(receiveEvent);
Here the receiveEvent method updates a temporary variable by reading the received message
void receiveEvent() {
x = Wire.read(); // read message from I2C / lire le message recu sur le bus I2C
}
At each passage through the loop(), the LED is on if the value of the variable x is even and vice versa.
Demonstration
As soon as the second Arduino starts up, the LED on the board starts blinking, indicating that the two Arduinos are communicating with each other via the I2C bus.
Example 2, make three Arduino communicate via the I2C bus
We add a second Arduino (Mega) secondary to the I2C network.
The network is constituted
- Arduino Uno main
- Secondary Arduino Uno at address 0x9 (9)
- Secondary Arduino Mega at address 0x01 (10)
Code to upload to the Main Arduino (1)
The code is similar to the previous code. We send the value of an integer variable to the secondary Arduino located at address 0x0A on the I2C network.
/* Arduino 1 code (main)
Example of code for use I2C communication between two Arduino
Arduino 1(Uno) Arduino 2(Uno) Arduino 3 (Mega)
A4 A4 20
A5 A5 21
GND GND GND
*/
// Include the required Wire library for I2C
#include <Wire.h>
int x = 0; // creation of the variable x for stockage the state of the led on the secondary arduino
void setup() {
// Start the I2C Bus
Wire.begin();
}
void loop() {
Wire.beginTransmission(0x09); // transmit to device #9 / transmission sur arduino 2 a l adresse 0x09 (=9 en decimale)
Wire.write(x); // sends x / envoi de la valeur de x
Wire.endTransmission(); // stop transmitting / arret de la transmission
Wire.beginTransmission(0x0A); // transmit to device #10 / transmission sur arduino 3 a l adresse 0x01 (=10 en decimale)
Wire.write(x); // sends x / envoi de la valeur de x
Wire.endTransmission(); // stop transmitting / arret de la transmission
x++; // Increment x / incremente x
delay(500); // delay of 500 milli secondes between two send / delay de 500 milli secondes entre 2 envoi sur le bus I2C
}
Code to upload to secondary Arduino n°2
The code is unchanged
Code to upload to secondary Arduino n°3
The Arduino Mega takes address 10 (0x0A). The LED is on if x is even and vice versa.
/*Arduino 3 code
Example of code for use I2C communication between tree Arduino
Arduino 1(Uno) Arduino 2(Uno) Arduino 3 (Mega)
A4 A4 20
A5 A5 21
GND GND GND
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Include the required Wire library for I2C / integrer la librairie Wire pour utiliser l I2C
#include <Wire.h>
int LED = 13;
int x = 0;
void setup() {
pinMode (LED, OUTPUT); // Define the LED pin as Output / definir le pin de la led comme sortie
Wire.begin(10); // Start the I2C Bus with address 10 in decimal (= 0x0A in hexadecimal) / initialisation du bus I2C avec comme adresse 9 en decimal soit 0x09 en hexadecimal
Wire.onReceive(receiveEvent); // Attach a function to trigger when something is received / Attacher une fonction a declencher lorsque quelque chose est recu
}
void receiveEvent() {
x = Wire.read(); // read one character from the I2C / lire le caractere recu sur le bus I2C
}
void loop() {
//If value received is a multiple of 2 turn on the led / si la valeur recu sur l I2C est un multiple de 2 alors on allume la led
if (x % 2 == 0) {
digitalWrite(LED, LOW);
}
//Else turn off the led / sinon eteindre la led
else {
digitalWrite(LED, HIGH);
}
}
Demonstration
As soon as the Arduino Mega restarts, the led starts blinking.
Updates
10/07/2020 First publication of the article
- 4 solutions to add I/O (ADS1115, MCP23017, PCF8574, PCA9685). Arduino, ESP8266, ESP32, ESP8266, Raspberry Pi Projects
- I2C addresses of the most common sensors and actuators
- Get started with the I2C bus on Arduino ESP8266 ESP32. Wire.h library
- Unpacking Wemos ESP32 LoLin clone with 0.96” SSD1306 monochrome OLED display
- Unpacking the Geekcreit PCA9685 I2C Shield 16 Servos + 2 DC motors for Arduino or ESPDuino (ESP8266)