ESP32 I2C Communication: Set Pins, Multiple Bus Interfaces and Peripherals (Arduino IDE)

The ESP32 has two I2C bus interfaces that can serve as I2C master or slave. In this tutorial we’ll take a look at the I2C communication protocol with the ESP32 using Arduino IDE: how to choose I2C pins, connect multiple I2C devices to the same bus and how to use the two I2C bus interfaces.

ESP32 I2C Communication: Set Pins, Multiple Bus Interfaces and Peripherals (Arduino IDE)

In this tutorial, we’ll cover the following concepts:

We’ll program the ESP32 using Arduino IDE, so before proceeding with this tutorial you should have the ESP32 add-on installed in your Arduino IDE. Follow the next tutorial to install the ESP32 on the Arduino IDE, if you haven’t already.

Introducing ESP32 I2C Communication Protocol

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.

We use this protocol many times with the ESP32 to communicate with external devices like sensors and displays. In these cases, the ESP32 is the master chip and the external devices are the slaves.

I2C Communication protocol with ESP32 board using Arduino IDE multiple devices

We have several tutorials with the ESP32 interfacing with I2C devices:

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

Connecting I2C Devices with ESP32

I2C communication protocol uses two wires to share information. One is used for the clock signal (SCL) and the other is used to send and receive data (SDA).

Note: in many breakout boards, the SDA line may also be labeled as SDI and the SCL line as SCK.

BME280 sensor I2C SDA (SDI) and SCL (SCK) pins

The SDA and SCL lines are active low, so they should be pulled up with resistors. Typical values are 4.7k Ohm for 5V devices and 2.4k Ohm for 3.3V devices.

Most sensors we use in our projects are breakout boards that already have the resistors built-in. So, usually, when you’re dealing with this type of electronics components you don’t need to worry about this.

Connecting an I2C device to an ESP32 is normally as simple as connecting GND to GND, SDA to SDA, SCL to SCL and a positive power supply to a peripheral, usually 3.3V (but it depends on the module you’re using).

I2C DeviceESP32
SDASDA (default is GPIO 21)
SCLSCL (default is GPIO 22)
GNDGND
VCCusually 3.3V or 5V

When using the ESP32 with Arduino IDE, the default I2C pins are GPIO 22 (SCL) and GPIO 21 (SDA) but you can configure your code to use any other pins.

Recommended reading: ESP32 GPIO Reference Guide

Scan I2C Address with ESP32

With I2C communication, each slave on the bus has its own address, a hexadecimal number that allows the ESP32 to communicate with each device.

The I2C address can be usually found on the component’s datasheet. However, if it is difficult to find out, you may need to run an I2C scanner sketch to find out the I2C address.

You can use the following sketch to find your devices’ I2C address.

/*********
  Rui Santos
  Complete project details at https://randomnerdtutorials.com  
*********/

#include <Wire.h>
 
void setup() {
  Wire.begin();
  Serial.begin(115200);
  Serial.println("\nI2C Scanner");
}
 
void loop() {
  byte error, address;
  int nDevices;
  Serial.println("Scanning...");
  nDevices = 0;
  for(address = 1; address < 127; address++ ) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address<16) {
        Serial.print("0");
      }
      Serial.println(address,HEX);
      nDevices++;
    }
    else if (error==4) {
      Serial.print("Unknow error at address 0x");
      if (address<16) {
        Serial.print("0");
      }
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0) {
    Serial.println("No I2C devices found\n");
  }
  else {
    Serial.println("done\n");
  }
  delay(5000);          
}

View raw code

You’ll get something similar in your Serial Monitor. This specific example is for an I2C LCD Display.

I2C scanner sketch serial monitor found I2C address

Use Different I2C Pins with ESP32 (change default I2C pins)

With the ESP32 you can set almost any pin to have I2C capabilities, you just need to set that in your code.

When using the ESP32 with the Arduino IDE, use the Wire.h library to communicate with devices using I2C. With this library, you initialize the I2C as follows:

Wire.begin(I2C_SDA, I2C_SCL);

So, you just need to set your desired SDA and SCL GPIOs on the I2C_SDA and I2C_SCL variables.

However, if you’re using libraries to communicate with those sensors, this might not work and it might be a bit tricky to select other pins. That happens because those libraries might overwrite your pins if you don’t pass your own Wire instance when initializing the library.

In those cases, you need to take a closer look at the .cpp library files and see how to pass your own TwoWire parameters.

For example, if you take a closer look at the Adafruit BME280 library, you’ll find out that you can pass your own TwoWire to the begin() method.

Adafruit_BME280 library using different I2C pins

So, the example sketch to read from the BME280 using other pins, for example GPIO 33 as SDA and and GPIO 32 as SCL is as follows.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-i2c-communication-arduino-ide/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#define I2C_SDA 33
#define I2C_SCL 32

#define SEALEVELPRESSURE_HPA (1013.25)

TwoWire I2CBME = TwoWire(0);
Adafruit_BME280 bme;

unsigned long delayTime;

