Skip to Content

Measuring Air Quality with SGP30 and Arduino

Measuring Air Quality with SGP30 and Arduino

In this tutorial you will learn how to measure air quality using a SGP30 sensor and an Arduino Uno . The SGP30 is a digital gas sensor with an I2C interface that can measure the concentration of volatile organic compounds (VOCs) and carbon dioxide (CO2) in the air.

VOCs are a group of organic chemicals that can easily evaporate into the air. They are emitted by a wide range of products and materials often found indoors, such as cleaning products, paints, and furniture. High levels of VOCs can cause eye, nose, and throat irritation, as well as headaches, dizziness, and fatigue.

CO2 is a measure of the concentration of carbon dioxide in the air. Elevated levels of CO2 can indicate poor ventilation and the presence of other indoor pollutants. High levels of CO2 can cause drowsiness, headaches, and impaired cognitive function.

In this project we will use an Arduino Uno and the SGP30 sensor to monitor VOC and CO2 levels.

Required Parts

I used an Arduino Uno for this project but any other Arduino board, or an ESP8266/ESP32 board will work just as well.

GY-SGP30 Sensor

OLED display

Pantalla OLED

Arduino

Arduino Uno

Dupont wire set

Dupont Wire Set

Half_breadboard56a

Tablero de pruebas

USB Data Sync cable Arduino

USB Cable for Arduino UNO

Makerguides.com is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to products on Amazon.com. As an Amazon Associate we earn from qualifying purchases.

Basics of The SGP30 Air Quality Sensor

The SGP30 Air Quality Sensor is a digital multi-pixel gas sensor designed to measure total volatile organic compounds (TVOCs) and equivalent carbon dioxide (eCO2) levels in the air. It actually measures H2 and Ethanol, which an internal signal processer converts into TVOC and eCO2 values. You can get the “raw” readings for the measured H2 and Ethanol levels as well but in the tutorial we will focus on the TVOC and eCO2 measurements.

The sensor operates on a low power consumption mode (a few mA), making it suitable for battery-powered applications. It has a built-in heater that allows for self-calibration and can compensate for temperature and humidity changes.

The SGP30 communicates over I2C interface, making it easy to connect to microcontrollers like Arduino and ESP32. It provides real-time data, which can be used to monitor indoor air quality and trigger actions based on predefined thresholds.

The image below shows the internal schematics of the sensor. You can identify the hot plate (MEMS) and the Signal Processor that converts the analog signal from the hot plate into a digital signal at the I2C interface.

Internal build blocks of the SGP30
Internal build blocks of the SGP30 (source)

The hot plate in the SGP30 enables the sensor to measure the concentration of gases. When the hot plate is heated up, the SnO2 sensing material is oxidized by the gas molecules in the surrounding air. This is causing a change in its electrical conductivity, which is then measured by the sensor.

The following table lists the main specifications of the SGP30:

Parámetrorange
TVOC0 ppb to 60000 ppb
eCO2400 ppm to 60000 ppm
Sampling rate1 Hz
Tensión de alimentación1.62 V to 1.98 V
Interfaz de comunicaciónI2C fast mode
Measurement Current 48 mA
Idle Current10 µA
I2C address0x58

For additional information have a look at the datasheet liked below:

Breakout Board

The SGP30 itself is tiny and you usually don’t use it directly but via a breakout board. The picture below shows the front and back of the breakout board we are using in this project. Note the square little thing with the white dot – that is the actual SGP30 sensor.

Breakout board for SGP30

The breakout board adds a few resistors and capacitors required by the SGP30, and most importantly has a voltage regulator that allows us to run the sensor module on 3.3V or 5V.

There are many other breakout boards for the SGP30 that come in different shapes and colors (see below). However, they are all essentially identically with the similar circuitry and an I2C interface. They should work the same way as the one suggested in the Required Parts.

Other breakout boards for SG30
Other breakout boards for SG30

Connecting the SG30 to Arduino UNO

Due to the I2C interface, connecting the SG30 breakout board to the Arduino is very simple. Connect VIN to 5V (red wire), GND to GND (black wire), SCL to SCL (yellow wire) and SDA to SDA (white wire). The following image shows the wiring of the SG30 with the Arduino.

Wiring of SGP30 with Arduino
Wiring of SGP30 with Arduino

Instead of using the dedicated SCL, SDA pins of the Arduino, you could also use the A4 and A5 inputs (SCL->A5, SDA->A4). For other Arduinos or ESP32s, just connect to the hardware I2C pins or define them via the Wire library – more about that in the next section.

