Measurement of eCO2 and VOC (Volatile Organic Compounds) with CCS811 | そう備忘録

Measurement of eCO2 and VOC (Volatile Organic Compounds) with CCS811


This article describes the measurement of eCO2 (equivalent carbon dioxide) and TVOC (total volatile organic compounds) with ESP32 and CCS811 module and display on OLED display.

What is eCO2?

The CO2 (carbon dioxide) that can be measured by CCS811 is called eCO2 (equivalent CO2), which is not exactly the same as CO2.

The following description can be found on this site.

“It’s important to recognize that the CCS811 cannot measure CO2 and that the “equivalent CO2″ being reported by the CCS811 has nothing to do with actual CO2 present in the area.”

When the cap of a magic pen was opened and placed close to the sensor, the CO2 concentration of a commercial CO2 measuring instrument (NDIR-based product costing about $180) did not change, but the eCO2 value of the CCS811 increased.

When the CCS811’s eCO2 value increased in response to volatile gases, it seemed that the CCS811’s eCO2 value also increased in response to volatile gases, although it could be used as a CO2 measurement if there were “no gases around.

However, from the viewpoint of “whether indoor ventilation is necessary or not,” there is no problem with eCO2, and in fact, eCO2 may be easier to use because it reacts to volatile gases in addition to carbon dioxide.

If you want to measure purely CO2 concentration, I think MH-Z19C (NDIR method), which I wrote about in a previous article, is better.

What is TVOC?

TVOC (Total Volatile Organic Compounds) is a generic term for organic compounds that become gas in the atmosphere.

TVOC is effective as an indicator for detecting “air pollution” because its value increased in response to gases such as paint, ink, and gasoline.


The main specifications are as follows

  • eCO2: 400 – 8,192 ppm (parts per million)
  • TVOC: 0 – 1187 ppb (parts per billion)
  • I2C communication: Address 0x5A or 0x5B
  • Voltage: 3.3V
  • Conditioning time: Valid values are output at least 20 minutes after startup


Pins, from top.

  • RST: Used for hardware reset
  • INT: Output when measurement ends or threshold is exceeded (not used this time)
  • WAK: Used to switch between startup and hibernation to save power. High:hibernation, Low:startup
  • SCL: SCL
  • SDA: SDA
  • 3.3V: Power supplied by ESP32
  • GND: GND

There is a terminal for connecting an NTC thermistor at the bottom center.

CCS811 surface

The image of the back side is as follows.

The pin slightly below the center is the I2C address control pin, which is 0x5B (default) when open, and 0x5A when shorted, allowing the I2C address to be changed.

The pull-up resistor in the lower center is normally left short-circuited.

When opened, the pull-up resistor is disabled.

CCS811 back side

NTC Thermistor

There is an optional NTC(Negative Thermal Coefficient) thermistor that can be added by soldering to compensate for gas concentration based on ambient temperature, but it seems that the temperature guarantee is no longer supported (the connector is still there).

It said that an external sensor such as BME280 is recommended instead of NTC .

Baseline application time

There is a function that automatically corrects resistance values in response to variations in sensor manufacturing and changes over time.

Since the correction is made in 24 hours, it is better to assume that the correct value is output after 24 hours or more, in addition to the aforementioned conditioning time (20 minutes).

Other Parts

Other main parts prepared are as follows.

ESP32 module

The pin layout is the same as ESP32 DEVKIT V1 (30 pins).

ESP-WROOM-32 module

OLED Module

The 0.96-inch OLED (Organic Light Emitting Diode) display was used.

OLED Display surface
OLED Display back side

The specifications are as follows

  • Size: 0.96 inches
  • Voltage: 3.3V to 5V
  • Resolution: 128 × 64
  • I2C connection: 0x3C (default) or 0x3D
  • SSD1306 Compatible

Library Installation

The development environment was Arduino IDE (Ver 1.8.19).

Install the necessary libraries in advance.


Download the library from sparkfun’s Github and add the library from the Arduino IDE by going to Sketch->Include Library->Add .ZIP Library.


Install the SSD1306 library for OLED displays.

From the Arduino IDE, go to Tools > Manage Libraries and search for “SSD1306” in the search field to install Adafruit SSD1306.

Adafruit SSD1306 Library

Wiring Diagram

The wiring is as follows.

INT is not connected this time, and RST and WAK are used. However, if you simply want to acquire and display values without considering power saving, it is not necessary to connect them.

It is possible to acquire values only by connecting GND, 3.3V, SDA, and SCL.

CCS811 Wiring Diagram


The sketch measures eCO2 and TVOC at approximately 60-second intervals and displays them on the display.


 * Created on Sun Feb 27 21:46:24 2022
 * CCS811 Co2 Sensor & OLED Display
 * @author: Souichirou Kikuchi
#include <Wire.h> // I2C
#include <SparkFunCCS811.h> // CCS811
#include <Adafruit_SSD1306.h> // OLED Display

