diff --git a/BUILDS.md b/BUILDS.md index 7cfe5a568..1c1824a58 100644 --- a/BUILDS.md +++ b/BUILDS.md @@ -132,6 +132,8 @@ Note: `minimal` variant is not listed as it shouldn't be used outside of the [up | USE_MPR121 | - | - / - | - | - | - | - | | USE_CCS811 | - | - / - | - | x | - | - | | USE_CCS811_V2 | - | - / x | - | - | - | - | +| USE_ENS16x | - | - / - | - | - | - | - | +| USE_ENS210 | - | - / - | - | - | - | - | | USE_MPU6050 | - | - / - | - | - | - | - | | USE_DS3231 | - | - / - | - | - | - | - | | USE_MGC3130 | - | - / - | - | - | - | - | diff --git a/I2CDEVICES.md b/I2CDEVICES.md index 9bba8962e..fcda37377 100644 --- a/I2CDEVICES.md +++ b/I2CDEVICES.md @@ -120,3 +120,6 @@ Index | Define | Driver | Device | Address(es) | Description 81 | USE_PCA9557 | xdrv_69 | PCA95xx | 0x18 - 0x1F | 8-bit I/O expander as virtual button/switch/relay 82 | USE_SGP4X | xsns_109 | SGP4X | 0x59 | Gas (TVOC/NOx index) 83 | USE_MAX17043 | xsns_110 | MAX17043 | 0x36 | Fuel-gauge for 3.7 Volt Lipo battery + 84 | USE_ENS16x | xsns_111 | ENS16x | 0x52 - 0x53 | Gas (TVOC, eCO2) and air quality sensor + 85 | USE_ENS210 | xsns_112 | ENS210 | 0x43 - 0x44 | Temperature and humidity sensor + diff --git a/lib/lib_i2c/ScioSense_ENS16x/LICENSE.md b/lib/lib_i2c/ScioSense_ENS16x/LICENSE.md new file mode 100644 index 000000000..dbc30683e --- /dev/null +++ b/lib/lib_i2c/ScioSense_ENS16x/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Sciosense + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/lib_i2c/ScioSense_ENS16x/README.md b/lib/lib_i2c/ScioSense_ENS16x/README.md new file mode 100644 index 000000000..2bbcaed40 --- /dev/null +++ b/lib/lib_i2c/ScioSense_ENS16x/README.md @@ -0,0 +1,38 @@ +# ScioSense ENS16x +Arduino library for the ENS160 and ENS161 digital four channel MOX gas sensor with I2C interface from ScioSense + +## Introduction +This project is an Arduino *library*. It implements a driver with examples for the ENS160 and ENS161. +The ENS16x chip is a digital gas sensor for TVOC and eCO2 with an I2C interface. +The driver in this Arduino library is based on the code supplied by *Sciosense*, the manufacturer of the chip. + +Note that the ENS16x requires a supply voltage of 1.71V .. 1.98V. +The ENS16x also requires a IO voltage of 1.71V .. 3.6V. + +## Links +The ENS16x is made by [Sciosense](http://www.sciosense.com). + - The datasheet of the ENS160 is available through the website. The datasheet of ENS161 is not yet released but can be provided on request + +## Prerequisites +It is assumed that + - The Arduino IDE has been installed. + If not, refer to "Install the Arduino Desktop IDE" on the + [Arduino site](https://www.arduino.cc/en/Guide/HomePage). + - The library directory is at its default location. + For me, that is `C:\Users\sciosense\Documents\Arduino\libraries`. + +## Installation +- Visit the project page for the Arduino ENS16x library. +- Click the green button Clone or download on the right side. +- From the pop-up choose Download ZIP. +- In Arduino IDE, select Sketch > Include Library > Manage Libraries ... and browse to the just downloaded ZIP file. +- When the IDE is ready this README.md should be located at e.g. `C:\Users\sciosense\Documents\Arduino\libraries\ScioSense_ENS16x\README.md`. + +## Build an example +To build an example sketch + - (Re)start Arduino. + - Open File > Example > Examples from Custom Libraries > ENS16x > ENS16xbasic_normal + - Make sure Tools > Board lists the correct board. + - Select Sketch > Verify/Compile. + +### ScioSense is a Joint Venture of ams AG diff --git a/lib/lib_i2c/ScioSense_ENS16x/keywords.txt b/lib/lib_i2c/ScioSense_ENS16x/keywords.txt new file mode 100644 index 000000000..35ce5e6fb --- /dev/null +++ b/lib/lib_i2c/ScioSense_ENS16x/keywords.txt @@ -0,0 +1,65 @@ +####################################### +# Syntax Coloring Map +####################################### +# https://spencer.bliven.us/index.php/2012/01/18/arduino-ide-keywords/ +# KEYWORD1 Classes, datatypes, and C++ keywords +# KEYWORD2 Methods and functions +# KEYWORD3 setup and loop functions, as well as the Serial keywords +# LITERAL1 Constants +# LITERAL2 Built-in variables (unused by default) + + +####################################### +# Classes, datatypes (KEYWORD1) +####################################### +ENS160 KEYWORD1 +ens160 KEYWORD1 +ENS161 KEYWORD1 +ens161 KEYWORD1 +ScioSense_ENS16x KEYWORD1 +ScioSense_ens16x KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### +begin KEYWORD2 +setI2C KEYWORD2 +available KEYWORD2 +revENS16x KEYWORD2 +setMode KEYWORD2 + +initCustomMode KEYWORD2 +addCustomStep KEYWORD2 + +measure KEYWORD2 +measureRaw KEYWORD2 +set_envdata KEYWORD2 +set_envdata210 KEYWORD2 +getMajorRev KEYWORD2 +getMinorRev KEYWORD2 +getBuild KEYWORD2 + +getAQI KEYWORD2 +getTVOC KEYWORD2 +geteCO2 KEYWORD2 +getAQIS KEYWORD2 +getHP0 KEYWORD2 +getHP0BL KEYWORD2 +getHP1 KEYWORD2 +getHP1BL KEYWORD2 +getHP2 KEYWORD2 +getHP2BL KEYWORD2 +getHP3 KEYWORD2 +getHP3BL KEYWORD2 +getMISR KEYWORD2 + +###################################### +# Constants (LITERAL1) +####################################### + +ENS16x_I2CADDR_0 LITERAL1 +ENS16x_I2CADDR_1 LITERAL1 +ENS16x_OPMODE_IDLE LITERAL1 +ENS16x_OPMODE_STD LITERAL1 +ENS161_OPMODE_LP LITERAL1 +ENS16x_OPMODE_CUSTOM LITERAL1 \ No newline at end of file diff --git a/lib/lib_i2c/ScioSense_ENS16x/library.properties b/lib/lib_i2c/ScioSense_ENS16x/library.properties new file mode 100644 index 000000000..f85dc659e --- /dev/null +++ b/lib/lib_i2c/ScioSense_ENS16x/library.properties @@ -0,0 +1,9 @@ +name=ScioSense ENS16x +version=8.0.0 +author=Christoph Friese +maintainer=ScioSense +sentence=Arduino library for the ENS160 and ENS161 digital four channel MOX gas sensor with I2C interface from ScioSense +paragraph=This library controls the ENS160 and ENS161. The main feature of this library is performing a single shot measurement, retrieving the measurement data. +category=Device Control +url=https://github.com/sciosense/ens16x +architectures=* diff --git a/lib/lib_i2c/ScioSense_ENS16x/src/ScioSense_ENS16x.cpp b/lib/lib_i2c/ScioSense_ENS16x/src/ScioSense_ENS16x.cpp new file mode 100644 index 000000000..6f21190e2 --- /dev/null +++ b/lib/lib_i2c/ScioSense_ENS16x/src/ScioSense_ENS16x.cpp @@ -0,0 +1,490 @@ +/* + ScioSense_ENS16x.h - Library for the ENS160 & ENS161 sensor with I2C interface from ScioSense + 2023 Jul 03 v8 Christoph Friese Update to cover ENS160 and ENS161 + 2023 Mar 23 v7 Christoph Friese Bugfix measurement routine, prepare next release + 2021 Nov 25 v6 Martin Herold Custom mode timing fixed + 2021 July 29 v5 Christoph Friese Changed nomenclature to ScioSense as product shifted from ams + 2021 Feb 04 v4 Giuseppe de Pinto Custom mode fixed + 2020 Apr 06 v3 Christoph Friese Changed nomenclature to ScioSense as product shifted from ams + 2020 Feb 15 v2 Giuseppe Pasetti Corrected firmware flash option + 2019 May 05 v1 Christoph Friese Created + + based on application note "ENS160 Software Integration.pdf" rev 0.01 +*/ + +#include "ScioSense_ENS16x.h" +#include "math.h" + +ScioSense_ENS16x::ScioSense_ENS16x(uint8_t slaveaddr) { + this->_slaveaddr = slaveaddr; + + this->_ADDR = 0; + this->_nINT = 0; + this->_nCS = 0; +} + +#ifndef ENS16x_DISABLE_ENHANCED_FEATURES +ScioSense_ENS16x::ScioSense_ENS16x(uint8_t ADDR, uint8_t nCS, uint8_t nINT) { + this->_slaveaddr = ENS16x_I2CADDR_0; + + this->_ADDR = ADDR; + this->_nINT = nINT; + this->_nCS = nCS; +} + +ScioSense_ENS16x::ScioSense_ENS16x(uint8_t slaveaddr, uint8_t ADDR, uint8_t nCS, uint8_t nINT) { + this->_slaveaddr = slaveaddr; + + this->_ADDR = ADDR; + this->_nINT = nINT; + this->_nCS = nCS; +} + +// Function to redefine I2C pins +void ScioSense_ENS16x::setI2C(uint8_t sda, uint8_t scl) { + this->_sdaPin = sda; + this->_sclPin = scl; +} +#endif + +// Init I2C communication, resets ENS16x and checks its PART_ID. Returns false on I2C problems or wrong PART_ID. +bool ScioSense_ENS16x::begin(bool debug) +{ +#ifndef ENS16x_DISABLE_DEBUG + debugENS16x = debug; + + //Set pin levels + if (this->_ADDR > 0) { + pinMode(this->_ADDR, OUTPUT); + digitalWrite(this->_ADDR, LOW); + } + if (this->_nINT > 0) pinMode(this->_nINT, INPUT_PULLUP); + if (this->_nCS > 0) { + pinMode(this->_nCS, OUTPUT); + digitalWrite(this->_nCS, HIGH); + } +#endif + + //init I2C + _i2c_init(); +#ifndef ENS16x_DISABLE_DEBUG + if (debugENS16x) { + Serial.println("begin() - I2C init done"); + } +#endif + delay(ENS16x_BOOTING); // Wait to boot after reset + + this->_available = false; + this->_available = this->reset(); + + this->_available = this->checkPartID(); + + if (this->_available) { + this->_available = this->setMode(ENS16x_OPMODE_IDLE); + this->_available = this->clearCommand(); + this->_available = this->getFirmware(); + } +#ifndef ENS16x_DISABLE_DEBUG + if (debugENS16x) { + Serial.println("ENS16x in idle mode"); + } +#endif + return this->_available; +} + +// Sends a reset to the ENS16x. Returns false on I2C problems. +bool ScioSense_ENS16x::reset(void) +{ + uint8_t result = this->write8(_slaveaddr, ENS16x_REG_OPMODE, ENS16x_OPMODE_RESET); + +#ifndef ENS16x_DISABLE_DEBUG + if (debugENS16x) { + Serial.print("reset() result: "); + Serial.println(result == 0 ? "ok" : "nok"); + } +#endif + delay(ENS16x_BOOTING); // Wait to boot after reset + + return result == 0; +} + +// Reads the part ID and confirms valid sensor +bool ScioSense_ENS16x::checkPartID(void) { + uint8_t i2cbuf[2]; + uint16_t part_id; + bool result = false; + + this->read(_slaveaddr, ENS16x_REG_PART_ID, i2cbuf, 2); + part_id = i2cbuf[0] | ((uint16_t)i2cbuf[1] << 8); + +#ifndef ENS16x_DISABLE_DEBUG + if (debugENS16x) { + Serial.print("checkPartID() result: "); + if (part_id == ENS160_PARTID) Serial.println("ENS160 ok"); + else if (part_id == ENS161_PARTID) Serial.println("ENS161 ok"); + else Serial.println("nok"); + } +#endif + delay(ENS16x_BOOTING); // Wait to boot after reset + + if (part_id == ENS160_PARTID) { this->_revENS16x = 0; result = true; } + else if (part_id == ENS161_PARTID) { this->_revENS16x = 1; result = true; } + + return result; +} + +// Initialize idle mode and confirms +bool ScioSense_ENS16x::clearCommand(void) { + uint8_t status; + uint8_t result; + + result = this->write8(_slaveaddr, ENS16x_REG_COMMAND, ENS16x_COMMAND_NOP); + result = this->write8(_slaveaddr, ENS16x_REG_COMMAND, ENS16x_COMMAND_CLRGPR); +#ifndef ENS16x_DISABLE_DEBUG + if (debugENS16x) { + Serial.print("clearCommand() result: "); + Serial.println(result == 0 ? "ok" : "nok"); + } +#endif + delay(ENS16x_BOOTING); // Wait to boot after reset + + status = this->read8(_slaveaddr, ENS16x_REG_DATA_STATUS); +#ifndef ENS16x_DISABLE_DEBUG + if (debugENS16x) { + Serial.print("clearCommand() status: 0x"); + Serial.println(status, HEX); + } +#endif + delay(ENS16x_BOOTING); // Wait to boot after reset + + return result == 0; +} + +// Read firmware revisions +bool ScioSense_ENS16x::getFirmware() { + uint8_t i2cbuf[3]; + uint8_t result; + + this->clearCommand(); + + delay(ENS16x_BOOTING); // Wait to boot after reset + + result = this->write8(_slaveaddr, ENS16x_REG_COMMAND, ENS16x_COMMAND_GET_APPVER); + result = this->read(_slaveaddr, ENS16x_REG_GPR_READ_4, i2cbuf, 3); + + this->_fw_ver_major = i2cbuf[0]; + this->_fw_ver_minor = i2cbuf[1]; + this->_fw_ver_build = i2cbuf[2]; + + if (this->_fw_ver_major > 6) this->_revENS16x = 1; + else this->_revENS16x = 0; + +#ifndef ENS16x_DISABLE_DEBUG + if (debugENS16x) { + Serial.println(this->_fw_ver_major); + Serial.println(this->_fw_ver_minor); + Serial.println(this->_fw_ver_build); + Serial.print("getFirmware() result: "); + Serial.println(result == 0 ? "ok" : "nok"); + } +#endif + delay(ENS16x_BOOTING); // Wait to boot after reset + + return result == 0; +} + +// Set operation mode of sensor +bool ScioSense_ENS16x::setMode(uint8_t mode) { + uint8_t result; + + //LP only valid for rev>0 + if ((mode == ENS161_OPMODE_LP) and (_revENS16x == 0)) result = 1; + else result = this->write8(_slaveaddr, ENS16x_REG_OPMODE, mode); + +#ifndef ENS16x_DISABLE_DEBUG + //if (debugENS16x) { + Serial.print("setMode() activate result: "); + Serial.println(result == 0 ? "ok" : "nok"); + //} +#endif + delay(ENS16x_BOOTING); // Wait to boot after reset + + return result == 0; +} + +#ifndef ENS16x_DISABLE_ENHANCED_FEATURES +// Initialize definition of custom mode with steps +bool ScioSense_ENS16x::initCustomMode(uint16_t stepNum) { + uint8_t result; + + if (stepNum > 0) { + this->_stepCount = stepNum; + + result = this->setMode(ENS16x_OPMODE_IDLE); + result = this->clearCommand(); + + result = this->write8(_slaveaddr, ENS16x_REG_COMMAND, ENS16x_COMMAND_SETSEQ); + } else { + result = 1; + } + delay(ENS16x_BOOTING); // Wait to boot after reset + + return result == 0; +} +#endif + +#ifndef ENS16x_DISABLE_ENHANCED_FEATURES +// Add a step to custom measurement profile with definition of duration, enabled data acquisition and temperature for each hotplate +bool ScioSense_ENS16x::addCustomStep(uint16_t time, bool measureHP0, bool measureHP1, bool measureHP2, bool measureHP3, uint16_t tempHP0, uint16_t tempHP1, uint16_t tempHP2, uint16_t tempHP3) { + uint8_t seq_ack; + uint8_t temp; + +#ifndef ENS16x_DISABLE_DEBUG + if (debugENS16x) { + Serial.print("setCustomMode() write step "); + Serial.println(this->_stepCount); + } +#endif + delay(ENS16x_BOOTING); // Wait to boot after reset + + temp = (uint8_t)(((time / 24)-1) << 6); + if (measureHP0) temp = temp | 0x20; + if (measureHP1) temp = temp | 0x10; + if (measureHP2) temp = temp | 0x8; + if (measureHP3) temp = temp | 0x4; + this->write8(_slaveaddr, ENS16x_REG_GPR_WRITE_0, temp); + + temp = (uint8_t)(((time / 24)-1) >> 2); + this->write8(_slaveaddr, ENS16x_REG_GPR_WRITE_1, temp); + + this->write8(_slaveaddr, ENS16x_REG_GPR_WRITE_2, (uint8_t)(tempHP0/2)); + this->write8(_slaveaddr, ENS16x_REG_GPR_WRITE_3, (uint8_t)(tempHP1/2)); + this->write8(_slaveaddr, ENS16x_REG_GPR_WRITE_4, (uint8_t)(tempHP2/2)); + this->write8(_slaveaddr, ENS16x_REG_GPR_WRITE_5, (uint8_t)(tempHP3/2)); + + this->write8(_slaveaddr, ENS16x_REG_GPR_WRITE_6, (uint8_t)(this->_stepCount - 1)); + + if (this->_stepCount == 1) { + this->write8(_slaveaddr, ENS16x_REG_GPR_WRITE_7, 128); + } else { + this->write8(_slaveaddr, ENS16x_REG_GPR_WRITE_7, 0); + } + delay(ENS16x_BOOTING); + + seq_ack = this->read8(_slaveaddr, ENS16x_REG_GPR_READ_7); + delay(ENS16x_BOOTING); // Wait to boot after reset + + if ((ENS16x_SEQ_ACK_COMPLETE | this->_stepCount) != seq_ack) { + this->_stepCount = this->_stepCount - 1; + return 0; + } else { + return 1; + } + +} +#endif + +// Perform prediction measurement and stores result in internal variables +bool ScioSense_ENS16x::measure(bool waitForNew) { + uint8_t i2cbuf[8]; + uint8_t status; + bool newData = false; + + // Set default status for early bail out +#ifndef ENS16x_DISABLE_DEBUG + if (debugENS16x) Serial.println("Start measurement"); +#endif + + if (waitForNew) { + do { + delay(10); + status = this->read8(_slaveaddr, ENS16x_REG_DATA_STATUS); + +#ifndef ENS16x_DISABLE_DEBUG + if (debugENS16x) { + Serial.print("Status: "); + Serial.println(status); + } +#endif + } while (!IS_NEWDAT(status)); + } else { + status = this->read8(_slaveaddr, ENS16x_REG_DATA_STATUS); + } + + // Read predictions + if (IS_NEWDAT(status)) { + newData = true; + this->read(_slaveaddr, ENS16x_REG_DATA_AQI, i2cbuf, 7); + _data_aqi = i2cbuf[0]; + _data_tvoc = i2cbuf[1] | ((uint16_t)i2cbuf[2] << 8); + _data_eco2 = i2cbuf[3] | ((uint16_t)i2cbuf[4] << 8); + if (_revENS16x > 0) _data_aqis = ((uint16_t)i2cbuf[5]) | ((uint16_t)i2cbuf[6] << 8); + else _data_aqis = 0; + } + + return newData; +} + +#ifndef ENS16x_DISABLE_ENHANCED_FEATURES +// Perfrom raw measurement and stores result in internal variables +bool ScioSense_ENS16x::measureRaw(bool waitForNew) { + uint8_t i2cbuf[8]; + uint8_t status; + bool newData = false; + + // Set default status for early bail out +#ifndef ENS16x_DISABLE_DEBUG + if (debugENS16x) Serial.println("Start measurement"); +#endif + + if (waitForNew) { + do { + delay(10); + status = this->read8(_slaveaddr, ENS16x_REG_DATA_STATUS); + +#ifndef ENS16x_DISABLE_DEBUG + if (debugENS16x) { + Serial.print("Status: "); + Serial.println(status); + } +#endif + } while (!IS_NEWGPR(status)); + } else { + status = this->read8(_slaveaddr, ENS16x_REG_DATA_STATUS); + } + + if (IS_NEWGPR(status)) { + newData = true; + + // Read raw resistance values + this->read(_slaveaddr, ENS16x_REG_GPR_READ_0, i2cbuf, 8); + _hp0_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[0] | ((uint16_t)i2cbuf[1] << 8))); + _hp1_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[2] | ((uint16_t)i2cbuf[3] << 8))); + _hp2_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[4] | ((uint16_t)i2cbuf[5] << 8))); + _hp3_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[6] | ((uint16_t)i2cbuf[7] << 8))); + + // Read baselines + this->read(_slaveaddr, ENS16x_REG_DATA_BL, i2cbuf, 8); + _hp0_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[0] | ((uint16_t)i2cbuf[1] << 8))); + _hp1_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[2] | ((uint16_t)i2cbuf[3] << 8))); + _hp2_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[4] | ((uint16_t)i2cbuf[5] << 8))); + _hp3_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[6] | ((uint16_t)i2cbuf[7] << 8))); + + this->read(_slaveaddr, ENS16x_REG_DATA_MISR, i2cbuf, 1); + _misr = i2cbuf[0]; + } + + return newData; +} + +// Writes t (degC) and h (%rh) to ENV_DATA. Returns false on I2C problems. +bool ScioSense_ENS16x::set_envdata(float t, float h) { + + uint16_t t_data = (uint16_t)((t + 273.15f) * 64.0f); + + uint16_t rh_data = (uint16_t)(h * 512.0f); + + return this->set_envdata210(t_data, rh_data); +} + +// Writes t and h (in ENS210 format) to ENV_DATA. Returns false on I2C problems. +bool ScioSense_ENS16x::set_envdata210(uint16_t t, uint16_t h) { + //uint16_t temp; + uint8_t trh_in[4]; + + //temp = (uint16_t)((t + 273.15f) * 64.0f); + trh_in[0] = t & 0xff; + trh_in[1] = (t >> 8) & 0xff; + + //temp = (uint16_t)(h * 512.0f); + trh_in[2] = h & 0xff; + trh_in[3] = (h >> 8) & 0xff; + + uint8_t result = this->write(_slaveaddr, ENS16x_REG_TEMP_IN, trh_in, 4); + + return result; +} +#endif + +/**************************************************************************/ + +void ScioSense_ENS16x::_i2c_init() { +#ifndef ENS16x_DISABLE_ENHANCED_FEATURES + if (this->_sdaPin != this->_sclPin) + Wire.begin(this->_sdaPin, this->_sclPin); + else +#endif + Wire.begin(); +} + +/**************************************************************************/ + +uint8_t ScioSense_ENS16x::read8(uint8_t addr, byte reg) { + uint8_t ret; + this->read(addr, reg, &ret, 1); + + return ret; +} + +uint8_t ScioSense_ENS16x::read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num) { + uint8_t pos = 0; + uint8_t result = 0; + +#ifndef ENS16x_DISABLE_DEBUG + if (debugENS16x) { + Serial.print("I2C read address: 0x"); + Serial.print(addr, HEX); + Serial.print(" - register: 0x"); + Serial.println(reg, HEX); + } +#endif + + //on arduino we need to read in 32 byte chunks + while(pos < num){ + + uint8_t read_now = min((uint8_t)32, (uint8_t)(num - pos)); + Wire.beginTransmission((uint8_t)addr); + + Wire.write((uint8_t)reg + pos); + result = Wire.endTransmission(); + Wire.requestFrom((uint8_t)addr, read_now); + + for(int i=0; iwrite(addr, reg, &value, 1); + return result; +} + +uint8_t ScioSense_ENS16x::write(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num) { +#ifndef ENS16x_DISABLE_DEBUG + if (debugENS16x) { + Serial.print("I2C write address: 0x"); + Serial.print(addr, HEX); + Serial.print(" - register: 0x"); + Serial.print(reg, HEX); + Serial.print(" - value:"); + for (int i = 0; i= 100) + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +#include + +// Chip constants +#define ENS160_PARTID 0x0160 +#define ENS161_PARTID 0x0161 +#define ENS16x_BOOTING 10 + +// 7-bit I2C slave address of the ENS16x +#define ENS16x_I2CADDR_0 0x52 //ADDR low +#define ENS16x_I2CADDR_1 0x53 //ADDR high + +// ENS16x registers for version V0 +#define ENS16x_REG_PART_ID 0x00 // 2 byte register +#define ENS16x_REG_OPMODE 0x10 +#define ENS16x_REG_CONFIG 0x11 +#define ENS16x_REG_COMMAND 0x12 +#define ENS16x_REG_TEMP_IN 0x13 +#define ENS16x_REG_RH_IN 0x15 +#define ENS16x_REG_DATA_STATUS 0x20 +#define ENS16x_REG_DATA_AQI 0x21 +#define ENS16x_REG_DATA_TVOC 0x22 +#define ENS16x_REG_DATA_ECO2 0x24 +#define ENS16x_REG_DATA_BL 0x28 +#define ENS16x_REG_DATA_T 0x30 +#define ENS16x_REG_DATA_RH 0x32 +#define ENS16x_REG_DATA_MISR 0x38 +#define ENS16x_REG_GPR_WRITE_0 0x40 +#define ENS16x_REG_GPR_WRITE_1 ENS16x_REG_GPR_WRITE_0 + 1 +#define ENS16x_REG_GPR_WRITE_2 ENS16x_REG_GPR_WRITE_0 + 2 +#define ENS16x_REG_GPR_WRITE_3 ENS16x_REG_GPR_WRITE_0 + 3 +#define ENS16x_REG_GPR_WRITE_4 ENS16x_REG_GPR_WRITE_0 + 4 +#define ENS16x_REG_GPR_WRITE_5 ENS16x_REG_GPR_WRITE_0 + 5 +#define ENS16x_REG_GPR_WRITE_6 ENS16x_REG_GPR_WRITE_0 + 6 +#define ENS16x_REG_GPR_WRITE_7 ENS16x_REG_GPR_WRITE_0 + 7 +#define ENS16x_REG_GPR_READ_0 0x48 +#define ENS16x_REG_GPR_READ_4 ENS16x_REG_GPR_READ_0 + 4 +#define ENS16x_REG_GPR_READ_6 ENS16x_REG_GPR_READ_0 + 6 +#define ENS16x_REG_GPR_READ_7 ENS16x_REG_GPR_READ_0 + 7 + +//ENS16x data register fields +#define ENS16x_COMMAND_NOP 0x00 +#define ENS16x_COMMAND_CLRGPR 0xCC +#define ENS16x_COMMAND_GET_APPVER 0x0E +#define ENS16x_COMMAND_SETTH 0x02 +#define ENS16x_COMMAND_SETSEQ 0xC2 + +#define ENS16x_OPMODE_RESET 0xF0 +#define ENS16x_OPMODE_DEP_SLEEP 0x00 +#define ENS16x_OPMODE_IDLE 0x01 +#define ENS16x_OPMODE_STD 0x02 +#define ENS161_OPMODE_LP 0x03 +#define ENS16x_OPMODE_CUSTOM 0xC0 + +#define ENS16x_BL_CMD_START 0x02 +#define ENS16x_BL_CMD_ERASE_APP 0x04 +#define ENS16x_BL_CMD_ERASE_BLINE 0x06 +#define ENS16x_BL_CMD_WRITE 0x08 +#define ENS16x_BL_CMD_VERIFY 0x0A +#define ENS16x_BL_CMD_GET_BLVER 0x0C +#define ENS16x_BL_CMD_GET_APPVER 0x0E +#define ENS16x_BL_CMD_EXITBL 0x12 + +#define ENS16x_SEQ_ACK_NOTCOMPLETE 0x80 +#define ENS16x_SEQ_ACK_COMPLETE 0xC0 + +#define IS_ENS16x_SEQ_ACK_NOT_COMPLETE(x) (ENS16x_SEQ_ACK_NOTCOMPLETE == (ENS16x_SEQ_ACK_NOTCOMPLETE & (x))) +#define IS_ENS16x_SEQ_ACK_COMPLETE(x) (ENS16x_SEQ_ACK_COMPLETE == (ENS16x_SEQ_ACK_COMPLETE & (x))) + +#define ENS16x_DATA_STATUS_NEWDAT 0x02 +#define ENS16x_DATA_STATUS_NEWGPR 0x01 + +#define IS_NEWDAT(x) (ENS16x_DATA_STATUS_NEWDAT == (ENS16x_DATA_STATUS_NEWDAT & (x))) +#define IS_NEWGPR(x) (ENS16x_DATA_STATUS_NEWGPR == (ENS16x_DATA_STATUS_NEWGPR & (x))) +#define IS_NEW_DATA_AVAILABLE(x) (0 != ((ENS16x_DATA_STATUS_NEWDAT | ENS16x_DATA_STATUS_NEWGPR ) & (x))) + +#define CONVERT_RS_RAW2OHMS_I(x) (1 << ((x) >> 11)) +#define CONVERT_RS_RAW2OHMS_F(x) (pow (2, (float)(x) / 2048)) + +class ScioSense_ENS16x { + + public: + ScioSense_ENS16x(uint8_t slaveaddr = ENS16x_I2CADDR_0); // Constructor using slave address (5A or 5B) +#ifndef ENS16x_DISABLE_ENHANCED_FEATURES + ScioSense_ENS16x(uint8_t ADDR, uint8_t nCS, uint8_t nINT); // Constructor with pin definition + ScioSense_ENS16x(uint8_t slaveaddr, uint8_t ADDR, uint8_t nCS, uint8_t nINT); // Constructor with slave address and pin definition +#endif + + void setI2C(uint8_t sda, uint8_t scl); // Function to redefine I2C pins + + bool begin(bool debug=false); // Init I2C communication, resets ENS16x and checks its PART_ID. Returns false on I2C problems or wrong PART_ID. + bool available() { return this->_available; } // Report availability of sensor + uint8_t revENS16x() { return this->_revENS16x; } // Report version of sensor (0: ENS16x, 1: ENS161) + bool setMode(uint8_t mode); // Set operation mode of sensor + +#ifndef ENS16x_DISABLE_ENHANCED_FEATURES + bool initCustomMode(uint16_t stepNum); // Initialize definition of custom mode with steps + bool addCustomStep(uint16_t time, bool measureHP0, bool measureHP1, bool measureHP2, bool measureHP3, uint16_t tempHP0, uint16_t tempHP1, uint16_t tempHP2, uint16_t tempHP3); + // Add a step to custom measurement profile with definition of duration, enabled data acquisition and temperature for each hotplate +#endif + + bool measure(bool waitForNew = true); // Perform measurement and stores result in internal variables +#ifndef ENS16x_DISABLE_ENHANCED_FEATURES + bool measureRaw(bool waitForNew = true); // Perform raw measurement and stores result in internal variables + bool set_envdata(float t, float h); // Writes t (degC) and h (%rh) to ENV_DATA. Returns "0" if I2C transmission is successful + bool set_envdata210(uint16_t t, uint16_t h); // Writes t and h (in ENS210 format) to ENV_DATA. Returns "0" if I2C transmission is successful +#endif + uint8_t getMajorRev() { return this->_fw_ver_major; } // Get major revision number of used firmware + uint8_t getMinorRev() { return this->_fw_ver_minor; } // Get minor revision number of used firmware + uint8_t getBuild() { return this->_fw_ver_build; } // Get build revision number of used firmware + + uint8_t getAQI() { return this->_data_aqi; } // Get AQI value of last measurement + uint16_t getTVOC() { return this->_data_tvoc; } // Get TVOC value of last measurement + uint16_t geteCO2() { return this->_data_eco2; } // Get eCO2 value of last measurement + uint16_t getAQIS() { return this->_data_aqis; } // Get AQI500 value of last measurement + uint32_t getHP0() { return this->_hp0_rs; } // Get resistance of HP0 of last measurement + uint32_t getHP1() { return this->_hp1_rs; } // Get resistance of HP1 of last measurement + uint32_t getHP2() { return this->_hp2_rs; } // Get resistance of HP2 of last measurement + uint32_t getHP3() { return this->_hp3_rs; } // Get resistance of HP3 of last measurement + uint32_t getHP0BL() { return this->_hp0_bl; } // Get baseline resistance of HP0 of last measurement + uint32_t getHP1BL() { return this->_hp1_bl; } // Get baseline resistance of HP1 of last measurement + uint32_t getHP2BL() { return this->_hp2_bl; } // Get baseline resistance of HP2 of last measurement + uint32_t getHP3BL() { return this->_hp3_bl; } // Get baseline resistance of HP3 of last measurement + uint8_t getMISR() { return this->_misr; } // Return status code of sensor + + private: + uint8_t _ADDR; + uint8_t _nINT; + uint8_t _nCS; + uint8_t _sdaPin = 0; + uint8_t _sclPin = 0; + +#ifndef ENS16x_DISABLE_DEBUG + bool debugENS16x = false; +#endif + + bool reset(); // Sends a reset to the ENS16x. Returns false on I2C problems. + bool checkPartID(); // Reads the part ID and confirms valid sensor + bool clearCommand(); // Initialize idle mode and confirms + bool getFirmware(); // Read firmware revisions + + bool _available = false; // ENS16x available + uint8_t _revENS16x = 0; // ENS160 or ENS161 connected? (FW >7) + + uint8_t _fw_ver_major; + uint8_t _fw_ver_minor; + uint8_t _fw_ver_build; + + uint16_t _stepCount; // Counter for custom sequence + + uint8_t _data_aqi; + uint16_t _data_tvoc; + uint16_t _data_eco2; + uint16_t _data_aqis; + uint32_t _hp0_rs; + uint32_t _hp0_bl; + uint32_t _hp1_rs; + uint32_t _hp1_bl; + uint32_t _hp2_rs; + uint32_t _hp2_bl; + uint32_t _hp3_rs; + uint32_t _hp3_bl; + uint16_t _temp; + int _slaveaddr; + uint8_t _misr; + + //Isotherm, HP0 252°C / HP1 350°C / HP2 250°C / HP3 324°C / measure every 1008ms + uint8_t _seq_steps[1][8] = { + { 0x7C, 0x0A, 0x7E, 0xAF, 0xAF, 0xA2, 0x00, 0x80 }, + }; + + +/****************************************************************************/ +/* General functions */ +/****************************************************************************/ + void _i2c_init(); + + uint8_t read8(uint8_t addr, byte reg); + uint8_t read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num); + + uint8_t write8(uint8_t addr, byte reg, byte value); + uint8_t write(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num); + +}; + + +#endif \ No newline at end of file diff --git a/lib/lib_i2c/ScioSense_ENS210/README.md b/lib/lib_i2c/ScioSense_ENS210/README.md new file mode 100644 index 000000000..3e5ea811e --- /dev/null +++ b/lib/lib_i2c/ScioSense_ENS210/README.md @@ -0,0 +1,28 @@ +# ENS210 +Arduino library for the ENS210 temperature & humidity sensor with I2C interface from ScioSense + +## Introduction +This project is an Arduino *library*. It implements a driver with examples for the ENS210. +The ENS210 chip is a digital temperature & humidity sensor with an I2C interface. +The driver in this Arduino library is based on the code supplied by *Sciosense*, the manufacturer of the chip. + +Note that the ENS210 requires a supply voltage of 1.71V .. 1.98V. + +## Links +The ENS210 is made by [Sciosense](http://www.sciosense.com). + - The datasheet of the ENS210 is not yet released + +## Prerequisites +It is assumed that + - The Arduino IDE has been installed. + If not, refer to "Install the Arduino Desktop IDE" on the + [Arduino site](https://www.arduino.cc/en/Guide/HomePage). + - The library directory is at its default location. + For me, Christoph, that is `C:\Users\christoph\Documents\Arduino\libraries`. + +## Build an example +To build an example sketch + - (Re)start Arduino. + - Open File > Example > Examples from Custom Libraries > ENS210 > ENS210basic. + - Make sure Tools > Board lists the correct board. + - Select Sketch > Verify/Compile. \ No newline at end of file diff --git a/lib/lib_i2c/ScioSense_ENS210/keywords.txt b/lib/lib_i2c/ScioSense_ENS210/keywords.txt new file mode 100644 index 000000000..db012a2a2 --- /dev/null +++ b/lib/lib_i2c/ScioSense_ENS210/keywords.txt @@ -0,0 +1,53 @@ +####################################### +# Syntax Coloring Map +####################################### +# https://spencer.bliven.us/index.php/2012/01/18/arduino-ide-keywords/ +# KEYWORD1 Classes, datatypes, and C++ keywords +# KEYWORD2 Methods and functions +# KEYWORD3 setup and loop functions, as well as the Serial keywords +# LITERAL1 Constants +# LITERAL2 Built-in variables (unused by default) + + +####################################### +# Classes, datatypes (KEYWORD1) +####################################### +ENS210 KEYWORD1 +ens210 KEYWORD1 +sciosense_ens210 KEYWORD1 +Sciosense_ens210 KEYWORD1 +ScioSense_ens210 KEYWORD1 +sciosense_ENS210 KEYWORD1 +Sciosense_ENS210 KEYWORD1 +ScioSense_ENS210 KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### +begin KEYWORD2 +setSingleMode KEYWORD2 +available KEYWORD2 +getPartID KEYWORD2 +getHighUID KEYWORD2 +measure KEYWORD2 +getTempKelvin KEYWORD2 +getTempCelsius KEYWORD2 +getTempFahrenheit KEYWORD2 +getHumidityPercent KEYWORD2 +getAbsoluteHumidityPercent KEYWORD2 +getStatusT KEYWORD2 +getDataT KEYWORD2 +getStatusH KEYWORD2 +getDataH KEYWORD2 +status_str KEYWORD2 +correction_set KEYWORD2 +correction_get KEYWORD2 + +###################################### +# Constants (LITERAL1) +####################################### + +ENS210_STATUS_I2CERROR LITERAL1 +ENS210_STATUS_CRCERROR LITERAL1 +ENS210_STATUS_INVALID LITERAL1 +ENS210_STATUS_OK LITERAL1 diff --git a/lib/lib_i2c/ScioSense_ENS210/library.properties b/lib/lib_i2c/ScioSense_ENS210/library.properties new file mode 100644 index 000000000..0a9f30ed6 --- /dev/null +++ b/lib/lib_i2c/ScioSense_ENS210/library.properties @@ -0,0 +1,9 @@ +name=ScioSense ENS210 +version=3.0.0 +author=Christoph Friese +maintainer=Christoph Friese +sentence=Arduino library for the ENS210 digital temperature & humidity sensor with I2C interface from ScioSense +paragraph=This library controls the ENS210. The main feature of this library is performing a single shot measurement, retrieving the measurement data. +category=Device Control +url=https://github.com/sciosense/ens210 +architectures=* diff --git a/lib/lib_i2c/ScioSense_ENS210/src/ScioSense_ENS210.cpp b/lib/lib_i2c/ScioSense_ENS210/src/ScioSense_ENS210.cpp new file mode 100644 index 000000000..48c291cdc --- /dev/null +++ b/lib/lib_i2c/ScioSense_ENS210/src/ScioSense_ENS210.cpp @@ -0,0 +1,648 @@ +/* + ScioSense_ENS210.h - Library for the ENS210 relative humidity and temperature sensor with I2C interface from ScioSense + 2020 Apr 06 v3 Christoph Friese Changed nomenclature to ScioSense as product shifted from ams + 2018 Aug 28 v2 Christoph Friese Adjusted I2C communication + 2017 Aug 01 v1 Maarten Pennings Created +*/ + +#include "ScioSense_ENS210.h" +#include + +// Compute the CRC-7 of 'val' (should only have 17 bits) +// https://en.wikipedia.org/wiki/Cyclic_redundancy_check#Computation +static uint32_t crc7( uint32_t val ) +{ + // Setup polynomial + uint32_t pol= CRC7POLY; + // Align polynomial with data + pol = pol << (DATA7WIDTH-CRC7WIDTH-1); + // Loop variable (indicates which bit to test, start with highest) + uint32_t bit = DATA7MSB; + // Make room for CRC value + val = val << CRC7WIDTH; + bit = bit << CRC7WIDTH; + pol = pol << CRC7WIDTH; + // Insert initial vector + val |= CRC7IVEC; + // Apply division until all bits done + while( bit & (DATA7MASK<>= 1; + pol >>= 1; + } + return val; +} + +ScioSense_ENS210::ScioSense_ENS210(uint8_t slaveaddr) { + this->_slaveaddress = slaveaddr; +} + +#ifndef ENS210_DISABLE_ENHANCED_FEATURES +void ScioSense_ENS210::setI2C(uint8_t sda, uint8_t scl) { + this->_sdaPin = sda; + this->_sclPin = scl; +} +#endif + +// Init I2C communication, resets ENS210 and checks its PART_ID. Returns false on I2C problems or wrong PART_ID. +// Stores solder correction. +bool ScioSense_ENS210::begin(bool debug) { + bool result; + +#ifndef ENS210_DISABLE_DEBUG + debugENS210 = debug; +#endif + + //init I2C + _i2c_init(); +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.println("ens210 debug - I2C init done"); + } +#endif + + // Record solder correction + this->_soldercorrection= 0; + this->_available = false; + this->_singleMode = true; + this->_t_data = -1; + this->_h_data = -1; + this->_partID = 0; + + this->lowpower(false); + + result = this->getversion(); + + if(this->_partID == ENS210_PARTID ) { + this->_available = true; + } + return result; +} + +#ifndef ENS210_DISABLE_ENHANCED_FEATURES +void ScioSense_ENS210::changeAddr(uint8_t oldAddr, uint8_t newAddr) { + uint8_t i2cbuf[2]; + + _i2c_init(); + + //Disable low power mode --> always on + uint8_t result = this->write8(oldAddr, 0x10, 0x00); +#ifndef ENS210_DISABLE_DEBUG + Serial.print("Low Power off result: 0x"); + Serial.println(result,HEX); +#endif + delay(100); + + // read status register + result = this->read(oldAddr, 0x11, i2cbuf, 1); +#ifndef ENS210_DISABLE_DEBUG + Serial.print("Read status result: 0x"); + Serial.println(result,HEX); + for (uint8_t i=0; i<1; i++) { + Serial.print("\t0x"); + Serial.print(i2cbuf[i],HEX); + } + Serial.println(); +#endif + delay(100); + + // enable low level access. PASSWORD_WRITE_ENABLE + result = this->write8(oldAddr, 0x10, 0x30); +#ifndef ENS210_DISABLE_DEBUG + Serial.print("PASSWORD_WRITE_ENABLE result: 0x"); + Serial.println(result,HEX); +#endif + delay(100); + + // read status register + result = this->read(oldAddr, 0x11, i2cbuf, 1); +#ifndef ENS210_DISABLE_DEBUG + Serial.print("Read status result: 0x"); + Serial.println(result,HEX); + for (uint8_t i=0; i<1; i++) { + Serial.print("\t0x"); + Serial.print(i2cbuf[i],HEX); + } + Serial.println(); +#endif + delay(100); + + // low level access password. Password is 00 00 00 00 + uint8_t passwdCmd[4] = {0x00, 0x00, 0x00, 0x00}; + result = this->write(oldAddr, 0x48, passwdCmd, 5); +#ifndef ENS210_DISABLE_DEBUG + Serial.print("Write password result: 0x"); + Serial.println(result,HEX); +#endif + delay(100); + + // read low-level mode sensor output data + result = this->read(oldAddr, 0x46, i2cbuf, 2); +#ifndef ENS210_DISABLE_DEBUG + Serial.print("Read low level result: 0x"); + Serial.println(result,HEX); + for (uint8_t i=0; i<2; i++) { + Serial.print("\t0x"); + Serial.print(i2cbuf[i],HEX); + } + Serial.println(); +#endif + delay(100); + + // write values to parameter field and data1&2 field + uint8_t newAddrCmd[5] = {0x00,0x00,0x22,0x00,0x41}; + newAddrCmd[4] = newAddr; + result = this->write(oldAddr, 0x41, newAddrCmd, 5); +#ifndef ENS210_DISABLE_DEBUG + Serial.print("Write parameters result: 0x"); + Serial.println(result,HEX); +#endif + delay(100); + + // write SEN_CMD to execute low-level command specified, 0xCE means “APB_WRITE” + result = this->write8(oldAddr, 0x40, 0xCE); +#ifndef ENS210_DISABLE_DEBUG + Serial.print("Execute result: 0x"); + Serial.println(result,HEX); +#endif + delay(100); + + // Read low-level mode sensor output data to check if programming succeeded. Expected response: 0x00 0xAC + result = this->read(oldAddr, 0x46, i2cbuf, 2); +#ifndef ENS210_DISABLE_DEBUG + Serial.print("Read back result: 0x"); + Serial.println(result,HEX); + for (uint8_t i=0; i<2; i++) { + Serial.print("\t0x"); + Serial.print(i2cbuf[i],HEX); + } + Serial.println(); +#endif + delay(100); + + // reset ENS210 + result = this->write8(oldAddr, 0x10, 0xFF); +#ifndef ENS210_DISABLE_DEBUG + Serial.print("Reset result: 0x"); + Serial.println(result,HEX); +#endif + delay(100); + + // reset ENS210 + result = this->write8(oldAddr, 0x10, 0xFF); +#ifndef ENS210_DISABLE_DEBUG + Serial.print("Reset result: 0x"); + Serial.println(result,HEX); +#endif + delay(100); + + // reset ENS210 + result = this->write8(newAddr, 0x10, 0xFF); +#ifndef ENS210_DISABLE_DEBUG + Serial.print("Reset result: 0x"); + Serial.println(result,HEX); +#endif + delay(100); +} +#endif //#ifndef ENS210_DISABLE_ENHANCED_FEATURES + +// Sends a reset to the ENS210. Returns false on I2C problems. +bool ScioSense_ENS210::reset(void) { + uint8_t result = this->write8(_slaveaddress, ENS210_REG_SYS_CTRL, 0x80); +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("ens210 debug - reset: 0x"); + Serial.println(result,HEX); + } +#endif + delay(ENS210_BOOTING); // Wait to boot after reset + return result==0; +} + + +// Sets ENS210 to low (true) or high (false) power. Returns false on I2C problems. +bool ScioSense_ENS210::lowpower(bool enable) { + uint8_t power = enable ? 0x01: 0x00; + uint8_t result = this->write8(this->_slaveaddress, ENS210_REG_SYS_CTRL, power); +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("ens210 debug - lowpower: 0x"); + Serial.println(result,HEX); + } +#endif + delay(ENS210_BOOTING); // Wait boot-time after power switch + return result==0; +} + + +// Reads PART_ID and UID of ENS210. Returns false on I2C problems. +bool ScioSense_ENS210::getversion() { + bool ok; + uint8_t i2cbuf[8]; + uint8_t result; + + // Must disable low power to read PART_ID or UID + ok= lowpower(false); if(!ok) goto errorexit; + + // Read the PART_ID + result = this->read(this->_slaveaddress, ENS210_REG_PART_ID, i2cbuf, 2); +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("ens210 debug - PART_ID I2C result: 0x"); + Serial.println(result, HEX); + } +#endif + //this->_partID = (uint16_t)(i2cbuf[1]*256U + i2cbuf[0]*1U); + this->_partID = (uint16_t)(((uint16_t)i2cbuf[1] << 8) | ((uint16_t)i2cbuf[0])); +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("PART_ID: 0x"); + Serial.println(this->_partID,HEX); + } +#endif + + // Read the REV + result = this->read(this->_slaveaddress, ENS210_REG_REV, i2cbuf, 2); +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("ens210 debug - REV I2C result: 0x"); + Serial.println(result, HEX); + } +#endif + this->_rev = (uint16_t)(((uint16_t)i2cbuf[1] << 8) | ((uint16_t)i2cbuf[0])); +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("REV: 0x"); + Serial.println(this->_rev,HEX); + } +#endif + + // Read the UID + result = this->read(_slaveaddress, ENS210_REG_UID,i2cbuf,8); +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("ens210 debug - UID 0x"); + Serial.println(result,HEX); + } +#endif + this->_uID = (uint64_t)((uint64_t)i2cbuf[7]<<56 | (uint64_t)i2cbuf[6]<<48 | (uint64_t)i2cbuf[5]<<40 | (uint64_t)i2cbuf[4]<<32 | (uint64_t)i2cbuf[3]<<24 | (uint64_t)i2cbuf[2]<<16 | (uint64_t)i2cbuf[1]<<8 | (uint64_t)i2cbuf[0]); + + //for( int i=0; i<8; i++) ((uint8_t*)this->_uID)[i]=i2cbuf[i]; //((uint8_t*)this->_uID)[i]=i2cbuf[i]; + this->_uIDhi = (uint32_t)(this->_uID >> 32); + this->_uIDlo = (uint32_t)(this->_uID & 0xFFFFFFFF); + +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("UID hi 0x"); + Serial.print(this->_uIDhi,HEX); + Serial.print(" - 0x"); + Serial.println(this->_uIDlo,HEX); + } +#endif + + // Go back to default power mode (low power enabled) + ok= lowpower(true); if(!ok) goto errorexit; + + // Success + return true; + + errorexit: + // Try to go back to default mode (low power enabled) + ok= lowpower(true); + + // Hopefully enabling low power was successful; but there was an error before that anyhow + return false; +} + +// Configures ENS210 measurement mode +// false for continuous mode / true for single shot measurement. Returns false on I2C problems. +bool ScioSense_ENS210::setSingleMode(bool enable) +{ + this->_singleMode = enable; + uint8_t mode = enable ? 0x00: 0x03; + uint8_t result = this->write8(_slaveaddress, ENS210_REG_SENS_RUN, mode); +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("ens210 debug - start mode 0x"); + Serial.print(mode,HEX); + Serial.print(" - result 0x"); + Serial.println(result,HEX); + } +#endif + return result==0; +} + +// Performs one single shot temperature and relative humidity measurement. +void ScioSense_ENS210::measure() //int * t_data, int * t_status, int * h_data, int * h_status ) +{ + bool ok; + // Set default status for early bail out +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) Serial.println("Start measurement"); +#endif + this->_t_status = ENS210_STATUS_I2CERROR; + this->_h_status = ENS210_STATUS_I2CERROR; + + // Start a single shot measurement + if (this->_singleMode) + { + uint8_t result = this->write8(_slaveaddress, ENS210_REG_SENS_RUN, 0x00); +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("ens210 debug - start single 0x"); + Serial.println(result,HEX); + } +#endif + } + + //Trigger measurement + uint8_t result = this->write8(_slaveaddress, ENS210_REG_SENS_START, 0x03); +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("ens210 debug - trigger measurement 0x"); + Serial.println(result,HEX); + } +#endif + + // Wait for measurement to complete + if (this->_singleMode) delay(ENS210_THCONV_SINGLE_MS); + else delay(ENS210_THCONV_CONT_MS); + + // Get the measurement data +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) Serial.println("Start measurement"); +#endif + ok = readValue(); if(!ok) return; +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) Serial.println("Measurement ok"); +#endif +} + +// Reads measurement data from the ENS210. Returns false on I2C problems. +bool ScioSense_ENS210::readValue() //uint32_t *t_val, uint32_t *h_val) +{ + uint8_t i2cbuf[6]; + uint8_t valid; + uint32_t crc; + uint32_t payload; + uint8_t crc_ok; + + // Read T_VAL and H_VAL + uint8_t result = this->read(_slaveaddress, ENS210_REG_T_VAL, i2cbuf, 6); +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("ens210 debug - readValue:"); + Serial.println(result); + Serial.print(i2cbuf[0],HEX); Serial.print("\t"); + Serial.print(i2cbuf[1],HEX); Serial.print("\t"); + Serial.print(i2cbuf[2],HEX); Serial.print("\t"); + Serial.print(i2cbuf[3],HEX); Serial.print("\t"); + Serial.print(i2cbuf[4],HEX); Serial.print("\t"); + Serial.print(i2cbuf[5],HEX); Serial.println("\t"); + } +#endif + // Retrieve and pack bytes into t_val and h_val + uint32_t t_val= (uint32_t)((uint32_t)i2cbuf[2]<<16 | (uint32_t)i2cbuf[1]<<8 | (uint32_t)i2cbuf[0]); + this->_t_data = (t_val>>0) & 0xffff; + valid = (t_val>>16) & 0x1; + crc = (t_val>>17) & 0x7f; + payload = (t_val>>0 ) & 0x1ffff; + crc_ok = crc7(payload)==crc; + if( !crc_ok ) this->_t_status = ENS210_STATUS_CRCERROR; + else if( !valid ) this->_t_status = ENS210_STATUS_INVALID; + else this->_t_status = ENS210_STATUS_OK; +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("_t_data 0x"); + Serial.print(t_val,HEX); + Serial.print(" - 0x"); + Serial.println(this->_t_data,HEX); + Serial.print("Valid: "); + Serial.print(valid); + Serial.print(" Status: "); + Serial.println(this->_t_status); + } +#endif + + uint32_t h_val= (uint32_t)((uint32_t)i2cbuf[5]<<16 | (uint32_t)i2cbuf[4]<<8 | (uint32_t)i2cbuf[3]); + this->_h_data = (h_val>>0) & 0xffff; + + valid = (h_val>>16) & 0x1; + crc = (h_val>>17) & 0x7f; + payload = (h_val>>0 ) & 0x1ffff; + crc_ok= crc7(payload)==crc; + if( !crc_ok ) this->_h_status= ENS210_STATUS_CRCERROR; + else if( !valid ) this->_h_status= ENS210_STATUS_INVALID; + else this->_h_status= ENS210_STATUS_OK; +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("_h_data 0x"); + Serial.print(h_val,HEX); + Serial.print(" - 0x"); + Serial.println(this->_h_data,HEX); + Serial.print("Valid: "); + Serial.print(valid); + Serial.print(" Status: "); + Serial.println(this->_h_status); + } +#endif + + // Success + return true; +} + + +#ifndef ENS210_DISABLE_ENHANCED_FEATURES +// Converts a status (ENS210_STATUS_XXX) to a human readable string. +const char * ScioSense_ENS210::status_str( int status ) +{ + switch( status ) { + case ENS210_STATUS_I2CERROR : return "i2c-error"; + case ENS210_STATUS_CRCERROR : return "crc-error"; + case ENS210_STATUS_INVALID : return "data-invalid"; + case ENS210_STATUS_OK : return "ok"; + default : return "unknown-status"; + } +} +#endif + +#ifndef ENS210_DISABLE_ENHANCED_FEATURES +// Convert raw `t_data` temperature to Kelvin (also applies the solder correction). +// The output value is in Kelvin multiplied by parameter `multiplier`. +float ScioSense_ENS210::getTempKelvin() +{ + // Force 32 bits + float t = this->_t_data & 0xFFFF; + // Compensate for soldering effect + t-= _soldercorrection; + // Return m*K. This equals m*(t/64) = (m*t)/64 + // Note m is the multiplier, K is temperature in Kelvin, t is raw t_data value. + // Uses K=t/64. + return t/64; //IDIV(t,64); +} +#endif + +// Convert raw `t_data` temperature to Celsius (also applies the solder correction). +// The output value is in Celsius multiplied by parameter `multiplier`. +float ScioSense_ENS210::getTempCelsius() +{ + //assert( (1<=multiplier) && (multiplier<=1024) ); + // Force 32 bits + float t= this->_t_data & 0xFFFF; + // Compensate for soldering effect + t-= _soldercorrection; + // Return m*C. This equals m*(K-273.15) = m*K - 27315*m/100 = m*t/64 - 27315*m/100 + // Note m is the multiplier, C is temperature in Celsius, K is temperature in Kelvin, t is raw t_data value. + // Uses C=K-273.15 and K=t/64. + return t/64 - 27315L/100; //IDIV(t,64) - IDIV(27315L,100); +} + +#ifndef ENS210_DISABLE_ENHANCED_FEATURES +// Convert raw `t_data` temperature to Fahrenheit (also applies the solder correction). +float ScioSense_ENS210::getTempFahrenheit() +{ + float t= this->_t_data & 0xFFFF; + // Compensate for soldering effect + t-= _soldercorrection; + // Return m*F. This equals m*(1.8*(K-273.15)+32) = m*(1.8*K-273.15*1.8+32) = 1.8*m*K-459.67*m = 9*m*K/5 - 45967*m/100 = 9*m*t/320 - 45967*m/100 + // Note m is the multiplier, F is temperature in Fahrenheit, K is temperature in Kelvin, t is raw t_data value. + // Uses F=1.8*(K-273.15)+32 and K=t/64. + return 9*t/320 - 45967/100; //IDIV(9*t,320) - IDIV(45967L,100); + // The first multiplication stays below 32 bits (t:16, multiplier:11, 9:4) + // The second multiplication stays below 32 bits (multiplier:10, 45967:16) +} +#endif + +// Convert raw `h_data` relative humidity to %RH. +float ScioSense_ENS210::getHumidityPercent() +{ + float h= this->_h_data & 0xFFFF; + // Return m*H. This equals m*(h/512) = (m*h)/512 + // Note m is the multiplier, H is the relative humidity in %RH, h is raw h_data value. + // Uses H=h/512. + return h/512; //IDIV(h, 512); +} + + +// Convert raw `h_data` absolute humidity to %RH. +#define MOLAR_MASS_OF_WATER 18.01534 +#define UNIVERSAL_GAS_CONSTANT 8.21447215 + +#ifndef ENS210_DISABLE_ENHANCED_FEATURES +float ScioSense_ENS210::getAbsoluteHumidityPercent() +{ + //taken from https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/ + //precision is about 0.1°C in range -30 to 35°C + //August-Roche-Magnus 6.1094 exp(17.625 x T)/(T + 243.04) + //Buck (1981) 6.1121 exp(17.502 x T)/(T + 240.97) + //reference https://www.eas.ualberta.ca/jdwilson/EAS372_13/Vomel_CIRES_satvpformulae.html // Use Buck (1981) + + return (6.1121 * pow(2.718281828,(17.67* this->getTempCelsius())/(this->getTempCelsius() + 243.5))* this->getHumidityPercent() *MOLAR_MASS_OF_WATER)/((273.15+ this->getTempCelsius() )*UNIVERSAL_GAS_CONSTANT); +} +#endif + +#ifndef ENS210_DISABLE_ENHANCED_FEATURES +// Sets the solder correction (default is 50mK) - only used by the `toXxx` functions. +void ScioSense_ENS210::correction_set(int correction) +{ + assert( -1*64_soldercorrection = correction; +} +#endif + + +/****************************************************************************/ +/* General functions */ +/****************************************************************************/ + +void ScioSense_ENS210::_i2c_init() { +#ifndef ENS210_DISABLE_ENHANCED_FEATURES + if (this->_sdaPin != this->_sclPin) + Wire.begin(this->_sdaPin, this->_sclPin); + else +#endif + Wire.begin(); +} +/**************************************************************************/ + +uint8_t ScioSense_ENS210::read8(uint8_t addr, byte reg) +{ + uint8_t ret; + this->read(addr, reg, &ret, 1); + + return ret; +} + +uint8_t ScioSense_ENS210::read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num) +{ + uint8_t pos = 0; + uint8_t result = 0; + +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("I2C read address: 0x"); + Serial.print(addr, HEX); + Serial.print(" - register: 0x"); + Serial.println(reg, HEX); + } +#endif + + //on arduino we need to read in 32 byte chunks + while(pos < num){ + + uint8_t read_now = 32; //min((uint8_t)32, (uint8_t)(num - pos)); + Wire.beginTransmission((uint8_t)addr); + + Wire.write((uint8_t)reg + pos); + result = Wire.endTransmission(); + Wire.requestFrom((uint8_t)addr, read_now); + + //for(int i=0; iwrite(addr, reg, &value, 1); + return result; +} + +uint8_t ScioSense_ENS210::write(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num) +{ +#ifndef ENS210_DISABLE_DEBUG + if (debugENS210) { + Serial.print("I2C write address: 0x"); + Serial.print(addr, HEX); + Serial.print(" - register: 0x"); + Serial.print(reg, HEX); + Serial.print(" - value: 0x"); + for (int i = 0; i= 100) + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +#include + +#define ENS210_I2CADDR 0x43 //ADDR low + +// Chip constants +#define ENS210_PARTID 0x0210 // The expected part id of the ENS210 +#define ENS210_BOOTING 2 // Booting time in ms (also after reset, or going to high power) +#define ENS210_THCONV_SINGLE_MS 130 // Conversion time in ms for single shot T/H measurement +#define ENS210_THCONV_CONT_MS 238 // Conversion time in ms for continuous T/H measurement + +// Addresses of the ENS210 registers +#define ENS210_REG_PART_ID 0x00 +#define ENS210_REG_REV 0x02 +#define ENS210_REG_UID 0x04 +#define ENS210_REG_SYS_CTRL 0x10 +#define ENS210_REG_SYS_STAT 0x11 +#define ENS210_REG_SENS_RUN 0x21 +#define ENS210_REG_SENS_START 0x22 +#define ENS210_REG_SENS_STOP 0x23 +#define ENS210_REG_SENS_STAT 0x24 +#define ENS210_REG_T_VAL 0x30 +#define ENS210_REG_H_VAL 0x33 + +// Division macro (used in conversion functions), implementing integer division with rounding. +// It supports both positive and negative dividends (n), but ONLY positive divisors (d). +//#define IDIV(n,d) ((n)>0 ? ((n)+(d)/2)/(d) : ((n)-(d)/2)/(d)) + +// 7654 3210 +// Polynomial 0b 1000 1001 ~ x^7+x^3+x^0 +// 0x 8 9 +#define CRC7WIDTH 7 // A 7 bits CRC has polynomial of 7th order, which has 8 terms +#define CRC7POLY 0x89 // The 8 coefficients of the polynomial +#define CRC7IVEC 0x7F // Initial vector has all 7 bits high +// Payload data +#define DATA7WIDTH 17 +#define DATA7MASK ((1UL<_available; } + uint16_t getPartID() { return this->_partID; } + uint16_t getRev() { return this->_rev; } + uint32_t getHighUID(bool high) { uint32_t result = high ? this->_uIDhi : this->_uIDlo; return result; } + void measure(); // perfrom measurement and stores result in internal variables +#ifndef ENS210_DISABLE_ENHANCED_FEATURES + float getTempKelvin (); // Converts and returns data (from `measure`) in Kelvin + float getTempFahrenheit (); // Converts and returns data (from `measure`) in Fahrenheit +#endif + float getTempCelsius (); // Converts and returns data (from `measure`) in Celsius + float getHumidityPercent(); // Converts and returns data (from `measure`) in %RH +#ifndef ENS210_DISABLE_ENHANCED_FEATURES + float getAbsoluteHumidityPercent(); // Converts and returns data (from `measure`) in %aH +#endif + char getStatusT() { return this->_t_status; } // Converts a status (ENS210_STATUS_XXX) to a human readable string. + uint32_t getDataT() { return this->_t_data; } + char getStatusH() { return this->_h_status; } // Converts a status (ENS210_STATUS_XXX) to a human readable string. + uint32_t getDataH() { return this->_h_data; } +#ifndef ENS210_DISABLE_ENHANCED_FEATURES + static const char * status_str( int status ); // Converts a status (ENS210_STATUS_XXX) to a human readable string. +#endif + + // Optionally set a solder `correction` (units: 1/64K, default from `begin` is 0). + // See "Effect of Soldering on Temperature Readout" in "Design-Guidelines" from + // https://download.ams.com/ENVIRONMENTAL-SENSORS/ENS210/Documentation +#ifndef ENS210_DISABLE_ENHANCED_FEATURES + void correction_set(int correction=50*64/1000); // Sets the solder correction (default is 50mK) - only used by the `toXxx()` functions. + int correction_get() {return this->_soldercorrection;} // Gets the solder correction. +#endif + + private: +#ifndef ENS210_DISABLE_DEBUG + bool debugENS210 = false; +#endif + bool reset(void); // Sends a reset to the ENS210. Returns false on I2C problems. + bool lowpower(bool enable); // Sets ENS210 to low (true) or high (false) power. Returns false on I2C problems. + bool getversion(); // Reads PART_ID and UID of ENS210. Returns false on I2C problems. + bool readValue(); // Reads measurement data from the ENS210. Returns false on I2C problems. + + bool _available = false; // ENS210 available + uint16_t _partID; // Part ID of ENS210, should be 0x210 + uint16_t _rev; // Revision 0 MRA2.6 / 1 MRA2.12 + uint64_t _uID; // Unique ID of this specific ENS210 + uint32_t _uIDhi; // First 32bit of unique ID of this specific ENS210 + uint32_t _uIDlo; // Second 32bit of unique ID of this specific ENS210 + bool _singleMode = true; // Measurement mode: true single shot / false continuous + uint32_t _t_data; + uint8_t _t_status; + uint32_t _h_data; + uint8_t _h_status; + uint8_t _slaveaddress = 0x43; // Slave address of ENS210 + uint8_t _soldercorrection; // Correction due to soldering (in 1/64K); subtracted from `t_data` by conversion functions. + + uint8_t _sdaPin = 0; + uint8_t _sclPin = 0; + + /****************************************************************************/ + /* General functions */ + /****************************************************************************/ + + uint8_t write8(uint8_t addr, byte reg, byte value); + uint8_t read8(uint8_t addr, byte reg); + + uint8_t read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num); + uint8_t write(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num); + void _i2c_init(); +}; + + +#endif diff --git a/tasmota/include/tasmota_configurations.h b/tasmota/include/tasmota_configurations.h index c588f16cf..a572440f6 100644 --- a/tasmota/include/tasmota_configurations.h +++ b/tasmota/include/tasmota_configurations.h @@ -111,6 +111,8 @@ //#define USE_MPR121 // [I2cDriver23] Enable MPR121 controller (I2C addresses 0x5A, 0x5B, 0x5C and 0x5D) in input mode for touch buttons (+1k3 code) #define USE_CCS811 // [I2cDriver24] Enable CCS811 sensor (I2C address 0x5A) (+2k2 code) //#define USE_CCS811_V2 // [I2cDriver24] Enable CCS811 sensor (I2C addresses 0x5A and 0x5B) (+2k8 code) +//#define USE_ENS16x // [I2cDriver85] Enable ENS160 or ENS161 sensor (I2C addresses 0x52 and 0x53) (+3k1 of code and 524 of RAM) +//#define USE_ENS210 // [I2cDriver86] Enable ENS210 sensor (I2C addresses 0x43 and 0x44) (+4k0 of code and 944 of RAM) //#define USE_MPU6050 // [I2cDriver25] Enable MPU6050 sensor (I2C address 0x68 AD0 low or 0x69 AD0 high) (+3K3 of code and 188 Bytes of RAM) //#define USE_MGC3130 // [I2cDriver27] Enable MGC3130 Electric Field Effect Sensor (I2C address 0x42) (+2k7 code, 0k3 mem) //#define USE_MAX44009 // [I2cDriver28] Enable MAX44009 Ambient Light sensor (I2C addresses 0x4A and 0x4B) (+0k8 code) diff --git a/tasmota/include/tasmota_configurations_ESP32.h b/tasmota/include/tasmota_configurations_ESP32.h index 10258a701..f1276104d 100644 --- a/tasmota/include/tasmota_configurations_ESP32.h +++ b/tasmota/include/tasmota_configurations_ESP32.h @@ -451,6 +451,8 @@ //#define USE_MPR121 // [I2cDriver23] Enable MPR121 controller (I2C addresses 0x5A, 0x5B, 0x5C and 0x5D) in input mode for touch buttons (+1k3 code) //#define USE_CCS811 // [I2cDriver24] Enable CCS811 sensor (I2C address 0x5A) (+2k2 code) //#define USE_CCS811_V2 // [I2cDriver24] Enable CCS811 sensor (I2C addresses 0x5A and 0x5B) (+2k8 code) +//#define USE_ENS16x // [I2cDriver85] Enable ENS160 and ENS161 sensor (I2C addresses 0x52 and 0x53) (+3k1 of code and 524 of RAM) +//#define USE_ENS210 // [I2cDriver86] Enable ENS210 sensor (I2C addresses 0x43 and 0x44) (+4k0 of code and 944 of RAM) //#define USE_MPU6050 // [I2cDriver25] Enable MPU6050 sensor (I2C address 0x68 AD0 low or 0x69 AD0 high) (+3K3 of code and 188 Bytes of RAM) //#define USE_MGC3130 // [I2cDriver27] Enable MGC3130 Electric Field Effect Sensor (I2C address 0x42) (+2k7 code, 0k3 mem) //#define USE_MAX44009 // [I2cDriver28] Enable MAX44009 Ambient Light sensor (I2C addresses 0x4A and 0x4B) (+0k8 code) @@ -686,6 +688,8 @@ //#define USE_MPR121 // [I2cDriver23] Enable MPR121 controller (I2C addresses 0x5A, 0x5B, 0x5C and 0x5D) in input mode for touch buttons (+1k3 code) //#define USE_CCS811 // [I2cDriver24] Enable CCS811 sensor (I2C address 0x5A) (+2k2 code) #define USE_CCS811_V2 // [I2cDriver24] Enable CCS811 sensor (I2C addresses 0x5A and 0x5B) (+2k8 code) +//#define USE_ENS16x // [I2cDriver85] Enable ENS160 and ENS161 sensor (I2C addresses 0x52 and 0x53) (+3k1 of code and 524 of RAM) +//#define USE_ENS210 // [I2cDriver86] Enable ENS210 sensor (I2C addresses 0x43 and 0x44) (+4k0 of code and 944 of RAM) #define USE_MPU_ACCEL // [I2cDriver58] Enable MPU6886, MPU9250 6-axis MotionTracking sensor (I2C address 0x68) //#define USE_MPU6050 // [I2cDriver25] Enable MPU6050 sensor (I2C address 0x68 AD0 low or 0x69 AD0 high) (+3K3 of code and 188 Bytes of RAM) //#define USE_MGC3130 // [I2cDriver27] Enable MGC3130 Electric Field Effect Sensor (I2C address 0x42) (+2k7 code, 0k3 mem) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index d35faf0f2..33db2bdd1 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -628,6 +628,8 @@ // #define USE_MPR121 // [I2cDriver23] Enable MPR121 controller (I2C addresses 0x5A, 0x5B, 0x5C and 0x5D) in input mode for touch buttons (+1k3 code) // #define USE_CCS811 // [I2cDriver24] Enable CCS811 sensor (I2C address 0x5A) (+2k2 code) // #define USE_CCS811_V2 // [I2cDriver24] Enable CCS811 sensor (I2C addresses 0x5A and 0x5B) (+2k8 code) +// #define USE_ENS16x // [I2cDriver85] Enable ENS160 and ENS161 sensor (I2C addresses 0x52 and 0x53) (+1.9kB of code and 12B of RAM) +// #define USE_ENS210 // [I2cDriver86] Enable ENS210 sensor (I2C addresses 0x43) (+1.7kB of code and 12B of RAM) // #define USE_MPU6050 // [I2cDriver25] Enable MPU6050 sensor (I2C address 0x68 AD0 low or 0x69 AD0 high) (+3K3 of code and 188 Bytes of RAM) // #define USE_MPU6050_DMP // Enable in MPU6050 to use the DMP on the chip, should create better results (+8k6 of code) // #define USE_MGC3130 // [I2cDriver27] Enable MGC3130 Electric Field Effect Sensor (I2C address 0x42) (+2k7 code, 0k3 mem) diff --git a/tasmota/tasmota_support/support_features.ino b/tasmota/tasmota_support/support_features.ino index b942d4942..f83181f24 100644 --- a/tasmota/tasmota_support/support_features.ino +++ b/tasmota/tasmota_support/support_features.ino @@ -903,8 +903,12 @@ void ResponseAppendFeatures(void) #if defined(USE_I2C) && defined(USE_MAX17043) feature9 |= 0x02000000; #endif -// feature9 |= 0x04000000; -// feature9 |= 0x08000000; +#if defined(USE_I2C) && defined(USE_ENS16x) + feature9 |= 0x04000000; //xsns_111_ens16x.ino +#endif +#if defined(USE_I2C) && defined(USE_ENS210) + feature9 |= 0x08000000; //xsns_112_ens210.ino +#endif // feature9 |= 0x10000000; // feature9 |= 0x20000000; diff --git a/tasmota/tasmota_xsns_sensor/xsns_111_ens16x.ino b/tasmota/tasmota_xsns_sensor/xsns_111_ens16x.ino new file mode 100644 index 000000000..83d732480 --- /dev/null +++ b/tasmota/tasmota_xsns_sensor/xsns_111_ens16x.ino @@ -0,0 +1,178 @@ +/* + xsns_98_ENS16x.ino - ENS16x gas and air quality sensor support for Tasmota + + Copyright (C) 2021 Christoph Friese and Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_I2C +#ifdef USE_ENS16x + +/*********************************************************************************************\ + * ENS16x - Gas (TVOC - Total Volatile Organic Compounds) and Air Quality (CO2) + * + * Source: ScioSense + * + * I2C Address: 0x52 +\*********************************************************************************************/ + +#define XSNS_111 111 +#define XI2C_84 84 // See I2CDEVICES.md + +#define ENS16x_EVERYNSECONDS 5 +#define ENS16x_DEVICE_NAME "EN16X" +#define ENS16x_LOG "E16" +#define ENS16x_MAX_COUNT 2 + +#include "ScioSense_ENS16x.h" + +typedef struct ENS16xData_s { + ScioSense_ENS16x *ens16x; + uint16_t TVOC; + uint16_t eCO2; + uint16_t AQIS; + + uint8_t ready; + uint8_t tcnt; + uint8_t ecnt; +} ENS16xDATA_t; + +ENS16xDATA_t *ENS16xData[ENS16x_MAX_COUNT] = {nullptr, }; +uint8_t ENS16xCount = 0; + +/********************************************************************************************/ + +void ens16xDetect(void) +{ + ENS16xCount = 0; + ENS16xDATA_t *pENS16X; + ScioSense_ENS16x *ens16x; + uint8_t i2caddr = ENS16x_I2CADDR_0; + for (uint8_t i = 0 ; i < ENS16x_MAX_COUNT; i++, i2caddr++) { + if (!I2cActive(i2caddr)) { + ens16x = new ScioSense_ENS16x(i2caddr); + if (!ens16x) { + AddLog(LOG_LEVEL_DEBUG, PSTR(ENS16x_LOG":@%02X create error!"), i2caddr); + continue; + } + if (!(ens16x->begin())) { + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(ENS16x_LOG":@%02X begin error!"), i2caddr); + continue; + } + if (!(ens16x->setMode(ENS16x_OPMODE_STD))) { + AddLog(LOG_LEVEL_ERROR, PSTR(ENS16x_LOG":@%02X setmode error!"), i2caddr); + continue; + } + pENS16X = (ENS16xDATA_t*)calloc(1, sizeof(ENS16xDATA_t)); + if (!pENS16X) { + AddLog(LOG_LEVEL_ERROR, PSTR(ENS16x_LOG":@%02X Memory error!"), i2caddr); + continue; + } + pENS16X->ens16x = ens16x; + ENS16xData[ENS16xCount] = pENS16X; + ENS16xCount++; + I2cSetActiveFound(i2caddr, ENS16x_DEVICE_NAME); + } + } +} + +void ens16xUpdate(void) // Perform every n second +{ + for (uint8_t i = 0 ; i < ENS16xCount; i++) { + ENS16xDATA_t *pENS16X = ENS16xData[i]; + pENS16X->tcnt++; + if (pENS16X->tcnt >= ENS16x_EVERYNSECONDS) { + pENS16X->tcnt = 0; + pENS16X->ready = 0; + if (pENS16X->ens16x->available()) { + pENS16X->ens16x->measure(); + pENS16X->TVOC = pENS16X->ens16x->getTVOC(); + pENS16X->eCO2 = pENS16X->ens16x->geteCO2(); + if (pENS16X->ens16x->revENS16x() > 0) pENS16X->AQIS = pENS16X->ens16x->getAQIS(); + else pENS16X->AQIS = pENS16X->ens16x->getAQI(); + + pENS16X->ready = 1; + pENS16X->ecnt = 0; + } + } else { + // failed, count up + pENS16X->ecnt++; + if (pENS16X->ecnt > 6) { + // after 30 seconds, restart + pENS16X->ens16x->begin(); + if (pENS16X->ens16x->available()) { + pENS16X->ens16x->setMode(ENS16x_OPMODE_STD); + } + } + } + } +} + +const char HTTP_SNS_ENS16x[] PROGMEM = + "{s}%s AQI{m}%d {e}" // {s} = , {m} = , {e} = + "{s}%s " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}" + "{s}%s " D_TVOC "{m}%d " D_UNIT_PARTS_PER_BILLION "{e}"; + +void ens16xShow(bool json) +{ + char name[20]; + for (uint8_t i = 0 ; i < ENS16xCount; i++) { + ENS16xDATA_t *pENS16X = ENS16xData[i]; + if (pENS16X->ready) { + snprintf_P(name, sizeof(name), (ENS16xCount > 1) ? PSTR("%s%c%d") : PSTR("%s"), ENS16x_DEVICE_NAME, IndexSeparator(), i +1); + if (json) { + ResponseAppend_P(PSTR(",\"%s\":{\"AQIS\":%d,\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TVOC "\":%d}"), name, pENS16X->AQIS, pENS16X->eCO2, pENS16X->TVOC); + #ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_ENS16x, name, pENS16X->AQIS, name, pENS16X->eCO2, name, pENS16X->TVOC); + #endif + } + } + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xsns111(uint32_t function) +{ + if (!I2cEnabled(XI2C_84)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + ens16xDetect(); + } + else if (ENS16xCount) { + switch (function) { + case FUNC_EVERY_SECOND: + ens16xUpdate(); + break; + case FUNC_JSON_APPEND: + ens16xShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + ens16xShow(0); + break; +#endif // USE_WEBSERVER + } + } + return result; +} + +#endif // USE_ENS16x +#endif // USE_I2C diff --git a/tasmota/tasmota_xsns_sensor/xsns_112_ens210.ino b/tasmota/tasmota_xsns_sensor/xsns_112_ens210.ino new file mode 100644 index 000000000..ecd6761ca --- /dev/null +++ b/tasmota/tasmota_xsns_sensor/xsns_112_ens210.ino @@ -0,0 +1,150 @@ +/* + xsns_99_ens210.ino - ENS210 gas and air quality sensor support for Tasmota + + Copyright (C) 2021 Christoph Friese and Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_I2C +#ifdef USE_ENS210 + +/*********************************************************************************************\ + * ENS210 - Temperature & Humidity sensor + * + * Source: ScioSense + * + * I2C Address: 0x43 +\*********************************************************************************************/ + +#define XSNS_112 112 +#define XI2C_85 85 // See I2CDEVICES.md + +#define ENS210_EVERYNSECONDS 5 +#define ENS210_DEVICE_NAME "EN210" +#define ENS210_LOG "E21" + +#include "ScioSense_ENS210.h" + +typedef struct ENS210DATA_s { + ScioSense_ENS210 *ens210; + + float temperature; + float humidity; + + uint8_t ready; + uint8_t tcnt; + uint8_t ecnt; +} ENS210DATA_t; + +ENS210DATA_t *ENS210data = nullptr; + +/********************************************************************************************/ + +void ens210Detect(void) +{ + uint8_t i2caddr = ENS210_I2CADDR; + if (!I2cActive(i2caddr)) + { + ScioSense_ENS210 *ens210 = new ScioSense_ENS210(i2caddr); + if (!ens210) { + AddLog(LOG_LEVEL_DEBUG, PSTR(ENS210_LOG ":@%02X create error!"), i2caddr); + return; + } + if (!ens210->begin()) { + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(ENS210_LOG ":@%02X begin error!"), i2caddr); + return; + } + if (!ens210->available()) { + AddLog(LOG_LEVEL_ERROR, PSTR(ENS210_LOG ":@%02X available error!"), i2caddr); + return; + } + ens210->setSingleMode(false); + ENS210data = (ENS210DATA_t *)calloc(1, sizeof(ENS210DATA_t)); + if (!ENS210data) { + AddLog(LOG_LEVEL_ERROR, PSTR(ENS210_LOG ":@%02X Memory error!"), i2caddr); + return; + } + ENS210data->ens210 = ens210; + I2cSetActiveFound(i2caddr, ENS210_DEVICE_NAME); + } +} + +void ens210Update(void) // Perform every n second +{ + ENS210data->tcnt++; + if (ENS210data->tcnt >= ENS210_EVERYNSECONDS) { + ENS210data->tcnt = 0; + ENS210data->ready = 0; + if (ENS210data->ens210->available()) { + ENS210data->ens210->measure(); + + ENS210data->temperature = ENS210data->ens210->getTempCelsius(); + ENS210data->humidity = ENS210data->ens210->getHumidityPercent(); + ENS210data->ready = 1; + ENS210data->ecnt = 0; + } else { + // failed, count up + ENS210data->ecnt++; + if (ENS210data->ecnt > 6) { + // after 30 seconds, restart + ENS210data->ens210->begin(); + if (ENS210data->ens210->available()) { + ENS210data->ens210->setSingleMode(false); + } + } + } + } +} + +void ens210Show(bool json) +{ + if (ENS210data->ready) { + TempHumDewShow(json, (0 == TasmotaGlobal.tele_period), ENS210_DEVICE_NAME, ENS210data->temperature, ENS210data->humidity); + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xsns112(uint32_t function) +{ + if (!I2cEnabled(XI2C_85)) { return false; } + + bool result = false; + + if (FUNC_INIT == function) { + ens210Detect(); + } + else if (ENS210data) { + switch (function) { + case FUNC_EVERY_SECOND: + ens210Update(); + break; + case FUNC_JSON_APPEND: + ens210Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + ens210Show(0); + break; +#endif // USE_WEBSERVER + } + } + return result; +} + +#endif // USE_ENS210 +#endif // USE_I2C diff --git a/tools/decode-status.py b/tools/decode-status.py index c318e5e90..982c04516 100755 --- a/tools/decode-status.py +++ b/tools/decode-status.py @@ -299,7 +299,7 @@ a_features = [[ "USE_DISPLAY_TM1650","USE_PCA9632","USE_TUYAMCUBR","USE_SEN5X", "USE_BIOPDU","USE_MCP23XXX_DRV","USE_PMSA003I","USE_LOX_O2", "USE_GDK101","USE_GM861","USE_TC74","USE_PCA9557", - "USE_SGP4X","USE_MAX17043","","", + "USE_SGP4X","USE_MAX17043","USE_ENS16x","USE_ENS210", "","","","" ]]