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.

On this page
SCD41 pinout
The SCD41 breakout brings out five pins - power in, a regulated 3.3V output, ground, and the I2C clock and data lines.
| Pin | Type | Description | Notes |
|---|---|---|---|
| VIN | Power | Power input; supply at the microcontroller logic level (3.3V or 5V) | |
| 3Vo | Power | 3.3V output from the onboard regulator (at least 100mA) | |
| GND | Ground | Common ground for power and logic | |
| SCL | I2C | I2C clock line; 10K pull-up onboard | |
| SDA | I2C | I2C 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
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.
| SCD41 pin | ESP32 pin | Purpose |
|---|---|---|
| VIN | 3V3 | Power supply (3.3V logic level) |
| GND | GND | Common ground |
| SCL | GPIO22 | I2C clock (SCL) |
| SDA | GPIO21 | I2C 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
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:
- Open the Arduino IDE.
- Go to Sketch -> Include Library -> Manage Libraries.
- 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
Copyi2c:
sda: GPIO21
scl: GPIO22
sensor:
- platform: scd4x
co2:
name: "CO2"
temperature:
name: "Temperature"
humidity:
name: "Humidity"
update_interval: 60sThis 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// 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
Copyfrom 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
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
Similar sensors



