The Sensirion SGP30 Air Quality Sensor measures eCO2 (CO2 equivalent) and TVOC (Total Volatile Organic Compounds), giving a measure of the overall air quality. It’s available on an SGP30 Air Quality Sensor Breakout from Adafruit with logic-level conversion to allow it to be used with either 3.3V or 5V systems:
It will measure eCO2 (equivalent calculated carbon-dioxide) concentration within a range of 400 to 60,000 parts per million (ppm), and TVOC (Total Volatile Organic Compound) concentration within a range of 0 to 60,000 parts per billion (ppb).
The I2C address is #x58.
Utilities
The SGP30 routines use two utility functions. The function readbytes reads a specified number of bytes from the stream s and returns them as a list:
(defun readbytes (s n)
(let (lst) (dotimes (i n (reverse lst)) (push (read-byte s) lst))))
The function crc8 performs a CRC checksum on a list of bytes and returns the CRC:
(defun crc8 (data)
(let ((crc #xff))
(mapc #'(lambda (byte)
(setq crc (logxor crc byte))
(dotimes (i 8)
(if (logbitp 7 crc)
(setq crc (logxor (ash crc 1) #x31))
(setq crc (ash crc 1)))))
data)
(logand crc #xff)))
Testing the sensor
The SGP30 provides a self-test function that runs an on-chip self-test:
(defun sgp30-test ()
(with-i2c (s #x58)
(write-byte #x20 s)
(write-byte #x32 s))
(delay 220)
(with-i2c (s #x58 3)
(zerop (crc8 (readbytes s 3)))))
The function returns t if the test succeeds.
Initialising the sensor
To start the air quality measurement you need to call the initialisation function:
(defun sgp30-init ()
(with-i2c (s #x58)
(write-byte #x20 s)
(write-byte #x03 s)))
For the first 15s after the sgp30-init command the sensor is in an initialization phase during which an sgp30-measure command will return fixed values of 400 ppm eCO2 and 0 ppb TVOC.
Measuring air quality
To measure the air quality give the sgp30-measure command:
(defun sgp30-measure ()
(with-i2c (s #x58)
(write-byte #x20 s)
(write-byte #x08 s))
(delay 12)
(with-i2c (s #x58 6)
#| data is (co2h co2l crc tvoch tvocl crc) |#
(let ((data (readbytes s 6)))
(when (crc8 data)
(list (logior (ash (nth 0 data) 8) (nth 1 data))
(logior (ash (nth 3 data) 8) (nth 4 data)))))))
This returns a list of two values: the eCO2 in ppm and the TVOC in ppb. For example:
> (sgp30-measure)
(415 16)
If you breathe on the sensor you will see both values increase.
Running continuous measurements
After the sgp30-init command, an sgp30-measure command should be sent in regular intervals of 1s to ensure proper operation of the dynamic baseline compensation algorithm. This is implemented by the function sgp30-run, which prints out a pair of measurements every second:
(defun sgp30-run ()
(sgp30-init)
(loop
(print (sgp30-measure))
(delay 1000)))
Alternatively you could plot these values to provide a graphical display of air quality.