Reference
SCD40 / SCD41 CO2 Sensor Complete Guide
The SCD40 and SCD41 CO2 sensors: the real accuracy numbers, wiring, the full command set, calibration (ASC, altitude, temperature offset), code and the traps that give wrong readings.
- CO2 400 to 5000 ppm
- I2C address 0x62
- 2.4 to 5.5 V
- Photoacoustic NDIR
The SCD40 and SCD41 are Sensirion’s small CO2 sensors. They measure carbon dioxide in parts per million with photoacoustic NDIR (non-dispersive infrared) sensing, and they measure temperature and humidity as well. That is a different measurement from the BME280 or the SHT sensors: none of those measures CO2. The chip is only 10.1 × 10.1 × 6.5 mm and talks I2C.
This guide covers the difference between the SCD40 and SCD41 with the real numbers, the pins, wiring, the full command set, calibration (ASC, forced recalibration, altitude and pressure), a code example that talks to the chip, and the traps that give wrong readings. The chip data comes from the Sensirion SCD4x datasheet (version 1.5, July 2023). Breakout boards differ from seller to seller, so check the labels on your own board.
SCD40 or SCD41
| SCD40 | SCD41 | |
|---|---|---|
| Specified CO2 range | 400 to 2000 ppm | 400 to 5000 ppm |
| CO2 accuracy | ±(50 ppm + 5 % of the reading) over 400 to 2000 ppm | ±(50 ppm + 2.5 %) over 400 to 1000 ppm, ±(50 ppm + 3 %) over 1001 to 2000 ppm, ±(40 ppm + 5 %) over 2001 to 5000 ppm |
| Single shot mode | No | Yes, one measurement on command |
| Low-power single shot | No | Yes, 0.45 mA average at one measurement every 5 minutes (3.3 V) |
| Periodic measurement | Yes, one every 5 s | Yes, one every 5 s |
| I2C address | 0x62 | 0x62 |
Both have the same pins, address and commands, so code written for one runs on the other, except for the single shot commands that only the SCD41 knows. Take the SCD41 for battery projects, for a wider range or for the best accuracy. The SCD40 is cheaper and fine for a monitor on mains power.
Source: Sensirion SCD4x datasheet, version 1.5 (July 2023).
Getting started
Specifications
CO2, humidity and temperature
| CO2 output range | 0 to 40000 ppm |
| CO2 repeatability | ±10 ppm typical |
| CO2 accuracy with ASC | ±(5 ppm + 0.5 % of the reading) typical over 400 to 2000 ppm |
| Humidity range and accuracy | 0 to 100 %RH, ±6 %RH typical (15 to 35 °C, 20 to 65 %RH), ±9 %RH over the full range |
| Humidity response | 90 s (63 %), drift less than 0.25 %RH per year |
| Temperature range and accuracy | −10 to 60 °C, ±0.8 °C typical (15 to 35 °C), ±1.5 °C over the full range |
| Temperature response | 120 s (63 %), drift less than 0.03 °C per year |
Electrical
| Supply voltage | 2.4 to 5.5 V, 3.3 or 5 V typical. Absolute maximum 6 V |
| Supply ripple | At most 30 mV peak to peak, an LDO is recommended |
| Average current, periodic | 15 mA at 3.3 V, 11 mA at 5 V (one measurement every 5 s) |
| Average current, low-power periodic | 3.2 mA at 3.3 V |
| Average current, single shot (SCD41) | 0.45 mA at 3.3 V, 0.36 mA at 5 V (one measurement every 5 minutes) |
| Interface | I2C, up to 400 kHz, address 0x62, a CRC on every 16-bit word |
| Power-up time | 30 ms at most |
| Storage | 10 to 50 °C recommended |
Source: Sensirion SCD4x datasheet, version 1.5 (July 2023). The accuracy holds at 25 °C, 50 %RH and 1013 mbar with the ASC working.
Dimensions
The chip is 10.1 × 10.1 mm and 6.5 mm high, with a protection membrane on top. Breakout boards come in many sizes, so measure your own before you design a case. Leave the membrane free: air must reach it, and a conformal coating or glue blocks it.
Source: Sensirion SCD4x datasheet.
Pins
| Pin | Type | Function |
|---|---|---|
| VDD | Power | Supply, 2.4 to 5.5 V |
| VDDH | Power | Supply of the infrared source. Must be tied to VDD (bare chip). Breakouts do this for you |
| GND | Power | Ground |
| SDA | I2C | Data, needs a pull-up, for example 10 kΩ |
| SCL | I2C | Clock, needs a pull-up, for example 10 kΩ |
A breakout normally brings out VDD (or VIN), GND, SDA and SCL. The order differs between makers, so read the print on yours.
Source: Sensirion SCD4x datasheet, pin assignment.
Wiring
| SCD4x pin | ESP32 | Arduino Uno / Nano | Raspberry Pi | ESP8266 |
|---|---|---|---|---|
| VDD | 3.3 V | 3.3 V | 3.3 V (pin 1) | 3.3 V |
| GND | GND | GND | GND (pin 6) | GND |
| SDA | GPIO21 | A4 | GPIO2 (pin 3) | D2 (GPIO4) |
| SCL | GPIO22 | A5 | GPIO3 (pin 5) | D1 (GPIO5) |
The chip draws 15 mA on average and has current peaks, so power it from a stable supply and keep the wires short. A noisy supply (more than 30 mV ripple) disturbs the sensor. An Arduino Uno at 5 V needs a board with level shifting.
Software setup
| Platform | What to do |
|---|---|
| Arduino IDE | Install SparkFun SCD4x Arduino Library, or Sensirion’s Sensirion I2C SCD4x, from the Library Manager |
| PlatformIO | Add sparkfun/SparkFun SCD4x Arduino Library to lib_deps |
| ESP-IDF | Use the I2C master driver and the commands below |
| Note | There is no first-party Adafruit Arduino library for this chip, only a CircuitPython driver |
Read the sensor
#include <Wire.h>
#include <SparkFun_SCD4x_Arduino_Library.h>
SCD4x co2Sensor;
void setup() {
Serial.begin(115200);
Wire.begin(21, 22);
if (!co2Sensor.begin()) {
Serial.println("Couldn't find SCD4x sensor");
while (1) delay(10);
}
}
void loop() {
if (co2Sensor.readMeasurement()) {
Serial.print("CO2 (ppm): ");
Serial.print(co2Sensor.getCO2());
Serial.print(" Temp (C): ");
Serial.print(co2Sensor.getTemperature());
Serial.print(" Humidity (%RH): ");
Serial.println(co2Sensor.getHumidity());
}
delay(5000); // the sensor itself only produces a new reading every 5s
}I did not run this sketch on a board, it follows the library example.
Talking to the chip yourself
All commands are 16 bits and every data word has an 8-bit CRC (polynomial 0x31, start value 0xFF). Start periodic measurement, wait 5 seconds, ask whether data is ready and read nine bytes: CO2, temperature and humidity, each followed by its CRC.
uint8_t crc8(const uint8_t *d, int n) {
uint8_t c = 0xFF;
for (int i = 0; i < n; i++) {
c ^= d[i];
for (int b = 0; b < 8; b++)
c = (c & 0x80) ? (c << 1) ^ 0x31 : (c << 1);
}
return c;
}
void cmd(uint16_t c) {
Wire.beginTransmission(0x62);
Wire.write(c >> 8); Wire.write(c & 0xFF);
Wire.endTransmission();
}
bool ready() {
cmd(0xE4B8); // get_data_ready_status
delay(1);
Wire.requestFrom(0x62, 3);
uint16_t w = (Wire.read() << 8) | Wire.read(); Wire.read();
return (w & 0x07FF) != 0; // lower 11 bits zero = not ready
}
void setup() {
Serial.begin(115200);
Wire.begin(21, 22);
cmd(0x3F86); delay(500); // stop, in case it is running
cmd(0x21B1); // start_periodic_measurement
}
void loop() {
if (ready()) {
cmd(0xEC05); delay(1); // read_measurement
Wire.requestFrom(0x62, 9);
uint8_t b[9];
for (int i = 0; i < 9; i++) b[i] = Wire.read();
if (crc8(b, 2) == b[2] && crc8(b + 3, 2) == b[5] && crc8(b + 6, 2) == b[8]) {
uint16_t co2 = (b[0] << 8) | b[1];
float t = -45.0f + 175.0f * ((b[3] << 8) | b[4]) / 65535.0f;
float h = 100.0f * ((b[6] << 8) | b[7]) / 65535.0f;
Serial.printf("%u ppm %.2f C %.1f %%RHn", co2, t, h);
}
}
delay(1000);
}
This sketch has not been run on a board. The CRC routine is the same one as in the SHT sensors, where 0xBE 0xEF gives 0x92.
Commands
| Command | What it does |
|---|---|
| 0x21B1 | start_periodic_measurement (a new value every 5 s) |
| 0xEC05 | read_measurement (1 ms, works during measurement) |
| 0x3F86 | stop_periodic_measurement (wait 500 ms) |
| 0xE4B8 | get_data_ready_status |
| 0x21AC | start_low_power_periodic_measurement |
| 0x241D / 0x2318 | set / get temperature offset (default 4 °C) |
| 0x2427 / 0x2322 | set / get sensor altitude in metres (default 0) |
| 0xE000 | set / get ambient pressure in pascal (70000 to 120000, default 101300) |
| 0x362F | perform_forced_recalibration (400 ms) |
| 0x2416 / 0x2313 | set / get automatic self-calibration enabled (default on) |
| 0x3615 | persist_settings to EEPROM (800 ms) |
| 0x3682 | get_serial_number |
| 0x3639 | perform_self_test (10 s) |
| 0x3632 | perform_factory_reset (1200 ms) |
| 0x3646 | reinit (30 ms) |
| 0x219D | measure_single_shot (SCD41, 5 s) |
| 0x2196 | measure_single_shot_rht_only (SCD41, 50 ms) |
| 0x36E0 / 0x36F6 | power_down / wake_up (SCD41, wake-up takes 30 ms) |
The three values are CO2 [ppm] = word 0, T = −45 + 175 × word 1 / 65535 °C and RH = 100 × word 2 / 65535 %RH. Most settings can only be changed while the sensor is idle, so stop the periodic measurement first. Use persist_settings to keep a setting after a power cycle.
Source: Sensirion SCD4x datasheet, sections 3.4 to 3.10.
Calibration: ASC, altitude and pressure
Automatic self-calibration (ASC)
The ASC is on by default. It assumes the sensor sees fresh air with about 400 ppm CO2 at least once a week, and it corrects drift from that. If the sensor never sees fresh air (a sealed room, a greenhouse with CO2 enrichment, a bedroom that is always occupied), the ASC pulls the readings down and the values come out too low. Switch it off with 0x2416 in that case, or ventilate the room well once a week.
Forced recalibration
Put the sensor in outdoor air for several minutes, then send the forced recalibration command with the known value (about 400 to 420 ppm outside). Do this when you cannot rely on the ASC.
Altitude and pressure
CO2 readings depend on the air pressure. Set the altitude of your site once with 0x2427, or set the ambient pressure with 0xE000 when you have a pressure sensor, for example the BME280. Setting a pressure overrides the altitude.
Temperature offset
The chip heats itself, and the board around it heats it more. The default offset is 4 °C, valid values are 0 to 20 °C. Set the offset so the temperature and humidity match a reference thermometer, once the sensor has been running in its final enclosure. It only changes the temperature and humidity, not the CO2 value.
Source: Sensirion SCD4x datasheet, sections 3.6 and 3.7.
Getting accurate readings
- Wait for the warm-up. The first value comes after 5 seconds, and full accuracy needs a few minutes. After assembly, the accuracy is restored by the ASC or a forced recalibration within about 5 days.
- Do not breathe on it while you test. Your breath is several thousand ppm and the reading takes a while to fall.
- Mount it in the air, not in a closed box. The response time of the CO2 reading depends on airflow around the membrane.
- Keep it away from heat sources. Voltage regulators, displays and the ESP32 itself warm the board and raise the temperature and lower the humidity readings.
- Use a stable supply. Keep the ripple under 30 mV.
- Use the SCD41 single shot mode for batteries. One measurement every 5 minutes costs 0.45 mA on average instead of 15 mA.
- Do not cover the membrane with tape, glue or coating.
Real gotchas
- The accuracy is not a flat number. It is a number of ppm plus a percentage of the reading, and it differs between SCD40 and SCD41.
- The ASC can drive the values wrong. If the sensor never sees about 400 ppm, either switch the ASC off or ventilate once a week.
- The temperature reads too high. The chip and the board heat it. Set the temperature offset (default 4 °C) or the humidity will also be wrong.
- Wrong altitude, wrong CO2. Set the altitude or the ambient pressure, otherwise the error grows with height above sea level.
- Settings are lost without persist_settings. Write them to the EEPROM if they must survive a power cycle. The EEPROM has a limited number of writes, so do not do it in a loop.
- Stop before you configure. Most commands only work while the sensor is idle. Send stop_periodic_measurement and wait 500 ms first.
- A single 5 V supply is not always fine. The chip takes 2.4 to 5.5 V, but the I2C lines of your microcontroller must match the supply of the board.
- The SCD40 has no single shot mode. The commands 0x219D and 0x2196 only exist on the SCD41.
- Not a safety device. Do not use it as the only protection against dangerous CO2 levels, and it is not a carbon monoxide (CO) sensor.
Compared with other air sensors
| Sensor | Measures | Good for | Weak point |
|---|---|---|---|
| SCD40 / SCD41 | CO2 (NDIR), temperature, humidity | Real CO2 values | About 15 mA, a few seconds per sample |
| BME280 | Temperature, humidity, pressure | Cheap, tiny | No CO2 |
| SHT4x | Temperature, humidity | Accurate humidity | No CO2 |
| MQ or eCO2 gas sensors | A gas guess | Cheap | A calculated eCO2 is not a real CO2 measurement |
See the BME280 guide for pressure, which also feeds the SCD4x pressure compensation, and the SHT40/SHT41 guide for accurate humidity. The last row is general knowledge and not from a datasheet read for this guide.
What to build with it
- Indoor air quality monitoring: CO2 buildup is a genuine, well-studied proxy for how stale/poorly-ventilated a room is.
- Demand-controlled ventilation: only run an extractor fan or HRV when CO2 actually rises.
- Meeting-room/classroom monitors: a common, practical use case for this exact chip.
- Greenhouse CO2 supplementation control: closing the loop on CO2 enrichment for plant growth.
- A CO2 traffic light with three LEDs: green below 800 ppm, orange up to 1200 ppm, red above.
- A HomeKit air-quality sensor on an ESP32.
Safety notes
Before you wire anything up
- Keep the supply between 2.4 and 5.5 V, never above 6 V.
- Check the pin order on your own breakout before you apply power.
- Never connect the board to mains voltage.
- A CO2 sensor is not a safety device. High CO2 makes you drowsy, and the sensor does not measure carbon monoxide or fire.
- Do not use it for life-safety decisions such as an alarm in a confined space.
Conclusion
The SCD40 and SCD41 are real CO2 sensors that fit in a few square centimetres. Take the SCD41 for a wider range, better accuracy and a low-power single shot mode, and the SCD40 for an always-powered monitor. Set the altitude, the temperature offset, and decide on the ASC, then the readings can be trusted. Keep it away from heat and leave the membrane free.
Chip data is from the Sensirion SCD4x datasheet, version 1.5. The comparison row for gas sensors is general knowledge.
More guides
Keep building.
From ESP32 HomeKit accessories to Arduino and ATtiny reference guides, there’s more where this came from.
All guides

