ESP32 ESP-IDF BME680 Environmental Sensor (Gas, Pressure, Humidity, Temperature)

In this tutorial, we will learn to interface BME680 with ESP32 using ESP-IDF driver. This guide will include a brief description of BME680, connection diagram with ESP32 board and then setting up a project in VS Code with ESP-IDF extension to acquire ambient temperature, barometric pressure, relative humidity, and gas (VOC) or Indoor air quality (IAQ) from this environmental sensor.

Prerequisites

Before we move ahead, make sure you have the latest version of VS Code installed on your system with the ESP-IDF extension configured.

BME680 Introduction

BME680 is a four in one low power-driven sensor, which has integrated temperature, pressure, humidity, and gas sensors. It runs on an operating voltage of 1.8-3.8V and communicates with other microcontrollers through I2C and SPI protocol. This sensor is used in areas such as tracking the quality of air, humidity indicators, weather trends, home automation, and controlling GPS enhancements.

Pinout Diagram

The pinout diagram of BME680 sensor is shown below:

BME680 pinout diagram
Pin NameDescription
VCCPin to power up sensor
GNDGND pin for power supply
SCLBME680 provides 2 interfaces such as SPI and I2C to get sensor readings. This pin is serial clock signal for both I2C and SPI interfaces.
SDAWith I2C protocol this pin acts as SDA or Serial Data pin and when using SPI protocol this pin acts as SDI or Serial Data In also known as MOSI (‘Master out Slave in’).
SDOThis is the SDO (Slave data out) /MISO (Master in slave out) pin for SPI communication. This pin is used for SPI interface only.
CSThis is the Chip Select pin used in SPI communication. Acts as an input to the sensor.

ESP32 I2C Pins

We will be using I2C interface to get BME680 sensor readings with ESP32 and ESP-IDF. Now let’s discuss I2C pins of ESP32. The diagram below shows the pinout for the ESP32, where the default I2C pins are highlighted in red. These are the default I2C pins of the ESP32 DEVKIT module. However, if you want to use other GPIO pins as I2C pins then you will have to set them in code.

ESP32 Pinout I2C Pins

Refer to the following article to know more about ESP32 GPIO pins:

You can check this complete guide on ESP32 I2C communication:

Interface BME680 with ESP32 ESP-IDF

BME680 sensor with ESP32 ESP-IDF

We will need the following components to connect ESP32 board with BME680.

  1. ESP32 board
  2. BME680 Sensor
  3. Connecting Wires
  4. Breadboard

The connection of BME680 with the ESP32 board is straightforward as it just involves the connection of the 4 pins (GND, VCC, SDA, and SCL) with ESP32. We have to connect the VCC terminal with 3.3V pin, ground with the ground (common ground), SCL of the sensor with SCL of ESP32 board, and SDA of the sensor with the SDA pin of the ESP32 board.

In ESP32, the default I2C pin for SDA is GPIO21 and for SCL is GPIO22.

The connections between the devices are specified in the table below:

BME680 SensorESP32
GNDGND
VCC3.3V
SDAGPIO21(I2C SDA)
SCLGPIO22 (I2C SCL)
interface BME680 with ESP32 using ESP-IDF

BME680 ESP32 ESP-IDF Code

We will build and create a project in VS Code with ESP-IDF extension to measure ambient temperature, barometric pressure, relative humidity, and gas (VOC) or Indoor air quality (IAQ) from BME60 environmental sensor.

The code will read sensor measurements and print them in the ESP-IDF terminal after every 1 seconds. We will use the BME680 ESP-IDF library provided by UncleRus on GitHub.

Create Example Project

Open your VS Code and head over to View > Command Palette. Type ESP-IDF: New Project in the search bar and press enter.

Specify the project name and directory, along with the ESP-IDF board and target. For the ESP-IDF board, we have chosen the custom board option. For ESP-IDF target, we have chosen ESP32 module. Click ‘Choose Template’ button to proceed forward.

In the Extension, select ESP-IDF option:

ESP-IDF in VS Code New Project 2

We will click the ‘sample_project’ under the get-started tab. Now click ‘Create project using template sample_project.’

