SCD41 CO2 Temperature and Humidity Sensor

The SCD41 is a miniature CO2 sensor from Sensirion that measures carbon dioxide concentration using the photoacoustic NDIR sensing principle, with integrated temperature and humidity sensing for on-chip compensation. It communicates over I2C, covers a specified CO2 range of 400 to 5000 ppm (0 to 40000 ppm output), and ships factory-calibrated.

SCD41 CO2 Temperature and Humidity Sensor image
SCD41 · I2C
I2C
Interface
5pins
Connections
2.4-5.5V
Supply
-10 to 60 C
Operating temp
15mA
Power
About 50 USD
Typical price
On this page

SCD41 pinout

5 pins · I2C

The SCD41 breakout brings out five pins - power in, a regulated 3.3V output, ground, and the I2C clock and data lines.

PinTypeDescriptionNotes
VINPowerPower input; supply at the microcontroller logic level (3.3V or 5V)
3VoPower3.3V output from the onboard regulator (at least 100mA)
GNDGroundCommon ground for power and logic
SCLI2CI2C clock line; 10K pull-up onboard
SDAI2CI2C data line; 10K pull-up onboard
  • I2C address: 0x62 (default)

  • Supply voltage 2.4 to 5.5 V

  • Onboard 10K pull-up resistors on SDA and SCL

  • Factory calibrated - no user calibration required

  • 3Vo is a regulated 3.3V output rated for at least 100mA

Wiring the SCD41 to ESP32

4 connections · all required

To wire the SCD41 to an ESP32 over I2C, connect VIN to 3V3, GND to GND, SCL to GPIO22, and SDA to GPIO21. The onboard 10K pull-ups mean no extra resistors are needed.

Wiring diagram coming soon
The pin-to-pin table covers every connection.
SCD41 pinESP32 pinPurpose
VIN3V3Power supply (3.3V logic level)
GNDGNDCommon ground
SCLGPIO22I2C clock (SCL)
SDAGPIO21I2C data (SDA)
  • I2C address: 0x62 (default)

  • Onboard 10K pull-up resistors on SDA and SCL

  • VIN accepts the microcontroller logic level, either 3.3V or 5V

  • Power budget: averages 15 mA but peaks at up to 205 mA

  • GPIO21 (SDA) and GPIO22 (SCL) are the ESP32 default I2C pins

SCD41 code examples

5 platforms
Platform:

SCD41 Arduino example

Copy
// Requires library: "SparkFun SCD4x Arduino Library"
#include <Wire.h>
#include "SparkFun_SCD4x_Arduino_Library.h"

SCD4x scd41;

void setup() {
  Serial.begin(115200);
  Wire.begin();  // ESP32 default I2C: SDA = GPIO21, SCL = GPIO22

  // begin() also starts periodic measurement on the SCD41
  if (scd41.begin() == false) {
    Serial.println("SCD41 not detected. Check wiring. Freezing...");
    while (1)
      ;
  }
}

void loop() {
  // The SCD41 produces a new sample about every 5 seconds
  if (scd41.readMeasurement()) {
    Serial.print("CO2: ");
    Serial.print(scd41.getCO2());
    Serial.print(" ppm, Temperature: ");
    Serial.print(scd41.getTemperature(), 1);
    Serial.print(" C, Humidity: ");
    Serial.print(scd41.getHumidity(), 1);
    Serial.println(" %");
  }
  delay(1000);
}

This sketch reads CO2, temperature and humidity from the SCD41 over I2C using the SparkFun SCD4x Arduino Library.

Required Library

Install the SparkFun SCD4x Arduino Library from the Arduino Library Manager:

  1. Open the Arduino IDE.
  2. Go to Sketch -> Include Library -> Manage Libraries.
  3. Search for “SparkFun SCD4x Arduino Library” and install it.

Calling scd41.begin() also starts periodic measurement, after which the SCD41 produces a fresh sample about every 5 seconds. readMeasurement() returns true only when new data is ready, and getCO2(), getTemperature() and getHumidity() return the latest values.

SCD41 ESP-IDF example

Copy
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c.h"
#include "esp_log.h"

#define I2C_MASTER_NUM      I2C_NUM_0
#define I2C_MASTER_SDA_IO   21
#define I2C_MASTER_SCL_IO   22
#define I2C_MASTER_FREQ_HZ  100000
#define SCD41_ADDR          0x62

static const char *TAG = "SCD41";