And that is all you need to know about the wiring.

Code to read Air Quality Data from SG30

There are several libraries you can use to to read data from the SG30 sensor. We are going to use the Adafruit_SGP30 by Adafruit here. You can install it in the usual way. Click on Tools -> Manage Libraries ... to open the Library Manager, enter “Adafruit_SGP30” into the search bar and then click INSTALL. A completed installation should look like this:

Adafruit_SGP30 library installed via Library Manager
Adafruit_SGP30 library installed via Library Manager

The Adafruit_SGP30 library makes reading air quality data from the SG30 sensor very easy. Have a look at the following code.

#include "Wire.h"
#include "Adafruit_SGP30.h"

Adafruit_SGP30 sgp;

void setup() {
  Serial.begin(9600);
  sgp.begin();
}

void loop() {
  if (sgp.IAQmeasure()) {
    Serial.print("TVOC:");  // ppb
    Serial.println(sgp.TVOC);
    Serial.print("eCO2:");  // ppm
    Serial.println(sgp.eCO2);    
  }
  delay(1000);
}

We start by including the required libraries and creating the sensor object sgp. In the setup function we initiate the Serial communication and the sensor. If you connect the SG30 not to the default pins for I2C you have to specify them. Add the following line to the setup function (before sgp.begin) and set your I2C pins this way.

Wire.begin(int sda, int scl);

In the loop function, we call sgp.IAQmeasure() to get a measurement. If it is successful, we print the total volatile organic compounds (sgp.TVOC) and the equivalent carbon dioxide (sgp.eCO2) levels to the Serial monitor.

The warm-up time for the SGP30 Air Quality Sensor is typically around 15 seconds. During this time, the sensor’s internal heater reaches the optimal operating temperature for accurate air quality measurements. So if you upload and run the code above for several seconds you will get constants readings of TVOC=0 and eCO2=400 for a while. See the example in the Serial Monitor below.

Output of SGP30 during warm-up phase
Output of SGP30 during warm-up phase

After that you should start to see small fluctuations in those values. Open the Serial Plotter and give the sensor a first test by blowing on it. You should see a slight bump in the TVOC and eCO2 readings.

Output of SGP30 after blowing on it
Output of SGP30 after blowing on it

To see some real affect get a gas lighter and let some gas out. Don’t ignite the lighter! You will see a massive increase in TVOC and eCO2 values that lingers on for quite a while. The screenshot below shows how that looked like for me:

Output of SGP30 after reaction to gas
Output of SGP30 after reaction to gas

Note that TVOC increases from 400 to 1700 and eCO2 from 0 to over 800. The readings are in parts per billion (ppb) for TVOC and in parts per million (ppm) for eCO2.

Air Quality Warning System with SG30

Healthy levels of Total Volatile Organic Compounds (TVOC) in indoor environments typically range from 0 to 200 ppb. Levels below 200 ppb are considered acceptable and indicate good indoor air quality.

For equivalent carbon dioxide (eCO2), healthy levels in indoor environments are typically below 1000 ppm. Levels between 400 to 1000 ppm are considered normal for indoor spaces with good ventilation.

Unhealthy levels of TVOC and eCO2 can vary depending on individual sensitivity and exposure duration. However, levels above 500 ppb for TVOC and above 1000 ppm for eCO2 are generally considered poor and may lead to discomfort and health issues over time.

Based on these thresholds we can create an Air Quality Warning System. Let’s add a red LED to the circuit above and change the code. If the TVOC or eCO2 values reach unhealth levels, we switch on the LED. Here is the circuit with the LED connected to pin 7, of the Arduino.

Air Quality Warning System with SG30
Air Quality Warning System with SG30

And here is the code. If the TOV reading goes above 500 or the eCO2 reading goes above 1000, we switch the LED on.

#include "Wire.h"
#include "Adafruit_SGP30.h"

const int ledPin = 7;
Adafruit_SGP30 sgp;

void setup() {
  pinMode(ledPin, OUTPUT);
  sgp.begin(); 
  delay(15000);
}

void loop() {
  if (sgp.IAQmeasure()) {
    bool isBad = sgp.TVOC > 500 || sgp.eCO2 > 1000;
    digitalWrite(ledPin, isBad ? HIGH : LOW);
  }
  delay(1000);
}

As before, you can test the sensor by blowing on it. If you blow strongly enough the red LED should light up. If you use gas or perfume fumes it definitely will light up.

