Search

ESP32 Environmental Sensor: BME680 Gas, Pressure, Humidity, Temperature

The BME680 serves as a digital sensor for environmental measurements, covering gas, pressure, humidity, and temperature. This guide teaches you to integrate the BME680 sensor module with the ESP32 board through the Arduino IDE. Communication between the sensor and microcontroller occurs via I2C or SPI protocols.

You’ll be instructed on wiring the sensor to the ESP32 board, installing necessary libraries, employing a basic sketch to showcase sensor readings on the Serial Monitor, and setting up a web server for remote sensor monitoring.

Introducing BME680 Environmental Sensor Module

The BME680 is a comprehensive environmental sensor unit integrating gas, pressure, humidity, and temperature sensors. Its gas sensor is adept at detecting a wide spectrum of gases, including volatile organic compounds (VOCs), making it valuable for indoor air quality monitoring.

BME680 Measurements

This 4-in-1 digital sensor facilitates measurement of:

  • Temperature
  • Humidity
  • Barometric pressure
  • Gas: Volatile Organic Compounds (VOCs) such as ethanol and carbon monoxide

Gas Sensor

Equipped with a Metal-oxide (MOX) sensor, the BME680 detects VOCs present in the atmosphere. This sensor offers a qualitative assessment of the collective VOCs and contaminants in the air rather than identifying individual gas molecules.

MOX sensors feature a metal-oxide surface, a sensing chip for conductivity changes, and a heating element. VOC detection occurs through the adsorption of oxygen molecules onto the sensor’s sensitive layer. The BME680 responds to most indoor air pollutants, excluding CO2.

Upon exposure to reducing gases, oxygen molecules react, enhancing conductivity across the sensor’s surface. The BME680 initially outputs resistance values as a raw signal, which fluctuate in response to varying VOC concentrations:

  • Higher concentration of VOCs » Lower resistance
  • Lower concentration of VOCs » Higher resistance

The sensor’s surface reactions (and hence, resistance) are influenced by factors beyond VOC concentration, such as temperature and humidity.

Important Details About the Gas Sensor

The gas sensor provides a qualitative assessment of VOC gases in the surrounding atmosphere. This allows you to observe trends, compare results, and determine whether air quality is improving or deteriorating. For accurate measurements, calibration against known sources is necessary, along with the construction of a calibration curve.

Upon initial acquisition of the sensor, it is advisable to operate it for 48 hours to collect meaningful data. Additionally, it is recommended to run the sensor for 30 minutes before obtaining a gas reading.

BME680 Accuracy

Here’s the accuracy of the temperature, humidity and pressure sensors of the BME680:

SensorAccuracy
Temperature+/- 1.0ºC
Humidity+/- 3%
Pressure+/- 1 hPa

BME680 Operation Range

The following table shows the operation range for the temperature, humidity and pressure sensors for the BME680.

SensorOperation Range
Temperature-40 to 85 ºC
Humidity0 to 100 %
Pressure300 to 1100 hPa

BME680 Pinout

Here’s the BME680 Pinout:

VCCPowers the sensor
GNDCommon GND
SCLSCL pin for I2C communication
SCK pin for SPI communication
SDASDA pin for I2C communication
SDI (MOSI) pin for SPI communication
SDOSDO (MISO) pin for SPI communication
CSChip select pin for SPI communication

BME680 Interface

The BME680 supports I2C and SPI Interfaces.

BME680 I2C

To use I2C communication protocol, use the following pins:

BME680ESP32
SCLGPIO22
SDAGPIO 21

GPIO 22 (SCL) and GPIO 21 (SDA) are the default ESP32 I2C pins. You can use other pins as long as you set them properly on code.

BME680 SPI

To use SPI communication protocol, use the following pins:

BME680ESP32
SCL (SCK SPI Clock)GPIO 18
SDA (SDI MOSI)GPIO 23
SDO (MISO)GPIO 19
CS (Chip Select)GPIO 5

These are the default ESP32 SPI pins. You can use other pins as long as you set them properly in the code.

Parts Required

Component NameBuy Now
ESP32-WROOM-32 DevelopmentAmazon
BME680 Digital Temperature Humidity Pressure SensorAmazon
Please Note: These are affiliate links. I may make a commission if you buy the components through these links. I would appreciate your support in this way!

Schematic – ESP32 with BME680

The BME680 supports communication via both I2C and SPI protocols.

ESP32 with BME680 using I2C

Refer to the following schematic diagram for connecting the BME680 to the ESP32 using the default I2C pins.

ESP32 with BME680 using SPI