void setup() {
  Serial.begin(115200);
  Serial.println(F("BME280 test"));
  I2CBME.begin(I2C_SDA, I2C_SCL, 100000);

  bool status;

  // default settings
  // (you can also pass in a Wire library object like &Wire2)
  status = bme.begin(0x76, &I2CBME);  
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }

  Serial.println("-- Default Test --");
  delayTime = 1000;

  Serial.println();
}

void loop() { 
  printValues();
  delay(delayTime);
}

void printValues() {
  Serial.print("Temperature = ");
  Serial.print(bme.readTemperature());
  Serial.println(" *C");
  
  // Convert temperature to Fahrenheit
  /*Serial.print("Temperature = ");
  Serial.print(1.8 * bme.readTemperature() + 32);
  Serial.println(" *F");*/
  
  Serial.print("Pressure = ");
  Serial.print(bme.readPressure() / 100.0F);
  Serial.println(" hPa");

  Serial.print("Approx. Altitude = ");
  Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
  Serial.println(" m");

  Serial.print("Humidity = ");
  Serial.print(bme.readHumidity());
  Serial.println(" %");

  Serial.println();
}

View raw code

ESP32 with BME280 on Different I2C Pins Schematic Diagram Wiring
Click image to enlarge

Let’s take a look at the relevant parts to use other I2C pins.

First, define your new I2C pins on the I2C_SDA and I2C_SCL variables. In this case, we’re using GPIO 33 and GPIO 32.

#define I2C_SDA 33
#define I2C_SCL 32

Create a new TwoWire instance. In this case, it’s called I2CBME. This simply creates an I2C bus.

TwoWire I2CBME = TwoWire(0);

In the setup(), initialize the I2C communication with the pins you’ve defined earlier. The third parameter is the clock frequency.

I2CBME.begin(I2C_SDA, I2C_SCL, 400000);

Finally, initialize a BME280 object with your sensor address and your TwoWire object.

status = bme.begin(0x76, &I2CBME);

After this, you can use use the usual methods on your bme object to request temperature, humidity and pressure.

Note: if the library you’re using uses a statement like wire.begin() in its file, you may need to comment that line, so that you can use your own pins.


ESP32 with Multiple I2C Devices

As we’ve mentioned previously, each I2C device has its own address, so it is possible to have multiple I2C devices on the same bus.

Multiple I2C devices (same bus, different addresses)

When we have multiple devices with different addresses, it is trivial how to set them up:

  • connect both peripherals to the ESP32 SCL and SDA lines;
  • in the code, refer to each peripheral by its address;

Take a look at the following example that gets sensor readings from a BME280 sensor (via I2C) and displays the results on an I2C OLED display.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-i2c-communication-arduino-ide/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

Adafruit_BME280 bme;

void setup() {
  Serial.begin(115200);

  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }
  
  bool status = bme.begin(0x76);  
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
  
  delay(2000);
  display.clearDisplay();
  display.setTextColor(WHITE);
}

void loop() {
  display.clearDisplay();
  // display temperature
  display.setTextSize(1);
  display.setCursor(0,0);
  display.print("Temperature: ");
  display.setTextSize(2);
  display.setCursor(0,10);
  display.print(String(bme.readTemperature()));
  display.print(" ");
  display.setTextSize(1);
  display.cp437(true);
  display.write(167);
  display.setTextSize(2);
  display.print("C");
  
  // display humidity
  display.setTextSize(1);
  display.setCursor(0, 35);
  display.print("Humidity: ");
  display.setTextSize(2);
  display.setCursor(0, 45);
  display.print(String(bme.readHumidity()));
  display.print(" %"); 
  
  display.display();

  delay(1000);
}

View raw code

ESP32 with BME280 and OLED Display Schematic Diagram Wiring
Click image to enlarge

Because the OLED and the BME280 have different addresses, we can use the same SDA and SCL lines without any problem. The OLED display address is 0x3C and the BME280 address is 0x76.

if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
  Serial.println(F("SSD1306 allocation failed"));
  for(;;);
}
  
bool status = bme.begin(0x76);  
if (!status) {
  Serial.println("Could not find a valid BME280 sensor, check wiring!");
  while (1);
}
Display BME280 readings on I2C OLED display - two I2C peripherals on the same bus

Recommended reading: ESP32 OLED Display with Arduino IDE

Multiple I2C devices (same address)

But, what if you have multiple peripherals with the same address? For example, multiple OLED displays or multiple BME280 sensors? There are several solutions.

  • change the device I2C address;
  • use an I2C multiplexer.

Changing the I2C address

Many breakout boards have the option to change the I2C address depending on its circuitry. For example, that a look at the following OLED display.

Selecting a different I2C address on SSD1306 OLED Display

By placing the resistor on one side or the other, you can select different I2C addresses. This also happens with other components.

Using an I2C Multiplexer

However, in this previous example, this only allows you to have two I2C displays on the same bus: one with 0x3C address and another with 0x3D address.

Additionally, sometimes it is not trivial changing the I2C address. So, in order to have multiple devices with the same address in the same I2C bus, you can use an I2C multiplexer like the TCA9548A that allows you to communicate with up to 8 devices with the same address.

TCA9548A I2C Multiplexer