Instead, or in addition to the LED you could also add a buzzer. For more information on that, have a look at the tutorial How to use the MQ-4 Methane Gas Sensor With Arduino, where we did something similar.

But, let’s go one step further and add a display, so that we have a complete Air Quality Monitoring System that shows us the current TVOC and eCO2 in addition to warning us about unhealth conditions.

Air Quality Monitoring System with SG30

The following image shows the circuit with an additional OLED display. Since the OLED display also has an I2C interface, connecting it is easy. Just connect it to the same bus in the same way as the SG30 sensor. VDD to 5V (red wire), GND to GND (black wire), SCL to SCK (yellow wire) and SDA to SDA (white wire).

Air Quality Monitoring System with SG30
Air Quality Monitoring System with SG30

If you need more details on how to connect and use an OLED display read the tutorial on How to use the MQ-2 Gas Sensor with Arduino and an OLED. Especially, identifying the I2C address of the OLED can be tricky. The OLED, I am using here, has the address 0x3C, and that’s the one you see in the code below. If your OLED has a different address, you will have to adjust the code, accordingly.

The following code is an extension of the code of our warning system. It displays the measured TVOC and eCO2 values on the OLED. It uses the Adafruit_SSD1306 library to control the OLED and you will have to install it, if you haven’t already.

#include "Wire.h"
#include "Adafruit_SGP30.h"
#include "Adafruit_SSD1306.h"

const int width = 128;
const int height = 64;
const int adr = 0x3C;
const int ledPin = 7;

Adafruit_SSD1306 oled(width, height, &Wire, -1);
Adafruit_SGP30 sgp;

void setup() {
  pinMode(ledPin, OUTPUT);
  oled.begin(SSD1306_SWITCHCAPVCC, adr);
  sgp.begin();

  oled.setTextSize(2);
  oled.setTextColor(WHITE);
  oled.clearDisplay();
  oled.print("...");
  oled.display();  
  delay(15000);
}

void loop() {
  if (sgp.IAQmeasure()) {
    bool isBad = sgp.TVOC > 500 || sgp.eCO2 > 1000;
    digitalWrite(ledPin, isBad ? HIGH : LOW);

    oled.clearDisplay();
    oled.setCursor(4, 10);
    oled.print("TVOC:"); 
    oled.println(sgp.TVOC);
    oled.setCursor(4, 40);
    oled.print("eCO2:"); 
    oled.println(sgp.eCO2);    
    oled.display();
  }
  delay(1000);
}

The code starts by including the required libraries and defining the constants. We then create the SGP30 sensor object sgp, and the OLED object oled. If your OLED display is using a different I2C address, then adr is the constant you need to change. The SGP30 sensor has a fixed I2C address of 0x58, which must not conflict with the address of the OLED!

In the setup function we set the ledPin to OUTPUT mode, and initiate the sensor object sgp, and the OLED object oled. Next we set the parameters for the OLED and print “…”, which will be shown while the sensor warms up (15 sec delay).

In the loop function, as before we read the sensor data via sgp.IAQmeasure() and if successful, we check the TVOC and eCO2 values. If they are too high, we switch on the LED. In addition we print the TVOC and eCO2 readings to the OLED.

And with that you have a nice little Air Quality Monitoring System.

Calibration of the SG30 Air Quality Sensor

If you want to accurately measure TVOC and eCO2 you would have to calibrate the sensor against a known source of these gases. Usually that is not something you can do easily at home.

However, the SG30 allows you to compensate for sensor drift and humidity to improve the consistency of sensor reading. Let’s start with the humidity compensation.

Compensate for Humidity

El Adafruit_SGP30 library provides the function setHumidity(uint32_t absolute_humidity) to tell the SGP sensor the current absolute humidity, which it internally uses to adjust its measurements to compensate for different levels of humidity.

According to the datasheet for the SGP30 the absolute humidity dv can be calculated from relative humidity RH and temperature T as follows:

Calculate absolute humidity dv
Calculate absolute humidity dv

If you have a typical temperature and humidity sensor such as the AM2320, for instance, it returns the temperature T in °C and the relative humidity RH in %. The following function uses the formula above to convert temperature and humidity readings to an absolute humidity, which you can then use to adjust the SGP30.

uint32_t getHumidity(float t, float rh) {   
  float dv = 216.7 * ((rh / 100.0) * 6.112f * exp((17.62 * t) / 
             (243.12 + t)) / (273.15 + t)); 
  return static_cast<uint32_t>(1000.0 * dv); 
}

