Library for Sensirion Environmental Sensor Shield for SGP30 & SHTC1

Dependents:   example-sensirion-ublox-c030

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers sensirion_ess.cpp Source File

sensirion_ess.cpp

00001 /*
00002  * Copyright (c) 2017-2018, Sensirion AG
00003  * All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following conditions are met:
00007  *
00008  * * Redistributions of source code must retain the above copyright notice, this
00009  *   list of conditions and the following disclaimer.
00010  *
00011  * * Redistributions in binary form must reproduce the above copyright notice,
00012  *   this list of conditions and the following disclaimer in the documentation
00013  *   and/or other materials provided with the distribution.
00014  *
00015  * * Neither the name of Sensirion AG nor the names of its
00016  *   contributors may be used to endorse or promote products derived from
00017  *   this software without specific prior written permission.
00018  *
00019  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00020  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00021  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00022  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
00023  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
00024  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
00025  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00026  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
00027  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
00028  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00029  */
00030 
00031 /*
00032  * TODO:
00033  * - Baseline store/restore
00034  * - proper init
00035  * - IAQ levels, getIAQLevel()
00036  */
00037 
00038 
00039 
00040 #include <string.h>
00041 
00042 #include "sensirion_ess.h"
00043 DigitalOut led_red(LED_RED);
00044 DigitalOut led_yellow(LED_YEL);
00045 DigitalOut led_green(LED_GRN);
00046 
00047 SensirionESS::SensirionESS(void *interface)
00048 {
00049     i2c_connection = (I2C*)(interface);
00050     t = new Timer();
00051 }
00052 
00053 int SensirionESS::initSensors()
00054 {
00055 
00056     if (measureRHTInt() != 0) {
00057         setError("Error communicating with SHTC1");
00058         return -1;
00059     }
00060 
00061     if (initSGP() != 0) {
00062         setError("Error communicating with SGPC3");
00063         return -2;
00064     }
00065     
00066     t->start();
00067 
00068     mInitialized = true;
00069     return 0;
00070 }
00071 
00072 //////////////////////////////////////////////////////////////////////////////
00073 // SHT
00074 
00075 int SensirionESS::measureRHT()
00076 {
00077     if (!mInitialized) {
00078         setError("ESS not initialized");
00079         return -1;
00080     }
00081 
00082     measureRHTInt();
00083     return 0;
00084 }
00085 
00086 int SensirionESS::measureRHTInt()
00087 {
00088     uint8_t cmd[CMD_LENGTH] = { 0x7C, 0xA2 };
00089 
00090     if (i2c_write(SHT_I2C_ADDR, cmd, CMD_LENGTH)) {
00091         setError("I2C write error");
00092         return -1;
00093     }
00094 
00095     wait_ms(SHT_MEASURE_DELAY);
00096 
00097     int ret = i2c_read(SHT_I2C_ADDR, mDataBuf, SHT_DATA_LENGTH);
00098     if (ret == -1) {
00099         setError("I2C read error");
00100         return -2;
00101     }
00102 
00103     // check CRC for both RH and T
00104     if (crc8(mDataBuf+0, 2) != mDataBuf[2] ||
00105         crc8(mDataBuf+3, 2) != mDataBuf[5]) {
00106         setError("CRC mismatch");
00107         return -3;
00108     }
00109 
00110     uint16_t val;
00111     val = (mDataBuf[0] << 8) + mDataBuf[1];
00112     mTemperature = -45 + 175 * (val / 65535.0);
00113     val = (mDataBuf[3] << 8) + mDataBuf[4];
00114     mHumidity = 100 * (val / 65535.0);
00115 
00116     return 0;
00117 }
00118 
00119 //////////////////////////////////////////////////////////////////////////////
00120 // SGP
00121 
00122 int SensirionESS::getProductType() const
00123 {
00124     return mProductType;
00125 }
00126 
00127 int SensirionESS::getFeatureSetVersion() const
00128 {
00129     return mFeatureSetVersion;
00130 }
00131 
00132 int SensirionESS::measureIAQ()
00133 {
00134     if (!mInitialized) {
00135         setError("ESS not initialized");
00136         return -1;
00137     }
00138 
00139     // keep track of timing
00140     mSGPMeasurementTimestamp = t->read_ms();
00141 
00142     uint8_t cmd[CMD_LENGTH] = { 0x20, 0x08 };
00143 
00144     if (i2c_write(SGP_I2C_ADDR, cmd, CMD_LENGTH)) {
00145         setError("error in i2c_write");
00146         return -1;
00147     }
00148 
00149     wait_ms(SGP_MEASURE_DELAY);
00150 
00151     int ret = i2c_read(SGP_I2C_ADDR, mDataBuf, SGP_DATA_LENGTH);
00152     if (ret == -1) {
00153         setError("error in i2c_read");
00154         return -2;
00155     }
00156 
00157     if (crc8(mDataBuf, 2) != mDataBuf[2]) {
00158         setError("CRC mismatch");
00159         return -3;
00160     }
00161 
00162     // SGPC3 only has TVOC; SGP30 sends [eco2, tvoc]
00163     if (mProductType == PRODUCT_TYPE_SGP30) {
00164       mECO2 = (mDataBuf[0] << 8) | mDataBuf[1];
00165       if (crc8(mDataBuf+3, 2) != mDataBuf[5]) {
00166           setError("CRC mismatch");
00167           return -4;
00168       }
00169       mTVOC = (mDataBuf[3] << 8) | mDataBuf[4];
00170   } else {
00171       mTVOC = (mDataBuf[0] << 8) | mDataBuf[1];;
00172   }
00173 
00174     return 0;
00175 }
00176 
00177 int SensirionESS::readFeatureSetInt()
00178 {
00179     uint8_t cmd[CMD_LENGTH] = { 0x20, 0x2f };
00180 
00181     if (i2c_write(SGP_I2C_ADDR, cmd, CMD_LENGTH)) {
00182         setError("error in i2c_write");
00183         return -1;
00184     }
00185 
00186     wait_ms(2);
00187 
00188     const uint8_t DATA_LEN = 3;
00189     uint8_t data[DATA_LEN] = { 0 };
00190     int ret = i2c_read(SGP_I2C_ADDR, data, DATA_LEN);
00191     if (ret == -1) {
00192         setError("I2C read error");
00193         return -3;
00194     }
00195 
00196     // check CRC
00197     if (crc8(data, 2) != data[2]) {
00198         setError("CRC mismatch");
00199         return -4;
00200     }
00201 
00202     // 0 = SGP30, 1 = SGPC3
00203     mProductType = (data[0] & 0xF0) >> 4;
00204     mFeatureSetVersion = data[1] & 0xFF;
00205 
00206     return 0;
00207 }
00208 
00209 int SensirionESS::initSGP()
00210 {
00211     int ret = readFeatureSetInt();
00212     // default: SGP30
00213     SGP_INTERMEASURE_DELAY = SGP30_INTERMEASURE_DELAY;
00214     SGP_DATA_LENGTH = SGP30_DATA_LENGTH;
00215     uint8_t cmd[CMD_LENGTH] = { 0x20, 0x03 };
00216 
00217     if (mProductType == PRODUCT_TYPE_SGPC3) {
00218       SGP_INTERMEASURE_DELAY = SGPC3_INTERMEASURE_DELAY;
00219       cmd[1] = 0xae;
00220     }
00221 
00222     // run init air quality
00223     if (i2c_write(SGP_I2C_ADDR, cmd, CMD_LENGTH)) {
00224         setError("error in i2c_write");
00225         return -1;
00226     }
00227     wait_ms(10);
00228 
00229     return ret;
00230 }
00231 
00232 //////////////////////////////////////////////////////////////////////////////
00233 // getter for values read earlier
00234 bool SensirionESS::isInitialized()
00235 {
00236     return mInitialized;
00237 }
00238 
00239 float SensirionESS::getTemperature() const
00240 {
00241     return mTemperature;
00242 }
00243 
00244 float SensirionESS::getHumidity() const
00245 {
00246     return mHumidity;
00247 }
00248 
00249 float SensirionESS::getTVOC() const
00250 {
00251     return mTVOC;
00252 }
00253 
00254 float SensirionESS::getECO2() const
00255 {
00256     return mECO2;
00257 }
00258 
00259 void SensirionESS::setLedRYG(int r, int y, int g)
00260 {
00261     led_red = r;
00262     led_yellow = y;
00263     led_green = g;
00264 }
00265 
00266 //////////////////////////////////////////////////////////////////////////////
00267 // error handling
00268 
00269 inline void SensirionESS::setError(const char* error)
00270 {
00271     strlcpy(mErrorBuf, error, ERROR_BUF_LENGTH);
00272 }
00273 
00274 const char* SensirionESS::getError() const
00275 {
00276     return mErrorBuf;
00277 }
00278 
00279 //////////////////////////////////////////////////////////////////////////////
00280 // helper
00281 
00282 int SensirionESS::remainingWaitTimeMS()
00283 {
00284     unsigned long deltaT = t->read_ms() - mSGPMeasurementTimestamp;
00285     if (deltaT > SGP_INTERMEASURE_DELAY) {
00286         // we're already late, don't wait any longer
00287         return 0;
00288     }
00289     return (SGP_INTERMEASURE_DELAY - deltaT);
00290 }
00291 
00292 int8_t SensirionESS::i2c_read(uint8_t address, uint8_t* data, uint16_t count)
00293 {
00294     if (i2c_connection->read(address << 1, (char *) data, count) != 0)
00295         return -1;
00296     return 0;
00297 }
00298 
00299 int8_t SensirionESS::i2c_write(uint8_t address, const uint8_t* data, uint16_t count)
00300 {
00301     if (i2c_connection->write(address << 1, (char *) data, count) != 0)
00302         return -1;
00303     return 0;
00304 }
00305 
00306 uint8_t SensirionESS::crc8(const uint8_t* data, uint8_t len)
00307 {
00308   // adapted from SHT21 sample code from http://www.sensirion.com/en/products/humidity-temperature/download-center/
00309 
00310   uint8_t crc = 0xff;
00311   uint8_t byteCtr;
00312   for (byteCtr = 0; byteCtr < len; ++byteCtr) {
00313     crc ^= (data[byteCtr]);
00314     for (uint8_t bit = 8; bit > 0; --bit) {
00315       if (crc & 0x80) {
00316         crc = (crc << 1) ^ 0x31;
00317       } else {
00318         crc = (crc << 1);
00319       }
00320     }
00321   }
00322   return crc;
00323 }