Once its loose four-pin header is soldered on, the GY-SGP30 air quality sensor wires to an Arduino Uno or ESP32 with four jumpers β VIN, GND and the two I2C lines to A4/A5 or GPIO21/22 β and the Adafruit_SGP30 library reads TVOC and eCO2 from address 0x58. The numbers need interpreting, though: the first 15 seconds are fixed at 400 ppm and 0 ppb by design, the first 12 hours are the sensor learning what clean air looks like, and eCO2 is not measured CO2.
What does the SGP30 actually measure β and is eCO2 real CO2?
The SGP30 is a metal-oxide (MOX) gas sensor: a micro-hotplate keeps a tiny metal-oxide film very hot. In clean air, oxygen settles on that hot surface and traps electrons from the film, which pushes its electrical resistance up; a reducing gas such as hydrogen, alcohol or most VOCs reacts with that trapped oxygen, the electrons flow back, and the resistance falls. The chip watches that effect on two channels β a hydrogen (H2) signal and an ethanol signal β and an on-chip algorithm converts them into the two numbers you read over I2C: TVOC (total volatile organic compounds, 0β60,000 ppb) and eCO2 (400β60,000 ppm), per the Sensirion SGP30 datasheet. The heater is also why this sensor draws about 48 mA while measuring β more than almost any other I2C breakout, and perfectly fine from an Uno or ESP32 power pin.
The “e” in eCO2 does a lot of work. Nothing inside this sensor measures carbon dioxide β CO2 is already fully oxidised, so it cannot react with the trapped oxygen, frees no electrons and leaves the film’s resistance untouched. The SGP30 estimates a CO2-equivalent from its hydrogen channel: exhaled breath carries hydrogen from digestion, and occupied rooms fill with VOCs, so in everyday indoor life those signals usually rise and fall together with real CO2. That makes eCO2 a good answer to “are people and their activities loading this room’s air?” β and the wrong instrument for verifying ventilation rates or CO2 safety, because there the correlation is exactly the thing you would need to test. Counting actual CO2 molecules takes an optical NDIR sensor like the SCD30 in our ESP32 air quality monitor build.
So read both outputs as trend indicators, not lab measurements. Uncap a permanent marker next to the sensor and TVOC jumps within seconds; open a window and both numbers glide down.
Parts list: SGP30 air quality monitor
Parts list
Prefer WiFi dashboards? The ESP32 swaps in for the Uno β its USB-C port needs a data cable, not a charge-only one. No soldering iron? The 5-pin soldering service covers the SGP30's four header joints before it ships. The DHT22 feeds the humidity compensation explained below, and the OLED shows readings without a laptop.
How do you wire the GY-SGP30 to an Arduino Uno or ESP32?
The GY-SGP30 module arrives with its four-pin header as a loose strip β the plated holes marked VIN, GND, SCL and SDA on the back are bare, so the first step is four solder joints. Our header-soldering guide walks through it with a reel of solder; if you’d rather not, tick the soldering service in the optional row and the header comes soldered.
Header on, the build is four wires. Seat the module in the breadboard and run flexible male-to-male dupont jumpers to the Uno β flexible is the operative word, because the rigid U-shaped wires from a breadboard jumper kit only bridge holes within a breadboard and cannot reach the Uno’s female headers. With the header soldered you can even skip the breadboard: male-to-female leads run the module straight to the Uno. The ESP32 needs these leads too, because its pins are male: male end in the breadboard beside the module, female end onto the ESP32 pin.
| GY-SGP30 pin | Arduino Uno | ESP32 (30-pin DevKit) |
|---|---|---|
| VIN | 5V | 3V3 |
| GND | GND | GND |
| SCL | A5 (or the SCL pin by the USB corner) | GPIO22 |
| SDA | A4 (or the SDA pin beside it) | GPIO21 |
Both supply voltages are correct, and here is why: the Sensirion chip itself runs on a 1.8V-class rail β 1.62β1.98V, with an absolute maximum of 2.16V β so the breakout carries its own regulator to make that rail from whatever reaches VIN, plus level-shifting on SDA and SCL so the 1.8V-only chip can talk to 5V or 3.3V logic. That is the same architecture Adafruit and SparkFun document for their SGP30 boards, and it is why VIN accepts anything from 2V to 5V. It also means VIN sets the logic level: the SDA and SCL pull-ups sit on the VIN side of the level shifter (the only supply on the module above 1.8V), so your board’s I2C pins see whatever feeds VIN. One trap on the ESP32: take 3V3, not the DevKit’s own pin labelled VIN β that pin carries the 5V from USB, which would put 5V on ESP32 pins that are not 5V-tolerant (Adafruit documents the same 10k pull-ups to Vin on its SGP30 board).
Which library and sketch give the first TVOC and eCO2 reading?
The SGP30’s standard Arduino driver is the Adafruit_SGP30 library: in the IDE open Tools β Manage Libraries, search “Adafruit SGP30” and install it together with the Adafruit BusIO helper it asks for. The same sketch runs unchanged on the ESP32 β Wire defaults to GPIO21/22 there β just pick the right board, and upload the ESP32 through a data-capable USB-C cable.
#include <Wire.h>
#include "Adafruit_SGP30.h"
Adafruit_SGP30 sgp;
unsigned long lastBaselineReport = 0;
void setup() {
Serial.begin(115200);
while (!Serial) { delay(10); }
if (!sgp.begin()) {
Serial.println("SGP30 not found - check VIN, GND, SCL, SDA and the header joints");
while (1) { delay(10); }
}
Serial.print("SGP30 found, serial #");
Serial.print(sgp.serialnumber[0], HEX);
Serial.print(sgp.serialnumber[1], HEX);
Serial.println(sgp.serialnumber[2], HEX);
// Saved a baseline less than a week ago? Restore it here with YOUR
// two numbers from the BASELINE lines below, then skip the 12 h wait:
// sgp.setIAQBaseline(0x8E68, 0x8F41);
}
void loop() {
if (!sgp.IAQmeasure()) {
Serial.println("Measurement failed");
delay(1000);
return;
}
Serial.print("eCO2: ");
Serial.print(sgp.eCO2);
Serial.print(" ppm TVOC: ");
Serial.print(sgp.TVOC);
Serial.println(" ppb");
// Once a minute, print the baseline pair so you can note it down
if (millis() - lastBaselineReport > 60000UL) {
lastBaselineReport = millis();
uint16_t eco2_base, tvoc_base;
if (sgp.getIAQBaseline(&eco2_base, &tvoc_base)) {
Serial.print("BASELINE eCO2: 0x");
Serial.print(eco2_base, HEX);
Serial.print(" TVOC: 0x");
Serial.println(tvoc_base, HEX);
}
}
delay(1000); // the baseline algorithm expects one reading per second
}
Open the Serial Monitor at 115200 baud. The serial-number line is your wiring proof β it only prints if the Uno found something answering at I2C address 0x58. Then come the readings, one per second: exactly 400 ppm and 0 ppb, for about fifteen readings. That is not a fault. After its init command the SGP30 spends 15 seconds heating the film and stabilising, and the datasheet has it return those fixed floor values until it is ready. After that the numbers go live β breathe at the sensor and watch both jump.
Why do the numbers hover near 400 and 0 β and what is the baseline?
The SGP30’s outputs are relative by construction. A MOX film’s resistance drifts with temperature, humidity and age, so an absolute resistance reading means nothing by itself. Instead the chip runs a dynamic baseline-compensation algorithm: it continuously tracks the cleanest air it has encountered, defines that as its reference β 400 ppm eCO2, 0 ppb TVOC, the floor of each scale β and reports everything else as elevation above that reference.
That reference is real air, not a guess. Per Sensirion’s gas sensor FAQ, the algorithm anchors to the hydrogen that clean outdoor air always carries, a steady background of about half a part per million, so the sensor needs about 10 minutes of genuinely clean air every week or two. A closed aircon room that is never aired out never shows it that anchor: its stuffy normal gets learned as clean, and later readings come out too low.
The reference also moves slowly on purpose, so one smelly hour cannot become the new normal; that is why a trustworthy one takes about 12 hours of powered operation. Open a window for ten minutes during that first run, and treat day-one absolute numbers as provisional. The sensor only measures when asked, and the algorithm expects those requests once a second, so the sketch’s delay(1000) is load-bearing.
Power off and the learned reference is gone β unless you save it. getIAQBaseline() reads the algorithm’s state as two 16-bit words, and the sketch prints that pair once a minute. Open the Serial Monitor once and leave it open, laptop awake: opening it restarts an Uno, the restart re-runs begin(), and that resets the SGP30’s baseline, so the 12 hours start over. After the first 12 hours, note a pair down; from then on, uncomment the setIAQBaseline() line with your numbers and the sensor resumes calibrated instead of re-learning from scratch. Two rules come with it, per Adafruit’s SGP30 guide: refresh your saved pair regularly (permanent installs store it to EEPROM or flash every hour), and treat a saved baseline as expired once the sensor has been switched off for more than about a week β seven days is the stated maximum β and let it re-learn.