Within the loop function, you would read the current ambient temperature and humidity via the AM2320, convert those readings to an absolute humidity and set it via setHumidity(uint32_t absolute_humidity). Here is the complete code.

#include "Wire.h"
#include "Adafruit_SGP30.h"
#include "Adafruit_Sensor.h"
#include "Adafruit_AM2320.h"

Adafruit_SGP30 sgp;
Adafruit_AM2320 am = Adafruit_AM2320();

uint32_t getHumidity(float t, float rh) {   
  float dv = 216.7 * ((rh / 100.0) * 6.112f * exp((17.62 * t) / 
            (243.12 + t)) / (273.15 + t)); 
  return static_cast<uint32_t>(1000.0 * dv); 
}

void setup() {
  Serial.begin(9600);
  sgp.begin();
}

void loop() {
  float t = am.readTemperature();
  float rh = am.readHumidity();
  sgp.setHumidity(getHumidity(t, rh));

  if (sgp.IAQmeasure()) {
    Serial.print("TVOC:");  // ppb
    Serial.println(sgp.TVOC);
    Serial.print("eCO2:");  // ppm
    Serial.println(sgp.eCO2);    
  }
  delay(1000);
}

For more information on how to use the AM2320 sensor, have a look at the tutorial: AM2320 digital temperature and humidity sensor. Instead of the AM2320, you could also the use the common DHT22. For more info, see the Use ESP32 With DHT11/ DHT22 Humidity & Temperature Sensor tutorial.

Compensating Sensor Drift and Variability

SGP30 sensors are manufactured with some variability from part to part and the MOX sensor element will slowly change over time. To compensate for these factors the SGP30 measures and allows to adjust a baseline reading.

When started, the SGP30 sensor keeps track of its reading over the first 12 hours and takes the minimum values seen for TVOC and eCO2 as a baseline. During this period the sensor should be exposed to clean/outside air to get a good baseline.

Once the baseline is established, we can read it, store in EEPROM and if the sensor restarts we can skip the 12 hour initialization phase, where the readings may be inaccurate due to a missing baseline.

The following code shows how to save and load baseline readings for TVOC and eCO2 values, using the getIAQBaseline() y setIAQBaseline() functions of the Adafruit_SGP30 library.

#include "Wire.h"
#include "Adafruit_SGP30.h"
#include "EEPROM.h"

Adafruit_SGP30 sgp;
uint16_t TVOC_base, eCO2_base;

void saveBaseline() {
  static unsigned long old_t = 0;
  unsigned long cur_t = millis();
  if (cur_t - old_t > 2*3600000) { // 2h
    old_t = cur_t;
    sgp.getIAQBaseline(&eCO2_base, &TVOC_base); 
    eeprom_write_word(0, TVOC_base);
    eeprom_write_word(2, eCO2_base);

  }  
}

void loadBaseline() {
  TVOC_base = eeprom_read_word(0);
  eCO2_base = eeprom_read_word(2);
  if (eCO2_base != 0) {
    sgp.setIAQBaseline(TVOC_base, eCO2_base);   
  }  
}

void setup() {
  Serial.begin(9600);
  sgp.begin();
  loadBaseline();
}

void loop() {
  if (sgp.IAQmeasure()) {
    Serial.print("TVOC:");  // ppb
    Serial.println(sgp.TVOC);
    Serial.print("eCO2:");  // ppm
    Serial.println(sgp.eCO2);    
  }  
  saveBaseline();
  delay(1000);
}

We take a baseline reading every 2 hours and save it in EEPROM but only set the baseline at a restart by loading the stored baseline values from EEPROM. A more detailed discussion of the reasons and process can be found here and here, and of course in the datasheet.

Conclusions

The SGP30 is a sensitive sensor that is very easy to use and connect. I used the Adafruit_SGP30 library here, but you can also try the SparkFun_SGP30_Arduino_Library or the SGP30 library by Rob Tillaart. They all work the same but you might prefer a certain style.

Similarly, if you don’t want to use the tiny OLED you can go with a bigger LCD or a TFT display. Have a look at our tutorial How to use the MQ-7 Gas Sensor with an LCD display and Arduino and Interfacing 1.8-inch TFT Color Display With Arduino, for more information on that.

If you want to measure dust and pollen in addition to TVOC and eCO2 values, read our post Dust Sensor GP2Y1010AU0F with Arduino.

Otherwise, stay healthy with your SGP30 air quality monitoring system : )