From 5009ec056399b674fab5251d27cd14ae7e474bbd Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Wed, 26 Dec 2018 23:01:54 +0100 Subject: [PATCH] initial --- include/bme680mems.h | 56 +++-- lib/Bosch-BSEC/bsec.cpp | 493 ++++++++++++++++++++++++++++++++++++++++ lib/Bosch-BSEC/bsec.h | 230 +++++++++++++++++++ platformio.ini | 8 +- src/bme680mems.cpp | 214 +++++++---------- src/hal/generic.h | 2 +- src/hal/octopus32.h | 4 +- src/hal/ttgobeam_new.h | 7 +- 8 files changed, 848 insertions(+), 166 deletions(-) create mode 100644 lib/Bosch-BSEC/bsec.cpp create mode 100644 lib/Bosch-BSEC/bsec.h diff --git a/include/bme680mems.h b/include/bme680mems.h index ad66c7cb..5ac87588 100644 --- a/include/bme680mems.h +++ b/include/bme680mems.h @@ -3,30 +3,52 @@ #include "globals.h" #include -#include "bsec_integration.h" #include "irqhandler.h" - -extern const uint8_t bsec_config_iaq[454]; +#include "bsec.h" extern bmeStatus_t bme_status; // Make struct for storing gps data globally available extern TaskHandle_t BmeTask; +// --- Bosch BSEC library configuration --- +// 3,3V supply voltage; 3s max time between sensor_control calls; 4 days +// calibration. Change this const if not applicable for your application (see +// BME680 datasheet) +const uint8_t bsec_config_iaq[454] = { + 1, 7, 4, 1, 61, 0, 0, 0, 0, 0, 0, 0, 174, 1, 0, + 0, 48, 0, 1, 0, 137, 65, 0, 63, 205, 204, 204, 62, 0, 0, + 64, 63, 205, 204, 204, 62, 0, 0, 225, 68, 0, 192, 168, 71, 64, + 49, 119, 76, 0, 0, 0, 0, 0, 80, 5, 95, 0, 0, 0, 0, + 0, 0, 0, 0, 28, 0, 2, 0, 0, 244, 1, 225, 0, 25, 0, + 0, 128, 64, 0, 0, 32, 65, 144, 1, 0, 0, 112, 65, 0, 0, + 0, 63, 16, 0, 3, 0, 10, 215, 163, 60, 10, 215, 35, 59, 10, + 215, 35, 59, 9, 0, 5, 0, 0, 0, 0, 0, 1, 88, 0, 9, + 0, 229, 208, 34, 62, 0, 0, 0, 0, 0, 0, 0, 0, 218, 27, + 156, 62, 225, 11, 67, 64, 0, 0, 160, 64, 0, 0, 0, 0, 0, + 0, 0, 0, 94, 75, 72, 189, 93, 254, 159, 64, 66, 62, 160, 191, + 0, 0, 0, 0, 0, 0, 0, 0, 33, 31, 180, 190, 138, 176, 97, + 64, 65, 241, 99, 190, 0, 0, 0, 0, 0, 0, 0, 0, 167, 121, + 71, 61, 165, 189, 41, 192, 184, 30, 189, 64, 12, 0, 10, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 229, 0, 254, 0, 2, 1, 5, 48, + 117, 100, 0, 44, 1, 112, 23, 151, 7, 132, 3, 197, 0, 92, 4, + 144, 1, 64, 1, 64, 1, 144, 1, 48, 117, 48, 117, 48, 117, 48, + 117, 100, 0, 100, 0, 100, 0, 48, 117, 48, 117, 48, 117, 100, 0, + 100, 0, 48, 117, 48, 117, 100, 0, 100, 0, 100, 0, 100, 0, 48, + 117, 48, 117, 48, 117, 100, 0, 100, 0, 100, 0, 48, 117, 48, 117, + 100, 0, 100, 0, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, + 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, + 44, 1, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, + 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, + 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, + 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 255, 255, + 255, 255, 255, 255, 255, 255, 220, 5, 220, 5, 220, 5, 255, 255, 255, + 255, 255, 255, 220, 5, 220, 5, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 44, 1, 0, 0, 0, 0, + 239, 79, 0, 0}; + int bme_init(); void bme_loop(void *pvParameters); -int8_t i2c_read(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, - uint16_t len); -int8_t i2c_write(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, - uint16_t len); -void output_ready(int64_t timestamp, float iaq, uint8_t iaq_accuracy, - float temperature, float humidity, float pressure, - float raw_temperature, float raw_humidity, float gas, - bsec_library_return_t bsec_status, float static_iaq, - float co2_equivalent, float breath_voc_equivalent); -uint32_t state_load(uint8_t *state_buffer, uint32_t n_buffer); -void state_save(const uint8_t *state_buffer, uint32_t length); -uint32_t config_load(uint8_t *config_buffer, uint32_t n_buffer); -void user_delay_ms(uint32_t period); -int64_t get_timestamp_us(); +int checkIaqSensorStatus(void); #endif \ No newline at end of file diff --git a/lib/Bosch-BSEC/bsec.cpp b/lib/Bosch-BSEC/bsec.cpp new file mode 100644 index 00000000..d9ac1868 --- /dev/null +++ b/lib/Bosch-BSEC/bsec.cpp @@ -0,0 +1,493 @@ +/** + * Copyright (C) 2017 - 2018 Bosch Sensortec GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of the copyright holder nor the names of the + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + * + * The information provided is believed to be accurate and reliable. + * The copyright holder assumes no responsibility + * for the consequences of use + * of such information nor for any infringement of patents or + * other rights of third parties which may result from its use. + * No license is granted by implication or otherwise under any patent or + * patent rights of the copyright holder. + * + * @file bsec.cpp + * @date 31 Jan 2018 + * @version 1.0 + * + */ + +#include "bsec.h" + +TwoWire* Bsec::wireObj = NULL; +SPIClass* Bsec::spiObj = NULL; + +/** + * @brief Constructor + */ +Bsec::Bsec() +{ + nextCall = 0; + version.major = 0; + version.minor = 0; + version.major_bugfix = 0; + version.minor_bugfix = 0; + millisOverflowCounter = 0; + lastTime = 0; + bme680Status = BME680_OK; + outputTimestamp = 0; + _tempOffset = 0.0f; + status = BSEC_OK; + zeroOutputs(); +} + +/** + * @brief Function to initialize the BSEC library and the BME680 sensor + */ +void Bsec::begin(uint8_t devId, enum bme680_intf intf, bme680_com_fptr_t read, bme680_com_fptr_t write, bme680_delay_fptr_t idleTask) +{ + _bme680.dev_id = devId; + _bme680.intf = intf; + _bme680.read = read; + _bme680.write = write; + _bme680.delay_ms = idleTask; + _bme680.amb_temp = 25; + _bme680.power_mode = BME680_FORCED_MODE; + + beginCommon(); +} + +/** + * @brief Function to initialize the BSEC library and the BME680 sensor + */ +void Bsec::begin(uint8_t i2cAddr, TwoWire &i2c) +{ + _bme680.dev_id = i2cAddr; + _bme680.intf = BME680_I2C_INTF; + _bme680.read = Bsec::i2cRead; + _bme680.write = Bsec::i2cWrite; + _bme680.delay_ms = Bsec::delay_ms; + _bme680.amb_temp = 25; + _bme680.power_mode = BME680_FORCED_MODE; + + Bsec::wireObj = &i2c; + Bsec::wireObj->begin(); + + beginCommon(); +} + +/** + * @brief Function to initialize the BSEC library and the BME680 sensor + */ +void Bsec::begin(uint8_t chipSelect, SPIClass &spi) +{ + _bme680.dev_id = chipSelect; + _bme680.intf = BME680_SPI_INTF; + _bme680.read = Bsec::spiTransfer; + _bme680.write = Bsec::spiTransfer; + _bme680.delay_ms = Bsec::delay_ms; + _bme680.amb_temp = 25; + _bme680.power_mode = BME680_FORCED_MODE; + + pinMode(chipSelect, OUTPUT); + digitalWrite(chipSelect, HIGH); + Bsec::spiObj = &spi; + Bsec::spiObj->begin(); + + beginCommon(); +} + +/** + * @brief Common code for the begin function + */ +void Bsec::beginCommon(void) +{ + status = bsec_init(); + + getVersion(); + + bme680Status = bme680_init(&_bme680); +} + +/** + * @brief Function that sets the desired sensors and the sample rates + */ +void Bsec::updateSubscription(bsec_virtual_sensor_t sensorList[], uint8_t nSensors, float sampleRate) +{ + bsec_sensor_configuration_t virtualSensors[BSEC_NUMBER_OUTPUTS], + sensorSettings[BSEC_MAX_PHYSICAL_SENSOR]; + uint8_t nVirtualSensors = 0, nSensorSettings = BSEC_MAX_PHYSICAL_SENSOR; + + for (uint8_t i = 0; i < nSensors; i++) { + virtualSensors[nVirtualSensors].sensor_id = sensorList[i]; + virtualSensors[nVirtualSensors].sample_rate = sampleRate; + nVirtualSensors++; + } + + status = bsec_update_subscription(virtualSensors, nVirtualSensors, sensorSettings, &nSensorSettings); + return; +} + +/** + * @brief Callback from the user to trigger reading of data from the BME680, process and store outputs + */ +bool Bsec::run(void) +{ + bool newData = false; + /* Check if the time has arrived to call do_steps() */ + int64_t callTimeMs = getTimeMs(); + + if (callTimeMs >= nextCall) { + + bsec_bme_settings_t bme680Settings; + + int64_t callTimeNs = callTimeMs * INT64_C(1000000); + + status = bsec_sensor_control(callTimeNs, &bme680Settings); + if (status < BSEC_OK) + return false; + + nextCall = bme680Settings.next_call / INT64_C(1000000); // Convert from ns to ms + + bme680Status = setBme680Config(bme680Settings); + if (bme680Status != BME680_OK) { + return false; + } + + bme680Status = bme680_set_sensor_mode(&_bme680); + if (bme680Status != BME680_OK) { + return false; + } + + /* Wait for measurement to complete */ + uint16_t meas_dur = 0; + + bme680_get_profile_dur(&meas_dur, &_bme680); + delay_ms(meas_dur); + + newData = readProcessData(callTimeNs, bme680Settings); + } + + return newData; +} + +/** + * @brief Function to get the state of the algorithm to save to non-volatile memory + */ +void Bsec::getState(uint8_t *state) +{ + uint8_t workBuffer[BSEC_MAX_STATE_BLOB_SIZE]; + uint32_t n_serialized_state = BSEC_MAX_STATE_BLOB_SIZE; + status = bsec_get_state(0, state, BSEC_MAX_STATE_BLOB_SIZE, workBuffer, BSEC_MAX_STATE_BLOB_SIZE, &n_serialized_state); +} + +/** + * @brief Function to set the state of the algorithm from non-volatile memory + */ +void Bsec::setState(uint8_t *state) +{ + uint8_t workBuffer[BSEC_MAX_STATE_BLOB_SIZE]; + + status = bsec_set_state(state, BSEC_MAX_STATE_BLOB_SIZE, workBuffer, BSEC_MAX_STATE_BLOB_SIZE); +} + +/** + * @brief Function to set the configuration of the algorithm from memory + */ +void Bsec::setConfig(const uint8_t *state) +{ + uint8_t workBuffer[BSEC_MAX_PROPERTY_BLOB_SIZE]; + + status = bsec_set_configuration(state, BSEC_MAX_PROPERTY_BLOB_SIZE, workBuffer, sizeof(workBuffer)); +} + +/* Private functions */ + +/** + * @brief Get the version of the BSEC library + */ +void Bsec::getVersion(void) +{ + bsec_get_version(&version); +} + +/** + * @brief Read data from the BME680 and process it + */ +bool Bsec::readProcessData(int64_t currTimeNs, bsec_bme_settings_t bme680Settings) +{ + bme680Status = bme680_get_sensor_data(&_data, &_bme680); + if (bme680Status != BME680_OK) { + return false; + } + + bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; // Temp, Pres, Hum & Gas + uint8_t nInputs = 0, nOutputs = 0; + + if (_data.status & BME680_NEW_DATA_MSK) { + if (bme680Settings.process_data & BSEC_PROCESS_TEMPERATURE) { + inputs[nInputs].sensor_id = BSEC_INPUT_TEMPERATURE; + inputs[nInputs].signal = _data.temperature; + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + /* Temperature offset from the real temperature due to external heat sources */ + inputs[nInputs].sensor_id = BSEC_INPUT_HEATSOURCE; + inputs[nInputs].signal = _tempOffset; + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + } + if (bme680Settings.process_data & BSEC_PROCESS_HUMIDITY) { + inputs[nInputs].sensor_id = BSEC_INPUT_HUMIDITY; + inputs[nInputs].signal = _data.humidity; + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + } + if (bme680Settings.process_data & BSEC_PROCESS_PRESSURE) { + inputs[nInputs].sensor_id = BSEC_INPUT_PRESSURE; + inputs[nInputs].signal = _data.pressure; + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + } + if (bme680Settings.process_data & BSEC_PROCESS_GAS) { + inputs[nInputs].sensor_id = BSEC_INPUT_GASRESISTOR; + inputs[nInputs].signal = _data.gas_resistance; + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + } + } + + if (nInputs > 0) { + nOutputs = BSEC_NUMBER_OUTPUTS; + bsec_output_t _outputs[BSEC_NUMBER_OUTPUTS]; + + status = bsec_do_steps(inputs, nInputs, _outputs, &nOutputs); + if (status != BSEC_OK) + return false; + + zeroOutputs(); + + if (nOutputs > 0) { + outputTimestamp = _outputs[0].time_stamp / 1000000; // Convert from ns to ms + + for (uint8_t i = 0; i < nOutputs; i++) { + switch (_outputs[i].sensor_id) { + case BSEC_OUTPUT_IAQ: + iaqEstimate = _outputs[i].signal; + iaqAccuracy = _outputs[i].accuracy; + break; + case BSEC_OUTPUT_STATIC_IAQ: + staticIaq = _outputs[i].signal; + staticIaqAccuracy = _outputs[i].accuracy; + break; + case BSEC_OUTPUT_CO2_EQUIVALENT: + co2Equivalent = _outputs[i].signal; + co2Accuracy = _outputs[i].accuracy; + break; + case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT: + breathVocEquivalent = _outputs[i].signal; + breathVocAccuracy = _outputs[i].accuracy; + break; + case BSEC_OUTPUT_RAW_TEMPERATURE: + rawTemperature = _outputs[i].signal; + break; + case BSEC_OUTPUT_RAW_PRESSURE: + pressure = _outputs[i].signal; + break; + case BSEC_OUTPUT_RAW_HUMIDITY: + rawHumidity = _outputs[i].signal; + break; + case BSEC_OUTPUT_RAW_GAS: + gasResistance = _outputs[i].signal; + break; + case BSEC_OUTPUT_STABILIZATION_STATUS: + stabStatus = _outputs[i].signal; + break; + case BSEC_OUTPUT_RUN_IN_STATUS: + runInStatus = _outputs[i].signal; + break; + case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE: + temperature = _outputs[i].signal; + break; + case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY: + humidity = _outputs[i].signal; + break; + case BSEC_OUTPUT_COMPENSATED_GAS: + compGasValue = _outputs[i].signal; + compGasAccuracy = _outputs[i].accuracy; + break; + case BSEC_OUTPUT_GAS_PERCENTAGE: + gasPercentage = _outputs[i].signal; + gasPercentageAcccuracy = _outputs[i].accuracy; + break; + default: + break; + } + } + return true; + } + } + + return false; +} + +/** + * @brief Set the BME680 sensor's configuration + */ +int8_t Bsec::setBme680Config(bsec_bme_settings_t bme680Settings) +{ + _bme680.gas_sett.run_gas = bme680Settings.run_gas; + _bme680.tph_sett.os_hum = bme680Settings.humidity_oversampling; + _bme680.tph_sett.os_temp = bme680Settings.temperature_oversampling; + _bme680.tph_sett.os_pres = bme680Settings.pressure_oversampling; + _bme680.gas_sett.heatr_temp = bme680Settings.heater_temperature; + _bme680.gas_sett.heatr_dur = bme680Settings.heating_duration; + uint16_t desired_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_FILTER_SEL + | BME680_GAS_SENSOR_SEL; + return bme680_set_sensor_settings(desired_settings, &_bme680); +} + +/** + * @brief Function to zero the outputs + */ +void Bsec::zeroOutputs(void) +{ + temperature = 0.0f; + pressure = 0.0f; + humidity = 0.0f; + gasResistance = 0.0f; + rawTemperature = 0.0f; + rawHumidity = 0.0f; + stabStatus = 0.0f; + runInStatus = 0.0f; + iaqEstimate = 0.0f; + iaqAccuracy = 0; + staticIaq = 0.0f; + staticIaqAccuracy = 0; + co2Equivalent = 0.0f; + co2Accuracy = 0; + breathVocEquivalent = 0.0f; + breathVocAccuracy = 0; + compGasValue = 0.0f; + compGasAccuracy = 0; + gasPercentage = 0.0f; + gasPercentageAcccuracy = 0; +} + +/** + * @brief Function to calculate an int64_t timestamp in milliseconds + */ +int64_t Bsec::getTimeMs(void) +{ + int64_t timeMs = millis(); + + if (lastTime > timeMs) { // An overflow occured + lastTime = timeMs; + millisOverflowCounter++; + } + + return timeMs + (millisOverflowCounter * 0xFFFFFFFF); +} + +/** + @brief Task that delays for a ms period of time + */ +void Bsec::delay_ms(uint32_t period) +{ + // Wait for a period amount of ms + // The system may simply idle, sleep or even perform background tasks + delay(period); +} + +/** + @brief Callback function for reading registers over I2C + */ +int8_t Bsec::i2cRead(uint8_t devId, uint8_t regAddr, uint8_t *regData, uint16_t length) +{ + uint16_t i; + int8_t rslt = 0; + if(Bsec::wireObj) { + Bsec::wireObj->beginTransmission(devId); + Bsec::wireObj->write(regAddr); + rslt = Bsec::wireObj->endTransmission(); + Bsec::wireObj->requestFrom((int) devId, (int) length); + for (i = 0; (i < length) && Bsec::wireObj->available(); i++) { + regData[i] = Bsec::wireObj->read(); + } + } else { + rslt = -1; + } + return rslt; +} + +/** + * @brief Callback function for writing registers over I2C + */ +int8_t Bsec::i2cWrite(uint8_t devId, uint8_t regAddr, uint8_t *regData, uint16_t length) +{ + uint16_t i; + int8_t rslt = 0; + if(Bsec::wireObj) { + Bsec::wireObj->beginTransmission(devId); + Bsec::wireObj->write(regAddr); + for (i = 0; i < length; i++) { + Bsec::wireObj->write(regData[i]); + } + rslt = Bsec::wireObj->endTransmission(); + } else { + rslt = -1; + } + + return rslt; +} + +/** + * @brief Callback function for reading and writing registers over SPI + */ +int8_t Bsec::spiTransfer(uint8_t devId, uint8_t regAddr, uint8_t *regData, uint16_t length) +{ + int8_t rslt = 0; + if(Bsec::spiObj) { + Bsec::spiObj->beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0)); // Can be upto 10MHz + + digitalWrite(devId, LOW); + + Bsec::spiObj->transfer(regAddr); // Write the register address, ignore the return + for (uint16_t i = 0; i < length; i++) + regData[i] = Bsec::spiObj->transfer(regData[i]); + + digitalWrite(devId, HIGH); + Bsec::spiObj->endTransaction(); + } else { + rslt = -1; + } + + return rslt;; +} \ No newline at end of file diff --git a/lib/Bosch-BSEC/bsec.h b/lib/Bosch-BSEC/bsec.h new file mode 100644 index 00000000..3c881c6b --- /dev/null +++ b/lib/Bosch-BSEC/bsec.h @@ -0,0 +1,230 @@ +/** + * Copyright (C) 2017 - 2018 Bosch Sensortec GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of the copyright holder nor the names of the + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + * + * The information provided is believed to be accurate and reliable. + * The copyright holder assumes no responsibility + * for the consequences of use + * of such information nor for any infringement of patents or + * other rights of third parties which may result from its use. + * No license is granted by implication or otherwise under any patent or + * patent rights of the copyright holder. + * + * @file bsec.h + * @date 31 Jan 2018 + * @version 1.0 + * + */ + +#ifndef BSEC_CLASS_H +#define BSEC_CLASS_H + +/* Includes */ +#include "Arduino.h" +#include "Wire.h" +#include "SPI.h" +#include "bsec_datatypes.h" +#include "bsec_interface.h" +#include "bme680.h" + +/* BSEC class definition */ +class Bsec +{ +public: + /* Public variables */ + bsec_version_t version; // Stores the version of the BSEC algorithm + int64_t nextCall; // Stores the time when the algorithm has to be called next in ms + int8_t bme680Status; // Placeholder for the BME680 driver's error codes + bsec_library_return_t status; + float iaqEstimate, rawTemperature, pressure, rawHumidity, gasResistance, stabStatus, runInStatus, temperature, humidity, + staticIaq, co2Equivalent, breathVocEquivalent, compGasValue, gasPercentage; + uint8_t iaqAccuracy, staticIaqAccuracy, co2Accuracy, breathVocAccuracy, compGasAccuracy, gasPercentageAcccuracy; + int64_t outputTimestamp; // Timestamp in ms of the output + static TwoWire *wireObj; + static SPIClass *spiObj; + + /* Public APIs */ + /** + * @brief Constructor + */ + Bsec(); + + /** + * @brief Function to initialize the BSEC library and the BME680 sensor + * @param devId : Device identifier parameter for the read/write interface functions + * @param intf : Physical communication interface + * @param read : Pointer to the read function + * @param write : Pointer to the write function + * @param idleTask : Pointer to the idling task + */ + void begin(uint8_t devId, enum bme680_intf intf, bme680_com_fptr_t read, bme680_com_fptr_t write, bme680_delay_fptr_t idleTask); + + /** + * @brief Function to initialize the BSEC library and the BME680 sensor + * @param i2cAddr : I2C address + * @param i2c : Pointer to the TwoWire object + */ + void begin(uint8_t i2cAddr, TwoWire &i2c); + + /** + * @brief Function to initialize the BSEC library and the BME680 sensor + * @param chipSelect : SPI chip select + * @param spi : Pointer to the SPIClass object + */ + void begin(uint8_t chipSelect, SPIClass &spi); + + /** + * @brief Function that sets the desired sensors and the sample rates + * @param sensorList : The list of output sensors + * @param nSensors : Number of outputs requested + * @param sampleRate : The sample rate of requested sensors + */ + void updateSubscription(bsec_virtual_sensor_t sensorList[], uint8_t nSensors, float sampleRate = BSEC_SAMPLE_RATE_ULP); + + /** + * @brief Callback from the user to trigger reading of data from the BME680, process and store outputs + * @return true if there are new outputs. false otherwise + */ + bool run(void); + + /** + * @brief Function to get the state of the algorithm to save to non-volatile memory + * @param state : Pointer to a memory location that contains the state + */ + void getState(uint8_t *state); + + /** + * @brief Function to set the state of the algorithm from non-volatile memory + * @param state : Pointer to a memory location that contains the state + */ + void setState(uint8_t *state); + + /** + * @brief Function to set the configuration of the algorithm from memory + * @param state : Pointer to a memory location that contains the configuration + */ + void setConfig(const uint8_t *config); + + /** + * @brief Function to set the temperature offset + * @param tempOffset : Temperature offset in degree Celsius + */ + void setTemperatureOffset(float tempOffset) + { + _tempOffset = tempOffset; + } + + + /** + * @brief Function to calculate an int64_t timestamp in milliseconds + */ + int64_t getTimeMs(void); + + /** + * @brief Task that delays for a ms period of time + * @param period : Period of time in ms + */ + static void delay_ms(uint32_t period); + + /** + * @brief Callback function for reading registers over I2C + * @param devId : Library agnostic parameter to identify the device to communicate with + * @param regAddr : Register address + * @param regData : Pointer to the array containing the data to be read + * @param length : Length of the array of data + * @return Zero for success, non-zero otherwise + */ + static int8_t i2cRead(uint8_t devId, uint8_t regAddr, uint8_t *regData, uint16_t length); + + /** + * @brief Callback function for writing registers over I2C + * @param devId : Library agnostic parameter to identify the device to communicate with + * @param regAddr : Register address + * @param regData : Pointer to the array containing the data to be written + * @param length : Length of the array of data + * @return Zero for success, non-zero otherwise + */ + static int8_t i2cWrite(uint8_t devId, uint8_t regAddr, uint8_t *regData, uint16_t length); + + /** + * @brief Callback function for reading and writing registers over SPI + * @param devId : Library agnostic parameter to identify the device to communicate with + * @param regAddr : Register address + * @param regData : Pointer to the array containing the data to be read or written + * @param length : Length of the array of data + * @return Zero for success, non-zero otherwise + */ + static int8_t spiTransfer(uint8_t devId, uint8_t regAddr, uint8_t *regData, uint16_t length); + +private: + /* Private variables */ + struct bme680_dev _bme680; + struct bme680_field_data _data; + float _tempOffset; + // Global variables to help create a millisecond timestamp that doesn't overflow every 51 days. + // If it overflows, it will have a negative value. Something that should never happen. + uint32_t millisOverflowCounter; + uint32_t lastTime; + + /* Private APIs */ + /** + * @brief Get the version of the BSEC library + */ + void getVersion(void); + + /** + * @brief Read data from the BME680 and process it + * @param currTimeNs: Current time in ns + * @param bme680Settings: BME680 sensor's settings + * @return true if there are new outputs. false otherwise + */ + bool readProcessData(int64_t currTimeNs, bsec_bme_settings_t bme680Settings); + + /** + * @brief Set the BME680 sensor's configuration + * @param bme680Settings: Settings to configure the BME680 + * @return BME680 return code. BME680_OK for success, failure otherwise + */ + int8_t setBme680Config(bsec_bme_settings_t bme680Settings); + + /** + * @brief Common code for the begin function + */ + void beginCommon(void); + + /** + * @brief Function to zero the outputs + */ + void zeroOutputs(void); +}; + +#endif diff --git a/platformio.ini b/platformio.ini index f8aa7392..31a35718 100644 --- a/platformio.ini +++ b/platformio.ini @@ -6,7 +6,7 @@ ; ---> SELECT TARGET PLATFORM HERE! <--- [platformio] -env_default = generic +;env_default = generic ;env_default = ebox ;env_default = eboxtube ;env_default = heltec @@ -16,7 +16,7 @@ env_default = generic ;env_default = ttgov21old ;env_default = ttgov21new ;env_default = ttgobeam_old -;env_default = ttgobeam_new +env_default = ttgobeam_new ;env_default = lopy ;env_default = lopy4 ;env_default = fipy @@ -30,10 +30,10 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 1.7.01 +release_version = 1.7.03 ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose -debug_level = 0 +debug_level = 4 ; UPLOAD MODE: select esptool to flash via USB/UART, select custom to upload to cloud for OTA upload_protocol = esptool ;upload_protocol = custom diff --git a/src/bme680mems.cpp b/src/bme680mems.cpp index 08795f31..dd7fc2d1 100644 --- a/src/bme680mems.cpp +++ b/src/bme680mems.cpp @@ -1,88 +1,89 @@ #ifdef HAS_BME #include "bme680mems.h" +#include "bsec.h" // Local logging tag static const char TAG[] = "main"; bmeStatus_t bme_status; TaskHandle_t BmeTask; - float bme_offset = (float)BME_TEMP_OFFSET; -// --- Bosch BSEC library configuration --- -// 3,3V supply voltage; 3s max time between sensor_control calls; 4 days -// calibration. Change this const if not applicable for your application (see -// BME680 datasheet) -const uint8_t bsec_config_iaq[454] = { - 1, 7, 4, 1, 61, 0, 0, 0, 0, 0, 0, 0, 174, 1, 0, - 0, 48, 0, 1, 0, 137, 65, 0, 63, 205, 204, 204, 62, 0, 0, - 64, 63, 205, 204, 204, 62, 0, 0, 225, 68, 0, 192, 168, 71, 64, - 49, 119, 76, 0, 0, 0, 0, 0, 80, 5, 95, 0, 0, 0, 0, - 0, 0, 0, 0, 28, 0, 2, 0, 0, 244, 1, 225, 0, 25, 0, - 0, 128, 64, 0, 0, 32, 65, 144, 1, 0, 0, 112, 65, 0, 0, - 0, 63, 16, 0, 3, 0, 10, 215, 163, 60, 10, 215, 35, 59, 10, - 215, 35, 59, 9, 0, 5, 0, 0, 0, 0, 0, 1, 88, 0, 9, - 0, 229, 208, 34, 62, 0, 0, 0, 0, 0, 0, 0, 0, 218, 27, - 156, 62, 225, 11, 67, 64, 0, 0, 160, 64, 0, 0, 0, 0, 0, - 0, 0, 0, 94, 75, 72, 189, 93, 254, 159, 64, 66, 62, 160, 191, - 0, 0, 0, 0, 0, 0, 0, 0, 33, 31, 180, 190, 138, 176, 97, - 64, 65, 241, 99, 190, 0, 0, 0, 0, 0, 0, 0, 0, 167, 121, - 71, 61, 165, 189, 41, 192, 184, 30, 189, 64, 12, 0, 10, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 229, 0, 254, 0, 2, 1, 5, 48, - 117, 100, 0, 44, 1, 112, 23, 151, 7, 132, 3, 197, 0, 92, 4, - 144, 1, 64, 1, 64, 1, 144, 1, 48, 117, 48, 117, 48, 117, 48, - 117, 100, 0, 100, 0, 100, 0, 48, 117, 48, 117, 48, 117, 100, 0, - 100, 0, 48, 117, 48, 117, 100, 0, 100, 0, 100, 0, 100, 0, 48, - 117, 48, 117, 48, 117, 100, 0, 100, 0, 100, 0, 48, 117, 48, 117, - 100, 0, 100, 0, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, - 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, - 44, 1, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, - 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, - 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, - 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 255, 255, - 255, 255, 255, 255, 255, 255, 220, 5, 220, 5, 220, 5, 255, 255, 255, - 255, 255, 255, 220, 5, 220, 5, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 44, 1, 0, 0, 0, 0, - 239, 79, 0, 0}; +Bsec iaqSensor; // initialize BME680 sensor int bme_init(void) { - // struct bme680_dev gas_sensor; - Wire.begin(HAS_BME, 400000); // I2C connect to BME680 sensor with 400 KHz + Wire.begin(HAS_BME); + iaqSensor.begin(BME_ADDR, Wire); - // Call to the function which initializes the BSEC library - // Switch on low-power mode and provide no temperature offset + ESP_LOGI(TAG, "BSEC v%d.%d.%d.%d", iaqSensor.version.major, + iaqSensor.version.minor, iaqSensor.version.major_bugfix, + iaqSensor.version.minor_bugfix); - return_values_init ret = - bsec_iot_init(BSEC_SAMPLE_RATE_LP, bme_offset, i2c_write, i2c_read, - user_delay_ms, state_load, config_load); + iaqSensor.setConfig(bsec_config_iaq); - if ((int)ret.bme680_status) { - ESP_LOGE(TAG, "Could not initialize BME680, error %d", - (int)ret.bme680_status); - } else if ((int)ret.bsec_status) { - ESP_LOGE(TAG, "Could not initialize BSEC library, error %d", - (int)ret.bsec_status); - } else { + if (checkIaqSensorStatus()) ESP_LOGI(TAG, "BME680 sensor found and initialized"); + else { + ESP_LOGE(TAG, "BME680 sensor not found"); + return 1; + } + + bsec_virtual_sensor_t sensorList[10] = { + BSEC_OUTPUT_RAW_TEMPERATURE, + BSEC_OUTPUT_RAW_PRESSURE, + BSEC_OUTPUT_RAW_HUMIDITY, + BSEC_OUTPUT_RAW_GAS, + BSEC_OUTPUT_IAQ, + BSEC_OUTPUT_STATIC_IAQ, + BSEC_OUTPUT_CO2_EQUIVALENT, + BSEC_OUTPUT_BREATH_VOC_EQUIVALENT, + BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE, + BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY, + }; + + iaqSensor.updateSubscription(sensorList, 10, BSEC_SAMPLE_RATE_LP); + + if (checkIaqSensorStatus()) + ESP_LOGI(TAG, "BSEC subscription succesful"); + else { + ESP_LOGE(TAG, "BSEC subscription error"); + return 1; + } + + iaqSensor.setTemperatureOffset(bme_offset); + + if (checkIaqSensorStatus()) + ESP_LOGI(TAG, "Ttemperature offset initialized succesful"); + else { + ESP_LOGE(TAG, "Temperature offset initialization error"); return 1; } - return 0; } -void output_ready(int64_t timestamp, float iaq, uint8_t iaq_accuracy, - float temperature, float humidity, float pressure, - float raw_temperature, float raw_humidity, float gas, - bsec_library_return_t bsec_status, float static_iaq, - float co2_equivalent, float breath_voc_equivalent) { +// Helper function definitions +int checkIaqSensorStatus(void) { + int rslt = 1; // true = 1 = no error, false = 0 = error - bme_status.temperature = temperature; - bme_status.humidity = humidity; - bme_status.pressure = (pressure / 100.0); // conversion Pa -> hPa - bme_status.iaq = iaq; + if (iaqSensor.status != BSEC_OK) { + rslt = 0; + if (iaqSensor.status < BSEC_OK) + ESP_LOGE(TAG, "BSEC error %d", iaqSensor.status); + else + ESP_LOGW(TAG, "BSEC warning %d", iaqSensor.status); + } + + if (iaqSensor.bme680Status != BME680_OK) { + rslt = 0; + if (iaqSensor.bme680Status < BME680_OK) + ESP_LOGE(TAG, "BME680 error %d", iaqSensor.bme680Status); + else + ESP_LOGW(TAG, "BME680 warning %d", iaqSensor.bme680Status); + } + + return rslt; } // loop function which reads and processes data based on sensor settings @@ -91,84 +92,23 @@ void bme_loop(void *pvParameters) { configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check #ifdef HAS_BME - // State is saved every 10.000 samples, which means every 10.000 * 3 secs = - // 500 minutes - bsec_iot_loop(user_delay_ms, get_timestamp_us, output_ready, state_save, - 10000); + while (checkIaqSensorStatus()) { + if (iaqSensor.run()) { // If new data is available + bme_status.raw_temperature = iaqSensor.rawTemperature; + bme_status.raw_humidity = iaqSensor.rawHumidity; + bme_status.temperature = iaqSensor.temperature; + bme_status.humidity = iaqSensor.humidity; + bme_status.pressure = + (iaqSensor.pressure / 100.0); // conversion Pa -> hPa + bme_status.iaq = iaqSensor.iaqEstimate; + bme_status.iaq_accuracy = iaqSensor.iaqAccuracy; + bme_status.gas = iaqSensor.gasResistance; + } + } #endif + ESP_LOGE(TAG, "BME task ended"); vTaskDelete(BmeTask); // should never be reached + } // bme_loop() -int8_t i2c_read(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, - uint16_t len) { - int8_t rslt = 0; - Wire.beginTransmission(dev_id); - Wire.write(reg_addr); - rslt = Wire.endTransmission(false); - - Wire.requestFrom((int)dev_id, (int)len); - for (uint16_t i = 0; (i < len) && Wire.available(); i++) { - reg_data[i] = Wire.read(); - } - // return 0 for success, non-zero for failure - return rslt; -} - -int8_t i2c_write(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, - uint16_t len) { - Wire.beginTransmission(dev_id); - Wire.write(reg_addr); - for (uint16_t i = 0; i < len; i++) { - Wire.write(reg_data[i]); - } - // return 0 for success, non-zero for failure - return Wire.endTransmission(true); -} - -/*! - * @brief Load previous library state from non-volatile memory - * - * @param[in,out] state_buffer buffer to hold the loaded state string - * @param[in] n_buffer size of the allocated state buffer - * - * @return number of bytes copied to state_buffer - */ -uint32_t state_load(uint8_t *state_buffer, uint32_t n_buffer) { - // ... - // Load a previous library state from non-volatile memory, if available. - // - // Return zero if loading was unsuccessful or no state was available, - // otherwise return length of loaded state string. - // ... - return 0; -} - -/*! - * @brief Save library state to non-volatile memory - * - * @param[in] state_buffer buffer holding the state to be stored - * @param[in] length length of the state string to be stored - * - * @return none - */ -void state_save(const uint8_t *state_buffer, uint32_t length) { - // ... - // Save the string some form of non-volatile memory, if possible. - // ... -} - -uint32_t config_load(uint8_t *config_buffer, uint32_t n_buffer) { - - // Load a library config from non-volatile memory, if available. - // Return zero if loading was unsuccessful or no config was available, - // otherwise return length of loaded config string. - - memcpy(config_buffer, bsec_config_iaq, sizeof(bsec_config_iaq)); - return sizeof(bsec_config_iaq); -} - -void user_delay_ms(uint32_t period) { delay(period); } - -int64_t get_timestamp_us() { return (int64_t)millis() * 1000; } - #endif // HAS_BME \ No newline at end of file diff --git a/src/hal/generic.h b/src/hal/generic.h index ad88536f..c4e6deb9 100644 --- a/src/hal/generic.h +++ b/src/hal/generic.h @@ -17,8 +17,8 @@ // enable only if device has these sensors, otherwise comment these lines // BME680 sensor on I2C bus -// don't forget to connect SDIO of BME680 to GND for selecting i2c addr 0x76 #define HAS_BME GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL +#define BME_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND // user defined sensors //#define HAS_SENSORS 1 // comment out if device has user defined sensors diff --git a/src/hal/octopus32.h b/src/hal/octopus32.h index a63b399b..288c286b 100644 --- a/src/hal/octopus32.h +++ b/src/hal/octopus32.h @@ -11,11 +11,9 @@ // disable brownout detection (avoid unexpected reset on some boards) #define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature -// enable only if device has these sensors, otherwise comment these lines -// BME680 sensor on I2C bus // Octopus32 has a pre-populated BME680 on i2c addr 0x76 #define HAS_BME GPIO_NUM_23, GPIO_NUM_22 // SDA, SCL -//#define HAS_BME 0x76 +#define BME_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND // user defined sensors //#define HAS_SENSORS 1 // comment out if device has user defined sensors diff --git a/src/hal/ttgobeam_new.h b/src/hal/ttgobeam_new.h index 1a4552a9..814e59c1 100644 --- a/src/hal/ttgobeam_new.h +++ b/src/hal/ttgobeam_new.h @@ -8,12 +8,11 @@ // Hardware related definitions for TTGO T-Beam board // // pinouts taken from http://tinymicros.com/wiki/TTGO_T-Beam -// + // enable only if device has these sensors, otherwise comment these lines // BME680 sensor on I2C bus -// don't forget to connect SDIO of BME680 to GND for selecting i2c addr 0x76 -// -//#define HAS_BME GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL +#define HAS_BME GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL +#define BME_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND #define HAS_LED GPIO_NUM_14 // on board green LED