xiaoxiang781216 commented on code in PR #9916: URL: https://github.com/apache/nuttx/pull/9916#discussion_r1278305221
########## 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 <nuttx/config.h> +#include <nuttx/nuttx.h> + +#include <stdio.h> +#include <stdlib.h> +#include <fixedmath.h> +#include <errno.h> +#include <debug.h> +#include <string.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/kthread.h> +#include <nuttx/signal.h> +#include <nuttx/fs/fs.h> +#include <nuttx/i2c/i2c_master.h> +#include <nuttx/sensors/bme680.h> +#include <nuttx/sensors/sensor.h> + +#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 <length> 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, 0)); + 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, "0x%" PRIxPTR, (uintptr_t)priv); Review Comment: ```suggestion snprintf(arg1, 16, "%p", priv); ``` like the patch: https://github.com/apache/nuttx/pull/9887 ########## 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 <nuttx/config.h> +#include <nuttx/nuttx.h> + +#include <stdio.h> +#include <stdlib.h> +#include <fixedmath.h> +#include <errno.h> +#include <debug.h> +#include <string.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/kthread.h> +#include <nuttx/signal.h> +#include <nuttx/fs/fs.h> +#include <nuttx/i2c/i2c_master.h> +#include <nuttx/sensors/bme680.h> +#include <nuttx/sensors/sensor.h> + +#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 <length> 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, 0)); Review Comment: ```suggestion (FAR struct bme680_dev_s *)((uintptr_t)strtoul(argv[1], NULL, 16)); ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
