From 4158f225e12878c42d71b62996c41b49296f68b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sun, 12 Dec 2021 16:27:04 +0100 Subject: [PATCH] add bsec --- components/bme680/CMakeLists.txt | 5 + components/bme680/include/bme680.h | 225 --- components/bme680/include/bme680_defs.h | 512 ------ components/bme680/include/bme68x.h | 323 ++++ components/bme680/include/bme68x_defs.h | 972 ++++++++++ components/bme680/include/bsec2.h | 135 ++ components/bme680/include/bsec_datatypes.h | 506 ++++++ components/bme680/include/bsec_interface.h | 562 ++++++ components/bme680/lib/libalgobsec.a | Bin 0 -> 247364 bytes components/bme680/src/bme680.c | 1138 ------------ components/bme680/src/bme68x.c | 1848 ++++++++++++++++++++ components/bme680/src/bsec2.c | 403 +++++ main/voc_sensor.c | 390 ++--- main/voc_sensor.h | 8 - 14 files changed, 4934 insertions(+), 2093 deletions(-) delete mode 100644 components/bme680/include/bme680.h delete mode 100644 components/bme680/include/bme680_defs.h create mode 100644 components/bme680/include/bme68x.h create mode 100644 components/bme680/include/bme68x_defs.h create mode 100644 components/bme680/include/bsec2.h create mode 100644 components/bme680/include/bsec_datatypes.h create mode 100644 components/bme680/include/bsec_interface.h create mode 100644 components/bme680/lib/libalgobsec.a delete mode 100644 components/bme680/src/bme680.c create mode 100644 components/bme680/src/bme68x.c create mode 100644 components/bme680/src/bsec2.c diff --git a/components/bme680/CMakeLists.txt b/components/bme680/CMakeLists.txt index b4b88cf..f041a12 100644 --- a/components/bme680/CMakeLists.txt +++ b/components/bme680/CMakeLists.txt @@ -5,3 +5,8 @@ set(COMPONENT_SRCDIRS "src") register_component() + +add_library(algobsec STATIC IMPORTED) +set_property(TARGET algobsec PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/lib/libalgobsec.a) + +target_link_libraries(${COMPONENT_LIB} PUBLIC algobsec) diff --git a/components/bme680/include/bme680.h b/components/bme680/include/bme680.h deleted file mode 100644 index 56eb7a8..0000000 --- a/components/bme680/include/bme680.h +++ /dev/null @@ -1,225 +0,0 @@ -/** - * 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 bme680.h - * @date 30 Oct 2017 - * @version 3.5.3 - * @brief - * - */ -/*! @file bme680.h - @brief Sensor driver for BME680 sensor */ -/*! - * @defgroup BME680 SENSOR API - * @{*/ -#ifndef BME680_H_ -#define BME680_H_ - -/*! CPP guard */ -#ifdef __cplusplus -extern "C" -{ -#endif - -/* Header includes */ -#include "bme680_defs.h" - -/* function prototype declarations */ -/*! - * @brief This API is the entry point. - * It reads the chip-id and calibration data from the sensor. - * - * @param[in,out] dev : Structure instance of bme680_dev - * - * @return Result of API execution status - * @retval zero -> Success / +ve value -> Warning / -ve value -> Error - */ -int8_t bme680_init(struct bme680_dev *dev); - -/*! - * @brief This API writes the given data to the register address - * of the sensor. - * - * @param[in] reg_addr : Register address from where the data to be written. - * @param[in] reg_data : Pointer to data buffer which is to be written - * in the sensor. - * @param[in] len : No of bytes of data to write.. - * @param[in] dev : Structure instance of bme680_dev. - * - * @return Result of API execution status - * @retval zero -> Success / +ve value -> Warning / -ve value -> Error - */ -int8_t bme680_set_regs(const uint8_t *reg_addr, const uint8_t *reg_data, uint8_t len, struct bme680_dev *dev); - -/*! - * @brief This API reads the data from the given register address of the sensor. - * - * @param[in] reg_addr : Register address from where the data to be read - * @param[out] reg_data : Pointer to data buffer to store the read data. - * @param[in] len : No of bytes of data to be read. - * @param[in] dev : Structure instance of bme680_dev. - * - * @return Result of API execution status - * @retval zero -> Success / +ve value -> Warning / -ve value -> Error - */ -int8_t bme680_get_regs(uint8_t reg_addr, uint8_t *reg_data, uint16_t len, struct bme680_dev *dev); - -/*! - * @brief This API performs the soft reset of the sensor. - * - * @param[in] dev : Structure instance of bme680_dev. - * - * @return Result of API execution status - * @retval zero -> Success / +ve value -> Warning / -ve value -> Error. - */ -int8_t bme680_soft_reset(struct bme680_dev *dev); - -/*! - * @brief This API is used to set the power mode of the sensor. - * - * @param[in] dev : Structure instance of bme680_dev - * @note : Pass the value to bme680_dev.power_mode structure variable. - * - * value | mode - * -------------|------------------ - * 0x00 | BME680_SLEEP_MODE - * 0x01 | BME680_FORCED_MODE - * - * * @return Result of API execution status - * @retval zero -> Success / +ve value -> Warning / -ve value -> Error - */ -int8_t bme680_set_sensor_mode(struct bme680_dev *dev); - -/*! - * @brief This API is used to get the power mode of the sensor. - * - * @param[in] dev : Structure instance of bme680_dev - * @note : bme680_dev.power_mode structure variable hold the power mode. - * - * value | mode - * ---------|------------------ - * 0x00 | BME680_SLEEP_MODE - * 0x01 | BME680_FORCED_MODE - * - * @return Result of API execution status - * @retval zero -> Success / +ve value -> Warning / -ve value -> Error - */ -int8_t bme680_get_sensor_mode(struct bme680_dev *dev); - -/*! - * @brief This API is used to set the profile duration of the sensor. - * - * @param[in] dev : Structure instance of bme680_dev. - * @param[in] duration : Duration of the measurement in ms. - * - * @return Nothing - */ -void bme680_set_profile_dur(uint16_t duration, struct bme680_dev *dev); - -/*! - * @brief This API is used to get the profile duration of the sensor. - * - * @param[in] dev : Structure instance of bme680_dev. - * @param[in] duration : Duration of the measurement in ms. - * - * @return Nothing - */ -void bme680_get_profile_dur(uint16_t *duration, const struct bme680_dev *dev); - -/*! - * @brief This API reads the pressure, temperature and humidity and gas data - * from the sensor, compensates the data and store it in the bme680_data - * structure instance passed by the user. - * - * @param[out] data: Structure instance to hold the data. - * @param[in] dev : Structure instance of bme680_dev. - * - * @return Result of API execution status - * @retval zero -> Success / +ve value -> Warning / -ve value -> Error - */ -int8_t bme680_get_sensor_data(struct bme680_field_data *data, struct bme680_dev *dev); - -/*! - * @brief This API is used to set the oversampling, filter and T,P,H, gas selection - * settings in the sensor. - * - * @param[in] dev : Structure instance of bme680_dev. - * @param[in] desired_settings : Variable used to select the settings which - * are to be set in the sensor. - * - * Macros | Functionality - *---------------------------------|---------------------------------------------- - * BME680_OST_SEL | To set temperature oversampling. - * BME680_OSP_SEL | To set pressure oversampling. - * BME680_OSH_SEL | To set humidity oversampling. - * BME680_GAS_MEAS_SEL | To set gas measurement setting. - * BME680_FILTER_SEL | To set filter setting. - * BME680_HCNTRL_SEL | To set humidity control setting. - * BME680_RUN_GAS_SEL | To set run gas setting. - * BME680_NBCONV_SEL | To set NB conversion setting. - * BME680_GAS_SENSOR_SEL | To set all gas sensor related settings - * - * @note : Below are the macros to be used by the user for selecting the - * desired settings. User can do OR operation of these macros for configuring - * multiple settings. - * - * @return Result of API execution status - * @retval zero -> Success / +ve value -> Warning / -ve value -> Error. - */ -int8_t bme680_set_sensor_settings(uint16_t desired_settings, struct bme680_dev *dev); - -/*! - * @brief This API is used to get the oversampling, filter and T,P,H, gas selection - * settings in the sensor. - * - * @param[in] dev : Structure instance of bme680_dev. - * @param[in] desired_settings : Variable used to select the settings which - * are to be get from the sensor. - * - * @return Result of API execution status - * @retval zero -> Success / +ve value -> Warning / -ve value -> Error. - */ -int8_t bme680_get_sensor_settings(uint16_t desired_settings, struct bme680_dev *dev); -#ifdef __cplusplus -} -#endif /* End of CPP guard */ -#endif /* BME680_H_ */ -/** @}*/ diff --git a/components/bme680/include/bme680_defs.h b/components/bme680/include/bme680_defs.h deleted file mode 100644 index 3afb94c..0000000 --- a/components/bme680/include/bme680_defs.h +++ /dev/null @@ -1,512 +0,0 @@ -/** - * 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 bme680_defs.h - * @date 30 Oct 2017 - * @version 3.5.3 - * @brief - * - */ - -/*! @file bme680_defs.h - @brief Sensor driver for BME680 sensor */ -/*! - * @defgroup BME680 SENSOR API - * @brief - * @{*/ -#ifndef BME680_DEFS_H_ -#define BME680_DEFS_H_ - -/********************************************************/ -/* header includes */ -#ifdef __KERNEL__ -#include -#include -#else -#include -#include -#endif - -/******************************************************************************/ -/*! @name Common macros */ -/******************************************************************************/ - -#if !defined(UINT8_C) && !defined(INT8_C) -#define INT8_C(x) S8_C(x) -#define UINT8_C(x) U8_C(x) -#endif - -#if !defined(UINT16_C) && !defined(INT16_C) -#define INT16_C(x) S16_C(x) -#define UINT16_C(x) U16_C(x) -#endif - -#if !defined(INT32_C) && !defined(UINT32_C) -#define INT32_C(x) S32_C(x) -#define UINT32_C(x) U32_C(x) -#endif - -#if !defined(INT64_C) && !defined(UINT64_C) -#define INT64_C(x) S64_C(x) -#define UINT64_C(x) U64_C(x) -#endif - -/**@}*/ - -/**\name C standard macros */ -#ifndef NULL -#ifdef __cplusplus -#define NULL 0 -#else -#define NULL ((void *) 0) -#endif -#endif - -/** BME680 General config */ -#define BME680_POLL_PERIOD_MS UINT8_C(10) - -/** BME680 I2C addresses */ -#define BME680_I2C_ADDR_PRIMARY UINT8_C(0x76) -#define BME680_I2C_ADDR_SECONDARY UINT8_C(0x77) - -/** BME680 unique chip identifier */ -#define BME680_CHIP_ID UINT8_C(0x61) - -/** BME680 coefficients related defines */ -#define BME680_COEFF_SIZE UINT8_C(0x41) -#define BME680_COEFF_ADDR1_LEN UINT8_C(25) -#define BME680_COEFF_ADDR2_LEN UINT8_C(16) - -/** BME680 field_x related defines */ -#define BME680_FIELD_LENGTH UINT8_C(15) -#define BME680_FIELD_ADDR_OFFSET UINT8_C(17) - -/** Soft reset command */ -#define BME680_SOFT_RESET_CMD UINT8_C(0xb6) - -/** Error code definitions */ -#define BME680_OK INT8_C(0) -/* Errors */ -#define BME680_E_NULL_PTR INT8_C(-1) -#define BME680_E_COM_FAIL INT8_C(-2) -#define BME680_E_DEV_NOT_FOUND INT8_C(-3) -#define BME680_E_INVALID_LENGTH INT8_C(-4) - -/* Warnings */ -#define BME680_W_DEFINE_PWR_MODE INT8_C(1) -#define BME680_W_NO_NEW_DATA INT8_C(2) - -/* Info's */ -#define BME680_I_MIN_CORRECTION UINT8_C(1) -#define BME680_I_MAX_CORRECTION UINT8_C(2) - -/** Register map */ -/** Other coefficient's address */ -#define BME680_ADDR_RES_HEAT_VAL_ADDR UINT8_C(0x00) -#define BME680_ADDR_RES_HEAT_RANGE_ADDR UINT8_C(0x02) -#define BME680_ADDR_RANGE_SW_ERR_ADDR UINT8_C(0x04) -#define BME680_ADDR_SENS_CONF_START UINT8_C(0x5A) -#define BME680_ADDR_GAS_CONF_START UINT8_C(0x64) - -/** Field settings */ -#define BME680_FIELD0_ADDR UINT8_C(0x1d) - -/** Heater settings */ -#define BME680_RES_HEAT0_ADDR UINT8_C(0x5a) -#define BME680_GAS_WAIT0_ADDR UINT8_C(0x64) - -/** Sensor configuration registers */ -#define BME680_CONF_HEAT_CTRL_ADDR UINT8_C(0x70) -#define BME680_CONF_ODR_RUN_GAS_NBC_ADDR UINT8_C(0x71) -#define BME680_CONF_OS_H_ADDR UINT8_C(0x72) -#define BME680_MEM_PAGE_ADDR UINT8_C(0xf3) -#define BME680_CONF_T_P_MODE_ADDR UINT8_C(0x74) -#define BME680_CONF_ODR_FILT_ADDR UINT8_C(0x75) - -/** Coefficient's address */ -#define BME680_COEFF_ADDR1 UINT8_C(0x89) -#define BME680_COEFF_ADDR2 UINT8_C(0xe1) - -/** Chip identifier */ -#define BME680_CHIP_ID_ADDR UINT8_C(0xd0) - -/** Soft reset register */ -#define BME680_SOFT_RESET_ADDR UINT8_C(0xe0) - -/** Heater control settings */ -#define BME680_ENABLE_HEATER UINT8_C(0x00) -#define BME680_DISABLE_HEATER UINT8_C(0x08) - -/** Gas measurement settings */ -#define BME680_DISABLE_GAS_MEAS UINT8_C(0x00) -#define BME680_ENABLE_GAS_MEAS UINT8_C(0x01) - -/** Over-sampling settings */ -#define BME680_OS_NONE UINT8_C(0) -#define BME680_OS_1X UINT8_C(1) -#define BME680_OS_2X UINT8_C(2) -#define BME680_OS_4X UINT8_C(3) -#define BME680_OS_8X UINT8_C(4) -#define BME680_OS_16X UINT8_C(5) - -/** IIR filter settings */ -#define BME680_FILTER_SIZE_0 UINT8_C(0) -#define BME680_FILTER_SIZE_1 UINT8_C(1) -#define BME680_FILTER_SIZE_3 UINT8_C(2) -#define BME680_FILTER_SIZE_7 UINT8_C(3) -#define BME680_FILTER_SIZE_15 UINT8_C(4) -#define BME680_FILTER_SIZE_31 UINT8_C(5) -#define BME680_FILTER_SIZE_63 UINT8_C(6) -#define BME680_FILTER_SIZE_127 UINT8_C(7) - -/** Power mode settings */ -#define BME680_SLEEP_MODE UINT8_C(0) -#define BME680_FORCED_MODE UINT8_C(1) - -/** Delay related macro declaration */ -#define BME680_RESET_PERIOD UINT32_C(10) - -/** SPI memory page settings */ -#define BME680_MEM_PAGE0 UINT8_C(0x10) -#define BME680_MEM_PAGE1 UINT8_C(0x00) - -/** Ambient humidity shift value for compensation */ -#define BME680_HUM_REG_SHIFT_VAL UINT8_C(4) - -/** Run gas enable and disable settings */ -#define BME680_RUN_GAS_DISABLE UINT8_C(0) -#define BME680_RUN_GAS_ENABLE UINT8_C(1) - -/** Buffer length macro declaration */ -#define BME680_TMP_BUFFER_LENGTH UINT8_C(40) -#define BME680_REG_BUFFER_LENGTH UINT8_C(6) -#define BME680_FIELD_DATA_LENGTH UINT8_C(3) -#define BME680_GAS_REG_BUF_LENGTH UINT8_C(20) -#define BME680_GAS_HEATER_PROF_LEN_MAX UINT8_C(10) - -/** Settings selector */ -#define BME680_OST_SEL UINT16_C(1) -#define BME680_OSP_SEL UINT16_C(2) -#define BME680_OSH_SEL UINT16_C(4) -#define BME680_GAS_MEAS_SEL UINT16_C(8) -#define BME680_FILTER_SEL UINT16_C(16) -#define BME680_HCNTRL_SEL UINT16_C(32) -#define BME680_RUN_GAS_SEL UINT16_C(64) -#define BME680_NBCONV_SEL UINT16_C(128) -#define BME680_GAS_SENSOR_SEL (BME680_GAS_MEAS_SEL | BME680_RUN_GAS_SEL | BME680_NBCONV_SEL) - -/** Number of conversion settings*/ -#define BME680_NBCONV_MIN UINT8_C(0) -#define BME680_NBCONV_MAX UINT8_C(10) - -/** Mask definitions */ -#define BME680_GAS_MEAS_MSK UINT8_C(0x30) -#define BME680_NBCONV_MSK UINT8_C(0X0F) -#define BME680_FILTER_MSK UINT8_C(0X1C) -#define BME680_OST_MSK UINT8_C(0XE0) -#define BME680_OSP_MSK UINT8_C(0X1C) -#define BME680_OSH_MSK UINT8_C(0X07) -#define BME680_HCTRL_MSK UINT8_C(0x08) -#define BME680_RUN_GAS_MSK UINT8_C(0x10) -#define BME680_MODE_MSK UINT8_C(0x03) -#define BME680_RHRANGE_MSK UINT8_C(0x30) -#define BME680_RSERROR_MSK UINT8_C(0xf0) -#define BME680_NEW_DATA_MSK UINT8_C(0x80) -#define BME680_GAS_INDEX_MSK UINT8_C(0x0f) -#define BME680_GAS_RANGE_MSK UINT8_C(0x0f) -#define BME680_GASM_VALID_MSK UINT8_C(0x20) -#define BME680_HEAT_STAB_MSK UINT8_C(0x10) -#define BME680_MEM_PAGE_MSK UINT8_C(0x10) -#define BME680_SPI_RD_MSK UINT8_C(0x80) -#define BME680_SPI_WR_MSK UINT8_C(0x7f) -#define BME680_BIT_H1_DATA_MSK UINT8_C(0x0F) - -/** Bit position definitions for sensor settings */ -#define BME680_GAS_MEAS_POS UINT8_C(4) -#define BME680_FILTER_POS UINT8_C(2) -#define BME680_OST_POS UINT8_C(5) -#define BME680_OSP_POS UINT8_C(2) -#define BME680_RUN_GAS_POS UINT8_C(4) - -/** Array Index to Field data mapping for Calibration Data*/ -#define BME680_T2_LSB_REG (1) -#define BME680_T2_MSB_REG (2) -#define BME680_T3_REG (3) -#define BME680_P1_LSB_REG (5) -#define BME680_P1_MSB_REG (6) -#define BME680_P2_LSB_REG (7) -#define BME680_P2_MSB_REG (8) -#define BME680_P3_REG (9) -#define BME680_P4_LSB_REG (11) -#define BME680_P4_MSB_REG (12) -#define BME680_P5_LSB_REG (13) -#define BME680_P5_MSB_REG (14) -#define BME680_P7_REG (15) -#define BME680_P6_REG (16) -#define BME680_P8_LSB_REG (19) -#define BME680_P8_MSB_REG (20) -#define BME680_P9_LSB_REG (21) -#define BME680_P9_MSB_REG (22) -#define BME680_P10_REG (23) -#define BME680_H2_MSB_REG (25) -#define BME680_H2_LSB_REG (26) -#define BME680_H1_LSB_REG (26) -#define BME680_H1_MSB_REG (27) -#define BME680_H3_REG (28) -#define BME680_H4_REG (29) -#define BME680_H5_REG (30) -#define BME680_H6_REG (31) -#define BME680_H7_REG (32) -#define BME680_T1_LSB_REG (33) -#define BME680_T1_MSB_REG (34) -#define BME680_GH2_LSB_REG (35) -#define BME680_GH2_MSB_REG (36) -#define BME680_GH1_REG (37) -#define BME680_GH3_REG (38) - -/** BME680 register buffer index settings*/ -#define BME680_REG_FILTER_INDEX UINT8_C(5) -#define BME680_REG_TEMP_INDEX UINT8_C(4) -#define BME680_REG_PRES_INDEX UINT8_C(4) -#define BME680_REG_HUM_INDEX UINT8_C(2) -#define BME680_REG_NBCONV_INDEX UINT8_C(1) -#define BME680_REG_RUN_GAS_INDEX UINT8_C(1) -#define BME680_REG_HCTRL_INDEX UINT8_C(0) - -/** Macro to combine two 8 bit data's to form a 16 bit data */ -#define BME680_CONCAT_BYTES(msb, lsb) (((uint16_t)msb << 8) | (uint16_t)lsb) - -/** Macro to SET and GET BITS of a register */ -#define BME680_SET_BITS(reg_data, bitname, data) \ - ((reg_data & ~(bitname##_MSK)) | \ - ((data << bitname##_POS) & bitname##_MSK)) -#define BME680_GET_BITS(reg_data, bitname) ((reg_data & (bitname##_MSK)) >> \ - (bitname##_POS)) - -/** Macro variant to handle the bitname position if it is zero */ -#define BME680_SET_BITS_POS_0(reg_data, bitname, data) \ - ((reg_data & ~(bitname##_MSK)) | \ - (data & bitname##_MSK)) -#define BME680_GET_BITS_POS_0(reg_data, bitname) (reg_data & (bitname##_MSK)) - -/** Type definitions */ -/* - * Generic communication function pointer - * @param[in] dev_id: Place holder to store the id of the device structure - * Can be used to store the index of the Chip select or - * I2C address of the device. - * @param[in] reg_addr: Used to select the register the where data needs to - * be read from or written to. - * @param[in/out] reg_data: Data array to read/write - * @param[in] len: Length of the data array - */ -typedef int8_t (*bme680_com_fptr_t)(uint8_t dev_id, uint8_t reg_addr, uint8_t *data, uint16_t len); - -/* - * Delay function pointer - * @param[in] period: Time period in milliseconds - */ -typedef void (*bme680_delay_fptr_t)(uint32_t period); - -/*! - * @brief Interface selection Enumerations - */ -enum bme680_intf { - /*! SPI interface */ - BME680_SPI_INTF, - /*! I2C interface */ - BME680_I2C_INTF -}; - -/* structure definitions */ -/*! - * @brief Sensor field data structure - */ -struct bme680_field_data { - /*! Contains new_data, gasm_valid & heat_stab */ - uint8_t status; - /*! The index of the heater profile used */ - uint8_t gas_index; - /*! Measurement index to track order */ - uint8_t meas_index; - /*! Temperature in degree celsius x100 */ - int16_t temperature; - /*! Pressure in Pascal */ - uint32_t pressure; - /*! Humidity in % relative humidity x1000 */ - uint32_t humidity; - /*! Gas resistance in Ohms */ - uint32_t gas_resistance; -}; - -/*! - * @brief Structure to hold the Calibration data - */ -struct bme680_calib_data { - /*! Variable to store calibrated humidity data */ - uint16_t par_h1; - /*! Variable to store calibrated humidity data */ - uint16_t par_h2; - /*! Variable to store calibrated humidity data */ - int8_t par_h3; - /*! Variable to store calibrated humidity data */ - int8_t par_h4; - /*! Variable to store calibrated humidity data */ - int8_t par_h5; - /*! Variable to store calibrated humidity data */ - uint8_t par_h6; - /*! Variable to store calibrated humidity data */ - int8_t par_h7; - /*! Variable to store calibrated gas data */ - int8_t par_gh1; - /*! Variable to store calibrated gas data */ - int16_t par_gh2; - /*! Variable to store calibrated gas data */ - int8_t par_gh3; - /*! Variable to store calibrated temperature data */ - uint16_t par_t1; - /*! Variable to store calibrated temperature data */ - int16_t par_t2; - /*! Variable to store calibrated temperature data */ - int8_t par_t3; - /*! Variable to store calibrated pressure data */ - uint16_t par_p1; - /*! Variable to store calibrated pressure data */ - int16_t par_p2; - /*! Variable to store calibrated pressure data */ - int8_t par_p3; - /*! Variable to store calibrated pressure data */ - int16_t par_p4; - /*! Variable to store calibrated pressure data */ - int16_t par_p5; - /*! Variable to store calibrated pressure data */ - int8_t par_p6; - /*! Variable to store calibrated pressure data */ - int8_t par_p7; - /*! Variable to store calibrated pressure data */ - int16_t par_p8; - /*! Variable to store calibrated pressure data */ - int16_t par_p9; - /*! Variable to store calibrated pressure data */ - uint8_t par_p10; - /*! Variable to store t_fine size */ - int32_t t_fine; - /*! Variable to store heater resistance range */ - uint8_t res_heat_range; - /*! Variable to store heater resistance value */ - int8_t res_heat_val; - /*! Variable to store error range */ - int8_t range_sw_err; -}; - -/*! - * @brief BME680 sensor settings structure which comprises of ODR, - * over-sampling and filter settings. - */ -struct bme680_tph_sett { - /*! Humidity oversampling */ - uint8_t os_hum; - /*! Temperature oversampling */ - uint8_t os_temp; - /*! Pressure oversampling */ - uint8_t os_pres; - /*! Filter coefficient */ - uint8_t filter; -}; - -/*! - * @brief BME680 gas sensor which comprises of gas settings - * and status parameters - */ -struct bme680_gas_sett { - /*! Variable to store nb conversion */ - uint8_t nb_conv; - /*! Variable to store heater control */ - uint8_t heatr_ctrl; - /*! Run gas enable value */ - uint8_t run_gas; - /*! Pointer to store heater temperature */ - uint16_t heatr_temp; - /*! Pointer to store duration profile */ - uint16_t heatr_dur; -}; - -/*! - * @brief BME680 device structure - */ -struct bme680_dev { - /*! Chip Id */ - uint8_t chip_id; - /*! Device Id */ - uint8_t dev_id; - /*! SPI/I2C interface */ - enum bme680_intf intf; - /*! Memory page used */ - uint8_t mem_page; - /*! Ambient temperature in Degree C*/ - int8_t amb_temp; - /*! Sensor calibration data */ - struct bme680_calib_data calib; - /*! Sensor settings */ - struct bme680_tph_sett tph_sett; - /*! Gas Sensor settings */ - struct bme680_gas_sett gas_sett; - /*! Sensor power modes */ - uint8_t power_mode; - /*! New sensor fields */ - uint8_t new_fields; - /*! Store the info messages */ - uint8_t info_msg; - /*! Burst read structure */ - bme680_com_fptr_t read; - /*! Burst write structure */ - bme680_com_fptr_t write; - /*! Delay in ms */ - bme680_delay_fptr_t delay_ms; - /*! Communication function result */ - int8_t com_rslt; -}; - - - -#endif /* BME680_DEFS_H_ */ -/** @}*/ -/** @}*/ diff --git a/components/bme680/include/bme68x.h b/components/bme680/include/bme68x.h new file mode 100644 index 0000000..e6d4828 --- /dev/null +++ b/components/bme680/include/bme68x.h @@ -0,0 +1,323 @@ +/** +* Copyright (c) 2021 Bosch Sensortec GmbH. All rights reserved. +* +* BSD-3-Clause +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. 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. +* +* 3. Neither the name of the copyright holder nor the names of its +* 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 THE +* 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. +* +* @file bme68x.h +* @date 2021-11-09 +* @version v4.4.7 +* +*/ + +/*! + * @defgroup bme68x BME68X + * @brief Product Overview + * and Sensor API Source Code + */ + +#ifndef BME68X_H_ +#define BME68X_H_ + +#include "bme68x_defs.h" + +/* CPP guard */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \ingroup bme68x + * \defgroup bme68xApiInit Initialization + * @brief Initialize the sensor and device structure + */ + +/*! + * \ingroup bme68xApiInit + * \page bme68x_api_bme68x_init bme68x_init + * \code + * int8_t bme68x_init(struct bme68x_dev *dev); + * \endcode + * @details This API reads the chip-id of the sensor which is the first step to + * verify the sensor and also calibrates the sensor + * As this API is the entry point, call this API before using other APIs. + * + * @param[in,out] dev : Structure instance of bme68x_dev + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_init(struct bme68x_dev *dev); + +/** + * \ingroup bme68x + * \defgroup bme68xApiRegister Registers + * @brief Generic API for accessing sensor registers + */ + +/*! + * \ingroup bme68xApiRegister + * \page bme68x_api_bme68x_set_regs bme68x_set_regs + * \code + * int8_t bme68x_set_regs(const uint8_t reg_addr, const uint8_t *reg_data, uint32_t len, struct bme68x_dev *dev) + * \endcode + * @details This API writes the given data to the register address of the sensor + * + * @param[in] reg_addr : Register addresses to where the data is to be written + * @param[in] reg_data : Pointer to data buffer which is to be written + * in the reg_addr of sensor. + * @param[in] len : No of bytes of data to write + * @param[in,out] dev : Structure instance of bme68x_dev + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_set_regs(const uint8_t *reg_addr, const uint8_t *reg_data, uint32_t len, struct bme68x_dev *dev); + +/*! + * \ingroup bme68xApiRegister + * \page bme68x_api_bme68x_get_regs bme68x_get_regs + * \code + * int8_t bme68x_get_regs(uint8_t reg_addr, uint8_t *reg_data, uint32_t len, struct bme68x_dev *dev) + * \endcode + * @details This API reads the data from the given register address of sensor. + * + * @param[in] reg_addr : Register address from where the data to be read + * @param[out] reg_data : Pointer to data buffer to store the read data. + * @param[in] len : No of bytes of data to be read. + * @param[in,out] dev : Structure instance of bme68x_dev. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_get_regs(uint8_t reg_addr, uint8_t *reg_data, uint32_t len, struct bme68x_dev *dev); + +/** + * \ingroup bme68x + * \defgroup bme68xApiSystem System + * @brief API that performs system-level operations + */ + +/*! + * \ingroup bme68xApiSystem + * \page bme68x_api_bme68x_soft_reset bme68x_soft_reset + * \code + * int8_t bme68x_soft_reset(struct bme68x_dev *dev); + * \endcode + * @details This API soft-resets the sensor. + * + * @param[in,out] dev : Structure instance of bme68x_dev. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_soft_reset(struct bme68x_dev *dev); + +/** + * \ingroup bme68x + * \defgroup bme68xApiOm Operation mode + * @brief API to configure operation mode + */ + +/*! + * \ingroup bme68xApiOm + * \page bme68x_api_bme68x_set_op_mode bme68x_set_op_mode + * \code + * int8_t bme68x_set_op_mode(const uint8_t op_mode, struct bme68x_dev *dev); + * \endcode + * @details This API is used to set the operation mode of the sensor + * @param[in] op_mode : Desired operation mode. + * @param[in] dev : Structure instance of bme68x_dev + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_set_op_mode(const uint8_t op_mode, struct bme68x_dev *dev); + +/*! + * \ingroup bme68xApiOm + * \page bme68x_api_bme68x_get_op_mode bme68x_get_op_mode + * \code + * int8_t bme68x_get_op_mode(uint8_t *op_mode, struct bme68x_dev *dev); + * \endcode + * @details This API is used to get the operation mode of the sensor. + * + * @param[out] op_mode : Desired operation mode. + * @param[in,out] dev : Structure instance of bme68x_dev + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_get_op_mode(uint8_t *op_mode, struct bme68x_dev *dev); + +/*! + * \ingroup bme68xApiConfig + * \page bme68x_api_bme68x_get_meas_dur bme68x_get_meas_dur + * \code + * uint32_t bme68x_get_meas_dur(const uint8_t op_mode, struct bme68x_conf *conf, struct bme68x_dev *dev); + * \endcode + * @details This API is used to get the remaining duration that can be used for heating. + * + * @param[in] op_mode : Desired operation mode. + * @param[in] conf : Desired sensor configuration. + * @param[in] dev : Structure instance of bme68x_dev + * + * @return Measurement duration calculated in microseconds + */ +uint32_t bme68x_get_meas_dur(const uint8_t op_mode, struct bme68x_conf *conf, struct bme68x_dev *dev); + +/** + * \ingroup bme68x + * \defgroup bme68xApiData Data Read out + * @brief Read our data from the sensor + */ + +/*! + * \ingroup bme68xApiData + * \page bme68x_api_bme68x_get_data bme68x_get_data + * \code + * int8_t bme68x_get_data(uint8_t op_mode, struct bme68x_data *data, uint8_t *n_data, struct bme68x_dev *dev); + * \endcode + * @details This API reads the pressure, temperature and humidity and gas data + * from the sensor, compensates the data and store it in the bme68x_data + * structure instance passed by the user. + * + * @param[in] op_mode : Expected operation mode. + * @param[out] data : Structure instance to hold the data. + * @param[out] n_data : Number of data instances available. + * @param[in,out] dev : Structure instance of bme68x_dev + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_get_data(uint8_t op_mode, struct bme68x_data *data, uint8_t *n_data, struct bme68x_dev *dev); + +/** + * \ingroup bme68x + * \defgroup bme68xApiConfig Configuration + * @brief Configuration API of sensor + */ + +/*! + * \ingroup bme68xApiConfig + * \page bme68x_api_bme68x_set_conf bme68x_set_conf + * \code + * int8_t bme68x_set_conf(struct bme68x_conf *conf, struct bme68x_dev *dev); + * \endcode + * @details This API is used to set the oversampling, filter and odr configuration + * + * @param[in] conf : Desired sensor configuration. + * @param[in,out] dev : Structure instance of bme68x_dev. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_set_conf(struct bme68x_conf *conf, struct bme68x_dev *dev); + +/*! + * \ingroup bme68xApiConfig + * \page bme68x_api_bme68x_get_conf bme68x_get_conf + * \code + * int8_t bme68x_get_conf(struct bme68x_conf *conf, struct bme68x_dev *dev); + * \endcode + * @details This API is used to get the oversampling, filter and odr + * configuration + * + * @param[out] conf : Present sensor configuration. + * @param[in,out] dev : Structure instance of bme68x_dev. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_get_conf(struct bme68x_conf *conf, struct bme68x_dev *dev); + +/*! + * \ingroup bme68xApiConfig + * \page bme68x_api_bme68x_set_heatr_conf bme68x_set_heatr_conf + * \code + * int8_t bme68x_set_heatr_conf(uint8_t op_mode, const struct bme68x_heatr_conf *conf, struct bme68x_dev *dev); + * \endcode + * @details This API is used to set the gas configuration of the sensor. + * + * @param[in] op_mode : Expected operation mode of the sensor. + * @param[in] conf : Desired heating configuration. + * @param[in,out] dev : Structure instance of bme68x_dev. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_set_heatr_conf(uint8_t op_mode, const struct bme68x_heatr_conf *conf, struct bme68x_dev *dev); + +/*! + * \ingroup bme68xApiConfig + * \page bme68x_api_bme68x_get_heatr_conf bme68x_get_heatr_conf + * \code + * int8_t bme68x_get_heatr_conf(const struct bme68x_heatr_conf *conf, struct bme68x_dev *dev); + * \endcode + * @details This API is used to get the gas configuration of the sensor. + * + * @param[out] conf : Current configurations of the gas sensor. + * @param[in,out] dev : Structure instance of bme68x_dev. + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_get_heatr_conf(const struct bme68x_heatr_conf *conf, struct bme68x_dev *dev); + +/*! + * \ingroup bme68xApiSystem + * \page bme68x_api_bme68x_selftest_check bme68x_selftest_check + * \code + * int8_t bme68x_selftest_check(const struct bme68x_dev *dev); + * \endcode + * @details This API performs Self-test of low gas variant of BME68X + * + * @param[in, out] dev : Structure instance of bme68x_dev + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +int8_t bme68x_selftest_check(const struct bme68x_dev *dev); + +#ifdef __cplusplus +} +#endif /* End of CPP guard */ +#endif /* BME68X_H_ */ diff --git a/components/bme680/include/bme68x_defs.h b/components/bme680/include/bme68x_defs.h new file mode 100644 index 0000000..861b2f7 --- /dev/null +++ b/components/bme680/include/bme68x_defs.h @@ -0,0 +1,972 @@ +/** +* Copyright (c) 2021 Bosch Sensortec GmbH. All rights reserved. +* +* BSD-3-Clause +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. 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. +* +* 3. Neither the name of the copyright holder nor the names of its +* 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 THE +* 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. +* +* @file bme68x_defs.h +* @date 2021-11-09 +* @version v4.4.7 +* +*/ + +/*! @cond DOXYGEN_SUPRESS */ + +#ifndef BME68X_DEFS_H_ +#define BME68X_DEFS_H_ + +/********************************************************* */ +/*! Header includes */ +/********************************************************* */ +#ifdef __KERNEL__ +#include +#include +#else +#include +#include +#endif + +/********************************************************* */ +/*! Common Macros */ +/********************************************************* */ +#ifdef __KERNEL__ +#if !defined(UINT8_C) && !defined(INT8_C) +#define INT8_C(x) S8_C(x) +#define UINT8_C(x) U8_C(x) +#endif + +#if !defined(UINT16_C) && !defined(INT16_C) +#define INT16_C(x) S16_C(x) +#define UINT16_C(x) U16_C(x) +#endif + +#if !defined(INT32_C) && !defined(UINT32_C) +#define INT32_C(x) S32_C(x) +#define UINT32_C(x) U32_C(x) +#endif + +#if !defined(INT64_C) && !defined(UINT64_C) +#define INT64_C(x) S64_C(x) +#define UINT64_C(x) U64_C(x) +#endif +#endif + +/*! C standard macros */ +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *) 0) +#endif +#endif + +#ifndef BME68X_DO_NOT_USE_FPU + +/* Comment or un-comment the macro to provide floating point data output */ +#define BME68X_USE_FPU +#endif + +/* Period between two polls (value can be given by user) */ +#ifndef BME68X_PERIOD_POLL +#define BME68X_PERIOD_POLL UINT32_C(10000) +#endif + +/* BME68X unique chip identifier */ +#define BME68X_CHIP_ID UINT8_C(0x61) + +/* Period for a soft reset */ +#define BME68X_PERIOD_RESET UINT32_C(10000) + +/* BME68X lower I2C address */ +#define BME68X_I2C_ADDR_LOW UINT8_C(0x76) + +/* BME68X higher I2C address */ +#define BME68X_I2C_ADDR_HIGH UINT8_C(0x77) + +/* Soft reset command */ +#define BME68X_SOFT_RESET_CMD UINT8_C(0xb6) + +/* Return code definitions */ +/* Success */ +#define BME68X_OK INT8_C(0) + +/* Errors */ +/* Null pointer passed */ +#define BME68X_E_NULL_PTR INT8_C(-1) + +/* Communication failure */ +#define BME68X_E_COM_FAIL INT8_C(-2) + +/* Sensor not found */ +#define BME68X_E_DEV_NOT_FOUND INT8_C(-3) + +/* Incorrect length parameter */ +#define BME68X_E_INVALID_LENGTH INT8_C(-4) + +/* Self test fail error */ +#define BME68X_E_SELF_TEST INT8_C(-5) + +/* Warnings */ +/* Define a valid operation mode */ +#define BME68X_W_DEFINE_OP_MODE INT8_C(1) + +/* No new data was found */ +#define BME68X_W_NO_NEW_DATA INT8_C(2) + +/* Define the shared heating duration */ +#define BME68X_W_DEFINE_SHD_HEATR_DUR INT8_C(3) + +/* Information - only available via bme68x_dev.info_msg */ +#define BME68X_I_PARAM_CORR UINT8_C(1) + +/* Register map addresses in I2C */ +/* Register for 3rd group of coefficients */ +#define BME68X_REG_COEFF3 UINT8_C(0x00) + +/* 0th Field address*/ +#define BME68X_REG_FIELD0 UINT8_C(0x1d) + +/* 0th Current DAC address*/ +#define BME68X_REG_IDAC_HEAT0 UINT8_C(0x50) + +/* 0th Res heat address */ +#define BME68X_REG_RES_HEAT0 UINT8_C(0x5a) + +/* 0th Gas wait address */ +#define BME68X_REG_GAS_WAIT0 UINT8_C(0x64) + +/* Shared heating duration address */ +#define BME68X_REG_SHD_HEATR_DUR UINT8_C(0x6E) + +/* CTRL_GAS_0 address */ +#define BME68X_REG_CTRL_GAS_0 UINT8_C(0x70) + +/* CTRL_GAS_1 address */ +#define BME68X_REG_CTRL_GAS_1 UINT8_C(0x71) + +/* CTRL_HUM address */ +#define BME68X_REG_CTRL_HUM UINT8_C(0x72) + +/* CTRL_MEAS address */ +#define BME68X_REG_CTRL_MEAS UINT8_C(0x74) + +/* CONFIG address */ +#define BME68X_REG_CONFIG UINT8_C(0x75) + +/* MEM_PAGE address */ +#define BME68X_REG_MEM_PAGE UINT8_C(0xf3) + +/* Unique ID address */ +#define BME68X_REG_UNIQUE_ID UINT8_C(0x83) + +/* Register for 1st group of coefficients */ +#define BME68X_REG_COEFF1 UINT8_C(0x8a) + +/* Chip ID address */ +#define BME68X_REG_CHIP_ID UINT8_C(0xd0) + +/* Soft reset address */ +#define BME68X_REG_SOFT_RESET UINT8_C(0xe0) + +/* Register for 2nd group of coefficients */ +#define BME68X_REG_COEFF2 UINT8_C(0xe1) + +/* Variant ID Register */ +#define BME68X_REG_VARIANT_ID UINT8_C(0xF0) + +/* Enable/Disable macros */ + +/* Enable */ +#define BME68X_ENABLE UINT8_C(0x01) + +/* Disable */ +#define BME68X_DISABLE UINT8_C(0x00) + +/* Variant ID macros */ + +/* Low Gas variant */ +#define BME68X_VARIANT_GAS_LOW UINT8_C(0x00) + +/* High Gas variant */ +#define BME68X_VARIANT_GAS_HIGH UINT8_C(0x01) + +/* Oversampling setting macros */ + +/* Switch off measurement */ +#define BME68X_OS_NONE UINT8_C(0) + +/* Perform 1 measurement */ +#define BME68X_OS_1X UINT8_C(1) + +/* Perform 2 measurements */ +#define BME68X_OS_2X UINT8_C(2) + +/* Perform 4 measurements */ +#define BME68X_OS_4X UINT8_C(3) + +/* Perform 8 measurements */ +#define BME68X_OS_8X UINT8_C(4) + +/* Perform 16 measurements */ +#define BME68X_OS_16X UINT8_C(5) + +/* IIR Filter settings */ + +/* Switch off the filter */ +#define BME68X_FILTER_OFF UINT8_C(0) + +/* Filter coefficient of 2 */ +#define BME68X_FILTER_SIZE_1 UINT8_C(1) + +/* Filter coefficient of 4 */ +#define BME68X_FILTER_SIZE_3 UINT8_C(2) + +/* Filter coefficient of 8 */ +#define BME68X_FILTER_SIZE_7 UINT8_C(3) + +/* Filter coefficient of 16 */ +#define BME68X_FILTER_SIZE_15 UINT8_C(4) + +/* Filter coefficient of 32 */ +#define BME68X_FILTER_SIZE_31 UINT8_C(5) + +/* Filter coefficient of 64 */ +#define BME68X_FILTER_SIZE_63 UINT8_C(6) + +/* Filter coefficient of 128 */ +#define BME68X_FILTER_SIZE_127 UINT8_C(7) + +/* ODR/Standby time macros */ + +/* Standby time of 0.59ms */ +#define BME68X_ODR_0_59_MS UINT8_C(0) + +/* Standby time of 62.5ms */ +#define BME68X_ODR_62_5_MS UINT8_C(1) + +/* Standby time of 125ms */ +#define BME68X_ODR_125_MS UINT8_C(2) + +/* Standby time of 250ms */ +#define BME68X_ODR_250_MS UINT8_C(3) + +/* Standby time of 500ms */ +#define BME68X_ODR_500_MS UINT8_C(4) + +/* Standby time of 1s */ +#define BME68X_ODR_1000_MS UINT8_C(5) + +/* Standby time of 10ms */ +#define BME68X_ODR_10_MS UINT8_C(6) + +/* Standby time of 20ms */ +#define BME68X_ODR_20_MS UINT8_C(7) + +/* No standby time */ +#define BME68X_ODR_NONE UINT8_C(8) + +/* Operating mode macros */ + +/* Sleep operation mode */ +#define BME68X_SLEEP_MODE UINT8_C(0) + +/* Forced operation mode */ +#define BME68X_FORCED_MODE UINT8_C(1) + +/* Parallel operation mode */ +#define BME68X_PARALLEL_MODE UINT8_C(2) + +/* Sequential operation mode */ +#define BME68X_SEQUENTIAL_MODE UINT8_C(3) + +/* SPI page macros */ + +/* SPI memory page 0 */ +#define BME68X_MEM_PAGE0 UINT8_C(0x10) + +/* SPI memory page 1 */ +#define BME68X_MEM_PAGE1 UINT8_C(0x00) + +/* Coefficient index macros */ + +/* Length for all coefficients */ +#define BME68X_LEN_COEFF_ALL UINT8_C(42) + +/* Length for 1st group of coefficients */ +#define BME68X_LEN_COEFF1 UINT8_C(23) + +/* Length for 2nd group of coefficients */ +#define BME68X_LEN_COEFF2 UINT8_C(14) + +/* Length for 3rd group of coefficients */ +#define BME68X_LEN_COEFF3 UINT8_C(5) + +/* Length of the field */ +#define BME68X_LEN_FIELD UINT8_C(17) + +/* Length between two fields */ +#define BME68X_LEN_FIELD_OFFSET UINT8_C(17) + +/* Length of the configuration register */ +#define BME68X_LEN_CONFIG UINT8_C(5) + +/* Length of the interleaved buffer */ +#define BME68X_LEN_INTERLEAVE_BUFF UINT8_C(20) + +/* Coefficient index macros */ + +/* Coefficient T2 LSB position */ +#define BME68X_IDX_T2_LSB (0) + +/* Coefficient T2 MSB position */ +#define BME68X_IDX_T2_MSB (1) + +/* Coefficient T3 position */ +#define BME68X_IDX_T3 (2) + +/* Coefficient P1 LSB position */ +#define BME68X_IDX_P1_LSB (4) + +/* Coefficient P1 MSB position */ +#define BME68X_IDX_P1_MSB (5) + +/* Coefficient P2 LSB position */ +#define BME68X_IDX_P2_LSB (6) + +/* Coefficient P2 MSB position */ +#define BME68X_IDX_P2_MSB (7) + +/* Coefficient P3 position */ +#define BME68X_IDX_P3 (8) + +/* Coefficient P4 LSB position */ +#define BME68X_IDX_P4_LSB (10) + +/* Coefficient P4 MSB position */ +#define BME68X_IDX_P4_MSB (11) + +/* Coefficient P5 LSB position */ +#define BME68X_IDX_P5_LSB (12) + +/* Coefficient P5 MSB position */ +#define BME68X_IDX_P5_MSB (13) + +/* Coefficient P7 position */ +#define BME68X_IDX_P7 (14) + +/* Coefficient P6 position */ +#define BME68X_IDX_P6 (15) + +/* Coefficient P8 LSB position */ +#define BME68X_IDX_P8_LSB (18) + +/* Coefficient P8 MSB position */ +#define BME68X_IDX_P8_MSB (19) + +/* Coefficient P9 LSB position */ +#define BME68X_IDX_P9_LSB (20) + +/* Coefficient P9 MSB position */ +#define BME68X_IDX_P9_MSB (21) + +/* Coefficient P10 position */ +#define BME68X_IDX_P10 (22) + +/* Coefficient H2 MSB position */ +#define BME68X_IDX_H2_MSB (23) + +/* Coefficient H2 LSB position */ +#define BME68X_IDX_H2_LSB (24) + +/* Coefficient H1 LSB position */ +#define BME68X_IDX_H1_LSB (24) + +/* Coefficient H1 MSB position */ +#define BME68X_IDX_H1_MSB (25) + +/* Coefficient H3 position */ +#define BME68X_IDX_H3 (26) + +/* Coefficient H4 position */ +#define BME68X_IDX_H4 (27) + +/* Coefficient H5 position */ +#define BME68X_IDX_H5 (28) + +/* Coefficient H6 position */ +#define BME68X_IDX_H6 (29) + +/* Coefficient H7 position */ +#define BME68X_IDX_H7 (30) + +/* Coefficient T1 LSB position */ +#define BME68X_IDX_T1_LSB (31) + +/* Coefficient T1 MSB position */ +#define BME68X_IDX_T1_MSB (32) + +/* Coefficient GH2 LSB position */ +#define BME68X_IDX_GH2_LSB (33) + +/* Coefficient GH2 MSB position */ +#define BME68X_IDX_GH2_MSB (34) + +/* Coefficient GH1 position */ +#define BME68X_IDX_GH1 (35) + +/* Coefficient GH3 position */ +#define BME68X_IDX_GH3 (36) + +/* Coefficient res heat value position */ +#define BME68X_IDX_RES_HEAT_VAL (37) + +/* Coefficient res heat range position */ +#define BME68X_IDX_RES_HEAT_RANGE (39) + +/* Coefficient range switching error position */ +#define BME68X_IDX_RANGE_SW_ERR (41) + +/* Gas measurement macros */ + +/* Disable gas measurement */ +#define BME68X_DISABLE_GAS_MEAS UINT8_C(0x00) + +/* Enable gas measurement low */ +#define BME68X_ENABLE_GAS_MEAS_L UINT8_C(0x01) + +/* Enable gas measurement high */ +#define BME68X_ENABLE_GAS_MEAS_H UINT8_C(0x02) + +/* Heater control macros */ + +/* Enable heater */ +#define BME68X_ENABLE_HEATER UINT8_C(0x00) + +/* Disable heater */ +#define BME68X_DISABLE_HEATER UINT8_C(0x01) + +#ifdef BME68X_USE_FPU + +/* 0 degree Celsius */ +#define BME68X_MIN_TEMPERATURE INT16_C(0) + +/* 60 degree Celsius */ +#define BME68X_MAX_TEMPERATURE INT16_C(60) + +/* 900 hecto Pascals */ +#define BME68X_MIN_PRESSURE UINT32_C(90000) + +/* 1100 hecto Pascals */ +#define BME68X_MAX_PRESSURE UINT32_C(110000) + +/* 20% relative humidity */ +#define BME68X_MIN_HUMIDITY UINT32_C(20) + +/* 80% relative humidity*/ +#define BME68X_MAX_HUMIDITY UINT32_C(80) +#else + +/* 0 degree Celsius */ +#define BME68X_MIN_TEMPERATURE INT16_C(0) + +/* 60 degree Celsius */ +#define BME68X_MAX_TEMPERATURE INT16_C(6000) + +/* 900 hecto Pascals */ +#define BME68X_MIN_PRESSURE UINT32_C(90000) + +/* 1100 hecto Pascals */ +#define BME68X_MAX_PRESSURE UINT32_C(110000) + +/* 20% relative humidity */ +#define BME68X_MIN_HUMIDITY UINT32_C(20000) + +/* 80% relative humidity*/ +#define BME68X_MAX_HUMIDITY UINT32_C(80000) + +#endif + +#define BME68X_HEATR_DUR1 UINT16_C(1000) +#define BME68X_HEATR_DUR2 UINT16_C(2000) +#define BME68X_HEATR_DUR1_DELAY UINT32_C(1000000) +#define BME68X_HEATR_DUR2_DELAY UINT32_C(2000000) +#define BME68X_N_MEAS UINT8_C(6) +#define BME68X_LOW_TEMP UINT8_C(150) +#define BME68X_HIGH_TEMP UINT16_C(350) + +/* Mask macros */ +/* Mask for number of conversions */ +#define BME68X_NBCONV_MSK UINT8_C(0X0f) + +/* Mask for IIR filter */ +#define BME68X_FILTER_MSK UINT8_C(0X1c) + +/* Mask for ODR[3] */ +#define BME68X_ODR3_MSK UINT8_C(0x80) + +/* Mask for ODR[2:0] */ +#define BME68X_ODR20_MSK UINT8_C(0xe0) + +/* Mask for temperature oversampling */ +#define BME68X_OST_MSK UINT8_C(0Xe0) + +/* Mask for pressure oversampling */ +#define BME68X_OSP_MSK UINT8_C(0X1c) + +/* Mask for humidity oversampling */ +#define BME68X_OSH_MSK UINT8_C(0X07) + +/* Mask for heater control */ +#define BME68X_HCTRL_MSK UINT8_C(0x08) + +/* Mask for run gas */ +#define BME68X_RUN_GAS_MSK UINT8_C(0x30) + +/* Mask for operation mode */ +#define BME68X_MODE_MSK UINT8_C(0x03) + +/* Mask for res heat range */ +#define BME68X_RHRANGE_MSK UINT8_C(0x30) + +/* Mask for range switching error */ +#define BME68X_RSERROR_MSK UINT8_C(0xf0) + +/* Mask for new data */ +#define BME68X_NEW_DATA_MSK UINT8_C(0x80) + +/* Mask for gas index */ +#define BME68X_GAS_INDEX_MSK UINT8_C(0x0f) + +/* Mask for gas range */ +#define BME68X_GAS_RANGE_MSK UINT8_C(0x0f) + +/* Mask for gas measurement valid */ +#define BME68X_GASM_VALID_MSK UINT8_C(0x20) + +/* Mask for heater stability */ +#define BME68X_HEAT_STAB_MSK UINT8_C(0x10) + +/* Mask for SPI memory page */ +#define BME68X_MEM_PAGE_MSK UINT8_C(0x10) + +/* Mask for reading a register in SPI */ +#define BME68X_SPI_RD_MSK UINT8_C(0x80) + +/* Mask for writing a register in SPI */ +#define BME68X_SPI_WR_MSK UINT8_C(0x7f) + +/* Mask for the H1 calibration coefficient */ +#define BME68X_BIT_H1_DATA_MSK UINT8_C(0x0f) + +/* Position macros */ + +/* Filter bit position */ +#define BME68X_FILTER_POS UINT8_C(2) + +/* Temperature oversampling bit position */ +#define BME68X_OST_POS UINT8_C(5) + +/* Pressure oversampling bit position */ +#define BME68X_OSP_POS UINT8_C(2) + +/* ODR[3] bit position */ +#define BME68X_ODR3_POS UINT8_C(7) + +/* ODR[2:0] bit position */ +#define BME68X_ODR20_POS UINT8_C(5) + +/* Run gas bit position */ +#define BME68X_RUN_GAS_POS UINT8_C(4) + +/* Heater control bit position */ +#define BME68X_HCTRL_POS UINT8_C(3) + +/* Macro to combine two 8 bit data's to form a 16 bit data */ +#define BME68X_CONCAT_BYTES(msb, lsb) (((uint16_t)msb << 8) | (uint16_t)lsb) + +/* Macro to set bits */ +#define BME68X_SET_BITS(reg_data, bitname, data) \ + ((reg_data & ~(bitname##_MSK)) | \ + ((data << bitname##_POS) & bitname##_MSK)) + +/* Macro to get bits */ +#define BME68X_GET_BITS(reg_data, bitname) ((reg_data & (bitname##_MSK)) >> \ + (bitname##_POS)) + +/* Macro to set bits starting from position 0 */ +#define BME68X_SET_BITS_POS_0(reg_data, bitname, data) \ + ((reg_data & ~(bitname##_MSK)) | \ + (data & bitname##_MSK)) + +/* Macro to get bits starting from position 0 */ +#define BME68X_GET_BITS_POS_0(reg_data, bitname) (reg_data & (bitname##_MSK)) + +/** + * BME68X_INTF_RET_TYPE is the read/write interface return type which can be overwritten by the build system. + * The default is set to int8_t. + */ +#ifndef BME68X_INTF_RET_TYPE +#define BME68X_INTF_RET_TYPE int8_t +#endif + +/** + * BME68X_INTF_RET_SUCCESS is the success return value read/write interface return type which can be + * overwritten by the build system. The default is set to 0. It is used to check for a successful + * execution of the read/write functions + */ +#ifndef BME68X_INTF_RET_SUCCESS +#define BME68X_INTF_RET_SUCCESS INT8_C(0) +#endif + +/********************************************************* */ +/*! Function Pointers */ +/********************************************************* */ + +/*! + * @brief Bus communication function pointer which should be mapped to + * the platform specific read functions of the user + * + * @param[in] reg_addr : 8bit register address of the sensor + * @param[out] reg_data : Data from the specified address + * @param[in] length : Length of the reg_data array + * @param[in,out] intf_ptr : Void pointer that can enable the linking of descriptors + * for interface related callbacks + * @retval 0 for Success + * @retval Non-zero for Failure + */ +typedef BME68X_INTF_RET_TYPE (*bme68x_read_fptr_t)(uint8_t reg_addr, uint8_t *reg_data, uint32_t length, + void *intf_ptr); + +/*! + * @brief Bus communication function pointer which should be mapped to + * the platform specific write functions of the user + * + * @param[in] reg_addr : 8bit register address of the sensor + * @param[out] reg_data : Data to the specified address + * @param[in] length : Length of the reg_data array + * @param[in,out] intf_ptr : Void pointer that can enable the linking of descriptors + * for interface related callbacks + * @retval 0 for Success + * @retval Non-zero for Failure + * + */ +typedef BME68X_INTF_RET_TYPE (*bme68x_write_fptr_t)(uint8_t reg_addr, const uint8_t *reg_data, uint32_t length, + void *intf_ptr); + +/*! + * @brief Delay function pointer which should be mapped to + * delay function of the user + * + * @param period - The time period in microseconds + * @param[in,out] intf_ptr : Void pointer that can enable the linking of descriptors + * for interface related callbacks + */ +typedef void (*bme68x_delay_us_fptr_t)(uint32_t period, void *intf_ptr); + +/* + * @brief Generic communication function pointer + * @param[in] dev_id: Place holder to store the id of the device structure + * Can be used to store the index of the Chip select or + * I2C address of the device. + * @param[in] reg_addr: Used to select the register the where data needs to + * be read from or written to. + * @param[in,out] reg_data: Data array to read/write + * @param[in] len: Length of the data array + */ + +/* + * @brief Interface selection Enumerations + */ +enum bme68x_intf { + /*! SPI interface */ + BME68X_SPI_INTF, + /*! I2C interface */ + BME68X_I2C_INTF +}; + +/* Structure definitions */ + +/* + * @brief Sensor field data structure + */ +struct bme68x_data +{ + /*! Contains new_data, gasm_valid & heat_stab */ + uint8_t status; + + /*! The index of the heater profile used */ + uint8_t gas_index; + + /*! Measurement index to track order */ + uint8_t meas_index; + + /*! Heater resistance */ + uint8_t res_heat; + + /*! Current DAC */ + uint8_t idac; + + /*! Gas wait period */ + uint8_t gas_wait; +#ifndef BME68X_USE_FPU + + /*! Temperature in degree celsius x100 */ + int16_t temperature; + + /*! Pressure in Pascal */ + uint32_t pressure; + + /*! Humidity in % relative humidity x1000 */ + uint32_t humidity; + + /*! Gas resistance in Ohms */ + uint32_t gas_resistance; +#else + + /*! Temperature in degree celsius */ + float temperature; + + /*! Pressure in Pascal */ + float pressure; + + /*! Humidity in % relative humidity x1000 */ + float humidity; + + /*! Gas resistance in Ohms */ + float gas_resistance; + +#endif + +}; + +/* + * @brief Structure to hold the calibration coefficients + */ +struct bme68x_calib_data +{ + /*! Calibration coefficient for the humidity sensor */ + uint16_t par_h1; + + /*! Calibration coefficient for the humidity sensor */ + uint16_t par_h2; + + /*! Calibration coefficient for the humidity sensor */ + int8_t par_h3; + + /*! Calibration coefficient for the humidity sensor */ + int8_t par_h4; + + /*! Calibration coefficient for the humidity sensor */ + int8_t par_h5; + + /*! Calibration coefficient for the humidity sensor */ + uint8_t par_h6; + + /*! Calibration coefficient for the humidity sensor */ + int8_t par_h7; + + /*! Calibration coefficient for the gas sensor */ + int8_t par_gh1; + + /*! Calibration coefficient for the gas sensor */ + int16_t par_gh2; + + /*! Calibration coefficient for the gas sensor */ + int8_t par_gh3; + + /*! Calibration coefficient for the temperature sensor */ + uint16_t par_t1; + + /*! Calibration coefficient for the temperature sensor */ + int16_t par_t2; + + /*! Calibration coefficient for the temperature sensor */ + int8_t par_t3; + + /*! Calibration coefficient for the pressure sensor */ + uint16_t par_p1; + + /*! Calibration coefficient for the pressure sensor */ + int16_t par_p2; + + /*! Calibration coefficient for the pressure sensor */ + int8_t par_p3; + + /*! Calibration coefficient for the pressure sensor */ + int16_t par_p4; + + /*! Calibration coefficient for the pressure sensor */ + int16_t par_p5; + + /*! Calibration coefficient for the pressure sensor */ + int8_t par_p6; + + /*! Calibration coefficient for the pressure sensor */ + int8_t par_p7; + + /*! Calibration coefficient for the pressure sensor */ + int16_t par_p8; + + /*! Calibration coefficient for the pressure sensor */ + int16_t par_p9; + + /*! Calibration coefficient for the pressure sensor */ + uint8_t par_p10; +#ifndef BME68X_USE_FPU + + /*! Variable to store the intermediate temperature coefficient */ + int32_t t_fine; +#else + + /*! Variable to store the intermediate temperature coefficient */ + float t_fine; +#endif + + /*! Heater resistance range coefficient */ + uint8_t res_heat_range; + + /*! Heater resistance value coefficient */ + int8_t res_heat_val; + + /*! Gas resistance range switching error coefficient */ + int8_t range_sw_err; +}; + +/* + * @brief BME68X sensor settings structure which comprises of ODR, + * over-sampling and filter settings. + */ +struct bme68x_conf +{ + /*! Humidity oversampling. Refer @ref osx*/ + uint8_t os_hum; + + /*! Temperature oversampling. Refer @ref osx */ + uint8_t os_temp; + + /*! Pressure oversampling. Refer @ref osx */ + uint8_t os_pres; + + /*! Filter coefficient. Refer @ref filter*/ + uint8_t filter; + + /*! + * Standby time between sequential mode measurement profiles. + * Refer @ref odr + */ + uint8_t odr; +}; + +/* + * @brief BME68X gas heater configuration + */ +struct bme68x_heatr_conf +{ + /*! Enable gas measurement. Refer @ref en_dis */ + uint8_t enable; + + /*! Store the heater temperature for forced mode degree Celsius */ + uint16_t heatr_temp; + + /*! Store the heating duration for forced mode in milliseconds */ + uint16_t heatr_dur; + + /*! Store the heater temperature profile in degree Celsius */ + uint16_t *heatr_temp_prof; + + /*! Store the heating duration profile in milliseconds */ + uint16_t *heatr_dur_prof; + + /*! Variable to store the length of the heating profile */ + uint8_t profile_len; + + /*! + * Variable to store heating duration for parallel mode + * in milliseconds + */ + uint16_t shared_heatr_dur; +}; + +/* + * @brief BME68X device structure + */ +struct bme68x_dev +{ + /*! Chip Id */ + uint8_t chip_id; + + /*! + * The interface pointer is used to enable the user + * to link their interface descriptors for reference during the + * implementation of the read and write interfaces to the + * hardware. + */ + void *intf_ptr; + + /*! + * Variant id + * ---------------------------------------- + * Value | Variant + * ---------------------------------------- + * 0 | BME68X_VARIANT_GAS_LOW + * 1 | BME68X_VARIANT_GAS_HIGH + * ---------------------------------------- + */ + uint32_t variant_id; + + /*! SPI/I2C interface */ + enum bme68x_intf intf; + + /*! Memory page used */ + uint8_t mem_page; + + /*! Ambient temperature in Degree C*/ + int8_t amb_temp; + + /*! Sensor calibration data */ + struct bme68x_calib_data calib; + + /*! Read function pointer */ + bme68x_read_fptr_t read; + + /*! Write function pointer */ + bme68x_write_fptr_t write; + + /*! Delay function pointer */ + bme68x_delay_us_fptr_t delay_us; + + /*! To store interface pointer error */ + BME68X_INTF_RET_TYPE intf_rslt; + + /*! Store the info messages */ + uint8_t info_msg; +}; + +#endif /* BME68X_DEFS_H_ */ +/*! @endcond */ diff --git a/components/bme680/include/bsec2.h b/components/bme680/include/bsec2.h new file mode 100644 index 0000000..0fbd17b --- /dev/null +++ b/components/bme680/include/bsec2.h @@ -0,0 +1,135 @@ +/** + * bsec2 but rewritten in C + * + * Created on 2021/12/12. + */ + +#ifndef ESPNODE_BSEC2_H +#define ESPNODE_BSEC2_H + +// BSEC +#include "bme68x.h" +#include "bsec_datatypes.h" +#include "bsec_interface.h" +#include +#include + +#define ARRAY_LEN(array) (sizeof(array)/sizeof((array)[0])) +#define BSEC_CHECK_INPUT(x, shift) ((x) & (1 << ((shift)-1))) +#define BSEC_TOTAL_HEAT_DUR UINT16_C(140) + +typedef bsec_output_t bsecData; +typedef bsec_virtual_sensor_t bsecSensor; + +typedef struct +{ + bsecData output[BSEC_NUMBER_OUTPUTS]; + uint8_t nOutputs; +} bsecOutputs; + +struct bsec2; +typedef void (*bsecCallback)(const struct bme68x_data *data, const bsecOutputs *outputs, const struct bsec2 * bsec); + +struct bsec2 { + // "public" + struct bme68x_dev *sensor; + bsec_version_t version; + bsec_library_return_t status; + // "private" + int8_t sensor_status; + bsec_bme_settings_t bmeConf; + bsecCallback newDataCallback; + bsecOutputs outputs; + uint8_t opMode; + float extTempOffset; + uint32_t ovfCounter; + uint32_t lastMillis; + uint8_t workBuffer[BSEC_MAX_WORKBUFFER_SIZE]; +}; + +int bsec2_init(struct bsec2 *self, struct bme68x_dev *dev); + +/** + * @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, def BSEC_SAMPLE_RATE_ULP + * @return true for success, false otherwise + */ +bool bsec2_updateSubscription(struct bsec2 *self, const bsecSensor *sensorList, uint8_t nSensors, float sampleRate); + +/** + * @brief Callback from the user to read data from the BME68x using parallel/forced mode, process and store outputs + * @return true for success, false otherwise + */ +bool bsec2_run(struct bsec2 *self); + +static inline void bsec2_attachCallback(struct bsec2 *self, bsecCallback callback) +{ + self->newDataCallback = callback; +} + +/** + * @brief Function to get the BSEC outputs + * @return pointer to BSEC outputs if available else nullptr + */ +static inline const bsecOutputs* bsec2_getOutputs(struct bsec2 *self) +{ + if (self->outputs.nOutputs) + return &self->outputs; + return NULL; +} + +/** + * @brief Function to get the BSEC output by sensor id + * @return pointer to BSEC output, nullptr otherwise + */ +bsecData bsec2_getData(struct bsec2 *self, bsecSensor id); + +/** + * @brief Function to get the state of the algorithm to save to non-volatile memory + * @param state : Pointer to a memory location, to hold the state + * @return true for success, false otherwise + */ +bool bsec2_getState(struct bsec2 *self, 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 + * @return true for success, false otherwise + */ +bool bsec2_setState(struct bsec2 *self, uint8_t *state); + +/** + * @brief Function to retrieve the current library configuration + * @param config : Pointer to a memory location, to hold the serialized config blob + * @return true for success, false otherwise + */ +bool bsec2_getConfig(struct bsec2 *self, uint8_t *config); + +/** + * @brief Function to set the configuration of the algorithm from memory + * @param state : Pointer to a memory location that contains the configuration + * @return true for success, false otherwise + */ +bool bsec2_setConfig(struct bsec2 *self, const uint8_t *config); + +/** + * @brief Function to set the temperature offset + * @param tempOffset : Temperature offset in degree Celsius + */ +static inline void bsec2_setTemperatureOffset(struct bsec2 *self, float tempOffset) +{ + self->extTempOffset = tempOffset; +} + +/** + * @brief Function to calculate an int64_t timestamp in milliseconds + */ +int64_t bsec2_getTimeMs(struct bsec2 *self); + +extern void bsec2_errormsg(const char *msg); + +extern uint32_t bsec2_timestamp_millis(); + +#endif //ESPNODE_BSEC2_H diff --git a/components/bme680/include/bsec_datatypes.h b/components/bme680/include/bsec_datatypes.h new file mode 100644 index 0000000..acb0900 --- /dev/null +++ b/components/bme680/include/bsec_datatypes.h @@ -0,0 +1,506 @@ +/** + * Copyright (C) Bosch Sensortec GmbH. All Rights Reserved. Confidential. + * + * Disclaimer + * + * Common: + * Bosch Sensortec products are developed for the consumer goods industry. They may only be used + * within the parameters of the respective valid product data sheet. Bosch Sensortec products are + * provided with the express understanding that there is no warranty of fitness for a particular purpose. + * They are not fit for use in life-sustaining, safety or security sensitive systems or any system or device + * that may lead to bodily harm or property damage if the system or device malfunctions. In addition, + * Bosch Sensortec products are not fit for use in products which interact with motor vehicle systems. + * The resale and/or use of products are at the purchaser's own risk and his own responsibility. The + * examination of fitness for the intended use is the sole responsibility of the Purchaser. + * + * The purchaser shall indemnify Bosch Sensortec from all third party claims, including any claims for + * incidental, or consequential damages, arising from any product use not covered by the parameters of + * the respective valid product data sheet or not approved by Bosch Sensortec and reimburse Bosch + * Sensortec for all costs in connection with such claims. + * + * The purchaser must monitor the market for the purchased products, particularly with regard to + * product safety and inform Bosch Sensortec without delay of all security relevant incidents. + * + * Engineering Samples are marked with an asterisk (*) or (e). Samples may vary from the valid + * technical specifications of the product series. They are therefore not intended or fit for resale to third + * parties or for use in end products. Their sole purpose is internal client testing. The testing of an + * engineering sample may in no way replace the testing of a product series. Bosch Sensortec + * assumes no liability for the use of engineering samples. By accepting the engineering samples, the + * Purchaser agrees to indemnify Bosch Sensortec from all claims arising from the use of engineering + * samples. + * + * Special: + * This software module (hereinafter called "Software") and any information on application-sheets + * (hereinafter called "Information") is provided free of charge for the sole purpose to support your + * application work. The Software and Information is subject to the following terms and conditions: + * + * The Software is specifically designed for the exclusive use for Bosch Sensortec products by + * personnel who have special experience and training. Do not use this Software if you do not have the + * proper experience or training. + * + * This Software package is provided `` as is `` and without any expressed or implied warranties, + * including without limitation, the implied warranties of merchantability and fitness for a particular + * purpose. + * + * Bosch Sensortec and their representatives and agents deny any liability for the functional impairment + * of this Software in terms of fitness, performance and safety. Bosch Sensortec and their + * representatives and agents shall not be liable for any direct or indirect damages or injury, except as + * otherwise stipulated in mandatory applicable law. + * + * The Information provided is believed to be accurate and reliable. Bosch Sensortec 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 Bosch. Specifications mentioned in the Information are + * subject to change without notice. + * + * It is not allowed to deliver the source code of the Software to any third party without permission of + * Bosch Sensortec. + * + * @file bsec_datatypes.h + * + * @brief + * Contains the data types used by BSEC + * + */ + + +#ifndef __BSEC_DATATYPES_H__ +#define __BSEC_DATATYPES_H__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/*! + * @addtogroup bsec_interface BSEC C Interface + * @{*/ + +#ifdef __KERNEL__ +#include +#endif +#include +#include + +#define BSEC_MAX_WORKBUFFER_SIZE (4096) /*!< Maximum size (in bytes) of the work buffer */ +#define BSEC_MAX_PHYSICAL_SENSOR (8) /*!< Number of physical sensors that need allocated space before calling bsec_update_subscription() */ +#define BSEC_MAX_PROPERTY_BLOB_SIZE (2277) /*!< Maximum size (in bytes) of the data blobs returned by bsec_get_configuration() */ +#define BSEC_MAX_STATE_BLOB_SIZE (197) /*!< Maximum size (in bytes) of the data blobs returned by bsec_get_state()*/ +#define BSEC_SAMPLE_RATE_DISABLED (65535.0f) /*!< Sample rate of a disabled sensor */ +#define BSEC_SAMPLE_RATE_ULP (0.0033333f) /*!< Sample rate in case of Ultra Low Power Mode */ +#define BSEC_SAMPLE_RATE_LP (0.33333f) /*!< Sample rate in case of Low Power Mode */ +#define BSEC_SAMPLE_RATE_ULP_MEASUREMENT_ON_DEMAND (0.0f) /*!< Input value used to trigger an extra measurment (ULP plus) */ +#define BSEC_SAMPLE_RATE_HIGH_PERFORMANCE (0.055556f) /*!< Sample rate in case of high performance */ + +#define BSEC_PROCESS_PRESSURE (1 << (BSEC_INPUT_PRESSURE-1)) /*!< process_data bitfield constant for pressure @sa bsec_bme_settings_t */ +#define BSEC_PROCESS_TEMPERATURE (1 << (BSEC_INPUT_TEMPERATURE-1)) /*!< process_data bitfield constant for temperature @sa bsec_bme_settings_t */ +#define BSEC_PROCESS_HUMIDITY (1 << (BSEC_INPUT_HUMIDITY-1)) /*!< process_data bitfield constant for humidity @sa bsec_bme_settings_t */ +#define BSEC_PROCESS_GAS (1 << (BSEC_INPUT_GASRESISTOR-1)) /*!< process_data bitfield constant for gas sensor @sa bsec_bme_settings_t */ +#define BSEC_NUMBER_OUTPUTS (19) /*!< Number of outputs, depending on solution */ +#define BSEC_OUTPUT_INCLUDED (66222575) /*!< bitfield that indicates which outputs are included in the solution */ + +/*! + * @brief Enumeration for input (physical) sensors. + * + * Used to populate bsec_input_t::sensor_id. It is also used in bsec_sensor_configuration_t::sensor_id structs + * returned in the parameter required_sensor_settings of bsec_update_subscription(). + * + * @sa bsec_sensor_configuration_t @sa bsec_input_t + */ +typedef enum +{ + /** + * @brief Pressure sensor output of BMExxx [Pa] + */ + BSEC_INPUT_PRESSURE = 1, + + /** + * @brief Humidity sensor output of BMExxx [%] + * + * @note Relative humidity strongly depends on the temperature (it is measured at). It may require a conversion to + * the temperature outside of the device. + * + * @sa bsec_virtual_sensor_t + */ + BSEC_INPUT_HUMIDITY = 2, + + /** + * @brief Temperature sensor output of BMExxx [degrees Celsius] + * + * @note The BME680 is factory trimmed, thus the temperature sensor of the BME680 is very accurate. + * The temperature value is a very local measurement value and can be influenced by external heat sources. + * + * @sa bsec_virtual_sensor_t + */ + BSEC_INPUT_TEMPERATURE = 3, + + /** + * @brief Gas sensor resistance output of BMExxx [Ohm] + * + * The resistance value changes due to varying VOC concentrations (the higher the concentration of reducing VOCs, + * the lower the resistance and vice versa). + */ + BSEC_INPUT_GASRESISTOR = 4, /*!< */ + + /** + * @brief Additional input for device heat compensation + * + * IAQ solution: The value is subtracted from ::BSEC_INPUT_TEMPERATURE to compute + * ::BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE. + * + * ALL solution: Generic heat source 1 + * + * @sa bsec_virtual_sensor_t + */ + BSEC_INPUT_HEATSOURCE = 14, + + /** + * @brief Additional input for device heat compensation 8 + * + * Generic heat source 8 + */ + + + /** + * @brief Additional input that disables baseline tracker + * + * 0 - Normal + * 1 - Event 1 + * 2 - Event 2 + */ + BSEC_INPUT_DISABLE_BASELINE_TRACKER = 23, + + /** + * @brief Additional input that provides information about the state of the profile (1-9) + * + */ + BSEC_INPUT_PROFILE_PART = 24 +} bsec_physical_sensor_t; + +/*! + * @brief Enumeration for output (virtual) sensors + * + * Used to populate bsec_output_t::sensor_id. It is also used in bsec_sensor_configuration_t::sensor_id structs + * passed in the parameter requested_virtual_sensors of bsec_update_subscription(). + * + * @sa bsec_sensor_configuration_t @sa bsec_output_t + */ +typedef enum +{ + /** + * @brief Indoor-air-quality estimate [0-500] + * + * Indoor-air-quality (IAQ) gives an indication of the relative change in ambient TVOCs detected by BME680. + * + * @note The IAQ scale ranges from 0 (clean air) to 500 (heavily polluted air). During operation, algorithms + * automatically calibrate and adapt themselves to the typical environments where the sensor is operated + * (e.g., home, workplace, inside a car, etc.).This automatic background calibration ensures that users experience + * consistent IAQ performance. The calibration process considers the recent measurement history (typ. up to four + * days) to ensure that IAQ=25 corresponds to typical good air and IAQ=250 indicates typical polluted air. + */ + BSEC_OUTPUT_IAQ = 1, + BSEC_OUTPUT_STATIC_IAQ = 2, /*!< Unscaled indoor-air-quality estimate */ + BSEC_OUTPUT_CO2_EQUIVALENT = 3, /*!< co2 equivalent estimate [ppm] */ + BSEC_OUTPUT_BREATH_VOC_EQUIVALENT = 4, /*!< breath VOC concentration estimate [ppm] */ + + /** + * @brief Temperature sensor signal [degrees Celsius] + * + * Temperature directly measured by BME680 in degree Celsius. + * + * @note This value is cross-influenced by the sensor heating and device specific heating. + */ + BSEC_OUTPUT_RAW_TEMPERATURE = 6, + + /** + * @brief Pressure sensor signal [Pa] + * + * Pressure directly measured by the BME680 in Pa. + */ + BSEC_OUTPUT_RAW_PRESSURE = 7, + + /** + * @brief Relative humidity sensor signal [%] + * + * Relative humidity directly measured by the BME680 in %. + * + * @note This value is cross-influenced by the sensor heating and device specific heating. + */ + BSEC_OUTPUT_RAW_HUMIDITY = 8, + + /** + * @brief Gas sensor signal [Ohm] + * + * Gas resistance measured directly by the BME680 in Ohm.The resistance value changes due to varying VOC + * concentrations (the higher the concentration of reducing VOCs, the lower the resistance and vice versa). + */ + BSEC_OUTPUT_RAW_GAS = 9, + + + /** + * @brief Gas sensor stabilization status [boolean] + * + * Indicates initial stabilization status of the gas sensor element: stabilization is ongoing (0) or stabilization + * is finished (1). + */ + BSEC_OUTPUT_STABILIZATION_STATUS = 12, + + /** + * @brief Gas sensor run-in status [boolean] + * + * Indicates power-on stabilization status of the gas sensor element: stabilization is ongoing (0) or stabilization + * is finished (1). + */ + BSEC_OUTPUT_RUN_IN_STATUS = 13, + + /** + * @brief Sensor heat compensated temperature [degrees Celsius] + * + * Temperature measured by BME680 which is compensated for the influence of sensor (heater) in degree Celsius. + * The self heating introduced by the heater is depending on the sensor operation mode and the sensor supply voltage. + * + * + * @note IAQ solution: In addition, the temperature output can be compensated by an user defined value + * (::BSEC_INPUT_HEATSOURCE in degrees Celsius), which represents the device specific self-heating. + * + * Thus, the value is calculated as follows: + * * IAQ solution: ```BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE = ::BSEC_INPUT_TEMPERATURE - function(sensor operation mode, sensor supply voltage) - ::BSEC_INPUT_HEATSOURCE``` + * * other solutions: ```::BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE = ::BSEC_INPUT_TEMPERATURE - function(sensor operation mode, sensor supply voltage)``` + * + * The self-heating in operation mode BSEC_SAMPLE_RATE_ULP is negligible. + * The self-heating in operation mode BSEC_SAMPLE_RATE_LP is supported for 1.8V by default (no config file required). If the BME680 sensor supply voltage is 3.3V, the IoT_LP_3_3V.config shall be used. + */ + BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE = 14, + + /** + * @brief Sensor heat compensated humidity [%] + * + * Relative measured by BME680 which is compensated for the influence of sensor (heater) in %. + * + * It converts the ::BSEC_INPUT_HUMIDITY from temperature ::BSEC_INPUT_TEMPERATURE to temperature + * ::BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE. + * + * @note IAQ solution: If ::BSEC_INPUT_HEATSOURCE is used for device specific temperature compensation, it will be + * effective for ::BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY too. + */ + BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY = 15, + + BSEC_OUTPUT_COMPENSATED_GAS = 18, /*!< Reserved internal debug output */ + BSEC_OUTPUT_GAS_PERCENTAGE = 21, /*!< percentage of min and max filtered gas value [%] */ + + BSEC_OUTPUT_GAS_ESTIMATE_1 = 22, /*!< Gas estimate output channel 1 */ + BSEC_OUTPUT_GAS_ESTIMATE_2 = 23, /*!< Gas estimate output channel 2 */ + BSEC_OUTPUT_GAS_ESTIMATE_3 = 24, /*!< Gas estimate output channel 3 */ + BSEC_OUTPUT_GAS_ESTIMATE_4 = 25, /*!< Gas estimate output channel 4 */ + + BSEC_OUTPUT_RAW_GAS_INDEX = 26 /* Gas index cyclically running from 0 to heater_profile_length-1 */ +} bsec_virtual_sensor_t; + +/*! + * @brief Enumeration for function return codes + */ +typedef enum +{ + BSEC_OK = 0, /*!< Function execution successful */ + BSEC_E_DOSTEPS_INVALIDINPUT = -1, /*!< Input (physical) sensor id passed to bsec_do_steps() is not in the valid range or not valid for requested virtual sensor */ + BSEC_E_DOSTEPS_VALUELIMITS = -2, /*!< Value of input (physical) sensor signal passed to bsec_do_steps() is not in the valid range */ + BSEC_E_DOSTEPS_TSINTRADIFFOUTOFRANGE = -4, /*!< Past timestamps passed to bsec_do_steps() */ + BSEC_E_DOSTEPS_DUPLICATEINPUT = -6, /*!< Duplicate input (physical) sensor ids passed as input to bsec_do_steps() */ + BSEC_I_DOSTEPS_NOOUTPUTSRETURNABLE = 2, /*!< No memory allocated to hold return values from bsec_do_steps(), i.e., n_outputs == 0 */ + BSEC_W_DOSTEPS_EXCESSOUTPUTS = 3, /*!< Not enough memory allocated to hold return values from bsec_do_steps(), i.e., n_outputs < maximum number of requested output (virtual) sensors */ + BSEC_W_DOSTEPS_GASINDEXMISS = 5, /*!< Gas index not provided to bsec_do_steps() */ + BSEC_E_SU_WRONGDATARATE = -10, /*!< The sample_rate of the requested output (virtual) sensor passed to bsec_update_subscription() is zero */ + BSEC_E_SU_SAMPLERATELIMITS = -12, /*!< The sample_rate of the requested output (virtual) sensor passed to bsec_update_subscription() does not match with the sampling rate allowed for that sensor */ + BSEC_E_SU_DUPLICATEGATE = -13, /*!< Duplicate output (virtual) sensor ids requested through bsec_update_subscription() */ + BSEC_E_SU_INVALIDSAMPLERATE = -14, /*!< The sample_rate of the requested output (virtual) sensor passed to bsec_update_subscription() does not fall within the global minimum and maximum sampling rates */ + BSEC_E_SU_GATECOUNTEXCEEDSARRAY = -15, /*!< Not enough memory allocated to hold returned input (physical) sensor data from bsec_update_subscription(), i.e., n_required_sensor_settings < #BSEC_MAX_PHYSICAL_SENSOR */ + BSEC_E_SU_SAMPLINTVLINTEGERMULT = -16, /*!< The sample_rate of the requested output (virtual) sensor passed to bsec_update_subscription() is not correct */ + BSEC_E_SU_MULTGASSAMPLINTVL = -17, /*!< The sample_rate of the requested output (virtual), which requires the gas sensor, is not equal to the sample_rate that the gas sensor is being operated */ + BSEC_E_SU_HIGHHEATERONDURATION = -18, /*!< The duration of one measurement is longer than the requested sampling interval */ + BSEC_W_SU_UNKNOWNOUTPUTGATE = 10, /*!< Output (virtual) sensor id passed to bsec_update_subscription() is not in the valid range; e.g., n_requested_virtual_sensors > actual number of output (virtual) sensors requested */ + BSEC_W_SU_MODINNOULP = 11, /*!< ULP plus can not be requested in non-ulp mode */ /*MOD_ONLY*/ + BSEC_I_SU_SUBSCRIBEDOUTPUTGATES = 12, /*!< No output (virtual) sensor data were requested via bsec_update_subscription() */ + BSEC_I_SU_GASESTIMATEPRECEDENCE = 13, /*!< GAS_ESTIMATE is suscribed and take precedence over other requested outputs */ + BSEC_E_PARSE_SECTIONEXCEEDSWORKBUFFER = -32, /*!< n_work_buffer_size passed to bsec_set_[configuration/state]() not sufficient */ + BSEC_E_CONFIG_FAIL = -33, /*!< Configuration failed */ + BSEC_E_CONFIG_VERSIONMISMATCH = -34, /*!< Version encoded in serialized_[settings/state] passed to bsec_set_[configuration/state]() does not match with current version */ + BSEC_E_CONFIG_FEATUREMISMATCH = -35, /*!< Enabled features encoded in serialized_[settings/state] passed to bsec_set_[configuration/state]() does not match with current library implementation */ + BSEC_E_CONFIG_CRCMISMATCH = -36, /*!< serialized_[settings/state] passed to bsec_set_[configuration/state]() is corrupted */ + BSEC_E_CONFIG_EMPTY = -37, /*!< n_serialized_[settings/state] passed to bsec_set_[configuration/state]() is to short to be valid */ + BSEC_E_CONFIG_INSUFFICIENTWORKBUFFER = -38, /*!< Provided work_buffer is not large enough to hold the desired string */ + BSEC_E_CONFIG_INVALIDSTRINGSIZE = -40, /*!< String size encoded in configuration/state strings passed to bsec_set_[configuration/state]() does not match with the actual string size n_serialized_[settings/state] passed to these functions */ + BSEC_E_CONFIG_INSUFFICIENTBUFFER = -41, /*!< String buffer insufficient to hold serialized data from BSEC library */ + BSEC_E_SET_INVALIDCHANNELIDENTIFIER = -100, /*!< Internal error code, size of work buffer in setConfig must be set to BSEC_MAX_WORKBUFFER_SIZE */ + BSEC_E_SET_INVALIDLENGTH = -104, /*!< Internal error code */ + BSEC_W_SC_CALL_TIMING_VIOLATION = 100, /*!< Difference between actual and defined sampling intervals of bsec_sensor_control() greater than allowed */ + BSEC_W_SC_MODEXCEEDULPTIMELIMIT = 101, /*!< ULP plus is not allowed because an ULP measurement just took or will take place */ /*MOD_ONLY*/ + BSEC_W_SC_MODINSUFFICIENTWAITTIME = 102 /*!< ULP plus is not allowed because not sufficient time passed since last ULP plus */ /*MOD_ONLY*/ +} bsec_library_return_t; + +/*! + * @brief Structure containing the version information + * + * Please note that configuration and state strings are coded to a specific version and will not be accepted by other + * versions of BSEC. + * + */ +typedef struct +{ + uint8_t major; /**< @brief Major version */ + uint8_t minor; /**< @brief Minor version */ + uint8_t major_bugfix; /**< @brief Major bug fix version */ + uint8_t minor_bugfix; /**< @brief Minor bug fix version */ +} bsec_version_t; + +/*! + * @brief Structure describing an input sample to the library + * + * Each input sample is provided to BSEC as an element in a struct array of this type. Timestamps must be provided + * in nanosecond resolution. Moreover, duplicate timestamps for subsequent samples are not allowed and will results in + * an error code being returned from bsec_do_steps(). + * + * The meaning unit of the signal field are determined by the bsec_input_t::sensor_id field content. Possible + * bsec_input_t::sensor_id values and and their meaning are described in ::bsec_physical_sensor_t. + * + * @sa bsec_physical_sensor_t + * + */ +typedef struct +{ + /** + * @brief Time stamp in nanosecond resolution [ns] + * + * Timestamps must be provided as non-repeating and increasing values. They can have their 0-points at system start or + * at a defined wall-clock time (e.g., 01-Jan-1970 00:00:00) + */ + int64_t time_stamp; + float signal; /*!< @brief Signal sample in the unit defined for the respective sensor_id @sa bsec_physical_sensor_t */ + uint8_t signal_dimensions; /*!< @brief Signal dimensions (reserved for future use, shall be set to 1) */ + uint8_t sensor_id; /*!< @brief Identifier of physical sensor @sa bsec_physical_sensor_t */ +} bsec_input_t; + +/*! + * @brief Structure describing an output sample of the library + * + * Each output sample is returned from BSEC by populating the element of a struct array of this type. The contents of + * the signal field is defined by the supplied bsec_output_t::sensor_id. Possible output + * bsec_output_t::sensor_id values are defined in ::bsec_virtual_sensor_t. + * + * @sa bsec_virtual_sensor_t + */ +typedef struct +{ + int64_t time_stamp; /*!< @brief Time stamp in nanosecond resolution as provided as input [ns] */ + float signal; /*!< @brief Signal sample in the unit defined for the respective bsec_output_t::sensor_id @sa bsec_virtual_sensor_t */ + uint8_t signal_dimensions; /*!< @brief Signal dimensions (reserved for future use, shall be set to 1) */ + uint8_t sensor_id; /*!< @brief Identifier of virtual sensor @sa bsec_virtual_sensor_t */ + + /** + * @brief Accuracy status 0-3 + * + * Some virtual sensors provide a value in the accuracy field. If this is the case, the meaning of the field is as + * follows: + * + * | Name | Value | Accuracy description | + * |----------------------------|-------|-------------------------------------------------------------| + * | UNRELIABLE | 0 | Sensor data is unreliable, the sensor must be calibrated | + * | LOW_ACCURACY | 1 | Low accuracy, sensor should be calibrated | + * | MEDIUM_ACCURACY | 2 | Medium accuracy, sensor calibration may improve performance | + * | HIGH_ACCURACY | 3 | High accuracy | + * + * For example: + * + * - Ambient temperature accuracy is derived from change in the temperature in 1 minute. + * + * | Virtual sensor | Value | Accuracy description | + * |--------------------- |-------|------------------------------------------------------------------------------| + * | Ambient temperature | 0 | The difference in ambient temperature is greater than 4 degree in one minute | + * | | 1 | The difference in ambient temperature is less than 4 degree in one minute | + * | | 2 | The difference in ambient temperature is less than 3 degree in one minute | + * | | 3 | The difference in ambient temperature is less than 2 degree in one minute | + * + * - IAQ accuracy indicator will notify the user when she/he should initiate a calibration process. Calibration is + * performed automatically in the background if the sensor is exposed to clean and polluted air for approximately + * 30 minutes each. + * + * | Virtual sensor | Value | Accuracy description | + * |----------------------------|-------|-----------------------------------------------------------------| + * | IAQ | 0 | The sensor is not yet stabilized or in a run-in status | + * | | 1 | Calibration required | + * | | 2 | Calibration on-going | + * | | 3 | Calibration is done, now IAQ estimate achieves best performance | + */ + uint8_t accuracy; +} bsec_output_t; + +/*! + * @brief Structure describing sample rate of physical/virtual sensors + * + * This structure is used together with bsec_update_subscription() to enable BSEC outputs and to retrieve information + * about the sample rates used for BSEC inputs. + */ +typedef struct +{ + /** + * @brief Sample rate of the virtual or physical sensor in Hertz [Hz] + * + * Only supported sample rates are allowed. + */ + float sample_rate; + + /** + * @brief Identifier of the virtual or physical sensor + * + * The meaning of this field changes depending on whether the structs are as the requested_virtual_sensors argument + * to bsec_update_subscription() or as the required_sensor_settings argument. + * + * | bsec_update_subscription() argument | sensor_id field interpretation | + * |-------------------------------------|--------------------------------| + * | requested_virtual_sensors | ::bsec_virtual_sensor_t | + * | required_sensor_settings | ::bsec_physical_sensor_t | + * + * @sa bsec_physical_sensor_t + * @sa bsec_virtual_sensor_t + */ + uint8_t sensor_id; +} bsec_sensor_configuration_t; + +/*! + * @brief Structure returned by bsec_sensor_control() to configure BMExxx sensor + * + * This structure contains settings that must be used to configure the BMExxx to perform a forced-mode measurement. + * A measurement should only be executed if bsec_bme_settings_t::trigger_measurement is 1. If so, the oversampling + * settings for temperature, humidity, and pressure should be set to the provided settings provided in + * bsec_bme_settings_t::temperature_oversampling, bsec_bme_settings_t::humidity_oversampling, and + * bsec_bme_settings_t::pressure_oversampling, respectively. + * + * In case of bsec_bme_settings_t::run_gas = 1, the gas sensor must be enabled with the provided + * bsec_bme_settings_t::heater_temperature and bsec_bme_settings_t::heating_duration settings. + */ +typedef struct +{ + int64_t next_call; /*!< @brief Time stamp of the next call of the sensor_control*/ + uint32_t process_data; /*!< @brief Bit field describing which data is to be passed to bsec_do_steps() @sa BSEC_PROCESS_* */ + uint16_t heater_temperature; /*!< @brief Heater temperature [degrees Celsius] */ + uint16_t heater_duration; /*!< @brief Heater duration [ms] */ + uint16_t heater_temperature_profile[10]; /*!< @brief Heater temperature profile [degrees Celsius] */ + uint16_t heater_duration_profile[10]; /*!< @brief Heater duration profile [ms] */ + uint8_t heater_profile_len; /*!< @brief Heater profile length [0-10] */ + uint8_t run_gas; /*!< @brief Enable gas measurements [0/1] */ + uint8_t pressure_oversampling; /*!< @brief Pressure oversampling settings [0-5] */ + uint8_t temperature_oversampling; /*!< @brief Temperature oversampling settings [0-5] */ + uint8_t humidity_oversampling; /*!< @brief Humidity oversampling settings [0-5] */ + uint8_t trigger_measurement; /*!< @brief Trigger a forced measurement with these settings now [0/1] */ + uint8_t op_mode; /*!< @brief Sensor operation mode [0/1] */ +} bsec_bme_settings_t; + +/* internal defines and backward compatibility */ +#define BSEC_STRUCT_NAME Bsec /*!< Internal struct name */ + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/bme680/include/bsec_interface.h b/components/bme680/include/bsec_interface.h new file mode 100644 index 0000000..759907e --- /dev/null +++ b/components/bme680/include/bsec_interface.h @@ -0,0 +1,562 @@ +/** + * Copyright (C) Bosch Sensortec GmbH. All Rights Reserved. Confidential. + * + * Disclaimer + * + * Common: + * Bosch Sensortec products are developed for the consumer goods industry. They may only be used + * within the parameters of the respective valid product data sheet. Bosch Sensortec products are + * provided with the express understanding that there is no warranty of fitness for a particular purpose. + * They are not fit for use in life-sustaining, safety or security sensitive systems or any system or device + * that may lead to bodily harm or property damage if the system or device malfunctions. In addition, + * Bosch Sensortec products are not fit for use in products which interact with motor vehicle systems. + * The resale and/or use of products are at the purchaser's own risk and his own responsibility. The + * examination of fitness for the intended use is the sole responsibility of the Purchaser. + * + * The purchaser shall indemnify Bosch Sensortec from all third party claims, including any claims for + * incidental, or consequential damages, arising from any product use not covered by the parameters of + * the respective valid product data sheet or not approved by Bosch Sensortec and reimburse Bosch + * Sensortec for all costs in connection with such claims. + * + * The purchaser must monitor the market for the purchased products, particularly with regard to + * product safety and inform Bosch Sensortec without delay of all security relevant incidents. + * + * Engineering Samples are marked with an asterisk (*) or (e). Samples may vary from the valid + * technical specifications of the product series. They are therefore not intended or fit for resale to third + * parties or for use in end products. Their sole purpose is internal client testing. The testing of an + * engineering sample may in no way replace the testing of a product series. Bosch Sensortec + * assumes no liability for the use of engineering samples. By accepting the engineering samples, the + * Purchaser agrees to indemnify Bosch Sensortec from all claims arising from the use of engineering + * samples. + * + * Special: + * This software module (hereinafter called "Software") and any information on application-sheets + * (hereinafter called "Information") is provided free of charge for the sole purpose to support your + * application work. The Software and Information is subject to the following terms and conditions: + * + * The Software is specifically designed for the exclusive use for Bosch Sensortec products by + * personnel who have special experience and training. Do not use this Software if you do not have the + * proper experience or training. + * + * This Software package is provided `` as is `` and without any expressed or implied warranties, + * including without limitation, the implied warranties of merchantability and fitness for a particular + * purpose. + * + * Bosch Sensortec and their representatives and agents deny any liability for the functional impairment + * of this Software in terms of fitness, performance and safety. Bosch Sensortec and their + * representatives and agents shall not be liable for any direct or indirect damages or injury, except as + * otherwise stipulated in mandatory applicable law. + * + * The Information provided is believed to be accurate and reliable. Bosch Sensortec 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 Bosch. Specifications mentioned in the Information are + * subject to change without notice. + * + * It is not allowed to deliver the source code of the Software to any third party without permission of + * Bosch Sensortec. + * + * @file bsec_interface.h + * + * @brief + * Contains the API for BSEC + * + */ + +#ifndef __BSEC_INTERFACE_H__ +#define __BSEC_INTERFACE_H__ + +#include "bsec_datatypes.h" + +#ifdef __cplusplus + extern "C" { +#endif + + + /*! @addtogroup bsec_interface BSEC C Interface + * @brief Interfaces of BSEC signal processing library + * + * ### Interface usage + * + * The following provides a short overview on the typical operation sequence for BSEC. + * + * - Initialization of the library + * + * | Steps | Function | + * |---------------------------------------------------------------------|--------------------------| + * | Initialization of library | bsec_init() | + * | Update configuration settings (optional) | bsec_set_configuration() | + * | Restore the state of the library (optional) | bsec_set_state() | + * + * + * - The following function is called to enable output signals and define their sampling rate / operation mode. + * + * | Steps | Function | + * |---------------------------------------------|----------------------------| + * | Enable library outputs with specified mode | bsec_update_subscription() | + * + * + * - This table describes the main processing loop. + * + * | Steps | Function | + * |-------------------------------------------|----------------------------------| + * | Retrieve sensor settings to be used | bsec_sensor_control() | + * | Configure sensor and trigger measurement | See BME68x API and example codes | + * | Read results from sensor | See BME68x API and example codes | + * | Perform signal processing | bsec_do_steps() | + * + * + * - Before shutting down the system, the current state of BSEC can be retrieved and can then be used during + * re-initialization to continue processing. + * + * | Steps | Function | + * |----------------------------------------|-------------------| + * | To retrieve the current library state | bsec_get_state() | + * + * + * + * ### Configuration and state + * + * Values of variables belonging to a BSEC instance are divided into two groups: + * - Values **not updated by processing** of signals belong to the **configuration group**. If available, BSEC can be + * configured before use with a customer specific configuration string. + * - Values **updated during processing** are member of the **state group**. Saving and restoring of the state of BSEC + * is necessary to maintain previously estimated sensor models and baseline information which is important for best + * performance of the gas sensor outputs. + * + * @note BSEC library consists of adaptive algorithms which models the gas sensor which improves its performance over + * the time. These will be lost if library is initialized due to system reset. In order to avoid this situation + * library state shall be stored in non volatile memory so that it can be loaded after system reset. + * + * + * @{ + */ + + +/*! + * @brief Return the version information of BSEC library + * + * @param [out] bsec_version_p pointer to struct which is to be populated with the version information + * + * @return Zero if successful, otherwise an error code + * + * See also: bsec_version_t + * + \code{.c} + // Example // + bsec_version_t version; + bsec_get_version(&version); + printf("BSEC version: %d.%d.%d.%d",version.major, version.minor, version.major_bugfix, version.minor_bugfix); + + \endcode +*/ + +bsec_library_return_t bsec_get_version(bsec_version_t * bsec_version_p); + + +/*! + * @brief Initialize the library + * + * Initialization and reset of BSEC is performed by calling bsec_init(). Calling this function sets up the relation + * among all internal modules, initializes run-time dependent library states and resets the configuration and state + * of all BSEC signal processing modules to defaults. + * + * Before any further use, the library must be initialized. This ensure that all memory and states are in defined + * conditions prior to processing any data. + * + * @return Zero if successful, otherwise an error code + * + \code{.c} + + // Initialize BSEC library before further use + bsec_init(); + + \endcode +*/ + +bsec_library_return_t bsec_init(void); + +/*! + * @brief Subscribe to library virtual sensors outputs + * + * Use bsec_update_subscription() to instruct BSEC which of the processed output signals are requested at which sample rates. + * See ::bsec_virtual_sensor_t for available library outputs. + * + * Based on the requested virtual sensors outputs, BSEC will provide information about the required physical sensor input signals + * (see ::bsec_physical_sensor_t) with corresponding sample rates. This information is purely informational as bsec_sensor_control() + * will ensure the sensor is operated in the required manner. To disable a virtual sensor, set the sample rate to BSEC_SAMPLE_RATE_DISABLED. + * + * The subscription update using bsec_update_subscription() is apart from the signal processing one of the the most + * important functions. It allows to enable the desired library outputs. The function determines which physical input + * sensor signals are required at which sample rate to produce the virtual output sensor signals requested by the user. + * When this function returns with success, the requested outputs are called subscribed. A very important feature is the + * retaining of already subscribed outputs. Further outputs can be requested or disabled both individually and + * group-wise in addition to already subscribed outputs without changing them unless a change of already subscribed + * outputs is requested. + * + * @note The state of the library concerning the subscribed outputs cannot be retained among reboots. + * + * The interface of bsec_update_subscription() requires the usage of arrays of sensor configuration structures. + * Such a structure has the fields sensor identifier and sample rate. These fields have the properties: + * - Output signals of virtual sensors must be requested using unique identifiers (Member of ::bsec_virtual_sensor_t) + * - Different sets of identifiers are available for inputs of physical sensors and outputs of virtual sensors + * - Identifiers are unique values defined by the library, not from external + * - Sample rates must be provided as value of + * - An allowed sample rate for continuously sampled signals + * - 65535.0f (BSEC_SAMPLE_RATE_DISABLED) to turn off outputs and identify disabled inputs + * + * @note The same sensor identifiers are also used within the functions bsec_do_steps(). + * + * The usage principles of bsec_update_subscription() are: + * - Differential updates (i.e., only asking for outputs that the user would like to change) is supported. + * - Invalid requests of outputs are ignored. Also if one of the requested outputs is unavailable, all the requests + * are ignored. At the same time, a warning is returned. + * - To disable BSEC, all outputs shall be turned off. Only enabled (subscribed) outputs have to be disabled while + * already disabled outputs do not have to be disabled explicitly. + * + * @param[in] requested_virtual_sensors Pointer to array of requested virtual sensor (output) configurations for the library + * @param[in] n_requested_virtual_sensors Number of virtual sensor structs pointed by requested_virtual_sensors + * @param[out] required_sensor_settings Pointer to array of required physical sensor configurations for the library + * @param[in,out] n_required_sensor_settings [in] Size of allocated required_sensor_settings array, [out] number of sensor configurations returned + * + * @return Zero when successful, otherwise an error code + * + * @sa bsec_sensor_configuration_t + * @sa bsec_physical_sensor_t + * @sa bsec_virtual_sensor_t + * + \code{.c} + // Example // + + // Change 3 virtual sensors (switch IAQ and raw temperature -> on / pressure -> off) + bsec_sensor_configuration_t requested_virtual_sensors[3]; + uint8_t n_requested_virtual_sensors = 3; + + requested_virtual_sensors[0].sensor_id = BSEC_OUTPUT_IAQ; + requested_virtual_sensors[0].sample_rate = BSEC_SAMPLE_RATE_ULP; + requested_virtual_sensors[1].sensor_id = BSEC_OUTPUT_RAW_TEMPERATURE; + requested_virtual_sensors[1].sample_rate = BSEC_SAMPLE_RATE_ULP; + requested_virtual_sensors[2].sensor_id = BSEC_OUTPUT_RAW_PRESSURE; + requested_virtual_sensors[2].sample_rate = BSEC_SAMPLE_RATE_DISABLED; + + // Allocate a struct for the returned physical sensor settings + bsec_sensor_configuration_t required_sensor_settings[BSEC_MAX_PHYSICAL_SENSOR]; + uint8_t n_required_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR; + + // Call bsec_update_subscription() to enable/disable the requested virtual sensors + bsec_update_subscription(requested_virtual_sensors, n_requested_virtual_sensors, required_sensor_settings, &n_required_sensor_settings); + \endcode + * + */ +bsec_library_return_t bsec_update_subscription(const bsec_sensor_configuration_t * requested_virtual_sensors, + uint8_t n_requested_virtual_sensors, bsec_sensor_configuration_t * required_sensor_settings, + uint8_t * n_required_sensor_settings); + + +/*! + * @brief Main signal processing function of BSEC + * + * + * Processing of the input signals and returning of output samples is performed by bsec_do_steps(). + * - The samples of all library inputs must be passed with unique identifiers representing the input signals from + * physical sensors where the order of these inputs can be chosen arbitrary. However, all input have to be provided + * within the same time period as they are read. A sequential provision to the library might result in undefined + * behavior. + * - The samples of all library outputs are returned with unique identifiers corresponding to the output signals of + * virtual sensors where the order of the returned outputs may be arbitrary. + * - The samples of all input as well as output signals of physical as well as virtual sensors use the same + * representation in memory with the following fields: + * - Sensor identifier: + * - For inputs: required to identify the input signal from a physical sensor + * - For output: overwritten by bsec_do_steps() to identify the returned signal from a virtual sensor + * - Time stamp of the sample + * + * Calling bsec_do_steps() requires the samples of the input signals to be provided along with their time stamp when + * they are recorded and only when they are acquired. Repetition of samples with the same time stamp are ignored and + * result in a warning. Repetition of values of samples which are not acquired anew by a sensor result in deviations + * of the computed output signals. Concerning the returned output samples, an important feature is, that a value is + * returned for an output only when a new occurrence has been computed. A sample of an output signal is returned only + * once. + * + * + * @param[in] inputs Array of input data samples. Each array element represents a sample of a different physical sensor. + * @param[in] n_inputs Number of passed input data structs. + * @param[out] outputs Array of output data samples. Each array element represents a sample of a different virtual sensor. + * @param[in,out] n_outputs [in] Number of allocated output structs, [out] number of outputs returned + * + * @return Zero when successful, otherwise an error code + * + + \code{.c} + // Example // + + // Allocate input and output memory + bsec_input_t input[3]; + uint8_t n_input = 3; + bsec_output_t output[2]; + uint8_t n_output=2; + + bsec_library_return_t status; + + // Populate the input structs, assuming the we have timestamp (ts), + // gas sensor resistance (R), temperature (T), and humidity (rH) available + // as input variables + input[0].sensor_id = BSEC_INPUT_GASRESISTOR; + input[0].signal = R; + input[0].time_stamp= ts; + input[1].sensor_id = BSEC_INPUT_TEMPERATURE; + input[1].signal = T; + input[1].time_stamp= ts; + input[2].sensor_id = BSEC_INPUT_HUMIDITY; + input[2].signal = rH; + input[2].time_stamp= ts; + + + // Invoke main processing BSEC function + status = bsec_do_steps( input, n_input, output, &n_output ); + + // Iterate through the BSEC output data, if the call succeeded + if(status == BSEC_OK) + { + for(int i = 0; i < n_output; i++) + { + switch(output[i].sensor_id) + { + case BSEC_OUTPUT_IAQ: + // Retrieve the IAQ results from output[i].signal + // and do something with the data + break; + case BSEC_OUTPUT_AMBIENT_TEMPERATURE: + // Retrieve the ambient temperature results from output[i].signal + // and do something with the data + break; + + } + } + } + + \endcode + */ + +bsec_library_return_t bsec_do_steps(const bsec_input_t * inputs, uint8_t n_inputs, bsec_output_t * outputs, uint8_t * n_outputs); + + +/*! + * @brief Reset a particular virtual sensor output + * + * This function allows specific virtual sensor outputs to be reset. The meaning of "reset" depends on the specific + * output. In case of the IAQ output, reset means zeroing the output to the current ambient conditions. + * + * @param[in] sensor_id Virtual sensor to be reset + * + * @return Zero when successful, otherwise an error code + * + * + \code{.c} + // Example // + bsec_reset_output(BSEC_OUTPUT_IAQ); + + \endcode + */ + +bsec_library_return_t bsec_reset_output(uint8_t sensor_id); + + +/*! + * @brief Update algorithm configuration parameters + * + * BSEC uses a default configuration for the modules and common settings. The initial configuration can be customized + * by bsec_set_configuration(). This is an optional step. + * + * @note A work buffer with sufficient size is required and has to be provided by the function caller to decompose + * the serialization and apply it to the library and its modules. + * + * Please use #BSEC_MAX_PROPERTY_BLOB_SIZE for allotting the required size. + * + * @param[in] serialized_settings Settings serialized to a binary blob + * @param[in] n_serialized_settings Size of the settings blob + * @param[in,out] work_buffer Work buffer used to parse the blob + * @param[in] n_work_buffer_size Length of the work buffer available for parsing the blob + * + * @return Zero when successful, otherwise an error code + * + \code{.c} + // Example // + + // Allocate variables + uint8_t serialized_settings[BSEC_MAX_PROPERTY_BLOB_SIZE]; + uint32_t n_serialized_settings_max = BSEC_MAX_PROPERTY_BLOB_SIZE; + uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE]; + uint32_t n_work_buffer = BSEC_MAX_WORKBUFFER_SIZE; + + // Here we will load a provided config string into serialized_settings + + // Apply the configuration + bsec_set_configuration(serialized_settings, n_serialized_settings_max, work_buffer, n_work_buffer); + + \endcode + */ + +bsec_library_return_t bsec_set_configuration(const uint8_t * serialized_settings, + uint32_t n_serialized_settings, uint8_t * work_buffer, + uint32_t n_work_buffer_size); + + +/*! + * @brief Restore the internal state of the library + * + * BSEC uses a default state for all signal processing modules and the BSEC module. To ensure optimal performance, + * especially of the gas sensor functionality, it is recommended to retrieve the state using bsec_get_state() + * before unloading the library, storing it in some form of non-volatile memory, and setting it using bsec_set_state() + * before resuming further operation of the library. + * + * @note A work buffer with sufficient size is required and has to be provided by the function caller to decompose the + * serialization and apply it to the library and its modules. + * + * Please use #BSEC_MAX_STATE_BLOB_SIZE for allotting the required size. + * + * @param[in] serialized_state States serialized to a binary blob + * @param[in] n_serialized_state Size of the state blob + * @param[in,out] work_buffer Work buffer used to parse the blob + * @param[in] n_work_buffer_size Length of the work buffer available for parsing the blob + * + * @return Zero when successful, otherwise an error code + * + \code{.c} + // Example // + + // Allocate variables + uint8_t serialized_state[BSEC_MAX_PROPERTY_BLOB_SIZE]; + uint32_t n_serialized_state = BSEC_MAX_PROPERTY_BLOB_SIZE; + uint8_t work_buffer_state[BSEC_MAX_WORKBUFFER_SIZE]; + uint32_t n_work_buffer_size = BSEC_MAX_WORKBUFFER_SIZE; + + // Here we will load a state string from a previous use of BSEC + + // Apply the previous state to the current BSEC session + bsec_set_state(serialized_state, n_serialized_state, work_buffer_state, n_work_buffer_size); + + \endcode +*/ + +bsec_library_return_t bsec_set_state(const uint8_t * serialized_state, uint32_t n_serialized_state, + uint8_t * work_buffer, uint32_t n_work_buffer_size); + + +/*! + * @brief Retrieve the current library configuration + * + * BSEC allows to retrieve the current configuration using bsec_get_configuration(). Returns a binary blob encoding + * the current configuration parameters of the library in a format compatible with bsec_set_configuration(). + * + * @note The function bsec_get_configuration() is required to be used for debugging purposes only. + * @note A work buffer with sufficient size is required and has to be provided by the function caller to decompose the + * serialization and apply it to the library and its modules. + * + * Please use #BSEC_MAX_PROPERTY_BLOB_SIZE for allotting the required size. + * + * @param[in] config_id Identifier for a specific set of configuration settings to be returned; + * shall be zero to retrieve all configuration settings. + * @param[out] serialized_settings Buffer to hold the serialized config blob + * @param[in] n_serialized_settings_max Maximum available size for the serialized settings + * @param[in,out] work_buffer Work buffer used to parse the binary blob + * @param[in] n_work_buffer Length of the work buffer available for parsing the blob + * @param[out] n_serialized_settings Actual size of the returned serialized configuration blob + * + * @return Zero when successful, otherwise an error code + * + \code{.c} + // Example // + + // Allocate variables + uint8_t serialized_settings[BSEC_MAX_PROPERTY_BLOB_SIZE]; + uint32_t n_serialized_settings_max = BSEC_MAX_PROPERTY_BLOB_SIZE; + uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE]; + uint32_t n_work_buffer = BSEC_MAX_WORKBUFFER_SIZE; + uint32_t n_serialized_settings = 0; + + // Configuration of BSEC algorithm is stored in 'serialized_settings' + bsec_get_configuration(0, serialized_settings, n_serialized_settings_max, work_buffer, n_work_buffer, &n_serialized_settings); + + \endcode + */ + +bsec_library_return_t bsec_get_configuration(uint8_t config_id, uint8_t * serialized_settings, uint32_t n_serialized_settings_max, + uint8_t * work_buffer, uint32_t n_work_buffer, uint32_t * n_serialized_settings); + + +/*! + *@brief Retrieve the current internal library state + * + * BSEC allows to retrieve the current states of all signal processing modules and the BSEC module using + * bsec_get_state(). This allows a restart of the processing after a reboot of the system by calling bsec_set_state(). + * + * @note A work buffer with sufficient size is required and has to be provided by the function caller to decompose the + * serialization and apply it to the library and its modules. + * + * Please use #BSEC_MAX_STATE_BLOB_SIZE for allotting the required size. + * + * @param[in] state_set_id Identifier for a specific set of states to be returned; shall be + * zero to retrieve all states. + * @param[out] serialized_state Buffer to hold the serialized config blob + * @param[in] n_serialized_state_max Maximum available size for the serialized states + * @param[in,out] work_buffer Work buffer used to parse the blob + * @param[in] n_work_buffer Length of the work buffer available for parsing the blob + * @param[out] n_serialized_state Actual size of the returned serialized blob + * + * @return Zero when successful, otherwise an error code + * + \code{.c} + // Example // + + // Allocate variables + uint8_t serialized_state[BSEC_MAX_STATE_BLOB_SIZE]; + uint32_t n_serialized_state_max = BSEC_MAX_STATE_BLOB_SIZE; + uint32_t n_serialized_state = BSEC_MAX_STATE_BLOB_SIZE; + uint8_t work_buffer_state[BSEC_MAX_WORKBUFFER_SIZE]; + uint32_t n_work_buffer_size = BSEC_MAX_WORKBUFFER_SIZE; + + // Algorithm state is stored in 'serialized_state' + bsec_get_state(0, serialized_state, n_serialized_state_max, work_buffer_state, n_work_buffer_size, &n_serialized_state); + + \endcode + */ + +bsec_library_return_t bsec_get_state(uint8_t state_set_id, uint8_t * serialized_state, + uint32_t n_serialized_state_max, uint8_t * work_buffer, uint32_t n_work_buffer, + uint32_t * n_serialized_state); + +/*! + * @brief Retrieve BMExxx sensor instructions + * + * The bsec_sensor_control() interface is a key feature of BSEC, as it allows an easy way for the signal processing + * library to control the operation of the BME sensor. This is important since gas sensor behaviour is mainly + * determined by how the integrated heater is configured. To ensure an easy integration of BSEC into any system, + * bsec_sensor_control() will provide the caller with information about the current sensor configuration that is + * necessary to fulfill the input requirements derived from the current outputs requested via + * bsec_update_subscription(). + * + * In practice the use of this function shall be as follows: + * - Call bsec_sensor_control() which returns a bsec_bme_settings_t struct. + * - Based on the information contained in this struct, the sensor is configured and a forced-mode measurement is + * triggered if requested by bsec_sensor_control(). + * - Once this forced-mode measurement is complete, the signals specified in this struct shall be passed to + * bsec_do_steps() to perform the signal processing. + * - After processing, the process should sleep until the bsec_bme_settings_t::next_call timestamp is reached. + * + * + * @param [in] time_stamp Current timestamp in [ns] + * @param[out] sensor_settings Settings to be passed to API to operate sensor at this time instance + * + * @return Zero when successful, otherwise an error code + */ + +bsec_library_return_t bsec_sensor_control(int64_t time_stamp, bsec_bme_settings_t *sensor_settings); + +/*@}*/ //BSEC Interface + +#ifdef __cplusplus + } +#endif + +#endif /* __BSEC_INTERFACE_H__ */ diff --git a/components/bme680/lib/libalgobsec.a b/components/bme680/lib/libalgobsec.a new file mode 100644 index 0000000000000000000000000000000000000000..8b6d179a2c4aa5b608156189ff203826604150e7 GIT binary patch literal 247364 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 Success / +ve value -> Warning / -ve value -> Error - */ -static int8_t get_calib_data(struct bme680_dev *dev); - -/*! - * @brief This internal API is used to set the gas configuration of the sensor. - * - * @param[in] dev :Structure instance of bme680_dev. - * - * @return Result of API execution status. - * @retval zero -> Success / +ve value -> Warning / -ve value -> Error - */ -static int8_t set_gas_config(struct bme680_dev *dev); - -/*! - * @brief This internal API is used to get the gas configuration of the sensor. - * - * @param[in] dev :Structure instance of bme680_dev. - * - * @return Result of API execution status. - * @retval zero -> Success / +ve value -> Warning / -ve value -> Error - */ -static int8_t get_gas_config(struct bme680_dev *dev); - -/*! - * @brief This internal API is used to calculate the Heat duration value. - * - * @param[in] dur :Value of the duration to be shared. - * - * @return uint8_t threshold duration after calculation. - */ -static uint8_t calc_heater_dur(uint16_t dur); - -/*! - * @brief This internal API is used to calculate the temperature value. - * - * @param[in] dev :Structure instance of bme680_dev. - * @param[in] temp_adc :Contains the temperature ADC value . - * - * @return uint32_t calculated temperature. - */ -static int16_t calc_temperature(uint32_t temp_adc, struct bme680_dev *dev); - -/*! - * @brief This internal API is used to calculate the pressure value. - * - * @param[in] dev :Structure instance of bme680_dev. - * @param[in] pres_adc :Contains the pressure ADC value . - * - * @return uint32_t calculated pressure. - */ -static uint32_t calc_pressure(uint32_t pres_adc, const struct bme680_dev *dev); - -/*! - * @brief This internal API is used to calculate the humidity value. - * - * @param[in] dev :Structure instance of bme680_dev. - * @param[in] hum_adc :Contains the humidity ADC value. - * - * @return uint32_t calculated humidity. - */ -static uint32_t calc_humidity(uint16_t hum_adc, const struct bme680_dev *dev); - -/*! - * @brief This internal API is used to calculate the Gas Resistance value. - * - * @param[in] dev :Structure instance of bme680_dev. - * @param[in] gas_res_adc :Contains the Gas Resistance ADC value. - * @param[in] gas_range :Contains the range of gas values. - * - * @return uint32_t calculated gas resistance. - */ -static uint32_t calc_gas_resistance(uint16_t gas_res_adc, uint8_t gas_range, const struct bme680_dev *dev); - -/*! - * @brief This internal API is used to calculate the Heat Resistance value. - * - * @param[in] dev :Structure instance of bme680_dev. - * @param[in] temp :Contains the temporary value. - * - * @return uint8_t calculated heater resistance. - */ -static uint8_t calc_heater_res(uint16_t temp, const struct bme680_dev *dev); - -/*! - * @brief This internal API is used to calculate the field data of sensor. - * - * @param[out] data :Structure instance to hold the data - * @param[in] dev :Structure instance of bme680_dev. - * - * @return int8_t result of the field data from sensor. - */ -static int8_t read_field_data(struct bme680_field_data *data, struct bme680_dev *dev); - -/*! - * @brief This internal API is used to set the memory page - * based on register address. - * - * The value of memory page - * value | Description - * --------|-------------- - * 0 | BME680_PAGE0_SPI - * 1 | BME680_PAGE1_SPI - * - * @param[in] dev :Structure instance of bme680_dev. - * @param[in] reg_addr :Contains the register address array. - * - * @return Result of API execution status - * @retval zero -> Success / +ve value -> Warning / -ve value -> Error - */ -static int8_t set_mem_page(uint8_t reg_addr, struct bme680_dev *dev); - -/*! - * @brief This internal API is used to get the memory page based - * on register address. - * - * The value of memory page - * value | Description - * --------|-------------- - * 0 | BME680_PAGE0_SPI - * 1 | BME680_PAGE1_SPI - * - * @param[in] dev :Structure instance of bme680_dev. - * - * @return Result of API execution status - * @retval zero -> Success / +ve value -> Warning / -ve value -> Error - */ -static int8_t get_mem_page(struct bme680_dev *dev); - -/*! - * @brief This internal API is used to validate the device pointer for - * null conditions. - * - * @param[in] dev :Structure instance of bme680_dev. - * - * @return Result of API execution status - * @retval zero -> Success / +ve value -> Warning / -ve value -> Error - */ -static int8_t null_ptr_check(const struct bme680_dev *dev); - -/*! - * @brief This internal API is used to check the boundary - * conditions. - * - * @param[in] value :pointer to the value. - * @param[in] min :minimum value. - * @param[in] max :maximum value. - * @param[in] dev :Structure instance of bme680_dev. - * - * @return Result of API execution status - * @retval zero -> Success / +ve value -> Warning / -ve value -> Error - */ -static int8_t boundary_check(uint8_t *value, uint8_t min, uint8_t max, struct bme680_dev *dev); - -/****************** Global Function Definitions *******************************/ -/*! - *@brief This API is the entry point. - *It reads the chip-id and calibration data from the sensor. - */ -int8_t bme680_init(struct bme680_dev *dev) -{ - int8_t rslt; - - /* Check for null pointer in the device structure*/ - rslt = null_ptr_check(dev); - if (rslt == BME680_OK) { - /* Soft reset to restore it to default values*/ - rslt = bme680_soft_reset(dev); - if (rslt == BME680_OK) { - rslt = bme680_get_regs(BME680_CHIP_ID_ADDR, &dev->chip_id, 1, dev); - if (rslt == BME680_OK) { - if (dev->chip_id == BME680_CHIP_ID) { - /* Get the Calibration data */ - rslt = get_calib_data(dev); - } else { - rslt = BME680_E_DEV_NOT_FOUND; - } - } - } - } - - return rslt; -} - -/*! - * @brief This API reads the data from the given register address of the sensor. - */ -int8_t bme680_get_regs(uint8_t reg_addr, uint8_t *reg_data, uint16_t len, struct bme680_dev *dev) -{ - int8_t rslt; - - /* Check for null pointer in the device structure*/ - rslt = null_ptr_check(dev); - if (rslt == BME680_OK) { - if (dev->intf == BME680_SPI_INTF) { - /* Set the memory page */ - rslt = set_mem_page(reg_addr, dev); - if (rslt == BME680_OK) - reg_addr = reg_addr | BME680_SPI_RD_MSK; - } - dev->com_rslt = dev->read(dev->dev_id, reg_addr, reg_data, len); - if (dev->com_rslt != 0) - rslt = BME680_E_COM_FAIL; - } - - return rslt; -} - -/*! - * @brief This API writes the given data to the register address - * of the sensor. - */ -int8_t bme680_set_regs(const uint8_t *reg_addr, const uint8_t *reg_data, uint8_t len, struct bme680_dev *dev) -{ - int8_t rslt; - /* Length of the temporary buffer is 2*(length of register)*/ - uint8_t tmp_buff[BME680_TMP_BUFFER_LENGTH] = { 0 }; - uint16_t index; - - /* Check for null pointer in the device structure*/ - rslt = null_ptr_check(dev); - if (rslt == BME680_OK) { - if ((len > 0) && (len < BME680_TMP_BUFFER_LENGTH / 2)) { - /* Interleave the 2 arrays */ - for (index = 0; index < len; index++) { - if (dev->intf == BME680_SPI_INTF) { - /* Set the memory page */ - rslt = set_mem_page(reg_addr[index], dev); - tmp_buff[(2 * index)] = reg_addr[index] & BME680_SPI_WR_MSK; - } else { - tmp_buff[(2 * index)] = reg_addr[index]; - } - tmp_buff[(2 * index) + 1] = reg_data[index]; - } - /* Write the interleaved array */ - if (rslt == BME680_OK) { - dev->com_rslt = dev->write(dev->dev_id, tmp_buff[0], &tmp_buff[1], (2 * len) - 1); - if (dev->com_rslt != 0) - rslt = BME680_E_COM_FAIL; - } - } else { - rslt = BME680_E_INVALID_LENGTH; - } - } - - return rslt; -} - -/*! - * @brief This API performs the soft reset of the sensor. - */ -int8_t bme680_soft_reset(struct bme680_dev *dev) -{ - int8_t rslt; - uint8_t reg_addr = BME680_SOFT_RESET_ADDR; - /* 0xb6 is the soft reset command */ - uint8_t soft_rst_cmd = BME680_SOFT_RESET_CMD; - - /* Check for null pointer in the device structure*/ - rslt = null_ptr_check(dev); - if (rslt == BME680_OK) { - if (dev->intf == BME680_SPI_INTF) - rslt = get_mem_page(dev); - - /* Reset the device */ - if (rslt == BME680_OK) { - rslt = bme680_set_regs(®_addr, &soft_rst_cmd, 1, dev); - /* Wait for 5ms */ - dev->delay_ms(BME680_RESET_PERIOD); - - if (rslt == BME680_OK) { - /* After reset get the memory page */ - if (dev->intf == BME680_SPI_INTF) - rslt = get_mem_page(dev); - } - } - } - - return rslt; -} - -/*! - * @brief This API is used to set the oversampling, filter and T,P,H, gas selection - * settings in the sensor. - */ -int8_t bme680_set_sensor_settings(uint16_t desired_settings, struct bme680_dev *dev) -{ - int8_t rslt; - uint8_t reg_addr; - uint8_t data = 0; - uint8_t count = 0; - uint8_t reg_array[BME680_REG_BUFFER_LENGTH] = { 0 }; - uint8_t data_array[BME680_REG_BUFFER_LENGTH] = { 0 }; - uint8_t intended_power_mode = dev->power_mode; /* Save intended power mode */ - - /* Check for null pointer in the device structure*/ - rslt = null_ptr_check(dev); - if (rslt == BME680_OK) { - if (desired_settings & BME680_GAS_MEAS_SEL) - rslt = set_gas_config(dev); - - dev->power_mode = BME680_SLEEP_MODE; - if (rslt == BME680_OK) - rslt = bme680_set_sensor_mode(dev); - - /* Selecting the filter */ - if (desired_settings & BME680_FILTER_SEL) { - rslt = boundary_check(&dev->tph_sett.filter, BME680_FILTER_SIZE_0, BME680_FILTER_SIZE_127, dev); - reg_addr = BME680_CONF_ODR_FILT_ADDR; - - if (rslt == BME680_OK) - rslt = bme680_get_regs(reg_addr, &data, 1, dev); - - if (desired_settings & BME680_FILTER_SEL) - data = BME680_SET_BITS(data, BME680_FILTER, dev->tph_sett.filter); - - reg_array[count] = reg_addr; /* Append configuration */ - data_array[count] = data; - count++; - } - - /* Selecting heater control for the sensor */ - if (desired_settings & BME680_HCNTRL_SEL) { - rslt = boundary_check(&dev->gas_sett.heatr_ctrl, BME680_ENABLE_HEATER, - BME680_DISABLE_HEATER, dev); - reg_addr = BME680_CONF_HEAT_CTRL_ADDR; - - if (rslt == BME680_OK) - rslt = bme680_get_regs(reg_addr, &data, 1, dev); - data = BME680_SET_BITS_POS_0(data, BME680_HCTRL, dev->gas_sett.heatr_ctrl); - - reg_array[count] = reg_addr; /* Append configuration */ - data_array[count] = data; - count++; - } - - /* Selecting heater T,P oversampling for the sensor */ - if (desired_settings & (BME680_OST_SEL | BME680_OSP_SEL)) { - rslt = boundary_check(&dev->tph_sett.os_temp, BME680_OS_NONE, BME680_OS_16X, dev); - reg_addr = BME680_CONF_T_P_MODE_ADDR; - - if (rslt == BME680_OK) - rslt = bme680_get_regs(reg_addr, &data, 1, dev); - - if (desired_settings & BME680_OST_SEL) - data = BME680_SET_BITS(data, BME680_OST, dev->tph_sett.os_temp); - - if (desired_settings & BME680_OSP_SEL) - data = BME680_SET_BITS(data, BME680_OSP, dev->tph_sett.os_pres); - - reg_array[count] = reg_addr; - data_array[count] = data; - count++; - } - - /* Selecting humidity oversampling for the sensor */ - if (desired_settings & BME680_OSH_SEL) { - rslt = boundary_check(&dev->tph_sett.os_hum, BME680_OS_NONE, BME680_OS_16X, dev); - reg_addr = BME680_CONF_OS_H_ADDR; - - if (rslt == BME680_OK) - rslt = bme680_get_regs(reg_addr, &data, 1, dev); - data = BME680_SET_BITS_POS_0(data, BME680_OSH, dev->tph_sett.os_hum); - - reg_array[count] = reg_addr; /* Append configuration */ - data_array[count] = data; - count++; - } - - /* Selecting the runGas and NB conversion settings for the sensor */ - if (desired_settings & (BME680_RUN_GAS_SEL | BME680_NBCONV_SEL)) { - rslt = boundary_check(&dev->gas_sett.run_gas, BME680_RUN_GAS_DISABLE, - BME680_RUN_GAS_ENABLE, dev); - if (rslt == BME680_OK) { - /* Validate boundary conditions */ - rslt = boundary_check(&dev->gas_sett.nb_conv, BME680_NBCONV_MIN, - BME680_NBCONV_MAX, dev); - } - - reg_addr = BME680_CONF_ODR_RUN_GAS_NBC_ADDR; - - if (rslt == BME680_OK) - rslt = bme680_get_regs(reg_addr, &data, 1, dev); - - if (desired_settings & BME680_RUN_GAS_SEL) - data = BME680_SET_BITS(data, BME680_RUN_GAS, dev->gas_sett.run_gas); - - if (desired_settings & BME680_NBCONV_SEL) - data = BME680_SET_BITS_POS_0(data, BME680_NBCONV, dev->gas_sett.nb_conv); - - reg_array[count] = reg_addr; /* Append configuration */ - data_array[count] = data; - count++; - } - - if (rslt == BME680_OK) - rslt = bme680_set_regs(reg_array, data_array, count, dev); - - /* Restore previous intended power mode */ - dev->power_mode = intended_power_mode; - } - - return rslt; -} - -/*! - * @brief This API is used to get the oversampling, filter and T,P,H, gas selection - * settings in the sensor. - */ -int8_t bme680_get_sensor_settings(uint16_t desired_settings, struct bme680_dev *dev) -{ - int8_t rslt; - /* starting address of the register array for burst read*/ - uint8_t reg_addr = BME680_CONF_HEAT_CTRL_ADDR; - uint8_t data_array[BME680_REG_BUFFER_LENGTH] = { 0 }; - - /* Check for null pointer in the device structure*/ - rslt = null_ptr_check(dev); - if (rslt == BME680_OK) { - rslt = bme680_get_regs(reg_addr, data_array, BME680_REG_BUFFER_LENGTH, dev); - - if (rslt == BME680_OK) { - if (desired_settings & BME680_GAS_MEAS_SEL) - rslt = get_gas_config(dev); - - /* get the T,P,H ,Filter,ODR settings here */ - if (desired_settings & BME680_FILTER_SEL) - dev->tph_sett.filter = BME680_GET_BITS(data_array[BME680_REG_FILTER_INDEX], - BME680_FILTER); - - if (desired_settings & (BME680_OST_SEL | BME680_OSP_SEL)) { - dev->tph_sett.os_temp = BME680_GET_BITS(data_array[BME680_REG_TEMP_INDEX], BME680_OST); - dev->tph_sett.os_pres = BME680_GET_BITS(data_array[BME680_REG_PRES_INDEX], BME680_OSP); - } - - if (desired_settings & BME680_OSH_SEL) - dev->tph_sett.os_hum = BME680_GET_BITS_POS_0(data_array[BME680_REG_HUM_INDEX], - BME680_OSH); - - /* get the gas related settings */ - if (desired_settings & BME680_HCNTRL_SEL) - dev->gas_sett.heatr_ctrl = BME680_GET_BITS_POS_0(data_array[BME680_REG_HCTRL_INDEX], - BME680_HCTRL); - - if (desired_settings & (BME680_RUN_GAS_SEL | BME680_NBCONV_SEL)) { - dev->gas_sett.nb_conv = BME680_GET_BITS_POS_0(data_array[BME680_REG_NBCONV_INDEX], - BME680_NBCONV); - dev->gas_sett.run_gas = BME680_GET_BITS(data_array[BME680_REG_RUN_GAS_INDEX], - BME680_RUN_GAS); - } - } - } else { - rslt = BME680_E_NULL_PTR; - } - - return rslt; -} - -/*! - * @brief This API is used to set the power mode of the sensor. - */ -int8_t bme680_set_sensor_mode(struct bme680_dev *dev) -{ - int8_t rslt; - uint8_t tmp_pow_mode; - uint8_t pow_mode = 0; - uint8_t reg_addr = BME680_CONF_T_P_MODE_ADDR; - - /* Check for null pointer in the device structure*/ - rslt = null_ptr_check(dev); - if (rslt == BME680_OK) { - /* Call recursively until in sleep */ - do { - rslt = bme680_get_regs(BME680_CONF_T_P_MODE_ADDR, &tmp_pow_mode, 1, dev); - if (rslt == BME680_OK) { - /* Put to sleep before changing mode */ - pow_mode = (tmp_pow_mode & BME680_MODE_MSK); - - if (pow_mode != BME680_SLEEP_MODE) { - tmp_pow_mode = tmp_pow_mode & (~BME680_MODE_MSK); /* Set to sleep */ - rslt = bme680_set_regs(®_addr, &tmp_pow_mode, 1, dev); - dev->delay_ms(BME680_POLL_PERIOD_MS); - } - } - } while (pow_mode != BME680_SLEEP_MODE); - - /* Already in sleep */ - if (dev->power_mode != BME680_SLEEP_MODE) { - tmp_pow_mode = (tmp_pow_mode & ~BME680_MODE_MSK) | (dev->power_mode & BME680_MODE_MSK); - if (rslt == BME680_OK) - rslt = bme680_set_regs(®_addr, &tmp_pow_mode, 1, dev); - } - } - - return rslt; -} - -/*! - * @brief This API is used to get the power mode of the sensor. - */ -int8_t bme680_get_sensor_mode(struct bme680_dev *dev) -{ - int8_t rslt; - uint8_t mode; - - /* Check for null pointer in the device structure*/ - rslt = null_ptr_check(dev); - if (rslt == BME680_OK) { - rslt = bme680_get_regs(BME680_CONF_T_P_MODE_ADDR, &mode, 1, dev); - /* Masking the other register bit info*/ - dev->power_mode = mode & BME680_MODE_MSK; - } - - return rslt; -} - -/*! - * @brief This API is used to set the profile duration of the sensor. - */ -void bme680_set_profile_dur(uint16_t duration, struct bme680_dev *dev) -{ - uint32_t tph_dur; /* Calculate in us */ - - /* TPH measurement duration */ - tph_dur = ((uint32_t) (dev->tph_sett.os_temp + dev->tph_sett.os_pres + dev->tph_sett.os_hum) * UINT32_C(1963)); - tph_dur += UINT32_C(477 * 4); /* TPH switching duration */ - tph_dur += UINT32_C(477 * 5); /* Gas measurement duration */ - tph_dur += UINT32_C(500); /* Get it to the closest whole number.*/ - tph_dur /= UINT32_C(1000); /* Convert to ms */ - - tph_dur += UINT32_C(1); /* Wake up duration of 1ms */ - /* The remaining time should be used for heating */ - dev->gas_sett.heatr_dur = duration - (uint16_t) tph_dur; -} - -/*! - * @brief This API is used to get the profile duration of the sensor. - */ -void bme680_get_profile_dur(uint16_t *duration, const struct bme680_dev *dev) -{ - uint32_t tph_dur; /* Calculate in us */ - - /* TPH measurement duration */ - tph_dur = ((uint32_t) (dev->tph_sett.os_temp + dev->tph_sett.os_pres + dev->tph_sett.os_hum) * UINT32_C(1963)); - tph_dur += UINT32_C(477 * 4); /* TPH switching duration */ - tph_dur += UINT32_C(477 * 5); /* Gas measurement duration */ - tph_dur += UINT32_C(500); /* Get it to the closest whole number.*/ - tph_dur /= UINT32_C(1000); /* Convert to ms */ - - tph_dur += UINT32_C(1); /* Wake up duration of 1ms */ - - *duration = (uint16_t) tph_dur; - - /* Get the gas duration only when the run gas is enabled */ - if (dev->gas_sett.run_gas) { - /* The remaining time should be used for heating */ - *duration += dev->gas_sett.heatr_dur; - } -} - -/*! - * @brief This API reads the pressure, temperature and humidity and gas data - * from the sensor, compensates the data and store it in the bme680_data - * structure instance passed by the user. - */ -int8_t bme680_get_sensor_data(struct bme680_field_data *data, struct bme680_dev *dev) -{ - int8_t rslt; - - /* Check for null pointer in the device structure*/ - rslt = null_ptr_check(dev); - if (rslt == BME680_OK) { - /* Reading the sensor data in forced mode only */ - rslt = read_field_data(data, dev); - if (rslt == BME680_OK) { - if (data->status & BME680_NEW_DATA_MSK) - dev->new_fields = 1; - else - dev->new_fields = 0; - } - } - - return rslt; -} - -/*! - * @brief This internal API is used to read the calibrated data from the sensor. - */ -static int8_t get_calib_data(struct bme680_dev *dev) -{ - int8_t rslt; - uint8_t coeff_array[BME680_COEFF_SIZE] = { 0 }; - uint8_t temp_var = 0; /* Temporary variable */ - - /* Check for null pointer in the device structure*/ - rslt = null_ptr_check(dev); - if (rslt == BME680_OK) { - rslt = bme680_get_regs(BME680_COEFF_ADDR1, coeff_array, BME680_COEFF_ADDR1_LEN, dev); - /* Append the second half in the same array */ - if (rslt == BME680_OK) - rslt = bme680_get_regs(BME680_COEFF_ADDR2, &coeff_array[BME680_COEFF_ADDR1_LEN] - , BME680_COEFF_ADDR2_LEN, dev); - - /* Temperature related coefficients */ - dev->calib.par_t1 = (uint16_t) (BME680_CONCAT_BYTES(coeff_array[BME680_T1_MSB_REG], - coeff_array[BME680_T1_LSB_REG])); - dev->calib.par_t2 = (int16_t) (BME680_CONCAT_BYTES(coeff_array[BME680_T2_MSB_REG], - coeff_array[BME680_T2_LSB_REG])); - dev->calib.par_t3 = (int8_t) (coeff_array[BME680_T3_REG]); - - /* Pressure related coefficients */ - dev->calib.par_p1 = (uint16_t) (BME680_CONCAT_BYTES(coeff_array[BME680_P1_MSB_REG], - coeff_array[BME680_P1_LSB_REG])); - dev->calib.par_p2 = (int16_t) (BME680_CONCAT_BYTES(coeff_array[BME680_P2_MSB_REG], - coeff_array[BME680_P2_LSB_REG])); - dev->calib.par_p3 = (int8_t) coeff_array[BME680_P3_REG]; - dev->calib.par_p4 = (int16_t) (BME680_CONCAT_BYTES(coeff_array[BME680_P4_MSB_REG], - coeff_array[BME680_P4_LSB_REG])); - dev->calib.par_p5 = (int16_t) (BME680_CONCAT_BYTES(coeff_array[BME680_P5_MSB_REG], - coeff_array[BME680_P5_LSB_REG])); - dev->calib.par_p6 = (int8_t) (coeff_array[BME680_P6_REG]); - dev->calib.par_p7 = (int8_t) (coeff_array[BME680_P7_REG]); - dev->calib.par_p8 = (int16_t) (BME680_CONCAT_BYTES(coeff_array[BME680_P8_MSB_REG], - coeff_array[BME680_P8_LSB_REG])); - dev->calib.par_p9 = (int16_t) (BME680_CONCAT_BYTES(coeff_array[BME680_P9_MSB_REG], - coeff_array[BME680_P9_LSB_REG])); - dev->calib.par_p10 = (uint8_t) (coeff_array[BME680_P10_REG]); - - /* Humidity related coefficients */ - dev->calib.par_h1 = (uint16_t) (((uint16_t) coeff_array[BME680_H1_MSB_REG] << BME680_HUM_REG_SHIFT_VAL) - | (coeff_array[BME680_H1_LSB_REG] & BME680_BIT_H1_DATA_MSK)); - dev->calib.par_h2 = (uint16_t) (((uint16_t) coeff_array[BME680_H2_MSB_REG] << BME680_HUM_REG_SHIFT_VAL) - | ((coeff_array[BME680_H2_LSB_REG]) >> BME680_HUM_REG_SHIFT_VAL)); - dev->calib.par_h3 = (int8_t) coeff_array[BME680_H3_REG]; - dev->calib.par_h4 = (int8_t) coeff_array[BME680_H4_REG]; - dev->calib.par_h5 = (int8_t) coeff_array[BME680_H5_REG]; - dev->calib.par_h6 = (uint8_t) coeff_array[BME680_H6_REG]; - dev->calib.par_h7 = (int8_t) coeff_array[BME680_H7_REG]; - - /* Gas heater related coefficients */ - dev->calib.par_gh1 = (int8_t) coeff_array[BME680_GH1_REG]; - dev->calib.par_gh2 = (int16_t) (BME680_CONCAT_BYTES(coeff_array[BME680_GH2_MSB_REG], - coeff_array[BME680_GH2_LSB_REG])); - dev->calib.par_gh3 = (int8_t) coeff_array[BME680_GH3_REG]; - - /* Other coefficients */ - if (rslt == BME680_OK) { - rslt = bme680_get_regs(BME680_ADDR_RES_HEAT_RANGE_ADDR, &temp_var, 1, dev); - - dev->calib.res_heat_range = ((temp_var & BME680_RHRANGE_MSK) / 16); - if (rslt == BME680_OK) { - rslt = bme680_get_regs(BME680_ADDR_RES_HEAT_VAL_ADDR, &temp_var, 1, dev); - - dev->calib.res_heat_val = (int8_t) temp_var; - if (rslt == BME680_OK) - rslt = bme680_get_regs(BME680_ADDR_RANGE_SW_ERR_ADDR, &temp_var, 1, dev); - } - } - dev->calib.range_sw_err = ((int8_t) temp_var & (int8_t) BME680_RSERROR_MSK) / 16; - } - - return rslt; -} - -/*! - * @brief This internal API is used to set the gas configuration of the sensor. - */ -static int8_t set_gas_config(struct bme680_dev *dev) -{ - int8_t rslt; - - /* Check for null pointer in the device structure*/ - rslt = null_ptr_check(dev); - if (rslt == BME680_OK) { - - uint8_t reg_addr[2] = {0}; - uint8_t reg_data[2] = {0}; - - if (dev->power_mode == BME680_FORCED_MODE) { - reg_addr[0] = BME680_RES_HEAT0_ADDR; - reg_data[0] = calc_heater_res(dev->gas_sett.heatr_temp, dev); - reg_addr[1] = BME680_GAS_WAIT0_ADDR; - reg_data[1] = calc_heater_dur(dev->gas_sett.heatr_dur); - dev->gas_sett.nb_conv = 0; - } else { - rslt = BME680_W_DEFINE_PWR_MODE; - } - if (rslt == BME680_OK) - rslt = bme680_set_regs(reg_addr, reg_data, 2, dev); - } - - return rslt; -} - -/*! - * @brief This internal API is used to get the gas configuration of the sensor. - */ -static int8_t get_gas_config(struct bme680_dev *dev) -{ - int8_t rslt; - /* starting address of the register array for burst read*/ - uint8_t reg_addr1 = BME680_ADDR_SENS_CONF_START; - uint8_t reg_addr2 = BME680_ADDR_GAS_CONF_START; - uint8_t data_array[BME680_GAS_HEATER_PROF_LEN_MAX] = { 0 }; - uint8_t index; - - /* Check for null pointer in the device structure*/ - rslt = null_ptr_check(dev); - if (rslt == BME680_OK) { - if (BME680_SPI_INTF == dev->intf) { - /* Memory page switch the SPI address*/ - rslt = set_mem_page(reg_addr1, dev); - } - - if (rslt == BME680_OK) { - rslt = bme680_get_regs(reg_addr1, data_array, BME680_GAS_HEATER_PROF_LEN_MAX, dev); - if (rslt == BME680_OK) { - for (index = 0; index < BME680_GAS_HEATER_PROF_LEN_MAX; index++) - dev->gas_sett.heatr_temp = data_array[index]; - } - - rslt = bme680_get_regs(reg_addr2, data_array, BME680_GAS_HEATER_PROF_LEN_MAX, dev); - if (rslt == BME680_OK) { - for (index = 0; index < BME680_GAS_HEATER_PROF_LEN_MAX; index++) - dev->gas_sett.heatr_dur = data_array[index]; - } - } - } - - return rslt; -} - -/*! - * @brief This internal API is used to calculate the temperature value. - */ -static int16_t calc_temperature(uint32_t temp_adc, struct bme680_dev *dev) -{ - int64_t var1; - int64_t var2; - int64_t var3; - int16_t calc_temp; - - var1 = ((int32_t) temp_adc >> 3) - ((int32_t) dev->calib.par_t1 << 1); - var2 = (var1 * (int32_t) dev->calib.par_t2) >> 11; - var3 = ((var1 >> 1) * (var1 >> 1)) >> 12; - var3 = ((var3) * ((int32_t) dev->calib.par_t3 << 4)) >> 14; - dev->calib.t_fine = (int32_t) (var2 + var3); - calc_temp = (int16_t) (((dev->calib.t_fine * 5) + 128) >> 8); - - return calc_temp; -} - -/*! - * @brief This internal API is used to calculate the pressure value. - */ -static uint32_t calc_pressure(uint32_t pres_adc, const struct bme680_dev *dev) -{ - int32_t var1 = 0; - int32_t var2 = 0; - int32_t var3 = 0; - int32_t var4 = 0; - int32_t pressure_comp = 0; - - var1 = (((int32_t)dev->calib.t_fine) >> 1) - 64000; - var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) * - (int32_t)dev->calib.par_p6) >> 2; - var2 = var2 + ((var1 * (int32_t)dev->calib.par_p5) << 1); - var2 = (var2 >> 2) + ((int32_t)dev->calib.par_p4 << 16); - var1 = (((((var1 >> 2) * (var1 >> 2)) >> 13) * - ((int32_t)dev->calib.par_p3 << 5)) >> 3) + - (((int32_t)dev->calib.par_p2 * var1) >> 1); - var1 = var1 >> 18; - var1 = ((32768 + var1) * (int32_t)dev->calib.par_p1) >> 15; - pressure_comp = 1048576 - pres_adc; - pressure_comp = (int32_t)((pressure_comp - (var2 >> 12)) * ((uint32_t)3125)); - var4 = (1 << 31); - if (pressure_comp >= var4) - pressure_comp = ((pressure_comp / (uint32_t)var1) << 1); - else - pressure_comp = ((pressure_comp << 1) / (uint32_t)var1); - var1 = ((int32_t)dev->calib.par_p9 * (int32_t)(((pressure_comp >> 3) * - (pressure_comp >> 3)) >> 13)) >> 12; - var2 = ((int32_t)(pressure_comp >> 2) * - (int32_t)dev->calib.par_p8) >> 13; - var3 = ((int32_t)(pressure_comp >> 8) * (int32_t)(pressure_comp >> 8) * - (int32_t)(pressure_comp >> 8) * - (int32_t)dev->calib.par_p10) >> 17; - - pressure_comp = (int32_t)(pressure_comp) + ((var1 + var2 + var3 + - ((int32_t)dev->calib.par_p7 << 7)) >> 4); - - return (uint32_t)pressure_comp; - -} - -/*! - * @brief This internal API is used to calculate the humidity value. - */ -static uint32_t calc_humidity(uint16_t hum_adc, const struct bme680_dev *dev) -{ - int32_t var1; - int32_t var2; - int32_t var3; - int32_t var4; - int32_t var5; - int32_t var6; - int32_t temp_scaled; - int32_t calc_hum; - - temp_scaled = (((int32_t) dev->calib.t_fine * 5) + 128) >> 8; - var1 = (int32_t) (hum_adc - ((int32_t) ((int32_t) dev->calib.par_h1 * 16))) - - (((temp_scaled * (int32_t) dev->calib.par_h3) / ((int32_t) 100)) >> 1); - var2 = ((int32_t) dev->calib.par_h2 - * (((temp_scaled * (int32_t) dev->calib.par_h4) / ((int32_t) 100)) - + (((temp_scaled * ((temp_scaled * (int32_t) dev->calib.par_h5) / ((int32_t) 100))) >> 6) - / ((int32_t) 100)) + (int32_t) (1 << 14))) >> 10; - var3 = var1 * var2; - var4 = (int32_t) dev->calib.par_h6 << 7; - var4 = ((var4) + ((temp_scaled * (int32_t) dev->calib.par_h7) / ((int32_t) 100))) >> 4; - var5 = ((var3 >> 14) * (var3 >> 14)) >> 10; - var6 = (var4 * var5) >> 1; - calc_hum = (((var3 + var6) >> 10) * ((int32_t) 1000)) >> 12; - - if (calc_hum > 100000) /* Cap at 100%rH */ - calc_hum = 100000; - else if (calc_hum < 0) - calc_hum = 0; - - return (uint32_t) calc_hum; -} - -/*! - * @brief This internal API is used to calculate the Gas Resistance value. - */ -static uint32_t calc_gas_resistance(uint16_t gas_res_adc, uint8_t gas_range, const struct bme680_dev *dev) -{ - int64_t var1; - uint64_t var2; - int64_t var3; - uint32_t calc_gas_res; - - var1 = (int64_t) ((1340 + (5 * (int64_t) dev->calib.range_sw_err)) * - ((int64_t) lookupTable1[gas_range])) >> 16; - var2 = (((int64_t) ((int64_t) gas_res_adc << 15) - (int64_t) (16777216)) + var1); - var3 = (((int64_t) lookupTable2[gas_range] * (int64_t) var1) >> 9); - calc_gas_res = (uint32_t) ((var3 + ((int64_t) var2 >> 1)) / (int64_t) var2); - - return calc_gas_res; -} - -/*! - * @brief This internal API is used to calculate the Heat Resistance value. - */ -static uint8_t calc_heater_res(uint16_t temp, const struct bme680_dev *dev) -{ - uint8_t heatr_res; - int32_t var1; - int32_t var2; - int32_t var3; - int32_t var4; - int32_t var5; - int32_t heatr_res_x100; - - if (temp < 200) /* Cap temperature */ - temp = 200; - else if (temp > 400) - temp = 400; - - var1 = (((int32_t) dev->amb_temp * dev->calib.par_gh3) / 1000) * 256; - var2 = (dev->calib.par_gh1 + 784) * (((((dev->calib.par_gh2 + 154009) * temp * 5) / 100) + 3276800) / 10); - var3 = var1 + (var2 / 2); - var4 = (var3 / (dev->calib.res_heat_range + 4)); - var5 = (131 * dev->calib.res_heat_val) + 65536; - heatr_res_x100 = (int32_t) (((var4 / var5) - 250) * 34); - heatr_res = (uint8_t) ((heatr_res_x100 + 50) / 100); - - return heatr_res; -} - -/*! - * @brief This internal API is used to calculate the Heat duration value. - */ -static uint8_t calc_heater_dur(uint16_t dur) -{ - uint8_t factor = 0; - uint8_t durval; - - if (dur >= 0xfc0) { - durval = 0xff; /* Max duration*/ - } else { - while (dur > 0x3F) { - dur = dur / 4; - factor += 1; - } - durval = (uint8_t) (dur + (factor * 64)); - } - - return durval; -} - -/*! - * @brief This internal API is used to calculate the field data of sensor. - */ -static int8_t read_field_data(struct bme680_field_data *data, struct bme680_dev *dev) -{ - int8_t rslt; - uint8_t buff[BME680_FIELD_LENGTH] = { 0 }; - uint8_t gas_range; - uint32_t adc_temp; - uint32_t adc_pres; - uint16_t adc_hum; - uint16_t adc_gas_res; - uint8_t tries = 10; - - /* Check for null pointer in the device structure*/ - rslt = null_ptr_check(dev); - do { - if (rslt == BME680_OK) { - rslt = bme680_get_regs(((uint8_t) (BME680_FIELD0_ADDR)), buff, (uint16_t) BME680_FIELD_LENGTH, - dev); - - data->status = buff[0] & BME680_NEW_DATA_MSK; - data->gas_index = buff[0] & BME680_GAS_INDEX_MSK; - data->meas_index = buff[1]; - - /* read the raw data from the sensor */ - adc_pres = (uint32_t) (((uint32_t) buff[2] * 4096) | ((uint32_t) buff[3] * 16) - | ((uint32_t) buff[4] / 16)); - adc_temp = (uint32_t) (((uint32_t) buff[5] * 4096) | ((uint32_t) buff[6] * 16) - | ((uint32_t) buff[7] / 16)); - adc_hum = (uint16_t) (((uint32_t) buff[8] * 256) | (uint32_t) buff[9]); - adc_gas_res = (uint16_t) ((uint32_t) buff[13] * 4 | (((uint32_t) buff[14]) / 64)); - gas_range = buff[14] & BME680_GAS_RANGE_MSK; - - data->status |= buff[14] & BME680_GASM_VALID_MSK; - data->status |= buff[14] & BME680_HEAT_STAB_MSK; - - if (data->status & BME680_NEW_DATA_MSK) { - data->temperature = calc_temperature(adc_temp, dev); - data->pressure = calc_pressure(adc_pres, dev); - data->humidity = calc_humidity(adc_hum, dev); - data->gas_resistance = calc_gas_resistance(adc_gas_res, gas_range, dev); - break; - } - /* Delay to poll the data */ - dev->delay_ms(BME680_POLL_PERIOD_MS); - } - tries--; - } while (tries); - - if (!tries) - rslt = BME680_W_NO_NEW_DATA; - - return rslt; -} - -/*! - * @brief This internal API is used to set the memory page based on register address. - */ -static int8_t set_mem_page(uint8_t reg_addr, struct bme680_dev *dev) -{ - int8_t rslt; - uint8_t reg; - uint8_t mem_page; - - /* Check for null pointers in the device structure*/ - rslt = null_ptr_check(dev); - if (rslt == BME680_OK) { - if (reg_addr > 0x7f) - mem_page = BME680_MEM_PAGE1; - else - mem_page = BME680_MEM_PAGE0; - - if (mem_page != dev->mem_page) { - dev->mem_page = mem_page; - - dev->com_rslt = dev->read(dev->dev_id, BME680_MEM_PAGE_ADDR | BME680_SPI_RD_MSK, ®, 1); - if (dev->com_rslt != 0) - rslt = BME680_E_COM_FAIL; - - if (rslt == BME680_OK) { - reg = reg & (~BME680_MEM_PAGE_MSK); - reg = reg | (dev->mem_page & BME680_MEM_PAGE_MSK); - - dev->com_rslt = dev->write(dev->dev_id, BME680_MEM_PAGE_ADDR & BME680_SPI_WR_MSK, - ®, 1); - if (dev->com_rslt != 0) - rslt = BME680_E_COM_FAIL; - } - } - } - - return rslt; -} - -/*! - * @brief This internal API is used to get the memory page based on register address. - */ -static int8_t get_mem_page(struct bme680_dev *dev) -{ - int8_t rslt; - uint8_t reg; - - /* Check for null pointer in the device structure*/ - rslt = null_ptr_check(dev); - if (rslt == BME680_OK) { - dev->com_rslt = dev->read(dev->dev_id, BME680_MEM_PAGE_ADDR | BME680_SPI_RD_MSK, ®, 1); - if (dev->com_rslt != 0) - rslt = BME680_E_COM_FAIL; - else - dev->mem_page = reg & BME680_MEM_PAGE_MSK; - } - - return rslt; -} - -/*! - * @brief This internal API is used to validate the boundary - * conditions. - */ -static int8_t boundary_check(uint8_t *value, uint8_t min, uint8_t max, struct bme680_dev *dev) -{ - int8_t rslt = BME680_OK; - - if (value != NULL) { - /* Check if value is below minimum value */ - if (*value < min) { - /* Auto correct the invalid value to minimum value */ - *value = min; - dev->info_msg |= BME680_I_MIN_CORRECTION; - } - /* Check if value is above maximum value */ - if (*value > max) { - /* Auto correct the invalid value to maximum value */ - *value = max; - dev->info_msg |= BME680_I_MAX_CORRECTION; - } - } else { - rslt = BME680_E_NULL_PTR; - } - - return rslt; -} - -/*! - * @brief This internal API is used to validate the device structure pointer for - * null conditions. - */ -static int8_t null_ptr_check(const struct bme680_dev *dev) -{ - int8_t rslt; - - if ((dev == NULL) || (dev->read == NULL) || (dev->write == NULL) || (dev->delay_ms == NULL)) { - /* Device structure pointer is not valid */ - rslt = BME680_E_NULL_PTR; - } else { - /* Device structure is fine */ - rslt = BME680_OK; - } - - return rslt; -} diff --git a/components/bme680/src/bme68x.c b/components/bme680/src/bme68x.c new file mode 100644 index 0000000..7c3c935 --- /dev/null +++ b/components/bme680/src/bme68x.c @@ -0,0 +1,1848 @@ +/** +* Copyright (c) 2021 Bosch Sensortec GmbH. All rights reserved. +* +* BSD-3-Clause +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. 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. +* +* 3. Neither the name of the copyright holder nor the names of its +* 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 THE +* 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. +* +* @file bme68x.c +* @date 2021-11-09 +* @version v4.4.7 +* +*/ + +#include "bme68x.h" +#include + +/* This internal API is used to read the calibration coefficients */ +static int8_t get_calib_data(struct bme68x_dev *dev); + +/* This internal API is used to read variant ID information register status */ +static int8_t read_variant_id(struct bme68x_dev *dev); + +/* This internal API is used to calculate the gas wait */ +static uint8_t calc_gas_wait(uint16_t dur); + +#ifndef BME68X_USE_FPU + +/* This internal API is used to calculate the temperature in integer */ +static int16_t calc_temperature(uint32_t temp_adc, struct bme68x_dev *dev); + +/* This internal API is used to calculate the pressure in integer */ +static uint32_t calc_pressure(uint32_t pres_adc, const struct bme68x_dev *dev); + +/* This internal API is used to calculate the humidity in integer */ +static uint32_t calc_humidity(uint16_t hum_adc, const struct bme68x_dev *dev); + +/* This internal API is used to calculate the gas resistance high */ +static uint32_t calc_gas_resistance_high(uint16_t gas_res_adc, uint8_t gas_range); + +/* This internal API is used to calculate the gas resistance low */ +static uint32_t calc_gas_resistance_low(uint16_t gas_res_adc, uint8_t gas_range, const struct bme68x_dev *dev); + +/* This internal API is used to calculate the heater resistance using integer */ +static uint8_t calc_res_heat(uint16_t temp, const struct bme68x_dev *dev); + +#else + +/* This internal API is used to calculate the temperature value in float */ +static float calc_temperature(uint32_t temp_adc, struct bme68x_dev *dev); + +/* This internal API is used to calculate the pressure value in float */ +static float calc_pressure(uint32_t pres_adc, const struct bme68x_dev *dev); + +/* This internal API is used to calculate the humidity value in float */ +static float calc_humidity(uint16_t hum_adc, const struct bme68x_dev *dev); + +/* This internal API is used to calculate the gas resistance high value in float */ +static float calc_gas_resistance_high(uint16_t gas_res_adc, uint8_t gas_range); + +/* This internal API is used to calculate the gas resistance low value in float */ +static float calc_gas_resistance_low(uint16_t gas_res_adc, uint8_t gas_range, const struct bme68x_dev *dev); + +/* This internal API is used to calculate the heater resistance value using float */ +static uint8_t calc_res_heat(uint16_t temp, const struct bme68x_dev *dev); + +#endif + +/* This internal API is used to read a single data of the sensor */ +static int8_t read_field_data(uint8_t index, struct bme68x_data *data, struct bme68x_dev *dev); + +/* This internal API is used to read all data fields of the sensor */ +static int8_t read_all_field_data(struct bme68x_data * const data[], struct bme68x_dev *dev); + +/* This internal API is used to switch between SPI memory pages */ +static int8_t set_mem_page(uint8_t reg_addr, struct bme68x_dev *dev); + +/* This internal API is used to get the current SPI memory page */ +static int8_t get_mem_page(struct bme68x_dev *dev); + +/* This internal API is used to check the bme68x_dev for null pointers */ +static int8_t null_ptr_check(const struct bme68x_dev *dev); + +/* This internal API is used to set heater configurations */ +static int8_t set_conf(const struct bme68x_heatr_conf *conf, uint8_t op_mode, uint8_t *nb_conv, struct bme68x_dev *dev); + +/* This internal API is used to limit the max value of a parameter */ +static int8_t boundary_check(uint8_t *value, uint8_t max, struct bme68x_dev *dev); + +/* This internal API is used to calculate the register value for + * shared heater duration */ +static uint8_t calc_heatr_dur_shared(uint16_t dur); + +/* This internal API is used to swap two fields */ +static void swap_fields(uint8_t index1, uint8_t index2, struct bme68x_data *field[]); + +/* This internal API is used sort the sensor data */ +static void sort_sensor_data(uint8_t low_index, uint8_t high_index, struct bme68x_data *field[]); + +/* + * @brief Function to analyze the sensor data + * + * @param[in] data Array of measurement data + * @param[in] n_meas Number of measurements + * + * @return Result of API execution status + * @retval 0 -> Success + * @retval < 0 -> Fail + */ +static int8_t analyze_sensor_data(const struct bme68x_data *data, uint8_t n_meas); + +/******************************************************************************************/ +/* Global API definitions */ +/******************************************************************************************/ + +/* @brief This API reads the chip-id of the sensor which is the first step to +* verify the sensor and also calibrates the sensor +* As this API is the entry point, call this API before using other APIs. +*/ +int8_t bme68x_init(struct bme68x_dev *dev) +{ + int8_t rslt; + + rslt = bme68x_soft_reset(dev); + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_CHIP_ID, &dev->chip_id, 1, dev); + if (rslt == BME68X_OK) + { + if (dev->chip_id == BME68X_CHIP_ID) + { + /* Read Variant ID */ + rslt = read_variant_id(dev); + + if (rslt == BME68X_OK) + { + /* Get the Calibration data */ + rslt = get_calib_data(dev); + } + } + else + { + rslt = BME68X_E_DEV_NOT_FOUND; + } + } + } + + return rslt; +} + +/* + * @brief This API writes the given data to the register address of the sensor + */ +int8_t bme68x_set_regs(const uint8_t *reg_addr, const uint8_t *reg_data, uint32_t len, struct bme68x_dev *dev) +{ + int8_t rslt; + + /* Length of the temporary buffer is 2*(length of register)*/ + uint8_t tmp_buff[BME68X_LEN_INTERLEAVE_BUFF] = { 0 }; + uint16_t index; + + /* Check for null pointer in the device structure*/ + rslt = null_ptr_check(dev); + if ((rslt == BME68X_OK) && reg_addr && reg_data) + { + if ((len > 0) && (len <= (BME68X_LEN_INTERLEAVE_BUFF / 2))) + { + /* Interleave the 2 arrays */ + for (index = 0; index < len; index++) + { + if (dev->intf == BME68X_SPI_INTF) + { + /* Set the memory page */ + rslt = set_mem_page(reg_addr[index], dev); + tmp_buff[(2 * index)] = reg_addr[index] & BME68X_SPI_WR_MSK; + } + else + { + tmp_buff[(2 * index)] = reg_addr[index]; + } + + tmp_buff[(2 * index) + 1] = reg_data[index]; + } + + /* Write the interleaved array */ + if (rslt == BME68X_OK) + { + dev->intf_rslt = dev->write(tmp_buff[0], &tmp_buff[1], (2 * len) - 1, dev->intf_ptr); + if (dev->intf_rslt != 0) + { + rslt = BME68X_E_COM_FAIL; + } + } + } + else + { + rslt = BME68X_E_INVALID_LENGTH; + } + } + else + { + rslt = BME68X_E_NULL_PTR; + } + + return rslt; +} + +/* + * @brief This API reads the data from the given register address of sensor. + */ +int8_t bme68x_get_regs(uint8_t reg_addr, uint8_t *reg_data, uint32_t len, struct bme68x_dev *dev) +{ + int8_t rslt; + + /* Check for null pointer in the device structure*/ + rslt = null_ptr_check(dev); + if ((rslt == BME68X_OK) && reg_data) + { + if (dev->intf == BME68X_SPI_INTF) + { + /* Set the memory page */ + rslt = set_mem_page(reg_addr, dev); + if (rslt == BME68X_OK) + { + reg_addr = reg_addr | BME68X_SPI_RD_MSK; + } + } + + dev->intf_rslt = dev->read(reg_addr, reg_data, len, dev->intf_ptr); + if (dev->intf_rslt != 0) + { + rslt = BME68X_E_COM_FAIL; + } + } + else + { + rslt = BME68X_E_NULL_PTR; + } + + return rslt; +} + +/* + * @brief This API soft-resets the sensor. + */ +int8_t bme68x_soft_reset(struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t reg_addr = BME68X_REG_SOFT_RESET; + + /* 0xb6 is the soft reset command */ + uint8_t soft_rst_cmd = BME68X_SOFT_RESET_CMD; + + /* Check for null pointer in the device structure*/ + rslt = null_ptr_check(dev); + if (rslt == BME68X_OK) + { + if (dev->intf == BME68X_SPI_INTF) + { + rslt = get_mem_page(dev); + } + + /* Reset the device */ + if (rslt == BME68X_OK) + { + rslt = bme68x_set_regs(®_addr, &soft_rst_cmd, 1, dev); + + /* Wait for 5ms */ + dev->delay_us(BME68X_PERIOD_RESET, dev->intf_ptr); + if (rslt == BME68X_OK) + { + /* After reset get the memory page */ + if (dev->intf == BME68X_SPI_INTF) + { + rslt = get_mem_page(dev); + } + } + } + } + + return rslt; +} + +/* + * @brief This API is used to set the oversampling, filter and odr configuration + */ +int8_t bme68x_set_conf(struct bme68x_conf *conf, struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t odr20 = 0, odr3 = 1; + uint8_t current_op_mode; + + /* Register data starting from BME68X_REG_CTRL_GAS_1(0x71) up to BME68X_REG_CONFIG(0x75) */ + uint8_t reg_array[BME68X_LEN_CONFIG] = { 0x71, 0x72, 0x73, 0x74, 0x75 }; + uint8_t data_array[BME68X_LEN_CONFIG] = { 0 }; + + rslt = bme68x_get_op_mode(¤t_op_mode, dev); + if (rslt == BME68X_OK) + { + /* Configure only in the sleep mode */ + rslt = bme68x_set_op_mode(BME68X_SLEEP_MODE, dev); + } + + if (conf == NULL) + { + rslt = BME68X_E_NULL_PTR; + } + else if (rslt == BME68X_OK) + { + /* Read the whole configuration and write it back once later */ + rslt = bme68x_get_regs(reg_array[0], data_array, BME68X_LEN_CONFIG, dev); + dev->info_msg = BME68X_OK; + if (rslt == BME68X_OK) + { + rslt = boundary_check(&conf->filter, BME68X_FILTER_SIZE_127, dev); + } + + if (rslt == BME68X_OK) + { + rslt = boundary_check(&conf->os_temp, BME68X_OS_16X, dev); + } + + if (rslt == BME68X_OK) + { + rslt = boundary_check(&conf->os_pres, BME68X_OS_16X, dev); + } + + if (rslt == BME68X_OK) + { + rslt = boundary_check(&conf->os_hum, BME68X_OS_16X, dev); + } + + if (rslt == BME68X_OK) + { + rslt = boundary_check(&conf->odr, BME68X_ODR_NONE, dev); + } + + if (rslt == BME68X_OK) + { + data_array[4] = BME68X_SET_BITS(data_array[4], BME68X_FILTER, conf->filter); + data_array[3] = BME68X_SET_BITS(data_array[3], BME68X_OST, conf->os_temp); + data_array[3] = BME68X_SET_BITS(data_array[3], BME68X_OSP, conf->os_pres); + data_array[1] = BME68X_SET_BITS_POS_0(data_array[1], BME68X_OSH, conf->os_hum); + if (conf->odr != BME68X_ODR_NONE) + { + odr20 = conf->odr; + odr3 = 0; + } + + data_array[4] = BME68X_SET_BITS(data_array[4], BME68X_ODR20, odr20); + data_array[0] = BME68X_SET_BITS(data_array[0], BME68X_ODR3, odr3); + } + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_set_regs(reg_array, data_array, BME68X_LEN_CONFIG, dev); + } + + if ((current_op_mode != BME68X_SLEEP_MODE) && (rslt == BME68X_OK)) + { + rslt = bme68x_set_op_mode(current_op_mode, dev); + } + + return rslt; +} + +/* + * @brief This API is used to get the oversampling, filter and odr + */ +int8_t bme68x_get_conf(struct bme68x_conf *conf, struct bme68x_dev *dev) +{ + int8_t rslt; + + /* starting address of the register array for burst read*/ + uint8_t reg_addr = BME68X_REG_CTRL_GAS_1; + uint8_t data_array[BME68X_LEN_CONFIG]; + + rslt = bme68x_get_regs(reg_addr, data_array, 5, dev); + if (!conf) + { + rslt = BME68X_E_NULL_PTR; + } + else if (rslt == BME68X_OK) + { + conf->os_hum = BME68X_GET_BITS_POS_0(data_array[1], BME68X_OSH); + conf->filter = BME68X_GET_BITS(data_array[4], BME68X_FILTER); + conf->os_temp = BME68X_GET_BITS(data_array[3], BME68X_OST); + conf->os_pres = BME68X_GET_BITS(data_array[3], BME68X_OSP); + if (BME68X_GET_BITS(data_array[0], BME68X_ODR3)) + { + conf->odr = BME68X_ODR_NONE; + } + else + { + conf->odr = BME68X_GET_BITS(data_array[4], BME68X_ODR20); + } + } + + return rslt; +} + +/* + * @brief This API is used to set the operation mode of the sensor + */ +int8_t bme68x_set_op_mode(const uint8_t op_mode, struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t tmp_pow_mode; + uint8_t pow_mode = 0; + uint8_t reg_addr = BME68X_REG_CTRL_MEAS; + + /* Call until in sleep */ + do + { + rslt = bme68x_get_regs(BME68X_REG_CTRL_MEAS, &tmp_pow_mode, 1, dev); + if (rslt == BME68X_OK) + { + /* Put to sleep before changing mode */ + pow_mode = (tmp_pow_mode & BME68X_MODE_MSK); + if (pow_mode != BME68X_SLEEP_MODE) + { + tmp_pow_mode &= ~BME68X_MODE_MSK; /* Set to sleep */ + rslt = bme68x_set_regs(®_addr, &tmp_pow_mode, 1, dev); + dev->delay_us(BME68X_PERIOD_POLL, dev->intf_ptr); + } + } + } while ((pow_mode != BME68X_SLEEP_MODE) && (rslt == BME68X_OK)); + + /* Already in sleep */ + if ((op_mode != BME68X_SLEEP_MODE) && (rslt == BME68X_OK)) + { + tmp_pow_mode = (tmp_pow_mode & ~BME68X_MODE_MSK) | (op_mode & BME68X_MODE_MSK); + rslt = bme68x_set_regs(®_addr, &tmp_pow_mode, 1, dev); + } + + return rslt; +} + +/* + * @brief This API is used to get the operation mode of the sensor. + */ +int8_t bme68x_get_op_mode(uint8_t *op_mode, struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t mode; + + if (op_mode) + { + rslt = bme68x_get_regs(BME68X_REG_CTRL_MEAS, &mode, 1, dev); + + /* Masking the other register bit info*/ + *op_mode = mode & BME68X_MODE_MSK; + } + else + { + rslt = BME68X_E_NULL_PTR; + } + + return rslt; +} + +/* + * @brief This API is used to get the remaining duration that can be used for heating. + */ +uint32_t bme68x_get_meas_dur(const uint8_t op_mode, struct bme68x_conf *conf, struct bme68x_dev *dev) +{ + int8_t rslt; + uint32_t meas_dur = 0; /* Calculate in us */ + uint32_t meas_cycles; + uint8_t os_to_meas_cycles[6] = { 0, 1, 2, 4, 8, 16 }; + + if (conf != NULL) + { + /* Boundary check for temperature oversampling */ + rslt = boundary_check(&conf->os_temp, BME68X_OS_16X, dev); + + if (rslt == BME68X_OK) + { + /* Boundary check for pressure oversampling */ + rslt = boundary_check(&conf->os_pres, BME68X_OS_16X, dev); + } + + if (rslt == BME68X_OK) + { + /* Boundary check for humidity oversampling */ + rslt = boundary_check(&conf->os_hum, BME68X_OS_16X, dev); + } + + if (rslt == BME68X_OK) + { + meas_cycles = os_to_meas_cycles[conf->os_temp]; + meas_cycles += os_to_meas_cycles[conf->os_pres]; + meas_cycles += os_to_meas_cycles[conf->os_hum]; + + /* TPH measurement duration */ + meas_dur = meas_cycles * UINT32_C(1963); + meas_dur += UINT32_C(477 * 4); /* TPH switching duration */ + meas_dur += UINT32_C(477 * 5); /* Gas measurement duration */ + + if (op_mode != BME68X_PARALLEL_MODE) + { + meas_dur += UINT32_C(1000); /* Wake up duration of 1ms */ + } + } + } + + return meas_dur; +} + +/* + * @brief This API reads the pressure, temperature and humidity and gas data + * from the sensor, compensates the data and store it in the bme68x_data + * structure instance passed by the user. + */ +int8_t bme68x_get_data(uint8_t op_mode, struct bme68x_data *data, uint8_t *n_data, struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t i = 0, j = 0, new_fields = 0; + struct bme68x_data *field_ptr[3] = { 0 }; + struct bme68x_data field_data[3] = { { 0 } }; + + field_ptr[0] = &field_data[0]; + field_ptr[1] = &field_data[1]; + field_ptr[2] = &field_data[2]; + + rslt = null_ptr_check(dev); + if ((rslt == BME68X_OK) && (data != NULL)) + { + /* Reading the sensor data in forced mode only */ + if (op_mode == BME68X_FORCED_MODE) + { + rslt = read_field_data(0, data, dev); + if (rslt == BME68X_OK) + { + if (data->status & BME68X_NEW_DATA_MSK) + { + new_fields = 1; + } + else + { + new_fields = 0; + rslt = BME68X_W_NO_NEW_DATA; + } + } + } + else if ((op_mode == BME68X_PARALLEL_MODE) || (op_mode == BME68X_SEQUENTIAL_MODE)) + { + /* Read the 3 fields and count the number of new data fields */ + rslt = read_all_field_data(field_ptr, dev); + + new_fields = 0; + for (i = 0; (i < 3) && (rslt == BME68X_OK); i++) + { + if (field_ptr[i]->status & BME68X_NEW_DATA_MSK) + { + new_fields++; + } + } + + /* Sort the sensor data in parallel & sequential modes*/ + for (i = 0; (i < 2) && (rslt == BME68X_OK); i++) + { + for (j = i + 1; j < 3; j++) + { + sort_sensor_data(i, j, field_ptr); + } + } + + /* Copy the sorted data */ + for (i = 0; ((i < 3) && (rslt == BME68X_OK)); i++) + { + data[i] = *field_ptr[i]; + } + + if (new_fields == 0) + { + rslt = BME68X_W_NO_NEW_DATA; + } + } + else + { + rslt = BME68X_W_DEFINE_OP_MODE; + } + + if (n_data == NULL) + { + rslt = BME68X_E_NULL_PTR; + } + else + { + *n_data = new_fields; + } + } + else + { + rslt = BME68X_E_NULL_PTR; + } + + return rslt; +} + +/* + * @brief This API is used to set the gas configuration of the sensor. + */ +int8_t bme68x_set_heatr_conf(uint8_t op_mode, const struct bme68x_heatr_conf *conf, struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t nb_conv = 0; + uint8_t hctrl, run_gas = 0; + uint8_t ctrl_gas_data[2]; + uint8_t ctrl_gas_addr[2] = { BME68X_REG_CTRL_GAS_0, BME68X_REG_CTRL_GAS_1 }; + + if (conf != NULL) + { + rslt = bme68x_set_op_mode(BME68X_SLEEP_MODE, dev); + if (rslt == BME68X_OK) + { + rslt = set_conf(conf, op_mode, &nb_conv, dev); + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_CTRL_GAS_0, ctrl_gas_data, 2, dev); + if (rslt == BME68X_OK) + { + if (conf->enable == BME68X_ENABLE) + { + hctrl = BME68X_ENABLE_HEATER; + if (dev->variant_id == BME68X_VARIANT_GAS_HIGH) + { + run_gas = BME68X_ENABLE_GAS_MEAS_H; + } + else + { + run_gas = BME68X_ENABLE_GAS_MEAS_L; + } + } + else + { + hctrl = BME68X_DISABLE_HEATER; + run_gas = BME68X_DISABLE_GAS_MEAS; + } + + ctrl_gas_data[0] = BME68X_SET_BITS(ctrl_gas_data[0], BME68X_HCTRL, hctrl); + ctrl_gas_data[1] = BME68X_SET_BITS_POS_0(ctrl_gas_data[1], BME68X_NBCONV, nb_conv); + ctrl_gas_data[1] = BME68X_SET_BITS(ctrl_gas_data[1], BME68X_RUN_GAS, run_gas); + rslt = bme68x_set_regs(ctrl_gas_addr, ctrl_gas_data, 2, dev); + } + } + } + else + { + rslt = BME68X_E_NULL_PTR; + } + + return rslt; +} + +/* + * @brief This API is used to get the gas configuration of the sensor. + */ +int8_t bme68x_get_heatr_conf(const struct bme68x_heatr_conf *conf, struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t data_array[10] = { 0 }; + uint8_t i; + + /* FIXME: Add conversion to deg C and ms and add the other parameters */ + rslt = bme68x_get_regs(BME68X_REG_RES_HEAT0, data_array, 10, dev); + if (rslt == BME68X_OK) + { + if (conf && conf->heatr_dur_prof && conf->heatr_temp_prof) + { + for (i = 0; i < 10; i++) + { + conf->heatr_temp_prof[i] = data_array[i]; + } + + rslt = bme68x_get_regs(BME68X_REG_GAS_WAIT0, data_array, 10, dev); + if (rslt == BME68X_OK) + { + for (i = 0; i < 10; i++) + { + conf->heatr_dur_prof[i] = data_array[i]; + } + } + } + else + { + rslt = BME68X_E_NULL_PTR; + } + } + + return rslt; +} + +/* + * @brief This API performs Self-test of low and high gas variants of BME68X + */ +int8_t bme68x_selftest_check(const struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t n_fields; + uint8_t i = 0; + struct bme68x_data data[BME68X_N_MEAS] = { { 0 } }; + struct bme68x_dev t_dev; + struct bme68x_conf conf; + struct bme68x_heatr_conf heatr_conf; + + /* Copy required parameters from reference bme68x_dev struct */ + t_dev.amb_temp = 25; + t_dev.read = dev->read; + t_dev.write = dev->write; + t_dev.intf = dev->intf; + t_dev.delay_us = dev->delay_us; + t_dev.intf_ptr = dev->intf_ptr; + rslt = bme68x_init(&t_dev); + if (rslt == BME68X_OK) + { + /* Set the temperature, pressure and humidity & filter settings */ + conf.os_hum = BME68X_OS_1X; + conf.os_pres = BME68X_OS_16X; + conf.os_temp = BME68X_OS_2X; + + /* Set the remaining gas sensor settings and link the heating profile */ + heatr_conf.enable = BME68X_ENABLE; + heatr_conf.heatr_dur = BME68X_HEATR_DUR1; + heatr_conf.heatr_temp = BME68X_HIGH_TEMP; + rslt = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &heatr_conf, &t_dev); + if (rslt == BME68X_OK) + { + rslt = bme68x_set_conf(&conf, &t_dev); + if (rslt == BME68X_OK) + { + rslt = bme68x_set_op_mode(BME68X_FORCED_MODE, &t_dev); /* Trigger a measurement */ + if (rslt == BME68X_OK) + { + /* Wait for the measurement to complete */ + t_dev.delay_us(BME68X_HEATR_DUR1_DELAY, t_dev.intf_ptr); + rslt = bme68x_get_data(BME68X_FORCED_MODE, &data[0], &n_fields, &t_dev); + if (rslt == BME68X_OK) + { + if ((data[0].idac != 0x00) && (data[0].idac != 0xFF) && + (data[0].status & BME68X_GASM_VALID_MSK)) + { + rslt = BME68X_OK; + } + else + { + rslt = BME68X_E_SELF_TEST; + } + } + } + } + } + + heatr_conf.heatr_dur = BME68X_HEATR_DUR2; + while ((rslt == BME68X_OK) && (i < BME68X_N_MEAS)) + { + if (i % 2 == 0) + { + heatr_conf.heatr_temp = BME68X_HIGH_TEMP; /* Higher temperature */ + } + else + { + heatr_conf.heatr_temp = BME68X_LOW_TEMP; /* Lower temperature */ + } + + rslt = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &heatr_conf, &t_dev); + if (rslt == BME68X_OK) + { + rslt = bme68x_set_conf(&conf, &t_dev); + if (rslt == BME68X_OK) + { + rslt = bme68x_set_op_mode(BME68X_FORCED_MODE, &t_dev); /* Trigger a measurement */ + if (rslt == BME68X_OK) + { + /* Wait for the measurement to complete */ + t_dev.delay_us(BME68X_HEATR_DUR2_DELAY, t_dev.intf_ptr); + rslt = bme68x_get_data(BME68X_FORCED_MODE, &data[i], &n_fields, &t_dev); + } + } + } + + i++; + } + + if (rslt == BME68X_OK) + { + rslt = analyze_sensor_data(data, BME68X_N_MEAS); + } + } + + return rslt; +} + +/*****************************INTERNAL APIs***********************************************/ +#ifndef BME68X_USE_FPU + +/* @brief This internal API is used to calculate the temperature value. */ +static int16_t calc_temperature(uint32_t temp_adc, struct bme68x_dev *dev) +{ + int64_t var1; + int64_t var2; + int64_t var3; + int16_t calc_temp; + + /*lint -save -e701 -e702 -e704 */ + var1 = ((int32_t)temp_adc >> 3) - ((int32_t)dev->calib.par_t1 << 1); + var2 = (var1 * (int32_t)dev->calib.par_t2) >> 11; + var3 = ((var1 >> 1) * (var1 >> 1)) >> 12; + var3 = ((var3) * ((int32_t)dev->calib.par_t3 << 4)) >> 14; + dev->calib.t_fine = (int32_t)(var2 + var3); + calc_temp = (int16_t)(((dev->calib.t_fine * 5) + 128) >> 8); + + /*lint -restore */ + return calc_temp; +} + +/* @brief This internal API is used to calculate the pressure value. */ +static uint32_t calc_pressure(uint32_t pres_adc, const struct bme68x_dev *dev) +{ + int32_t var1; + int32_t var2; + int32_t var3; + int32_t pressure_comp; + + /* This value is used to check precedence to multiplication or division + * in the pressure compensation equation to achieve least loss of precision and + * avoiding overflows. + * i.e Comparing value, pres_ovf_check = (1 << 31) >> 1 + */ + const int32_t pres_ovf_check = INT32_C(0x40000000); + + /*lint -save -e701 -e702 -e713 */ + var1 = (((int32_t)dev->calib.t_fine) >> 1) - 64000; + var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) * (int32_t)dev->calib.par_p6) >> 2; + var2 = var2 + ((var1 * (int32_t)dev->calib.par_p5) << 1); + var2 = (var2 >> 2) + ((int32_t)dev->calib.par_p4 << 16); + var1 = (((((var1 >> 2) * (var1 >> 2)) >> 13) * ((int32_t)dev->calib.par_p3 << 5)) >> 3) + + (((int32_t)dev->calib.par_p2 * var1) >> 1); + var1 = var1 >> 18; + var1 = ((32768 + var1) * (int32_t)dev->calib.par_p1) >> 15; + pressure_comp = 1048576 - pres_adc; + pressure_comp = (int32_t)((pressure_comp - (var2 >> 12)) * ((uint32_t)3125)); + if (pressure_comp >= pres_ovf_check) + { + pressure_comp = ((pressure_comp / var1) << 1); + } + else + { + pressure_comp = ((pressure_comp << 1) / var1); + } + + var1 = ((int32_t)dev->calib.par_p9 * (int32_t)(((pressure_comp >> 3) * (pressure_comp >> 3)) >> 13)) >> 12; + var2 = ((int32_t)(pressure_comp >> 2) * (int32_t)dev->calib.par_p8) >> 13; + var3 = + ((int32_t)(pressure_comp >> 8) * (int32_t)(pressure_comp >> 8) * (int32_t)(pressure_comp >> 8) * + (int32_t)dev->calib.par_p10) >> 17; + pressure_comp = (int32_t)(pressure_comp) + ((var1 + var2 + var3 + ((int32_t)dev->calib.par_p7 << 7)) >> 4); + + /*lint -restore */ + return (uint32_t)pressure_comp; +} + +/* This internal API is used to calculate the humidity in integer */ +static uint32_t calc_humidity(uint16_t hum_adc, const struct bme68x_dev *dev) +{ + int32_t var1; + int32_t var2; + int32_t var3; + int32_t var4; + int32_t var5; + int32_t var6; + int32_t temp_scaled; + int32_t calc_hum; + + /*lint -save -e702 -e704 */ + temp_scaled = (((int32_t)dev->calib.t_fine * 5) + 128) >> 8; + var1 = (int32_t)(hum_adc - ((int32_t)((int32_t)dev->calib.par_h1 * 16))) - + (((temp_scaled * (int32_t)dev->calib.par_h3) / ((int32_t)100)) >> 1); + var2 = + ((int32_t)dev->calib.par_h2 * + (((temp_scaled * (int32_t)dev->calib.par_h4) / ((int32_t)100)) + + (((temp_scaled * ((temp_scaled * (int32_t)dev->calib.par_h5) / ((int32_t)100))) >> 6) / ((int32_t)100)) + + (int32_t)(1 << 14))) >> 10; + var3 = var1 * var2; + var4 = (int32_t)dev->calib.par_h6 << 7; + var4 = ((var4) + ((temp_scaled * (int32_t)dev->calib.par_h7) / ((int32_t)100))) >> 4; + var5 = ((var3 >> 14) * (var3 >> 14)) >> 10; + var6 = (var4 * var5) >> 1; + calc_hum = (((var3 + var6) >> 10) * ((int32_t)1000)) >> 12; + if (calc_hum > 100000) /* Cap at 100%rH */ + { + calc_hum = 100000; + } + else if (calc_hum < 0) + { + calc_hum = 0; + } + + /*lint -restore */ + return (uint32_t)calc_hum; +} + +/* This internal API is used to calculate the gas resistance low */ +static uint32_t calc_gas_resistance_low(uint16_t gas_res_adc, uint8_t gas_range, const struct bme68x_dev *dev) +{ + int64_t var1; + uint64_t var2; + int64_t var3; + uint32_t calc_gas_res; + uint32_t lookup_table1[16] = { + UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2147483647), + UINT32_C(2126008810), UINT32_C(2147483647), UINT32_C(2130303777), UINT32_C(2147483647), UINT32_C(2147483647), + UINT32_C(2143188679), UINT32_C(2136746228), UINT32_C(2147483647), UINT32_C(2126008810), UINT32_C(2147483647), + UINT32_C(2147483647) + }; + uint32_t lookup_table2[16] = { + UINT32_C(4096000000), UINT32_C(2048000000), UINT32_C(1024000000), UINT32_C(512000000), UINT32_C(255744255), + UINT32_C(127110228), UINT32_C(64000000), UINT32_C(32258064), UINT32_C(16016016), UINT32_C(8000000), UINT32_C( + 4000000), UINT32_C(2000000), UINT32_C(1000000), UINT32_C(500000), UINT32_C(250000), UINT32_C(125000) + }; + + /*lint -save -e704 */ + var1 = (int64_t)((1340 + (5 * (int64_t)dev->calib.range_sw_err)) * ((int64_t)lookup_table1[gas_range])) >> 16; + var2 = (((int64_t)((int64_t)gas_res_adc << 15) - (int64_t)(16777216)) + var1); + var3 = (((int64_t)lookup_table2[gas_range] * (int64_t)var1) >> 9); + calc_gas_res = (uint32_t)((var3 + ((int64_t)var2 >> 1)) / (int64_t)var2); + + /*lint -restore */ + return calc_gas_res; +} + +/* This internal API is used to calculate the gas resistance */ +static uint32_t calc_gas_resistance_high(uint16_t gas_res_adc, uint8_t gas_range) +{ + uint32_t calc_gas_res; + uint32_t var1 = UINT32_C(262144) >> gas_range; + int32_t var2 = (int32_t)gas_res_adc - INT32_C(512); + + var2 *= INT32_C(3); + var2 = INT32_C(4096) + var2; + + /* multiplying 10000 then dividing then multiplying by 100 instead of multiplying by 1000000 to prevent overflow */ + calc_gas_res = (UINT32_C(10000) * var1) / (uint32_t)var2; + calc_gas_res = calc_gas_res * 100; + + return calc_gas_res; +} + +/* This internal API is used to calculate the heater resistance value using float */ +static uint8_t calc_res_heat(uint16_t temp, const struct bme68x_dev *dev) +{ + uint8_t heatr_res; + int32_t var1; + int32_t var2; + int32_t var3; + int32_t var4; + int32_t var5; + int32_t heatr_res_x100; + + if (temp > 400) /* Cap temperature */ + { + temp = 400; + } + + var1 = (((int32_t)dev->amb_temp * dev->calib.par_gh3) / 1000) * 256; + var2 = (dev->calib.par_gh1 + 784) * (((((dev->calib.par_gh2 + 154009) * temp * 5) / 100) + 3276800) / 10); + var3 = var1 + (var2 / 2); + var4 = (var3 / (dev->calib.res_heat_range + 4)); + var5 = (131 * dev->calib.res_heat_val) + 65536; + heatr_res_x100 = (int32_t)(((var4 / var5) - 250) * 34); + heatr_res = (uint8_t)((heatr_res_x100 + 50) / 100); + + return heatr_res; +} + +#else + +/* @brief This internal API is used to calculate the temperature value. */ +static float calc_temperature(uint32_t temp_adc, struct bme68x_dev *dev) +{ + float var1; + float var2; + float calc_temp; + + /* calculate var1 data */ + var1 = ((((float)temp_adc / 16384.0f) - ((float)dev->calib.par_t1 / 1024.0f)) * ((float)dev->calib.par_t2)); + + /* calculate var2 data */ + var2 = + (((((float)temp_adc / 131072.0f) - ((float)dev->calib.par_t1 / 8192.0f)) * + (((float)temp_adc / 131072.0f) - ((float)dev->calib.par_t1 / 8192.0f))) * ((float)dev->calib.par_t3 * 16.0f)); + + /* t_fine value*/ + dev->calib.t_fine = (var1 + var2); + + /* compensated temperature data*/ + calc_temp = ((dev->calib.t_fine) / 5120.0f); + + return calc_temp; +} + +/* @brief This internal API is used to calculate the pressure value. */ +static float calc_pressure(uint32_t pres_adc, const struct bme68x_dev *dev) +{ + float var1; + float var2; + float var3; + float calc_pres; + + var1 = (((float)dev->calib.t_fine / 2.0f) - 64000.0f); + var2 = var1 * var1 * (((float)dev->calib.par_p6) / (131072.0f)); + var2 = var2 + (var1 * ((float)dev->calib.par_p5) * 2.0f); + var2 = (var2 / 4.0f) + (((float)dev->calib.par_p4) * 65536.0f); + var1 = (((((float)dev->calib.par_p3 * var1 * var1) / 16384.0f) + ((float)dev->calib.par_p2 * var1)) / 524288.0f); + var1 = ((1.0f + (var1 / 32768.0f)) * ((float)dev->calib.par_p1)); + calc_pres = (1048576.0f - ((float)pres_adc)); + + /* Avoid exception caused by division by zero */ + if ((int)var1 != 0) + { + calc_pres = (((calc_pres - (var2 / 4096.0f)) * 6250.0f) / var1); + var1 = (((float)dev->calib.par_p9) * calc_pres * calc_pres) / 2147483648.0f; + var2 = calc_pres * (((float)dev->calib.par_p8) / 32768.0f); + var3 = ((calc_pres / 256.0f) * (calc_pres / 256.0f) * (calc_pres / 256.0f) * (dev->calib.par_p10 / 131072.0f)); + calc_pres = (calc_pres + (var1 + var2 + var3 + ((float)dev->calib.par_p7 * 128.0f)) / 16.0f); + } + else + { + calc_pres = 0; + } + + return calc_pres; +} + +/* This internal API is used to calculate the humidity in integer */ +static float calc_humidity(uint16_t hum_adc, const struct bme68x_dev *dev) +{ + float calc_hum; + float var1; + float var2; + float var3; + float var4; + float temp_comp; + + /* compensated temperature data*/ + temp_comp = ((dev->calib.t_fine) / 5120.0f); + var1 = (float)((float)hum_adc) - + (((float)dev->calib.par_h1 * 16.0f) + (((float)dev->calib.par_h3 / 2.0f) * temp_comp)); + var2 = var1 * + ((float)(((float)dev->calib.par_h2 / 262144.0f) * + (1.0f + (((float)dev->calib.par_h4 / 16384.0f) * temp_comp) + + (((float)dev->calib.par_h5 / 1048576.0f) * temp_comp * temp_comp)))); + var3 = (float)dev->calib.par_h6 / 16384.0f; + var4 = (float)dev->calib.par_h7 / 2097152.0f; + calc_hum = var2 + ((var3 + (var4 * temp_comp)) * var2 * var2); + if (calc_hum > 100.0f) + { + calc_hum = 100.0f; + } + else if (calc_hum < 0.0f) + { + calc_hum = 0.0f; + } + + return calc_hum; +} + +/* This internal API is used to calculate the gas resistance low value in float */ +static float calc_gas_resistance_low(uint16_t gas_res_adc, uint8_t gas_range, const struct bme68x_dev *dev) +{ + float calc_gas_res; + float var1; + float var2; + float var3; + float gas_res_f = gas_res_adc; + float gas_range_f = (1U << gas_range); /*lint !e790 / Suspicious truncation, integral to float */ + const float lookup_k1_range[16] = { + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, -0.8f, 0.0f, 0.0f, -0.2f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f + }; + const float lookup_k2_range[16] = { + 0.0f, 0.0f, 0.0f, 0.0f, 0.1f, 0.7f, 0.0f, -0.8f, -0.1f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f + }; + + var1 = (1340.0f + (5.0f * dev->calib.range_sw_err)); + var2 = (var1) * (1.0f + lookup_k1_range[gas_range] / 100.0f); + var3 = 1.0f + (lookup_k2_range[gas_range] / 100.0f); + calc_gas_res = 1.0f / (float)(var3 * (0.000000125f) * gas_range_f * (((gas_res_f - 512.0f) / var2) + 1.0f)); + + return calc_gas_res; +} + +/* This internal API is used to calculate the gas resistance value in float */ +static float calc_gas_resistance_high(uint16_t gas_res_adc, uint8_t gas_range) +{ + float calc_gas_res; + uint32_t var1 = UINT32_C(262144) >> gas_range; + int32_t var2 = (int32_t)gas_res_adc - INT32_C(512); + + var2 *= INT32_C(3); + var2 = INT32_C(4096) + var2; + + calc_gas_res = 1000000.0f * (float)var1 / (float)var2; + + return calc_gas_res; +} + +/* This internal API is used to calculate the heater resistance value */ +static uint8_t calc_res_heat(uint16_t temp, const struct bme68x_dev *dev) +{ + float var1; + float var2; + float var3; + float var4; + float var5; + uint8_t res_heat; + + if (temp > 400) /* Cap temperature */ + { + temp = 400; + } + + var1 = (((float)dev->calib.par_gh1 / (16.0f)) + 49.0f); + var2 = ((((float)dev->calib.par_gh2 / (32768.0f)) * (0.0005f)) + 0.00235f); + var3 = ((float)dev->calib.par_gh3 / (1024.0f)); + var4 = (var1 * (1.0f + (var2 * (float)temp))); + var5 = (var4 + (var3 * (float)dev->amb_temp)); + res_heat = + (uint8_t)(3.4f * + ((var5 * (4 / (4 + (float)dev->calib.res_heat_range)) * + (1 / (1 + ((float)dev->calib.res_heat_val * 0.002f)))) - + 25)); + + return res_heat; +} + +#endif + +/* This internal API is used to calculate the gas wait */ +static uint8_t calc_gas_wait(uint16_t dur) +{ + uint8_t factor = 0; + uint8_t durval; + + if (dur >= 0xfc0) + { + durval = 0xff; /* Max duration*/ + } + else + { + while (dur > 0x3F) + { + dur = dur / 4; + factor += 1; + } + + durval = (uint8_t)(dur + (factor * 64)); + } + + return durval; +} + +/* This internal API is used to read a single data of the sensor */ +static int8_t read_field_data(uint8_t index, struct bme68x_data *data, struct bme68x_dev *dev) +{ + int8_t rslt = BME68X_OK; + uint8_t buff[BME68X_LEN_FIELD] = { 0 }; + uint8_t gas_range_l, gas_range_h; + uint32_t adc_temp; + uint32_t adc_pres; + uint16_t adc_hum; + uint16_t adc_gas_res_low, adc_gas_res_high; + uint8_t tries = 5; + + while ((tries) && (rslt == BME68X_OK)) + { + rslt = bme68x_get_regs(((uint8_t)(BME68X_REG_FIELD0 + (index * BME68X_LEN_FIELD_OFFSET))), + buff, + (uint16_t)BME68X_LEN_FIELD, + dev); + if (!data) + { + rslt = BME68X_E_NULL_PTR; + break; + } + + data->status = buff[0] & BME68X_NEW_DATA_MSK; + data->gas_index = buff[0] & BME68X_GAS_INDEX_MSK; + data->meas_index = buff[1]; + + /* read the raw data from the sensor */ + adc_pres = (uint32_t)(((uint32_t)buff[2] * 4096) | ((uint32_t)buff[3] * 16) | ((uint32_t)buff[4] / 16)); + adc_temp = (uint32_t)(((uint32_t)buff[5] * 4096) | ((uint32_t)buff[6] * 16) | ((uint32_t)buff[7] / 16)); + adc_hum = (uint16_t)(((uint32_t)buff[8] * 256) | (uint32_t)buff[9]); + adc_gas_res_low = (uint16_t)((uint32_t)buff[13] * 4 | (((uint32_t)buff[14]) / 64)); + adc_gas_res_high = (uint16_t)((uint32_t)buff[15] * 4 | (((uint32_t)buff[16]) / 64)); + gas_range_l = buff[14] & BME68X_GAS_RANGE_MSK; + gas_range_h = buff[16] & BME68X_GAS_RANGE_MSK; + if (dev->variant_id == BME68X_VARIANT_GAS_HIGH) + { + data->status |= buff[16] & BME68X_GASM_VALID_MSK; + data->status |= buff[16] & BME68X_HEAT_STAB_MSK; + } + else + { + data->status |= buff[14] & BME68X_GASM_VALID_MSK; + data->status |= buff[14] & BME68X_HEAT_STAB_MSK; + } + + if ((data->status & BME68X_NEW_DATA_MSK) && (rslt == BME68X_OK)) + { + rslt = bme68x_get_regs(BME68X_REG_RES_HEAT0 + data->gas_index, &data->res_heat, 1, dev); + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_IDAC_HEAT0 + data->gas_index, &data->idac, 1, dev); + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_GAS_WAIT0 + data->gas_index, &data->gas_wait, 1, dev); + } + + if (rslt == BME68X_OK) + { + data->temperature = calc_temperature(adc_temp, dev); + data->pressure = calc_pressure(adc_pres, dev); + data->humidity = calc_humidity(adc_hum, dev); + if (dev->variant_id == BME68X_VARIANT_GAS_HIGH) + { + data->gas_resistance = calc_gas_resistance_high(adc_gas_res_high, gas_range_h); + } + else + { + data->gas_resistance = calc_gas_resistance_low(adc_gas_res_low, gas_range_l, dev); + } + + break; + } + } + + if (rslt == BME68X_OK) + { + dev->delay_us(BME68X_PERIOD_POLL, dev->intf_ptr); + } + + tries--; + } + + return rslt; +} + +/* This internal API is used to read all data fields of the sensor */ +static int8_t read_all_field_data(struct bme68x_data * const data[], struct bme68x_dev *dev) +{ + int8_t rslt = BME68X_OK; + uint8_t buff[BME68X_LEN_FIELD * 3] = { 0 }; + uint8_t gas_range_l, gas_range_h; + uint32_t adc_temp; + uint32_t adc_pres; + uint16_t adc_hum; + uint16_t adc_gas_res_low, adc_gas_res_high; + uint8_t off; + uint8_t set_val[30] = { 0 }; /* idac, res_heat, gas_wait */ + uint8_t i; + + if (!data[0] && !data[1] && !data[2]) + { + rslt = BME68X_E_NULL_PTR; + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_FIELD0, buff, (uint32_t) BME68X_LEN_FIELD * 3, dev); + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_IDAC_HEAT0, set_val, 30, dev); + } + + for (i = 0; ((i < 3) && (rslt == BME68X_OK)); i++) + { + off = (uint8_t)(i * BME68X_LEN_FIELD); + data[i]->status = buff[off] & BME68X_NEW_DATA_MSK; + data[i]->gas_index = buff[off] & BME68X_GAS_INDEX_MSK; + data[i]->meas_index = buff[off + 1]; + + /* read the raw data from the sensor */ + adc_pres = + (uint32_t) (((uint32_t) buff[off + 2] * 4096) | ((uint32_t) buff[off + 3] * 16) | + ((uint32_t) buff[off + 4] / 16)); + adc_temp = + (uint32_t) (((uint32_t) buff[off + 5] * 4096) | ((uint32_t) buff[off + 6] * 16) | + ((uint32_t) buff[off + 7] / 16)); + adc_hum = (uint16_t) (((uint32_t) buff[off + 8] * 256) | (uint32_t) buff[off + 9]); + adc_gas_res_low = (uint16_t) ((uint32_t) buff[off + 13] * 4 | (((uint32_t) buff[off + 14]) / 64)); + adc_gas_res_high = (uint16_t) ((uint32_t) buff[off + 15] * 4 | (((uint32_t) buff[off + 16]) / 64)); + gas_range_l = buff[off + 14] & BME68X_GAS_RANGE_MSK; + gas_range_h = buff[off + 16] & BME68X_GAS_RANGE_MSK; + if (dev->variant_id == BME68X_VARIANT_GAS_HIGH) + { + data[i]->status |= buff[off + 16] & BME68X_GASM_VALID_MSK; + data[i]->status |= buff[off + 16] & BME68X_HEAT_STAB_MSK; + } + else + { + data[i]->status |= buff[off + 14] & BME68X_GASM_VALID_MSK; + data[i]->status |= buff[off + 14] & BME68X_HEAT_STAB_MSK; + } + + data[i]->idac = set_val[data[i]->gas_index]; + data[i]->res_heat = set_val[10 + data[i]->gas_index]; + data[i]->gas_wait = set_val[20 + data[i]->gas_index]; + data[i]->temperature = calc_temperature(adc_temp, dev); + data[i]->pressure = calc_pressure(adc_pres, dev); + data[i]->humidity = calc_humidity(adc_hum, dev); + if (dev->variant_id == BME68X_VARIANT_GAS_HIGH) + { + data[i]->gas_resistance = calc_gas_resistance_high(adc_gas_res_high, gas_range_h); + } + else + { + data[i]->gas_resistance = calc_gas_resistance_low(adc_gas_res_low, gas_range_l, dev); + } + } + + return rslt; +} + +/* This internal API is used to switch between SPI memory pages */ +static int8_t set_mem_page(uint8_t reg_addr, struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t reg; + uint8_t mem_page; + + /* Check for null pointers in the device structure*/ + rslt = null_ptr_check(dev); + if (rslt == BME68X_OK) + { + if (reg_addr > 0x7f) + { + mem_page = BME68X_MEM_PAGE1; + } + else + { + mem_page = BME68X_MEM_PAGE0; + } + + if (mem_page != dev->mem_page) + { + dev->mem_page = mem_page; + dev->intf_rslt = dev->read(BME68X_REG_MEM_PAGE | BME68X_SPI_RD_MSK, ®, 1, dev->intf_ptr); + if (dev->intf_rslt != 0) + { + rslt = BME68X_E_COM_FAIL; + } + + if (rslt == BME68X_OK) + { + reg = reg & (~BME68X_MEM_PAGE_MSK); + reg = reg | (dev->mem_page & BME68X_MEM_PAGE_MSK); + dev->intf_rslt = dev->write(BME68X_REG_MEM_PAGE & BME68X_SPI_WR_MSK, ®, 1, dev->intf_ptr); + if (dev->intf_rslt != 0) + { + rslt = BME68X_E_COM_FAIL; + } + } + } + } + + return rslt; +} + +/* This internal API is used to get the current SPI memory page */ +static int8_t get_mem_page(struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t reg; + + /* Check for null pointer in the device structure*/ + rslt = null_ptr_check(dev); + if (rslt == BME68X_OK) + { + dev->intf_rslt = dev->read(BME68X_REG_MEM_PAGE | BME68X_SPI_RD_MSK, ®, 1, dev->intf_ptr); + if (dev->intf_rslt != 0) + { + rslt = BME68X_E_COM_FAIL; + } + else + { + dev->mem_page = reg & BME68X_MEM_PAGE_MSK; + } + } + + return rslt; +} + +/* This internal API is used to limit the max value of a parameter */ +static int8_t boundary_check(uint8_t *value, uint8_t max, struct bme68x_dev *dev) +{ + int8_t rslt; + + rslt = null_ptr_check(dev); + if ((value != NULL) && (rslt == BME68X_OK)) + { + /* Check if value is above maximum value */ + if (*value > max) + { + /* Auto correct the invalid value to maximum value */ + *value = max; + dev->info_msg |= BME68X_I_PARAM_CORR; + } + } + else + { + rslt = BME68X_E_NULL_PTR; + } + + return rslt; +} + +/* This internal API is used to check the bme68x_dev for null pointers */ +static int8_t null_ptr_check(const struct bme68x_dev *dev) +{ + int8_t rslt = BME68X_OK; + + if ((dev == NULL) || (dev->read == NULL) || (dev->write == NULL) || (dev->delay_us == NULL)) + { + /* Device structure pointer is not valid */ + rslt = BME68X_E_NULL_PTR; + } + + return rslt; +} + +/* This internal API is used to set heater configurations */ +static int8_t set_conf(const struct bme68x_heatr_conf *conf, uint8_t op_mode, uint8_t *nb_conv, struct bme68x_dev *dev) +{ + int8_t rslt = BME68X_OK; + uint8_t i; + uint8_t shared_dur; + uint8_t write_len = 0; + uint8_t heater_dur_shared_addr = BME68X_REG_SHD_HEATR_DUR; + uint8_t rh_reg_addr[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + uint8_t rh_reg_data[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + uint8_t gw_reg_addr[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + uint8_t gw_reg_data[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + switch (op_mode) + { + case BME68X_FORCED_MODE: + rh_reg_addr[0] = BME68X_REG_RES_HEAT0; + rh_reg_data[0] = calc_res_heat(conf->heatr_temp, dev); + gw_reg_addr[0] = BME68X_REG_GAS_WAIT0; + gw_reg_data[0] = calc_gas_wait(conf->heatr_dur); + (*nb_conv) = 0; + write_len = 1; + break; + case BME68X_SEQUENTIAL_MODE: + if ((!conf->heatr_dur_prof) || (!conf->heatr_temp_prof)) + { + rslt = BME68X_E_NULL_PTR; + break; + } + + for (i = 0; i < conf->profile_len; i++) + { + rh_reg_addr[i] = BME68X_REG_RES_HEAT0 + i; + rh_reg_data[i] = calc_res_heat(conf->heatr_temp_prof[i], dev); + gw_reg_addr[i] = BME68X_REG_GAS_WAIT0 + i; + gw_reg_data[i] = calc_gas_wait(conf->heatr_dur_prof[i]); + } + + (*nb_conv) = conf->profile_len; + write_len = conf->profile_len; + break; + case BME68X_PARALLEL_MODE: + if ((!conf->heatr_dur_prof) || (!conf->heatr_temp_prof)) + { + rslt = BME68X_E_NULL_PTR; + break; + } + + if (conf->shared_heatr_dur == 0) + { + rslt = BME68X_W_DEFINE_SHD_HEATR_DUR; + } + + for (i = 0; i < conf->profile_len; i++) + { + rh_reg_addr[i] = BME68X_REG_RES_HEAT0 + i; + rh_reg_data[i] = calc_res_heat(conf->heatr_temp_prof[i], dev); + gw_reg_addr[i] = BME68X_REG_GAS_WAIT0 + i; + gw_reg_data[i] = (uint8_t) conf->heatr_dur_prof[i]; + } + + (*nb_conv) = conf->profile_len; + write_len = conf->profile_len; + shared_dur = calc_heatr_dur_shared(conf->shared_heatr_dur); + if (rslt == BME68X_OK) + { + rslt = bme68x_set_regs(&heater_dur_shared_addr, &shared_dur, 1, dev); + } + + break; + default: + rslt = BME68X_W_DEFINE_OP_MODE; + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_set_regs(rh_reg_addr, rh_reg_data, write_len, dev); + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_set_regs(gw_reg_addr, gw_reg_data, write_len, dev); + } + + return rslt; +} + +/* This internal API is used to calculate the register value for + * shared heater duration */ +static uint8_t calc_heatr_dur_shared(uint16_t dur) +{ + uint8_t factor = 0; + uint8_t heatdurval; + + if (dur >= 0x783) + { + heatdurval = 0xff; /* Max duration */ + } + else + { + /* Step size of 0.477ms */ + dur = (uint16_t)(((uint32_t)dur * 1000) / 477); + while (dur > 0x3F) + { + dur = dur >> 2; + factor += 1; + } + + heatdurval = (uint8_t)(dur + (factor * 64)); + } + + return heatdurval; +} + +/* This internal API is used sort the sensor data */ +static void sort_sensor_data(uint8_t low_index, uint8_t high_index, struct bme68x_data *field[]) +{ + int16_t meas_index1; + int16_t meas_index2; + + meas_index1 = (int16_t)field[low_index]->meas_index; + meas_index2 = (int16_t)field[high_index]->meas_index; + if ((field[low_index]->status & BME68X_NEW_DATA_MSK) && (field[high_index]->status & BME68X_NEW_DATA_MSK)) + { + int16_t diff = meas_index2 - meas_index1; + if (((diff > -3) && (diff < 0)) || (diff > 2)) + { + swap_fields(low_index, high_index, field); + } + } + else if (field[high_index]->status & BME68X_NEW_DATA_MSK) + { + swap_fields(low_index, high_index, field); + } + + /* Sorting field data + * + * The 3 fields are filled in a fixed order with data in an incrementing + * 8-bit sub-measurement index which looks like + * Field index | Sub-meas index + * 0 | 0 + * 1 | 1 + * 2 | 2 + * 0 | 3 + * 1 | 4 + * 2 | 5 + * ... + * 0 | 252 + * 1 | 253 + * 2 | 254 + * 0 | 255 + * 1 | 0 + * 2 | 1 + * + * The fields are sorted in a way so as to always deal with only a snapshot + * of comparing 2 fields at a time. The order being + * field0 & field1 + * field0 & field2 + * field1 & field2 + * Here the oldest data should be in field0 while the newest is in field2. + * In the following documentation, field0's position would referred to as + * the lowest and field2 as the highest. + * + * In order to sort we have to consider the following cases, + * + * Case A: No fields have new data + * Then do not sort, as this data has already been read. + * + * Case B: Higher field has new data + * Then the new field get's the lowest position. + * + * Case C: Both fields have new data + * We have to put the oldest sample in the lowest position. Since the + * sub-meas index contains in essence the age of the sample, we calculate + * the difference between the higher field and the lower field. + * Here we have 3 sub-cases, + * Case 1: Regular read without overwrite + * Field index | Sub-meas index + * 0 | 3 + * 1 | 4 + * + * Field index | Sub-meas index + * 0 | 3 + * 2 | 5 + * + * The difference is always <= 2. There is no need to swap as the + * oldest sample is already in the lowest position. + * + * Case 2: Regular read with an overflow and without an overwrite + * Field index | Sub-meas index + * 0 | 255 + * 1 | 0 + * + * Field index | Sub-meas index + * 0 | 254 + * 2 | 0 + * + * The difference is always <= -3. There is no need to swap as the + * oldest sample is already in the lowest position. + * + * Case 3: Regular read with overwrite + * Field index | Sub-meas index + * 0 | 6 + * 1 | 4 + * + * Field index | Sub-meas index + * 0 | 6 + * 2 | 5 + * + * The difference is always > -3. There is a need to swap as the + * oldest sample is not in the lowest position. + * + * Case 4: Regular read with overwrite and overflow + * Field index | Sub-meas index + * 0 | 0 + * 1 | 254 + * + * Field index | Sub-meas index + * 0 | 0 + * 2 | 255 + * + * The difference is always > 2. There is a need to swap as the + * oldest sample is not in the lowest position. + * + * To summarize, we have to swap when + * - The higher field has new data and the lower field does not. + * - If both fields have new data, then the difference of sub-meas index + * between the higher field and the lower field creates the + * following condition for swapping. + * - (diff > -3) && (diff < 0), combination of cases 1, 2, and 3. + * - diff > 2, case 4. + * + * Here the limits of -3 and 2 derive from the fact that there are 3 fields. + * These values decrease or increase respectively if the number of fields increases. + */ +} + +/* This internal API is used sort the sensor data */ +static void swap_fields(uint8_t index1, uint8_t index2, struct bme68x_data *field[]) +{ + struct bme68x_data *temp; + + temp = field[index1]; + field[index1] = field[index2]; + field[index2] = temp; +} + +/* This Function is to analyze the sensor data */ +static int8_t analyze_sensor_data(const struct bme68x_data *data, uint8_t n_meas) +{ + int8_t rslt = BME68X_OK; + uint8_t self_test_failed = 0, i; + uint32_t cent_res = 0; + + if ((data[0].temperature < BME68X_MIN_TEMPERATURE) || (data[0].temperature > BME68X_MAX_TEMPERATURE)) + { + self_test_failed++; + } + + if ((data[0].pressure < BME68X_MIN_PRESSURE) || (data[0].pressure > BME68X_MAX_PRESSURE)) + { + self_test_failed++; + } + + if ((data[0].humidity < BME68X_MIN_HUMIDITY) || (data[0].humidity > BME68X_MAX_HUMIDITY)) + { + self_test_failed++; + } + + for (i = 0; i < n_meas; i++) /* Every gas measurement should be valid */ + { + if (!(data[i].status & BME68X_GASM_VALID_MSK)) + { + self_test_failed++; + } + } + + if (n_meas >= 6) + { + cent_res = (uint32_t)((5 * (data[3].gas_resistance + data[5].gas_resistance)) / (2 * data[4].gas_resistance)); + } + + if (cent_res < 6) + { + self_test_failed++; + } + + if (self_test_failed) + { + rslt = BME68X_E_SELF_TEST; + } + + return rslt; +} + +/* This internal API is used to read the calibration coefficients */ +static int8_t get_calib_data(struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t coeff_array[BME68X_LEN_COEFF_ALL]; + + rslt = bme68x_get_regs(BME68X_REG_COEFF1, coeff_array, BME68X_LEN_COEFF1, dev); + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_COEFF2, &coeff_array[BME68X_LEN_COEFF1], BME68X_LEN_COEFF2, dev); + } + + if (rslt == BME68X_OK) + { + rslt = bme68x_get_regs(BME68X_REG_COEFF3, + &coeff_array[BME68X_LEN_COEFF1 + BME68X_LEN_COEFF2], + BME68X_LEN_COEFF3, + dev); + } + + if (rslt == BME68X_OK) + { + /* Temperature related coefficients */ + dev->calib.par_t1 = + (uint16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_T1_MSB], coeff_array[BME68X_IDX_T1_LSB])); + dev->calib.par_t2 = + (int16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_T2_MSB], coeff_array[BME68X_IDX_T2_LSB])); + dev->calib.par_t3 = (int8_t)(coeff_array[BME68X_IDX_T3]); + + /* Pressure related coefficients */ + dev->calib.par_p1 = + (uint16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_P1_MSB], coeff_array[BME68X_IDX_P1_LSB])); + dev->calib.par_p2 = + (int16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_P2_MSB], coeff_array[BME68X_IDX_P2_LSB])); + dev->calib.par_p3 = (int8_t)coeff_array[BME68X_IDX_P3]; + dev->calib.par_p4 = + (int16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_P4_MSB], coeff_array[BME68X_IDX_P4_LSB])); + dev->calib.par_p5 = + (int16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_P5_MSB], coeff_array[BME68X_IDX_P5_LSB])); + dev->calib.par_p6 = (int8_t)(coeff_array[BME68X_IDX_P6]); + dev->calib.par_p7 = (int8_t)(coeff_array[BME68X_IDX_P7]); + dev->calib.par_p8 = + (int16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_P8_MSB], coeff_array[BME68X_IDX_P8_LSB])); + dev->calib.par_p9 = + (int16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_P9_MSB], coeff_array[BME68X_IDX_P9_LSB])); + dev->calib.par_p10 = (uint8_t)(coeff_array[BME68X_IDX_P10]); + + /* Humidity related coefficients */ + dev->calib.par_h1 = + (uint16_t)(((uint16_t)coeff_array[BME68X_IDX_H1_MSB] << 4) | + (coeff_array[BME68X_IDX_H1_LSB] & BME68X_BIT_H1_DATA_MSK)); + dev->calib.par_h2 = + (uint16_t)(((uint16_t)coeff_array[BME68X_IDX_H2_MSB] << 4) | ((coeff_array[BME68X_IDX_H2_LSB]) >> 4)); + dev->calib.par_h3 = (int8_t)coeff_array[BME68X_IDX_H3]; + dev->calib.par_h4 = (int8_t)coeff_array[BME68X_IDX_H4]; + dev->calib.par_h5 = (int8_t)coeff_array[BME68X_IDX_H5]; + dev->calib.par_h6 = (uint8_t)coeff_array[BME68X_IDX_H6]; + dev->calib.par_h7 = (int8_t)coeff_array[BME68X_IDX_H7]; + + /* Gas heater related coefficients */ + dev->calib.par_gh1 = (int8_t)coeff_array[BME68X_IDX_GH1]; + dev->calib.par_gh2 = + (int16_t)(BME68X_CONCAT_BYTES(coeff_array[BME68X_IDX_GH2_MSB], coeff_array[BME68X_IDX_GH2_LSB])); + dev->calib.par_gh3 = (int8_t)coeff_array[BME68X_IDX_GH3]; + + /* Other coefficients */ + dev->calib.res_heat_range = ((coeff_array[BME68X_IDX_RES_HEAT_RANGE] & BME68X_RHRANGE_MSK) / 16); + dev->calib.res_heat_val = (int8_t)coeff_array[BME68X_IDX_RES_HEAT_VAL]; + dev->calib.range_sw_err = ((int8_t)(coeff_array[BME68X_IDX_RANGE_SW_ERR] & BME68X_RSERROR_MSK)) / 16; + } + + return rslt; +} + +/* This internal API is used to read variant ID information from the register */ +static int8_t read_variant_id(struct bme68x_dev *dev) +{ + int8_t rslt; + uint8_t reg_data = 0; + + /* Read variant ID information register */ + rslt = bme68x_get_regs(BME68X_REG_VARIANT_ID, ®_data, 1, dev); + + if (rslt == BME68X_OK) + { + dev->variant_id = reg_data; + } + + return rslt; +} diff --git a/components/bme680/src/bsec2.c b/components/bme680/src/bsec2.c new file mode 100644 index 0000000..4209a66 --- /dev/null +++ b/components/bme680/src/bsec2.c @@ -0,0 +1,403 @@ +#include +#include "bsec2.h" + +/* Private funcs - prototypes */ + +/** + * @brief Reads the data from the BME68x sensor and process it + * @param currTimeNs: Current time in ns + * @return true if there are new outputs. false otherwise + */ +static bool bsec2_processData(struct bsec2 *self, int64_t currTimeNs, const struct bme68x_data *data); + +/** + * @brief Set the BME68x sensor configuration to forced mode + */ +static void bsec2_setBme68xConfigForced(struct bsec2 *self); + +/** + * @brief Set the BME68x sensor configuration to parallel mode + */ +static void bsec2_setBme68xConfigParallel(struct bsec2 *self); + + +/* Public funcs impl */ + +int bsec2_init(struct bsec2 *self, struct bme68x_dev *dev) { + self->ovfCounter = 0; + self->lastMillis = 0; + self->status = BSEC_OK; + self->extTempOffset = 0.0f; + self->opMode = BME68X_SLEEP_MODE; + self->newDataCallback = NULL; + self->sensor = dev; + memset(&self->version, 0, sizeof(self->version)); + memset(&self->bmeConf, 0, sizeof(self->bmeConf)); + memset(&self->outputs, 0, sizeof(self->outputs)); + + // The sensor must be inited and OK by the time this func is called! + + // Begin + self->status = bsec_init(); + if (self->status != BSEC_OK) { + return -1; + } + self->status = bsec_get_version(&self->version); + if (self->status != BSEC_OK) { + return -2; + } + memset(&self->bmeConf, 0, sizeof(self->bmeConf)); + memset(&self->outputs, 0, sizeof(self->outputs)); + return 0; +} + +bool bsec2_updateSubscription(struct bsec2 *self, const bsecSensor *sensorList, uint8_t nSensors, float sampleRate) { + bsec_sensor_configuration_t virtualSensors[BSEC_NUMBER_OUTPUTS], sensorSettings[BSEC_MAX_PHYSICAL_SENSOR]; + uint8_t nSensorSettings = BSEC_MAX_PHYSICAL_SENSOR; + + for (uint8_t i = 0; i < nSensors; i++) { + virtualSensors[i].sensor_id = sensorList[i]; + virtualSensors[i].sample_rate = sampleRate; + } + + /* Subscribe to library virtual sensors outputs */ + self->status = bsec_update_subscription(virtualSensors, nSensors, sensorSettings, &nSensorSettings); + if (self->status != BSEC_OK) { + return false; + } + + return true; +} + +bool bsec2_run(struct bsec2 *self) { + int64_t currTimeNs = bsec2_getTimeMs(self) * INT64_C(1000000); + + uint8_t lastOpMode = self->opMode; + + // set new opmode to what bsec wants + self->opMode = self->bmeConf.op_mode; + + if (currTimeNs >= self->bmeConf.next_call) { + /* Provides the information about the current sensor configuration that is + necessary to fulfill the input requirements, eg: operation mode, timestamp + at which the sensor data shall be fetched etc */ + self->status = bsec_sensor_control(currTimeNs, &self->bmeConf); + if (self->status != BSEC_OK) { + return false; + } + + switch (self->bmeConf.op_mode) { + case BME68X_FORCED_MODE: + bsec2_setBme68xConfigForced(self); + break; + case BME68X_PARALLEL_MODE: + if (self->opMode != self->bmeConf.op_mode) { + bsec2_setBme68xConfigParallel(self); + } + break; + + case BME68X_SLEEP_MODE: + if (self->opMode != self->bmeConf.op_mode) { + self->sensor_status = bme68x_set_op_mode(BME68X_SLEEP_MODE, self->sensor); + self->opMode = BME68X_SLEEP_MODE; + } + break; + } + + if (self->sensor_status < BME68X_OK) { + bsec2_errormsg("bsec2 sensor error"); + return false; + } + + struct bme68x_data sensorData[3]; + struct bme68x_data *pdata; + + if (self->bmeConf.trigger_measurement && self->bmeConf.op_mode != BME68X_SLEEP_MODE) { + uint8_t nfields; + self->sensor_status = bme68x_get_data(self->opMode, sensorData, &nfields, self->sensor); + + if (self->sensor_status == BME68X_OK && nfields > 0) { + if (lastOpMode == BME68X_FORCED_MODE) { + pdata = &sensorData[0]; + if (pdata->status & BME68X_GASM_VALID_MSK) { + if (!bsec2_processData(self, currTimeNs, pdata)) { + return false; + } + } + } else { + for (int i = 0; i < nfields; i++) { + pdata = &sensorData[i]; + if (pdata->status & BME68X_GASM_VALID_MSK) { + if (!bsec2_processData(self, currTimeNs, pdata)) { + return false; + } + } + } + } + } + } + } + return true; +} + +bsecData bsec2_getData(struct bsec2 *self, bsecSensor id) { + bsecData emp = + {0}; + for (uint8_t i = 0; i < self->outputs.nOutputs; i++) + if (id == self->outputs.output[i].sensor_id) + return self->outputs.output[i]; + return emp; +} + +/** + * @brief Function to get the state of the algorithm to save to non-volatile memory + * @param state : Pointer to a memory location, to hold the state + * @return true for success, false otherwise + */ +bool bsec2_getState(struct bsec2 *self, uint8_t *state) { + uint32_t n_serialized_state = BSEC_MAX_STATE_BLOB_SIZE; + + self->status = bsec_get_state(0, state, BSEC_MAX_STATE_BLOB_SIZE, self->workBuffer, BSEC_MAX_WORKBUFFER_SIZE, + &n_serialized_state); + if (self->status != BSEC_OK) { + bsec2_errormsg("bsec2_getState: error from bsec_get_state"); + return false; + } + return true; +} + +/** + * @brief Function to set the state of the algorithm from non-volatile memory + * @param state : Pointer to a memory location that contains the state + * @return true for success, false otherwise + */ +bool bsec2_setState(struct bsec2 *self, uint8_t *state) { + self->status = bsec_set_state(state, BSEC_MAX_STATE_BLOB_SIZE, self->workBuffer, BSEC_MAX_WORKBUFFER_SIZE); + if (self->status != BSEC_OK) { + bsec2_errormsg("bsec2_setState: error from bsec_set_state"); + return false; + } + + memset(&self->bmeConf, 0, sizeof(self->bmeConf)); + + return true; +} + +/** + * @brief Function to retrieve the current library configuration + * @param config : Pointer to a memory location, to hold the serialized config blob, must hold BSEC_MAX_PROPERTY_BLOB_SIZE + * @return true for success, false otherwise + */ +bool bsec2_getConfig(struct bsec2 *self, uint8_t *config) { + uint32_t n_serialized_settings = 0; + + self->status = bsec_get_configuration(0, config, BSEC_MAX_PROPERTY_BLOB_SIZE, self->workBuffer, BSEC_MAX_WORKBUFFER_SIZE, &n_serialized_settings); + if (self->status != BSEC_OK) { + bsec2_errormsg("bsec2_getConfig: error from bsec_get_configuration"); + return false; + } + + return true; +} + +/** + * @brief Function to set the configuration of the algorithm from memory + * @param state : Pointer to a memory location that contains the configuration + * @return true for success, false otherwise + */ +bool bsec2_setConfig(struct bsec2 *self, const uint8_t *config) { + self->status = bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, self->workBuffer, BSEC_MAX_WORKBUFFER_SIZE); + if (self->status != BSEC_OK) { + bsec2_errormsg("bsec2_setConfig: error from bsec_set_configuration"); + return false; + } + + // copied from arduino lib, but why? + memset(&self->bmeConf, 0, sizeof(self->bmeConf)); + return true; +} + +/** + * @brief Function to calculate an int64_t timestamp in milliseconds + */ +int64_t bsec2_getTimeMs(struct bsec2 *self) { + int64_t timeMs = bsec2_timestamp_millis(); + + if (self->lastMillis > timeMs) /* An overflow occurred */ + { + self->lastMillis = timeMs; + self->ovfCounter++; + } + return timeMs + (self->ovfCounter * INT64_C(0xFFFFFFFF)); +} + +/* Private funcs impl */ + +static bool bsec2_processData(struct bsec2 *self, int64_t currTimeNs, const struct bme68x_data *data) { + + bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; /* Temp, Pres, Hum & Gas */ + uint8_t nInputs = 0; + /* Checks all the required sensor inputs, required for the BSEC library for the requested outputs */ + if (BSEC_CHECK_INPUT(self->bmeConf.process_data, BSEC_INPUT_HEATSOURCE)) { + inputs[nInputs].sensor_id = BSEC_INPUT_HEATSOURCE; + inputs[nInputs].signal = self->extTempOffset; + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + } + if (BSEC_CHECK_INPUT(self->bmeConf.process_data, BSEC_INPUT_TEMPERATURE)) { +#ifdef BME68X_USE_FPU + inputs[nInputs].sensor_id = BSEC_INPUT_TEMPERATURE; +#else + inputs[nInputs].sensor_id = BSEC_INPUT_TEMPERATURE / 100.0f; +#endif + inputs[nInputs].signal = data->temperature; + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + } + if (BSEC_CHECK_INPUT(self->bmeConf.process_data, BSEC_INPUT_HUMIDITY)) { +#ifdef BME68X_USE_FPU + inputs[nInputs].sensor_id = BSEC_INPUT_HUMIDITY; +#else + inputs[nInputs].sensor_id = BSEC_INPUT_HUMIDITY / 1000.0f; +#endif + inputs[nInputs].signal = data->humidity; + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + } + if (BSEC_CHECK_INPUT(self->bmeConf.process_data, BSEC_INPUT_PRESSURE)) { + inputs[nInputs].sensor_id = BSEC_INPUT_PRESSURE; + inputs[nInputs].signal = data->pressure; + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + } + if (BSEC_CHECK_INPUT(self->bmeConf.process_data, BSEC_INPUT_GASRESISTOR) && + (data->status & BME68X_GASM_VALID_MSK)) { + inputs[nInputs].sensor_id = BSEC_INPUT_GASRESISTOR; + inputs[nInputs].signal = data->gas_resistance; + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + } + if (BSEC_CHECK_INPUT(self->bmeConf.process_data, BSEC_INPUT_PROFILE_PART) && + (data->status & BME68X_GASM_VALID_MSK)) { + inputs[nInputs].sensor_id = BSEC_INPUT_PROFILE_PART; + inputs[nInputs].signal = (self->opMode == BME68X_FORCED_MODE) ? 0 : (float) data->gas_index; + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + } + + if (nInputs > 0) { + + self->outputs.nOutputs = BSEC_NUMBER_OUTPUTS; + memset(self->outputs.output, 0, sizeof(self->outputs.output)); + + /* Processing of the input signals and returning of output samples is performed by bsec_do_steps() */ + self->status = bsec_do_steps(inputs, nInputs, self->outputs.output, &self->outputs.nOutputs); + + if (self->status != BSEC_OK) { + bsec2_errormsg("bsec2_processData: error from bsec_do_steps"); + return false; + } + + if (self->newDataCallback) { + self->newDataCallback(data, &self->outputs, self); + } + } + return true; +} + +static void bsec2_setBme68xConfigForced(struct bsec2 *self) { + struct bme68x_conf conf; + struct bme68x_heatr_conf hconf; + + self->sensor_status = bme68x_get_conf(&conf, self->sensor); + if (BME68X_OK != self->sensor_status) { + bsec2_errormsg("bsec2_setBme68xConfigForced: error from bme68x_get_conf"); + return; + } + + self->sensor_status = bme68x_get_heatr_conf(&hconf, self->sensor); + if (BME68X_OK != self->sensor_status) { + bsec2_errormsg("bsec2_setBme68xConfigForced: error from bme68x_get_heatr_conf"); + return; + } + + conf.os_hum = self->bmeConf.humidity_oversampling; + conf.os_pres = self->bmeConf.pressure_oversampling; + conf.os_temp = self->bmeConf.temperature_oversampling; + + self->sensor_status = bme68x_set_conf(&conf, self->sensor); + if (BME68X_OK != self->sensor_status) { + bsec2_errormsg("bsec2_setBme68xConfigForced: error from bme68x_set_conf"); + return; + } + + hconf.enable = BME68X_ENABLE; + hconf.heatr_dur = self->bmeConf.heater_duration; + hconf.heatr_temp = self->bmeConf.heater_temperature; + + self->sensor_status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &hconf, self->sensor); + if (BME68X_OK != self->sensor_status) { + bsec2_errormsg("bsec2_setBme68xConfigForced: error from bme68x_set_heatr_conf"); + return; + } + + self->sensor_status = bme68x_set_op_mode(BME68X_FORCED_MODE, self->sensor); + if (BME68X_OK != self->sensor_status) { + bsec2_errormsg("bsec2_setBme68xConfigForced: error from bme68x_set_op_mode"); + return; + } + + self->opMode = BME68X_FORCED_MODE; +} + +static void bsec2_setBme68xConfigParallel(struct bsec2 *self) { + uint16_t sharedHeaterDur = 0; + + struct bme68x_conf conf; + struct bme68x_heatr_conf hconf; + + self->sensor_status = bme68x_get_conf(&conf, self->sensor); + if (BME68X_OK != self->sensor_status) { + bsec2_errormsg("bsec2_setBme68xConfigParallel: error from bme68x_get_conf"); + return; + } + + self->sensor_status = bme68x_get_heatr_conf(&hconf, self->sensor); + if (BME68X_OK != self->sensor_status) { + bsec2_errormsg("bsec2_setBme68xConfigParallel: error from bme68x_get_heatr_conf"); + return; + } + + conf.os_hum = self->bmeConf.humidity_oversampling; + conf.os_pres = self->bmeConf.pressure_oversampling; + conf.os_temp = self->bmeConf.temperature_oversampling; + + self->sensor_status = bme68x_set_conf(&conf, self->sensor); + if (BME68X_OK != self->sensor_status) { + bsec2_errormsg("bsec2_setBme68xConfigParallel: error from bme68x_set_conf"); + return; + } + + sharedHeaterDur = BSEC_TOTAL_HEAT_DUR - (bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &conf, self->sensor) / INT64_C(1000)); + + hconf.enable = BME68X_ENABLE; + hconf.heatr_dur = self->bmeConf.heater_duration; + hconf.heatr_temp = self->bmeConf.heater_temperature; + hconf.shared_heatr_dur = sharedHeaterDur; + hconf.heatr_dur_prof = self->bmeConf.heater_duration_profile; + hconf.profile_len = self->bmeConf.heater_profile_len; + + self->sensor_status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &hconf, self->sensor); + if (BME68X_OK != self->sensor_status) { + bsec2_errormsg("bsec2_setBme68xConfigParallel: error from bme68x_set_heatr_conf"); + return; + } + + self->sensor_status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, self->sensor); + if (BME68X_OK != self->sensor_status) { + bsec2_errormsg("bsec2_setBme68xConfigParallel: error from bme68x_set_op_mode"); + return; + } + + self->opMode = BME68X_PARALLEL_MODE; +} diff --git a/main/voc_sensor.c b/main/voc_sensor.c index bb2b6f4..1785c06 100644 --- a/main/voc_sensor.c +++ b/main/voc_sensor.c @@ -5,264 +5,234 @@ #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG #include +#include #include #include #include #include +#include #include "voc_sensor.h" - -struct bme680_dev gas_sensor; +#include "i2c_utils.h" static const char *TAG = "vocsensor"; -#define i2c_num I2C_NUM_0 -#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ -#define READ_BIT I2C_MASTER_READ /*!< I2C master read */ -#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ -#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ -#define ACK_VAL 0x0 /*!< I2C ack value */ -#define NACK_VAL 0x1 /*!< I2C nack value */ - -static void user_delay_ms(uint32_t period) { - /* - * Return control or wait, - * for a period amount of milliseconds - */ - vTaskDelay(pdMS_TO_TICKS(period)); +#define VOC_I2C_NUM I2C_NUM_0 +#define VOC_I2C_TO_MS 250 + +// Config overrides for BSEC +#define BME68X_PERIOD_POLL UINT32_C(5000) + +#include "bme68x.h" +#include "bme68x_defs.h" +#include "bsec2.h" + +struct sensor_itf { + uint8_t dev_addr; +}; + +static struct sensor_itf gas_sensor_intf = {}; +static struct bme68x_dev gas_sensor = {}; +static struct bsec2 gas_sensor_bsec = {}; + +void bsec2_errormsg(const char *msg) { + ESP_LOGE("BSEC", "%s", msg); } +uint32_t bsec2_timestamp_millis() { #if 0 -static int8_t user_spi_read(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t len) -{ - int8_t rslt = 0; /* Return 0 for Success, non-zero for failure */ - - /* - * The parameter dev_id can be used as a variable to select which Chip Select pin has - * to be set low to activate the relevant device on the SPI bus - */ - - /* - * Data on the bus should be like - * |----------------+---------------------+-------------| - * | MOSI | MISO | Chip Select | - * |----------------+---------------------|-------------| - * | (don't care) | (don't care) | HIGH | - * | (reg_addr) | (don't care) | LOW | - * | (don't care) | (reg_data[0]) | LOW | - * | (....) | (....) | LOW | - * | (don't care) | (reg_data[len - 1]) | LOW | - * | (don't care) | (don't care) | HIGH | - * |----------------+---------------------|-------------| - */ - - return rslt; + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec*1000 + (tv.tv_usec / 1000000); +#else + return pdTICKS_TO_MS(xTaskGetTickCount()); // This is no-op when using 1ms tick +#endif } -static int8_t user_spi_write(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t len) -{ - int8_t rslt = 0; /* Return 0 for Success, non-zero for failure */ - - /* - * The parameter dev_id can be used as a variable to select which Chip Select pin has - * to be set low to activate the relevant device on the SPI bus - */ - - /* - * Data on the bus should be like - * |---------------------+--------------+-------------| - * | MOSI | MISO | Chip Select | - * |---------------------+--------------|-------------| - * | (don't care) | (don't care) | HIGH | - * | (reg_addr) | (don't care) | LOW | - * | (reg_data[0]) | (don't care) | LOW | - * | (....) | (....) | LOW | - * | (reg_data[len - 1]) | (don't care) | LOW | - * | (don't care) | (don't care) | HIGH | - * |---------------------+--------------|-------------| - */ - - return rslt; -} +static void user_delay_us(uint32_t period, void *intf_ptr) { + (void) intf_ptr; + + uint32_t millis = period / 1000; + uint32_t usec = period % 1000; + +#if configTICK_RATE_HZ != 1000 + uint32_t remainder = millis % pdTICKS_TO_MS(1); + millis -= remainder; + usec += remainder * 1000; #endif -static int8_t user_i2c_read(uint8_t dev_id, uint8_t reg_addr, uint8_t *data_rd, uint16_t size) { - ESP_LOGD(TAG, "BME680 i2c read"); + if (millis) { + vTaskDelay(pdMS_TO_TICKS(millis)); + } + if (usec) { + ets_delay_us(usec); + } +} - /* - * The parameter dev_id can be used as a variable to store the I2C address of the device - */ - - /* - * Data on the bus should be like - * |------------+---------------------| - * | I2C action | Data | - * |------------+---------------------| - * | Start | - | - * | Write | (reg_addr) | - * | Stop | - | - * | Start | - | - * | Read | (reg_data[0]) | - * | Read | (....) | - * | Read | (reg_data[len - 1]) | - * | Stop | - | - * |------------+---------------------| - */ - - if (size == 0) { - return 0; +static BME68X_INTF_RET_TYPE user_i2c_read(uint8_t reg_addr, uint8_t *reg_data, uint32_t length, void *intf_ptr) { + ESP_LOGD(TAG, "BME680 i2c read"); + if (length == 0) { + return BME68X_OK; } + struct sensor_itf *itf = intf_ptr; i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (dev_id << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN); - i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN); - - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (dev_id << 1) | I2C_MASTER_READ, ACK_CHECK_EN); - i2c_master_read(cmd, data_rd, size, I2C_MASTER_LAST_NACK); - i2c_master_stop(cmd); - - esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, pdMS_TO_TICKS(1000)); + i2c_reg_read(cmd, itf->dev_addr, reg_addr, reg_data, length); + esp_err_t ret = i2c_master_cmd_begin(VOC_I2C_NUM, cmd, pdMS_TO_TICKS(VOC_I2C_TO_MS)); i2c_cmd_link_delete(cmd); - -// HAL_I2C_Master_Transmit(&hi2c1, dev_id<<1, ®_addr, 1, 100); -// HAL_I2C_Master_Receive(&hi2c1, dev_id<<1, reg_data, len, 100); - - return ret == ESP_OK ? BME680_OK : BME680_E_COM_FAIL; + return ret == ESP_OK ? BME68X_OK : BME68X_E_COM_FAIL; } -static int8_t user_i2c_write(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t len) { +static BME68X_INTF_RET_TYPE user_i2c_write(uint8_t reg_addr, const uint8_t *reg_data, uint32_t length, void *intf_ptr) { ESP_LOGD(TAG, "BME680 i2c write"); - /* - * The parameter dev_id can be used as a variable to store the I2C address of the device - */ - - /* - * Data on the bus should be like - * |------------+---------------------| - * | I2C action | Data | - * |------------+---------------------| - * | Start | - | - * | Write | (reg_addr) | - * | Write | (reg_data[0]) | - * | Write | (....) | - * | Write | (reg_data[len - 1]) | - * | Stop | - | - * |------------+---------------------| - */ - //HAL_I2C_Master_Transmit(&hi2c1, dev_id<<1, &data[0], (uint16_t) (len + 1), 100); - + if (length == 0) { + return BME68X_OK; + } + struct sensor_itf *itf = intf_ptr; i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (dev_id << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN); - i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN); - i2c_master_write(cmd, reg_data, len, ACK_CHECK_EN); - i2c_master_stop(cmd); - - esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, pdMS_TO_TICKS(1000)); + i2c_reg_write(cmd, itf->dev_addr, reg_addr, reg_data, length); + esp_err_t ret = i2c_master_cmd_begin(VOC_I2C_NUM, cmd, pdMS_TO_TICKS(VOC_I2C_TO_MS)); i2c_cmd_link_delete(cmd); - - return ret == ESP_OK ? BME680_OK : BME680_E_COM_FAIL; + return ret == ESP_OK ? BME68X_OK : BME68X_E_COM_FAIL; } - -esp_err_t voc_init(void) { +static esp_err_t voc_init(void) { int8_t rslt; - gas_sensor.dev_id = BME680_I2C_ADDR_PRIMARY; - gas_sensor.intf = BME680_I2C_INTF; + gas_sensor_intf.dev_addr = BME68X_I2C_ADDR_LOW; + gas_sensor.intf_ptr = &gas_sensor_intf; + gas_sensor.amb_temp = 25; // TODO set this from senseair! + gas_sensor.intf = BME68X_I2C_INTF; gas_sensor.read = user_i2c_read; gas_sensor.write = user_i2c_write; - gas_sensor.delay_ms = user_delay_ms; - - ESP_LOGD(TAG, "BME680 initializing"); - rslt = bme680_init(&gas_sensor); - if (rslt != BME680_OK) { + gas_sensor.delay_us = user_delay_us; + gas_sensor.intf_ptr = &gas_sensor; + + for (int retry = 0; retry < 3; retry++) { + ESP_LOGD(TAG, "BME680 initializing"); + rslt = bme68x_init(&gas_sensor); + if (rslt == BME68X_OK) { + break; + } + } + if (rslt != BME68X_OK) { ESP_LOGE(TAG, "Error from bme680_init: %d", rslt); return ESP_FAIL; } - ESP_LOGD(TAG, "BME680 configuring"); - /* Set the temperature, pressure and humidity settings */ - gas_sensor.tph_sett.os_hum = BME680_OS_2X; - gas_sensor.tph_sett.os_pres = BME680_OS_4X; - gas_sensor.tph_sett.os_temp = BME680_OS_8X; - gas_sensor.tph_sett.filter = BME680_FILTER_SIZE_3; - - /* Set the remaining gas sensor settings and link the heating profile */ - gas_sensor.gas_sett.run_gas = BME680_ENABLE_GAS_MEAS; - /* Create a ramp heat waveform in 3 steps */ - gas_sensor.gas_sett.heatr_temp = 320; /* degree Celsius */ - gas_sensor.gas_sett.heatr_dur = 150; /* milliseconds */ - - /* Select the power mode */ - /* Must be set before writing the sensor configuration */ - gas_sensor.power_mode = BME680_FORCED_MODE; - - /* Set the required sensor settings needed */ - uint8_t set_required_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_FILTER_SEL | BME680_GAS_SENSOR_SEL; - - /* Set the desired sensor configuration */ - rslt = bme680_set_sensor_settings(set_required_settings, &gas_sensor); - if (rslt != BME680_OK) { - ESP_LOGE(TAG, "Error from bme680_set_sensor_settings: %d", rslt); - return ESP_FAIL; - } return ESP_OK; } -uint32_t voc_start_measure(void) { - /* Set the power mode */ - int8_t rslt; - - rslt = bme680_set_sensor_mode(&gas_sensor); - if (rslt != BME680_OK) { - ESP_LOGE(TAG, "Error from bme680_set_sensor_mode: %d", rslt); - return 0; +static void new_data_callback( + const struct bme68x_data *data, + const bsecOutputs *outputs, + const struct bsec2 *bsec +) { + if (!outputs->nOutputs) { + return; } - /* Get the total measurement duration so as to sleep or wait till the - * measurement is complete */ - uint16_t meas_period; - bme680_get_profile_dur(&meas_period, &gas_sensor); - meas_period += 10; // add some extra safety margin - - ESP_LOGD(TAG, "Measurement will take %d ms\r\n", meas_period); - - return meas_period; + ESP_LOGI(TAG, "BSEC outputs: timestamp = %d", (int) (outputs->output[0].time_stamp / INT64_C(1000000))); + for (uint8_t i = 0; i < outputs->nOutputs; i++) { + const bsecData output = outputs->output[i]; + switch (output.sensor_id) { + case BSEC_OUTPUT_IAQ: + ESP_LOGI(TAG, "\tIAQ = %f, accuracy %d", output.signal, (int) output.accuracy); + break; + case BSEC_OUTPUT_STATIC_IAQ: + ESP_LOGI(TAG, "\tSTATIC_IAQ = %f, accuracy %d", output.signal, (int) output.accuracy); + break; + case BSEC_OUTPUT_CO2_EQUIVALENT: + ESP_LOGI(TAG, "\tCO2_EQUIVALENT = %f, accuracy %d", output.signal, (int) output.accuracy); + break; + case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT: + ESP_LOGI(TAG, "\tBREATH_VOC_EQUIVALENT = %f, accuracy %d", output.signal, (int) output.accuracy); + break; + case BSEC_OUTPUT_RAW_TEMPERATURE: + ESP_LOGI(TAG, "\tRAW_TEMPERATURE = %f, accuracy %d", output.signal, (int) output.accuracy); + break; + case BSEC_OUTPUT_RAW_PRESSURE: + ESP_LOGI(TAG, "\tRAW_PRESSURE = %f, accuracy %d", output.signal, (int) output.accuracy); + break; + case BSEC_OUTPUT_RAW_HUMIDITY: + ESP_LOGI(TAG, "\tRAW_HUMIDITY = %f, accuracy %d", output.signal, (int) output.accuracy); + break; + case BSEC_OUTPUT_RAW_GAS: + ESP_LOGI(TAG, "\tRAW_GAS = %f, accuracy %d", output.signal, (int) output.accuracy); + break; + case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE: + ESP_LOGI(TAG, "\tSENSOR_HEAT_COMPENSATED_TEMPERATURE = %f, accuracy %d", output.signal, (int) output.accuracy); + break; + case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY: + ESP_LOGI(TAG, "\tSENSOR_HEAT_COMPENSATED_HUMIDITY = %f, accuracy %d", output.signal, (int) output.accuracy); + break; + case BSEC_OUTPUT_STABILIZATION_STATUS: + ESP_LOGI(TAG, "\tSTABILIZATION_STATUS status = %d", (int) output.signal); + break; + case BSEC_OUTPUT_RUN_IN_STATUS: + ESP_LOGI(TAG, "\tRUN_IN_STATUS = %d", (int) output.signal); + break; + default: + break; + } + } } -esp_err_t voc_read(struct bme680_field_data *data) { - /* Set the power mode */ - int8_t rslt; - rslt = bme680_get_sensor_data(data, &gas_sensor); - if (rslt != BME680_OK) { - ESP_LOGE(TAG, "Error from bme680_get_sensor_data: %d", rslt); - return ESP_FAIL; +void voc_read_task(void *param) { + ESP_LOGI(TAG, "VOC sensor init"); + + if (ESP_OK != voc_init()) { + ESP_LOGE(TAG, "Fail to init sensor!"); + return; } - ESP_LOGI(TAG, "T: %d/100 degC, P: %d/100 hPa, H %d/1000 %%rH ", data->temperature, data->pressure, data->humidity); - /* Avoid using measurements from an unstable heating setup */ - if (data->status & BME680_GASM_VALID_MSK) { - ESP_LOGI(TAG, "G: %d ohms", data->gas_resistance); + int rv = bsec2_init(&gas_sensor_bsec, &gas_sensor); + if (rv != 0) { + ESP_LOGE(TAG, "Error in bsec init: %d", rv); + return; } - return ESP_OK; -} -void voc_read_task(void *param) { - ESP_LOGI(TAG, "VOC sensor init"); - voc_init(); + bsecSensor sensorList[] = { + BSEC_OUTPUT_IAQ, + BSEC_OUTPUT_STATIC_IAQ, + BSEC_OUTPUT_CO2_EQUIVALENT, + BSEC_OUTPUT_BREATH_VOC_EQUIVALENT, - struct bme680_field_data data; + BSEC_OUTPUT_RAW_TEMPERATURE, // XXX probably useless, we can get these directly from the data struct + BSEC_OUTPUT_RAW_PRESSURE, + BSEC_OUTPUT_RAW_HUMIDITY, + BSEC_OUTPUT_RAW_GAS, - while (1) { - ESP_LOGI(TAG, "VOC sensor meas"); - uint32_t mswait = voc_start_measure(); - vTaskDelay(pdMS_TO_TICKS(mswait)); + BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE, + BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY, - ESP_LOGI(TAG, "VOC sensor read"); - voc_read(&data); + BSEC_OUTPUT_STABILIZATION_STATUS, + BSEC_OUTPUT_RUN_IN_STATUS, + }; - // TODO do something with it + rv = bsec2_updateSubscription(&gas_sensor_bsec, + sensorList, + sizeof(sensorList) / sizeof(bsecSensor), + BSEC_SAMPLE_RATE_LP); - vTaskDelay(pdMS_TO_TICKS(1000)); + if (false == rv) { + ESP_LOGE(TAG, "Fail to update bsec subscriptions!"); + return; + } + + bsec2_attachCallback(&gas_sensor_bsec, new_data_callback); + + ESP_LOGI(TAG, "BSEC library version %d.%d.%d.%d", + gas_sensor_bsec.version.major, + gas_sensor_bsec.version.minor, + gas_sensor_bsec.version.major_bugfix, + gas_sensor_bsec.version.minor_bugfix); + + // TODO add bsec state persistence to NVS at some landmark intervals, e.g. 1 day + // TODO periodic updating of sensor ambient temp + + while (1) { + rv = bsec2_run(&gas_sensor_bsec); + if (false == rv) { + ESP_LOGE(TAG, "Error in bsec run!"); + } + vTaskDelay(pdMS_TO_TICKS(100)); } } diff --git a/main/voc_sensor.h b/main/voc_sensor.h index 800498c..ce3378b 100644 --- a/main/voc_sensor.h +++ b/main/voc_sensor.h @@ -5,14 +5,6 @@ #ifndef PROJ_VOC_SENSOR_H #define PROJ_VOC_SENSOR_H -#include -#include - -extern struct bme680_dev gas_sensor; - -esp_err_t voc_init(void); -uint32_t voc_start_measure(void); -esp_err_t voc_read(struct bme680_field_data *data); void voc_read_task(void *param); #endif //PROJ_VOC_SENSOR_H