static void i2c_master_init(void) {
    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = I2C_MASTER_FREQ_HZ,
    };
    i2c_param_config(I2C_MASTER_NUM, &conf);
    i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0);
}

// Send a 16-bit command to the SCD41
static esp_err_t scd41_send_command(uint16_t command) {
    uint8_t buf[2] = { command >> 8, command & 0xFF };
    return i2c_master_write_to_device(I2C_MASTER_NUM, SCD41_ADDR, buf, 2, pdMS_TO_TICKS(1000));
}

// Trigger a read and convert the 9-byte response
static esp_err_t scd41_read_measurement(uint16_t *co2, float *temperature, float *humidity) {
    uint8_t data[9];
    esp_err_t ret = scd41_send_command(0xEC05);  // read_measurement
    if (ret != ESP_OK) {
        return ret;
    }
    vTaskDelay(pdMS_TO_TICKS(2));  // wait > 1 ms for the data to be ready
    ret = i2c_master_read_from_device(I2C_MASTER_NUM, SCD41_ADDR, data, 9, pdMS_TO_TICKS(1000));
    if (ret != ESP_OK) {
        return ret;
    }
    uint16_t raw_co2  = (data[0] << 8) | data[1];
    uint16_t raw_temp = (data[3] << 8) | data[4];
    uint16_t raw_hum  = (data[6] << 8) | data[7];

    *co2 = raw_co2;
    *temperature = -45.0f + 175.0f * (float)raw_temp / 65535.0f;
    *humidity = 100.0f * (float)raw_hum / 65535.0f;
    return ESP_OK;
}

void app_main(void) {
    i2c_master_init();

    // Stop any running measurement, then start periodic measurement
    scd41_send_command(0x3F86);  // stop_periodic_measurement
    vTaskDelay(pdMS_TO_TICKS(500));
    scd41_send_command(0x21B1);  // start_periodic_measurement

    uint16_t co2;
    float temperature, humidity;

    while (1) {
        vTaskDelay(pdMS_TO_TICKS(5000));  // a new sample is ready about every 5 s
        if (scd41_read_measurement(&co2, &temperature, &humidity) == ESP_OK) {
            ESP_LOGI(TAG, "CO2: %u ppm, Temperature: %.2f C, Humidity: %.2f %%",
                     (unsigned int)co2, temperature, humidity);
        } else {
            ESP_LOGE(TAG, "Failed to read from SCD41");
        }
    }
}

This ESP-IDF example talks to the SCD41 directly over the I2C driver, with no external component. i2c_master_init() configures GPIO21 as SDA and GPIO22 as SCL. scd41_send_command() writes a 16-bit command; the code first stops any running measurement (0x3F86), then starts periodic measurement (0x21B1). Every 5 seconds it issues the read command (0xEC05), waits briefly, and reads 9 bytes. CO2 is returned directly in ppm, temperature is -45 + 175 * raw / 65535 and humidity is 100 * raw / 65535.

SCD41 ESPHome example

Copy
i2c:
  sda: GPIO21
  scl: GPIO22

sensor:
  - platform: scd4x
    co2:
      name: "CO2"
    temperature:
      name: "Temperature"
    humidity:
      name: "Humidity"
    update_interval: 60s

This configuration uses ESPHome's native scd4x sensor platform on the I2C bus (SDA GPIO21, SCL GPIO22). It exposes three sensors - CO2 in ppm, temperature and humidity - and polls them on the update_interval. See the ESPHome SCD4x documentation for options such as automatic self-calibration and ambient pressure compensation.

SCD41 PlatformIO example

Copy
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
    sparkfun/SparkFun SCD4x Arduino Library @ ^1.0.0
src/main.cppCopy
// Requires library: "SparkFun SCD4x Arduino Library"
#include <Arduino.h>
#include <Wire.h>
#include "SparkFun_SCD4x_Arduino_Library.h"

SCD4x scd41;

void setup() {
  Serial.begin(115200);
  Wire.begin();  // ESP32 default I2C: SDA = GPIO21, SCL = GPIO22

  if (scd41.begin() == false) {
    Serial.println("SCD41 not detected. Check wiring. Freezing...");
    while (1)
      ;
  }
}