ESP32 Using Two I2C Bus Interfaces

To use the two I2C bus interfaces of the ESP32, you need to create two TwoWire instances.

TwoWire I2Cone = TwoWire(0);
TwoWire I2Ctwo = TwoWire(1)

Then, initialize I2C communication on your desired pins with a defined frequency.

void setup() {
  I2Cone.begin(SDA_1, SCL_1, freq1);
  I2Ctwo.begin(SDA_2, SCL_2, freq2); 
}

Then, you can use the methods from the Wire.h library to interact with the I2C bus interfaces.

A simpler alternative is using the predefined Wire() and Wire1() objects. Wire().begin() creates an I2C communication on the first I2C bus using the default pins and default frequency. For the Wire1.begin() you should pass your desired SDA and SCL pins as well as the frequency.

setup(){
  Wire.begin(); //uses default SDA and SCL and 100000HZ freq
  Wire1.begin(SDA_2, SCL_2, freq);
}

This method allows you to use two I2C buses, one of them uses the default parameters.

To better understand how this works, we’ll take a look at a simple example that reads temperature, humidity and pressure from two BME280 sensors.

Multiple BME280 sensors with ESP32 - using the ESP32 two I2C bus interfaces

Each sensor is connected to a different I2C bus.

  • I2C Bus 1: uses GPIO 27 (SDA) and GPIO 26 (SCL);
  • I2C Bus 2: uses GPIO 33 (SDA) and GPIO 32 (SCL);
ESP32 with Multiple I2C BME280 (multiple I2C bus) Schematic Diagram
Click image to enlarge
/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-i2c-communication-arduino-ide/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#define SDA_1 27
#define SCL_1 26

#define SDA_2 33
#define SCL_2 32

TwoWire I2Cone = TwoWire(0);
TwoWire I2Ctwo = TwoWire(1);

Adafruit_BME280 bme1;
Adafruit_BME280 bme2;

void setup() {
  Serial.begin(115200);
  Serial.println(F("BME280 test"));

  I2Cone.begin(SDA_1, SCL_1, 100000); 
  I2Ctwo.begin(SDA_2, SCL_2, 100000);

  bool status1 = bme1.begin(0x76, &I2Cone);  
  if (!status1) {
    Serial.println("Could not find a valid BME280_1 sensor, check wiring!");
    while (1);
  }
  
  bool status2 = bme2.begin(0x76, &I2Ctwo);  
  if (!status2) {
    Serial.println("Could not find a valid BME280_2 sensor, check wiring!");
    while (1);
  }
  
  Serial.println();
}

void loop() { 
  // Read from bme1
  Serial.print("Temperature from BME1= ");
  Serial.print(bme1.readTemperature());
  Serial.println(" *C");

  Serial.print("Humidity from BME1 = ");
  Serial.print(bme1.readHumidity());
  Serial.println(" %");

  Serial.print("Pressure from BME1 = ");
  Serial.print(bme1.readPressure() / 100.0F);
  Serial.println(" hPa");

  Serial.println("--------------------");

  // Read from bme2
  Serial.print("Temperature from BME2 = ");
  Serial.print(bme2.readTemperature());
  Serial.println(" *C");

  Serial.print("Humidity from BME2 = ");
  Serial.print(bme2.readHumidity());
  Serial.println(" %");

  Serial.print("Pressure from BME2 = ");
  Serial.print(bme2.readPressure() / 100.0F);
  Serial.println(" hPa");

  Serial.println("--------------------");
  
  delay(5000);
}

View raw code

Let’s take a look at the relevant parts to use the two I2C bus interfaces.

Define the SDA and SCL pins you want to use:

#define SDA_1 27
#define SCL_1 26

#define SDA_2 33
#define SCL_2 32

Create two TwoWire objects (two I2C bus interfaces):

TwoWire I2Cone = TwoWire(0);
TwoWire I2Ctwo = TwoWire(1);

Create two instances of the Adafruit_BME280 library to interact with your sensors: bme1 and bme2.

Adafruit_BME280 bme1;
Adafruit_BME280 bme2;

Initialize an I2C communication on the defined pins and frequency:

I2Cone.begin(SDA_1, SCL_1, 100000); 
I2Ctwo.begin(SDA_2, SCL_2, 100000);

Then, you should initialize the bme1 and bme2 objects with the right address and I2C bus. bme1 uses I2Cone:

bool status = bme1.begin(0x76, &I2Cone);  

And bme2 uses I2Ctwo:

bool status1 = bme2.begin(0x76, &I2Ctwo);

Now, you can use the methods from the Adafruit_BME280 library on your bme1 and bme2 objects to read temperature, humidity and pressure.

Another alternative

For simplicity, you can use the predefined Wire() and Wire1() objects:

  • Wire(): creates an I2C bus on the default pins GPIO 21 (SDA) and GPIO 22 (SCL)
  • Wire1(SDA_2, SCL_2, freq): creates an I2C bus on the defined SDA_2 and SCL_2 pins with the desired frequency.
ESP32 with Multiple I2C BME280 Schematic Diagram
Click image to enlarge