#define CCS811_ADDRESS 0x5B // CCS811 I2C Address
#define SCREEN_ADDRESS 0x3C // OLED Display Address

// I2C Pin
constexpr short int SDA_PIN = 21;
constexpr short int SCL_PIN = 22;

constexpr short int RESET_PIN = 5;
constexpr short int WAKE_UP_PIN = 4;

// OLED Display
constexpr short int SCREEN_WIDTH = 128; // OLED display width, in pixels
constexpr short int SCREEN_HEIGHT = 64; // OLED display height, in pixels
constexpr short int OLED_RESET = 4; // Reset pin # (or -1 if sharing Arduino reset pin)

CCS811 eco2_sensor(CCS811_ADDRESS); // eCo2 Sensor 
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // OLEDDisplay

void ccs811_hw_reset() { // CCS811 Hardware Reset
    pinMode(RESET_PIN, OUTPUT);
    digitalWrite(RESET_PIN, LOW);
    digitalWrite(RESET_PIN, HIGH);

void ccs811_wake() { // Wake up
    digitalWrite(WAKE_UP_PIN, LOW);

void ccs811_sleep() { // Lower power consumption
    digitalWrite(WAKE_UP_PIN, HIGH);

void setup()
    int cnt;
    const short int MAX_RETRY = 10;

    Serial.println("Program Start");

    pinMode(WAKE_UP_PIN, OUTPUT);

    // I2C
    Wire.begin(SDA_PIN, SCL_PIN);

    // CCS811
    cnt = 0;
    while ((eco2_sensor.begin() == false) and (cnt < MAX_RETRY)) {
        Serial.print("CCS811 initialize attempt");
    if (cnt >= MAX_RETRY){
        Serial.println("CCS811 initialize Error.");
    } else {
        Serial.println("CCS811 Initialized.");

    // OLED Display Initialize
    if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
        Serial.println(F("SSD1306 allocation failed"));
    // Display settings
    display.setTextSize(1); // size 1~5

void loop()
    short int co2_ppm;
    short int voc_ppb;
    int co2_ttl;
    int voc_ttl;
    std::vector<int> co2List;
    std::vector<int> vocList;
    constexpr short int DATA_COUNT = 5; // Number of data acquisition

    // Maximum and minimum values are truncated
    if (eco2_sensor.dataAvailable()) {
        for (int i = 0; i < DATA_COUNT; i++) {
    } else {
        Serial.println("CCS811 not Available");

    std::sort(co2List.begin(), co2List.end());
    std::sort(vocList.begin(), vocList.end());
    co2List.pop_back(); // Delete trailing (maximum) data 
    co2List.erase(co2List.begin()); // Delete the first (minimum) data

    // average
    co2_ttl = 0;
    voc_ttl = 0;
    // for (int i = 0; i < DATA_COUNT-2; i++) {
    for (int i = 0; i < co2List.size(); i++) {

        co2_ttl += co2List[i];
        voc_ttl += vocList[i];
    co2_ppm = co2_ttl / co2List.size();
    voc_ppb = voc_ttl / vocList.size();

    display.clearDisplay(); // Buffer Clear
    display.setCursor(0, 0); // X Y
    display.println("eCO2:" + String(co2_ppm) + " ppm");
    display.setCursor(0, 30); // X Y
    display.println("VOC:" + String(voc_ppb) + " ppb");

    delay(60000); // 60秒

supplementary explanation


Function to hardware reset the CCS811.

Low and High are sent to the reset pin to reset.


Function to start up CCS811.

Low is sent to the Wake up pin.


Function to put the CCS811 into power-saving mode.

High is sent to the Wake up pin.


Initial Processing.

Hardware reset and initialization of the CCS811 and initialization of the OLED display.


CCS811, however, when I tried to obtain the value continuously every second, I found that there was a slight difference each time.

In addition, it was confirmed that a large value was sometimes suddenly detected, and one second later, the value returned to the original value or thereabouts.

Example: 447 → 449 → 680 → 445 → 450 …

For this reason, I consider it problematic to assume that a value is positive after only one measurement, so I obtain the value several times in succession, truncate the maximum and minimum values, and take the average of all values except the truncated ones.

In the program, the maximum and minimum values are truncated after five consecutive measurements, and the average of the three intermediate data is taken.


The execution is shown below.

eCo2 concentration and TVOC are measured at 60 second intervals and displayed on the display.


This concludes this article.


I hope this article will be useful to someone somewhere.

souichirou kikuchi

I'm Japanese. A reminder to remember what I've done. I'm blogging in the hope that it will be helpful to others who want to do similar things. I mainly write blogs about LEGO, AWS (Amazon Web Services), WordPress, Deep Learning and Raspberry Pi. At work, I'm working on installing collaborative robots and IoT in factories. I passed the JDLA (Japan Deep Learning Association) Deep Learning for GENERAL in July 2019. If you have any questions, please leave them in the comments at the bottom of the article.


Name, Email, and Website are optional.
and, your Email address will not be published.