251 lines
8.2 KiB
C++
251 lines
8.2 KiB
C++
/******************************************************************************
|
|
sgp30.cpp
|
|
Code based on "Sensirion_Gas_Sensors_Datasheet_SGP30.pdf" on sensirion.com.
|
|
Code written by Simon Reap, March 17, 2021
|
|
https://github.com/simon3270/pico-pimoroni
|
|
|
|
This code is released under the [MIT License](http://opensource.org/licenses/MIT).
|
|
Please review the LICENSE file included with this example.
|
|
Distributed as-is; no warranty is given.
|
|
******************************************************************************/
|
|
|
|
#include "sgp30.hpp"
|
|
|
|
namespace pimoroni {
|
|
|
|
bool SGP30::init() {
|
|
|
|
i2c_init(i2c, 400000);
|
|
|
|
gpio_set_function(sda, GPIO_FUNC_I2C);
|
|
gpio_pull_up(sda);
|
|
gpio_set_function(scl, GPIO_FUNC_I2C);
|
|
gpio_pull_up(scl);
|
|
|
|
soft_reset();
|
|
|
|
if(!retrieve_unique_id()) {
|
|
return false;
|
|
}
|
|
|
|
// Retrieve and check Feature Set
|
|
uint16_t featureset;
|
|
if(!read_reg_1_word(GET_FEATURE_SET_VERSION, 10, &featureset))
|
|
return false;
|
|
if((featureset & 0xF0) != SGP30_REQ_FEATURES)
|
|
return false;
|
|
|
|
// Start the measurement process
|
|
// - parameter true = wait for readings to initialise
|
|
// false = return immediately
|
|
// As usual, function returns true if the request succeeded
|
|
// if (!start_measurement(true))
|
|
// return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
i2c_inst_t* SGP30::get_i2c() const {
|
|
return i2c;
|
|
}
|
|
|
|
int SGP30::get_sda() const {
|
|
return sda;
|
|
}
|
|
|
|
int SGP30::get_scl() const {
|
|
return scl;
|
|
}
|
|
|
|
// Get the unique ID from the Chip. Will fail if no chip attached
|
|
bool SGP30::retrieve_unique_id() {
|
|
// return the Chip ID, in three separate 16-bit values
|
|
return read_reg_3_words(GET_SERIAL_ID, 10, serial_number, serial_number + 1, serial_number + 2);
|
|
}
|
|
|
|
// get the previously-retreved Chip ID as the lower 48 bits of a 64-bit uint
|
|
uint64_t SGP30::get_unique_id() {
|
|
return (((uint64_t)serial_number[0]) << 32) \
|
|
+ (((uint64_t)serial_number[1]) << 16) \
|
|
+ serial_number[2];
|
|
}
|
|
|
|
// Write a soft reset - writes globally, so all devices on this
|
|
// I2C bus receive the request
|
|
bool SGP30::soft_reset() {
|
|
return write_global(SOFT_RESET, 10);
|
|
}
|
|
|
|
// Start the measurement process.
|
|
// If the parameter is true, wait for the readings to be valid
|
|
// If false, return immediately
|
|
bool SGP30::start_measurement(bool wait_for_setup) {
|
|
// First kick off the "measurement" phase
|
|
bool rc = write_reg(INIT_AIR_QUALITY, 10);
|
|
|
|
// Optionally wait up to 20 seconds for the measurement process to initiate
|
|
if(wait_for_setup) {
|
|
// It takes 15 seconds to start the measurement process but allow 20.
|
|
// Ignore the first 2 readings completely.
|
|
uint16_t eCO2, TVOC;
|
|
uint8_t sec_count = 0;
|
|
while(sec_count < 20) {
|
|
sleep_ms(988); // Will sleep last 12ms of 1sec in get_air_quality()
|
|
get_air_quality(&eCO2, &TVOC);
|
|
if((sec_count >= 2) && (eCO2 != 400 || TVOC != 0)) {
|
|
// startup process finished
|
|
break;
|
|
}
|
|
sec_count++;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
// get the air quality values - will be 400 and 0 respectively for the
|
|
// first 15 seconds after starting measurement
|
|
bool SGP30::get_air_quality(uint16_t * eCO2, uint16_t * TVOC) {
|
|
return read_reg_2_words(MEASURE_AIR_QUALITY, 12, eCO2, TVOC);
|
|
}
|
|
|
|
// Get the raw readings - not useful in real-world settings
|
|
bool SGP30::get_air_quality_raw(uint16_t * rawH2, uint16_t * rawEthanol) {
|
|
return read_reg_2_words(MEASURE_RAW_SIGNALS, 25, rawH2, rawEthanol);
|
|
}
|
|
|
|
// Get the baseline compensation values for eCO2 and VOC
|
|
bool SGP30::get_baseline(uint16_t *eco2, uint16_t *tvoc) {
|
|
return read_reg_2_words(GET_BASELINE, 10, eco2, tvoc);
|
|
}
|
|
|
|
// Write the baseline compensation values for eCO2 and VOC
|
|
void SGP30::set_baseline(uint16_t eco2, uint16_t tvoc) {
|
|
write_reg_2_words(SET_BASELINE, 10, eco2, tvoc);
|
|
}
|
|
|
|
// Set the absolute humidity, e.g. from an SHTxx chip
|
|
bool SGP30::set_humidity(uint32_t absolute_humidity) {
|
|
if(absolute_humidity > 256000) {
|
|
return false;
|
|
}
|
|
|
|
uint16_t ah_scaled = (uint16_t)(((uint64_t)absolute_humidity * 256 * 16777) >> 24);
|
|
|
|
return write_reg_1_word(SET_HUMIDITY, 10, ah_scaled);
|
|
}
|
|
|
|
// Write a single byte globally (not to a specifc I2c address)
|
|
bool SGP30::write_global(uint16_t reg, uint16_t delay_ms) {
|
|
uint8_t buffer[1] = { (uint8_t)(reg & 0xFF)};
|
|
i2c_write_blocking(i2c, 0, buffer, 1, false);
|
|
sleep_ms(delay_ms);
|
|
return true;
|
|
}
|
|
|
|
// Write just the register to the i2c address, no parameter
|
|
bool SGP30::write_reg(uint16_t reg, uint16_t delay_ms) {
|
|
uint8_t buffer[2] = { (uint8_t)((reg >> 8) & 0xFF), (uint8_t)(reg & 0xFF)};
|
|
i2c_write_blocking(i2c, address, buffer, 2, false);
|
|
sleep_ms(delay_ms);
|
|
return true;
|
|
}
|
|
|
|
// Write one 16-bit word (+CRC)
|
|
bool SGP30::write_reg_1_word(uint16_t reg, uint16_t delay_ms, uint16_t value) {
|
|
uint8_t buffer[5] = { (uint8_t)((reg >> 8) & 0xFF), (uint8_t)(reg & 0xFF),
|
|
(uint8_t)((value >> 8) & 0xFF), (uint8_t)(value & 0xFF), calculate_crc(value)};
|
|
i2c_write_blocking(i2c, address, buffer, 5, false);
|
|
sleep_ms(delay_ms);
|
|
return true;
|
|
}
|
|
|
|
// Write two 16-bit words (+CRC)
|
|
bool SGP30::write_reg_2_words(uint16_t reg, uint16_t delay_ms, uint16_t value1, uint16_t value2) {
|
|
uint8_t buffer[8] = { (uint8_t)((reg >> 8) & 0xFF), (uint8_t)(reg & 0xFF),
|
|
(uint8_t)((value1 >> 8) & 0xFF), (uint8_t)(value1 & 0xFF), calculate_crc(value1),
|
|
(uint8_t)((value2 >> 8) & 0xFF), (uint8_t)(value2 & 0xFF), calculate_crc(value2)};
|
|
i2c_write_blocking(i2c, address, buffer, 8, false);
|
|
sleep_ms(delay_ms);
|
|
return true;
|
|
}
|
|
|
|
// Write register and read one 16-bit word in response
|
|
bool SGP30::read_reg_1_word(uint16_t reg, uint16_t delay_ms, uint16_t *value) {
|
|
uint8_t regbuf[2] = { (uint8_t)((reg >> 8) & 0xFF), (uint8_t)(reg & 0xFF) };
|
|
uint8_t buffer[3];
|
|
i2c_write_blocking(i2c, address, regbuf, 2, true);
|
|
sleep_ms(delay_ms);
|
|
i2c_read_blocking(i2c, address, buffer, 3, false);
|
|
if(buffer[2] != calculate_crc(buffer[0], buffer[1]))
|
|
return false;
|
|
*value = (buffer[0] << 8) + buffer[1];
|
|
return true;
|
|
}
|
|
|
|
// Write register and read two 16-bit words
|
|
bool SGP30::read_reg_2_words(uint16_t reg, uint16_t delay_ms, uint16_t *value1, uint16_t *value2) {
|
|
uint8_t regbuf[2] = { (uint8_t)((reg >> 8) & 0xFF), (uint8_t)(reg & 0xFF) };
|
|
uint8_t buffer[6];
|
|
i2c_write_blocking(i2c, address, regbuf, 2, true);
|
|
sleep_ms(delay_ms);
|
|
i2c_read_blocking(i2c, address, buffer, 6, false);
|
|
if((buffer[2] != calculate_crc(buffer[0], buffer[1])) || (buffer[5] != calculate_crc(buffer[3], buffer[4]))) {
|
|
return false;
|
|
}
|
|
*value1 = (buffer[0] << 8) + buffer[1];
|
|
*value2 = (buffer[3] << 8) + buffer[4];
|
|
return true;
|
|
}
|
|
|
|
// Write register and read three 16-bit words
|
|
bool SGP30::read_reg_3_words(uint16_t reg, uint16_t delay_ms, uint16_t *value1, uint16_t *value2, uint16_t *value3) {
|
|
uint8_t regbuf[2] = { (uint8_t)((reg >> 8) & 0xFF), (uint8_t)(reg & 0xFF) };
|
|
uint8_t buffer[9];
|
|
i2c_write_blocking(i2c, address, regbuf, 2, true);
|
|
sleep_ms(delay_ms);
|
|
i2c_read_blocking(i2c, address, buffer, 9, false);
|
|
if(buffer[2] != calculate_crc(buffer[0], buffer[1])) {
|
|
return false;
|
|
}
|
|
if (buffer[5] != calculate_crc(buffer[3], buffer[4])) {
|
|
return false;
|
|
}
|
|
if (buffer[8] != calculate_crc(buffer[6], buffer[7])) {
|
|
return false;
|
|
}
|
|
*value1 = (buffer[0] << 8) + buffer[1];
|
|
*value2 = (buffer[3] << 8) + buffer[4];
|
|
*value3 = (buffer[6] << 8) + buffer[7];
|
|
return true;
|
|
}
|
|
|
|
// calculate the CRC for a single 16-bit word
|
|
uint8_t SGP30::calculate_crc(uint16_t value) {
|
|
return calculate_crc((value >> 8) && 0xFF, value & 0xFF);
|
|
}
|
|
|
|
// calculate the CRC for two 8-bit bytes
|
|
uint8_t SGP30::calculate_crc(uint8_t value1, uint8_t value2) {
|
|
// calculates 8-Bit checksum with given polynomial
|
|
uint8_t crc = SGP30_CRC_BASE;
|
|
|
|
crc ^= value1;
|
|
for(uint8_t b = 0; b < 8; b++) {
|
|
if(crc & 0x80)
|
|
crc = (crc << 1) ^ SGP30_CRC_SEED;
|
|
else
|
|
crc <<= 1;
|
|
}
|
|
crc ^= value2;
|
|
for(uint8_t b = 0; b < 8; b++) {
|
|
if(crc & 0x80)
|
|
crc = (crc << 1) ^ SGP30_CRC_SEED;
|
|
else
|
|
crc <<= 1;
|
|
}
|
|
|
|
return crc;
|
|
}
|
|
|
|
}
|