From 9794dc118f73c4bca1c647e3f55b118c50bdc117 Mon Sep 17 00:00:00 2001 From: simonatoaca Date: Fri, 14 Jul 2023 15:57:13 +0300 Subject: [PATCH] drivers: Added support for BME680 drivers/sensors/bme680.c: The bme680 driver sensor.h: Added new type of sensor (Gas) to be used for retrieving the bme680 data esp32/common/src/esp32_bme680.c: bme680 support on esp32 esp32/esp32-sparrow_kit/esp32_bringup.c: added support for the bme680 esp32/esp32-sparrow-kit/configs/nsh/defconfig: fixed defconfig ci problem Signed-off-by: simonatoaca esp32-sparrow-kit: Fixed defconfig Signed-off-by: simonatoaca Code is now C89 compatible Signed-off-by: simonatoaca Reused already defined sensor data structs by registering every sub-sensor separately Signed-off-by: simonatoaca Implemented suggestions Signed-off-by: simonatoaca --- .../esp32/common/include/esp32_bme680.h | 85 + boards/xtensa/esp32/common/src/Make.defs | 4 + boards/xtensa/esp32/common/src/esp32_bme680.c | 87 + .../esp32-sparrow-kit/configs/nsh/defconfig | 2 + .../esp32-sparrow-kit/src/esp32_bringup.c | 15 + drivers/sensors/CMakeLists.txt | 4 + drivers/sensors/Kconfig | 57 + drivers/sensors/Make.defs | 4 + drivers/sensors/bme680.c | 1764 +++++++++++++++++ drivers/sensors/sensor.c | 1 + include/nuttx/sensors/bme680.h | 136 ++ include/nuttx/sensors/sensor.h | 15 +- 12 files changed, 2173 insertions(+), 1 deletion(-) create mode 100644 boards/xtensa/esp32/common/include/esp32_bme680.h create mode 100644 boards/xtensa/esp32/common/src/esp32_bme680.c create mode 100644 drivers/sensors/bme680.c create mode 100644 include/nuttx/sensors/bme680.h diff --git a/boards/xtensa/esp32/common/include/esp32_bme680.h b/boards/xtensa/esp32/common/include/esp32_bme680.h new file mode 100644 index 0000000000..01dc1bdc34 --- /dev/null +++ b/boards/xtensa/esp32/common/include/esp32_bme680.h @@ -0,0 +1,85 @@ +/**************************************************************************** + * boards/xtensa/esp32/common/include/esp32_bme680.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __BOARDS_XTENSA_ESP32_COMMON_INCLUDE_ESP32_BME680_H +#define __BOARDS_XTENSA_ESP32_COMMON_INCLUDE_ESP32_BME680_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Type Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Inline Functions + ****************************************************************************/ + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: board_bme680_initialize + * + * Description: + * Initialize and register the BME680 BME680 Temperature, Pressure, + * Humidity and Gas Resistance sensor. + * + * Input Parameters: + * devno - The device number, used to build the device path as /dev/pressN + * busno - The I2C bus number + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int board_bme680_initialize(int devno, int busno); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __BOARDS_XTENSA_ESP32_COMMON_INCLUDE_ESP32_BME680_H */ diff --git a/boards/xtensa/esp32/common/src/Make.defs b/boards/xtensa/esp32/common/src/Make.defs index ed718e251a..abdd2344fe 100644 --- a/boards/xtensa/esp32/common/src/Make.defs +++ b/boards/xtensa/esp32/common/src/Make.defs @@ -80,6 +80,10 @@ ifeq ($(CONFIG_SENSORS_BMP280),y) CSRCS += esp32_bmp280.c endif +ifeq ($(CONFIG_SENSORS_BME680),y) + CSRCS += esp32_bme680.c +endif + ifeq ($(CONFIG_SENSORS_SHT3X),y) CSRCS += esp32_sht3x.c endif diff --git a/boards/xtensa/esp32/common/src/esp32_bme680.c b/boards/xtensa/esp32/common/src/esp32_bme680.c new file mode 100644 index 0000000000..644eeee7d0 --- /dev/null +++ b/boards/xtensa/esp32/common/src/esp32_bme680.c @@ -0,0 +1,87 @@ +/**************************************************************************** + * boards/xtensa/esp32/common/src/esp32_bme680.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include +#include +#include + +#include "esp32_board_i2c.h" +#include "esp32_i2c.h" +#include "esp32_ltr308.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: board_bme680_initialize + * + * Description: + * Initialize and register the BME680 Temperature, Pressure, Humidity + * and Gas Resistance sensor. + * + * Input Parameters: + * devno - The device number, used to build the device path as + * /dev/uorb/sensor_gas0 + * busno - The I2C bus number + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int board_bme680_initialize(int devno, int busno) +{ + struct i2c_master_s *i2c; + int ret; + + sninfo("Initializing BME680!\n"); + + /* Initialize BME680 */ + + i2c = esp32_i2cbus_initialize(busno); + if (i2c != NULL) + { + /* Then try to register the gas sensor in one of the two I2C + * available controllers. + */ + + ret = bme680_register(devno, i2c); + if (ret < 0) + { + snerr("ERROR: Error registering BME680 in I2C%d\n", busno); + } + } + else + { + ret = -ENODEV; + } + + return ret; +} diff --git a/boards/xtensa/esp32/esp32-sparrow-kit/configs/nsh/defconfig b/boards/xtensa/esp32/esp32-sparrow-kit/configs/nsh/defconfig index be810647e9..1cec9907f2 100644 --- a/boards/xtensa/esp32/esp32-sparrow-kit/configs/nsh/defconfig +++ b/boards/xtensa/esp32/esp32-sparrow-kit/configs/nsh/defconfig @@ -22,6 +22,7 @@ CONFIG_ARCH_CHIP_ESP32=y CONFIG_ARCH_CHIP_ESP32WROVER=y CONFIG_ARCH_STACKDUMP=y CONFIG_ARCH_XTENSA=y +CONFIG_BME680_ENABLE_IIR_FILTER=y CONFIG_BOARD_LOOPSPERMSEC=16717 CONFIG_BUILTIN=y CONFIG_DRIVERS_VIDEO=y @@ -76,6 +77,7 @@ CONFIG_RGBLED_PWM_FREQ=200 CONFIG_RR_INTERVAL=200 CONFIG_SCHED_WAITPID=y CONFIG_SENSORS=y +CONFIG_SENSORS_BME680=y CONFIG_SENSORS_LTR308=y CONFIG_START_DAY=6 CONFIG_START_MONTH=12 diff --git a/boards/xtensa/esp32/esp32-sparrow-kit/src/esp32_bringup.c b/boards/xtensa/esp32/esp32-sparrow-kit/src/esp32_bringup.c index cf8d36f95c..35a21fdb90 100644 --- a/boards/xtensa/esp32/esp32-sparrow-kit/src/esp32_bringup.c +++ b/boards/xtensa/esp32/esp32-sparrow-kit/src/esp32_bringup.c @@ -84,6 +84,10 @@ # include "esp32_bmp180.h" #endif +#ifdef CONFIG_SENSORS_BME680 +# include "esp32_bme680.h" +#endif + #ifdef CONFIG_SENSORS_LTR308 # include "esp32_ltr308.h" #endif @@ -350,6 +354,17 @@ int esp32_bringup(void) } #endif +#ifdef CONFIG_SENSORS_BME680 + /* Try to register BME680 device in I2C0 */ + + ret = board_bme680_initialize(0, 0); + + if (ret < 0) + { + syslog(LOG_ERR, "Failed to initialize BME680 driver: %d\n", ret); + } +#endif + #ifdef CONFIG_SENSORS_LTR308 /* Try to register LTR308 device in I2C0 */ diff --git a/drivers/sensors/CMakeLists.txt b/drivers/sensors/CMakeLists.txt index f010425cf3..fdf86db521 100644 --- a/drivers/sensors/CMakeLists.txt +++ b/drivers/sensors/CMakeLists.txt @@ -133,6 +133,10 @@ if(CONFIG_SENSORS) list(APPEND SRCS bmp280.c) endif() + if(CONFIG_SENSORS_BME680) + list(APPEND SRCS bme680.c) + endif() + if(CONFIG_SENSORS_ISL29023) list(APPEND SRCS isl29023.c) endif() diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig index b781a804c9..57e0c6f8d2 100644 --- a/drivers/sensors/Kconfig +++ b/drivers/sensors/Kconfig @@ -240,6 +240,63 @@ config BMP280_I2C_FREQUENCY endif # SENSORS_BMP280 +config SENSORS_BME680 + bool "Bosch BME680 Temperature, Gas, Humidity and Pressure Sensor" + default n + select I2C + ---help--- + Enable driver for the Bosch BME680 sensor. + +config BME680_I2C_FREQUENCY + int "BME680 I2C frequency" + depends on SENSORS_BME680 + default 400000 + +config BME680_DISABLE_PRESS_MEAS + bool "Disable Pressure Measurement" + depends on SENSORS_BME680 + default n + ---help--- + If enabled, the sensor will not measure pressure. + +config BME680_DISABLE_HUM_MEAS + bool "Disable Humidity Measurement" + depends on SENSORS_BME680 + default n + ---help--- + If enabled, the sensor will not measure humidity. + +config BME680_DISABLE_GAS_MEAS + bool "Disable Gas Measurement" + depends on SENSORS_BME680 + default n + ---help--- + If enabled, the sensor will not measure gas. + +config BME680_ENABLE_IIR_FILTER + bool "Enable IIR Filter" + depends on SENSORS_BME680 + default n + ---help--- + Enable IIR filtering for temperature and pressure + measurements. + +config SENSORS_BME680_POLL_INTERVAL + int "Polling interval in microseconds, default 3 sec" + depends on SENSORS_BME680 + default 3000000 + range 0 4294967295 + ---help--- + The interval until a new sensor measurement will be triggered. + +config SENSORS_BME680_THREAD_STACKSIZE + int "Worker thread stack size" + depends on SENSORS_BME680 + default 1024 + ---help--- + The stack size for the worker thread. + + config SENSORS_DHTXX bool "DHTxx humidity/temperature Sensor support" default n diff --git a/drivers/sensors/Make.defs b/drivers/sensors/Make.defs index 56f51e75c5..2f94ff7ec6 100644 --- a/drivers/sensors/Make.defs +++ b/drivers/sensors/Make.defs @@ -148,6 +148,10 @@ ifeq ($(CONFIG_SENSORS_BMP280),y) CSRCS += bmp280.c endif +ifeq ($(CONFIG_SENSORS_BME680),y) + CSRCS += bme680.c +endif + ifeq ($(CONFIG_SENSORS_ISL29023),y) CSRCS += isl29023.c endif diff --git a/drivers/sensors/bme680.c b/drivers/sensors/bme680.c new file mode 100644 index 0000000000..7ce081268e --- /dev/null +++ b/drivers/sensors/bme680.c @@ -0,0 +1,1764 @@ +/**************************************************************************** + * drivers/sensors/bme680.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_I2C) && defined(CONFIG_SENSORS_BME680) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define BME680_ADDR 0x76 /* I2C Slave Address */ +#define BME680_FREQ CONFIG_BME680_I2C_FREQUENCY +#define BME680_DEVID 0x61 + +/* Sub-sensor definitions */ + +#ifdef CONFIG_BME680_DISABLE_PRESS_MEAS +#define BME680_TEMP_IDX (0) +#else +#define BME680_TEMP_IDX (-1) +#endif + +#define BME680_TEMP_IDX_OFF BME680_TEMP_IDX + +#ifndef CONFIG_BME680_DISABLE_PRESS_MEAS +# define BME680_PRESS_IDX (BME680_TEMP_IDX_OFF + (1)) +# define BME680_PRESS_IDX_OFF BME680_PRESS_IDX +#else +# define BME680_PRESS_IDX (-1) +# define BME680_PRESS_IDX_OFF BME680_TEMP_IDX_OFF +#endif + +#ifndef CONFIG_BME680_DISABLE_HUM_MEAS +# define BME680_HUM_IDX (BME680_PRESS_IDX_OFF + (1)) +# define BME680_HUM_IDX_OFF BME680_HUM_IDX +#else +# define BME680_HUM_IDX (-1) +# define BME680_HUM_IDX_OFF BME680_PRESS_IDX_OFF +#endif + +#ifndef CONFIG_BME680_DISABLE_GAS_MEAS +# define BME680_GAS_IDX (BME680_HUM_IDX_OFF + (1)) +# define BME680_GAS_IDX_OFF BME680_GAS_IDX +#else +# define BME680_GAS_IDX (-1) +# define BME680_GAS_IDX_OFF BME680_HUM_IDX_OFF +#endif + +#define BME680_SENSORS_COUNT (BME680_GAS_IDX_OFF + (1)) + +/* Register addresses */ + +#define BME680_STATUS_REG_ADDR 0X73 +#define BME680_RESET_REG_ADDR 0xE0 +#define BME680_ID_REG_ADDR 0xD0 + +/* Registers controlling oversampling */ + +#define BME680_CTRL_HUM_ADDR 0x72 +#define BME680_CTRL_MEAS_ADDR 0x74 +#define BME680_CONFIG_REG_ADDR 0x75 + +/* Gas control register */ +#define BME680_CTRL_GAS0 0x70 +#define BME680_CTRL_GAS1 0x71 + +/* Data registers */ + +/* Pressure data */ + +#define BME680_PRESS_MSB 0x1F +#define BME680_PRESS_LSB 0x20 +#define BME680_PRESS_XLSB 0x21 + +/* Temperature data */ + +#define BME680_TEMP_MSB 0x22 +#define BME680_TEMP_LSB 0x23 +#define BME680_TEMP_XLSB 0x24 + +/* Humidity data */ + +#define BME680_HUM_MSB 0x25 +#define BME680_HUM_LSB 0x26 + +/* Gas sensor resistance data */ + +#define BME680_GAS_R_MSB 0x2A +#define BME680_GAS_R_LSB 0x2B + +#define BME680_IDAC_HEAT_ADDR 0x50 +#define BME680_RES_HEAT_ADDR 0x5A +#define BME680_GAS_WAIT_ADDR 0x64 + +/* Status registers */ + +#define BME680_MEAS_STAT0 0x1D + +/* nbconv boundaries */ + +#define BME680_NBCONV_MIN (0) +#define BME680_NBCONV_MAX (9) + +/* Power modes */ + +#define BME680_SLEEP_MODE (0x00) +#define BME680_FORCED_MODE (0x01) + +/* Soft reset, same effect as a power-on reset */ + +#define BME680_SOFT_RESET (0xB6) + +/* Start addresses for coefficient arrays */ + +#define BME680_COEFF_ADDR1 (0x89) +#define BME680_COEFF_ADDR2 (0xE1) + +#define BME680_COEFF_SIZE (41) +#define BME680_COEFF_ADDR1_LEN (25) +#define BME680_COEFF_ADDR2_LEN (16) + +/* Start address for measurements and status regs */ + +#define BME680_DATA_ADDR (0x1D) +#define BME680_DATA_LEN (15) + +/* Gas coefficients */ +#define BME680_RES_HEAT_RANGE_ADDR (0x02) +#define BME680_RES_HEAT_VAL_ADDR (0x00) +#define BME680_RANGE_SW_ERR_ADDR (0x04) + +/* Array index for mapping 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) + +/* Masks for register values */ + +#define BME680_GAS_MEAS_MSK (0x30) +#define BME680_NBCONV_MSK (0X0F) +#define BME680_FILTER_MSK (0X1C) +#define BME680_OST_MSK (0XE0) +#define BME680_OSP_MSK (0X1C) +#define BME680_OSH_MSK (0X07) +#define BME680_HCTRL_MSK (0x08) +#define BME680_RUN_GAS_MSK (0x10) +#define BME680_MODE_MSK (0x03) +#define BME680_RHRANGE_MSK (0x30) +#define BME680_RSERROR_MSK (0xF0) +#define BME680_NEW_DATA_MSK (0x80) +#define BME680_GAS_INDEX_MSK (0x0F) +#define BME680_GAS_RANGE_MSK (0x0F) +#define BME680_GASM_VALID_MSK (0x20) +#define BME680_HEAT_STAB_MSK (0x10) +#define BME680_MEM_PAGE_MSK (0x10) +#define BME680_BIT_H1_DATA_MSK (0x0F) + +/* Bounds for tpg */ + +#define MIN_HOT_PLATE_TEMP (200) /* Celsius */ +#define MAX_HOT_PLATE_TEMP (400) /* Celsius */ + +#define BME680_MAX_OVERFLOW_VAL (0x40000000ULL) + +/* Possible gas range values */ + +const uint32_t const_array1_int[16] = + {(2147483647), (2147483647), (2147483647), (2147483647), + (2147483647), (2126008810), (2147483647), (2130303777), + (2147483647), (2147483647), (2143188679), (2136746228), + (2147483647), (2126008810), (2147483647), (2147483647) + }; + +const uint32_t const_array2_int[16] = + {(4096000000), (2048000000), (1024000000), (512000000), + (255744255), (127110228), (64000000), (32258064), (16016016), + (8000000), (4000000), (2000000), (1000000), (500000), + (250000), (125000) + }; + +const float const_array1[16] = + {1.0, 1.0, 1.0, 1.0, 1.0, 0.99, 1.0, + 0.992, 1.0, 1.0, 0.998, 0.995, 1.0, + 0.99, 1.0, 1.0 + }; + +const float const_array2[16] = + {8000000.0, 4000000.0, 2000000.0, 1000000.0, + 499500.4995, 248262.1648, 125000.0, 63004.03226, + 31281.28128, 15625.0, 7812.5, 3906.25, 1953.125, + 976.5625, 488.28125, 244.140625 + }; + +#define CHECK_OS_BOUNDS(type) \ + (type >= BME680_OS_SKIPPED && type <= BME680_OS_16X) + +/**************************************************************************** + * Private Type Definitions + ****************************************************************************/ + +struct bme680_data_s +{ + uint64_t timestamp; /* Units is microseconds */ + float temperature; /* Temperature in degrees Celsius */ + float pressure; /* Pressure in millibar or hPa */ + float humidity; /* Relative humidity in rH */ + float gas_resistance; /* Gas resistance in Ohm */ +}; + +struct bme680_calib_s +{ + /* Temperature coefficients */ + + uint16_t t1; + int16_t t2; + int8_t t3; + +#ifndef CONFIG_BME680_DISABLE_PRESS_MEAS + /* Pressure coefficients */ + + uint16_t p1; + int16_t p2; + int8_t p3; + int16_t p4; + int16_t p5; + int8_t p6; + int8_t p7; + int16_t p8; + int16_t p9; + uint8_t p10; +#endif /* !CONFIG_BME680_DISABLE_PRESS_MEAS */ + +#ifndef CONFIG_BME680_DISABLE_HUM_MEAS + + /* Humidity coefficients */ + + uint16_t h1; + uint16_t h2; + int8_t h3; + int8_t h4; + int8_t h5; + uint8_t h6; + int8_t h7; +#endif /* !CONFIG_BME680_DISABLE_PRESS_MEAS */ + +#ifndef CONFIG_BME680_DISABLE_GAS_MEAS + /* Gas heater coefficients */ + + int8_t gh1; + int16_t gh2; + int8_t gh3; + + uint8_t res_heat_range; /* Heater resistance range */ + int8_t res_heat_val; /* Heater resistance value */ + int8_t range_sw_err; /* Error switching range */ + +#endif /* !CONFIG_BME680_DISABLE_GAS_MEAS */ + + int32_t t_fine; +}; + +struct bme680_sensor_s +{ + /* Lowerhalfs for every sub-sensor */ + + struct sensor_lowerhalf_s lower[BME680_SENSORS_COUNT]; + struct bme680_calib_s calib; /* Calibration data */ + struct bme680_config_s config; /* Configuration data */ + bool calibrated; /* Is the device set up? */ +}; + +struct bme680_dev_s +{ + struct bme680_sensor_s dev; /* Sensor private data */ + FAR struct i2c_master_s *i2c; /* I2C interface */ + mutex_t dev_lock; /* Manages exclusive access to the device */ + sem_t run; /* Locks sensor thread */ + bool enabled; /* Enable/Disable BME680 */ +}; + +typedef int (*push_data_func)(FAR struct bme680_dev_s *priv, + FAR struct bme680_data_s *data); + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static uint8_t bme680_getreg8(FAR struct bme680_dev_s *priv, + uint8_t regaddr); +static int bme680_putreg8(FAR struct bme680_dev_s *priv, uint8_t regaddr, + uint8_t regval); +static int bme680_getregs(FAR struct bme680_dev_s *priv, uint8_t regaddr, + uint8_t *rxbuffer, uint8_t length); + +#ifndef CONFIG_BME680_DISABLE_PRESS_MEAS +static int bme680_push_press_data(FAR struct bme680_dev_s *priv, + FAR struct bme680_data_s *data); +#else +static int bme680_push_temp_data(FAR struct bme680_dev_s *priv, + FAR struct bme680_data_s *data); +#endif + +#ifndef CONFIG_BME680_DISABLE_HUM_MEAS +static int bme680_push_hum_data(FAR struct bme680_dev_s *priv, + FAR struct bme680_data_s *data); +#endif + +#ifndef CONFIG_BME680_DISABLE_GAS_MEAS +static int bme680_push_gas_data(FAR struct bme680_dev_s *priv, + FAR struct bme680_data_s *data); +#endif + +/* Sensor methods */ + +static int bme680_activate(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + bool enable); +static int bme680_calibrate(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + unsigned long arg); +static int bme680_control(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + int cmd, unsigned long arg); +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const push_data_func deliver_data[BME680_SENSORS_COUNT] = + { +#ifndef CONFIG_BME680_DISABLE_PRESS_MEAS + bme680_push_press_data +#else + bme680_push_temp_data +#endif + +#ifndef CONFIG_BME680_DISABLE_HUM_MEAS + , + bme680_push_hum_data +#endif + +#ifndef CONFIG_BME680_DISABLE_GAS_MEAS + , + bme680_push_gas_data +#endif +}; + +static const struct sensor_ops_s g_sensor_ops = + { + NULL, /* open */ + NULL, /* close */ + bme680_activate, /* activate */ + NULL, /* set_interval */ + NULL, /* batch */ + NULL, /* fetch */ + NULL, /* selftest */ + NULL, /* set_calibvalue */ + bme680_calibrate, /* calibrate */ + bme680_control /* control */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: bme680_getreg8 + * + * Description: + * Read from an 8-bit BME680 register + * + ****************************************************************************/ + +static uint8_t bme680_getreg8(FAR struct bme680_dev_s *priv, uint8_t regaddr) +{ + struct i2c_msg_s msg[2]; + uint8_t regval = 0; + int ret; + + msg[0].frequency = BME680_FREQ; + msg[0].addr = BME680_ADDR; + msg[0].flags = 0; + msg[0].buffer = ®addr; + msg[0].length = 1; + + msg[1].frequency = BME680_FREQ; + msg[1].addr = BME680_ADDR; + msg[1].flags = I2C_M_READ; + msg[1].buffer = ®val; + msg[1].length = 1; + + ret = I2C_TRANSFER(priv->i2c, msg, 2); + if (ret < 0) + { + snerr("I2C_TRANSFER failed: %d\n", ret); + return 0; + } + + return regval; +} + +/**************************************************************************** + * Name: bme680_getregs + * + * Description: + * Read bytes starting from a BME680 register addr + * + ****************************************************************************/ + +static int bme680_getregs(FAR struct bme680_dev_s *priv, uint8_t regaddr, + uint8_t *rxbuffer, uint8_t length) +{ + struct i2c_msg_s msg[2]; + int ret; + + msg[0].frequency = BME680_FREQ; + msg[0].addr = BME680_ADDR; + msg[0].flags = 0; + msg[0].buffer = ®addr; + msg[0].length = 1; + + msg[1].frequency = BME680_FREQ; + msg[1].addr = BME680_ADDR; + msg[1].flags = I2C_M_READ; + msg[1].buffer = rxbuffer; + msg[1].length = length; + + ret = I2C_TRANSFER(priv->i2c, msg, 2); + if (ret < 0) + { + snerr("I2C_TRANSFER failed: %d\n", ret); + return -1; + } + + return OK; +} + +/**************************************************************************** + * Name: bme680_putreg8 + * + * Description: + * Write to an 8-bit BME680 register + * + ****************************************************************************/ + +static int bme680_putreg8(FAR struct bme680_dev_s *priv, uint8_t regaddr, + uint8_t regval) +{ + struct i2c_msg_s msg[2]; + uint8_t txbuffer[2]; + int ret; + + txbuffer[0] = regaddr; + txbuffer[1] = regval; + + msg[0].frequency = BME680_FREQ; + msg[0].addr = BME680_ADDR; + msg[0].flags = 0; + msg[0].buffer = txbuffer; + msg[0].length = 2; + + ret = I2C_TRANSFER(priv->i2c, msg, 1); + if (ret < 0) + { + snerr("I2C_TRANSFER failed: %d\n", ret); + } + + return ret; +} + +/**************************************************************************** + * Name: bme680_checkid + * + * Description: + * Read and verify the BME680 chip ID + * + ****************************************************************************/ + +static int bme680_checkid(FAR struct bme680_dev_s *priv) +{ + uint8_t devid = 0; + + /* Read device ID */ + + devid = bme680_getreg8(priv, BME680_ID_REG_ADDR); + up_mdelay(1); + sninfo("devid: 0x%02x\n", devid); + + if (devid != (uint8_t)BME680_DEVID) + { + /* ID is not Correct */ + + snerr("Wrong Device ID! %02x\n", devid); + return -ENODEV; + } + + return OK; +} + +/**************************************************************************** + * Name: bme680_get_calib_data + * + * Description: + * Read sensor-specific parameters and store them for later + * use in computing the compensated values. + * + ****************************************************************************/ + +static int bme680_get_calib_data(FAR struct bme680_dev_s *priv) +{ + uint8_t coeff[BME680_COEFF_SIZE]; + uint8_t temp_val; + int ret; + + /* Get first part of the calibration data. */ + + ret = bme680_getregs(priv, BME680_COEFF_ADDR1, coeff, + BME680_COEFF_ADDR1_LEN); + if (ret < 0) + { + return ret; + } + + /* Concatenate the second part of the data to coeff */ + + ret = bme680_getregs(priv, BME680_COEFF_ADDR2, + &coeff[BME680_COEFF_ADDR1_LEN], + BME680_COEFF_ADDR2_LEN); + if (ret < 0) + { + return ret; + } + + /* Get data */ + + priv->dev.calib.t1 = coeff[BME680_T1_MSB_REG] << 8 + | coeff[BME680_T1_LSB_REG]; + priv->dev.calib.t2 = coeff[BME680_T2_MSB_REG] << 8 + | coeff[BME680_T2_LSB_REG]; + priv->dev.calib.t3 = coeff[BME680_T3_REG]; + +#ifndef CONFIG_BME680_DISABLE_PRESS_MEAS + priv->dev.calib.p1 = coeff[BME680_P1_MSB_REG] << 8 + | coeff[BME680_P1_LSB_REG]; + priv->dev.calib.p2 = coeff[BME680_P2_MSB_REG] << 8 + | coeff[BME680_P2_LSB_REG]; + priv->dev.calib.p3 = coeff[BME680_P3_REG]; + priv->dev.calib.p4 = coeff[BME680_P4_MSB_REG] << 8 + | coeff[BME680_P4_LSB_REG]; + priv->dev.calib.p5 = coeff[BME680_P5_MSB_REG] << 8 + | coeff[BME680_P5_LSB_REG]; + priv->dev.calib.p6 = coeff[BME680_P6_REG]; + priv->dev.calib.p7 = coeff[BME680_P7_REG]; + priv->dev.calib.p8 = coeff[BME680_P8_MSB_REG] << 8 + | coeff[BME680_P8_LSB_REG]; + priv->dev.calib.p9 = coeff[BME680_P9_MSB_REG] << 8 + | coeff[BME680_P9_LSB_REG]; + priv->dev.calib.p10 = coeff[BME680_P10_REG]; +#endif /* !CONFIG_BME680_DISABLE_PRESS_MEAS */ + +#ifndef CONFIG_BME680_DISABLE_HUM_MEAS + priv->dev.calib.h1 = (uint16_t)(((uint16_t)coeff[BME680_H1_MSB_REG] << 4) + | (coeff[BME680_H1_LSB_REG] & BME680_BIT_H1_DATA_MSK)); + priv->dev.calib.h2 = (uint16_t)(((uint16_t)coeff[BME680_H2_MSB_REG] << 4) + | ((coeff[BME680_H2_LSB_REG]) >> 4)); + priv->dev.calib.h3 = coeff[BME680_H3_REG]; + priv->dev.calib.h4 = coeff[BME680_H4_REG]; + priv->dev.calib.h5 = coeff[BME680_H5_REG]; + priv->dev.calib.h6 = coeff[BME680_H6_REG]; + priv->dev.calib.h7 = coeff[BME680_H7_REG]; +#endif /* !CONFIG_BME680_DISABLE_HUM_MEAS */ + +#ifndef CONFIG_BME680_DISABLE_GAS_MEAS + + /* Gas-related coefficients */ + + priv->dev.calib.gh1 = coeff[BME680_GH1_REG]; + priv->dev.calib.gh2 = coeff[BME680_GH2_MSB_REG] << 8 + | coeff[BME680_GH2_LSB_REG]; + priv->dev.calib.gh3 = coeff[BME680_GH3_REG]; + + ret = bme680_getregs(priv, BME680_RES_HEAT_RANGE_ADDR, &temp_val, 1); + if (ret < 0) + { + return ret; + } + + priv->dev.calib.res_heat_range = ((temp_val & BME680_RHRANGE_MSK)) / 16; + + ret = bme680_getregs(priv, BME680_RES_HEAT_VAL_ADDR, &temp_val, 1); + if (ret < 0) + { + return ret; + } + + priv->dev.calib.res_heat_val = (int8_t)temp_val; + + ret = bme680_getregs(priv, BME680_RANGE_SW_ERR_ADDR, &temp_val, 1); + if (ret < 0) + { + return ret; + } + + priv->dev.calib.range_sw_err = ((int8_t)temp_val + & (int8_t)BME680_RSERROR_MSK) / 16; + +#endif /* !CONFIG_BME680_DISABLE_GAS_MEAS */ + + return ret; +} + +/**************************************************************************** + * Name: bme680_set_mode + * + * Description: + * Set sensor mode and wait for it to change accordingly. + * + ****************************************************************************/ + +static int bme680_set_mode(FAR struct bme680_dev_s *priv, uint8_t mode) +{ + int ret; + uint8_t power_mode; + uint8_t regval; + + /* Get current sensor mode */ + + ret = bme680_getregs(priv, BME680_CTRL_MEAS_ADDR, ®val, 1); + + if (ret < 0) + { + return ret; + } + + power_mode = regval & BME680_MODE_MSK; + + if (power_mode != mode) + { + regval &= (uint8_t)(~BME680_MODE_MSK); + regval |= (mode & BME680_MODE_MSK); + + ret = bme680_putreg8(priv, BME680_CTRL_MEAS_ADDR, regval); + + if (ret < 0) + { + return ret; + } + + ret = bme680_getregs(priv, BME680_CTRL_MEAS_ADDR, ®val, 1); + + /* Check if the mode has changed and wait if it hasn't */ + + while ((regval & BME680_MODE_MSK) != mode) + { + up_mdelay(100); + ret = bme680_getregs(priv, BME680_CTRL_MEAS_ADDR, ®val, 1); + } + } + + return OK; +} + +/**************************************************************************** + * Name: bme680_set_oversamp + * + * Description: + * Set temperature, pressure and humidity oversampling. + * + ****************************************************************************/ + +static int bme680_set_oversamp(FAR struct bme680_dev_s *priv) +{ + struct bme680_config_s config = priv->dev.config; + + int ret; + uint8_t regval; + +#ifndef CONFIG_BME680_DISABLE_HUM_MEAS + /* Set humidity oversampling */ + + regval = config.hum_os & BME680_OSH_MSK; + ret = bme680_putreg8(priv, BME680_CTRL_HUM_ADDR, regval); +#endif + + /* Set temperature and pressure oversampling */ + + regval = 0; + ret = bme680_getregs(priv, BME680_CTRL_MEAS_ADDR, ®val, 1); + + if (ret < 0) + { + return ret; + } + + regval &= BME680_MODE_MSK; + regval |= ((config.temp_os << 5) & BME680_OST_MSK); + +#ifndef CONFIG_BME680_DISABLE_PRESS_MEAS + regval |= ((config.press_os << 2) & BME680_OSP_MSK); +#endif + + ret = bme680_putreg8(priv, BME680_CTRL_MEAS_ADDR, regval); + + if (ret < 0) + { + return ret; + } + + return OK; +} + +#ifndef CONFIG_BME680_DISABLE_PRESS_MEAS +static int bme680_push_press_data(FAR struct bme680_dev_s *priv, + FAR struct bme680_data_s *data) +{ + struct sensor_baro press_data; + int ret; + + struct sensor_lowerhalf_s lower = priv->dev.lower[BME680_PRESS_IDX]; + + press_data.timestamp = data->timestamp; + press_data.temperature = data->temperature; + press_data.pressure = data->pressure / 100.f; + + ret = lower.push_event(lower.priv, &press_data, + sizeof(struct sensor_baro)); + + if (ret < 0) + { + snerr("Pushing baro data failed\n"); + return ret; + } + + return OK; +} +#else +static int bme680_push_temp_data(FAR struct bme680_dev_s *priv, + FAR struct bme680_data_s *data) +{ + struct sensor_temp temp_data; + int ret; + + struct sensor_lowerhalf_s lower = priv->dev.lower[BME680_TEMP_IDX]; + + temp_data.timestamp = data->timestamp; + temp_data.temperature = data->temperature; + + ret = lower.push_event(lower.priv, &temp_data, + sizeof(struct sensor_temp)); + + if (ret < 0) + { + snerr("Pushing temperature data failed\n"); + return ret; + } + + return OK; +} +#endif /* !CONFIG_BME680_DISABLE_PRESS_MEAS */ + +#ifndef CONFIG_BME680_DISABLE_HUM_MEAS +static int bme680_push_hum_data(FAR struct bme680_dev_s *priv, + FAR struct bme680_data_s *data) +{ + struct sensor_humi hum_data; + int ret; + + struct sensor_lowerhalf_s lower = priv->dev.lower[BME680_HUM_IDX]; + + hum_data.timestamp = data->timestamp; + hum_data.humidity = data->humidity; + + ret = lower.push_event(lower.priv, &hum_data, + sizeof(struct sensor_humi)); + + if (ret < 0) + { + snerr("Pushing humidity data failed\n"); + return ret; + } + + return OK; +} +#endif /* !CONFIG_BME680_DISABLE_HUM_MEAS */ + +#ifndef CONFIG_BME680_DISABLE_GAS_MEAS +static int bme680_push_gas_data(FAR struct bme680_dev_s *priv, + FAR struct bme680_data_s *data) +{ + struct sensor_gas gas_data; + int ret; + + struct sensor_lowerhalf_s lower = priv->dev.lower[BME680_GAS_IDX]; + + gas_data.timestamp = data->timestamp; + gas_data.gas_resistance = data->gas_resistance / 1000.f; + + ret = lower.push_event(lower.priv, &gas_data, + sizeof(struct sensor_gas)); + + if (ret < 0) + { + snerr("Pushing gas data failed\n"); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: calc_heater_res + * + * Description: + * Compute the heater resistance using the target temperature. + * + ****************************************************************************/ + +static uint8_t calc_heater_res(const struct bme680_dev_s *priv) +{ + uint8_t res_heat; + int32_t var1; + int32_t var2; + int32_t var3; + int32_t var4; + int32_t var5; + int32_t res_heat_x100; + + int16_t temp; + int16_t amb_temp; + + struct bme680_sensor_s dev = priv->dev; + + temp = dev.config.target_temp; + + if (temp > 400) + temp = 400; + + amb_temp = dev.config.amb_temp; + + var1 = (((int32_t)amb_temp * dev.calib.gh3) / 10) * 256; + var2 = (dev.calib.gh1 + 784) * (((((dev.calib.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; + res_heat_x100 = (int32_t)(((var4 / var5) - 250) * 34); + res_heat = (uint8_t)((res_heat_x100 + 50) / 100); + + return res_heat; +} + +/**************************************************************************** + * Name: calc_heater_dur + * + * Description: + * Compute the heater duration to be written to heat_dur register. + * + ****************************************************************************/ + +static uint8_t calc_heater_dur(const struct bme680_dev_s *priv) +{ + uint16_t heat_dur = priv->dev.config.heater_duration; + uint8_t gas_wait_val; + uint8_t factor; + + /* Max value of duration is 4032 ms */ + + if (heat_dur > 0xfc0) + { + heat_dur = 0xfc0; + } + + /* Compute multiplication factor */ + + factor = 0; + + while (heat_dur > 0x3f) + { + heat_dur = heat_dur / 4; + factor += 1; + } + + gas_wait_val = (factor << 6) | heat_dur; + + return gas_wait_val; +} + +static int bme680_set_gas_config(FAR struct bme680_dev_s *priv) +{ + int ret; + uint8_t heat_res; + uint8_t heat_dur; + uint8_t run_gas; + uint8_t regval; + uint8_t nb_conv = priv->dev.config.nb_conv; + + /* Set heater resistance */ + + heat_res = calc_heater_res(priv); + + ret = bme680_putreg8(priv, (BME680_RES_HEAT_ADDR + nb_conv), heat_res); + + if (ret < 0) + { + return ret; + } + + /* Set heater duration */ + + heat_dur = calc_heater_dur(priv); + + ret = bme680_putreg8(priv, (BME680_GAS_WAIT_ADDR + nb_conv), heat_dur); + + if (ret < 0) + { + return ret; + } + + /* Set nbconv and run_gas */ + + run_gas = priv->dev.config.target_temp ? 1 : 0; + regval = (run_gas << 4) | nb_conv; + + ret = bme680_putreg8(priv, BME680_CTRL_GAS1, regval); + + if (ret < 0) + { + return ret; + } + + return OK; +} +#endif /* !CONFIG_BME680_DISABLE_GAS_MEAS */ + +/**************************************************************************** + * Name: bme680_write_config + * + * Description: + * Write the configuration of the sensor into its registers + * (oversampling, heater temp, heater duration, etc). + * + ****************************************************************************/ + +static int bme680_write_config(FAR struct bme680_dev_s *priv) +{ + int ret; + +#ifdef CONFIG_BME680_ENABLE_IIR_FILTER + uint8_t regval; +#endif /* CONFIG_BME680_ENABLE_IIR_FILTER */ + + nxmutex_lock(&priv->dev_lock); + + /* Before anything is written, make sure it is in sleep mode */ + + ret = bme680_set_mode(priv, BME680_SLEEP_MODE); + + if (ret < 0) + { + goto err_out; + } + + /* Set oversampling */ + + ret = bme680_set_oversamp(priv); + + if (ret < 0) + { + goto err_out; + } + +#ifdef CONFIG_BME680_ENABLE_IIR_FILTER + /* Set filter */ + + regval = priv->dev.config.filter_coef << 2; + ret = bme680_putreg8(priv, BME680_CONFIG_REG_ADDR, regval); + + if (ret < 0) + { + goto err_out; + } +#endif /* CONFIG_ENABLE_IIR_FILTER */ + +#ifndef CONFIG_BME680_DISABLE_GAS_MEAS + /* Set gas configs */ + + ret = bme680_set_gas_config(priv); + + if (ret < 0) + { + goto err_out; + } +#endif /* !CONFIG_BME680_DISABLE_GAS_MEAS */ + + nxmutex_unlock(&priv->dev_lock); + return OK; + +err_out: + snerr("Failed to calibrate sensor.\n"); + nxmutex_unlock(&priv->dev_lock); + return ret; +} + +static float bme680_comp_temp(FAR struct bme680_dev_s *priv, + uint32_t adc_temp) +{ + float var1 = 0; + float var2 = 0; + float calc_temp = 0; + + struct bme680_sensor_s dev = priv->dev; + + var1 = ((((float)adc_temp / 16384.0f) - ((float)dev.calib.t1 / 1024.0f)) + * ((float)dev.calib.t2)); + + var2 = (((((float)adc_temp / 131072.0f) - ((float)dev.calib.t1 / 8192.0f)) + * (((float)adc_temp / 131072.0f) - ((float)dev.calib.t1 / 8192.0f))) + * ((float)dev.calib.t3 * 16.0f)); + + priv->dev.calib.t_fine = (var1 + var2); + + /* Compensated temperature data */ + + calc_temp = ((priv->dev.calib.t_fine) / 5120.0f); + + return calc_temp; +} + +#ifndef CONFIG_BME680_DISABLE_PRESS_MEAS +static float bme680_comp_press(FAR struct bme680_dev_s *priv, + uint32_t adc_press) +{ + float var1 = 0; + float var2 = 0; + float var3 = 0; + float calc_pres = 0; + + struct bme680_sensor_s dev = priv->dev; + + var1 = (((float)dev.calib.t_fine / 2.0f) - 64000.0f); + var2 = var1 * var1 * (((float)dev.calib.p6) / (131072.0f)); + var2 = var2 + (var1 * ((float)dev.calib.p5) * 2.0f); + var2 = (var2 / 4.0f) + (((float)dev.calib.p4) * 65536.0f); + var1 = (((((float)dev.calib.p3 * var1 * var1) / 16384.0f) + + ((float)dev.calib.p2 * var1)) / 524288.0f); + var1 = ((1.0f + (var1 / 32768.0f)) * ((float)dev.calib.p1)); + calc_pres = (1048576.0f - ((float)adc_press)); + + /* 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.p9) * calc_pres * calc_pres) / 2147483648.0f; + var2 = calc_pres * (((float)dev.calib.p8) / 32768.0f); + var3 = ((calc_pres / 256.0f) * (calc_pres / 256.0f) + * (calc_pres / 256.0f) * (dev.calib.p10 / 131072.0f)); + calc_pres = (calc_pres + (var1 + var2 + var3 + + ((float)dev.calib.p7 * 128.0f)) / 16.0f); + } + + return calc_pres; +} +#endif /* !CONFIG_BME680_DISABLE_PRESS_MEAS */ + +#ifndef CONFIG_BME680_DISABLE_HUM_MEAS +static float bme680_comp_hum(FAR struct bme680_dev_s *priv, + uint16_t adc_hum) +{ + float calc_hum = 0; + float var1 = 0; + float var2 = 0; + float var3 = 0; + float var4 = 0; + float temp_comp; + + struct bme680_sensor_s dev = priv->dev; + + /* Compensated temperature data */ + + temp_comp = ((dev.calib.t_fine) / 5120.0f); + + var1 = (float)((float)adc_hum) - (((float)dev.calib.h1 * 16.0f) + + (((float)dev.calib.h3 / 2.0f) * temp_comp)); + + var2 = var1 * ((float)(((float)dev.calib.h2 / 262144.0f) + * (1.0f + (((float)dev.calib.h4 / 16384.0f) * temp_comp) + + (((float)dev.calib.h5 / 1048576.0f) * temp_comp * temp_comp)))); + + var3 = (float)dev.calib.h6 / 16384.0f; + + var4 = (float)dev.calib.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; +} +#endif /* !CONFIG_BME680_DISABLE_HUM_MEAS */ + +#ifndef CONFIG_BME680_DISABLE_GAS_MEAS +static float bme680_calc_gas_res(FAR struct bme680_dev_s *priv, + uint16_t adc_gas_res, uint8_t gas_range) +{ + float calc_gas_res; + float var1 = 0; + + struct bme680_sensor_s dev = priv->dev; + + var1 = (1340.0f + (5.0f * dev.calib.range_sw_err)) + * const_array1[gas_range]; + calc_gas_res = var1 * const_array2[gas_range] + / (adc_gas_res - 512.0f + var1); + + return calc_gas_res; +} +#endif /* !CONFIG_BME680_DISABLE_GAS_MEAS */ + +/**************************************************************************** + * Name: bme680_read_measurements + * + * Description: + * Reads the raw data from the sensor and computes the compensated + * values, storing them in the data struct. + * + ****************************************************************************/ + +static int bme680_read_measurements(FAR struct bme680_dev_s *priv, + FAR struct bme680_data_s *data) +{ + uint8_t status; + uint32_t adc_temp; + +#ifndef CONFIG_BME680_DISABLE_PRESS_MEAS + uint32_t adc_press; +#endif /* !CONFIG_BME680_DISABLE_PRESS_MEAS */ + +#ifndef CONFIG_BME680_DISABLE_HUM_MEAS + uint16_t adc_hum; +#endif /* !CONFIG_BME680_DISABLE_HUM_MEAS */ + +#ifndef CONFIG_BME680_DISABLE_GAS_MEAS + uint16_t adc_gas_res; + uint8_t gas_range; + uint8_t gas_valid; + uint8_t heat_stab; +#endif /* !CONFIG_BME680_DISABLE_GAS_MEAS */ + + int ret; + + uint8_t data_regs[BME680_DATA_LEN]; + + ret = bme680_getregs(priv, BME680_DATA_ADDR, + data_regs, BME680_DATA_LEN); + + if (ret < 0) + { + snerr("Failed to read data registers.\n"); + return ret; + } + + status = data_regs[0] & BME680_NEW_DATA_MSK; + + /* No new data, return */ + + if (!status) + { + sninfo("No new data\n"); + return -ENODATA; + } + + adc_temp = (uint32_t)(((uint32_t)data_regs[5] << 12) + | ((uint32_t)data_regs[6] << 4) + | ((uint32_t)data_regs[7] >> 4)); + + data->temperature = bme680_comp_temp(priv, adc_temp); + +#ifndef CONFIG_BME680_DISABLE_PRESS_MEAS + adc_press = (uint32_t)(((uint32_t)data_regs[2] << 12) + | ((uint32_t)data_regs[3] << 4) + | ((uint32_t)data_regs[4] >> 4)); + + data->pressure = bme680_comp_press(priv, adc_press); +#endif /* !CONFIG_BME680_DISABLE_PRESS_MEAS */ + +#ifndef CONFIG_BME680_DISABLE_HUM_MEAS + adc_hum = (uint16_t)(((uint32_t)data_regs[8] << 8) + | (uint32_t)data_regs[9]); + + data->humidity = bme680_comp_hum(priv, adc_hum); +#endif /* !CONFIG_BME680_DISABLE_HUM_MEAS */ + +#ifndef CONFIG_BME680_DISABLE_GAS_MEAS + adc_gas_res = (uint16_t)((uint32_t)data_regs[13] << 2 + | (((uint32_t)data_regs[14]) >> 6)); + gas_range = data_regs[14] & BME680_GAS_RANGE_MSK; + + /* Is measured gas valid? */ + + gas_valid = data_regs[14] & BME680_GASM_VALID_MSK; + + if (!gas_valid) + { + sninfo("Invalid gas measurement.\n"); + return -1; + } + + heat_stab = data_regs[14] & BME680_HEAT_STAB_MSK; + + if (!heat_stab) + { + sninfo("The heater did not stabilize.\n"); + return -1; + } + + priv->dev.config.amb_temp = data->temperature; /* Update ambient temp */ + + data->gas_resistance = bme680_calc_gas_res(priv, adc_gas_res, gas_range); +#endif /* !CONFIG_BME680_DISABLE_GAS_MEAS */ + + return OK; +} + +/**************************************************************************** + * Name: bme680_get_tphg_dur + * + * Description: + * Compute the duration of a tphg cycle in us, taking into consideration + * the settings of the sensor. + * + ****************************************************************************/ + +static uint16_t bme680_get_tphg_dur(FAR struct bme680_dev_s *priv) +{ + uint32_t tph_dur; /* Calculate in us */ + uint32_t meas_cycles; + uint16_t duration; + uint8_t os_to_meas_cycles[6] = + { + 0, 1, 2, 4, 8, 16 + }; + + meas_cycles = os_to_meas_cycles[priv->dev.config.temp_os]; + +#ifndef CONFIG_BME680_DISABLE_PRESS_MEAS + meas_cycles += os_to_meas_cycles[priv->dev.config.press_os]; +#endif + +#ifndef CONFIG_BME680_DISABLE_HUM_MEAS + meas_cycles += os_to_meas_cycles[priv->dev.config.hum_os]; +#endif + + /* TPH measurement duration */ + + tph_dur = meas_cycles * (1963); + tph_dur += (477 * 4); /* TPH switching duration */ + +#ifndef CONFIG_BME680_DISABLE_GAS_MEAS + tph_dur += (477 * 5); /* Gas measurement duration */ +#endif + + tph_dur += (500); + tph_dur /= (1000); /* Convert to ms */ + + tph_dur += (1); /* Wake up duration of 1ms */ + + duration = (uint16_t)tph_dur; + +#ifndef CONFIG_BME680_DISABLE_GAS_MEAS + /* The remaining time should be used for heating */ + + duration += priv->dev.config.heater_duration; +#endif + + return duration; +} + +static int bme680_activate(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + bool enable) +{ + int offset; + + /* Get offset inside array of lowerhalfs */ + + switch (lower->type) + { + case SENSOR_TYPE_AMBIENT_TEMPERATURE: + offset = BME680_TEMP_IDX; + break; + + case SENSOR_TYPE_BAROMETER: + offset = BME680_PRESS_IDX; + break; + + case SENSOR_TYPE_RELATIVE_HUMIDITY: + offset = BME680_HUM_IDX; + break; + + case SENSOR_TYPE_GAS: + offset = BME680_GAS_IDX; + break; + + default: + offset = 0; + break; + } + + FAR struct bme680_sensor_s *dev = + (FAR struct bme680_sensor_s *) + ((uintptr_t)lower - offset * sizeof(*lower)); + + FAR struct bme680_dev_s *priv = container_of(dev, + FAR struct bme680_dev_s, + dev); + + /* Wake the thread only once (the activate method will be called + * multiple times for the bme680 sub-sensors) + */ + + if (priv->enabled == false && enable == true) + { + dev->calibrated = false; + priv->enabled = enable; + + /* Wake up the polling thread */ + + nxsem_post(&priv->run); + + return OK; + } + + priv->enabled = enable; + + return OK; +} + +static int bme680_calibrate(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + unsigned long arg) +{ + int offset; + + /* Get offset inside array of lowerhalfs */ + + switch (lower->type) + { + case SENSOR_TYPE_AMBIENT_TEMPERATURE: + offset = BME680_TEMP_IDX; + break; + + case SENSOR_TYPE_BAROMETER: + offset = BME680_PRESS_IDX; + break; + + case SENSOR_TYPE_RELATIVE_HUMIDITY: + offset = BME680_HUM_IDX; + break; + + case SENSOR_TYPE_GAS: + offset = BME680_GAS_IDX; + break; + + default: + offset = 0; + break; + } + + FAR struct bme680_sensor_s *dev = + (FAR struct bme680_sensor_s *) + ((uintptr_t)lower - offset * sizeof(*lower)); + + FAR struct bme680_dev_s *priv = container_of(dev, + FAR struct bme680_dev_s, + dev); + FAR struct bme680_config_s *calibval = + (FAR struct bme680_config_s *)arg; + + int ret; + + /* Sanity checks */ + + if (!CHECK_OS_BOUNDS(calibval->temp_os)) + { + return -EINVAL; + } + +#ifndef CONFIG_BME680_DISABLE_PRESS_MEAS + if (!CHECK_OS_BOUNDS(calibval->press_os)) + { + return -EINVAL; + } +#endif + +#ifndef CONFIG_BME680_DISABLE_HUM_MEAS + if (!CHECK_OS_BOUNDS(calibval->hum_os)) + { + return -EINVAL; + } +#endif + +#ifdef CONFIG_BME680_ENABLE_IIR_FILTER + if (calibval->filter_coef < BME680_FILTER_COEF0 || + calibval->filter_coef > BME680_FILTER_COEF127) + { + return -EINVAL; + } +#endif + +#ifndef CONFIG_BME680_DISABLE_GAS_MEAS + if (calibval->target_temp < MIN_HOT_PLATE_TEMP || + calibval->target_temp > MAX_HOT_PLATE_TEMP) + { + return -EINVAL; + } + + if (calibval->nb_conv > 9) + { + return -EINVAL; + } +#endif + + /* Update config in priv */ + + memcpy(&priv->dev.config, calibval, + sizeof(struct bme680_config_s)); + + ret = bme680_write_config(priv); + + if (ret < 0) + { + snerr("Failed to calibrate sensor.\n"); + return ret; + } + + priv->dev.calibrated = true; + + return ret; +} + +static int bme680_control(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + int cmd, unsigned long arg) +{ + FAR struct bme680_sensor_s *dev = container_of(lower, + FAR struct bme680_sensor_s, + lower); + FAR struct bme680_dev_s *priv = container_of(dev, + FAR struct bme680_dev_s, + dev); + int ret; + + switch (cmd) + { + case SNIOC_RESET: + { + /* Perform Soft Reset */ + + uint8_t regval = 0xb6; + ret = bme680_putreg8(priv, BME680_RESET_REG_ADDR, regval); + + if (ret < 0) + { + return ret; + } + + /* Wait for the device to reset */ + + up_mdelay(100); + } + break; + + default: + break; + } + + return OK; +} + +static int bme680_thread(int argc, char **argv) +{ + FAR struct bme680_dev_s *priv = + (FAR struct bme680_dev_s *)((uintptr_t)strtoul(argv[1], NULL, 16)); + struct bme680_data_s data; + int ret; + + while (true) + { + if (priv->enabled == false) + { + /* Wait for the sensor to be enabled */ + + nxsem_wait(&priv->run); + } + + /* No measurements are done unless the sensor is calibrated */ + + if (priv->dev.calibrated == false) + { + sninfo("The sensor is not calibrated!\n"); + goto thread_sleep; + } + + /* Trigger a measurement */ + + ret = bme680_set_mode(priv, BME680_FORCED_MODE); + + /* Wait for the TPHG cycle to complete */ + + uint16_t cycle_duration = bme680_get_tphg_dur(priv); + + up_mdelay(cycle_duration); + + ret = bme680_read_measurements(priv, &data); + + if (ret < 0) + { + goto thread_sleep; + } + + data.timestamp = sensor_get_timestamp(); + + for (int sensor = 0; sensor < BME680_SENSORS_COUNT; sensor++) + { + deliver_data[sensor](priv, &data); + } + + thread_sleep: + nxsig_usleep(CONFIG_SENSORS_BME680_POLL_INTERVAL); + } + + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: bme680_register + * + * Description: + * Register the BME680 character device + * @devno - The user-specified device number, starting from 0 + * @i2c - An instance of the I2C interface to use to communicate with + * BME680 + * + * Return value: + * Zero (OK) on success; a negated errno value on failure + ****************************************************************************/ + +int bme680_register(int devno, FAR struct i2c_master_s *i2c) +{ + FAR struct sensor_lowerhalf_s *lower; + FAR struct bme680_dev_s *priv; + FAR char *argv[2]; + char arg1[32]; + int ret = OK; + + DEBUGASSERT(i2c != NULL); + + /* Initialize the BME680 device structure */ + + priv = (FAR struct bme680_dev_s *)kmm_zalloc(sizeof(struct bme680_dev_s)); + if (priv == NULL) + { + snerr("ERROR: Failed to allocate instance (err = %d)\n", ret); + return -ENOMEM; + } + + priv->i2c = i2c; + priv->enabled = false; + nxmutex_init(&priv->dev_lock); + nxsem_init(&priv->run, 0, 0); + + /* Check Device ID */ + + ret = bme680_checkid(priv); + if (ret < 0) + { + snerr("ERROR: Wrong device ID!\n"); + goto err_init; + } + + /* Get Calibration Data */ + + ret = bme680_get_calib_data(priv); + + if (ret < 0) + { + snerr("Failed to read calib data from bme680:%d\n", ret); + goto err_init; + } + +#ifndef CONFIG_BME680_DISABLE_PRESS_MEAS + /* Register the barometer driver */ + + lower = &priv->dev.lower[BME680_PRESS_IDX]; + lower->ops = &g_sensor_ops; + lower->type = SENSOR_TYPE_BAROMETER; + + ret = sensor_register(lower, devno); + if (ret < 0) + { + snerr("ERROR: Failed to register barometer \ + driver (err = %d)\n", + ret); + goto err_init; + } +#else + /* Register the temperature driver */ + + lower = &priv->dev.lower[BME680_TEMP_IDX]; + lower->ops = &g_sensor_ops; + lower->type = SENSOR_TYPE_AMBIENT_TEMPERATURE; + + ret = sensor_register(lower, devno); + if (ret < 0) + { + snerr("ERROR: Failed to register temperature \ + driver (err = %d)\n", + ret); + goto err_init; + } +#endif + +#ifndef CONFIG_BME680_DISABLE_HUM_MEAS + /* Register the humidity driver */ + + lower = &priv->dev.lower[BME680_HUM_IDX]; + lower->ops = &g_sensor_ops; + lower->type = SENSOR_TYPE_RELATIVE_HUMIDITY; + + ret = sensor_register(lower, devno); + if (ret < 0) + { + snerr("ERROR: Failed to register humidity \ + driver (err = %d)\n", + ret); + goto err_init; + } +#endif + +#ifndef CONFIG_BME680_DISABLE_GAS_MEAS + /* Register the gas driver */ + + lower = &priv->dev.lower[BME680_GAS_IDX]; + lower->ops = &g_sensor_ops; + lower->type = SENSOR_TYPE_GAS; + + ret = sensor_register(lower, devno); + if (ret < 0) + { + snerr("ERROR: Failed to register gas \ + driver (err = %d)\n", + ret); + goto err_init; + } +#endif + + /* Create thread for polling sensor data */ + + snprintf(arg1, 16, "%p", priv); + argv[0] = arg1; + argv[1] = NULL; + ret = kthread_create("bme680_thread", SCHED_PRIORITY_DEFAULT, + CONFIG_SENSORS_BME680_THREAD_STACKSIZE, + bme680_thread, argv); + if (ret < 0) + { + snerr("ERROR: Failed to create poll thread (err = %d)\n", ret); + goto err_register; + } + + sninfo("BME680 driver loaded successfully!\n"); + return OK; + +err_register: + for (int i = 0; i < BME680_SENSORS_COUNT; i++) + { + sensor_unregister(&priv->dev.lower[i], devno); + } + +err_init: + nxsem_destroy(&priv->run); + nxmutex_destroy(&priv->dev_lock); + kmm_free(priv); + return ret; +} + +#endif /* CONFIG_I2C && CONFIG_SENSORS_BME680 */ diff --git a/drivers/sensors/sensor.c b/drivers/sensors/sensor.c index b10e568310..eab7ec94a5 100644 --- a/drivers/sensors/sensor.c +++ b/drivers/sensors/sensor.c @@ -157,6 +157,7 @@ static const struct sensor_info_s g_sensor_info[] = {sizeof(struct sensor_gps_satellite), "gps_satellite"}, {sizeof(struct sensor_wake_gesture), "wake_gesture"}, {sizeof(struct sensor_cap), "cap"}, + {sizeof(struct sensor_gas), "gas"} }; static const struct file_operations g_sensor_fops = diff --git a/include/nuttx/sensors/bme680.h b/include/nuttx/sensors/bme680.h new file mode 100644 index 0000000000..213c199ea8 --- /dev/null +++ b/include/nuttx/sensors/bme680.h @@ -0,0 +1,136 @@ +/**************************************************************************** + * include/nuttx/sensors/bme680.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_SENSORS_BME680_H +#define __INCLUDE_NUTTX_SENSORS_BME680_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#if defined(CONFIG_I2C) && defined(CONFIG_SENSORS_BME680) +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +/* Oversampling settings */ + +#define BME680_OS_SKIPPED (0x00) /* Output set to 0x8000 */ +#define BME680_OS_1X (0x01) +#define BME680_OS_2X (0x02) +#define BME680_OS_4X (0x03) +#define BME680_OS_8X (0x04) +#define BME680_OS_16X (0x05) + +/* IIR filter settings */ + +#define BME680_FILTER_COEF0 (0) +#define BME680_FILTER_COEF1 (1) +#define BME680_FILTER_COEF3 (2) +#define BME680_FILTER_COEF7 (3) +#define BME680_FILTER_COEF15 (4) +#define BME680_FILTER_COEF31 (5) +#define BME680_FILTER_COEF63 (6) +#define BME680_FILTER_COEF127 (7) + +/* Prerequisites: + * + * CONFIG_BME680 + * Enables support for the BME680 driver + */ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct i2c_master_s; + +struct bme680_config_s +{ + /* Oversampling settings */ + + uint8_t temp_os; + +#ifndef CONFIG_BME680_DISABLE_PRESS_MEAS + uint8_t press_os; +#endif + +#ifndef CONFIG_BME680_DISABLE_HUM_MEAS + uint8_t hum_os; +#endif + +#ifdef CONFIG_BME680_ENABLE_IIR_FILTER + /* Filter coefficient */ + + uint8_t filter_coef; +#endif + +#ifndef CONFIG_BME680_DISABLE_GAS_MEAS + /* Gas settings */ + + int16_t target_temp; /* degrees Celsius */ + uint16_t heater_duration; /* ms */ + uint8_t nb_conv; + int16_t amb_temp; /* degrees Celsius */ +#endif +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: bme680_register + * + * Description: + * Register the BME680 character device + * + * Input Parameters: + * devno - Instance number for driver + * i2c - An instance of the I2C interface to use to communicate with + * BME680 + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int bme680_register(int devno, FAR struct i2c_master_s *i2c); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_I2C && CONFIG_SENSORS_BME680 */ +#endif /* __INCLUDE_NUTTX_BME680_H */ diff --git a/include/nuttx/sensors/sensor.h b/include/nuttx/sensors/sensor.h index e550166797..e0731e615d 100644 --- a/include/nuttx/sensors/sensor.h +++ b/include/nuttx/sensors/sensor.h @@ -290,9 +290,16 @@ #define SENSOR_TYPE_CAP 32 +/* Gas sensor + * This sensor measures the gas resistance, indicating the presence + * of volatile organic compounds in the air. + */ + +#define SENSOR_TYPE_GAS 33 + /* The total number of sensor */ -#define SENSOR_TYPE_COUNT 33 +#define SENSOR_TYPE_COUNT 34 /* The additional sensor open flags */ @@ -595,6 +602,12 @@ struct sensor_cap /* Type: Capacitance */ int32_t rawdata[4]; /* in SI units pF */ }; +struct sensor_gas /* Type: Gas */ +{ + uint64_t timestamp; /* Units is microseconds */ + float gas_resistance; /* Gas resistance in kOhm */ +}; + /* The sensor lower half driver interface */ struct sensor_lowerhalf_s;