ESP-IDF in VS Code New Project 3

You will get a notification that the project has been created. To open the project in a new window, click ‘Yes.’

This opens the project that we created inside the EXPLORER tab. There are several folders inside our project folder. This is the same for every project which you will create through ESP-IDF Explorer.

  • First, let’s add the necessary header files for the BME680 libraries required for this project. Create a new folder called ‘components’ and Now add the bme680, esp_idf_lib_helpers and i2cdev folders in ‘components’ folder by copying from this link as listed below:
bme680 library esp-idf

Now head over to the main.c file. The main folder contains the source code meaning the main.c file will be found here. Go to main > main.c and open it. Copy the code given below in that file and save it.

#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_system.h>
#include <bme680.h>
#include <string.h>

#define BME680_I2C_ADDR 0x77
#define PORT 0
#define CONFIG_EXAMPLE_I2C_MASTER_SDA 21
#define CONFIG_EXAMPLE_I2C_MASTER_SCL 22

#ifndef APP_CPU_NUM
#define APP_CPU_NUM PRO_CPU_NUM
#endif

void bme680_test(void *pvParameters)
{
    bme680_t sensor;
    memset(&sensor, 0, sizeof(bme680_t));

    ESP_ERROR_CHECK(bme680_init_desc(&sensor, BME680_I2C_ADDR, PORT, CONFIG_EXAMPLE_I2C_MASTER_SDA, CONFIG_EXAMPLE_I2C_MASTER_SCL));

    // init the sensor
    ESP_ERROR_CHECK(bme680_init_sensor(&sensor));

    // Changes the oversampling rates to 4x oversampling for temperature
    // and 2x oversampling for humidity. Pressure measurement is skipped.
    bme680_set_oversampling_rates(&sensor, BME680_OSR_4X, BME680_OSR_2X, BME680_OSR_2X);

    // Change the IIR filter size for temperature and pressure to 7.
    bme680_set_filter_size(&sensor, BME680_IIR_SIZE_7);

    // Change the heater profile 0 to 200 degree Celsius for 100 ms.
    bme680_set_heater_profile(&sensor, 0, 200, 100);
    bme680_use_heater_profile(&sensor, 0);

    // Set ambient temperature to 10 degree Celsius
    bme680_set_ambient_temperature(&sensor, 10);

    // as long as sensor configuration isn't changed, duration is constant
    uint32_t duration;
    bme680_get_measurement_duration(&sensor, &duration);

    TickType_t last_wakeup = xTaskGetTickCount();

    bme680_values_float_t values;
    while (1)
    {
        // trigger the sensor to start one TPHG measurement cycle
        if (bme680_force_measurement(&sensor) == ESP_OK)
        {
            // passive waiting until measurement results are available
            vTaskDelay(duration);

            // get the results and do something with them
            if (bme680_get_results_float(&sensor, &values) == ESP_OK)
                printf("BME680 Sensor: %.2f °C, %.2f %%, %.2f hPa, %.2f Ohm\n",
                        values.temperature, values.humidity, values.pressure, values.gas_resistance);
        }
        // passive waiting until 1 second is over
        vTaskDelayUntil(&last_wakeup, pdMS_TO_TICKS(1000));
    }
}

void app_main()
{
    ESP_ERROR_CHECK(i2cdev_init());
    xTaskCreatePinnedToCore(bme680_test, "bme680_test", configMINIMAL_STACK_SIZE * 8, NULL, 5, NULL, APP_CPU_NUM);
}

How Code Works?

Firstly, we will start by including the necessary esp-idf libraries for this project. This includes the i2c driver, gpio driver, ESP logging library, BME680 library and FreeRTOS library.

#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_system.h>
#include <bme680.h>
#include <string.h>

These lines define the address of BME680 which 0x77, I2C port and ESP32 I2C pin numbers which 21 (SDA) and 22(SCL).

#define BME680_I2C_ADDR 0x77
#define PORT 0
#define CONFIG_EXAMPLE_I2C_MASTER_SDA 21
#define CONFIG_EXAMPLE_I2C_MASTER_SCL 22

