diff --git a/app/include/user_modules.h b/app/include/user_modules.h index d1d672c8..456cb5cf 100644 --- a/app/include/user_modules.h +++ b/app/include/user_modules.h @@ -25,6 +25,7 @@ #define LUA_USE_MODULES_BIT //#define LUA_USE_MODULES_BMP085 //#define LUA_USE_MODULES_BME280 +//#define LUA_USE_MODULES_BME680 //#define LUA_USE_MODULES_COAP //#define LUA_USE_MODULES_CRON //#define LUA_USE_MODULES_CRYPTO diff --git a/app/modules/bme680.c b/app/modules/bme680.c new file mode 100644 index 00000000..e90a3bda --- /dev/null +++ b/app/modules/bme680.c @@ -0,0 +1,547 @@ +// *************************************************************************** +// Port of BMP680 module for ESP8266 with nodeMCU +// +// Written by Lukas Voborsky, @voborsky +// *************************************************************************** + +// #define NODE_DEBUG + +#include "module.h" +#include "lauxlib.h" +#include "platform.h" +#include "c_math.h" + +#include "bme680_defs.h" + +#define DEFAULT_HEATER_DUR 100 +#define DEFAULT_HEATER_TEMP 300 +#define DEFAULT_AMBIENT_TEMP 23 + +static const uint32_t bme680_i2c_id = BME680_CHIP_ID_ADDR; + +static uint8_t bme680_i2c_addr = BME680_I2C_ADDR_PRIMARY; +os_timer_t bme680_timer; // timer for forced mode readout +int lua_connected_readout_ref; // callback when readout is ready + +static struct bme680_calib_data bme680_data; +static uint8_t bme680_mode = 0; // stores oversampling settings +static uint8 os_temp = 0; +static uint8 os_pres = 0; +static uint8 os_hum = 0; // stores humidity oversampling settings +static uint16_t heatr_dur; +static int8_t amb_temp = 23; //DEFAULT_AMBIENT_TEMP; + +static uint32_t bme680_h = 0; +static double bme680_hc = 1.0; + +// return 0 if good +static int r8u_n(uint8_t reg, int n, uint8_t *buff) { + int i; + + platform_i2c_send_start(bme680_i2c_id); + platform_i2c_send_address(bme680_i2c_id, bme680_i2c_addr, PLATFORM_I2C_DIRECTION_TRANSMITTER); + platform_i2c_send_byte(bme680_i2c_id, reg); +// platform_i2c_send_stop(bme680_i2c_id); // doco says not needed + + platform_i2c_send_start(bme680_i2c_id); + platform_i2c_send_address(bme680_i2c_id, bme680_i2c_addr, PLATFORM_I2C_DIRECTION_RECEIVER); + + while (n-- > 0) + *buff++ = platform_i2c_recv_byte(bme680_i2c_id, n > 0); + platform_i2c_send_stop(bme680_i2c_id); + + return 0; +} + +static uint8_t w8u(uint8_t reg, uint8_t val) { + platform_i2c_send_start(bme680_i2c_id); + platform_i2c_send_address(bme680_i2c_id, bme680_i2c_addr, PLATFORM_I2C_DIRECTION_TRANSMITTER); + platform_i2c_send_byte(bme680_i2c_id, reg); + platform_i2c_send_byte(bme680_i2c_id, val); + platform_i2c_send_stop(bme680_i2c_id); +} + +static uint8_t r8u(uint8_t reg) { + uint8_t ret[1]; + r8u_n(reg, 1, ret); + return ret[0]; +} + +/* This part of code is coming from the original bme680.c driver by Bosch. + * Copyright (C) 2017 - 2018 Bosch Sensortec GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of the copyright holder nor the names of the + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + * + * The information provided is believed to be accurate and reliable. + * The copyright holder assumes no responsibility + * for the consequences of use + * of such information nor for any infringement of patents or + * other rights of third parties which may result from its use. + * No license is granted by implication or otherwise under any patent or + * patent rights of the copyright holder. + */ + +/**static variables */ +/**Look up table for the possible gas range values */ +uint32_t lookupTable1[16] = { UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2147483647), + UINT32_C(2147483647), UINT32_C(2126008810), UINT32_C(2147483647), UINT32_C(2130303777), UINT32_C(2147483647), + UINT32_C(2147483647), UINT32_C(2143188679), UINT32_C(2136746228), UINT32_C(2147483647), UINT32_C(2126008810), + UINT32_C(2147483647), UINT32_C(2147483647) }; +/**Look up table for the possible gas range values */ +uint32_t lookupTable2[16] = { UINT32_C(4096000000), UINT32_C(2048000000), UINT32_C(1024000000), UINT32_C(512000000), + UINT32_C(255744255), UINT32_C(127110228), UINT32_C(64000000), UINT32_C(32258064), UINT32_C(16016016), UINT32_C( + 8000000), UINT32_C(4000000), UINT32_C(2000000), UINT32_C(1000000), UINT32_C(500000), UINT32_C(250000), + UINT32_C(125000) }; + +static uint8_t calc_heater_res(uint16_t temp) +{ + uint8_t heatr_res; + int32_t var1; + int32_t var2; + int32_t var3; + int32_t var4; + int32_t var5; + int32_t heatr_res_x100; + + if (temp < 200) /* Cap temperature */ + temp = 200; + else if (temp > 400) + temp = 400; + + var1 = (((int32_t) amb_temp * bme680_data.par_gh3) / 1000) * 256; + var2 = (bme680_data.par_gh1 + 784) * (((((bme680_data.par_gh2 + 154009) * temp * 5) / 100) + 3276800) / 10); + var3 = var1 + (var2 / 2); + var4 = (var3 / (bme680_data.res_heat_range + 4)); + var5 = (131 * bme680_data.res_heat_val) + 65536; + heatr_res_x100 = (int32_t) (((var4 / var5) - 250) * 34); + heatr_res = (uint8_t) ((heatr_res_x100 + 50) / 100); + + return heatr_res; +} + +static uint8_t calc_heater_dur(uint16_t dur) +{ + uint8_t factor = 0; + uint8_t durval; + + if (dur >= 0xfc0) { + durval = 0xff; /* Max duration*/ + } else { + while (dur > 0x3F) { + dur = dur / 4; + factor += 1; + } + durval = (uint8_t) (dur + (factor * 64)); + } + + return durval; +} + +static int16_t calc_temperature(uint32_t temp_adc) +{ + int64_t var1; + int64_t var2; + int64_t var3; + int16_t calc_temp; + + var1 = ((int32_t) temp_adc / 8) - ((int32_t) bme680_data.par_t1 * 2); + var2 = (var1 * (int32_t) bme680_data.par_t2) / 2048; + var3 = ((var1 / 2) * (var1 / 2)) / 4096; + var3 = ((var3) * ((int32_t) bme680_data.par_t3 * 16)) / 16384; + bme680_data.t_fine = (int32_t) (var2 + var3); + calc_temp = (int16_t) (((bme680_data.t_fine * 5) + 128) / 256); + + return calc_temp; +} + +static uint32_t calc_pressure(uint32_t pres_adc) +{ + int32_t var1; + int32_t var2; + int32_t var3; + int32_t calc_pres; + + var1 = (((int32_t) bme680_data.t_fine) / 2) - 64000; + var2 = ((var1 / 4) * (var1 / 4)) / 2048; + var2 = ((var2) * (int32_t) bme680_data.par_p6) / 4; + var2 = var2 + ((var1 * (int32_t) bme680_data.par_p5) * 2); + var2 = (var2 / 4) + ((int32_t) bme680_data.par_p4 * 65536); + var1 = ((var1 / 4) * (var1 / 4)) / 8192; + var1 = (((var1) * ((int32_t) bme680_data.par_p3 * 32)) / 8) + (((int32_t) bme680_data.par_p2 * var1) / 2); + var1 = var1 / 262144; + var1 = ((32768 + var1) * (int32_t) bme680_data.par_p1) / 32768; + calc_pres = (int32_t) (1048576 - pres_adc); + calc_pres = (int32_t) ((calc_pres - (var2 / 4096)) * (3125)); + calc_pres = ((calc_pres / var1) * 2); + var1 = ((int32_t) bme680_data.par_p9 * (int32_t) (((calc_pres / 8) * (calc_pres / 8)) / 8192)) / 4096; + var2 = ((int32_t) (calc_pres / 4) * (int32_t) bme680_data.par_p8) / 8192; + var3 = ((int32_t) (calc_pres / 256) * (int32_t) (calc_pres / 256) * (int32_t) (calc_pres / 256) + * (int32_t) bme680_data.par_p10) / 131072; + calc_pres = (int32_t) (calc_pres) + ((var1 + var2 + var3 + ((int32_t) bme680_data.par_p7 * 128)) / 16); + + return (uint32_t) calc_pres; +} + +static uint32_t calc_humidity(uint16_t hum_adc) +{ + int32_t var1; + int32_t var2; + int32_t var3; + int32_t var4; + int32_t var5; + int32_t var6; + int32_t temp_scaled; + int32_t calc_hum; + + temp_scaled = (((int32_t) bme680_data.t_fine * 5) + 128) / 256; + var1 = (int32_t) (hum_adc - ((int32_t) ((int32_t) bme680_data.par_h1 * 16))) + - (((temp_scaled * (int32_t) bme680_data.par_h3) / ((int32_t) 100)) / 2); + var2 = ((int32_t) bme680_data.par_h2 + * (((temp_scaled * (int32_t) bme680_data.par_h4) / ((int32_t) 100)) + + (((temp_scaled * ((temp_scaled * (int32_t) bme680_data.par_h5) / ((int32_t) 100))) / 64) + / ((int32_t) 100)) + (int32_t) (1 * 16384))) / 1024; + var3 = var1 * var2; + var4 = (int32_t) bme680_data.par_h6 * 128; + var4 = ((var4) + ((temp_scaled * (int32_t) bme680_data.par_h7) / ((int32_t) 100))) / 16; + var5 = ((var3 / 16384) * (var3 / 16384)) / 1024; + var6 = (var4 * var5) / 2; + calc_hum = (((var3 + var6) / 1024) * ((int32_t) 1000)) / 4096; + + if (calc_hum > 100000) /* Cap at 100%rH */ + calc_hum = 100000; + else if (calc_hum < 0) + calc_hum = 0; + + return (uint32_t) calc_hum; +} + +static uint32_t calc_gas_resistance(uint16_t gas_res_adc, uint8_t gas_range) +{ + int64_t var1; + uint64_t var2; + int64_t var3; + uint32_t calc_gas_res; + + var1 = (int64_t) ((1340 + (5 * (int64_t) bme680_data.range_sw_err)) * ((int64_t) lookupTable1[gas_range])) / 65536; + var2 = (((int64_t) ((int64_t) gas_res_adc * 32768) - (int64_t) (16777216)) + var1); + var3 = (((int64_t) lookupTable2[gas_range] * (int64_t) var1) / 512); + calc_gas_res = (uint32_t) ((var3 + ((int64_t) var2 / 2)) / (int64_t) var2); + + return calc_gas_res; +} + +uint16_t calc_dur() +{ + uint32_t tph_dur; /* Calculate in us */ + + /* TPH measurement duration */ + + tph_dur = ((uint32_t) (os_temp + os_pres + os_hum) * UINT32_C(1963)); + tph_dur += UINT32_C(477 * 4); /* TPH switching duration */ + tph_dur += UINT32_C(477 * 5); /* Gas measurement duration */ + tph_dur += UINT32_C(500); /* Get it to the closest whole number.*/ + tph_dur /= UINT32_C(1000); /* Convert to ms */ + + tph_dur += UINT32_C(1); /* Wake up duration of 1ms */ + NODE_DBG("tpc_dur: %d\n", tph_dur); + /* The remaining time should be used for heating */ + return heatr_dur + (uint16_t) tph_dur; +} +/* This part of code is coming from the original bme680.c driver by Bosch. + * END */ + + +static double ln(double x) { + double y = (x-1)/(x+1); + double y2 = y*y; + double r = 0; + for (int8_t i=33; i>0; i-=2) { //we've got the power + r = 1.0/(double)i + y2 * r; + } + return 2*y*r; +} + +static double bme280_qfe2qnh(int32_t qfe, int32_t h) { + double hc; + if (bme680_h == h) { + hc = bme680_hc; + } else { + hc = pow((double)(1.0 - 2.25577e-5 * h), (double)(-5.25588)); + bme680_hc = hc; bme680_h = h; + } + double qnh = (double)qfe * hc; + return qnh; +} + +static int bme680_lua_setup(lua_State* L) { + uint8_t ack; + + bme680_i2c_addr = BME680_I2C_ADDR_PRIMARY; + platform_i2c_send_start(bme680_i2c_id); + ack = platform_i2c_send_address(bme680_i2c_id, bme680_i2c_addr, PLATFORM_I2C_DIRECTION_TRANSMITTER); + platform_i2c_send_stop(bme680_i2c_id); + if (!ack) { + NODE_DBG("No ACK on address: %x\n", bme680_i2c_addr); + bme680_i2c_addr = BME680_I2C_ADDR_SECONDARY; + platform_i2c_send_start(bme680_i2c_id); + ack = platform_i2c_send_address(bme680_i2c_id, bme680_i2c_addr, PLATFORM_I2C_DIRECTION_TRANSMITTER); + platform_i2c_send_stop(bme680_i2c_id); + if (!ack) { + NODE_DBG("No ACK on address: %x\n", bme680_i2c_addr); + return 0; + } + } + + uint8_t chipid = r8u(BME680_CHIP_ID_ADDR); + NODE_DBG("chip_id: %x\n", chipid); + +#define r16uLE_buf(reg) (uint16_t)(((uint16_t)reg[1] << 8) | (uint16_t)reg[0]) +#define r16sLE_buf(reg) (int16_t)(r16uLE_buf(reg)) + uint8_t buff[BME680_COEFF_SIZE], *reg; + r8u_n(BME680_COEFF_ADDR1, BME680_COEFF_ADDR1_LEN, buff); + r8u_n(BME680_COEFF_ADDR2, BME680_COEFF_ADDR2_LEN, &buff[BME680_COEFF_ADDR1_LEN]); + + reg = buff + 1; + bme680_data.par_t2 = r16sLE_buf(reg); reg+=2; // #define BME680_T3_REG (3) + bme680_data.par_t3 = (int8_t) reg[0]; reg+=2; // #define BME680_P1_LSB_REG (5) + bme680_data.par_p1 = r16uLE_buf(reg); reg+=2; // #define BME680_P2_LSB_REG (7) + bme680_data.par_p2 = r16sLE_buf(reg); reg+=2; // #define BME680_P3_REG (9) + bme680_data.par_p3 = (int8_t) reg[0]; reg+=2; // #define BME680_P4_LSB_REG (11) + bme680_data.par_p4 = r16sLE_buf(reg); reg+=2; // #define BME680_P5_LSB_REG (13) + bme680_data.par_p5 = r16sLE_buf(reg); reg+=2; // #define BME680_P7_REG (15) + bme680_data.par_p7 = (int8_t) reg[0]; reg++; // #define BME680_P6_REG (16) + bme680_data.par_p6 = (int8_t) reg[0]; reg+=3; // #define BME680_P8_LSB_REG (19) + bme680_data.par_p8 = r16sLE_buf(reg); reg+=2; // #define BME680_P9_LSB_REG (21) + bme680_data.par_p9 = r16sLE_buf(reg); reg+=2; // #define BME680_P10_REG (23) + bme680_data.par_p10 = (int8_t) reg[0]; reg+=2; // #define BME680_H2_MSB_REG (25) + bme680_data.par_h2 = (uint16_t) (((uint16_t) reg[0] << BME680_HUM_REG_SHIFT_VAL) + | ((reg[1]) >> BME680_HUM_REG_SHIFT_VAL)); reg++; // #define BME680_H1_LSB_REG (26) + bme680_data.par_h1 = (uint16_t) (((uint16_t) reg[1] << BME680_HUM_REG_SHIFT_VAL) + | (reg[0] & BME680_BIT_H1_DATA_MSK)); reg+=2; // #define BME680_H3_REG (28) + bme680_data.par_h3 = (int8_t) reg[0]; reg++; // #define BME680_H4_REG (29) + bme680_data.par_h4 = (int8_t) reg[0]; reg++; // #define BME680_H5_REG (30) + bme680_data.par_h5 = (int8_t) reg[0]; reg++; // #define BME680_H6_REG (31) + bme680_data.par_h6 = (uint8_t) reg[0]; reg++; // #define BME680_H7_REG (32) + bme680_data.par_h7 = (int8_t) reg[0]; reg++; // #define BME680_T1_LSB_REG (33) + bme680_data.par_t1 = r16uLE_buf(reg); reg+=2; // #define BME680_GH2_LSB_REG (35) + bme680_data.par_gh2 = r16sLE_buf(reg); reg+=2; // #define BME680_GH1_REG (37) + bme680_data.par_gh1 = reg[0]; reg++; // #define BME680_GH3_REG (38) + bme680_data.par_gh3 = reg[0]; +#undef r16uLE_buf +#undef r16sLE_buf + + /* Other coefficients */ + bme680_data.res_heat_range = ((r8u(BME680_ADDR_RES_HEAT_RANGE_ADDR) & BME680_RHRANGE_MSK) / 16); + bme680_data.res_heat_val = (int8_t) r8u(BME680_ADDR_RES_HEAT_VAL_ADDR); + bme680_data.range_sw_err = ((int8_t) r8u(BME680_ADDR_RANGE_SW_ERR_ADDR) & (int8_t) BME680_RSERROR_MSK) / 16; + + NODE_DBG("par_T: %d\t%d\t%d\n", bme680_data.par_t1, bme680_data.par_t2, bme680_data.par_t3); + NODE_DBG("par_P: %d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", bme680_data.par_p1, bme680_data.par_p2, bme680_data.par_p3, bme680_data.par_p4, bme680_data.par_p5, bme680_data.par_p6, bme680_data.par_p7, bme680_data.par_p8, bme680_data.par_p9, bme680_data.par_p10); + NODE_DBG("par_H: %d\t%d\t%d\t%d\t%d\t%d\t%d\n", bme680_data.par_h1, bme680_data.par_h2, bme680_data.par_h3, bme680_data.par_h4, bme680_data.par_h5, bme680_data.par_h6, bme680_data.par_h7); + NODE_DBG("par_GH: %d\t%d\t%d\n", bme680_data.par_gh1, bme680_data.par_gh2, bme680_data.par_gh3); + NODE_DBG("res_heat_range, res_heat_val, range_sw_err: %d\t%d\t%d\n", bme680_data.res_heat_range, bme680_data.res_heat_val, bme680_data.range_sw_err); + + uint8_t full_init = !lua_isnumber(L, 7)?1:lua_tointeger(L, 7); // 7-th parameter: init the chip too + if (full_init) { + uint8_t filter; + uint8_t const bit3 = 0b111; + uint8_t const bit2 = 0b11; + + //bme680.setup([temp_oss, press_oss, humi_oss, heater_temp, heater_duration, IIR_filter]) + + os_temp = (!lua_isnumber(L, 1)?BME680_OS_2X:(luaL_checkinteger(L, 1)&bit3)); // 1-st parameter: temperature oversampling + os_pres = (!lua_isnumber(L, 2)?BME680_OS_16X:(luaL_checkinteger(L, 2)&bit3)); // 2-nd parameter: pressure oversampling + os_hum = (!lua_isnumber(L, 3))?BME680_OS_1X:(luaL_checkinteger(L, 3)&bit3); + bme680_mode = BME680_SLEEP_MODE | (os_pres << 2) | (os_temp << 5); + os_hum = os_hum; // 3-rd parameter: humidity oversampling + + filter = ((!lua_isnumber(L, 6)?BME680_FILTER_SIZE_31:(luaL_checkinteger(L, 6)&bit3)) << 2); // 6-th parameter: IIR filter + + NODE_DBG("mode: %x\nhumidity oss: %x\nconfig: %x\n", bme680_mode, os_hum, filter); + + heatr_dur = (!lua_isnumber(L, 5)?DEFAULT_HEATER_DUR:(luaL_checkinteger(L, 5))); // 5-th parameter: heater duration + w8u(BME680_GAS_WAIT0_ADDR, calc_heater_dur(heatr_dur)); + w8u(BME680_RES_HEAT0_ADDR, calc_heater_res((!lua_isnumber(L, 4)?DEFAULT_HEATER_TEMP:(luaL_checkinteger(L, 4))))); // 4-th parameter: heater temperature + + w8u(BME680_CONF_ODR_FILT_ADDR, BME680_SET_BITS_POS_0(r8u(BME680_CONF_ODR_FILT_ADDR), BME680_FILTER, filter)); // #define BME680_CONF_ODR_FILT_ADDR UINT8_C(0x75) + + // set heater on + w8u(BME680_CONF_HEAT_CTRL_ADDR, BME680_SET_BITS_POS_0(r8u(BME680_CONF_HEAT_CTRL_ADDR), BME680_HCTRL, 1)); + + w8u(BME680_CONF_T_P_MODE_ADDR, bme680_mode); + w8u(BME680_CONF_OS_H_ADDR, BME680_SET_BITS_POS_0(r8u(BME680_CONF_OS_H_ADDR), BME680_OSH, os_hum)); + w8u(BME680_CONF_ODR_RUN_GAS_NBC_ADDR, 1 << 4 | 0 & bit3); + } + lua_pushinteger(L, 1); + + return 1; +} + +static void bme280_readoutdone (void *arg) +{ + NODE_DBG("timer out\n"); + lua_State *L = lua_getstate(); + lua_rawgeti (L, LUA_REGISTRYINDEX, lua_connected_readout_ref); + lua_call (L, 0, 0); + luaL_unref (L, LUA_REGISTRYINDEX, lua_connected_readout_ref); + os_timer_disarm (&bme680_timer); +} + +static int bme680_lua_startreadout(lua_State* L) { + uint32_t delay; + + if (lua_isnumber(L, 1)) { + delay = luaL_checkinteger(L, 1); + if (!delay) {delay = calc_dur();} // if delay is 0 then set the default delay + } + + if (!lua_isnoneornil(L, 2)) { + lua_pushvalue(L, 2); + lua_connected_readout_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + lua_connected_readout_ref = LUA_NOREF; + } + + w8u(BME680_CONF_OS_H_ADDR, os_hum); + w8u(BME680_CONF_T_P_MODE_ADDR, (bme680_mode & 0xFC) | BME680_FORCED_MODE); + + NODE_DBG("control old: %x, control: %x, delay: %d\n", bme680_mode, (bme680_mode & 0xFC) | BME680_FORCED_MODE, delay); + + if (lua_connected_readout_ref != LUA_NOREF) { + NODE_DBG("timer armed\n"); + os_timer_disarm (&bme680_timer); + os_timer_setfn (&bme680_timer, (os_timer_func_t *)bme280_readoutdone, L); + os_timer_arm (&bme680_timer, delay, 0); // trigger callback when readout is ready + } + return 0; +} + +// Return nothing on failure +// Return T, QFE, H if no altitude given +// Return T, QFE, H, QNH if altitude given +static int bme680_lua_read(lua_State* L) { + uint8_t buff[BME680_FIELD_LENGTH] = { 0 }; + uint8_t gas_range; + uint32_t adc_temp; + uint32_t adc_pres; + uint16_t adc_hum; + uint16_t adc_gas_res; + uint8_t status; + + uint32_t qfe; + uint8_t calc_qnh = lua_isnumber(L, 1); + + r8u_n(BME680_FIELD0_ADDR, BME680_FIELD_LENGTH, buff); + + status = buff[0] & BME680_NEW_DATA_MSK; + + /* read the raw data from the sensor */ + adc_pres = (uint32_t) (((uint32_t) buff[2] * 4096) | ((uint32_t) buff[3] * 16) | ((uint32_t) buff[4] / 16)); + adc_temp = (uint32_t) (((uint32_t) buff[5] * 4096) | ((uint32_t) buff[6] * 16) | ((uint32_t) buff[7] / 16)); + adc_hum = (uint16_t) (((uint32_t) buff[8] * 256) | (uint32_t) buff[9]); + adc_gas_res = (uint16_t) ((uint32_t) buff[13] * 4 | (((uint32_t) buff[14]) / 64)); + + gas_range = buff[14] & BME680_GAS_RANGE_MSK; + + status |= buff[14] & BME680_GASM_VALID_MSK; + status |= buff[14] & BME680_HEAT_STAB_MSK; + NODE_DBG("status, new_data, gas_range, gasm_valid: 0x%x, 0x%x, 0x%x, 0x%x\n", status, status & BME680_NEW_DATA_MSK, buff[14] & BME680_GAS_RANGE_MSK, buff[14] & BME680_GASM_VALID_MSK); + if (!(status & BME680_NEW_DATA_MSK)) { + return 0; + } + + int16_t temp = calc_temperature(adc_temp); + amb_temp = temp / 100; + lua_pushinteger(L, temp); + qfe = calc_pressure(adc_pres); + lua_pushinteger(L, qfe); + lua_pushinteger(L, calc_humidity(adc_hum)); + lua_pushinteger(L, calc_gas_resistance(adc_gas_res, gas_range)); + + if (calc_qnh) { // have altitude + int32_t h = luaL_checkinteger(L, 1); + double qnh = bme280_qfe2qnh(qfe, h); + lua_pushinteger(L, (int32_t)(qnh + 0.5)); + return 5; + } + return 4; +} + +static int bme680_lua_qfe2qnh(lua_State* L) { + if (!lua_isnumber(L, 2)) { + return luaL_error(L, "wrong arg range"); + } + int32_t qfe = luaL_checkinteger(L, 1); + int32_t h = luaL_checkinteger(L, 2); + double qnh = bme280_qfe2qnh(qfe, h); + lua_pushinteger(L, (int32_t)(qnh + 0.5)); + return 1; +} + +static int bme680_lua_altitude(lua_State* L) { + if (!lua_isnumber(L, 2)) { + return luaL_error(L, "wrong arg range"); + } + int32_t P = luaL_checkinteger(L, 1); + int32_t qnh = luaL_checkinteger(L, 2); + double h = (1.0 - pow((double)P/(double)qnh, 1.0/5.25588)) / 2.25577e-5 * 100.0; + + lua_pushinteger(L, (int32_t)(h + (((h<0)?-1:(h>0)) * 0.5))); + return 1; +} + +static int bme680_lua_dewpoint(lua_State* L) { + if (!lua_isnumber(L, 2)) { + return luaL_error(L, "wrong arg range"); + } + double H = luaL_checkinteger(L, 1)/100000.0; + double T = luaL_checkinteger(L, 2)/100.0; + + const double c243 = 243.5; + const double c17 = 17.67; + double c = ln(H) + ((c17 * T) / (c243 + T)); + double d = (c243 * c)/(c17 - c) * 100.0; + + lua_pushinteger(L, (int32_t)(d + (((d<0)?-1:(d>0)) * 0.5))); + return 1; +} + +static const LUA_REG_TYPE bme680_map[] = { + { LSTRKEY( "setup" ), LFUNCVAL(bme680_lua_setup)}, + { LSTRKEY( "startreadout" ), LFUNCVAL(bme680_lua_startreadout)}, + { LSTRKEY( "qfe2qnh" ), LFUNCVAL(bme680_lua_qfe2qnh)}, + { LSTRKEY( "altitude" ), LFUNCVAL(bme680_lua_altitude)}, + { LSTRKEY( "dewpoint" ), LFUNCVAL(bme680_lua_dewpoint)}, + { LSTRKEY( "read" ), LFUNCVAL(bme680_lua_read)}, + { LNILKEY, LNILVAL} +}; + +NODEMCU_MODULE(BME680, "bme680", bme680_map, NULL); diff --git a/app/modules/bme680_defs.h b/app/modules/bme680_defs.h new file mode 100644 index 00000000..4ef701f6 --- /dev/null +++ b/app/modules/bme680_defs.h @@ -0,0 +1,529 @@ +/** + * Copyright (C) 2017 - 2018 Bosch Sensortec GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of the copyright holder nor the names of the + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + * + * The information provided is believed to be accurate and reliable. + * The copyright holder assumes no responsibility + * for the consequences of use + * of such information nor for any infringement of patents or + * other rights of third parties which may result from its use. + * No license is granted by implication or otherwise under any patent or + * patent rights of the copyright holder. + * + * @file bme680_defs.h + * @date 5 Jul 2017 + * @version 3.5.1 + * @brief + * + */ + +/*! @file bme680_defs.h + @brief Sensor driver for BME680 sensor */ +/*! + * @defgroup BME680 SENSOR API + * @brief + * @{*/ +#ifndef BME680_DEFS_H_ +#define BME680_DEFS_H_ + +/********************************************************/ +/* header includes */ +#ifdef __KERNEL__ +#include +#else +#include +#endif + +#ifdef __KERNEL__ +#if (LONG_MAX) > 0x7fffffff +#define __have_long64 1 +#elif (LONG_MAX) == 0x7fffffff +#define __have_long32 1 +#endif + +#if !defined(UINT8_C) +#define INT8_C(x) x +#if (INT_MAX) > 0x7f +#define UINT8_C(x) x +#else +#define UINT8_C(x) x##U +#endif +#endif + +#if !defined(UINT16_C) +#define INT16_C(x) x +#if (INT_MAX) > 0x7fff +#define UINT16_C(x) x +#else +#define UINT16_C(x) x##U +#endif +#endif + +#if !defined(INT32_C) && !defined(UINT32_C) +#if __have_long32 +#define INT32_C(x) x##L +#define UINT32_C(x) x##UL +#else +#define INT32_C(x) x +#define UINT32_C(x) x##U +#endif +#endif + +#if !defined(INT64_C) && !defined(UINT64_C) +#if __have_long64 +#define INT64_C(x) x##L +#define UINT64_C(x) x##UL +#else +#define INT64_C(x) x##LL +#define UINT64_C(x) x##ULL +#endif +#endif +#endif +/**@}*/ + +/**\name C standard macros */ +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *) 0) +#endif +#endif + +/** BME680 General config */ +#define BME680_POLL_PERIOD_MS UINT8_C(10) + +/** BME680 I2C addresses */ +#define BME680_I2C_ADDR_PRIMARY UINT8_C(0x76) +#define BME680_I2C_ADDR_SECONDARY UINT8_C(0x77) + +/** BME680 unique chip identifier */ +#define BME680_CHIP_ID UINT8_C(0x61) + +/** BME680 coefficients related defines */ +#define BME680_COEFF_SIZE UINT8_C(0x41) +#define BME680_COEFF_ADDR1_LEN UINT8_C(25) +#define BME680_COEFF_ADDR2_LEN UINT8_C(16) + +/** BME680 field_x related defines */ +#define BME680_FIELD_LENGTH UINT8_C(15) +#define BME680_FIELD_ADDR_OFFSET UINT8_C(17) + +/** Soft reset command */ +#define BME680_SOFT_RESET_CMD UINT8_C(0xb6) + +/** Error code definitions */ +#define BME680_OK INT8_C(0) +/* Errors */ +#define BME680_E_NULL_PTR INT8_C(-1) +#define BME680_E_COM_FAIL INT8_C(-2) +#define BME680_E_DEV_NOT_FOUND INT8_C(-3) +#define BME680_E_INVALID_LENGTH INT8_C(-4) + +/* Warnings */ +#define BME680_W_DEFINE_PWR_MODE INT8_C(1) +#define BME680_W_NO_NEW_DATA INT8_C(2) + +/* Info's */ +#define BME680_I_MIN_CORRECTION UINT8_C(1) +#define BME680_I_MAX_CORRECTION UINT8_C(2) + +/** Register map */ +/** Other coefficient's address */ +#define BME680_ADDR_RES_HEAT_VAL_ADDR UINT8_C(0x00) +#define BME680_ADDR_RES_HEAT_RANGE_ADDR UINT8_C(0x02) +#define BME680_ADDR_RANGE_SW_ERR_ADDR UINT8_C(0x04) +#define BME680_ADDR_SENS_CONF_START UINT8_C(0x5A) +#define BME680_ADDR_GAS_CONF_START UINT8_C(0x64) + +/** Field settings */ +#define BME680_FIELD0_ADDR UINT8_C(0x1d) + +/** Heater settings */ +#define BME680_RES_HEAT0_ADDR UINT8_C(0x5a) +#define BME680_GAS_WAIT0_ADDR UINT8_C(0x64) + +/** Sensor configuration registers */ +#define BME680_CONF_HEAT_CTRL_ADDR UINT8_C(0x70) +#define BME680_CONF_ODR_RUN_GAS_NBC_ADDR UINT8_C(0x71) +#define BME680_CONF_OS_H_ADDR UINT8_C(0x72) +#define BME680_MEM_PAGE_ADDR UINT8_C(0xf3) +#define BME680_CONF_T_P_MODE_ADDR UINT8_C(0x74) +#define BME680_CONF_ODR_FILT_ADDR UINT8_C(0x75) + +/** Coefficient's address */ +#define BME680_COEFF_ADDR1 UINT8_C(0x89) +#define BME680_COEFF_ADDR2 UINT8_C(0xe1) + +/** Chip identifier */ +#define BME680_CHIP_ID_ADDR UINT8_C(0xd0) + +/** Soft reset register */ +#define BME680_SOFT_RESET_ADDR UINT8_C(0xe0) + +/** Heater control settings */ +#define BME680_ENABLE_HEATER UINT8_C(0x00) +#define BME680_DISABLE_HEATER UINT8_C(0x08) + +/** Gas measurement settings */ +#define BME680_DISABLE_GAS_MEAS UINT8_C(0x00) +#define BME680_ENABLE_GAS_MEAS UINT8_C(0x01) + +/** Over-sampling settings */ +#define BME680_OS_NONE UINT8_C(0) +#define BME680_OS_1X UINT8_C(1) +#define BME680_OS_2X UINT8_C(2) +#define BME680_OS_4X UINT8_C(3) +#define BME680_OS_8X UINT8_C(4) +#define BME680_OS_16X UINT8_C(5) + +/** IIR filter settings */ +#define BME680_FILTER_SIZE_0 UINT8_C(0) +#define BME680_FILTER_SIZE_1 UINT8_C(1) +#define BME680_FILTER_SIZE_3 UINT8_C(2) +#define BME680_FILTER_SIZE_7 UINT8_C(3) +#define BME680_FILTER_SIZE_15 UINT8_C(4) +#define BME680_FILTER_SIZE_31 UINT8_C(5) +#define BME680_FILTER_SIZE_63 UINT8_C(6) +#define BME680_FILTER_SIZE_127 UINT8_C(7) + +/** Power mode settings */ +#define BME680_SLEEP_MODE UINT8_C(0) +#define BME680_FORCED_MODE UINT8_C(1) + +/** Delay related macro declaration */ +#define BME680_RESET_PERIOD UINT32_C(10) + +/** SPI memory page settings */ +#define BME680_MEM_PAGE0 UINT8_C(0x10) +#define BME680_MEM_PAGE1 UINT8_C(0x00) + +/** Ambient humidity shift value for compensation */ +#define BME680_HUM_REG_SHIFT_VAL UINT8_C(4) + +/** Run gas enable and disable settings */ +#define BME680_RUN_GAS_DISABLE UINT8_C(0) +#define BME680_RUN_GAS_ENABLE UINT8_C(1) + +/** Buffer length macro declaration */ +#define BME680_TMP_BUFFER_LENGTH UINT8_C(40) +#define BME680_REG_BUFFER_LENGTH UINT8_C(6) +#define BME680_FIELD_DATA_LENGTH UINT8_C(3) +#define BME680_GAS_REG_BUF_LENGTH UINT8_C(20) +#define BME680_GAS_HEATER_PROF_LEN_MAX UINT8_C(10) + +/** Settings selector */ +#define BME680_OST_SEL UINT16_C(1) +#define BME680_OSP_SEL UINT16_C(2) +#define BME680_OSH_SEL UINT16_C(4) +#define BME680_GAS_MEAS_SEL UINT16_C(8) +#define BME680_FILTER_SEL UINT16_C(16) +#define BME680_HCNTRL_SEL UINT16_C(32) +#define BME680_RUN_GAS_SEL UINT16_C(64) +#define BME680_NBCONV_SEL UINT16_C(128) +#define BME680_GAS_SENSOR_SEL UINT16_C(BME680_GAS_MEAS_SEL | BME680_RUN_GAS_SEL | BME680_NBCONV_SEL) + +/** Number of conversion settings*/ +#define BME680_NBCONV_MIN UINT8_C(0) +#define BME680_NBCONV_MAX UINT8_C(10) + +/** Mask definitions */ +#define BME680_GAS_MEAS_MSK UINT8_C(0x30) +#define BME680_NBCONV_MSK UINT8_C(0X0F) +#define BME680_FILTER_MSK UINT8_C(0X1C) +#define BME680_OST_MSK UINT8_C(0XE0) +#define BME680_OSP_MSK UINT8_C(0X1C) +#define BME680_OSH_MSK UINT8_C(0X07) +#define BME680_HCTRL_MSK UINT8_C(0x08) +#define BME680_RUN_GAS_MSK UINT8_C(0x10) +#define BME680_MODE_MSK UINT8_C(0x03) +#define BME680_RHRANGE_MSK UINT8_C(0x30) +#define BME680_RSERROR_MSK UINT8_C(0xf0) +#define BME680_NEW_DATA_MSK UINT8_C(0x80) +#define BME680_GAS_INDEX_MSK UINT8_C(0x0f) +#define BME680_GAS_RANGE_MSK UINT8_C(0x0f) +#define BME680_GASM_VALID_MSK UINT8_C(0x20) +#define BME680_HEAT_STAB_MSK UINT8_C(0x10) +#define BME680_MEM_PAGE_MSK UINT8_C(0x10) +#define BME680_SPI_RD_MSK UINT8_C(0x80) +#define BME680_SPI_WR_MSK UINT8_C(0x7f) +#define BME680_BIT_H1_DATA_MSK UINT8_C(0x0F) + +/** Bit position definitions for sensor settings */ +#define BME680_GAS_MEAS_POS UINT8_C(4) +#define BME680_FILTER_POS UINT8_C(2) +#define BME680_OST_POS UINT8_C(5) +#define BME680_OSP_POS UINT8_C(2) +#define BME680_RUN_GAS_POS UINT8_C(4) + +/** Array Index to Field data mapping for Calibration Data*/ +#define BME680_T2_LSB_REG (1) +#define BME680_T2_MSB_REG (2) +#define BME680_T3_REG (3) +#define BME680_P1_LSB_REG (5) +#define BME680_P1_MSB_REG (6) +#define BME680_P2_LSB_REG (7) +#define BME680_P2_MSB_REG (8) +#define BME680_P3_REG (9) +#define BME680_P4_LSB_REG (11) +#define BME680_P4_MSB_REG (12) +#define BME680_P5_LSB_REG (13) +#define BME680_P5_MSB_REG (14) +#define BME680_P7_REG (15) +#define BME680_P6_REG (16) +#define BME680_P8_LSB_REG (19) +#define BME680_P8_MSB_REG (20) +#define BME680_P9_LSB_REG (21) +#define BME680_P9_MSB_REG (22) +#define BME680_P10_REG (23) +#define BME680_H2_MSB_REG (25) +#define BME680_H2_LSB_REG (26) +#define BME680_H1_LSB_REG (26) +#define BME680_H1_MSB_REG (27) +#define BME680_H3_REG (28) +#define BME680_H4_REG (29) +#define BME680_H5_REG (30) +#define BME680_H6_REG (31) +#define BME680_H7_REG (32) +#define BME680_T1_LSB_REG (33) +#define BME680_T1_MSB_REG (34) +#define BME680_GH2_LSB_REG (35) +#define BME680_GH2_MSB_REG (36) +#define BME680_GH1_REG (37) +#define BME680_GH3_REG (38) + +/** BME680 register buffer index settings*/ +#define BME680_REG_FILTER_INDEX UINT8_C(5) +#define BME680_REG_TEMP_INDEX UINT8_C(4) +#define BME680_REG_PRES_INDEX UINT8_C(4) +#define BME680_REG_HUM_INDEX UINT8_C(2) +#define BME680_REG_NBCONV_INDEX UINT8_C(1) +#define BME680_REG_RUN_GAS_INDEX UINT8_C(1) +#define BME680_REG_HCTRL_INDEX UINT8_C(0) + +/** Macro to combine two 8 bit data's to form a 16 bit data */ +#define BME680_CONCAT_BYTES(msb, lsb) (((uint16_t)msb << 8) | (uint16_t)lsb) + +/** Macro to SET and GET BITS of a register */ +#define BME680_SET_BITS(reg_data, bitname, data) \ + ((reg_data & ~(bitname##_MSK)) | \ + ((data << bitname##_POS) & bitname##_MSK)) +#define BME680_GET_BITS(reg_data, bitname) ((reg_data & (bitname##_MSK)) >> \ + (bitname##_POS)) + +/** Macro variant to handle the bitname position if it is zero */ +#define BME680_SET_BITS_POS_0(reg_data, bitname, data) \ + ((reg_data & ~(bitname##_MSK)) | \ + (data & bitname##_MSK)) +#define BME680_GET_BITS_POS_0(reg_data, bitname) (reg_data & (bitname##_MSK)) + +/** Type definitions */ +/* + * Generic communication function pointer + * @param[in] dev_id: Place holder to store the id of the device structure + * Can be used to store the index of the Chip select or + * I2C address of the device. + * @param[in] reg_addr: Used to select the register the where data needs to + * be read from or written to. + * @param[in/out] reg_data: Data array to read/write + * @param[in] len: Length of the data array + */ +typedef int8_t (*bme680_com_fptr_t)(uint8_t dev_id, uint8_t reg_addr, uint8_t *data, uint16_t len); + +/* + * Delay function pointer + * @param[in] period: Time period in milliseconds + */ +typedef void (*bme680_delay_fptr_t)(uint32_t period); + +/*! + * @brief Interface selection Enumerations + */ +enum bme680_intf { + /*! SPI interface */ + BME680_SPI_INTF, + /*! I2C interface */ + BME680_I2C_INTF +}; + +/* structure definitions */ +/*! + * @brief Sensor field data structure + */ +struct bme680_field_data { + /*! Contains new_data, gasm_valid & heat_stab */ + uint8_t status; + /*! The index of the heater profile used */ + uint8_t gas_index; + /*! Measurement index to track order */ + uint8_t meas_index; + /*! Temperature in degree celsius x100 */ + int16_t temperature; + /*! Pressure in Pascal */ + uint32_t pressure; + /*! Humidity in % relative humidity x1000 */ + uint32_t humidity; + /*! Gas resistance in Ohms */ + uint32_t gas_resistance; +}; + +/*! + * @brief Structure to hold the Calibration data + */ +struct bme680_calib_data { + /*! Variable to store calibrated humidity data */ + uint16_t par_h1; + /*! Variable to store calibrated humidity data */ + uint16_t par_h2; + /*! Variable to store calibrated humidity data */ + int8_t par_h3; + /*! Variable to store calibrated humidity data */ + int8_t par_h4; + /*! Variable to store calibrated humidity data */ + int8_t par_h5; + /*! Variable to store calibrated humidity data */ + uint8_t par_h6; + /*! Variable to store calibrated humidity data */ + int8_t par_h7; + /*! Variable to store calibrated gas data */ + int8_t par_gh1; + /*! Variable to store calibrated gas data */ + int16_t par_gh2; + /*! Variable to store calibrated gas data */ + int8_t par_gh3; + /*! Variable to store calibrated temperature data */ + uint16_t par_t1; + /*! Variable to store calibrated temperature data */ + int16_t par_t2; + /*! Variable to store calibrated temperature data */ + int8_t par_t3; + /*! Variable to store calibrated pressure data */ + uint16_t par_p1; + /*! Variable to store calibrated pressure data */ + int16_t par_p2; + /*! Variable to store calibrated pressure data */ + int8_t par_p3; + /*! Variable to store calibrated pressure data */ + int16_t par_p4; + /*! Variable to store calibrated pressure data */ + int16_t par_p5; + /*! Variable to store calibrated pressure data */ + int8_t par_p6; + /*! Variable to store calibrated pressure data */ + int8_t par_p7; + /*! Variable to store calibrated pressure data */ + int16_t par_p8; + /*! Variable to store calibrated pressure data */ + int16_t par_p9; + /*! Variable to store calibrated pressure data */ + uint8_t par_p10; + /*! Variable to store t_fine size */ + int32_t t_fine; + /*! Variable to store heater resistance range */ + uint8_t res_heat_range; + /*! Variable to store heater resistance value */ + int8_t res_heat_val; + /*! Variable to store error range */ + int8_t range_sw_err; +}; + +/*! + * @brief BME680 sensor settings structure which comprises of ODR, + * over-sampling and filter settings. + */ +struct bme680_tph_sett { + /*! Humidity oversampling */ + uint8_t os_hum; + /*! Temperature oversampling */ + uint8_t os_temp; + /*! Pressure oversampling */ + uint8_t os_pres; + /*! Filter coefficient */ + uint8_t filter; +}; + +/*! + * @brief BME680 gas sensor which comprises of gas settings + * and status parameters + */ +struct bme680_gas_sett { + /*! Variable to store nb conversion */ + uint8_t nb_conv; + /*! Variable to store heater control */ + uint8_t heatr_ctrl; + /*! Run gas enable value */ + uint8_t run_gas; + /*! Pointer to store heater temperature */ + uint16_t heatr_temp; + /*! Pointer to store duration profile */ + uint16_t heatr_dur; +}; + +/*! + * @brief BME680 device structure + */ +struct bme680_dev { + /*! Chip Id */ + uint8_t chip_id; + /*! Device Id */ + uint8_t dev_id; + /*! SPI/I2C interface */ + enum bme680_intf intf; + /*! Memory page used */ + uint8_t mem_page; + /*! Ambient temperature in Degree C*/ + int8_t amb_temp; + /*! Sensor calibration data */ + struct bme680_calib_data calib; + /*! Sensor settings */ + struct bme680_tph_sett tph_sett; + /*! Gas Sensor settings */ + struct bme680_gas_sett gas_sett; + /*! Sensor power modes */ + uint8_t power_mode; + /*! New sensor fields */ + uint8_t new_fields; + /*! Store the info messages */ + uint8_t info_msg; + /*! Burst read structure */ + bme680_com_fptr_t read; + /*! Burst write structure */ + bme680_com_fptr_t write; + /*! Delay in ms */ + bme680_delay_fptr_t delay_ms; + /*! Communication function result */ + int8_t com_rslt; +}; + +#endif /* BME680_DEFS_H_ */ +/** @}*/ +/** @}*/ diff --git a/docs/en/modules/bme680.md b/docs/en/modules/bme680.md new file mode 100644 index 00000000..e1d6d4bb --- /dev/null +++ b/docs/en/modules/bme680.md @@ -0,0 +1,157 @@ +# BME680 module +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2017-10-28 | [vsky279](https://github.com/vsky279) | [vsky279](https://github.com/vsky279) | [bme680.c](../../../app/modules/bme680.c)| + +This module provides a simple interface to [BME680](https://www.bosch-sensortec.com/bst/products/all_products/bme680) temperature/air presssure/humidity sensors/air quality sensor (Bosch Sensortec). Compared to the BME280 module the sensor does not support automatic mode which means that it can be setup to perform regular measurements. Every measurement has to be triggered manually. + +In order to measure the air quality the sensor needs to be heated first. In the example provided by the manufacturer the sensor is heated to 300 degrees centigrade for a period of 200 ms and then the measurement is taken. These values are taken as default values in this implementation. I have not tested the impact of different temperatures and heating times on the measurement. + +This module is able to measure the gas resistance (see Bosch's datasheet). The gas resistance is not the IAQ (Indoor Air Quality) Index. But apparently it can be used as some proxy. The value still should somehow reflect the air quality. It seems that the higher value the air quality is better. + +The algorithm for IAQ calculation from the gas restistances (probably measured at different temperatures) is not publicly available. Bosch says that at this point of time the calculations for the Indoor Air Quality index are offered only as a pre-compiled library (see discussion here: [BoschSensortec/BME680_driver#6](https://github.com/BoschSensortec/BME680_driver/issues/6)). It is available as the [BSEC Library](https://www.bosch-sensortec.com/bst/products/all_products/bsec). +The algorithm is implemented in the library `bsec/algo/bin/ESP8266/libalgobsec.a`. Unfortunately I did not even manage to run the Bosch BSEC example on ESP8266 using this library. + +## bme680.altitude() + +For given air pressure and sea level air pressure returns the altitude in meters as an integer multiplied with 100, i.e. altimeter function. + +#### Syntax +`bme680.altitude(P, QNH)` + +#### Parameters +- `P` measured pressure +- `QNH` current sea level pressure + +#### Returns +altitude in meters of measurement point + +## bme680.dewpoint() + +For given temperature and relative humidity returns the dew point in Celsius as an integer multiplied with 100. + +#### Syntax +`bme680.dewpoint(H, T)` + +#### Parameters +- `H` relative humidity in percent multiplied by 1000. +- `T` temperate in Celsius multiplied by 100. + +#### Returns +dew point in Celsius + +## bme680.qfe2qnh() + +For given altitude converts the air pressure to sea level air pressure. + +#### Syntax +`bme680.qfe2qnh(P, altitude)` + +#### Parameters +- `P` measured pressure +- `altitude` altitude in meters of measurement point + +#### Returns +sea level pressure + + +## bme680.read() + +Reads the sensor and returns the temperature, the air pressure, the air relative humidity and + +#### Syntax +`bme680.read([altitude])` + +#### Parameters +- (optional) `altitude`- altitude in meters of measurement point. If provided also the air pressure converted to sea level air pressure is returned. + +#### Returns +- `T` temperature in Celsius as an integer multiplied with 100 +- `P` air pressure in hectopascals multiplied by 100 +- `H` relative humidity in percent multiplied by 1000 +- `G` gas resistance +- `QNH` air pressure in hectopascals multiplied by 100 converted to sea level + +Any of these variables is `nil` if the readout of given measure was not successful. + +The measured values can be read only once. Following attempts to read values will return nil. A new `startreadout()` needs to be called first before next `read()`. + +## bme680.startreadout() +Starts readout (turns the sensor into forced mode). After the readout the sensor turns to sleep mode. + +#### Syntax +`bme680.startreadout(delay, callback)` + +#### Parameters +- `delay` sets sensor to forced mode and calls the `callback` (if provided) after given number of milliseconds. For 0 the default delay is calculated by the [formula provided by Bosch](https://github.com/BoschSensortec/BME680_driver/blob/2a51b9c0c1899f28e561e6701caa22cb23201cfc/bme680.c#L586). Apparently for certain combinations of oversamplings setup the the delay returned by the formula is not sufficient and the readout is not ready (make sure you are not reading the previous measurement). For default parameters (2x, 16x, 1x) the calculated delay is 121 ms while in reality 150 ms are needed to get the result. +- `callback` if provided it will be invoked after given `delay`. The sensor reading should be finalized by then so. + +#### Returns +`nil` + +## bme680.setup() + +Initializes module. Initialization is mandatory before read values. + +#### Syntax + +`bme680.setup([temp_oss, press_oss, humi_oss, heater_temp, heater_duration, IIR_filter, cold_start])` + +#### Parameters +- (optional) `temp_oss` - Controls oversampling of temperature data. Default oversampling is 2x. +- (optional) `press_oss` - Controls oversampling of pressure data. Default oversampling is 16x. +- (optional) `humi_oss` - Controls oversampling of humidity data. Default oversampling is 1x +- (optional) `heater_temp` - +- (optional) `heater_duration` - +- (optional) `IIR_filter` - Controls the time constant of the IIR filter. Default fitler coefficient is 31. +- (optional) `cold_start` - If 0 then the bme680 chip is not initialised. Usefull in a battery operated setup when the ESP deep sleeps and on wakeup needs to initialise the driver (the module) but not the chip itself. The chip was kept powered (sleeping too) and is holding the latest reading that should be fetched quickly before another reading starts (`bme680.startreadout()`). By default the chip is initialised. + +|`temp_oss`, `press_oss`, `humi_oss`|Data oversampling| +|-----|-----------------| +|0|Skipped (output set to 0x80000)| +|1|oversampling ×1| +|2|oversampling ×2| +|3|oversampling ×4| +|4|oversampling ×8| +|5|oversampling ×16| + +|`IIR_filter`|Filter coefficient | +|-----|-----------------| +|0|Filter off| +|1|1| +|2|3| +|3|7| +|4|15| +|5|31| +|6|63| +|7|127| + +#### Returns +`nil` if initialization has failed (no sensor connected?) + +#### Example + +```lua +alt=320 -- altitude of the measurement place + +sda, scl = 3, 4 +i2c.setup(0, sda, scl, i2c.SLOW) -- call i2c.setup() only once + +bme680.setup() + +-- delay calculated by formula provided by Bosch: 121 ms, minimum working (empirical): 150 ms +bme680.startreadout(150, function () + T, P, H, G, QNH = bme680.read(alt) + if T then + local Tsgn = (T < 0 and -1 or 1); T = Tsgn*T + print(string.format("T=%s%d.%02d", Tsgn<0 and "-" or "", T/100, T%100)) + print(string.format("QFE=%d.%03d", P/100, P%100)) + print(string.format("QNH=%d.%03d", QNH/100, QNH%100)) + print(string.format("humidity=%d.%03d%%", H/1000, H%1000)) + print(string.format("gas resistance=%d", G)) + D = bme680.dewpoint(H, T) + local Dsgn = (D < 0 and -1 or 1); D = Dsgn*D + print(string.format("dew_point=%s%d.%02d", Dsgn<0 and "-" or "", D/100, D%100)) + end +end) +```