How do you stop Malaysian humidity from skewing the readings?
Water vapour takes part in the same surface chemistry on the SGP30’s hot film that gases do, so humidity shifts the raw signals exactly like a gas would. The chip can compensate for this on-chip, but only once you send it a humidity figure: the datasheet states that every power-up or soft reset leaves humidity compensation switched off until a value arrives. Malaysian indoor air at 30Β°C and 75% RH holds about 23 g/mΒ³ β nearly double the 11.8 g/mΒ³ of the datasheet’s own 25Β°C, 50% RH worked example β and it swings with every rain shower and every aircon cycle. Uncorrected, those swings print as phantom air-quality changes. One hard limit while you are at it: the datasheet says the sensor must never be exposed to condensing air (above 90% RH), so keep it out of bathrooms and other spots where moisture condenses.
The fix is to tell the sensor the real figure. A DHT22 supplies temperature and relative humidity; the sketch converts them to absolute humidity and hands the result to setHumidity(), which takes milligrams per cubic metre. Absolute, not relative, because the hot film responds to how many water molecules arrive, not to how close the room is to saturation: 75% RH at 30Β°C carries about half again the water of 75% RH at 22Β°C.
Seat the DHT22 board’s pins in the breadboard and move your board’s single 5V (Uno) or 3V3 (ESP32) feed and GND onto the breadboard rails so both sensors share them. Then DHT22 + to the supply rail, β to GND, and out to D2, or GPIO4 on the ESP32. Avoid GPIO2 there: it is a boot pin, and the DHT22’s data line idles high, which can block uploads. Install Adafruit’s DHT sensor library from the same Library Manager (accept the Adafruit Unified Sensor helper it asks for), then add these three pieces to the sketch above:
// 1) At the top, below #include "Adafruit_SGP30.h":
#include "DHT.h"
#define DHTPIN 2 // DHT22 data pin (ESP32: use 4, not 2 or input-only 34-39)
DHT dht(DHTPIN, DHT22);
// Absolute humidity in mg/m3 from temperature (C) and relative humidity (%)
uint32_t absoluteHumidity(float t, float rh) {
float ah = 216.7f * ((rh / 100.0f) * 6.112f *
expf((17.62f * t) / (243.12f + t)) / (273.15f + t)); // g/m3
return (uint32_t)(1000.0f * ah); // mg/m3
}
// 2) In setup(), right after Serial.begin(115200):
// dht.begin();
// 3) In loop(), as the first lines, before sgp.IAQmeasure():
float t = dht.readTemperature();
float rh = dht.readHumidity();
if (!isnan(t) && !isnan(rh)) {
sgp.setHumidity(absoluteHumidity(t, rh));
}
A DHT11 does the same job on a tighter budget with coarser humidity steps β the trade-offs are in DHT11 vs DHT22.

