Skip to content
Snippets Groups Projects
Commit 0bb8ba06 authored by Callum Inglis's avatar Callum Inglis
Browse files

Add support for Co2 sensor on ESP

parent e133d7c4
No related branches found
No related tags found
3 merge requests!12CO2 Sensor, Refactoring, CAD Files, Update Config from API,!7Update sensor config from api; CO2 Sensor; Refactor & Tidy-Up,!6Update sensor config from api; CO2 Sensor; Refactor & Tidy-Up
This commit is part of merge request !6. Comments created here will be created in the context of that merge request.
#include "CCS811.h"
int CCS811::begin(void)
{
uint8_t id=0;
Wire.begin();
softReset();
delay(100);
if(readReg(CCS811_REG_HW_ID,&id,1) != 1){DBG("");
DBG("bus data access error");DBG("");
return ERR_DATA_BUS;DBG("");
}
DBG("real sensor id=");DBG(id);
if(id != CCS811_HW_ID){DBG("");
delay(1);
return ERR_IC_VERSION;
}
writeReg(CCS811_BOOTLOADER_APP_START, NULL, 0);
setMeasurementMode(0,0,eMode4);
setInTempHum(25, 50);
return ERR_OK;
}
void CCS811::softReset(){
uint8_t value[4] = {0x11, 0xE5, 0x72, 0x8A};
writeReg(CCS811_REG_SW_RESET, value, 4);
}
bool CCS811::checkDataReady()
{
int8_t status[1] = {0};
readReg(CCS811_REG_STATUS, status, 1);
DBG(status[0],HEX);
if(!((status[0] >> 3) & 0x01))
return false;
else
return true;
}
uint16_t CCS811::readBaseLine(){
uint8_t buffer[2];
readReg(CCS811_REG_BASELINE, buffer, 2);
return buffer[0]<<8|buffer[1];
}
void CCS811::writeBaseLine(uint16_t baseLine){
uint8_t buffer[2];
buffer[0] = baseLine>>8;
buffer[1] = baseLine;
writeReg(CCS811_REG_BASELINE, buffer, 2);
}
void CCS811::setMeasurementMode(uint8_t thresh, uint8_t interrupt, eDRIVE_MODE_t mode){
uint8_t measurement[1] = {0};
measurement[0] = (thresh << 2) | (interrupt << 3) | (mode << 4);
writeReg(CCS811_REG_MEAS_MODE, measurement, 1);
}
void CCS811::setMeasCycle(eCycle_t cycle){
uint8_t measurement[1] = {0};
measurement[0] = cycle << 4;
writeReg(CCS811_REG_MEAS_MODE, measurement, 1);
}
uint8_t CCS811::getMeasurementMode(){
uint8_t meas[1] = {0};
readReg(CCS811_REG_MEAS_MODE, meas, 1);
return meas[0];
}
void CCS811::setThresholds(uint16_t lowToMed, uint16_t medToHigh)
{
uint8_t buffer[] = {(uint8_t)((lowToMed >> 8) & 0xF),
(uint8_t)(lowToMed & 0xF),
(uint8_t)((medToHigh >> 8) & 0xF),
(uint8_t)(medToHigh & 0xF)};
writeReg(CCS811_REG_THRESHOLDS, buffer, 5);
uint8_t buf[1];
readReg(CCS811_REG_THRESHOLDS, buf, 1);
Serial.println(buf[0],HEX);
}
uint16_t CCS811::getCO2PPM(){
uint8_t buffer[8];
readReg(CCS811_REG_ALG_RESULT_DATA, buffer, 8);
eCO2 = (((uint16_t)buffer[0] << 8) | (uint16_t)buffer[1]);
return eCO2;
}
uint16_t CCS811::getTVOCPPB(){
uint8_t buffer[8];
readReg(CCS811_REG_ALG_RESULT_DATA, buffer, 8);
eTVOC = (((uint16_t)buffer[2] << 8) | (uint16_t)buffer[3]);
return eTVOC;
}
void CCS811::setInTempHum(float temperature, float humidity) // compensate for temperature and relative humidity
{
int _temp, _rh;
if(temperature>0)
_temp = (int)temperature + 0.5; // this will round off the floating point to the nearest integer value
else if(temperature<0) // account for negative temperatures
_temp = (int)temperature - 0.5;
_temp = _temp + 25; // temperature high byte is stored as T+25°C in the sensor's memory so the value of byte is positive
_rh = (int)humidity + 0.5; // this will round off the floating point to the nearest integer value
uint8_t envData[4];
envData[0] = _rh << 1; // shift the binary number to left by 1. This is stored as a 7-bit value
envData[1] = 0; // most significant fractional bit. Using 0 here - gives us accuracy of +/-1%. Current firmware (2016) only supports fractional increments of 0.5
envData[2] = _temp << 1;
envData[3] = 0;
writeReg(CCS811_REG_ENV_DATA, &envData, 4);
}
void CCS811::writeReg(uint8_t reg, const void* pBuf, size_t size)
{
if(pBuf == NULL){
DBG("pBuf ERROR!! : null pointer");
}
uint8_t * _pBuf = (uint8_t *)pBuf;
_pWire->beginTransmission(_deviceAddr);
_pWire->write(&reg, 1);
for(uint16_t i = 0; i < size; i++){
_pWire->write(_pBuf[i]);
}
_pWire->endTransmission();
}
uint8_t CCS811::readReg(uint8_t reg, const void* pBuf, size_t size)
{
if(pBuf == NULL){
DBG("pBuf ERROR!! : null pointer");
}
uint8_t * _pBuf = (uint8_t *)pBuf;
_pWire->beginTransmission(_deviceAddr);
_pWire->write(&reg, 1);
if( _pWire->endTransmission() != 0){
return 0;
}
_pWire->requestFrom(_deviceAddr, (uint8_t) size);
for(uint16_t i = 0; i < size; i++){
_pBuf[i] = _pWire->read();
}
_pWire->endTransmission();
return size;
}
#ifndef _CCS811_H
#define _CCS811_H
#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <Wire.h>
/*I2C ADDRESS*/
#define CCS811_I2C_ADDRESS1 0x5A
#define CCS811_I2C_ADDRESS2 0x5B
#define CCS811_REG_STATUS 0x00
#define CCS811_REG_MEAS_MODE 0x01
#define CCS811_REG_ALG_RESULT_DATA 0x02
#define CCS811_REG_RAW_DATA 0x03
#define CCS811_REG_ENV_DATA 0x05
#define CCS811_REG_NTC 0x06
#define CCS811_REG_THRESHOLDS 0x10
#define CCS811_REG_BASELINE 0x11
#define CCS811_REG_HW_ID 0x20
#define CCS811_REG_HW_VERSION 0x21
#define CCS811_REG_FW_BOOT_VERSION 0x23
#define CCS811_REG_FW_APP_VERSION 0x24
#define CCS811_REG_INTERNAL_STATE 0xA0
#define CCS811_REG_ERROR_ID 0xE0
#define CCS811_REG_SW_RESET 0xFF
#define CCS811_BOOTLOADER_APP_ERASE 0xF1
#define CCS811_BOOTLOADER_APP_DATA 0xF2
#define CCS811_BOOTLOADER_APP_VERIFY 0xF3
#define CCS811_BOOTLOADER_APP_START 0xF4
#define CCS811_HW_ID 0x81
//Open the macro to see the detailed program execution process.
//#define ENABLE_DBG
#ifdef ENABLE_DBG
#define DBG(...) {Serial.print("[");Serial.print(__FUNCTION__); Serial.print("(): "); Serial.print(__LINE__); Serial.print(" ] "); Serial.println(__VA_ARGS__);}
#else
#define DBG(...)
#endif
class CCS811
{
public:
#define ERR_OK 0 //OK
#define ERR_DATA_BUS -1 //error in data bus
#define ERR_IC_VERSION -2 //chip version mismatch
uint8_t _deviceAddr;
typedef enum{
eMode0, //Idle (Measurements are disabled in this mode)
eMode1, //Constant power mode, IAQ measurement every second
eMode2, //Pulse heating mode IAQ measurement every 10 seconds
eMode3, //Low power pulse heating mode IAQ measurement every 60 seconds
eMode4 //Constant power mode, sensor measurement every 250ms 1xx: Reserved modes (For future use)
}eDRIVE_MODE_t;
typedef enum{
eClosed, //Idle (Measurements are disabled in this mode)
eCycle_1s, //Constant power mode, IAQ measurement every second
eCycle_10s, //Pulse heating mode IAQ measurement every 10 seconds
eCycle_60s, //Low power pulse heating mode IAQ measurement every 60 seconds
eCycle_250ms //Constant power mode, sensor measurement every 250ms 1xx: Reserved modes (For future use)
}eCycle_t;
/**
* @brief Constructor
* @param Input in Wire address
*/
CCS811(TwoWire *pWire = &Wire, uint8_t deviceAddr = 0x5A){_pWire = pWire; _deviceAddr = deviceAddr;};
/**
* @brief Constructor
* @return Return 0 if initialization succeeds, otherwise return non-zero.
*/
int begin();
/**
* @brief Judge if there is data to read
* @return Return 1 if there is, otherwise return 0.
*/
bool checkDataReady();
/**
* @brief Reset sensor, clear all configured data.
*/
void softReset(),
/**
* @brief Set environment parameter
* @param temperature Set temperature value, unit: centigrade, range (-40~85℃)
* @param humidity Set humidity value, unit: RH, range (0~100)
*/
setInTempHum(float temperature, float humidity),
/**
* @brief Measurement parameter configuration
* @param thresh:0 for Interrupt mode operates normally; 1 for interrupt mode only asserts the nINT signal (driven low) if the new
* @param interrupt:0 for Interrupt generation is disabled; 1 for the nINT signal is asserted (driven low) when a new sample is ready in
* @param mode:in typedef enum eDRIVE_MODE_t
*/
setMeasurementMode(uint8_t thresh, uint8_t interrupt, eDRIVE_MODE_t mode),
/**
* @brief Measurement parameter configuration
* @param mode:in typedef enum eDRIVE_MODE_t
*/
setMeasCycle(eCycle_t cycle),
/**
* @brief Set interrupt thresholds
* @param lowToMed: interrupt triggered value in range low to middle
* @param medToHigh: interrupt triggered value in range middle to high
*/
setThresholds(uint16_t lowToMed, uint16_t medToHigh);
/**
* @brief Get current configured parameter
* @return configuration code, needs to be converted into binary code to analyze
* The 2nd: Interrupt mode (if enabled) operates normally,1: Interrupt mode (if enabled) only asserts the nINT signal (driven low) if the new
* The 3rd: Interrupt generation is disabled,1: The nINT signal is asserted (driven low) when a new sample is ready in
* The 4th: 6th: in typedef enum eDRIVE_MODE_t
*/
uint8_t getMeasurementMode();
/**
* @brief Get the current carbon dioxide concentration
* @return current carbon dioxide concentration, unit:ppm
*/
uint16_t getCO2PPM(),
/**
* @brief Get current TVOC concentration
* @return Return current TVOC concentration, unit: ppb
*/
getTVOCPPB();
uint16_t readBaseLine();
void writeBaseLine(uint16_t baseLine);
protected:
typedef struct{
/*
* The CCS811 received an I²C write request addressed to this station but with invalid register address ID
*/
uint8_t sWRITE_REG_INVALID: 1;
/*
* The CCS811 received an I²C read request to a mailbox ID that is invalid
*/
uint8_t sREAD_REG_INVALID: 1;
/*
* The CCS811 received an I²C request to write an unsupported mode to MEAS_MODE
*/
uint8_t sMEASMODE_INVALID: 1;
/*
* The sensor resistance measurement has reached or exceeded the maximum range
*/
uint8_t sMAX_RESISTANCE: 1;
/*
* The The Heater current in the CCS811 is not in range
*/
uint8_t sHEATER_FAULT: 1;
/*
* The Heater voltage is not being applied correctly
*/
uint8_t sHEATER_SUPPLY: 1;
} __attribute__ ((packed))sError_id;
typedef struct{
/*
* ALG_RESULT_DATA crosses one of the thresholds set in the THRESHOLDS register
* by more than the hysteresis value (also in the THRESHOLDS register)
*/
uint8_t sINT_THRESH: 1;
/*
* At the end of each measurement cycle (250ms, 1s, 10s, 60s) a flag is set in the
* STATUS register regardless of the setting of this bit.
*/
uint8_t sINT_DATARDY: 1;
/*
* A new sample is placed in ALG_RESULT_DATA and RAW_DATA registers and the
* DATA_READY bit in the STATUS register is set at the defined measurement interval.
*/
uint8_t sDRIVE_MODE: 3;
} __attribute__ ((packed))sMeas_mode;
typedef struct{
/*
* This bit is cleared by reading ERROR_ID
* It is not sufficient to read the ERROR field of ALG_RESULT_DATA and STATUS
*/
uint8_t sERROR: 1;
/*
* ALG_RESULT_DATA is read on the I²C interface
*/
uint8_t sDATA_READY: 1;
uint8_t sAPP_VALID: 1;
/*
* After issuing a VERIFY command the application software must wait 70ms before
* issuing any transactions to CCS811 over the I²C interface
*/
uint8_t sAPP_VERIFY: 1;
/*
* After issuing the ERASE command the application software must wait 500ms
* before issuing any transactions to the CCS811 over the I2C interface.
*/
uint8_t sAPP_ERASE: 1;
uint8_t sFW_MODE: 1;
} __attribute__ ((packed))sStatus;
void getData(void);
void writeConfig();
virtual void writeReg(uint8_t reg, const void* pBuf, size_t size);
virtual uint8_t readReg(uint8_t reg, const void* pBuf, size_t size);
private:
TwoWire *_pWire;
uint16_t eCO2;
uint16_t eTVOC;
};
#endif
#include "CCS811.h"
int CCS811::begin(void)
{
uint8_t id=0;
Wire.begin();
softReset();
delay(100);
if(readReg(CCS811_REG_HW_ID,&id,1) != 1){DBG("");
DBG("bus data access error");DBG("");
return ERR_DATA_BUS;DBG("");
}
DBG("real sensor id=");DBG(id);
if(id != CCS811_HW_ID){DBG("");
delay(1);
return ERR_IC_VERSION;
}
writeReg(CCS811_BOOTLOADER_APP_START, NULL, 0);
setMeasurementMode(0,0,eMode4);
setInTempHum(25, 50);
return ERR_OK;
}
void CCS811::softReset(){
uint8_t value[4] = {0x11, 0xE5, 0x72, 0x8A};
writeReg(CCS811_REG_SW_RESET, value, 4);
}
bool CCS811::checkDataReady()
{
int8_t status[1] = {0};
readReg(CCS811_REG_STATUS, status, 1);
DBG(status[0],HEX);
if(!((status[0] >> 3) & 0x01))
return false;
else
return true;
}
uint16_t CCS811::readBaseLine(){
uint8_t buffer[2];
readReg(CCS811_REG_BASELINE, buffer, 2);
return buffer[0]<<8|buffer[1];
}
void CCS811::writeBaseLine(uint16_t baseLine){
uint8_t buffer[2];
buffer[0] = baseLine>>8;
buffer[1] = baseLine;
writeReg(CCS811_REG_BASELINE, buffer, 2);
}
void CCS811::setMeasurementMode(uint8_t thresh, uint8_t interrupt, eDRIVE_MODE_t mode){
uint8_t measurement[1] = {0};
measurement[0] = (thresh << 2) | (interrupt << 3) | (mode << 4);
writeReg(CCS811_REG_MEAS_MODE, measurement, 1);
}
void CCS811::setMeasCycle(eCycle_t cycle){
uint8_t measurement[1] = {0};
measurement[0] = cycle << 4;
writeReg(CCS811_REG_MEAS_MODE, measurement, 1);
}
uint8_t CCS811::getMeasurementMode(){
uint8_t meas[1] = {0};
readReg(CCS811_REG_MEAS_MODE, meas, 1);
return meas[0];
}
void CCS811::setThresholds(uint16_t lowToMed, uint16_t medToHigh)
{
uint8_t buffer[] = {(uint8_t)((lowToMed >> 8) & 0xF),
(uint8_t)(lowToMed & 0xF),
(uint8_t)((medToHigh >> 8) & 0xF),
(uint8_t)(medToHigh & 0xF)};
writeReg(CCS811_REG_THRESHOLDS, buffer, 5);
uint8_t buf[1];
readReg(CCS811_REG_THRESHOLDS, buf, 1);
Serial.println(buf[0],HEX);
}
uint16_t CCS811::getCO2PPM(){
uint8_t buffer[8];
readReg(CCS811_REG_ALG_RESULT_DATA, buffer, 8);
eCO2 = (((uint16_t)buffer[0] << 8) | (uint16_t)buffer[1]);
return eCO2;
}
uint16_t CCS811::getTVOCPPB(){
uint8_t buffer[8];
readReg(CCS811_REG_ALG_RESULT_DATA, buffer, 8);
eTVOC = (((uint16_t)buffer[2] << 8) | (uint16_t)buffer[3]);
return eTVOC;
}
void CCS811::setInTempHum(float temperature, float humidity) // compensate for temperature and relative humidity
{
int _temp, _rh;
if(temperature>0)
_temp = (int)temperature + 0.5; // this will round off the floating point to the nearest integer value
else if(temperature<0) // account for negative temperatures
_temp = (int)temperature - 0.5;
_temp = _temp + 25; // temperature high byte is stored as T+25°C in the sensor's memory so the value of byte is positive
_rh = (int)humidity + 0.5; // this will round off the floating point to the nearest integer value
uint8_t envData[4];
envData[0] = _rh << 1; // shift the binary number to left by 1. This is stored as a 7-bit value
envData[1] = 0; // most significant fractional bit. Using 0 here - gives us accuracy of +/-1%. Current firmware (2016) only supports fractional increments of 0.5
envData[2] = _temp << 1;
envData[3] = 0;
writeReg(CCS811_REG_ENV_DATA, &envData, 4);
}
void CCS811::writeReg(uint8_t reg, const void* pBuf, size_t size)
{
if(pBuf == NULL){
DBG("pBuf ERROR!! : null pointer");
}
uint8_t * _pBuf = (uint8_t *)pBuf;
_pWire->beginTransmission(_deviceAddr);
_pWire->write(&reg, 1);
for(uint16_t i = 0; i < size; i++){
_pWire->write(_pBuf[i]);
}
_pWire->endTransmission();
}
uint8_t CCS811::readReg(uint8_t reg, const void* pBuf, size_t size)
{
if(pBuf == NULL){
DBG("pBuf ERROR!! : null pointer");
}
uint8_t * _pBuf = (uint8_t *)pBuf;
_pWire->beginTransmission(_deviceAddr);
_pWire->write(&reg, 1);
if( _pWire->endTransmission() != 0){
return 0;
}
_pWire->requestFrom(_deviceAddr, (uint8_t) size);
for(uint16_t i = 0; i < size; i++){
_pBuf[i] = _pWire->read();
}
_pWire->endTransmission();
return size;
}
#ifndef _CCS811_H
#define _CCS811_H
#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <Wire.h>
/*I2C ADDRESS*/
#define CCS811_I2C_ADDRESS1 0x5A
#define CCS811_I2C_ADDRESS2 0x5B
#define CCS811_REG_STATUS 0x00
#define CCS811_REG_MEAS_MODE 0x01
#define CCS811_REG_ALG_RESULT_DATA 0x02
#define CCS811_REG_RAW_DATA 0x03
#define CCS811_REG_ENV_DATA 0x05
#define CCS811_REG_NTC 0x06
#define CCS811_REG_THRESHOLDS 0x10
#define CCS811_REG_BASELINE 0x11
#define CCS811_REG_HW_ID 0x20
#define CCS811_REG_HW_VERSION 0x21
#define CCS811_REG_FW_BOOT_VERSION 0x23
#define CCS811_REG_FW_APP_VERSION 0x24
#define CCS811_REG_INTERNAL_STATE 0xA0
#define CCS811_REG_ERROR_ID 0xE0
#define CCS811_REG_SW_RESET 0xFF
#define CCS811_BOOTLOADER_APP_ERASE 0xF1
#define CCS811_BOOTLOADER_APP_DATA 0xF2
#define CCS811_BOOTLOADER_APP_VERIFY 0xF3
#define CCS811_BOOTLOADER_APP_START 0xF4
#define CCS811_HW_ID 0x81
//Open the macro to see the detailed program execution process.
//#define ENABLE_DBG
#ifdef ENABLE_DBG
#define DBG(...) {Serial.print("[");Serial.print(__FUNCTION__); Serial.print("(): "); Serial.print(__LINE__); Serial.print(" ] "); Serial.println(__VA_ARGS__);}
#else
#define DBG(...)
#endif
class CCS811
{
public:
#define ERR_OK 0 //OK
#define ERR_DATA_BUS -1 //error in data bus
#define ERR_IC_VERSION -2 //chip version mismatch
uint8_t _deviceAddr;
typedef enum{
eMode0, //Idle (Measurements are disabled in this mode)
eMode1, //Constant power mode, IAQ measurement every second
eMode2, //Pulse heating mode IAQ measurement every 10 seconds
eMode3, //Low power pulse heating mode IAQ measurement every 60 seconds
eMode4 //Constant power mode, sensor measurement every 250ms 1xx: Reserved modes (For future use)
}eDRIVE_MODE_t;
typedef enum{
eClosed, //Idle (Measurements are disabled in this mode)
eCycle_1s, //Constant power mode, IAQ measurement every second
eCycle_10s, //Pulse heating mode IAQ measurement every 10 seconds
eCycle_60s, //Low power pulse heating mode IAQ measurement every 60 seconds
eCycle_250ms //Constant power mode, sensor measurement every 250ms 1xx: Reserved modes (For future use)
}eCycle_t;
/**
* @brief Constructor
* @param Input in Wire address
*/
CCS811(TwoWire *pWire = &Wire, uint8_t deviceAddr = 0x5A){_pWire = pWire; _deviceAddr = deviceAddr;};
/**
* @brief Constructor
* @return Return 0 if initialization succeeds, otherwise return non-zero.
*/
int begin();
/**
* @brief Judge if there is data to read
* @return Return 1 if there is, otherwise return 0.
*/
bool checkDataReady();
/**
* @brief Reset sensor, clear all configured data.
*/
void softReset(),
/**
* @brief Set environment parameter
* @param temperature Set temperature value, unit: centigrade, range (-40~85℃)
* @param humidity Set humidity value, unit: RH, range (0~100)
*/
setInTempHum(float temperature, float humidity),
/**
* @brief Measurement parameter configuration
* @param thresh:0 for Interrupt mode operates normally; 1 for interrupt mode only asserts the nINT signal (driven low) if the new
* @param interrupt:0 for Interrupt generation is disabled; 1 for the nINT signal is asserted (driven low) when a new sample is ready in
* @param mode:in typedef enum eDRIVE_MODE_t
*/
setMeasurementMode(uint8_t thresh, uint8_t interrupt, eDRIVE_MODE_t mode),
/**
* @brief Measurement parameter configuration
* @param mode:in typedef enum eDRIVE_MODE_t
*/
setMeasCycle(eCycle_t cycle),
/**
* @brief Set interrupt thresholds
* @param lowToMed: interrupt triggered value in range low to middle
* @param medToHigh: interrupt triggered value in range middle to high
*/
setThresholds(uint16_t lowToMed, uint16_t medToHigh);
/**
* @brief Get current configured parameter
* @return configuration code, needs to be converted into binary code to analyze
* The 2nd: Interrupt mode (if enabled) operates normally,1: Interrupt mode (if enabled) only asserts the nINT signal (driven low) if the new
* The 3rd: Interrupt generation is disabled,1: The nINT signal is asserted (driven low) when a new sample is ready in
* The 4th: 6th: in typedef enum eDRIVE_MODE_t
*/
uint8_t getMeasurementMode();
/**
* @brief Get the current carbon dioxide concentration
* @return current carbon dioxide concentration, unit:ppm
*/
uint16_t getCO2PPM(),
/**
* @brief Get current TVOC concentration
* @return Return current TVOC concentration, unit: ppb
*/
getTVOCPPB();
uint16_t readBaseLine();
void writeBaseLine(uint16_t baseLine);
protected:
typedef struct{
/*
* The CCS811 received an I²C write request addressed to this station but with invalid register address ID
*/
uint8_t sWRITE_REG_INVALID: 1;
/*
* The CCS811 received an I²C read request to a mailbox ID that is invalid
*/
uint8_t sREAD_REG_INVALID: 1;
/*
* The CCS811 received an I²C request to write an unsupported mode to MEAS_MODE
*/
uint8_t sMEASMODE_INVALID: 1;
/*
* The sensor resistance measurement has reached or exceeded the maximum range
*/
uint8_t sMAX_RESISTANCE: 1;
/*
* The The Heater current in the CCS811 is not in range
*/
uint8_t sHEATER_FAULT: 1;
/*
* The Heater voltage is not being applied correctly
*/
uint8_t sHEATER_SUPPLY: 1;
} __attribute__ ((packed))sError_id;
typedef struct{
/*
* ALG_RESULT_DATA crosses one of the thresholds set in the THRESHOLDS register
* by more than the hysteresis value (also in the THRESHOLDS register)
*/
uint8_t sINT_THRESH: 1;
/*
* At the end of each measurement cycle (250ms, 1s, 10s, 60s) a flag is set in the
* STATUS register regardless of the setting of this bit.
*/
uint8_t sINT_DATARDY: 1;
/*
* A new sample is placed in ALG_RESULT_DATA and RAW_DATA registers and the
* DATA_READY bit in the STATUS register is set at the defined measurement interval.
*/
uint8_t sDRIVE_MODE: 3;
} __attribute__ ((packed))sMeas_mode;
typedef struct{
/*
* This bit is cleared by reading ERROR_ID
* It is not sufficient to read the ERROR field of ALG_RESULT_DATA and STATUS
*/
uint8_t sERROR: 1;
/*
* ALG_RESULT_DATA is read on the I²C interface
*/
uint8_t sDATA_READY: 1;
uint8_t sAPP_VALID: 1;
/*
* After issuing a VERIFY command the application software must wait 70ms before
* issuing any transactions to CCS811 over the I²C interface
*/
uint8_t sAPP_VERIFY: 1;
/*
* After issuing the ERASE command the application software must wait 500ms
* before issuing any transactions to the CCS811 over the I2C interface.
*/
uint8_t sAPP_ERASE: 1;
uint8_t sFW_MODE: 1;
} __attribute__ ((packed))sStatus;
void getData(void);
void writeConfig();
virtual void writeReg(uint8_t reg, const void* pBuf, size_t size);
virtual uint8_t readReg(uint8_t reg, const void* pBuf, size_t size);
private:
TwoWire *_pWire;
uint16_t eCO2;
uint16_t eTVOC;
};
#endif
#include <CCS811.h>
/*
* IIC address default 0x5A, the address becomes 0x5B if the ADDR_SEL is soldered.
*/
//CCS811 sensor(&Wire, /*IIC_ADDRESS=*/0x5A);
CCS811 sensor;
void setup(void)
{
Serial.begin(115200);
/*Wait for the chip to be initialized completely, and then exit*/
while(sensor.begin() != 0){
Serial.println("failed to init chip, please check if the chip connection is fine");
delay(1000);
}
/**
* @brief Set measurement cycle
* @param cycle:in typedef enum{
* eClosed, //Idle (Measurements are disabled in this mode)
* eCycle_1s, //Constant power mode, IAQ measurement every second
* eCycle_10s, //Pulse heating mode IAQ measurement every 10 seconds
* eCycle_60s, //Low power pulse heating mode IAQ measurement every 60 seconds
* eCycle_250ms //Constant power mode, sensor measurement every 250ms
* }eCycle_t;
*/
sensor.setMeasCycle(sensor.eCycle_250ms);
}
void loop() {
delay(1000);
if(sensor.checkDataReady() == true){
Serial.print("CO2: ");
Serial.print(sensor.getCO2PPM());
Serial.print("ppm, TVOC: ");
Serial.print(sensor.getTVOCPPB());
Serial.println("ppb");
} else {
Serial.println("Data is not ready!");
}
/*!
* @brief Set baseline
* @param get from getBaseline.ino
*/
sensor.writeBaseLine(0x847B);
//delay cannot be less than measurement cycle
//delay(1000);
}
......@@ -25,6 +25,7 @@
* ArduinoJson v6 Library - https://arduinojson.org/v6/doc/ (https://github.com/sandeepmistry/arduino-LoRa/blob/master/LICENSE)
* LoRa Library - https://github.com/sandeepmistry/arduino-LoRa/blob/master/API.md (https://github.com/bblanchon/ArduinoJson/blob/6.x/LICENSE.md)
* SHTSensor - https://github.com/Sensirion/arduino-sht (https://github.com/Sensirion/arduino-sht/blob/master/LICENSE)
* CCS811 - https://wiki.keyestudio.com/KS0457_keyestudio_CCS811_Carbon_Dioxide_Air_Quality_Sensor
*
* =========================================================================================
*
......@@ -38,21 +39,30 @@
#include <LoRa.h>
#include <SoftwareSerial.h>
#include <ArduinoJson.h>
/* #include <base64.h> */
#include "SHTSensor.h"
#include <CCS811.h>
// Debug / Polling / Transmitting Config
#define DEBUG 1
#define DEBUG 2
#define MIN_RAND_DELAY 500 // ms
#define MAX_RAND_DELAY 1250 // ms
#define MAX_TRANSMISSION_RETRIES 5 // No. Retries before recordings new values then retry
#define TX_RESERVATION_TIME 1 // How long do we require use of TX for sending packets? (Seconds)
// Poor mans memory-contrained hashmap alternative. Store index of polled count for each sensor in pollEventCountPerSensor
#define IDX_PPM10 0
#define IDX_PPM25 1
#define IDX_PPM100 2
#define IDX_TEMPERATURE 3
#define IDX_HUMIDITY 4
#define IDX_CO2 5
// Variable Values - May be updated by Gateway
int TX_AFTER_N_READINGS = 4; // No. of samples, after which Transmit Avg Readings
int POLLING_FREQUENCY = 30000; // ms Take value every POLLING_FREQUENCY ms
int pollEventCount = 0; // Number of times data has been sampled in this recording period
int pollEventCountPerSensor[6] = {0}; // Number of times each sensor has been polled in this recording period (Used for averaging values)
// Lora Config
#define ss 16 // Physical Pin 16 = D0 (Default is Physical Pin 5)
......@@ -62,34 +72,32 @@ static const int loraSpreadingFactor = 7;
static const int loraSignalBandwidth = 125E3;
static const int loraFrequency = 433E6;
// Serial PMS Config
static const int RXPin = 4, TXPin = 3;
// Temp / Humidity Sensor
SHTSensor sht;
// Particle Sensor Serial
SoftwareSerial pmsSerial(2, 3);
// LoRa Message Tracking
String sensorID = "";
byte localAddress = 0xBB;
byte destination = 0xFF;
uint32_t msgCount = 0;
// Sensor Config
static const int RXPin = 4, TXPin = 3; // Serial PMS Config
SHTSensor sht; // Temp / Humidity Sensor
SoftwareSerial pmsSerial(2, 3); // Particle Sensor Serial
CCS811 co2Sensor; // CO2 Sensor
// Immediate Sensor Data
double temperature; // Sensor Values - Temp
double humidity; // Sensor Values - Humidity
double co2; // Sensor Values - Co2
uint32_t ppm10; // Sensor Values - Particulate
uint32_t ppm25; // Sensor Values - Particulate
uint32_t ppm100; // Sensor Values - Particulate
int32_t ppm10; // Sensor Values - Particulate
int32_t ppm25; // Sensor Values - Particulate
int32_t ppm100; // Sensor Values - Particulate
// Partial Sensor Data
struct pms5003data {
uint16_t framelen;
uint16_t pm10_standard, pm25_standard, pm100_standard;
uint16_t pm10_env, pm25_env, pm100_env;
uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
int32_t pm10_standard, pm25_standard, pm100_standard;
int32_t pm10_env, pm25_env, pm100_env;
int32_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
uint16_t unused;
uint16_t checksum;
};
......@@ -132,10 +140,41 @@ double getHumidity() {
}
/**
* TODO getCo2()
* Setup CO2 Sensor
*/
bool setupCO2() {
if (co2Sensor.begin() != 0) {
Serial.println("[-] CO2 Init Failed");
return false;
}
/**
* @brief Set measurement cycle
* @param cycle:in typedef enum{
* eClosed, //Idle (Measurements are disabled in this mode)
* eCycle_1s, //Constant power mode, IAQ measurement every second
* eCycle_10s, //Pulse heating mode IAQ measurement every 10 seconds
* eCycle_60s, //Low power pulse heating mode IAQ measurement every 60 seconds
* eCycle_250ms //Constant power mode, sensor measurement every 250ms
* }eCycle_t;
*/
co2Sensor.setMeasCycle(co2Sensor.eCycle_250ms);
return true;
}
/**
* Get CO2 reading, returns -1 on error
*/
double getCo2() {
return 0;
double c = -1;
co2Sensor.writeBaseLine(0x847B);
if(co2Sensor.checkDataReady() == true){
c = co2Sensor.getCO2PPM();
}
return c; // -1 on error
}
......@@ -526,6 +565,28 @@ boolean transmitData(DynamicJsonDocument payload) {
return true;
}
/**
* Add recorded values to the total, do not store false/failed/invalid values, e.g. -1
*/
double addValueToTotal(double avgValue, double newValue, int idx_sensor_count) {
if (newValue == -1) {
return avgValue;
}
pollEventCountPerSensor[idx_sensor_count] += 1;
return avgValue + newValue;
}
double averageValueByNumberPolls(double value, int idx_sensor_count) {
// Ensure we do not divide by 0
if (pollEventCountPerSensor[idx_sensor_count] == 0) {
return -1;
}
// Otherwise divide total by number of times we sampled this value
return value / pollEventCountPerSensor[idx_sensor_count];
}
void setup() {
delay(1000);
......@@ -534,64 +595,59 @@ void setup() {
pmsSerial.begin(9600); // Partical Sensor
// Setup Hardware
if (!setupSHT()) { while(1); } // Temp/Humidity - Die on Error
if (!setupLoRa()) { while(1); } // Die on error
// Setup Hardware TODO Handle broken sensor!
if (!setupSHT()) { /*while(1);*/ } // Temp/Humidity - Die on Error
if (!setupCO2()) { /*while(1);*/ }
if (!setupLoRa()) { /*while(1);*/ } // Die on error
}
// Main
void loop() {
// TODO Gather Sensor Data
void loop() {
delay(POLLING_FREQUENCY);
pollEventCount++;
// Error reading PMS Data, ignore on this round
if (!readPMSdata(&pmsSerial)) {
data.pm10_standard = 0;
data.pm25_standard = 0;
data.pm100_standard = 0;
// Get values from sensors
double instantTemperature = getTemperature(); // Prevent polling of sensors too frequently when debugging
double instantHumidity = getHumidity();
double instantCo2 = getCo2();
int instantPPM10, instantPPM25, instantPPM100;
if (!readPMSdata(&pmsSerial)) { // Error reading PMS Data, ignore on this round
instantPPM10 = instantPPM25 = instantPPM100 = -1;
} else {
instantPPM10 = data.pm10_standard; // Standard (_standard) or Environmental (_env) readings for Particulate Data
instantPPM25 = data.pm25_standard;
instantPPM100 = data.pm100_standard;
}
pollEventCount++;
if (DEBUG) {
Serial.println();
Serial.print(String(pollEventCount) + ") ");
Serial.print("PM 1.0: "); Serial.print(data.pm10_standard);
Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_standard);
Serial.print("\t\tPM 10: "); Serial.print(data.pm100_standard);
Serial.print("\t\tTemp: "); Serial.print(getTemperature());
Serial.print("\t\tHumidity: "); Serial.print(getHumidity());
Serial.print("\t\tCo2: "); Serial.print(getCo2());
Serial.println(); Serial.print(String(pollEventCount) + ") "); Serial.print("PM 1.0: "); Serial.print(instantPPM10); Serial.print("\t\tPM 2.5: "); Serial.print(instantPPM25); Serial.print("\t\tPM 10: "); Serial.print(instantPPM100);
Serial.print("\t\tTemp: "); Serial.print(instantTemperature); Serial.print("\t\tHumidity: "); Serial.print(instantHumidity); Serial.print("\t\tCo2: "); Serial.print(instantCo2);
}
// Add to Average
temperature += getTemperature();
humidity += getHumidity();
co2 += getCo2();
ppm10 = ppm10 + data.pm10_standard; // Standard (_standard) or Environmental (_env) readings for Particulate Data
ppm25 = ppm25 + data.pm25_standard;
ppm100 = ppm100 + data.pm100_standard;
// Add to running total, we will divide by the number of times samples later on to get an average over sample period
temperature = addValueToTotal(temperature, instantTemperature, IDX_TEMPERATURE);
humidity = addValueToTotal(humidity, instantHumidity, IDX_HUMIDITY);
co2 = addValueToTotal(co2, instantCo2, IDX_CO2);
ppm10 = addValueToTotal(ppm10, instantPPM10, IDX_PPM10);
ppm25 = addValueToTotal(ppm25, instantPPM25, IDX_PPM25);
ppm100 = addValueToTotal(ppm100, instantPPM100, IDX_PPM100);
// If we should now transmit
if (pollEventCount >= TX_AFTER_N_READINGS) {
// Average Values over recording period
double avgTemperature = temperature / pollEventCount;
double avgHumidity = humidity / pollEventCount;
double avgCo2 = co2 / pollEventCount;
uint32_t avgPpm10 = ppm10 / pollEventCount;
uint32_t avgPpm25 = ppm25 / pollEventCount;
uint32_t avgPpm100 = ppm100 / pollEventCount;
double avgTemperature = averageValueByNumberPolls(temperature, IDX_TEMPERATURE);
double avgHumidity = averageValueByNumberPolls(humidity, IDX_HUMIDITY);
double avgCo2 = averageValueByNumberPolls(co2, IDX_CO2);
int avgPpm10 = averageValueByNumberPolls(ppm10, IDX_PPM10);
int avgPpm25 = averageValueByNumberPolls(ppm25, IDX_PPM25);
int avgPpm100 = averageValueByNumberPolls(ppm100, IDX_PPM100);
if (DEBUG) {
Serial.println("");
Serial.print("Avg ppm10: "); Serial.print(avgPpm10);
Serial.print("\t\tAvg ppm25: "); Serial.print(avgPpm25);
Serial.print("\t\tAvg ppm100: "); Serial.print(avgPpm100);
Serial.print("\t\tAvg Temp: "); Serial.print(avgTemperature);
Serial.print("\t\tAvg Humidity: "); Serial.print(avgHumidity);
Serial.print("\t\tAvg Co2: "); Serial.print(avgCo2);
Serial.print("\t\tChip ID: "); Serial.println(getSensorUID());
Serial.println("");
Serial.println(""); Serial.print("Avg ppm10: "); Serial.print(avgPpm10); Serial.print("\t\tAvg ppm25: "); Serial.print(avgPpm25); Serial.print("\t\tAvg ppm100: "); Serial.print(avgPpm100);
Serial.print("\t\tAvg Temp: "); Serial.print(avgTemperature); Serial.print("\t\tAvg Humidity: "); Serial.print(avgHumidity); Serial.print("\t\tAvg Co2: "); Serial.print(avgCo2);
Serial.print("\t\tChip ID: "); Serial.println(getSensorUID()); Serial.println("");
}
// Prepare Data For Send
......@@ -621,6 +677,4 @@ void loop() {
Serial.println("[-] Failed to send packet, max retries reached. Aborting");
return;
}
delay(POLLING_FREQUENCY);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment