CO2 monitor on an Arduino Uno
An SCD41 counts the CO2 in the room every five seconds and an SHT40 reads the temperature and humidity, with a warning past 1000 ppm. The whole build, an Arduino Uno and 2 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Intermediate About 20 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $61. On Mokxi they're free.
What you'll learn
- Two I2C sensors sharing one pair of wires
- Polling a sensor until its data is ready
- What CO2 ppm numbers mean for room air
- Using a named constant as an alert threshold
- Difficulty: Intermediate
- Time: About 20 minutes
- Board: Arduino Uno R3
- Parts: 4
How it works
The SCD41 and the SHT40 share one I2C bus. The SCD41 measures real CO2 and produces a fresh reading every five seconds. The SHT40 reports temperature and humidity.
The sketch starts the SCD41 measuring, then keeps asking whether a new reading is ready. When one is, it reads the CO2, takes the temperature and humidity from the SHT40, and prints all three over serial.
If the CO2 reaches 1000 ppm, it adds a line telling you to open a window.
Parts list
4 parts, plus the jumper wires. They retail for about $61; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × CO2 sensor, SCD41
- 1 × Power, 5 V
- 1 × Temperature and humidity, SHT40
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| CO2 sensor, SCD41 pin SCL | Arduino Uno R3 pin SCL |
| CO2 sensor, SCD41 pin SDA | Arduino Uno R3 pin SDA |
| CO2 sensor, SCD41 pin GND | Arduino Uno R3 pin GND |
| Temperature and humidity, SHT40 pin VIN | Arduino Uno R3 pin 5V |
| Temperature and humidity, SHT40 pin GND | Arduino Uno R3 pin GND |
| Temperature and humidity, SHT40 pin SDA | Arduino Uno R3 pin A4 |
| Temperature and humidity, SHT40 pin SCL | Arduino Uno R3 pin A5 |
| CO2 sensor, SCD41 pin VIN | 5 V |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// CO2 monitor: a Sensirion SCD41 counts the CO2 in the room and an SHT40
// reads the temperature and humidity, both on one I2C bus.
//
// SCD41, SHT40 SDA -> A4 SCL -> A5 VIN -> 5 V GND -> GND
//
// The SCD41 measures real CO2 by the sound a light pulse makes in it, and
// gives a new reading every five seconds. Fresh outdoor air is about 420 ppm;
// a closed room full of people climbs past 1000 ppm, and that is the point
// where a window wants opening.
//
// Things to try. Drag the CO2 slider on the SCD41 past 1000 ppm and watch
// the sketch ask for a window.
#include <Wire.h>
#include <SensirionI2cScd4x.h>
#include "Adafruit_SHT4x.h"
const uint16_t STUFFY_PPM = 1000;
SensirionI2cScd4x scd41;
Adafruit_SHT4x sht40;
void setup() {
Serial.begin(9600);
Wire.begin();
scd41.begin(Wire, SCD41_I2C_ADDR_62);
delay(30);
scd41.stopPeriodicMeasurement();
if (scd41.startPeriodicMeasurement() != NO_ERROR) {
Serial.println("No SCD41");
}
if (!sht40.begin()) {
Serial.println("No SHT40");
}
Serial.println("CO2 monitor: the first reading comes in five seconds");
}
void loop() {
bool ready = false;
scd41.getDataReadyStatus(ready);
if (!ready) {
delay(500);
return;
}
uint16_t co2 = 0;
float t = 0, rh = 0;
if (scd41.readMeasurement(co2, t, rh) != NO_ERROR) {
return;
}
sensors_event_t humidity, temp;
sht40.getEvent(&humidity, &temp);
Serial.print("CO2 ");
Serial.print(co2);
Serial.print(" ppm ");
Serial.print(temp.temperature, 1);
Serial.print(" C ");
Serial.print(humidity.relative_humidity, 0);
Serial.println(" %RH");
if (co2 >= STUFFY_PPM) {
Serial.println("Stuffy: open a window");
}
}
Code walkthrough
The key functions of the sketch, and what each one does.
setup sketch.ino
Opens serial and the I2C bus, then starts the SCD41. It stops any measurement already running before starting a fresh periodic one, so the sensor is in a known state. If either sensor fails to start, it prints a message such as No SCD41 or No SHT40.
void setup() {
Serial.begin(9600);
Wire.begin();
scd41.begin(Wire, SCD41_I2C_ADDR_62);
delay(30);
scd41.stopPeriodicMeasurement();
if (scd41.startPeriodicMeasurement() != NO_ERROR) {
Serial.println("No SCD41");
}
if (!sht40.begin()) {
Serial.println("No SHT40");
}
Serial.println("CO2 monitor: the first reading comes in five seconds");
}loop sketch.ino
Checks getDataReadyStatus. If no reading is ready, it waits half a second and tries again. When one is ready, it reads CO2 from the SCD41 and the SHT40 values, prints them, and prints a warning if CO2 is at or above STUFFY_PPM.
void loop() {
bool ready = false;
scd41.getDataReadyStatus(ready);
if (!ready) {
delay(500);
return;
}
uint16_t co2 = 0;
float t = 0, rh = 0;
if (scd41.readMeasurement(co2, t, rh) != NO_ERROR) {
return;
}
sensors_event_t humidity, temp;
sht40.getEvent(&humidity, &temp);
Serial.print("CO2 ");
Serial.print(co2);
Serial.print(" ppm ");
Serial.print(temp.temperature, 1);
Serial.print(" C ");
Serial.print(humidity.relative_humidity, 0);
Serial.println(" %RH");
if (co2 >= STUFFY_PPM) {
Serial.println("Stuffy: open a window");
}
}Troubleshooting
The usual mistakes with this circuit, and the fix for each.
- The monitor prints nothing for the first few seconds This project
The SCD41 only gives a new reading every five seconds. Wait for the first one. - No SCD41 or No SHT40 appears in the serial monitor This project
Check SDA and SCL, power and ground on that sensor. - The window warning never shows This project
It only prints at 1000 ppm or more. Raise the CO2 slider past that, or lowerSTUFFY_PPM. - Readings do not appear or look stuck. The SCD41 gives a new value only about every five seconds, and the first reading takes a while. CO2 sensor
Start periodic measurement, then wait at least 5 seconds. Check dataReady before you read. The I2C address is 0x62. - The CO2 reading is off or the sensor misbehaves. It needs 3.3 V or the board's regulator, and it needs fresh air for the baseline. CO2 sensor
Power it at the voltage the breakout says, with pull-ups on SDA and SCL. Let it settle for a few minutes, and keep it away from your breath and heat sources. - The sensor is not found. Temperature and humidity
The I2C address is 0x44 for the common SHT40. Check SDA, SCL and pull-ups, and use a library for the SHT4x family. - A 5 V board damages the sensor. Temperature and humidity
The SHT4x supply is 1.08 V to 3.6 V. Use 3.3 V. A precise reading takes about 8.3 ms, so wait before you read it back, and check the CRC bytes. - Upload fails or nothing runs after wiring pins 0 and 1 to a module. Arduino Uno R3
Pins 0 and 1 are the USB serial port. Move the module to other pins, or unplug it while uploading. Also check the right board is selected.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.