We will create a FreeRTOS task and pinned it to core 0 of ESP32. This task will take BME680 sensor readings after every one second. This define a name for APP_CPU_NUM.

#define APP_CPU_NUM PRO_CPU_NUM

Initialize the i2c library and it should be called before calling any bme680 esp-idf library function.

    ESP_ERROR_CHECK(i2cdev_init());

xTaskCreatePinnedToCore() creates a FreeRTOS task and pinned it to core 0 that means “bme680_test” task will run on core 0 of ESP32. If you don’t know how to create FreeRTOS tasks in esp-idf, you can refer to this article:

    xTaskCreatePinnedToCore(bme680_test, "bme680_test", configMINIMAL_STACK_SIZE * 8, NULL, 5, NULL, APP_CPU_NUM);

BME680 Sensor Readings Function

bme680_test() task initialize BME680 sensor, defines I2C port to which sensor is connected, sets sampling rates for temperature, humidity, pressure and gas readings, set filers size for temperature and pressure, sets the measurement duration, etc.

    bme680_t sensor;
    memset(&sensor, 0, sizeof(bme680_t));

    ESP_ERROR_CHECK(bme680_init_desc(&sensor, BME680_I2C_ADDR, PORT, CONFIG_EXAMPLE_I2C_MASTER_SDA, CONFIG_EXAMPLE_I2C_MASTER_SCL));

    // init the sensor
    ESP_ERROR_CHECK(bme680_init_sensor(&sensor));

    // Changes the oversampling rates to 4x oversampling for temperature
    // and 2x oversampling for humidity. Pressure measurement is skipped.
    bme680_set_oversampling_rates(&sensor, BME680_OSR_4X, BME680_OSR_2X, BME680_OSR_2X);

    // Change the IIR filter size for temperature and pressure to 7.
    bme680_set_filter_size(&sensor, BME680_IIR_SIZE_7);

    // Change the heater profile 0 to 200 degree Celsius for 100 ms.
    bme680_set_heater_profile(&sensor, 0, 200, 100);
    bme680_use_heater_profile(&sensor, 0);

    // Set ambient temperature to 10 degree Celsius
    bme680_set_ambient_temperature(&sensor, 10);

    // as long as sensor configuration isn't changed, duration is constant
    uint32_t duration;
    bme680_get_measurement_duration(&sensor, &duration);

    TickType_t last_wakeup = xTaskGetTickCount();

    bme680_values_float_t values;
    while (1)
    {
        // trigger the sensor to start one TPHG measurement cycle
        if (bme680_force_measurement(&sensor) == ESP_OK)
        {
            // passive waiting until measurement results are available
            vTaskDelay(duration);

            // get the results and do something with them
            if (bme680_get_results_float(&sensor, &values) == ESP_OK)
                printf("BME680 Sensor: %.2f °C, %.2f %%, %.2f hPa, %.2f Ohm\n",
                        values.temperature, values.humidity, values.pressure, values.gas_resistance);
        }
        // passive waiting until 1 second is over
        vTaskDelayUntil(&last_wakeup, pdMS_TO_TICKS(1000));
    }

Now let’s see function of each line one by one.

Here we create an instance of bme680_t struct which is used to configure and initialize sensor. After that initialize all members of sensor struct to zero with memset.

bme680_t sensor;
memset(&sensor, 0, sizeof(bme680_t));
typedef struct
{
    i2c_dev_t i2c_dev;              //!< I2C device descriptor

    bool meas_started;              //!< Indicates whether measurement started
    uint8_t meas_status;            //!< Last sensor status (for internal use only)

    bme680_settings_t settings;     //!< Sensor settings
    bme680_calib_data_t calib_data; //!< Calibration data of the sensor
} bme680_t; 

bme680_init_desc() function initialize bme680 device descriptor by passing sensor struct, BME680 I2C address, ESP32 I2C pin numbers.

    ESP_ERROR_CHECK(bme680_init_desc(&sensor, BME680_I2C_ADDR, PORT, CONFIG_EXAMPLE_I2C_MASTER_SDA, CONFIG_EXAMPLE_I2C_MASTER_SCL));

