Get started with the I2C bus on Arduino ESP8266 ESP32. Wire.h library

Get started I2C arduino esp8266 esp32
Share on facebook
Share on twitter
Share on linkedin
Share on pinterest
Share on email
Share on whatsapp

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.

When purchasing pre-assembled I2C breakout modules, the circuit already incorporates the pull-up resistor.

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 .

Two pull-up resistors are shown in principle, but they are already integrated into the BME280 and BH1750 (breakout) modules.

 

Architecture bus i2c arduino breakout

 

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.

Note that it is very rare to exceed 10 devices per assembly in the majority of connected object projects.

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

bme280 i2c selector jumperbh1750 i2c selector jumper gy-302
To assign the i2c address 0x77, you have to solder a jumper between the two right pins and cut the track between the two left pinsTo assign the address 0x5C, a voltage greater than 0.7V must be applied to the ADDR pin

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

SDASCLSDA1SCL1
Main busSecondary bus
One*A4A5
EthernetA4A5
Mega25602021
Leonardo23
Due2021SDA1SCL1
ESP8266D1D2
ESP32 DevkitC v42122UserUser
ESP32 (older generations)IO21IO22UserUser
STM32PB7 or PB9PB6 or PB8PB11PB10
Raspberry Pi (any generation)35  

(*) 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

ArduinoESP32 / ESP8266
Assign an address to a secondary device on the I2C networkYesNo
Create an I2C networkYesNo
Assign SDA and SCL pinsNoYes
Control and recover data from I2C equipmentYesYes

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.

Note, the Wire.h library for ESP8266 and ESP32 allows you to specify the SDA (and SCL) pins of the I2C bus. Pass the USER_PIN constant to true for this The default I2C pins for major development boards are listed in this table .

#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.

2 Arduino boards communicate via the I2C bus

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.

3 Arduino boards communicate via the I2C bus

The network is constituted

  1. Arduino Uno main
  2. Secondary Arduino Uno at address 0x9 (9)
  3. Secondary Arduino Mega at address 0x01 (10)

IMG 2726 2

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

Click to rate this post!
[Total: 0 Average: 0]

Are you having a problem with this topic?

Maybe someone has already found the solution, visit the forum before asking your question
Ask your question

Share on facebook
Share on twitter
Share on linkedin
Share on pinterest
Share on email
Share on whatsapp

Did you like this project ? Don't miss any more projects by subscribing to our weekly newsletter!

We will be happy to hear your thoughts

      Leave a Reply

      DIY Projects