Here’s the same example but using this method. Now, one of your sensors uses the default pins, and the other uses GPIO 32 and GPIO 33.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-i2c-communication-arduino-ide/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#define SDA_2 33
#define SCL_2 32

Adafruit_BME280 bme1;
Adafruit_BME280 bme2;

void setup() {
  Serial.begin(115200);
  Serial.println(F("BME280 test"));

  Wire.begin();
  Wire1.begin(SDA_2, SCL_2);

  bool status1 = bme1.begin(0x76);  
  if (!status1) {
    Serial.println("Could not find a valid BME280_1 sensor, check wiring!");
    while (1);
  }
  
  bool status2 = bme2.begin(0x76, &Wire1);  
  if (!status2) {
    Serial.println("Could not find a valid BME280_2 sensor, check wiring!");
    while (1);
  }
 
  Serial.println();
}

void loop() { 
  // Read from bme1
  Serial.print("Temperature from BME1= ");
  Serial.print(bme1.readTemperature());
  Serial.println(" *C");

  Serial.print("Humidity from BME1 = ");
  Serial.print(bme1.readHumidity());
  Serial.println(" %");
  
  Serial.print("Pressure from BME1 = ");
  Serial.print(bme1.readPressure() / 100.0F);
  Serial.println(" hPa");

  Serial.println("--------------------");
  
  // Read from bme2
  Serial.print("Temperature from BME2 = ");
  Serial.print(bme2.readTemperature());
  Serial.println(" *C");

  Serial.print("Humidity from BME2 = ");
  Serial.print(bme2.readHumidity());
  Serial.println(" %");
  
  Serial.print("Pressure from BME2 = ");
  Serial.print(bme2.readPressure() / 100.0F);
  Serial.println(" hPa");

  Serial.println("--------------------");
  delay(5000);
}

View raw code

You should get both sensor readings on your Serial Monitor.

ESP32 multiple BME280 sensors serial monitor output

Wrapping Up

In this tutorial you learned more about I2C communication protocol with the ESP32. We hope you’ve found the information in this article useful.

To learn more about the ESP32 GPIOs, read our reference guide: ESP32 Pinout Reference: Which GPIO pins should you use?

Learn more about the ESP32 with our resources:

Thanks for reading.



Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD »

Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD »

Recommended Resources

Build a Home Automation System from Scratch » With Raspberry Pi, ESP8266, Arduino, and Node-RED.

Home Automation using ESP8266 eBook and video course » Build IoT and home automation projects.

Arduino Step-by-Step Projects » Build 25 Arduino projects with our course, even with no prior experience!

What to Read Next…


Enjoyed this project? Stay updated by subscribing our newsletter!

