ESP32 - SCD40 CO2 Sensor

This tutorial instructs you how to use ESP32 to read the CO2 concentration from the SCD40 sensor module, and print it in ppm to the Serial Monitor together with a simple air quality word. At the end of the tutorial, we also read the temperature and humidity that the same module gives for free.

ESP32 SCD40 CO2 sensor

A classroom or a small office with the door closed can reach a high CO2 level in less than an hour, and people feel tired without knowing why. With an ESP32 and an SCD40, you can see that number and know when it is time to open a window.

This tutorial shows how to program the ESP32 using the Arduino language (C/C++) via the Arduino IDE. If you’d like to learn how to program the ESP32 with MicroPython, visit this ESP32 MicroPython - SCD40 CO2 Sensor tutorial.

Introduction to SCD40 CO2 Sensor

The SCD40 from Sensirion measures real CO2, not an "eCO2" value guessed from other gases like the MQ135 or CCS811 do. It uses photoacoustic NDIR sensing: a tiny infrared light pulses inside the sensor chamber, CO2 absorbs some of that light and makes a very small pressure "sound", and a microphone measures it. More CO2 means a stronger signal.

SCD40
The supply voltage 2.4V to 5.5V
The interface I2C, address 0x62
The CO2 output range 0 to 40,000 ppm
The CO2 accuracy ±(50 ppm + 5% of reading), for 400 to 2,000 ppm
The reading rate one new reading every 5 seconds (every 30 seconds in low-power mode)
The temperature accuracy ±0.8°C (15°C to 35°C)
The humidity accuracy ±6 %RH (15°C to 35°C, 20 to 65 %RH)
The average current about 15mA at 3.3V

The sensor has automatic self-calibration (ASC) turned on by default. It expects to see fresh, outdoor-level air (about 400 ppm) at least once a week, ideally for an hour or more. So place your project where it can get some fresh air from time to time, for example near a window you open every day. Also, the first readings after power-up may be less accurate while the sensor warms up.

SCD40 Pinout

SCD40 CO2 sensor pinout

The SCD40 module has 4 pins:

  • VCC pin: connect this pin to VCC (3.3V or 5V)
  • GND pin: connect this pin to GND (0V)
  • SDA pin: the I2C data pin, connect this pin to the SDA pin of ESP32
  • SCL pin: the I2C clock pin, connect this pin to the SCL pin of ESP32

Most SCD40 modules already have the I2C pull-up resistors on board, so we do not need to add any.

What Does the CO2 Number Mean?

CO2 level (ppm) Air quality
around 400 Outdoor air
400 to 1000 Good indoor air
1000 to 2000 Stuffy, open a window
above 2000 Poor, headache and drowsiness are possible

Wiring Diagram between SCD40 and ESP32

The SCD40 talks to the ESP32 over I2C. On the ESP32 the default I2C pins are GPIO21 (SDA) and GPIO22 (SCL), so we use them and do not need to set any pins in the code. Power the module from the 3.3V pin of the ESP32. This keeps the I2C lines at 3.3V, which is the safe level for ESP32 pins.

  • How to connect ESP32 and SCD40 CO2 sensor using breadboard
ESP32 SCD40 CO2 sensor wiring diagram

This image is created using Fritzing. Click to enlarge image

How to connect ESP32 and SCD40 CO2 sensor
SCD40 Module ESP32
VCC → 3.3V
GND → GND
SDA → GPIO21
SCL → GPIO22

If you're unfamiliar with how to supply power to the ESP32 and other components, you can find guidance in the following tutorial: The best way to Power ESP32 and sensors/displays.

How To Program for SCD40 CO2 Sensor

  • Include the libraries:
#include <Arduino.h> #include <SensirionI2cScd4x.h> #include <Wire.h>
  • Declare a sensor object:
SensirionI2cScd4x scd4x;
  • Start the I2C bus on the default pins and tell the library the sensor address:
Wire.begin(); scd4x.begin(Wire, SCD40_I2C_ADDR_62);
  • Make a clean start, then start the periodic measurement (one reading every 5 seconds):
scd4x.wakeUp(); scd4x.stopPeriodicMeasurement(); scd4x.reinit(); scd4x.startPeriodicMeasurement();
  • Ask whether a new reading is ready, then read it:
bool dataReady = false; scd4x.getDataReadyStatus(dataReady); if (dataReady) { scd4x.readMeasurement(co2, temperature, humidity); }

Every library function returns an error code. When it is not 0, the errorToString() function turns it into a text you can print. The full code below does this.

ESP32 Code - SCD40 CO2

/* * This ESP32 code is created by esp32io.com * * This ESP32 code is released in the public domain * * For more detail (instruction and wiring diagram), visit https://esp32io.com/tutorials/esp32-scd40-co2-sensor */ // ESP32 reads the CO2 level (ppm) from the SCD40 sensor and prints it to the Serial Monitor // Wiring: SCD40 SDA -> GPIO21, SCD40 SCL -> GPIO22 (the default I2C pins of the ESP32) #include <Arduino.h> #include <SensirionI2cScd4x.h> #include <Wire.h> SensirionI2cScd4x scd4x; // create the SCD4x sensor object char errorMessage[64]; // buffer for error text int16_t error; // error code returned by the library (0 = no error) void setup() { Serial.begin(9600); delay(500); Wire.begin(); // use the default I2C pins: SDA = GPIO21, SCL = GPIO22 scd4x.begin(Wire, SCD40_I2C_ADDR_62); // clean start: wake the sensor, stop any running measurement, reload settings scd4x.wakeUp(); // may return an error if the sensor is already awake. It is OK to ignore it scd4x.stopPeriodicMeasurement(); scd4x.reinit(); // start a new measurement every 5 seconds error = scd4x.startPeriodicMeasurement(); if (error != 0) { errorToString(error, errorMessage, sizeof errorMessage); Serial.print("Cannot start measurement: "); Serial.println(errorMessage); return; } Serial.println("SCD40 started. Waiting for the first reading..."); } void loop() { delay(1000); // check the sensor once per second // is a new measurement ready? bool dataReady = false; error = scd4x.getDataReadyStatus(dataReady); if (error != 0) { errorToString(error, errorMessage, sizeof errorMessage); Serial.print("Data ready error: "); Serial.println(errorMessage); return; } if (!dataReady) { return; // not yet, try again next time } // read the measurement. The SCD40 always sends CO2, temperature and humidity together uint16_t co2 = 0; float temperature = 0.0f; float humidity = 0.0f; error = scd4x.readMeasurement(co2, temperature, humidity); if (error != 0) { errorToString(error, errorMessage, sizeof errorMessage); Serial.print("Read error: "); Serial.println(errorMessage); return; } // turn the ppm value into a simple air quality word String airQuality; if (co2 < 1000) { airQuality = "Good"; } else if (co2 < 2000) { airQuality = "Moderate - open a window"; } else { airQuality = "Poor"; } Serial.print("CO2: "); Serial.print(co2); Serial.print(" ppm -> "); Serial.println(airQuality); }

Quick Instructions

  • If this is the first time you use ESP32, see how to setup environment for ESP32 on Arduino IDE.
  • Do the wiring as above image.
  • Connect the ESP32 board to your PC via a USB Type-C cable.
  • Open Arduino IDE on your PC.
  • Select the right ESP32 board (e.g. ESP32 Dev Module) and COM port.
  • Click to the Libraries icon on the left bar of the Arduino IDE.
  • Type "SCD4x" on the search box, then look for the Sensirion I2C SCD4x library by Sensirion.
  • Install the library by clicking on Install button.
  • Search for Sensirion I2C SCD4x created by Sensirion and click the Install button.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32 Dev Module