Alternatively, if you prefer using the SPI communication protocol, utilize the schematic diagram below for wiring the BME680 to the ESP32 using the default SPI pins.

Preparing Arduino IDE

For programming the ESP32 board, utilize the Arduino IDE. Ensure that the ESP32 add-on is installed. Refer to the following tutorial for guidance:

Additionally, install the Adafruit BME680 library and the Adafruit Unified Sensor library.

Installing the BME680 Library

To obtain readings from the BME680 sensor module, employ the Adafruit_BME680 library. Follow these steps to install the library in your Arduino IDE:

Open Arduino IDE and navigate to Sketch > Include Library > Manage Libraries. This action should open the Library Manager.

In the search box, enter “adafruit bme680” and install the library from the results.

    Installing the Adafruit_Sensor Library

    To utilize the BME680 library, install the Adafruit_Sensor library as well. Follow these steps to install it in your Arduino IDE:

    Go to Sketch > Include Library > Manage Libraries and search for “Adafruit Unified Sensor.”

    Scroll down to locate the library and install it.

      After installing the libraries, restart your Arduino IDE for the changes to take effect.

      Code – Reading BME680 Gas, Pressure, Humidity and Temperature

      To retrieve gas, pressure, temperature, and humidity data, we’ll utilize a sketch example provided in the library.

      After installing the BME680 library and the Adafruit_Sensor library, launch the Arduino IDE and navigate to File > Examples > Adafruit BME680 Library > bme680async.

      #include <Wire.h>
      #include <SPI.h>
      #include <Adafruit_Sensor.h>
      #include "Adafruit_BME680.h"
      
      /*#define BME_SCK 18
      #define BME_MISO 19
      #define BME_MOSI 23
      #define BME_CS 5*/
      
      #define SEALEVELPRESSURE_HPA (1013.25)
      
      Adafruit_BME680 bme; // I2C
      //Adafruit_BME680 bme(BME_CS); // hardware SPI
      //Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);
      
      void setup() {
        Serial.begin(115200);
        while (!Serial);
        Serial.println(F("BME680 async test"));
      
        if (!bme.begin()) {
          Serial.println(F("Could not find a valid BME680 sensor, check wiring!"));
          while (1);
        }
      
        // Set up oversampling and filter initialization
        bme.setTemperatureOversampling(BME680_OS_8X);
        bme.setHumidityOversampling(BME680_OS_2X);
        bme.setPressureOversampling(BME680_OS_4X);
        bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
        bme.setGasHeater(320, 150); // 320*C for 150 ms
      }
      
      void loop() {
        // Tell BME680 to begin measurement.
        unsigned long endTime = bme.beginReading();
        if (endTime == 0) {
          Serial.println(F("Failed to begin reading :("));
          return;
        }
        Serial.print(F("Reading started at "));
        Serial.print(millis());
        Serial.print(F(" and will finish at "));
        Serial.println(endTime);
      
        Serial.println(F("You can do other work during BME680 measurement."));
        delay(50); // This represents parallel work.
        // There's no need to delay() until millis() >= endTime: bme.endReading()
        // takes care of that. It's okay for parallel work to take longer than
        // BME680's measurement time.
      
        // Obtain measurement results from BME680. Note that this operation isn't
        // instantaneous even if milli() >= endTime due to I2C/SPI latency.
        if (!bme.endReading()) {
          Serial.println(F("Failed to complete reading :("));
          return;
        }
        Serial.print(F("Reading completed at "));
        Serial.println(millis());
      
        Serial.print(F("Temperature = "));
        Serial.print(bme.temperature);
        Serial.println(F(" *C"));
      
        Serial.print(F("Pressure = "));
        Serial.print(bme.pressure / 100.0);
        Serial.println(F(" hPa"));
      
        Serial.print(F("Humidity = "));
        Serial.print(bme.humidity);
        Serial.println(F(" %"));
      
        Serial.print(F("Gas = "));
        Serial.print(bme.gas_resistance / 1000.0);
        Serial.println(F(" KOhms"));
      
        Serial.print(F("Approx. Altitude = "));
        Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
        Serial.println(F(" m"));
      
        Serial.println();
        delay(2000);
      }
      

      We’ve made necessary adjustments to the sketch to ensure full compatibility with the ESP32.

      Code Explanation

      Keep reading to grasp how the code operates, or skip to the Demonstration section.

      Libraries

      The code begins by importing necessary libraries: the wire library for I2C usage, the SPI library (if SPI is preferred over I2C), and the Adafruit_Sensor and Adafruit_BME680 libraries to interact with the BME680 sensor.

      #include <Wire.h>
      #include <SPI.h>
      #include <Adafruit_Sensor.h>
      #include "Adafruit_BME680.h"

      SPI communication

      Although we opt for I2C communication with the sensor, the code is adaptable for SPI usage. Simply uncomment the following lines defining the SPI pins.

      /*#define BME_SCK 18
      #define BME_MISO 19
      #define BME_MOSI 23
      #define BME_CS 15*/

      Sea level pressure

      A variable named SEALEVELPRESSURE_HPA is established.

      #define SEALEVELPRESSURE_HPA (1013.25)

      This variable holds the sea level pressure in hectopascal (equivalent to millibar). It aids in estimating altitude based on pressure comparison with sea level pressure. While this example utilizes the default value, for precise outcomes, substitute it with the current sea level pressure at your location.

      I2C

      By default, this example employs I2C communication. The subsequent line generates an Adafruit_BME680 object named bme on the default ESP32 I2C pins: GPIO 22 (SCL) and GPIO 21 (SDA).

      Adafruit_BME680 bme; // I2C

      To utilize SPI, comment out the previous line and uncomment the subsequent line.

      //Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI

      setup()

      Within the setup(), initiate serial communication.

      Serial.begin(115200);

      Init BME680 Sensor

      Initialize the BME680 sensor:

      if (!bme.begin()) {
        Serial.println(F("Could not find a valid BME680 sensor, check wiring!"));
        while (1);
      }

      Configure parameters (oversampling, filter, and gas heater) for the sensor.

      // Set up oversampling and filter initialization
      bme.setTemperatureOversampling(BME680_OS_8X);
      bme.setHumidityOversampling(BME680_OS_2X);
      bme.setPressureOversampling(BME680_OS_4X);
      bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
      bme.setGasHeater(320, 150); // 320*C for 150 ms

      To increase the resolution of the raw sensor data, it supports oversampling. We’ll use the default oversampling parameters, but you can change them.

      • setTemperatureOversampling(): set temperature oversampling.
      • setHumidityOversampling(): set humidity oversampling.
      • setPressureOversampling(): set pressure oversampling.

      These methods can accepts one of the following parameters:

      • BME680_OS_NONE: turn off reading;
      • BME680_OS_1X
      • BME680_OS_2X
      • BME680_OS_4X
      • BME680_OS_8X
      • BME680_OS_16X

      The BME680 sensor integrates an internal IIR filter to reduce short-term changes in sensor output values caused by external disturbances. The setIIRFilterSize() method sets the IIR filter. It accepts the filter size as a parameter:

      • BME680_FILTER_SIZE_0 (no filtering)
      • BME680_FILTER_SIZE_1
      • BME680_FILTER_SIZE_3
      • BME680_FILTER_SIZE_7
      • BME680_FILTER_SIZE_15
      • BME680_FILTER_SIZE_31
      • BME680_FILTER_SIZE_63
      • BME680_FILTER_SIZE_127

      The gas sensor integrates a heater. Set the heater profile using the setGasHeater() method that accepts as arguments:

      • the heater temperature (in degrees Centigrade)
      • the time the heater should be on (in milliseconds)

      We’ll use the default settings: 320 ºC for 150 ms.

      loop()

      In the loop(), we’ll get measurements from the BME680 sensor.

      First, tell the sensor to start an asynchronous reading with bme.beginReading(). This returns the time when the reading would be ready.

      // Tell BME680 to begin measurement.
      unsigned long endTime = bme.beginReading();
      if (endTime == 0) {
        Serial.println(F("Failed to begin reading :("));
        return;
      }
      Serial.print(F("Reading started at "));
      Serial.print(millis());
      Serial.print(F(" and will finish at "));
      Serial.println(endTime);

      Then, call the endReading() method to end an asynchronous reading. If the asynchronous reading is still in progress, block until it ends.

      if (!bme.endReading()) {
        Serial.println(F("Failed to complete reading :("));
        return;
      }

      After this, we can get the readings as follows:

      • bme.temperature: returns temperature reading
      • bme.pressure: returns pressure reading
      • bme.humidity: returns humidity reading
      • bme.gas_resistance: returns gas resistance
      Serial.print(F("Temperature = "));
      Serial.print(bme.temperature);
      Serial.println(F(" *C"));
      
      Serial.print(F("Pressure = "));
      Serial.print(bme.pressure / 100.0);
      Serial.println(F(" hPa"));
      
      Serial.print(F("Humidity = "));
      Serial.print(bme.humidity);
      Serial.println(F(" %"));
      
      Serial.print(F("Gas = "));
      Serial.print(bme.gas_resistance / 1000.0);
      Serial.println(F(" KOhms"));
      
      Serial.print(F("Approx. Altitude = "));
      Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
      Serial.println(F(" m"));

      Demonstration

      Upload the code to your ESP32 board. Navigate to Tools > Board and select the ESP32 board you’re using. Then, go to Tools > Port and choose the port your board is connected to. Finally, click the upload button.

      Open the Serial Monitor with a baud rate of 115200, then press the on-board RST button. The sensor measurements will be displayed.

      Code – ESP32 Web Server with BME680

      In this section, we present an example of a web server that you can create with the ESP32 to showcase BME680 readings.

      Installing Libraries – Async Web Server

      To construct the web server, you’ll need to install the following libraries. Click the links below to download them:

      These libraries aren’t accessible for installation via the Arduino Library Manager, so you’ll have to manually copy the library files to the Arduino Installation Libraries folder. Alternatively, within your Arduino IDE, navigate to Sketch > Include Library > Add .zip Library and select the libraries you’ve just downloaded.

      Code

      Then, upload the following code to your board (type your SSID and password).

      #include <Wire.h>
      #include <SPI.h>
      #include <Adafruit_Sensor.h>
      #include "Adafruit_BME680.h"
      #include <WiFi.h>
      #include "ESPAsyncWebServer.h"
      
      // Replace with your network credentials
      const char* ssid = "REPLACE_WITH_YOUR_SSID";
      const char* password = "REPLACE_WITH_YOUR_PASSWORD";
      
      //Uncomment if using SPI
      /*#define BME_SCK 18
      #define BME_MISO 19
      #define BME_MOSI 23
      #define BME_CS 5*/
      
      Adafruit_BME680 bme; // I2C
      //Adafruit_BME680 bme(BME_CS); // hardware SPI
      //Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);
      
      float temperature;
      float humidity;
      float pressure;
      float gasResistance;
      
      AsyncWebServer server(80);
      AsyncEventSource events("/events");
      
      unsigned long lastTime = 0;  
      unsigned long timerDelay = 30000;  // send readings timer
      
      void getBME680Readings(){
        // Tell BME680 to begin measurement.
        unsigned long endTime = bme.beginReading();
        if (endTime == 0) {
          Serial.println(F("Failed to begin reading :("));
          return;
        }
        if (!bme.endReading()) {
          Serial.println(F("Failed to complete reading :("));
          return;
        }
        temperature = bme.temperature;
        pressure = bme.pressure / 100.0;
        humidity = bme.humidity;
        gasResistance = bme.gas_resistance / 1000.0;
      }
      
      String processor(const String& var){
        getBME680Readings();
        //Serial.println(var);
        if(var == "TEMPERATURE"){
          return String(temperature);
        }
        else if(var == "HUMIDITY"){
          return String(humidity);
        }
        else if(var == "PRESSURE"){
          return String(pressure);
        }
       else if(var == "GAS"){
          return String(gasResistance);
        }
      }
      
      const char index_html[] PROGMEM = R"rawliteral(
      <!DOCTYPE HTML><html>
      <head>
        <title>BME680 Web Server</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
        <link rel="icon" href="data:,">
        <style>
          html {font-family: Arial; display: inline-block; text-align: center;}
          p {  font-size: 1.2rem;}
          body {  margin: 0;}
          .topnav { overflow: hidden; background-color: #4B1D3F; color: white; font-size: 1.7rem; }
          .content { padding: 20px; }
          .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
          .cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
          .reading { font-size: 2.8rem; }
          .card.temperature { color: #0e7c7b; }
          .card.humidity { color: #17bebb; }
          .card.pressure { color: #3fca6b; }
          .card.gas { color: #d62246; }
        </style>
      </head>
      <body>
        <div class="topnav">
          <h3>BME680 WEB SERVER</h3>
        </div>
        <div class="content">
          <div class="cards">
            <div class="card temperature">
              <h4><i class="fas fa-thermometer-half"></i> TEMPERATURE</h4><p><span class="reading"><span id="temp">%TEMPERATURE%</span> &deg;C</span></p>
            </div>
            <div class="card humidity">
              <h4><i class="fas fa-tint"></i> HUMIDITY</h4><p><span class="reading"><span id="hum">%HUMIDITY%</span> &percnt;</span></p>
            </div>
            <div class="card pressure">
              <h4><i class="fas fa-angle-double-down"></i> PRESSURE</h4><p><span class="reading"><span id="pres">%PRESSURE%</span> hPa</span></p>
            </div>
            <div class="card gas">
              <h4><i class="fas fa-wind"></i> GAS</h4><p><span class="reading"><span id="gas">%GAS%</span> K&ohm;</span></p>
            </div>
          </div>
        </div>
      <script>
      if (!!window.EventSource) {
       var source = new EventSource('/events');
       
       source.addEventListener('open', function(e) {
        console.log("Events Connected");
       }, false);
       source.addEventListener('error', function(e) {
        if (e.target.readyState != EventSource.OPEN) {
          console.log("Events Disconnected");
        }
       }, false);
       
       source.addEventListener('message', function(e) {
        console.log("message", e.data);
       }, false);
       
       source.addEventListener('temperature', function(e) {
        console.log("temperature", e.data);
        document.getElementById("temp").innerHTML = e.data;
       }, false);
       
       source.addEventListener('humidity', function(e) {
        console.log("humidity", e.data);
        document.getElementById("hum").innerHTML = e.data;
       }, false);
       
       source.addEventListener('pressure', function(e) {
        console.log("pressure", e.data);
        document.getElementById("pres").innerHTML = e.data;
       }, false);
       
       source.addEventListener('gas', function(e) {
        console.log("gas", e.data);
        document.getElementById("gas").innerHTML = e.data;
       }, false);
      }
      </script>
      </body>
      </html>)rawliteral";
      
      void setup() {
        Serial.begin(115200);
      
        // Set the device as a Station and Soft Access Point simultaneously
        WiFi.mode(WIFI_AP_STA);
        
        // Set device as a Wi-Fi Station
        WiFi.begin(ssid, password);
        while (WiFi.status() != WL_CONNECTED) {
          delay(1000);
          Serial.println("Setting as a Wi-Fi Station..");
        }
        Serial.print("Station IP Address: ");
        Serial.println(WiFi.localIP());
        Serial.println();
      
        // Init BME680 sensor
        if (!bme.begin()) {
          Serial.println(F("Could not find a valid BME680 sensor, check wiring!"));
          while (1);
        }
        // Set up oversampling and filter initialization
        bme.setTemperatureOversampling(BME680_OS_8X);
        bme.setHumidityOversampling(BME680_OS_2X);
        bme.setPressureOversampling(BME680_OS_4X);
        bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
        bme.setGasHeater(320, 150); // 320*C for 150 ms
      
        // Handle Web Server
        server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
          request->send_P(200, "text/html", index_html, processor);
        });
      
        // Handle Web Server Events
        events.onConnect([](AsyncEventSourceClient *client){
          if(client->lastId()){
            Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
          }
          // send event with message "hello!", id current millis
          // and set reconnect delay to 1 second
          client->send("hello!", NULL, millis(), 10000);
        });
        server.addHandler(&events);
        server.begin();
      }
      
      void loop() {
        if ((millis() - lastTime) > timerDelay) {
          getBME680Readings();
          Serial.printf("Temperature = %.2f ºC \n", temperature);
          Serial.printf("Humidity = %.2f % \n", humidity);
          Serial.printf("Pressure = %.2f hPa \n", pressure);
          Serial.printf("Gas Resistance = %.2f KOhm \n", gasResistance);
          Serial.println();
      
          // Send Events to the Web Server with the Sensor Readings
          events.send("ping",NULL,millis());
          events.send(String(temperature).c_str(),"temperature",millis());
          events.send(String(humidity).c_str(),"humidity",millis());
          events.send(String(pressure).c_str(),"pressure",millis());
          events.send(String(gasResistance).c_str(),"gas",millis());
          
          lastTime = millis();
        }
      }
      

      Demonstration

      Once uploaded, launch the Serial Monitor at a baud rate of 115200 to obtain the ESP32 IP address.

      Open a web browser and enter the IP address. This grants access to the web server showcasing the latest sensor readings. You can access the web server from your computer, tablet, or smartphone within your local network.

      The readings are automatically refreshed on the web server using Server-Sent Events.

      In this tutorial, we won’t delve into the workings of the web server. For detailed instructions, refer to our dedicated guide on setting up a BME680 web server with the ESP32 board.

      Wrapping Up

      The BME680 sensor module is a versatile 4-in-1 digital sensor integrating gas, pressure, temperature, and humidity sensors. Featuring a MOX sensor, the BME680 detects the presence of most VOC gases, providing a qualitative assessment of the combined VOCs/contaminants in the surrounding air. Consequently, it proves valuable for monitoring indoor air quality.

      We trust you’ve found this introductory guide beneficial. Stay tuned for guides on other popular sensors:

      Leave a Comment