Air quality monitor on an Arduino Uno
An SGP30 and an SHT31 on one I2C bus with a four-digit LED display: CO2 in ppm, then temperature and humidity, and a red LED above 1000 ppm. The whole build, an Arduino Uno and 5 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.
IntermediateRuns in your browser. Free, and no account needed.
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Air quality monitor: an SGP30 gas sensor, an SHT31 thermometer and an
// HT16K33 four-digit LED display share the I2C bus, the way a home-built CO2
// monitor is made.
//
// SGP30, SHT31, display SDA -> A4 SCL -> A5 VIN or VCC -> 5 V GND -> GND
// red LED pin 8 through 220 ohms
//
// The SGP30 reads 400 ppm and 0 ppb for its first 15 seconds while its hot
// plate warms up, so the display counts that down after "--". Then it takes
// turns: the CO2eq in ppm for four seconds, the temperature ("23*C") and the
// humidity ("45rh") for one each. Above 1000 ppm, where a room wants a
// window opening, the red LED comes on and the display blinks.
//
// The SHT31's humidity makes the SGP30 more accurate: the sensor wants it as
// milligrams of water in a cubic meter of air, which is worked out here from
// a table of how much water air can hold, because this board has no exp().
//
// Drag the sliders on the two sensors and watch the display follow.
#include <Wire.h>
#include "Adafruit_LEDBackpack.h"
#include "Adafruit_SHT31.h"
#include "Adafruit_SGP30.h"
const int ALERT_LED = 8;
const uint16_t ALERT_PPM = 1000;
// Segments for the units: A is bit 0, G is bit 6.
const uint8_t DEGREE = 0x63;
const uint8_t LETTER_C = 0x39;
const uint8_t LETTER_R = 0x50;
const uint8_t LETTER_H = 0x74;
Adafruit_7segment display = Adafruit_7segment();
Adafruit_SHT31 sht31 = Adafruit_SHT31();
Adafruit_SGP30 sgp;
// How much water saturated air holds, in mg per cubic meter, at 0, 5, 10 ...
// 50 degrees Celsius.
const uint32_t SATURATION[] = {4847, 6797, 9399, 12830, 17300, 23050,
30380, 39630, 51120, 65330, 82770};
uint32_t absoluteHumidity(int32_t centiC, int32_t centiRh) {
if (centiC < 0) centiC = 0;
if (centiC > 4999) centiC = 4999;
uint8_t i = centiC / 500;
uint32_t frac = centiC % 500;
uint32_t sat = SATURATION[i] + (SATURATION[i + 1] - SATURATION[i]) * frac / 500;
return sat * (uint32_t)centiRh / 10000;
}
// Two digits on the left, two unit segments on the right.
void showWithUnit(int32_t value, uint8_t left, uint8_t right) {
display.print(value * 100, DEC);
display.writeDigitRaw(3, left);
display.writeDigitRaw(4, right);
}
unsigned long started;
uint8_t tick = 0;
bool alarm = false;
void setup() {
pinMode(ALERT_LED, OUTPUT);
display.begin(0x70);
display.setBrightness(10);
if (!sht31.begin(0x44) || !sgp.begin()) {
// "Err": a sensor did not answer.
display.writeDigitRaw(0, 0x79);
display.writeDigitRaw(1, LETTER_R);
display.writeDigitRaw(3, LETTER_R);
display.writeDisplay();
while (1) delay(10);
}
started = millis();
}
void loop() {
bool climate = sht31.measure();
int32_t centiC = sht31.getTemperatureCentis();
int32_t centiRh = sht31.getHumidityCentis();
if (climate) sgp.setHumidity(absoluteHumidity(centiC, centiRh));
if (!sgp.IAQmeasure()) {
delay(1000);
return;
}
unsigned long seconds = (millis() - started) / 1000;
tick = (tick + 1) % 6;
if (seconds < 15) {
display.print(15 - seconds, DEC);
display.writeDigitRaw(0, 0x40);
display.writeDigitRaw(1, 0x40);
} else if (tick == 4) {
showWithUnit((centiC + 50) / 100, DEGREE, LETTER_C);
} else if (tick == 5) {
showWithUnit((centiRh + 50) / 100, LETTER_R, LETTER_H);
} else {
display.print(sgp.eCO2, DEC);
}
display.writeDisplay();
bool high = seconds >= 15 && sgp.eCO2 >= ALERT_PPM;
if (high != alarm) {
alarm = high;
display.blinkRate(alarm ? HT16K33_BLINK_1HZ : HT16K33_BLINK_OFF);
}
digitalWrite(ALERT_LED, alarm ? HIGH : LOW);
// One measurement a second, as the SGP30's baseline algorithm wants.
delay(1000 - 70);
}
Parts list
7 parts, plus the jumper wires. Every one is in the editor's parts bin.
How it is wired
6 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.
- Arduino Uno R3 pin 8; Resistor, 220 Ω pin 1
- Arduino Uno R3 pin 5V; Temperature and humidity, SHT31 pin VIN; Air quality sensor, SGP30 pin VIN; Four-digit display, HT16K33 backpack pin VCC
- Ground: Arduino Uno R3 pin GND; Temperature and humidity, SHT31 pin GND; Air quality sensor, SGP30 pin GND; Four-digit display, HT16K33 backpack pin GND; LED, red pin C
- Arduino Uno R3 pin A4; Temperature and humidity, SHT31 pin SDA; Air quality sensor, SGP30 pin SDA; Four-digit display, HT16K33 backpack pin SDA
- Arduino Uno R3 pin A5; Temperature and humidity, SHT31 pin SCL; Air quality sensor, SGP30 pin SCL; Four-digit display, HT16K33 backpack pin SCL
- Resistor, 220 Ω pin 2; LED, red pin A
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.