In this guide, you’ll learn how to exchange data between two ESP32 boards using I2C communication protocol. One ESP32 board will be set as an I2C master and the other board as an I2C slave. The ESP32 boards will be programmed using Arduino IDE.
Do you need a wireless communication protocol? Try ESP-NOW communication protocol with the ESP32 to exchange data between boards.
Table of Contents
In this guide, we’ll cover the following topics:
- Introducing I2C
- ESP32 Master and ESP32 Slave
- Connecting two ESP32 Boards via I2C
- ESP32 I2C Slave – Arduino Code
- ESP32 I2C Master – Arduino Code
- Exchange Data Between Two ESP32 Boards via I2C – Demonstration
Introducing I2C
I²C means Inter Integrated Circuit (it’s pronounced I-squared-C), and it is a synchronous, multi-master, multi-slave communication protocol. You can connect :
- Multiple slaves to one master: for example, your ESP32 reads from a BME280 sensor using I2C and writes the sensor readings in an I2C OLED display.
- Multiple masters controlling the same slave: for example, two ESP32 boards writing data to the same I2C OLED display.
ESP32 I2C Bus Interfaces
The ESP32 supports I2C communication through its two I2C bus interfaces that can serve as I2C master or slave, depending on the user’s configuration. Accordingly to the ESP32 datasheet, the I2C interfaces of the ESP32 supports:
- Standard mode (100 Kbit/s)
- Fast mode (400 Kbit/s)
- Up to 5 MHz, yet constrained by SDA pull-up strength
- 7-bit/10-bit addressing mode
- Dual addressing mode. Users can program command registers to control I²C interfaces, so that they have more flexibility
For a more detailed introduction to I2C communication with the ESP32, read our guide:
ESP32 Master and ESP32 Slave
For I2C communication between two ESP32 boards, we’ll use
- The default Wire.h library
- The default I2C pins on both boards (these may vary depending on your board model)
Connecting two ESP32 Boards via I2C
Start by connecting the two ESP32 boards with each other. Use the default I2C pins for the boards you’re using. Don’t forget to connect the GND pins together.
ESP32 Master | ESP32 Slave |
SDA (GPIO 21)* | SDA(GPIO 21)* |
SCL (GPIO 22)* | SCL (GPIO 22)* |
GND | GND |
* in my case, I’m testing this with the ESP32 DOIT V1 board. The default I2C pins are GPIO 21 (SDA) and GPIO 22 (SCL). If you’re using an ESP32-C3, ESP32-S3, or other model, the default I2C pins might be different. Please check the pinout for the board you’re using.
I2C Communication Between 2 ESP32 boards
Here’s how I2C communication between two ESP32 boards works:
ESP32 Master | ESP32 Slave |
Sets its I2C address Sets callback functions: – to read received messages; – to handle requests. | |
Initializes I2C bus on the slave I2C address Starts I2C communication on the I2C bus | |
Sends a message via I2C (Starts transmission) | |
Reads the received message | |
Requests data from the slave | |
Sends data to the master |
ESP32 I2C Slave – Arduino Code
To test setting the ESP32 as an I2C slave, we’ll use the default example from the Wire.h library.
/*********
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp32-i2c-master-slave-arduino/
ESP32 I2C Slave example: https://github.com/espressif/arduino-esp32/blob/master/libraries/Wire/examples/WireSlave/WireSlave.ino
*********/
#include "Wire.h"
#define I2C_DEV_ADDR 0x55
uint32_t i = 0;
void onRequest() {
Wire.print(i++);
Wire.print(" Packets.");
Serial.println("onRequest");
Serial.println();
}
void onReceive(int len) {
Serial.printf("onReceive[%d]: ", len);
while (Wire.available()) {
Serial.write(Wire.read());
}
Serial.println();
}
void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
Wire.onReceive(onReceive);
Wire.onRequest(onRequest);
Wire.begin((uint8_t)I2C_DEV_ADDR);
/*#if CONFIG_IDF_TARGET_ESP32
char message[64];
snprintf(message, 64, "%lu Packets.", i++);
Wire.slaveWrite((uint8_t *)message, strlen(message));
Serial.print('Printing config %lu', i);
#endif*/
}
void loop() {
}
How does the code work?
Let’s take a quick look at how the slave code works.
Including the Wire Library
First, you need to include the Wire.h library.
#include "Wire.h"
Define the I2C address you want to give to your I2C device. In this case, it’s 0x55, but you can define any other address as long as you use the same one on the master device.
#define I2C_DEV_ADDR 0x55
Assign Callback Functions
In the setup(), you must assign two callback functions: one for when the board receives data from the master and another one for when the board receives a request from the master (send data to the master).
Wire.onReceive(onReceive);
Wire.onRequest(onRequest);
onReceive() Callback Function
The onReceive() function will read the data sent from the master and prints it on the Serial monitor.
void onReceive(int len) {
Serial.printf("onReceive[%d]: ", len);
while (Wire.available()) {
Serial.write(Wire.read());
}
Serial.println();
}
You check if there’s data available to read using Wire.available().
while (Wire.available()) {
Then, you read the received bytes with Wire.read().
Serial.write(Wire.read());
onRequest() Callback Function
The onRequest() function will send data to the Master when requested.
void onRequest() {
Wire.print(i++);
Wire.print(" Packets.");
Serial.println("onRequest");
Serial.println();
}
To send data to the master, we use Wire.print().
Wire.print(i++);
Wire.print(" Packets.");
In the documentation, it mentions that you need to use Wire.slaveWrite() in the case of the ESP32, so that it writes the response to the buffer before receiving a message from the master. However, in our case, it works well without Wire.slaveWrite() (that’s why we have that section commented).
Depending on your board, you may need to use the slaveWrite() method as follows.
/*#if CONFIG_IDF_TARGET_ESP32
char message[64];
snprintf(message, 64, "%lu Packets.", i++);
Wire.slaveWrite((uint8_t *)message, strlen(message));
Serial.print('Printing config %lu', i);
#endif*/
Initialize I2C
In the setup(), you must also initialize I2C communication on the I2C address defined earlier. Use the begin() method as follows. This will initialize I2C on the ESP32 default I2C pins.
Wire.begin((uint8_t)I2C_DEV_ADDR);
You can also pass the I2C pins and bus frequency to this method:
bool Wire.begin(uint8_t addr, int sdaPin, int sclPin, uint32_t frequency);
ESP32 I2C Master – Arduino Code
To test setting the ESP32 as an I2C master, we’ll use the default example from the Wire.h library.
/*********
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp32-i2c-master-slave-arduino/
ESP32 I2C Master Example: https://github.com/espressif/arduino-esp32/blob/master/libraries/Wire/examples/WireMaster/WireMaster.ino
*********/
#include "Wire.h"
#define I2C_DEV_ADDR 0x55
uint32_t i = 0;
void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
Wire.begin();
}
void loop() {
delay(5000);
// Write message to the slave
Wire.beginTransmission(I2C_DEV_ADDR);
Wire.printf("Hello World! %lu", i++);
uint8_t error = Wire.endTransmission(true);
Serial.printf("endTransmission: %u\n", error);
// Read 16 bytes from the slave
uint8_t bytesReceived = Wire.requestFrom(I2C_DEV_ADDR, 16);
Serial.printf("requestFrom: %u\n", bytesReceived);
if ((bool)bytesReceived) { //If received more than zero bytes
uint8_t temp[bytesReceived];
Wire.readBytes(temp, bytesReceived);
log_print_buf(temp, bytesReceived);
}
}
How does the code work?
Let’s take a quick look at how the ESP32 I2C Master code works.
Including the Wire Library
First, you need to include the Wire.h library.
#include "Wire.h"
Slave I2C Address
Set the I2C address of the slave device (set on the previous code):
#define I2C_DEV_ADDR 0x55
Initialize I2C
In the setup(), initialize I2C communication using Wire.begin(). This will initialize I2C on the default I2C pins.
Wire.begin();
You can also pass the I2C pins and bus frequency to this method:
bool Wire.begin(uint8_t addr, int sdaPin, int sclPin, uint32_t frequency);
Start Communication with the Slave
In the loop(), we send a message to the slave to inform that we’ll start I2C transmission:
// Write message to the slave
Wire.beginTransmission(I2C_DEV_ADDR);
Wire.printf("Hello World! %lu", i++);
uint8_t error = Wire.endTransmission(true);
Serial.printf("endTransmission: %u\n", error);
First, you need to call the beginTransmission() method and pass the slave address before writing a message.
Wire.beginTransmission(I2C_DEV_ADDR);
Then, you write a message to the buffer using the printf() method.
Serial.printf("endTransmission: %u\n", error);
Finally, to send the buffered message, you need to use the endTransmission() method.
uint8_t error = Wire.endTransmission(true);
Request Data From the Slave
Then, we request data from the slave using Wire.requestFrom(). Pass as argument the address of the slave and the number of requested bytes.
// Read 16 bytes from the slave
uint8_t bytesReceived = Wire.requestFrom(I2C_DEV_ADDR, 16);
Then, concatenate all the bytes and print the received data.
Serial.printf("requestFrom: %u\n", bytesReceived);
if ((bool)bytesReceived) { //If received more than zero bytes
uint8_t temp[bytesReceived];
Wire.readBytes(temp, bytesReceived);
log_print_buf(temp, bytesReceived);
}
Exchange Data Between Two ESP32 Boards via I2C – Demonstration
Upload the master and slave I2C sketches to your ESP32 boards. Open two instances of the Arduino IDE to see the Serial Monitor for both boards simultaneously.
You should see the master initializing a communication with the slave and receiving the data packets.
This is what you should get on the slave (a Hello World message! followed by a counter and the onRequest function being triggered):
On the Master, you’ll receive the packets sent from the slave.
Wrapping Up
In this tutorial, we’ve shown you how to set the ESP32 as an I2C slave and as an I2C master and how to exchange data between two EPS32 boards using I2C communication protocol.
For more details about using the Wire library for I2C communication, check the official documentation: Arduino-ESP32 I2C API Documentation.
We have other I2C tutorials with the ESP32 that you may find useful:
- ESP32: I2C Scanner (Arduino IDE) – Finding the Address of I2C Devices
- ESP32 I2C Communication: Set Pins, Multiple Bus Interfaces and Peripherals (Arduino IDE)
If you want to exchange data between two ESP32 boards wirelessly, we recommend using ESP-NOW instead:
- Getting Started with ESP-NOW (ESP32 with Arduino IDE)
- ESP-NOW Two-Way Communication Between ESP32 Boards
- ESP-NOW with ESP32: Send Data to Multiple Boards (one-to-many)
- ESP-NOW with ESP32: Receive Data from Multiple Boards (many-to-one)
We hope this tutorial is useful. Learn more about the ESP32 with our resources:
The “Master/Slave” terminology is out-of-date because of the slavery connotation. Please consider changing to Parent/Child or some other equivalent. Thanks!
How about Sender / Receiver?
I have used master/ slave for over 50 years and I have not had any complaints.
Sender/Receiver works, too.
And just because you’ve done it one way for 50 years doesn’t mean it’s the best way to do it today.
wired.com/story/tech-confronts-use-labels-master-slave/
Thanks to everyone for flagging this.
Yes, there are lots of things that were fine 50 years ago that aren’t now. It’s called progress. Let’s all celebrate it. I think the Wired link above is a good reference.
Sender -> Receiver is perfectly good
Conductor -> Performer is okay
Plenty of options.
You don’t get to redefine industry terms. we have male and female connectors (no non-binary) motherboards and daughter cards, and yes, master / slave processes.
Me too. How many lines of code and comments have master/slave in them. It is history we can’t change it now but we can learn from it.
Lucky we live in a magical future with a nearly universal feature called “Find & Replace”.
agree with you. there is nothing wrong with the words master and slave. they aptly describe the relationship here and have nothing to do with human slavery. people are just looking for something to be upset about
We know it’s nothing but virtue signalling. But the truly sad part is that while you and I just roll our eyes, the people who actually profit from human trafficking today are laughing their posteriors off. They absolutely LOVE the distraction because the more effort spent on bickering about worthless activities like this, the less effort is focused on actually going after them. Slavery is alive in well in the world today in places and forms that would surprise us all, and the ‘masters’ relish these futile attempts to change their hearts and minds. But if someone wants to think they are leading the army against injustice by changing some words in a source file, then gee whiz, I’m sure that’ll really show those bad guys a thing or two!
THIS ^^^
Thanks for this tutorial.
Is the Master ESP32 I2C port locked for the communication with the Slave ESP32 ? I mean, can the Master ESP32 still communicate with other I2C devices like sensors while connected at the same time to the Slave ESP32 ?
Hi.
I think so. The ESP32 can communicate with multiple I2C sensors at the same time. So, the other ESP32 is just another I2C device.
More information about I2C: https://randomnerdtutorials.com/esp32-i2c-communication-arduino-ide/
Regards,
Sara
Hi Sarah, I read this tutorial because I used the I2C communication protocol on other devices. Do you know the maximum length of wire line before raising of transmission errors? Thanks!
Hi.
Unfortunately, I didn’t test that.
Regards,
Sara
Hello Ivan, I see something similar (using the i2c link between a ESP8266 and ESP32. On both I use Wire.h. The maximum size seems to be 128Bytes (I only checked it from ESP8266 to ESP32 over the I2C link). I have not been able to find a reason (a tutorial, or e-mails, etc on Google).
Any help is still appreciated (I now split my messages…)
There will be some size limit due to the size of the buffer allocated in the Wire library. According to docs.arduino.cc/language-reference/en/functions/communication/wire/ the limit is 32 bytes. Searching for “ESP32 I2C buffer size” indicates it’s probably 128 bytes e.g. forum.arduino.cc/t/what-is-the-wire-buffer-limit-on-the-esp32/649989/3
Thanks for your reaction!
Looking now at the Wire.h I see the statement:
#define I2C_BUFFER_LENGTH 128
So that indeed explains the 128byes. I will see if I can edit this file and run/compile again.
Thanks.