ESP32 S3 - SCD40 CO2 Sensor

Stale room air is easy to miss until you feel tired, and the SCD40 turns that feeling into a real number: the CO2 level in parts per million (ppm). In this guide, the ESP32 S3 talks to the SCD40 over I2C, prints the CO2 level every few seconds, and labels the air as Good, Moderate or Poor.

What you'll build in this tutorial:

  1. What the SCD40 measures and why its CO2 value is a real measurement, not an estimate
  2. How to connect the SCD40 module to the I2C pins of the ESP32 S3
  3. A CO2 monitor sketch with a simple air-quality label
  4. A second sketch that adds the built-in temperature and humidity readings
ESP32 S3 - SCD40 CO2 Sensor

Hardware Preparation

1×ESP32 S3 WROOM N16R8
1×Alternatively, ESP32 S3 SuperMini Dev Module
1×Alternatively, ESP32 S3 Uno-form Board
1×Alternatively, ESP32 S3 Camera Board
1×USB Cable Type-A to Type-C (for USB-A PC)
1×USB Cable Type-C to Type-C (for USB-C PC)
1×SCD40 CO2 Sensor Module
1×Breadboard
1×Jumper Wires
1×Optionally, DC Power Jack
1×Recommended: Screw Terminal Expansion Board for ESP32 S3
1×Recommended: Breakout Expansion Board for ESP32 S3
1×Recommended: Power Splitter for ESP32 S3

Or you can buy the following kits:

1×DIYables ESP32 S3 Starter Kit (ESP32 S3 included)
1×DIYables Sensor Kit (18 sensors/displays)
Disclosure: Some of the links provided in this section are Amazon affiliate links. We may receive a commission for any purchases made through these links at no additional cost to you.
Additionally, some of these links are for products from our own brand, DIYables .

Overview of SCD40 CO2 Sensor

The SCD40 is a small CO2 sensor from Sensirion (part of the SCD4x family). Many cheap gas sensors, such as the MQ135 or CCS811, only guess an "equivalent CO2" from other gases. The SCD40 measures true CO2 with photoacoustic NDIR sensing: a tiny infrared light pulses inside its chamber, CO2 absorbs some of that light and makes a very small pressure "sound", and a microphone picks it up — more CO2 gives a stronger signal. It also has a temperature and humidity sensor on board, which it uses to correct the CO2 value and which you can read as well.

Key Specifications

The module runs from 2.4V to 5.5V, so the 3.3V pin of the ESP32 S3 powers it directly, and it talks over I2C at the fixed address 0x62. It can report 0 to 40,000 ppm, and inside the 400 to 2,000 ppm range its accuracy is ±(50 ppm + 5% of the reading). In normal periodic mode a fresh value is ready every 5 seconds (a low-power mode gives one every 30 seconds), and it draws about 15mA on average at 3.3V.

Feature Value
Supply voltage 2.4V to 5.5V
Interface / address I2C, 0x62
CO2 output range 0 to 40,000 ppm
CO2 accuracy (400 to 2,000 ppm) ±(50 ppm + 5% of reading)
Update rate every 5 s (low-power mode every 30 s)
Temperature accuracy ±0.8°C (15 to 35°C)
Humidity accuracy ±6 %RH (15 to 35°C, 20 to 65 %RH)
Average current about 15mA at 3.3V

Automatic self-calibration (ASC) is turned on by default. It assumes the sensor breathes fresh, outdoor-level air (about 400 ppm) at least once a week, ideally for an hour or more, so place it where a window gets opened now and then. Also, the first few readings after power-up can be less accurate while the sensor warms up.

Use this table to understand the numbers you will see:

CO2 level What it means
About 400 ppm Fresh outdoor air
400 to 1,000 ppm Good indoor air
1,000 to 2,000 ppm Stuffy, open a window
Above 2,000 ppm Poor air, headache or drowsiness possible

SCD40 Pinout

The SCD40 breakout module has just four pins. Most modules already include the I2C pull-up resistors, so no extra parts are needed.

SCD40 CO2 sensor pinout
  1. VCC (VIN) — power input, 2.4V to 5.5V; use the 3.3V pin of the ESP32 S3
  2. GND — ground (0V)
  3. SCL — I2C clock line
  4. SDA — I2C data line

Wiring Diagram between SCD40 and ESP32 S3

The ESP32 S3 uses GPIO8 as its I2C data line (SDA) and GPIO9 as its clock line (SCL), the same pins used by the other I2C tutorials on this site. Wire the four pins of the SCD40 module straight to the board as shown below.

Safety Notes

Power the SCD40 from the 3.3V pin so its I2C signals match the 3.3V logic of the ESP32 S3. Double-check that SDA and SCL are not swapped, because swapped lines don't damage anything but the sensor will simply not answer. Don't block the opening on top of the sensor, and keep it away from your breath or a heater while testing, or the reading will jump.

The wiring diagram between ESP32 S3 SCD40 CO2 sensor

This image is created using Fritzing. Click to enlarge image