Library Manager
Type:
All
Topic:
All
Sensirion I2C SCD4x by Sensirion
Enables you to use the SCD4X sensor family via I2C. More info
1.1.0
INSTALL
Newbiely.ino
···
1 void setup() {
Output
Serial Monitor
Ln 1, Col 1
ESP32 Dev Module on COM15
1
  • A windows appears to ask you to install dependencies for the library (Sensirion Core).
  • Install all dependencies for the library by clicking on Install All button.
  • Copy the above code and open with Arduino IDE.
  • Compile and upload code to ESP32 board by clicking Upload button on Arduino IDE.
  • Open the Serial Monitor. The first value comes after about 5 seconds.
  • Breathe gently toward the sensor for a few seconds and watch the CO2 value jump up, then fall back.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32 Dev Module' on 'COM15')
New Line
9600 baud
SCD40 started. Waiting for the first reading... CO2: 538 ppm -> Good CO2: 541 ppm -> Good CO2: 547 ppm -> Good CO2: 1186 ppm -> Moderate - open a window CO2: 1642 ppm -> Moderate - open a window CO2: 1203 ppm -> Moderate - open a window CO2: 874 ppm -> Good CO2: 652 ppm -> Good
Ln 11, Col 1
ESP32 Dev Module on COM15
2

The value is only updated once every 5 seconds, so the reaction to your breath is not instant. This is normal for the SCD40.

※ NOTE THAT:

If you see an error message instead of a CO2 value, check the SDA and SCL wires first (they are easy to swap). Then make sure the module gets power from the 3.3V pin and the GND wire is connected.

Reading Temperature and Humidity from the SCD40

The SCD40 has a small temperature and humidity sensor inside. It uses these values to correct its own CO2 reading, and it also sends them to the ESP32 in every measurement. In fact, the readMeasurement() function in the code above already fills the temperature and humidity variables. We just did not print them. The code below prints all three values on one line.

/* * This ESP32 code is created by esp32io.com * * This ESP32 code is released in the public domain * * For more detail (instruction and wiring diagram), visit https://esp32io.com/tutorials/esp32-scd40-co2-sensor */ // ESP32 reads CO2, temperature and humidity from the SCD40 sensor // Wiring: SCD40 SDA -> GPIO21, SCD40 SCL -> GPIO22 (the default I2C pins of the ESP32) #include <Arduino.h> #include <SensirionI2cScd4x.h> #include <Wire.h> SensirionI2cScd4x scd4x; // create the SCD4x sensor object char errorMessage[64]; // buffer for error text int16_t error; // error code returned by the library (0 = no error) void setup() { Serial.begin(9600); delay(500); Wire.begin(); // default I2C pins: SDA = GPIO21, SCL = GPIO22 scd4x.begin(Wire, SCD40_I2C_ADDR_62); // clean start scd4x.wakeUp(); // the result is ignored on purpose (sensor may already be awake) scd4x.stopPeriodicMeasurement(); scd4x.reinit(); error = scd4x.startPeriodicMeasurement(); // one new reading every 5 seconds if (error != 0) { errorToString(error, errorMessage, sizeof errorMessage); Serial.print("Cannot start measurement: "); Serial.println(errorMessage); } } void loop() { delay(1000); bool dataReady = false; error = scd4x.getDataReadyStatus(dataReady); if (error != 0 || !dataReady) { return; // no new data yet } uint16_t co2 = 0; float temperature = 0.0f; // in °C float humidity = 0.0f; // in %RH error = scd4x.readMeasurement(co2, temperature, humidity); if (error != 0) { errorToString(error, errorMessage, sizeof errorMessage); Serial.print("Read error: "); Serial.println(errorMessage); return; } // print all three values on one line Serial.print("CO2: "); Serial.print(co2); Serial.print(" ppm | Temperature: "); Serial.print(temperature, 1); Serial.print("°C | Humidity: "); Serial.print(humidity, 1); Serial.println("%"); }

Quick Instructions

  • Copy the above code and open with Arduino IDE.
  • Click Upload button on Arduino IDE to upload code to ESP32.
  • See the result on Serial Monitor:
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32 Dev Module' on 'COM15')
New Line
9600 baud
CO2: 612 ppm | Temperature: 24.3°C | Humidity: 47.8% CO2: 615 ppm | Temperature: 24.3°C | Humidity: 47.6% CO2: 621 ppm | Temperature: 24.4°C | Humidity: 47.9% CO2: 619 ppm | Temperature: 24.4°C | Humidity: 48.1%
Ln 11, Col 1
ESP32 Dev Module on COM15
2

The SCD40 warms itself a little while it works, so the temperature can read slightly higher than the room. For a precise room temperature, a separate sensor such as the DHT22 is a better choice.

Video Tutorial

Making video is a time-consuming work. If the video tutorial is necessary for your learning, please let us know by subscribing to our YouTube channel , If the demand for video is high, we will make the video tutorial.

※ OUR MESSAGES