Sensirion SGP30 and Bosch BME280 air sensors (WHL #64)
Small air sensor modules for today!
The Bosch BME280 has been in use on my ESP modules across the apartment since I discarded the unreliable DHT sensors, DHT22 in my case. It’s a combined temperature-pressure-humidity sensor, contrary to the much cheaper BMP280 or the predecessor BMP180, which lack the humidity part (pretty much vital indoors, while pressure is just a gimmick). They are sold in many similar versions, I’ve always picked the GY-BME280 module in 3.3V flavour (“GYBMEP”), as the sensor can be operated from 1.71V to 3.6V which is handy for different MCUs. They have been sold for 2.50€ to 3.00€ including shipping on AliExpress since at least late 2017 with prices slowly rising due to increased shipping cost.
Now I’ve added something to one of the boards, a “GY-SGP” module using a Sensirion SGP30 sensor. It offers a TVOC (total volatile organic compounds) readout and an estimated CO2 value based on measurements of hydrogen content in the air, the latter being useful for indoors with people and animals (sometimes people are animals) producing CO2, while it would not detect high concentrations from synthetic sources. The SGP30 is technically not recommended for new designs in favour of a SGP40 model, and I’m a bit puzzled why they would phase out the SGP30. The SGP40 is not a strictly better sensor, e.g. it does lose the H2 readout. Bosch, on the other hand, does still sell a lot of similar sensors and doesn’t just cancel the BME280 because a BME680 can do the same things plus a little VOC voodoo. Sometimes a less accurate sensor will do the job, or one that trades power consumption (µA!) or tighter voltage supply requirements for unit price.
Anyway, the GY-SGP module was 5.39€ including shipping in January 2021. It does benefit from a separate humidity reading to compensate sensor drift, so the more expensive BME680 that does all of these things in one chip would be favourable for new setups. As I just amended an existing unit, it didn’t really matter, plus it would be interesting to compare the two sensors.
Adafruit kindly offers libraries for all these sensors that can be conveniently installed via the Arduino package manager. I don’t know the exact protocol details on each sensor but I wonder why there is no unified library per manufacturer – just double check that you got the correct library in case your unit doesn’t work straight away. Dumb me didn’t bridge the 3.3V power selector on mine, so the SGP was supplied over the I2C connection only and it did read the serial number (cool trick!), but couldn’t operate properly
The BME280 library also supports the BMP280, but BMP180 and BME680 need separate libraries. SGP30 and SGP40 do as well.
The SGP needs a warm-up/burn-in phase of 12 hours (!) and also is prone to drift if reset afterwards without getting a proper baseline first. It is said to be long-term stable once that is done, but it’s way too soon to check that now. So I set up a mix of both BME280 and SGP30 Arduino examples that feeds the relative humidity data into the absolute humidity converter for the SGP, set it outside, and let it run for a while. And I did a longer run with a second machine while writing this very blog post and preparing the graphs. Before discussing the actual run, here’s the gist of the sample run shown below:
* The eCO2 value does not dip below 400ppm. If it flatlines at 400, you’re either outside or your sensor has not been calibrated properly. On the other hand, exhaled air only makes this jump up to 2000 ppm while it should be double that (according to the interwebs)
* TVOC and eCO2 mostly correlate, but these are in fact independent measurements, see e.g. the 30 minute mark where there’s two bumps in the TVOC signal that is barely present in the eCO2 data. Interestingly, it is visible in both raw data streams, but likely eliminated by the 400ppm limit.
* Once the calibration constants are set, they’re used. While that sounds as intended, I do not know how to recalibrate a sensor after more than 7 days of non-operation as suggested by the manufacturer. When sgp.getIAQBaseline(eCO2, TVOC) is called, it only returns those cal constants, not the current value from live data. One would need a separate calibration routine to run the sensor without cal constants, plus some means of setting those values to nonvolatile memory – otherwise the source code needs to be compiled anew.
* Changes in temperature change the two sensor values more than humidity or pressure does. These values are already corrected by absolute humidity, I don’t have two sensors to run an exact comparison on that subject, though.
Here’s the run with an ESP board sitting outside undisturbed, spanning a bit over six hours in total. Somewhat protected as placed on the patio under a balcony as shown above, but not protected against wind gusts. I think it never sat in direct sunlight either – the temperature graph supports that. The notable movements at the end are from a series of environmental changes:
a) bringing the unit in (quickly) and spending another 30 minutes inside without any door or window open,
b) actually airing the room for 10 minutes, demonstrating how quickly and drastically that affects air quality, and
c) closing the door and windows again, to see how fast VOCs and CO2 content get back up to previous levels.
This set captures data from around 14:00 to 20:20 on this very Easter sunday. The unit was brought outside after running a bit indoors, hence the (very short) initial adjustments of temperature and the relatively quick shift in the raw values. While the ethanol sensor for the TVOC reading enters a pretty stable phase after around one hour of operation with only a small decline when close to sunset, the hydrogen sensor steadily moves downwards in reading value over time. I would assume a higher free-air value is better since it offers more dynamic range for measurement, so I’m not sure if for some reason this is normal outside, or if this is still sensor warm-up or even initial degradation.
As for the calibration constants, the TVOC value is almost rock-solid after two hours at 37655 counts (0x9317) and only moves up or down by one count over hours, even after bringing it back inside. So I’d consider this stable and will hard code this value. The equivalent CO2 value however…not sure how to deal with that. I bet there are CO2 fluctuations over the span of a day (, month, year), but how would I ever find a steady cal value without some kind of standardized atmosphere? Moreover, since this sensor could end up in consumer gear that is never operated outside (especially for 12 hours), how would a manufacturer calibrate these things in a factory environment?
I guess the latter will require additional test runs, especially since it is unknown how a fixed cal value will interact with the reading itself. If it’s just like a static offset or linear relationship that’s fine, but if nonlinear funky business is about to happen, I will need this sorted out before the sensor is deployed and readings are recorded for literally years to come. Not that I do require indoor CO2 concentration logging, don’t get me wrong on that – but if I do log stuff, I want accurate readings that don’t drift all over the place. In the long run, that’ll feed into the ioBroker system that will start a fan once air quality has decreased to a certain level, so erratic behaviour won’t be helpful here.
With an eyeballed value of 37000 (0x9088), I now get 550ish ppb VOC levels in the living room, two hours after closing the door, plus 800ish ppms of CO2. CO2 seems alright, VOC is pretty high and also heavily dependent on the distance to the sofa – sitting directly on top it will go up to 800ppb, while just two meters away in the restroom it’ll drop below 400ppb [field study opportunity right there ]. The killer VOC source however is the kitchen…it’s way above 1000ppb in there, and it smells of it: It’s Bratwurst!
As a cross-check comparison, my Cat S61 does have a VOC sensor as well. Basically regardless of location, it shows between 200ppb and 220ppb at home (no Bratwurst peak!), while it drops significantly below 50ppb at work, which is especially notable because a) it’s so consistent, b) that room isn’t very well ventilated at all, and c) the sensor isn’t all that slow, so it’s no integration artefact. It often only takes ten to fifteen seconds for sounding an “uuh, high VOC concentration, soo unhealthy in here” alarm in my pocket when I use isopropyl alcohol for cleaning stuff. Still, a factor of 2 to 5 between those two sensors allows me to gauge the absolute accuracy of these things. By pure coincidence, the sensor of the Cat phone is a…SGPC3 – a Sensirion SGP30 variant.
(the humidity sensor is a Sensirion SHTW2 and off by at least 10 percent relative to all of my BME280s – not been in a pocket for more than three days!)
So, that cal constant thing will likely spawn a follow-up post on this, and when it’s all figured out, I might have some days, weeks or months of data to show and some insights on accuracy and usefulness in fan control to share. Glad to report that both sensors work out of the box with the free library.
One final gimmick: This is the last seven days of data from the BME280 that is placed on top of the Raspberry Pi that runs the ioBroker system and the attached temperature/humidity database. While temperature is systematically too high due to close proximity to the ~2W CPU/RAM/WiFi package (to be fixed), it shows the general weather trend with April 1st being the hottest day so far, plus every sharp decline in humidity indicates an open front door (the window in that room doesn’t do all that much in comparison). One’s in the morning when leaving for work, and the other is twenty minutes after clocking out…