SCD40 Module Pin ESP32 S3 Pin
VCC 3.3V
GND GND
SDA GPIO8 (I2C Data)
SCL GPIO9 (I2C Clock)

ESP32 S3 Code - SCD40 CO2 Sensor

The sketch starts I2C on GPIO8 and GPIO9, wakes the SCD40, stops any measurement left running from before, and then starts periodic measurement. Once per second it asks the sensor whether new data is ready, and when it is, it reads the result and prints the CO2 value with a word from the table above: Good below 1,000 ppm, Moderate up to 2,000 ppm, and Poor above that. If the sensor reports an error, the sketch prints a readable error message instead.

/* * This ESP32 S3 code was developed by newbiely.com * * This ESP32 S3 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-s3/esp32-s3-scd40-co2-sensor */ #include <Arduino.h> #include <SensirionI2cScd4x.h> #include <Wire.h> #define SDA_PIN 8 // ESP32 S3 GPIO8 connected to the SDA pin of the SCD40 module #define SCL_PIN 9 // ESP32 S3 GPIO9 connected to the SCL pin of the SCD40 module SensirionI2cScd4x scd4x; // the SCD40 sensor object char errorMessage[64]; int16_t error; // Turn a CO2 value (ppm) into a simple word const char* airQuality(uint16_t co2) { if (co2 < 1000) return "Good"; if (co2 < 2000) return "Moderate - open a window"; return "Poor"; } void setup() { Serial.begin(115200); delay(500); Wire.begin(SDA_PIN, SCL_PIN); // start I2C on GPIO8 (SDA) and GPIO9 (SCL) scd4x.begin(Wire, SCD40_I2C_ADDR_62); // the SCD40 uses I2C address 0x62 // Clean start: wake the sensor, stop any old measurement, reload settings scd4x.wakeUp(); // may return an error if the sensor is already awake - that is OK scd4x.stopPeriodicMeasurement(); scd4x.reinit(); // Start periodic measurement: a new result every 5 seconds error = scd4x.startPeriodicMeasurement(); if (error) { errorToString(error, errorMessage, sizeof errorMessage); Serial.print("Cannot start measurement: "); Serial.println(errorMessage); return; } Serial.println("SCD40 started. First reading in about 5 seconds..."); } void loop() { delay(1000); // Ask the sensor if a new measurement is ready bool dataReady = false; error = scd4x.getDataReadyStatus(dataReady); if (error) { errorToString(error, errorMessage, sizeof errorMessage); Serial.print("Data ready check failed: "); Serial.println(errorMessage); return; } if (!dataReady) { return; // not ready yet, check again in 1 second } // Read the measurement. The sensor always sends all three values. uint16_t co2 = 0; float temperature = 0.0f; float humidity = 0.0f; error = scd4x.readMeasurement(co2, temperature, humidity); if (error) { errorToString(error, errorMessage, sizeof errorMessage); Serial.print("Read failed: "); Serial.println(errorMessage); return; } // In this example we only print CO2 Serial.print("CO2: "); Serial.print(co2); Serial.print(" ppm -> "); Serial.println(airQuality(co2)); }

Detailed Instructions

  1. New to ESP32 S3? Complete our Getting Started with ESP32 S3 guide first.
  2. Wire the sensor: Connect the SCD40 module to the ESP32 S3 as shown in the wiring diagram.
  3. Connect the board: Plug the ESP32 S3 into your computer with the USB Type-C cable.
  4. Choose board and port: In Arduino IDE, select the ESP32 S3 board and the right COM port.
  5. Open Library Manager: Click the Library Manager icon on the left side of Arduino IDE.
  6. Find the library: Type SCD4x in the search box and find Sensirion I2C SCD4x by Sensirion.
  • Search for Sensirion I2C SCD4x created by Sensirion and click the Install button.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32S3 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
ESP32S3 Dev Module on COM15
1
  1. Install dependencies: When asked, click Install All so the Sensirion Core library is installed too.
  2. Upload the code: Paste the code above into Arduino IDE and click Upload.
  3. Open the Serial Monitor: Set it to 115200 baud and wait about 5 seconds for the first value.
  4. Test it: Breathe gently toward the sensor from a short distance and watch the CO2 value climb, then fall again.
  5. Pro Tip: Let the sensor run for a few minutes before you trust the numbers; the first readings after power-up are less accurate.

Serial Monitor Output

∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32S3 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32S3 Dev Module' on 'COM15')
New Line
9600 baud
SCD40 started. First reading in about 5 seconds... CO2: 537 ppm -> Good CO2: 541 ppm -> Good CO2: 549 ppm -> Good CO2: 1186 ppm -> Moderate - open a window CO2: 1342 ppm -> Moderate - open a window CO2: 958 ppm -> Good CO2: 712 ppm -> Good
Ln 11, Col 1
ESP32S3 Dev Module on COM15
2

In the sample above, the jump to more than 1,100 ppm happened after breathing near the sensor; the value drops back slowly as fresh air reaches the chamber.

Reading Temperature and Humidity from the SCD40

