ESP32 I2C Tutorial
ESP32 I2C Tutorial
ESP32 I2C Tutorial: Set Pins, Multi Devices, I2C Scanner (Arduino ID
In this tutorial, you’ll learn about ESP32 I2C communication protocol (in Arduino IDE). We’ll discuss
how ESP32 I2C works, how to change I2C default pins (SDA, SCL), how to connect multiple I2C
devices on the bus with proper addressing, and how to create an ESP32 I2C scanner to check for
available I2C devices on the bus and get their addresses.
Then, we’ll move to the Arduino Core libraries that implement drivers for the ESP32 I2C peripheral
and how to use its API functions. It’s something similar to the commonly known Arduino Wire
library as we’ll see. Without further ado, let’s get right into it!
Tutorial Contents
1 Requirements For This Tutorial
1.0.1 Prior Knowledge
1.0.2 Software Tools
1.0.3 Hardware Components
2 I2C Serial Communication Protocol
2.0.1 I2C Modes & Communication Speeds
2.0.2 I2C Bus Lines
2.0.3 I2C Device Addressing
2.0.4 I2C Full Guide (Tutorial)
3 ESP32 I2C Peripheral
3.0.1 ESP32 I2C Hardware Features
3.0.2 ESP32 I2C Default Pins
4 ESP32 Set I2C Pins (SDA, SCL)
5 ESP32 I2C Communication (Arduino IDE)
6 Components For This Tutorial’s LABs
7 ESP32 I2C Scanner (Arduino IDE) – LAB
7.1 ESP32 I2C Scanner – Arduino Code Example
7.2 Test #1 Setup & Results
7.3 Test #2 Setup & Results
8 ESP32 I2C Device Addresses Limitations
8.1 1- Change The I2C Device Address
8.2 2- ESP32 + I2C Expander
8.3 3- Use All ESP32 Hardware I2C Interfaces
8.4 4- ESP32 Software I2C (Bit-Banging)
9 ESP32 I2C Applications
9.1 Share this:
9.2 Related
It’s widely used for attaching lower-speed peripheral ICs to processors and microcontrollers in
short-distance, intra-board communication. Just as external EEPROMs, Digital Temperature
Sensors, LCD Controllers, OLED Display, RTC, and much more.
Bidirectional bus:
To connect any I2C device to your microcontroller, you’ll only need to hook it to the 2-wire bus and
it’s ready to send/receive data. It’s very easy to connect/disconnect I2C devices compared to
many other communication interfaces.
Note: Both lines (SDA & SCL) should be “Pulled-Up” by external resistors as you can see in the
diagram above. Sometimes you’ll find the pull-up resistors on each I2C device board. You need
only 2 resistors for (SDA & SCL) per I2C.
As you might have noticed in the diagram above, there are I2C bus multiplexors to extend the
addresses. Those are considered “independent” I2C buses, hence we need to Pull-Up the (SDA &
SCL) for each extra independent I2C bus.
There is also a 10-Bit addressing mode that extends the number of devices that can be addressed
on the bus significantly. We’re limited by the total bus capacitance in practice and it’s better to use
I2C bus bridges/multiplexors in some cases.
If you’re really interested in learning how it works on a very low level and creating your own I2C
communication driver in Embedded-C language, it’d definitely be a great resource for you. Be
informed that it’s like a +10k words article! But hopefully, it’s going to be very helpful.
Check this in-depth tutorial for more information about I2C serial communication
This is as stated in the datasheet of the ESP32. The problem is some of those GPIO pins are not
accessible in many ESP32 modules. Therefore, the Arduino Core driver for I2C had to make use of
the GPIO matrix & Mux to re-route those signals to other “more accessible” pins. The following
pins are the default I2C0 pins in Arduino Core:
SCL GPIO22
SDA GPIO21
This is when you use the generic Arduino Wire library as shown below
Wire.begin();
But, what I do recommend is to use the TwoWire object that enables you to define an I2C bus with
the (SCL & SDA) signals being routed to custom GPIO pins that you choose on your own. This is a
much better way in my opinion and gives you all the freedom in your design. This is shown in the
next section down below.
Wire.begin(SDA0_Pin, SCL0_Pin);
But what I do really recommend is using the TwoWire object to create an instance of the I2C bus
and assign the GPIO pins you want for the SDA & SCL signals for each peripheral. As shown next
1 #include <Wire.h>
2
3 #define I2C_Freq 100000
4
5 #define SDA_0 21
6 #define SCL_0 22
7
8 #define SDA_1 18
9 #define SCL_1 19
10
11 TwoWire I2C_0 = TwoWire(0);
12 TwoWire I2C_1 = TwoWire(1);
13
14 void setup()
15 {
16 I2C_0.begin(SDA_0 , SCL_0 , I2C_Freq );
17 I2C_1.begin(SDA_1 , SCL_1 , I2C_Freq );
18 }
This way gives you all the freedom you need in your design to assign any GPIO pin to any I2C
peripheral of the 2 we’ve got in the ESP32. And we’ll be testing this in the LABs hereafter in this
tutorial.
#include <Wire.h>
Step2– Define the I2C_Frequency and GPIO pins to be used for (SDA & SCL) lines
1 void setup()
2{
3 I2C_0.begin(SDA_0 , SCL_0 , I2C_Freq);
4}
Now, you’re good to go. You can assign this TwoWire I2C object (I2C_0) to any sensor’s object
and follow its library’s examples. And use it however you want in your project.
Next, we’ll be creating a couple of projects to put everything we’ve discussed under test and
introduce more topics related to I2C addresses and I2C device configurations on the bus.
2 BreadBoard Amazon.com – eBay.com – Banggood.com
LAB Number 17
Then, we’ll start to check all possible I2C address devices (7-Bit = 127 possible addresses
excluding 0x00). If there is an ACK, it means there is a working I2C device at that address so we
shall print it out. If no ACK (NACK) is received, we shall move to the next address until the end.
1 /*
2 * LAB: 17
3 * Name: ESP32 I2C Scanner
4 * Author: Khaled Magdy
5 * For More Info Visit: www.DeepBlueMbedded.com
6 */
7
8 #include <Wire.h>
9
10 #define I2C_Freq 100000
11 #define SDA_0 18
12 #define SCL_0 19
13
14 TwoWire I2C_0 = TwoWire(0);
15
16 void setup()
17 {
18 Serial.begin(115200);
19 I2C_0.begin(SDA_0 , SCL_0 , I2C_Freq);
20 }
21
22 void loop()
23 {
24 byte error, address;
25 int nDevices;
26
27
28
Serial.println("Scanning...");
29
nDevices = 0;
30
for(address = 1; address < 127; address++ )
31
{
32
// The i2c_scanner uses the return value of
33
// the Write.endTransmisstion to see if
34
// a device did acknowledge to the address.
35
I2C_0.beginTransmission(address);
36
error = I2C_0.endTransmission();
37
if (error == 0)
38
{
39
Serial.print("I2C device found at address 0x");
40
if (address<16)
41
Serial.print("0");
42
Serial.print(address,HEX);
43
Serial.println(" !");
44
nDevices++;
45
}
46
else if (error==4)
47
{
48
Serial.print("Unknown error at address 0x");
49
if (address<16)
50
Serial.print("0");
51
Serial.println(address,HEX);
52
}
53
}
54
if (nDevices == 0)
55
Serial.println("No I2C devices found\n");
56
else
57
Serial.println("done\n");
58
delay(5000); // wait 5 seconds for next scan
59
}
60
61
62
Choose the board, COM port, hold down the BOOT button, click upload and keep your finger on the
BOOT button pressed. When the Arduino IDE starts sending the code, you can release the button
and wait for the flashing process to be completed. Now, the ESP32 is flashed with the new
firmware.
Those are the GPIO pins I’ve used for I2C_0 in this example code:
SCL GPIO19
SDA GPIO18
Test #1 Setup & Results
For the first test, I’ve connected an MPU6050 IMU sensor to the SCL & SDA lines of I2C1 as you
can see in the image down below. The I2C address for this slave device is 0x68 as stated in the
datasheet.
e can change only one bit of that address so there could be 2 MPU6050 sensors on the same bus
at maximum. Anyway, we should expect to see that address after running the example on the
“Tera Term” serial terminal on my PC.
You can still play around with those bridges to change the device address to allow many I2C_LCD
devices on the same bus. Which we’ll be doing in the next tutorial and will be discussed hereafter
in this tutorial.
To further investigate this matter and provide some solution, let’s first define a problem statement
and start searching for a convenient solution. Here is an I2C LCD (IO expander) called PCF8574.
Its default address in the datasheet is 0x27 with all solder bridges open.
What if we’d like to hook up 3 units of I2C LCD on the exact same bus and control those LCDs with
our ESP32. What can we do to make this happen?
Here are the proposed solution one by one (ordered by my personal recommendations)
We want just 3, so it’s more than enough. Let’s now change the problem statement, and let’s say
we need to have 10 x I2C LCD units on the exact same bus. What to do?
2- ESP32 + I2C Expander
Next, we can consider getting an I2C expander IC like (TCA9548A). It’s a very cheap solution to
give some virtual addresses to identical I2C devices so that you become able of addressing each
device on the bus with a unique address.
Here is a simplified diagram for this I2C expander chip. Note that it has its own I2C address and
you can have multiple units of this expander IC so it does increase the addressable device
significantly at a very low cost.
As I’ve said earlier, by changing the solder bridges we can address up to 8 x I2C LCDs, now we’ve
got 2 x I2C peripherals. So, we can easily solve the required 10 x I2C LCD problem.
It acts as if it’s a hardware I2C that’s generating the signals, but in reality, it’s the CPU doing all of
this with certain timing constraints and vulnerable to all sorts of errors and issues. But it’s still
doable and being used under certain circumstances.