void loop() {
  if (scd41.readMeasurement()) {
    Serial.print("CO2: ");
    Serial.print(scd41.getCO2());
    Serial.print(" ppm, Temperature: ");
    Serial.print(scd41.getTemperature(), 1);
    Serial.print(" C, Humidity: ");
    Serial.print(scd41.getHumidity(), 1);
    Serial.println(" %");
  }
  delay(1000);
}

The platformio.ini targets a generic ESP32 board with the Arduino framework and pulls in the SparkFun SCD4x Arduino Library through lib_deps. The sketch is the same as the Arduino example: begin() starts periodic measurement, and readMeasurement() is polled in loop(), printing CO2, temperature and humidity whenever a new sample is ready.

SCD41 MicroPython example

Copy
from machine import Pin, I2C
import time

# SCD41 I2C address
SCD41_ADDR = 0x62

# Initialize I2C (SDA = GPIO21, SCL = GPIO22)
i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=100000)

def send_command(command):
    i2c.writeto(SCD41_ADDR, bytes([command >> 8, command & 0xFF]))

def read_measurement():
    send_command(0xEC05)   # read_measurement
    time.sleep_ms(2)       # wait > 1 ms for the data
    data = i2c.readfrom(SCD41_ADDR, 9)
    co2 = (data[0] << 8) | data[1]
    raw_temp = (data[3] << 8) | data[4]
    raw_hum = (data[6] << 8) | data[7]
    temperature = -45 + 175 * raw_temp / 65535
    humidity = 100 * raw_hum / 65535
    return co2, temperature, humidity

# Stop any running measurement, then start periodic measurement
send_command(0x3F86)   # stop_periodic_measurement
time.sleep_ms(500)
send_command(0x21B1)   # start_periodic_measurement

while True:
    time.sleep(5)      # a new sample is ready about every 5 seconds
    co2, temperature, humidity = read_measurement()
    print("CO2: {} ppm, Temperature: {:.2f} C, Humidity: {:.2f} %".format(co2, temperature, humidity))

This MicroPython example drives the SCD41 with the built-in machine.I2C class, so no external driver is needed. It stops any running measurement, starts periodic measurement, then every 5 seconds sends the read command (0xEC05) and reads 9 bytes. CO2 comes back directly in ppm, while temperature and humidity are scaled from their raw 16-bit words on the default ESP32 I2C pins (SDA GPIO21, SCL GPIO22).

SCD41 specifications

From the datasheet
Interface
I2C
Sensing Principle
Photoacoustic NDIR (PASens / CMOSens)
CO2 Measurement Range
400 to 5000 ppm specified (0 to 40000 ppm output)
CO2 Accuracy
+-(50 ppm + 2.5% of reading)
CO2 Response Time (tau63)
60 s
Temperature Accuracy
+-0.8 C (typical)
Humidity Accuracy
+-6% RH (typical)
Humidity Range
0 to 95% RH
Supply Voltage
2.4 to 5.5 V
Average Supply Current
15 mA
Maximum Supply Current
205 mA
Operating Temperature
-10 to 60 C
Dimensions
10.1 x 10.1 x 6.5 mm
I2C Address
0x62 (default)

About the SCD41

The SCD41 is Sensirion’s miniature CO2 sensor. It measures carbon dioxide directly with the photoacoustic NDIR principle, and integrates a temperature and humidity sensor that the chip uses for on-chip compensation, so a single I2C part reports all three values. It ships factory-calibrated and answers on the fixed I2C address 0x62.

The specified CO2 range is 400 to 5000 ppm, with the raw output reaching up to 40000 ppm, at an accuracy of ±(50 ppm + 2.5% of reading). Temperature is accurate to about ±0.8 C and humidity to about ±6% RH, which is enough for compensation and rough room monitoring rather than precision climate logging. The photoacoustic cell responds slowly: the tau63 response time is 60 s, so readings settle over roughly a minute rather than instantly.

On the Adafruit and SparkFun breakouts the VIN pin accepts the microcontroller logic level (3.3V or 5V), an onboard regulator exposes a 3.3V output on 3Vo, and the SDA and SCL lines carry 10K pull-ups, so no extra resistors are needed to wire it to an ESP32.

Budget the power supply for the sensor’s current draw: it averages about 15 mA but peaks at up to 205 mA while heating the photoacoustic cell, so a rail that only supplies a few tens of milliamps will brown out the measurement. The operating range is -10 to 60 C, and the module is small at 10.1 x 10.1 x 6.5 mm, which makes it easy to place inside an enclosure as long as it still sees ambient air.

Resources