Carbon dioxide (CO2) is the fourth most abundant gas in the earth's atmosphere. CO2 is a byproduct of normal cell function when it is breathed out of the body. It is also produced when fossil fuels are burned or decaying vegetation. Surface soils can sometimes contain high concentrations of this gas, from decaying vegetation or chemical changes in the bedrock.
We measure the CO2 level in ppm units. This number tells us how many parts of CO2 there are in one million parts of air. For example, if CO2 is at 800 ppm, that means in one million particles of air there are 800 particles of CO2.
Exposure to CO2 can produce a variety of health effects. These may include headaches, dizziness, restlessness, a tingling or pins or needles feeling, difficulty breathing, sweating, tiredness, increased heart rate, elevated blood pressure, coma, asphyxia, and convulsions.
The levels of CO2 in the air and potential health problems are:
- 400 ppm: average outdoor air level.
- 400-1000 ppm: typical level found in occupied spaces with good air exchange.
- 1000-2000 ppm: level associated with complaints of drowsiness and poor air.
- 2000-5000 ppm: level associated with headaches, sleepiness, and stagnant, stale, stuffy air. Poor concentration, loss of attention, increased heart rate and slight nausea may also be present.
- 5000 ppm: this indicates unusual air conditions where high levels of other gases could also be present. Toxicity or oxygen deprivation could occur. This is the permissible exposure limit for daily workplace exposures.
- 40,000 ppm: this level is immediately harmful due to oxygen deprivation.
In this project I built a simple and portable device using Seeed Wio Terminal which displays indoor CO2 level, temperature and percentage relative humidity on its LCD screen. When the CO2 level is exceeded by a set threshold value the inbuilt buzzer starts beeping and a 5V USB powered ventilation fan turns on to keep the circulation going and reduce CO2 levels. This device is useful especially during sleep when CO2 level can increase due to poor air circulation and can affect health.
This project was conceived by my own requirement in the bedroom where airflow was severely affected but I had no idea about it and after working on another data collection project it was revealed that the air circulation in the bedroom was not good and the CO2 level was increasing during night.
Hardware SetupWe will be using Grove CO2 sensor (SCD41) for CO2 level measurement. The SCD41 from Sensirion is a high quality Photoacoustic based CO₂ sensor capable of detecting 0 to 40000ppm with high accuracy over 400-5000ppm ±(40ppm+5%).
To control a ventilation fan, we will be using Grove Relay which is powered by the 5V from the 40-pins Wio Terminal headers. A USB micro B breakout is used to connect the fan and power supply from the Grove Relay. The ground connection is taken from the Wio Terminal headers. The wiring diagram can be found in the Schematics Section.
The application is developed, compiled and flashed using Arduino IDE. Please download the latest Arduino IDE and follow the instructions to install from here: https://www.arduino.cc/en/software. To install the Wio Terminal board library, open the Arduino IDE, click on File > Preferences, and copy below URL to Additional Boards Manager URLs: https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json
Click on Tools > Board > Board Manager and Search Wio Terminal and click on install.
You would need to select the board and port using the Tools > Board menu.
To install library for the Grove CO2 Sensor (SCD41), click on Tools > Manage Libraries... to open Library Manager and search Sensirion I2C Scd4x and click on install.
We do not need to install any library for Grove Relay which is driven by Digital GPIO pins.
Code#include <SPI.h>
#include <Wire.h>
#include <TFT_eSPI.h>
#include <SensirionI2CScd4x.h>
#include "icon_thermometer.h"
#include "icon_humidity.h"
#include "icon_co2.h"
#define CO2_THRESHOLD 1200
SensirionI2CScd4x scd4x;
TFT_eSPI tft = TFT_eSPI();
unsigned int prev_time = 0;
bool alarm = false;
void printUint16Hex(uint16_t value)
{
Serial.print(value < 4096 ? "0" : "");
Serial.print(value < 256 ? "0" : "");
Serial.print(value < 16 ? "0" : "");
Serial.print(value, HEX);
}
void printSerialNumber(uint16_t serial0, uint16_t serial1, uint16_t serial2)
{
Serial.print("Serial: 0x");
printUint16Hex(serial0);
printUint16Hex(serial1);
printUint16Hex(serial2);
Serial.println();
}
void setup() {
Serial.begin(115200);
pinMode(WIO_5S_PRESS, INPUT_PULLUP);
pinMode(WIO_BUZZER, OUTPUT);
pinMode(D0, OUTPUT); // Relay
digitalWrite(D0, LOW);
tft.init();
tft.setRotation(3);
tft.setTextFont(4);
tft.fillScreen(TFT_BLACK);
Wire.begin();
uint16_t error;
char errorMessage[256];
scd4x.begin(Wire);
// stop potentially previously started measurement
error = scd4x.stopPeriodicMeasurement();
if (error) {
Serial.print("Error trying to execute stopPeriodicMeasurement(): ");
errorToString(error, errorMessage, 256);
Serial.println(errorMessage);
tft.print(errorMessage);
}
uint16_t serial0;
uint16_t serial1;
uint16_t serial2;
error = scd4x.getSerialNumber(serial0, serial1, serial2);
if (error) {
Serial.print("Error trying to execute getSerialNumber(): ");
errorToString(error, errorMessage, 256);
Serial.println(errorMessage);
tft.print(errorMessage);
} else {
printSerialNumber(serial0, serial1, serial2);
}
// Start Measurement
error = scd4x.startPeriodicMeasurement();
if (error) {
Serial.print("Error trying to execute startPeriodicMeasurement(): ");
errorToString(error, errorMessage, 256);
Serial.println(errorMessage);
tft.print(errorMessage);
}
Serial.println("Waiting for first measurement... (5 sec)");
tft.setCursor(100, 80);
tft.print("Waiting...");
delay(5000);
tft.fillScreen(TFT_BLACK);
tft.drawXBitmap(20, 20, icon_co2_bits, icon_co2_width, icon_co2_height, TFT_YELLOW);
tft.drawXBitmap(20, 90, icon_therm_bits, icon_therm_width, icon_therm_height, TFT_RED);
tft.drawXBitmap(20, 160, icon_hum_bits, icon_hum_width, icon_hum_height, TFT_CYAN);
}
uint16_t prev_co2 = 0;
void loop()
{
unsigned long curr_time = millis();
if ((curr_time - prev_time) > 5000) {
prev_time = curr_time;
uint16_t error;
char errorMessage[256];
// Read Measurement
uint16_t co2;
float temperature;
float humidity;
error = scd4x.readMeasurement(co2, temperature, humidity);
if (error) {
Serial.print("Error trying to execute readMeasurement(): ");
errorToString(error, errorMessage, 256);
Serial.println(errorMessage);
tft.print(errorMessage);
} else if (co2 == 0) {
Serial.println("Invalid sample detected, skipping.");
} else {
if (co2 > CO2_THRESHOLD) {
alarm = true;
} else {
alarm = false;
}
Serial.print("Co2:");
Serial.print(co2);
Serial.print("\t");
Serial.print("Temperature:");
Serial.print(temperature);
Serial.print("\t");
Serial.print("Humidity:");
Serial.println(humidity);
// clear previous value by setting black font color
tft.setTextFont(6);
tft.setCursor(90, 30);
tft.setTextColor(TFT_BLACK, TFT_BLACK);
tft.print(prev_co2);
tft.setTextFont(4);
tft.print("ppm");
tft.setTextFont(6);
tft.setCursor(90, 30);
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.print(co2);
tft.setTextFont(4);
tft.print("ppm");
prev_co2 = co2;
char temp[5];
sprintf(temp, "%0.1f", temperature);
tft.setCursor(90, 100);
tft.setTextFont(6);
tft.setTextColor(TFT_RED, TFT_BLACK);
tft.print(temp);
tft.setTextFont(4);
tft.print("`C");
char hum[5];
sprintf(hum, "%0.1f", humidity);
tft.setCursor(90, 170);
tft.setTextFont(6);
tft.setTextColor(TFT_CYAN, TFT_BLACK);
tft.print(hum);
tft.setTextFont(4);
tft.print("RH");
}
}
if (digitalRead(WIO_5S_PRESS) == LOW) {
alarm = false;
}
if (alarm) {
analogWrite(WIO_BUZZER, 128);
delay(1000);
analogWrite(WIO_BUZZER, 0);
delay(700);
digitalWrite(D0, HIGH); // Relay Switch on
} else {
digitalWrite(D0, LOW); // Relay Switch off
}
delay(300);
}
The code bundle can be downloaded from the Github repository: https://github.com/metanav/WioTerminal_CO2_Monitoring/archive/refs/heads/main.zip. After downloading, unzip the bundle and open the Wio_Terminal_CO2_Monitor/Wio_Terminal_CO2_Monitor.ino sketch in the Arduino IDE and click on Sketch > Upload to compile/upload the firmware.
DisplayThe sensor readings are updated to the Wio Terminal LCD display at 5 minutes interval. The free icon images for CO2, temperature and humidity were downloaded from Flaticon and were converted to X BitMap (XBM), a plain text binary image format, using Convertio.
This device has many other possibilities such as we can collect the time-series environmental sensor data and save them using inbuilt SD card module which can be used for further analysis of the CO2 levels trends or finding anomalies. It can be connected to the Cloud services using the inbuilt Wifi module.
I would like to thank SeeedStudio for manufacturing such a great choices of environmental sensors as a Grove modules which is super easy to integrate into most of the development boards with Grove or Qwiic/Stemma connector using an adapter. Also, Wio Terminal is a beginner-friendly development board for quick prototyping.
Comments