sgp30.tc¶
SGP30 VOC/eCO2 Sensor Driver
// SGP30 VOC/eCO2 Sensor Driver
// I2C address: 0x58
// Measures: eCO2 (ppm), TVOC (ppb)
// CRC8: poly 0x31, init 0xFF (Sensirion)
// Must measure every second for IAQ baseline algorithm
int sgp_addr = 0;
int sgp_bus = 0;
int sgp_ok = 0;
int sgp_eco2 = 0;
int sgp_tvoc = 0;
int sgp_tick = 0;
char sgp_buf[10];
char sgp_lbl[32];
// CRC8 Sensirion: poly 0x31, init 0xFF, over 2 data bytes
int sgp_crc(int b1, int b2) {
int crc = 0xFF ^ b1;
int j = 0;
while (j < 8) {
if (crc & 0x80) crc = ((crc << 1) ^ 0x31) & 0xFF;
else crc = (crc << 1) & 0xFF;
j++;
}
crc = crc ^ b2;
j = 0;
while (j < 8) {
if (crc & 0x80) crc = ((crc << 1) ^ 0x31) & 0xFF;
else crc = (crc << 1) & 0xFF;
j++;
}
return crc;
}
// Send 16-bit I2C command
void sgp_cmd(int cmd) {
sgp_buf[0] = cmd & 0xFF;
i2cWrite(sgp_addr, cmd >> 8, sgp_buf, 1, sgp_bus);
}
int sgp_scan() {
int bus = 0;
while (bus < 2) {
if (i2cSetDevice(0x58, bus)) {
sgp_addr = 0x58;
sgp_bus = bus;
// Get serial number to verify sensor
sgp_buf[0] = 0x82;
i2cWrite(sgp_addr, 0x36, sgp_buf, 1, sgp_bus);
delay(10);
if (i2cRead0(sgp_addr, sgp_buf, 9, sgp_bus)) {
// Verify first word CRC
if (sgp_crc(sgp_buf[0], sgp_buf[1]) == sgp_buf[2]) {
// Init IAQ algorithm
sgp_cmd(0x2003);
delay(10);
i2cSetActiveFound(sgp_addr, "SGP30", sgp_bus);
return 1;
}
}
sgp_addr = 0;
}
bus++;
}
return 0;
}
void EverySecond() {
if (!sgp_addr) return;
if (sgp_tick == 0) {
// First call: send IAQ Measure command
sgp_cmd(0x2008);
sgp_tick = 1;
} else {
// Read response from previous command (>12ms ago — well exceeded)
if (i2cRead0(sgp_addr, sgp_buf, 6, sgp_bus)) {
if (sgp_crc(sgp_buf[0], sgp_buf[1]) == sgp_buf[2] &&
sgp_crc(sgp_buf[3], sgp_buf[4]) == sgp_buf[5]) {
sgp_eco2 = (sgp_buf[0] << 8) | sgp_buf[1];
sgp_tvoc = (sgp_buf[3] << 8) | sgp_buf[4];
sgp_ok = 1;
}
}
// Send next command immediately
sgp_cmd(0x2008);
}
}
void WebCall() {
char buf[48];
if (sgp_ok) {
LGetString(5, sgp_lbl); // eCO2
strcpy(buf, "{s}SGP30 ");
strcat(buf, sgp_lbl);
strcat(buf, "{m}");
webSend(buf);
sprintf(buf, "%d ppm{e}", sgp_eco2);
webSend(buf);
LGetString(6, sgp_lbl); // TVOC
strcpy(buf, "{s}SGP30 ");
strcat(buf, sgp_lbl);
strcat(buf, "{m}");
webSend(buf);
sprintf(buf, "%d ppb{e}", sgp_tvoc);
webSend(buf);
} else {
webSend("{s}SGP30{m}not ready{e}");
}
}
void JsonCall() {
if (!sgp_ok) return;
char buf[64];
sprintf(buf, ",\"SGP30\":{\"eCO2\":%d", sgp_eco2);
responseAppend(buf);
sprintf(buf, ",\"TVOC\":%d}", sgp_tvoc);
responseAppend(buf);
}
void OnExit() {
if (sgp_addr) {
I2cResetActive(sgp_addr, sgp_bus);
}
}
int main() {
sgp_ok = 0;
sgp_addr = 0;
sgp_tick = 0;
if (sgp_scan()) {
char buf[48];
sprintf(buf, "SGP30 found at 0x%x on bus %d", sgp_addr, sgp_bus);
addLog(buf);
} else {
addLog("SGP30 not found");
}
return 0;
}