76 thoughts on “ESP32 I2C Communication: Set Pins, Multiple Bus Interfaces and Peripherals (Arduino IDE)”

  1. Hi Sara,
    Very good tutorial. You have mentioned that multiple devices can be looped Pl, let me know how many devices we can connect on same bus. What will be the maximum distance between devices and the ESp32. Which type and size of cable is required to connect various devices since different types of cable have different resistance per meter of length.
    Thanks

    Reply
  2. Hi Rui and Sara

    I can see the advantage of having two I2C busses when you want to use two sensors with the same fixed addresses (although the two BME280s in your example produce different results when located next to each other) but what other advantages are there in running two separate busses?

    Apart from when I need BlueTooth, I tend to stick to ESP8266, but maybe I’m missing out on something…

    Reply
  3. Rui, great tutorial. Thanks.

    Looking forward to the ESP8266 version, since that’s the kind of hardware I have, mostly.

    Reply
  4. Hello, i would like to use my ESP32 as a Slave, to do this, i use de Wire.h library, but it’s look like the .onReceive don’t working…
    Do you have some solution ?

    Reply
  5. Hi! Thanks for the tutorial!
    I’m trying to connect a gpio extender (PCF8574) to the esp32 CAM, with no luck. Do you know how can I set up a new i2c bus for the extender? I’ve tried using ports I2C_SDA 0 , I2C_SCL 2 with no luck.

    Reply
  6. Hello friends.
    Could you please explain how this works when using the Adafruit ADS1115 library?

    How to tell her that it is necessary to use the second woWire bus (1)?

    I tried pointing as Adafruit_ADS1115 ads1115 (& I2CSensors);
    This does not work.

    Reply
    • Hello Nikolay.

      Did you manage to get the ADS1115 working on the second I2C bus?

      I already tried to modify the SDA, SCL Pins in the library, what Sara suggested.
      Did not work for me.
      Returns “no matching funtction to call” when I want to start the ads.begin like this:
      ads.begin(0x48, &I2Cone);
      The standard I2C bus is already blocked by OLED and LoRa, so I have to use Pins SDA 21 and SCL 13 for BME280, BH1750 and ADS1115.

      Reply
  7. hi !
    really great tutorial!! many many thanks !!
    My question is : is it available a tutorial to connect ESP32 as master and one or more d1-mini (esp8266) as slaves ?
    Many thanks
    peppe

    Reply
    • You may find a wireless protocol such as ESP-NOW would be a better solution than I2C. I am pretty sure ESP32 and ESP8266 would talk to each other just fine, with a payload of 250 bytes per transmission. It all depends what you want to do with the system.

      Reply
  8. Having worked a bit with I2C sensors in my past, I can say they are not very good for long distance (i.e, more than a meter) separation. One wire is a much better protocol for that. I2C was designed for use on the same board with distances of a few centimeters of bus length.

    That said, you can get more distance with lower bus frequencies, The maximum distance for your sensor would need to be found experimentally, and many factors affect it. Things such as the type of cable, external interference/noise, and the voltage used to drive the bus. The longer the distance the more critical these become.

    Personally, I prefer other methods to connect over longer distances. It is easier and more time efficient to use a protocol that was designed for distance such as RS232 or RS485 with appropriate driver ICs. For short distances such as to a display on on board sensor, I2C is great though!

    It is important in this hobby to pay attention to what things were designed to do, and use them appropriately for the greatest success.

    Reply
  9. Hello friends.
    Has anyone managed to combine this board with a solar battery for autonomous power supply?

    Share the schematic pls!

    Reply
  10. I have found your tutorials most enlightening. I am trying to log a BMP280 with ESP32 CAM on alternate I2C, but keep getting compiler errors with your code, which was written for BME280. I have delved into the .h and .cpp codes but cannot find why the BMP280 is not being found. Your code works fine with ESP12E and BMP280, if I use the library BMP280_DEV instead of the Adafruit library, but that library code does not support ESP32 with I2C, only SPI. I need to use the SD card facility on the ESP32CAM to log temperature and pressure. I tried mapping defaults 21 & 22 which are not accessible, to 3 & 16. Could it be that the Adafruit BMP280 library does not support alternate I2C ?

    Reply
    • Hi.
      The BME280 library from adafruit and the camera library for the ESP32-CAM conflict with each other.
      I have used the Saprkfun BME280 library with the ESP32-CAM and it works well.
      But I don’t know if it works with the BMP280.
      I hope this helps.
      Regards,
      Sara

      Reply
  11. Hi Sara,
    Thanks so much for the quick reply. I tried the Sparkfun Library you suggested but I get a compiler error “Error compiling for board AiThinker ESP32 CAM”, which is what I get with Adafruit as well.
    I do not think there has been much written for ESP32 Cam that uses alternate I2C, which is needed because of the scarcity of GPIO pins. I will try the TTGO next up.
    Regards
    Lockie

    Reply
  12. Hi Sara,
    I ran the example sketch”softI2C” from the Sparkfun BME library. I set compiler preferences to verbose and picked out examples of the noted errors. See below:

    C:\Users\marts\Documents\Arduino\libraries\SoftwareWire\SoftwareWire.cpp:134:16: error: cannot convert ‘volatile uint32_t* {aka volatile unsigned int}’ to ‘volatile uint8_t {aka volatile unsigned char}’ in assignment
    _sdaPinReg = portInputRegister(port); // PinReg is the input register, not the Arduino pin.
    C:\Users\marts\Documents\Arduino\libraries\SoftwareWire\SoftwareWire.cpp: In member function ‘void SoftwareWire::printStatus(Print&)’:
    C:\Users\marts\Documents\Arduino\libraries\SoftwareWire\SoftwareWire.cpp:533:27: error: cast from ‘volatile uint8_t
    {aka volatile unsigned char*}’ to ‘uint16_t {aka short unsigned int}’ loses precision [-fpermissive]
    Ser.println( (uint16_t) _sdaPortReg, HEX);
    Using library SoftwareWire at version 1.5.1 in folder: C:\Users\marts\Documents\Arduino\libraries\SoftwareWire
    Using library Wire at version 1.0.1 in folder: C:\Users\marts\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\Wire
    Using library SparkFun_BME280 at version 2.0.8 in folder: C:\Users\marts\Documents\Arduino\libraries\SparkFun_BME280
    Using library SPI at version 1.0 in folder: C:\Users\marts\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\SPI
    exit status 1
    Error compiling for board AI Thinker ESP32-CAM.

    I haven’t found anything yet that works with alternate I2C on ESP32-CAM.
    Cheers,

    lockie

    Reply
  13. Hi Sara,
    Thank you for your effort in helping me. In my debugging efforts I noticed that all I have to do is include “SparkFunBME280.h” to get compiler errors which relate to “SoftwareWire.h” by Testato, which is called by the former if you enable software I2C as per the instructions.
    Does your example (noted in your last post) enable software i2c?
    I have tried it with TTGO and get compiler errors as well, although if I use BMP280_Dev.h instead all works OK (But not for software I2C, which BMP280_Dev does not support for ESP32 devices)
    Regards

    lockie

    Reply
    • Hi.
      We use the library as shown in the post.
      include the following libraries:
      #include <Wire.h>
      #include “SparkFunBME280.h”

      Define the I2C pins:
      #define I2C_SDA 14
      #define I2C_SCL 15

      Initialize the sensor as follows:
      Wire.begin(I2C_SDA, I2C_SCL);
      bme.settings.commInterface = I2C_MODE;
      bme.settings.I2CAddress = 0x76;
      bme.settings.runMode = 3;
      bme.settings.tStandby = 0;
      bme.settings.filter = 0;
      bme.settings.tempOverSample = 1;
      bme.settings.pressOverSample = 1;
      bme.settings.humidOverSample = 1;
      bme.begin();

      Get readings:
      float temperature, humidity;
      temperature = bme.readTempC();
      //temperature = bme.readTempF();
      humidity = bme.readFloatHumidity();

      No need for any other libraries or other configurations.

      Regards,
      Sara

      Reply
  14. Hi Sara,
    I have included the code you posted in my sketch, but I still get the same errors in the SoftwareWire.cpp library, when using software I2C with my TTGO as “ESPDEV Module” in Arduino as suggested by TTGO. So that makes me think that the source of the error is in the SoftwareWire library.
    So I checked the Github for Testato’s SoftwareWire and it appears there are issues when using MCU’s with 32 bit registers. Maybe you are not using the software I2C feature – that might explain why the code works for you.
    Anyway, I appreciated your suggestions.
    Regards

    Lockie

    Reply
  15. Hello again,
    I tried to compile some example sketches from SparkfunBME280 and I get compiler errors. Then I tried to compile a sketch from SoftwareWire and that also gives a compile error. So then I reverted to the SparkfunBME280.h that doesn’t call SoftwareWire and tried compiling your code and it compiled OK.
    That suggests my argument that SoftwareWire library does not support ESP32 devices may be correct. My original intention was to use a BMP280 with ESP32-Cam using software defined I2C as there is a shortage of GPIO’s available. The hardware GPIO pins are 21 & 22, but when using the SD card on ESP32-CAM using one of RUI’s sketches only GPIO 0, 3, &16 are accessible. That is why I wanted to use software defined I2C pins.
    I have tried numerous BMP280 libraries and the only one that seems to work well is BMP280_DEV, but as I said previously it does not support ESP32.
    I then changed my board to AiThinker ESP32CAM and recompiled and got an out of memory error. So I thought that was more positive and decided to power down, then power-up and re-compile. This time it worked!!
    So to re-cap for other newbies, SoftwareWire library does not appear to work with ESP32 devices, so do not call it in SparkFunBME280 for software I2C.
    Once again, Thanks.

    Reply
  16. Whilst I can now print temperature and pressure to the serial monitor, I am struggling to print it to the SD card. I am using a version of Rui’s logSD which successfully writes the date and time to the card, but leaves the temperature blank. The code for the routine is:
    // Write the sensor readings on the SD card
    void logSDCard() {
    dataMessage = String(readingID) + “,” + String(dayStamp) + “,” + String(timeStamp) + “,” + String(temperature) + “\r\n”;
    Serial.print(“Save data: “);
    Serial.println(dataMessage);
    appendFile(SD_MMC, “/data.txt”, dataMessage.c_str());
    }

    I have not been able to pass the temperature variable to this routine.
    Any ideas?
    Thanks, Lockie

    Reply
    • Maybe this will help:
      You may need to convert the data to a string first. A welcomed utility function I found is dtostrf. (decimal to string). Remember that serial comms know nothing about data types, they simply send characters.

      char * dtostrf (double __val, signed char __width, unsigned char __prec, char *__s)

      The dtostrf() function converts the double value passed in val into an ASCII representationthat will be stored under s. The caller is responsible for providing sufficient storage in s.

      Conversion is done in the format “[-]d.ddd”. The minimum field width of the output string (including the ‘.’ and the possible sign for negative values) is given in width, and prec determines the number of digits after the decimal sign. width is signed value, negative for left adjustment.

      The dtostrf() function returns the pointer to the converted string s.

      char buffer[10];
      dtostrf(float or double Var, 5, 1, buffer);

      //now you can print or send the buffer to a text display

      Below is how I used it with a couple of one wire probes. As per above
      tempInC is a float, I expect a max of 6 chars, with 1 after decimal point. I have two char buffers named testInC and testOutC respectively, each 7 chars.
      dtostrf(tempInC, 6,1, testInC);
      dtostrf(tempOutC, 6,1, testOutC);

      Best of luck!
      Dave K.

      Reply
  17. Thanks so much, Dave.
    As a newbie what you told me was DD, but I perservered and after dozens of compiles I got the declarations correct, and so now it prints both to the screen and the SD card.
    Here is the code snippit:
    static char tempc[8];
    static char pressr[9];
    static float temperature;
    static float pressure;
    // Function to get temperature
    String Getreadings(){
    //float temperature, pressure;
    temperature = bme.readTempC();
    pressure = bme.readFloatPressure()/100;
    dtostrf(temperature,6,2,tempc);
    dtostrf(pressure,7,2,pressr);
    String message = “Temperature: ” + String (temperature) + ” ºC \n”;
    message += “Pressure: ” + String (pressure) + ” hPa \n”;
    return message;

    So now I am logging BMP280 temperature and pressure every minute and writing the data to the onboard SD card on the ESP32 CAM together with time and date off the internet. (I started Arduino mid-August)
    Thanks to both you and Sara for getting me there, and to Rui (for the ideas).
    I have been at this project for some weeks now, and I have been frustrated by the lack of documentation of the various libraries and their foibles.

    Reply
    • Sounds like it helped! GREAT!
      Congratulations on your success!

      I know for sure that info helped me get something to display on an OLED, and in a log, so I was happy to share it.

      Dave

      Reply
  18. As a beginner to the IOT area I find your tutorials really helpful.
    Could you please elaborate on using multiplexer for connecting two devices, probably with a schematic.

    Reply
  19. Hi Sara,
    Tanks very much for this very useful article.
    I’m ask if I can use two devices in the same pins of SPI interface (MISO,MOSI,CLK,SS)?
    and if yes, this is accurate or not?

    Reply
    • Hi.
      You can use more than one SPI device on the same pins as long as each device has its own chip select (SS) pin.
      Regards,
      Sara

      Reply
  20. Hi, Thank you very much for the information, Do you have an example of how to configure the ESP32 in Slave mode for example at address 10 or 0x0A? I try to make a chat between two modules ESP32.
    Best Regards
    Hugo

    Reply
  21. Hi Rui,
    I will want to know how can multiple masters controlling the same slave. I have tried to use two ESP8266 to read one MCP23017 module but not successful. After googling people commented that ESP8266 has no slave feature. I am not sure about ESP32. Please advise.

    Regards
    CEP

    Reply
  22. Hi,
    I want to connect 3 modules to the I2C protocols
    if it is easy to use it by giving it a different addresses ??
    or use i2c mux like TCA9548A better ??

    Reply
  23. Hi Sara,
    Thank you very much for the information.
    I tried to connect the sensor getting 0.0 values in both Tempeture and Humidity using the following code:

    #include <Wire.h>
    #include “SparkFunBME280.h”

    //CAMERA_MODEL_AI_THINKER
    #define PWDN_GPIO_NUM 32
    #define RESET_GPIO_NUM -1
    #define XCLK_GPIO_NUM 0
    #define SIOD_GPIO_NUM 26
    #define SIOC_GPIO_NUM 27

    #define Y9_GPIO_NUM 35
    #define Y8_GPIO_NUM 34
    #define Y7_GPIO_NUM 39
    #define Y6_GPIO_NUM 36
    #define Y5_GPIO_NUM 21
    #define Y4_GPIO_NUM 19
    #define Y3_GPIO_NUM 18
    #define Y2_GPIO_NUM 5
    #define VSYNC_GPIO_NUM 25
    #define HREF_GPIO_NUM 23
    #define PCLK_GPIO_NUM 22

    //Define the I2C pins:
    #define I2C_SDA 14
    #define I2C_SCL 15
    BME280 bme;
    void setup(){
    Serial.begin(115200);
    delay(5000);
    Serial.println(“Empezamos”);
    }

    void loop(){

    //Initialize the sensor as follows:
    Wire.begin(I2C_SDA, I2C_SCL);
    bme.settings.commInterface = I2C_MODE;
    bme.settings.I2CAddress = 0x76;
    bme.settings.runMode = 3;
    bme.settings.tStandby = 0;
    bme.settings.filter = 0;
    bme.settings.tempOverSample = 1;
    bme.settings.pressOverSample = 1;
    bme.settings.humidOverSample = 1;
    bme.begin();
    //Get readings:
    float temperature, humidity;
    temperature = bme.readTempC();
    //temperature = bme.readTempF();
    humidity = bme.readFloatHumidity();

    Serial.println(temperature);
    Serial.println(humidity);
    delay(5000);
    }

    Is there any option or code to check if the sensor is correctly wired ?
    Thanks in advance for your help

    Reply
    • Hi.
      We have this project that interfaces the ESP32-CAM the BME280 sensor.
      You can take a look at the parts of code that interface with the sensor to see if you’re missing something.
      Regards,
      Sara

      Reply
  24. Dear Sara ad Rui,

    Thank you for this explanation on using IIC on other than the “standard” GPIO pins. The BME280 works perfecto on an ESP32-CAM, using GPIO 12 and GPIO15. Connecting SCL on GPIO13 won’t work -> No proper boot.

    When adding other IIC component(s) though, I run into trouble.
    Basically since I can not discover the proper libraries where “off standard” GPIO-pins are supported or can be specified.
    More specifically for i.e. a RTC (DS1307). The goal is to make a temp/humidity/pressure logger, writing the data on a micro SD card, without internet access, hence the RTC. Time is set once, RTC keeps time.
    No Cam needed, I use the ESP32-Cam since it has a built in CF-card, an because of the size. (ESP32-CAM and BME280 do fit in one LEGO-Block 😉 )

    Where for the BME28 your example allows for “off standard” GPIO-pins by means of:

    I2CBME.begin(I2C_SDA, I2C_SCL, 100000);
    status = bme.begin(0x76, &I2CBME);
    This is not continued in your example where two IIC devices are used – the display and the BME280:

    display.begin(SSD1306_SWITCHCAPVCC, 0x3C) // init the display
    bool status = bme.begin(0x76); // init the BME280
    When I check the RTClib on the Adafruit repository, also no SCL and SCA can be specified. https://github.com/adafruit/RTClib/blob/master/RTClib.cpp

    boolean RTC_DS1307::begin(void) {
    Wire.begin();
    Wire.beginTransmission(DS1307_ADDRESS)
    ….etc…

    Question:
    On what way can I program around this and use pin , or is it required to (re)program the C++ methods?

    Thank you very much for any help

    Reply
  25. Hi Sara,

    Thank you for the lightning fast reply.

    The “test” library for the RTC works like a charm with the software set i2c pins. (i2c_rtclib.h)
    The BME280 works perfect with the Adafruit_BME280.h lib
    Each perfect when going without the other,….. but using them in one sketch provides a spin-crash-and-burn reboot (causes by a int. div-zero)

    Output below: Wifi connected, print temp, pressure and humidity,
    next line: print day and time….–>> Crash.

    IP address: 192.168.2.18
    Temp. C: 20.65 Pressure: 1020.47 Humidity: 42.46
    (Mon) 23:11:7
    Temp. C: Guru Meditation Error: Core 1 panic’ed (IntegerDivideByZero). Exception was unhandled.
    Core 1 register dump:
    .

    .

    Any Clues .. ?
    I will investigate further also.

    Greetings & Regards

    Reply
    • Hi again.
      I’m not sure what’s the issue.
      Are you using the same I2C pins or different for each module?
      You can use the same pins for both modules. If that doesn’t work, you can also try to use different pins for each of them.
      Regards,
      Sara

      Reply
  26. Hi Again….
    I’m using the same pins for both IO devices.
    Using other pins won’t do for me on the ESP32. Not enough free GPIO’s.
    I need -that’s the reason I took this dev-board- a small size board AND cf-card writing capabilities. Using an other dev-board or using more pins compromises either one. (soldering / tampering excluded)

    Next try is 2 other pin’s for both devices (BME280 and RTC DS1307), but initially 1 SCL/SDA set.

    Q: Is it helpfull if I undress the code snippet to a minimum and post it here ?

    Re-Regards,
    Piter

    Reply
    • Hi.
      You mentioned that both modules worked individually. So, it may not be related to the code.
      With both modules connected to the board, can you run an I2C scanner sketch? To see if it can find both modules when they are connected at the same time.
      If it can’t find the modules, it can be something related to the board…
      I’m not sure what can be the problem 😐
      Regards,
      Sara

      Reply
  27. Hi Sara,
    To begin with : The problem is solved.
    It was code related (Examples in soft_RTClib library caused the problem).

    I could not run the I2C scanner sketch, (no overloaded methods for softpins available).. Any plans for this… ?

    If you check the output from my posting April 19, 2021 at 9:35 pm, you see that the ESP32-CAM crashes when reading the BME280 temperature for the second time.
    It prints Temp. C:… and then the DIV 0 occurs.
    So, BME280 is read OK initially, also the RTC clock is read OK initially

    The culprit was in the file “Test.ino” in the soft_RTClib “test.zip

    I used / copied the following function readRTC():

    /Reads an shows RTC/
    void read_RTC(byte soft_SDA_pin, byte soft_SCL_pin) {
    RTC_DS3231 my_RTC;/address I2C: 0x68/
    TwoWire connection = TwoWire(0);
    /(SDA, SCL, freq);/
    connection.begin(soft_SDA_pin, soft_SCL_pin, 100000);
    my_RTC.begin(&connection);
    char week_day[7][4] = {“Sun”, “Mon”, “Tue”, “Wed”, “Thu”, “Fri”, “Sat”};
    DateTime now = my_RTC.now();
    Serial.print(” ………… etc…………

    Taking the decaration and initialisations out of this function, to global and setup() respectively solved the problem.
    Sounds easy… took some tries… 🙂

    Having SCL and SDA on 13 and 15 the sketch output is now like:
    ..
    (Tue) 23:26:42 Temp:20.41 C Pressure: 1018.82 Hpa Humidity: 44.31%
    (Tue) 23:26:43 Temp:20.41 C Pressure: 1018.86 Hpa Humidity: 44.30%
    (Tue) 23:26:44 Temp:20.41 C Pressure: 1018.82 Hpa Humidity: 44.30%
    ..

    On with the next steps for the datalogger.

    Thank you for your help in pointing me in the rigth direction for this library.

    Regards,
    Piter

    Reply
  28. Hi,
    So be aware… Declaring and initializing the soft_I2C variables / structs / methods inside a function to be called later, may cause havoc.

    I guess somewhere wire() uses some global var’s or global interaction with other 2wire comm. Doing the initial stuff inside a function (on the stack) does not perform well with this, since the function stack is invalid after function termination.
    Regards,
    Piter.

    Reply
  29. Your information is pure GOLD ! Thank you so much for your hard work. I am sure you spent many hours doing this for us ! Your the greatest.

    Reply
  30. I have an I2C sensor without a library (ADPD144RI sensor). Is there a tutorial or how to use an I2C sensor without a library?

    Reply

Leave a Comment

Download our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.