bme680_init_sensor() method initialize a BME680 sensor device data structure, probes the sensor, soft resets the sensor, and configures the sensor with the the following default settings:

  • Oversampling rate for temperature, pressure, humidity = osr_1x
  • Filter size for pressure and temperature = iir_size 3
  • Heater profile 0 with 320 degree C and 150 ms duration
    ESP_ERROR_CHECK(bme680_init_sensor(&sensor));

bme680_set_oversampling_rates() function set the oversampling rates for measurements of temperature, humidity, pressure and gas. With this function, we can set individual sampling rates for temperature, pressure and humidity. The bme680_init_desc() function sets the default sampling rates to 1x. But we can change them with this function. The possible oversampling rates are 1x (default), 2x, 4x, 8x, 16x.

   bme680_set_oversampling_rates(&sensor, BME680_OSR_4X, BME680_OSR_2X, BME680_OSR_2X);

bme680_set_filter_size() define the size of IRR filers because BME680 has an internal IRR filter which can be used to minimize effect of short-term changes in sensor output values that might be caused by external disturbances. IRR filer adjust the reduce the output bandwidth of sensor.

   bme680_set_filter_size(&sensor, BME680_IIR_SIZE_7);

The BME680 sensor have a built-in header for the gas measurement and it supports up to 10 heater profiles. Each profile consists of a temperature set-point (the target temperature) and a heating duration. This functions set the heater profile. The input arguments are sensor descriptor struct, heater profile, target temperature and heating duration. The bme680_set_heater_profile() function activate gas measurement with a given heater profile

 bme680_set_heater_profile(&sensor, 0, 200, 100);
 bme680_use_heater_profile(&sensor, 0);

The bme680_set_ambient_temperature() function set the ambient temperature for heater resistance calculation. Because heater resistance calculation algorithm takes into account the ambient temperature of the sensor.

   bme680_set_ambient_temperature(&sensor, 10);

bme680_get_measurement_duration() function determine the measurement duration from BME680 in terms of RTOS ticks.

 uint32_t duration;
 bme680_get_measurement_duration(&sensor, &duration);

TickType_t last_wakeup = xTaskGetTickCount();

This bme680_values_float_t struct will populate with sensor values when we call bme680_get_results_float() function to get temperature, humidity, pressure and gas data from the sensor.

typedef struct
{
    float temperature;    //!< temperature in degree C        (Invalid value -327.68)
    float pressure;       //!< barometric pressure in hPascal (Invalid value 0.0)
    float humidity;       //!< relative humidity in %         (Invalid value 0.0)
    float gas_resistance; //!< gas resistance in Ohm          (Invalid value 0.0)
} bme680_values_float_t;
  bme680_values_float_t values;

Inside the while loop, we call bme680_get_results_float() function to get BME680 sensor readings and print them on ESP-IDF terminal.

 while (1)
    {
        // trigger the sensor to start one TPHG measurement cycle
        if (bme680_force_measurement(&sensor) == ESP_OK)
        {
            // passive waiting until measurement results are available
            vTaskDelay(duration);

            // get the results and do something with them
            if (bme680_get_results_float(&sensor, &values) == ESP_OK)
                printf("BME680 Sensor: %.2f °C, %.2f %%, %.2f hPa, %.2f Ohm\n",
                        values.temperature, values.humidity, values.pressure, values.gas_resistance);
        }
        // passive waiting until 1 second is over
        vTaskDelayUntil(&last_wakeup, pdMS_TO_TICKS(1000));
    }

Compiling and uploading BME680 code to ESP32 with ESP-IDF

To flash your chip, type the following command in the serial terminal. Remember to replace the COM port with the one through which your board is connected.

idf.py -p COMX flash monitor

After the code is successfully flashed, you can view ambient temperature, barometric pressure, relative humidity, and gas (VOC) or Indoor air quality (IAQ) and if you hold sensor with your hand values of sensor will change.

BME680 EP32 ESP-IDF library tutorial

You may also check this BME680 Web server project:

You may also like to read:

Leave a Comment