Add ENS16x (air quality) and ENS210 (temp & RH) sensors (#19479)

* Add files via upload

Added ENS16x library enabling read-out of ENS160 and ENS161 sensor component (follow-up of CCS811 and iAQcore)
Added ENS210 library to read out ENS210 temperature & humidity sensor

* Add files via upload

Add air quality sensor readout for ENS160 and ENS161 checking two possible I2C addresses (follow up sensor for CCS811 and iAQcore)
Add temperature and humidity sensor readout checking two possible I2C addresses

* Update BUILDS.md

Add USE_ENS16x and USE_ENS210

* Update decode-status.py

Add USE_ENS16x and ENS210

* Update I2CDEVICES.md

Add USE_ENS16x and USE_ENS210

* Update my_user_config.h

Add USE_ENS16x and USE_ENS210

* Update support_features.ino

Add USE_ENS16x and USE_ENS210

* Update tasmota_configurations.h

Add USE_ENS16x and ENS210

* Update tasmota_configurations_ESP32.h

Add USE_ENS16x and USE_ENS210

* Update xsns_111_ens16x.ino

Corrected I2X number

* Update xsns_112_ens210.ino

Corrected I2C number

* Disable USE_ENS16x and USE_ENS210 by default

* Added code size information

* cut down in libs

* optimize tasmota side

* fix ens16x web display

* final fix on alternate addresses

* update code & RAM usage

---------

Co-authored-by: Barbudor <barbudor@barbudor.net>
This commit is contained in:
Christoph Friese 2023-09-24 18:30:15 +02:00 committed by GitHub
parent 0d7c2dee72
commit 5d97a73ddf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 2070 additions and 3 deletions

View File

@ -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 | - | - / - | - | - | - | - |

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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=*

View File

@ -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 <n> 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; i<read_now; i++){
buf[pos] = Wire.read();
pos++;
}
}
return result;
}
/**************************************************************************/
uint8_t ScioSense_ENS16x::write8(uint8_t addr, byte reg, byte value) {
uint8_t result = this->write(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<num; i++) {
Serial.print(" 0x");
Serial.print(buf[i], HEX);
}
Serial.println();
}
#endif
Wire.beginTransmission((uint8_t)addr);
Wire.write((uint8_t)reg);
Wire.write((uint8_t *)buf, num);
uint8_t result = Wire.endTransmission();
return result;
}
/**************************************************************************/

View File

@ -0,0 +1,212 @@
/*
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
*/
#ifndef __SCIOSENSE_ENS16x_H_
#define __SCIOSENSE_ENS16x_H_
#define ENS16x_DISABLE_DEBUG
#define ENS16x_DISABLE_ENHANCED_FEATURES
#if (ARDUINO >= 100)
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <Wire.h>
// 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 <n> 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

View File

@ -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.

View File

@ -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

View File

@ -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=*

View File

@ -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 <assert.h>
// 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<<CRC7WIDTH) ) {
if( bit & val ) val ^= pol;
bit >>= 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<correction && correction<+1*64 ); // A correction of more than 1 Kelvin does not make sense (but the 1K is arbitrary)
this->_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; i<read_now; i++){
for(int i=0; i<num; i++){
buf[pos] = Wire.read();
#ifndef ENS210_DISABLE_DEBUG
if (debugENS210) {
Serial.print("Pos: ");
Serial.print(pos);
Serial.print(" - Value: 0x");
Serial.println(buf[pos], HEX);
}
#endif
pos++;
}
}
return result;
}
/**************************************************************************/
uint8_t ScioSense_ENS210::write8(uint8_t addr, byte reg, byte value)
{
uint8_t result = this->write(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<num; i++) {
Serial.print(" 0x");
Serial.print(buf[i], HEX);
}
Serial.println();
}
#endif
Wire.beginTransmission((uint8_t)addr);
Wire.write((uint8_t)reg);
Wire.write((uint8_t *)buf, num);
uint8_t result = Wire.endTransmission();
return result;
}

View File

@ -0,0 +1,149 @@
/*
ScioSense_ENS210.h - Library for the ENS210 relative humidity and temperature sensor with I2C interface from ScioSense
2020 Apr 06 v3 Chrsitoph 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
*/
#ifndef __SCIOSENSE_ENS210_H_
#define __SCIOSENSE_ENS210_H_
#define ENS210_DISABLE_DEBUG
#define ENS210_DISABLE_ENHANCED_FEATURES
#if (ARDUINO >= 100)
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <Wire.h>
#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<<DATA7WIDTH)-1) // 0b 0 1111 1111 1111 1111
#define DATA7MSB (1UL<<(DATA7WIDTH-1)) // 0b 1 0000 0000 0000 0000
// Measurement status as output by `measure()` and `extract()`.
// Note that the ENS210 provides a "value" (`t_val` or `h_val` each 24 bit).
// A "value" consists of a payload (17 bit) and a CRC (7 bit) over that payload.
// The payload consists of a valid flag (1 bit) and the actual measurement "data" (`t_data` or `h_data`, 16 bit)
#define ENS210_STATUS_I2CERROR 4 // There was an I2C communication error, `read`ing the value.
#define ENS210_STATUS_CRCERROR 3 // The value was read, but the CRC over the payload (valid and data) does not match.
#define ENS210_STATUS_INVALID 2 // The value was read, the CRC matches, but the data is invalid (e.g. the measurement was not yet finished).
#define ENS210_STATUS_OK 1 // The value was read, the CRC matches, and data is valid.
class ScioSense_ENS210 {
public:
ScioSense_ENS210(uint8_t slaveaddr = ENS210_I2CADDR);
#ifndef ENS210_DISABLE_ENHANCED_FEATURES
void setI2C(uint8_t sda, uint8_t scl);
void changeAddr(uint8_t oldAddr, uint8_t newAddr);
#endif
bool begin(bool debug=false); // init i2c interface, get partID und uID. Returns false on I2C problems or wrong PART_ID.
bool setSingleMode(bool enable); // false for continuous mode / true for single shot measurement. Returns false on I2C problems.
bool available() { return this->_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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
"{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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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",
"","","",""
]]