Common mistakes we see
Declaring the sensor faulty because it reads exactly 400 and 0. For the first 15 seconds those values are hard-coded init output, and after that they are simply what clean air reads β the floor of each scale. The real health check is response: breathe on the sensor or uncap a marker nearby. If the numbers jump, nothing is broken.
Matching VIN to VIN. The ESP32 DevKit’s own VIN pin carries about 5V from USB; wire the module there and SDA and SCL get pulled to 5V on GPIO pins that are not 5V-tolerant. Go by the table, not the labels: 5V on the Uno, 3V3 on the ESP32.
Using eCO2 to judge ventilation. A room can read a reassuring eCO2 while real CO2 climbs: the SGP30 infers its number from hydrogen and VOC signals rather than measuring CO2, and in a room that is never aired out, its baseline learns the stuffy air as 400. Trend-watching occupancy: yes. Certifying a classroom’s ventilation: an NDIR sensor’s job.
Wiring the OLED by position instead of by label. On the 0.96in OLED the first header pin is GND β the opposite of many modules, where VCC comes first. Plugging it in “the same way as last time” swaps power and ground, which can kill the display. The OLED (0x3C) and this sensor (0x58) share one I2C bus happily; the display-side wiring and code are in the DHT11 + OLED monitor guide.
A charge-only USB-C cable on the ESP32. The board powers up, blinks, and never appears as a port, because the cable physically lacks data wires. Use a cable sold for data, and if the port still refuses to show, work through ESP32 not detected.
FAQ
Is the SGP30’s eCO2 reading real CO2?
No. The SGP30 estimates a CO2-equivalent from its hydrogen signal, relying on the fact that breath and indoor activity release hydrogen and VOCs roughly in step with CO2. It tracks occupancy trends well, but it cannot verify ventilation or CO2 safety β those need an NDIR sensor such as the SCD30, which measures CO2 molecules optically.
How long does the SGP30 take to give accurate readings?
Three timescales: 15 seconds of fixed 400 ppm / 0 ppb init output, usable trends within minutes, and about 12 hours of powered operation before the on-chip baseline makes the absolute numbers meaningful. Save the baseline pair afterwards and restore it at startup β it is valid for about a week β and the sensor comes back calibrated instead of starting over. Give it about ten minutes of fresh air every week or two so that reference stays honest.
Can I connect two SGP30 sensors to one Arduino?
Not on the same bus: the I2C address is fixed at 0x58 with no address jumper, and two devices sharing an address collide. Run the second sensor on an I2C multiplexer, or on the ESP32’s second I2C bus via Wire1, or give each sensor its own board.
Does the SGP30 detect haze, smoke or PM2.5?
It reacts to the gases in smoke, so smoke can lift its TVOC reading, but it cannot measure haze or PM2.5: haze is judged by fine particles, and a gas sensor cannot count particles. Particulate readings need an optical PM2.5 sensor like the one in our haze monitor build; for smoke and flammable-gas alarm projects, the MQ-2 is the usual choice, as a learning build, never a replacement for a certified smoke alarm. The SGP30’s territory is VOCs: solvents, sprays, off-gassing, people.
Can the SGP30 run on a 5V Arduino Uno?
The GY-SGP30 module can, because its onboard regulator drops VIN to the Sensirion chip’s own 1.62β1.98V rail and its level-shifters translate SDA and SCL. Feed VIN anything from 2V to 5V, matched to your board’s logic voltage because the module’s I2C pull-ups run from VIN: the Uno’s 5V pin, or the ESP32’s 3V3 pin, never the ESP32’s 5V VIN pin. Only a bare SGP30 chip without the breakout is strictly a 1.8V part.
Last updated November 2026. Stuck? Chat with us on WhatsApp.