Every SCD40 measurement already contains three values: CO2, temperature and relative humidity. The first sketch reads all three but only prints CO2. The sketch below prints all of them on one line, so your ESP32 S3 becomes a small three-in-one indoor climate monitor without any extra sensor. Keep in mind the accuracy from the specs: about ±0.8°C and ±6 %RH. The sensor also warms itself a little while running, so its temperature can read slightly higher than the room.

/* * This ESP32 S3 code was developed by newbiely.com * * This ESP32 S3 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-s3/esp32-s3-scd40-co2-sensor */ #include <Arduino.h> #include <SensirionI2cScd4x.h> #include <Wire.h> #define SDA_PIN 8 // ESP32 S3 GPIO8 connected to the SDA pin of the SCD40 module #define SCL_PIN 9 // ESP32 S3 GPIO9 connected to the SCL pin of the SCD40 module SensirionI2cScd4x scd4x; // the SCD40 sensor object char errorMessage[64]; int16_t error; void setup() { Serial.begin(115200); delay(500); Wire.begin(SDA_PIN, SCL_PIN); // start I2C on GPIO8 (SDA) and GPIO9 (SCL) scd4x.begin(Wire, SCD40_I2C_ADDR_62); // the SCD40 uses I2C address 0x62 // Clean start: wake the sensor, stop any old measurement, reload settings scd4x.wakeUp(); // may return an error if the sensor is already awake - that is OK scd4x.stopPeriodicMeasurement(); scd4x.reinit(); // Start periodic measurement: a new result every 5 seconds error = scd4x.startPeriodicMeasurement(); if (error) { errorToString(error, errorMessage, sizeof errorMessage); Serial.print("Cannot start measurement: "); Serial.println(errorMessage); return; } Serial.println("SCD40 started. Waiting for data..."); } void loop() { delay(1000); // Check if a new measurement is ready bool dataReady = false; error = scd4x.getDataReadyStatus(dataReady); if (error || !dataReady) { return; // try again in 1 second } // Read CO2 (ppm), temperature (°C) and relative humidity (%) uint16_t co2 = 0; float temperature = 0.0f; float humidity = 0.0f; error = scd4x.readMeasurement(co2, temperature, humidity); if (error) { errorToString(error, errorMessage, sizeof errorMessage); Serial.print("Read failed: "); 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(" %"); }

Detailed Instructions

  1. Keep the same wiring and library from the first sketch.
  2. Paste this second code into Arduino IDE and click Upload.
  3. Open the Serial Monitor at 115200 baud.
  4. Pro Tip: Compare the temperature with a DHT22 or a room thermometer; if it reads a bit high, mount the module where air can flow around it.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32S3 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32S3 Dev Module' on 'COM15')
New Line
9600 baud
SCD40 started. Waiting for data... CO2: 603 ppm | Temperature: 23.6 °C | Humidity: 47.2 % CO2: 608 ppm | Temperature: 23.6 °C | Humidity: 47.0 % CO2: 611 ppm | Temperature: 23.7 °C | Humidity: 46.9 % CO2: 617 ppm | Temperature: 23.7 °C | Humidity: 46.8 %
Ln 11, Col 1
ESP32S3 Dev Module on COM15
2

Applications and Project Ideas

Because the SCD40 gives a true CO2 value plus temperature and humidity, the ESP32 S3 can turn it into many practical air-quality tools.

  1. Classroom fresh-air alert: Light a red LED when CO2 passes 1,000 ppm so the teacher knows to open a window.
  2. Bedroom sleep monitor: Log CO2 overnight and check if the room needs better ventilation.
  3. Smart ventilation fan: Switch a fan through a relay when CO2 gets high and turn it off when the air is fresh.
  4. Home Assistant sensor: Send CO2, temperature and humidity to a dashboard over WiFi with MQTT.
  5. Mushroom or plant grow box: Keep an eye on CO2 and humidity inside a closed growing tent.
  6. Office meeting room display: Show the live CO2 value on an OLED or LCD on the meeting room door.

Video Tutorial

Watch the step-by-step video walkthrough for this ESP32 S3 project below.

Challenge Yourself

Once the readings look stable, try these tasks to build on the project.

  1. Beginner: Add a green, yellow and red LED that follow the Good, Moderate and Poor labels.
  2. Beginner: Add a buzzer that beeps once when CO2 rises above 2,000 ppm.
  3. Intermediate: Show CO2, temperature and humidity on an LCD I2C display that shares GPIO8 and GPIO9 with the SCD40.
  4. Intermediate: Print the average CO2 of the last 12 readings (one minute) to smooth out quick jumps.
  5. Advanced: Host a web page on the ESP32 S3 that draws a live CO2 chart in the browser.
  6. Advanced: Send a Telegram message from the ESP32 S3 when the room has been above 1,500 ppm for 10 minutes.

※ OUR MESSAGES

  • As freelancers, We are AVAILABLE for HIRE. See how to outsource your project to us
  • Please feel free to share the link of this tutorial. However, Please do not use our content on any other websites. We invested a lot of effort and time to create the content, please respect our work!