A no-frills device library for SGP30 sensors.
Some features:
- Portable!
- Written in C to be compatible with C++
- Microcontroller agnostic
- Maybe Linux kernel ready? (not tested)
- No asserts, errors should be recoverable
What is missing that may be nice to have:
- A logging interface
- Unit tests
- Hardware verification
The SGP30 (or GY-SGP30) is an I2C gas sensor for indoor industrial applications, that measures and reports CO2eq and Total Volatile Organic Compounds (TVOC) in the air.
The SGP30 is a digital multi-pixel gas sensor designed for easy integration into air purifier, demand-controlled ventilation, and IoT applications.
The Mouser Electronics datasheet is pretty short and describes the internals and operative basics of the device.
This is a very simple C library meant to be hardware agnostic and compatible with C++ and Arduino. It implements interfaces for the SGP30 but does not presume to automate configuration or handle background/asynchronous communication.
Note: This code does not take humidity compensation into account. Measurements will be made assuming the default humidity compensation value documented in the datasheet.
Restarting the sensor (power-on or soft reset) or sending a value of 0x0000 (= 0 g/m3) sets the humidity value used for compensation to its default value (0x0B92 = 11.> 57 g/m3) until a new humidity value is sent.
Users must provide read, write, and delay functions for the hardware they want to integrate this sensor code with.
Typical usage will follow the following flow:
- Write functions for hardware to handle I2C communication
- Create
Sgp30Dev
instance usingsgp30_alloc()
- Set read/write/delay_us functions using instance setters
- I2C master sends a measure command
- Wait either the expected execution time and poll the data, or wait until the execution times out
- I2C master reads measurement
Sgp30Dev
is freed usingsgp30_free()
In the future, Arduino and Furi (Flipper Zero) library interfaces may be provided.
Examples coming soon.
#include <sgp30.h>
#include <sgp30_defs.h>
#include <assert.h>
#include <Wire.h>
Sgp30Dev* dev;
SGP30_INTF_RET_TYPE device_read(uint8_t reg, uint8_t* data, uint32_t data_len, void* intf_ptr);
SGP30_INTF_RET_TYPE device_write(uint8_t reg, uint8_t* command, uint32_t data_len, void* intf_ptr);
void device_delay_us(uint32_t period, void* intf_ptr);
Arduino uses a library called Wire
that provides an interface for I2C communication.
We will set the read and write addresses to SGP30_I2C_ADDR
and the Wire
library will do the rest.
Import headers, declare a global device pointer dev
and prototype read/write/delay functions.
In setup()
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
delay(2000);
Wire.begin(dev->i2c_addr);
dev = sgp30_alloc();
sgp30_set_read_fptr(dev, device_read);
sgp30_set_write_fptr(dev, device_write);
sgp30_set_delay_us_fptr(dev, device_delay_us);
sgp30_set_i2c_addr_read(dev, SGP30_I2C_ADDR);
sgp30_set_i2c_addr_write(dev, SGP30_I2C_ADDR);
Initialize Wire communication and allocate a device.
Next we set the read/write/delay function pointers, and the I2C read and write addresses that work with the Wire
library.
uint8_t status = sgp30_read_chip_id(dev);
Initiate a read test
status = sgp30_measure_test(dev);
Initiate a measure test, and check the return code.
status = sgp30_init(dev);
Initialize the sensor. Setup is complete.
The main function, loop()
void loop() {
// put your main code here, to run repeatedly:
Sgp30DevReadings readings = { 0 };
int8_t status = sgp30_measure_air_quality(dev, &readings);
... HANDLE YOUR ERRORS ...
char out_buf[32];
snprintf(out_buf,
sizeof(out_buf),
"co_eq: %u, tvoc: %u",
readings.co2,
readings.tvoc);
Serial.println(out_buf);
delay(1000);
}
- Pass the
dev
pointer and aSgp30DevReadings
pointer tosgp30_measure_air_quality
. The results are stored in the readings object. - Access the measurements in
readings.co2
- CO2_eqreadings.tvoc
- Total volatile organic compounds
- wait 1 second!
The readings must be done in a frequency of 1Hz to maintain calibration (calibration settings support coming soon).
SGP30_INTF_RET_TYPE device_read(uint8_t reg, uint8_t* data, uint32_t data_len, void* intf_ptr)
{
assert(intf_ptr != nullptr);
Sgp30Dev* dev = (Sgp30Dev*)intf_ptr;
Wire.requestFrom(reg, data_len);
uint32_t num_read = 0;
for (uint32_t i = 0; i < data_len; i++) {
data[i] = Wire.read();
num_read++;
}
if (num_read != data_len) {
Serial.println("failed to read correct number of bytes");
return SGP30_E_READ;
}
return SGP30_OK;
}
SGP30_INTF_RET_TYPE device_write(uint8_t reg, uint8_t* command, uint32_t data_len, void* intf_ptr)
{
assert(command != nullptr);
assert(intf_ptr != nullptr);
Sgp30Dev* dev = (Sgp30Dev*)intf_ptr;
Wire.beginTransmission(reg);
for (uint32_t i = 0; i < data_len; i++) {
Wire.write((uint8_t)command[i]);
}
uint8_t status = Wire.endTransmission();
if (status != 0) {
Serial.print("failed to write: ");
Serial.println(status);
status = SGP30_E_WRITE;
}
return status;
}
void device_delay_us(uint32_t period, void* intf_ptr)
{
(void)intf_ptr;
char out_buf[16] = { 0 };
period /= 1000;
if (period < 1) {
period = 1;
}
delay(period);
}
Coming soon