GY-SGP30 AIR QUALITY SENSOR MODULE TVOC ECO2 SGP30 BREAKOUT I2C FOR ARDUINO
Arduino Uno Compatible SMD UNO R3 with Type B Cable - ATMEGA328P with CH340G-Microcontroller Project
MB102 Breadboard 170 400 830 Holes Breadboard Donut Board Arduino Prototype Multi Color - BREADBOARD (400 HOLES)
40pcs Dupont Wire 10cm 20cm 30cm for Breadboard DIY Experiment Jumper Wire Breadboard wire - DUPONT WIRE M-M 20CM
NodeMCU ESP32 Wi-Fi + Bluetooth Development Board CH340/CP2012 - For IOT Project - ESP-32 (CH340)
Data Cable Type-A Type-C MicroUSB Type-B 0.5m 1m 30cm 0.3m 100cm Data Transfer Upload Code - TYPE-A TO TYPE-C CABLE (1.0M)
Soldering Service for Circuit Boards & Electronics - 5 Pins Soldering Service
DHT 11 DHT 22 Temperature and Humidity Sensor DHT22 High Sensitivity Sensor DHT11 For Aduino IOT - DHT22 SENSOR
0.96β OLED Display Module Blue White Screen I2C IIC Serial 128X64 LCD Display Board - 0.96' OLED BLUE