GY-63_MS5611/libraries/SGP30/SGP30.cpp
2023-02-27 11:48:52 +01:00

452 lines
8.3 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// FILE: SGP30.cpp
// AUTHOR: Rob Tillaart
// VERSION: 0.2.0
// DATE: 2021-06-24
// PURPOSE: Arduino library for SGP30 environment sensor.
// URL: https://github.com/RobTillaart/SGP30
// https://www.adafruit.com/product/3709
#include "SGP30.h"
/////////////////////////////////////////////////////
//
// CONSTRUCTOR
//
SGP30::SGP30(TwoWire *wire)
{
_address = 0x58;
_wire = wire;
_tvoc = 0;
_co2 = 0;
_h2 = 0;
_ethanol = 0;
_lastTime = 0;
_error = SGP30_OK;
}
#if defined (ESP8266) || defined(ESP32)
bool SGP30::begin(uint8_t dataPin, uint8_t clockPin)
{
_wire = &Wire;
if ((dataPin < 255) && (clockPin < 255))
{
_wire->begin(dataPin, clockPin);
} else {
_wire->begin();
}
if (! isConnected()) return false;
return true;
}
#endif
bool SGP30::begin()
{
_wire->begin();
if (! isConnected()) return false;
_init();
return true;
}
bool SGP30::isConnected()
{
_wire->beginTransmission(_address);
return ( _wire->endTransmission() == 0);
}
// INITIAL VERSION - needs optimization
bool SGP30::getID()
{
_command(0x3682);
delay(1);
if (_wire->requestFrom(_address, (uint8_t)9) == 9)
{
for (uint8_t i = 0, j = 0; i < 3; i++)
{
_id[j++] = _wire->read();
_id[j++] = _wire->read();
uint8_t crc = _wire->read();
uint16_t val = _id[j-2] * 256 + _id[j-1];
if (_CRC8(val) != crc)
{
_error = SGP30_ERROR_CRC;
return false;
}
}
_error = SGP30_OK;
return true;
}
_error = SGP30_ERROR_I2C;
return false;
}
// expect to return 0x0022
uint16_t SGP30::getFeatureSet()
{
_command(0x202F);
delay(10);
uint16_t rv = 0;
if (_wire->requestFrom(_address, (uint8_t)3) == 3)
{
rv = _wire->read() << 8;
rv += _wire->read();
uint8_t crc = _wire->read();
if (_CRC8(rv) != crc)
{
_error = SGP30_ERROR_CRC;
return rv;
}
_error = SGP30_OK;
return rv;
}
_error = SGP30_ERROR_I2C;
return 0xFFFF;
}
// WARNING: resets all devices on I2C bus.
void SGP30::GenericReset()
{
_command(0x0006);
_init();
};
bool SGP30::measureTest()
{
uint16_t rv = 0;
_command(0x2032);
delay(220); // Page 11
if (_wire->requestFrom(_address, (uint8_t)3) == 3)
{
rv = _wire->read() << 8;
rv += _wire->read();
uint8_t crc = _wire->read();
if (_CRC8(rv) != crc)
{
_error = SGP30_ERROR_CRC;
return false;
}
_error = SGP30_OK;
return rv == 0xD400;
}
_error = SGP30_ERROR_I2C;
return false;
}
/////////////////////////////////////////////////////
//
// MEASUREMENT
//
// SYNCHRONUOUS MODUS
bool SGP30::measure(bool all) // this is the workhorse
{
// 1 second between measurements. Page 9
if (millis() - _lastTime < 1000) return false;
_lastTime = millis();
request();
delay(12); // blocking!!
read();
if (not all) return true;
requestRaw();
delay(25); // blocking!!
readRaw();
return true;
}
// A-SYNCHRONUOUS INTERFACE
void SGP30::request()
{
_lastRequest = millis();
_command(0x2008);
}
bool SGP30::read()
{
if (_lastRequest == 0) return false;
if (millis() - _lastRequest < 13) return false; // Page 11
_lastRequest = 0;
if (_wire->requestFrom(_address, (uint8_t)6) != 6)
{
_error = SGP30_ERROR_I2C;
return false;
}
_co2 = _wire->read() << 8;
_co2 += _wire->read();
uint8_t crc = _wire->read();
if (_CRC8(_co2) != crc)
{
_error = SGP30_ERROR_CRC;
return false;
}
_tvoc = _wire->read() << 8;
_tvoc += _wire->read();
crc = _wire->read();
if (_CRC8(_tvoc) != crc)
{
_error = SGP30_ERROR_CRC;
return false;
}
_error = SGP30_OK;
return true;
}
void SGP30::requestRaw()
{
_lastRequest = millis();
_command(0x2050);
}
bool SGP30::readRaw()
{
if (_lastRequest == 0) return false;
if (millis() - _lastRequest < 26) return false; // Page 11
_lastRequest = 0;
if (_wire->requestFrom(_address, (uint8_t)6) != 6)
{
_error = SGP30_ERROR_I2C;
return false;
}
_h2 = _wire->read() << 8;
_h2 += _wire->read();
uint8_t crc = _wire->read();
if (_CRC8(_h2) != crc)
{
_error = SGP30_ERROR_CRC;
return false;
}
_ethanol = _wire->read() << 8;
_ethanol += _wire->read();
crc = _wire->read();
if (_CRC8(_ethanol) != crc)
{
_error = SGP30_ERROR_CRC;
return false;
}
_error = SGP30_OK;
return true;
}
// experimental - datasheet Page 2
// 1.953125e-3 = 1/512
float SGP30::getH2()
{
float cref = 0.5; // ppm
return cref * exp((_srefH2 - _h2) * 1.953125e-3);
}
float SGP30::getEthanol()
{
float cref = 0.4; // ppm
return cref * exp((_srefEth - _ethanol) * 1.953125e-3);
}
/////////////////////////////////////////////////////
//
// CALIBRATION
//
// slightly different formula
// https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
// Absolute Humidity (grams/m3) = 6.112 × e^[(17.67 × T)/(T+243.5)] × rh × 2.1674
// (273.15+T)
// T in °C
// RH == RelativeHumidity
float SGP30::setRelHumidity(float T, float RH) // Page 10
{
// page 10 datasheet
// AH = AbsoluteHumidity
// uint16_t AH = 216.7 * RH/100 * 6.117 * exp((17.62 * T)/(243.12 + T)) / (273.15 + T);
float absoluteHumidity = (2.167 * 6.112) * RH;
absoluteHumidity *= exp((17.62 * T)/(243.12 + T));
absoluteHumidity /= (273.15 + T);
setAbsHumidity(absoluteHumidity);
return absoluteHumidity;
}
void SGP30::setAbsHumidity(float absoluteHumidity)
{
uint16_t AH = absoluteHumidity;
uint8_t tmp = (absoluteHumidity - AH) * 256;
AH = (AH << 8) | tmp;
_command(0x2061, AH); // Page 11
}
void SGP30::setBaseline(uint16_t CO2, uint16_t TVOC)
{
_command(0x201E, TVOC, CO2);
}
bool SGP30::getBaseline(uint16_t *CO2, uint16_t *TVOC)
{
_command(0x2015);
if (_wire->requestFrom(_address, (uint8_t)6) != 6)
{
_error = SGP30_ERROR_I2C;
return false;
}
*CO2 = _wire->read() << 8;
*CO2 += _wire->read();
uint8_t crc = _wire->read();
if (_CRC8(*CO2) != crc)
{
_error = SGP30_ERROR_CRC;
return false;
}
*TVOC = _wire->read() << 8;
*TVOC += _wire->read();
crc = _wire->read();
if (_CRC8(*TVOC) != crc)
{
_error = SGP30_ERROR_CRC;
return false;
}
_error = SGP30_OK;
return true;
}
void SGP30::setTVOCBaseline(uint16_t TVOC)
{
_command(0x2077, TVOC);
}
bool SGP30::getTVOCBaseline(uint16_t *TVOC)
{
_command(0x20B3);
if (_wire->requestFrom(_address, (uint8_t)3) != 3)
{
_error = SGP30_ERROR_I2C;
return false;
}
*TVOC = _wire->read() << 8;
*TVOC += _wire->read();
uint8_t crc = _wire->read();
if (_CRC8(*TVOC) != crc)
{
_error = SGP30_ERROR_CRC;
return false;
}
_error = SGP30_OK;
return true;
}
/////////////////////////////////////////////////////
//
// MISCELLANEOUS
//
int SGP30::lastError()
{
int error = _error;
_error = SGP30_OK;
return error;
}
/////////////////////////////////////////////////////
//
// PRIVATE
//
int SGP30::_command(uint16_t cmd)
{
_wire->beginTransmission(_address);
_wire->write(cmd >> 8);
_wire->write(cmd & 0xFF);
_error = _wire->endTransmission();
return _error;
}
int SGP30::_command(uint16_t cmd, uint16_t v1)
{
_wire->beginTransmission(_address);
_wire->write(cmd >> 8);
_wire->write(cmd & 0xFF);
_wire->write(v1 >> 8);
_wire->write(v1 & 0xFF);
_wire->write(_CRC8(v1));
_error = _wire->endTransmission();
return _error;
}
int SGP30::_command(uint16_t cmd, uint16_t v1, uint16_t v2)
{
_wire->beginTransmission(_address);
_wire->write(cmd >> 8);
_wire->write(cmd & 0xFF);
_wire->write(v1 >> 8);
_wire->write(v1 & 0xFF);
_wire->write(_CRC8(v1));
_wire->write(v2 >> 8);
_wire->write(v2 & 0xFF);
_wire->write(_CRC8(v2));
_error = _wire->endTransmission();
return _error;
}
// for sending command - CRC lib.
// always 2 bytes
uint8_t SGP30::_CRC8(uint16_t data)
{
uint8_t val[2];
val[0] = data >> 8;
val[1] = data & 0xFF;
uint8_t crc = 0xFF; // start value
for(uint8_t i = 0; i < 2; i++)
{
crc ^= val[i];
for (uint8_t b = 8; b > 0; b--)
{
if (crc & 0x80)
crc = (crc << 1) ^ 0x31; // polynomial
else
crc <<= 1;
}
}
return crc;
};
void SGP30::_init()
{
_command(0x2003);
};
// -- END OF FILE --