diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index e51c1100adfea31f8f21cf26096603a98e5f2f74..de0135945a06fa92d5511ad1184e8e75dcb1bbfe 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -29,6 +29,11 @@ ifneq (,$(filter bmp180,$(USEMODULE))) USEMODULE += xtimer endif +ifneq (,$(filter bme280,$(USEMODULE))) + FEATURES_REQUIRED += periph_i2c + USEMODULE += xtimer +endif + ifneq (,$(filter cc110x,$(USEMODULE))) USEMODULE += ieee802154 USEMODULE += uuid diff --git a/drivers/Makefile.include b/drivers/Makefile.include index 1461d12e7853e89aaf132afc7698238287c03d01..da1880178b2628019093c7bb8d0fa50baa81ab77 100644 --- a/drivers/Makefile.include +++ b/drivers/Makefile.include @@ -67,6 +67,9 @@ endif ifneq (,$(filter jc42,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/drivers/jc42/include endif +ifneq (,$(filter bme280,$(USEMODULE))) + USEMODULE_INCLUDES += $(RIOTBASE)/drivers/bme280/include +endif ifneq (,$(filter cc2420,$(USEMODULE))) USEMODULE_INCLUDES += $(RIOTBASE)/drivers/cc2420/include endif diff --git a/drivers/bme280/Makefile b/drivers/bme280/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..693aef674e6208f60f577ec96b1ad0f117b04a4c --- /dev/null +++ b/drivers/bme280/Makefile @@ -0,0 +1,3 @@ +MODULE = bme280 + +include $(RIOTBASE)/Makefile.base diff --git a/drivers/bme280/bme280.c b/drivers/bme280/bme280.c new file mode 100644 index 0000000000000000000000000000000000000000..c9f5706d8dfc4a6a111778840b83ff2ed014dd15 --- /dev/null +++ b/drivers/bme280/bme280.c @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2016 Kees Bakker, SODAQ + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_bme280 + * @{ + * + * @file + * @brief Device driver implementation for the BME280 temperature, + * pressure and humidity sensor. + * + * @author Kees Bakker <kees@sodaq.com> + * + * @} + */ + +#include <string.h> +#include <math.h> + +#include "log.h" +#include "bme280.h" +#include "bme280_internals.h" +#include "bme280_params.h" +#include "periph/i2c.h" +#include "xtimer.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +static int read_calibration_data(bme280_t* dev); +static int do_measurement(bme280_t* dev); +static uint8_t get_ctrl_meas(bme280_t* dev); +static uint8_t get_status(bme280_t* dev); +static uint8_t read_u8_reg(bme280_t* dev, uint8_t reg); +static void write_u8_reg(bme280_t* dev, uint8_t reg, uint8_t b); +static uint16_t get_uint16_le(const uint8_t *buffer, size_t offset); +static int16_t get_int16_le(const uint8_t *buffer, size_t offset); + +#if ENABLE_DEBUG +static void dump_buffer(const char *txt, uint8_t *buffer, size_t size); +#define DUMP_BUFFER(txt, buffer, size) dump_buffer(txt, buffer, size) +#else +#define DUMP_BUFFER(txt, buffer, size) +#endif + +/** + * @brief Fine resolution temperature value, also needed for pressure and humidity. + */ +static int32_t t_fine; + +/** + * @brief The measurement registers, including temperature, pressure and humidity + * + * A temporary buffer for the memory map 0xF7..0xFE + * These are read all at once and then used to compute the three sensor values. + */ +static uint8_t measurement_regs[8]; + +/*---------------------------------------------------------------------------* + * BME280 Core API * + *---------------------------------------------------------------------------*/ + +int bme280_init(bme280_t* dev, const bme280_params_t* params) +{ + uint8_t chip_id; + + dev->params = *params; + + /* Initialize I2C interface */ + if (i2c_init_master(dev->params.i2c_dev, I2C_SPEED_NORMAL)) { + DEBUG("[Error] I2C device not enabled\n"); + return BME280_ERR_I2C; + } + + /* Read chip ID */ + chip_id = read_u8_reg(dev, BME280_CHIP_ID_REG); + if (chip_id != BME280_CHIP_ID) { + DEBUG("[Error] Did not detect a BME280 at address %02x (%02X != %02X)\n", + dev->params.i2c_addr, chip_id, BME280_CHIP_ID); + return BME280_ERR_NODEV; + } + + /* Read compensation data, 0x88..0x9F, 0xA1, 0xE1..0xE7 */ + if (read_calibration_data(dev)) { + DEBUG("[Error] Could not read calibration data\n"); + return BME280_ERR_NOCAL; + } + + return BME280_OK; +} + +/* + * Returns temperature in DegC, resolution is 0.01 DegC. + * t_fine carries fine temperature as global value + */ +int16_t bme280_read_temperature(bme280_t* dev) +{ + if (do_measurement(dev) < 0) { + return INT16_MIN; + } + + bme280_calibration_t *cal = &dev->calibration; /* helper variable */ + + /* Read the uncompensated temperature */ + int32_t adc_T = (((uint32_t)measurement_regs[3 + 0]) << 12) | + (((uint32_t)measurement_regs[3 + 1]) << 4) | + ((((uint32_t)measurement_regs[3 + 2]) >> 4) & 0x0F); + + /* + * Compensate the temperature value. + * The following is code from Bosch's BME280_driver bme280_compensate_temperature_int32() + * The variable names and the many defines have been modified to make the code + * more readable. + */ + int32_t var1; + int32_t var2; + + var1 = ((((adc_T >> 3) - ((int32_t)cal->dig_T1 << 1))) * ((int32_t)cal->dig_T2)) >> 11; + var2 = (((((adc_T >> 4) - ((int32_t)cal->dig_T1)) * ((adc_T >> 4) - ((int32_t)cal->dig_T1))) >> 12) * + ((int32_t)cal->dig_T3)) >> 14; + + /* calculate t_fine (used for pressure and humidity too) */ + t_fine = var1 + var2; + + return (t_fine * 5 + 128) >> 8; +} + +/* + * Returns pressure in Pa + */ +uint32_t bme280_read_pressure(bme280_t *dev) +{ + bme280_calibration_t *cal = &dev->calibration; /* helper variable */ + + /* Read the uncompensated pressure */ + int32_t adc_P = (((uint32_t)measurement_regs[0 + 0]) << 12) | + (((uint32_t)measurement_regs[0 + 1]) << 4) | + ((((uint32_t)measurement_regs[0 + 2]) >> 4) & 0x0F); + + int64_t var1; + int64_t var2; + int64_t p_acc; + + /* + * Compensate the pressure value. + * The following is code from Bosch's BME280_driver bme280_compensate_pressure_int64() + * The variable names and the many defines have been modified to make the code + * more readable. + */ + var1 = ((int64_t)t_fine) - 128000; + var2 = var1 * var1 * (int64_t)cal->dig_P6; + var2 = var2 + ((var1 * (int64_t)cal->dig_P5) << 17); + var2 = var2 + (((int64_t)cal->dig_P4) << 35); + var1 = ((var1 * var1 * (int64_t)cal->dig_P3) >> 8) + ((var1 * (int64_t)cal->dig_P2) << 12); + var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)cal->dig_P1) >> 33; + /* Avoid division by zero */ + if (var1 == 0) { + return UINT32_MAX; + } + + p_acc = 1048576 - adc_P; + p_acc = (((p_acc << 31) - var2) * 3125) / var1; + var1 = (((int64_t)cal->dig_P9) * (p_acc >> 13) * (p_acc >> 13)) >> 25; + var2 = (((int64_t)cal->dig_P8) * p_acc) >> 19; + p_acc = ((p_acc + var1 + var2) >> 8) + (((int64_t)cal->dig_P7) << 4); + + return p_acc >> 8; +} + +uint16_t bme280_read_humidity(bme280_t *dev) +{ + bme280_calibration_t *cal = &dev->calibration; /* helper variable */ + + /* Read the uncompensated pressure */ + int32_t adc_H = (((uint32_t)measurement_regs[6 + 0]) << 8) | + (((uint32_t)measurement_regs[6 + 1])); + + /* + * Compensate the humidity value. + * The following is code from Bosch's BME280_driver bme280_compensate_humidity_int32() + * The variable names and the many defines have been modified to make the code + * more readable. + * The value is first computed as a value in %rH as unsigned 32bit integer + * in Q22.10 format(22 integer 10 fractional bits). + */ + int32_t var1; + + /* calculate x1*/ + var1 = (t_fine - ((int32_t)76800)); + /* calculate x1*/ + var1 = (((((adc_H << 14) - (((int32_t)cal->dig_H4) << 20) - (((int32_t)cal->dig_H5) * var1)) + + ((int32_t)16384)) >> 15) * + (((((((var1 * ((int32_t)cal->dig_H6)) >> 10) * + (((var1 * ((int32_t)cal->dig_H3)) >> 11) + ((int32_t)32768))) >> 10) + + ((int32_t)2097152)) * ((int32_t)cal->dig_H2) + 8192) >> 14)); + var1 = (var1 - (((((var1 >> 15) * (var1 >> 15)) >> 7) * ((int32_t)cal->dig_H1)) >> 4)); + var1 = (var1 < 0 ? 0 : var1); + var1 = (var1 > 419430400 ? 419430400 : var1); + /* First multiply to avoid losing the accuracy after the shift by ten */ + return (100 * ((uint32_t)var1 >> 12)) >> 10; +} + +/** + * Read compensation data, 0x88..0x9F, 0xA1, 0xE1..0xE7 + * + * This function reads all calibration bytes at once. These are + * the registers DIG_T1_LSB (0x88) upto and including DIG_H6 (0xE7). + */ +static int read_calibration_data(bme280_t* dev) +{ + uint8_t buffer[128]; /* 128 should be enough to read all calibration bytes */ + int nr_bytes; + int nr_bytes_to_read = (BME280_DIG_H6_REG - BME280_DIG_T1_LSB_REG) + 1; + uint8_t offset = 0x88; + + memset(buffer, 0, sizeof(buffer)); + nr_bytes = i2c_read_regs(dev->params.i2c_dev, dev->params.i2c_addr, offset, + buffer, nr_bytes_to_read); + if (nr_bytes != nr_bytes_to_read) { + LOG_ERROR("Unable to read calibration data\n"); + return -1; + } + DUMP_BUFFER("Raw Calibration Data", buffer, nr_bytes); + + /* All little endian */ + dev->calibration.dig_T1 = get_uint16_le(buffer, BME280_DIG_T1_LSB_REG - offset); + dev->calibration.dig_T2 = get_int16_le(buffer, BME280_DIG_T2_LSB_REG - offset); + dev->calibration.dig_T3 = get_int16_le(buffer, BME280_DIG_T3_LSB_REG - offset); + + dev->calibration.dig_P1 = get_uint16_le(buffer, BME280_DIG_P1_LSB_REG - offset); + dev->calibration.dig_P2 = get_int16_le(buffer, BME280_DIG_P2_LSB_REG - offset); + dev->calibration.dig_P3 = get_int16_le(buffer, BME280_DIG_P3_LSB_REG - offset); + dev->calibration.dig_P4 = get_int16_le(buffer, BME280_DIG_P4_LSB_REG - offset); + dev->calibration.dig_P5 = get_int16_le(buffer, BME280_DIG_P5_LSB_REG - offset); + dev->calibration.dig_P6 = get_int16_le(buffer, BME280_DIG_P6_LSB_REG - offset); + dev->calibration.dig_P7 = get_int16_le(buffer, BME280_DIG_P7_LSB_REG - offset); + dev->calibration.dig_P8 = get_int16_le(buffer, BME280_DIG_P8_LSB_REG - offset); + dev->calibration.dig_P9 = get_int16_le(buffer, BME280_DIG_P9_LSB_REG - offset); + + dev->calibration.dig_H1 = buffer[BME280_DIG_H1_REG - offset]; + dev->calibration.dig_H2 = get_int16_le(buffer, BME280_DIG_H2_LSB_REG - offset); + dev->calibration.dig_H3 = buffer[BME280_DIG_H3_REG - offset]; + dev->calibration.dig_H4 = (((int16_t)buffer[BME280_DIG_H4_MSB_REG - offset]) << 4) + + (buffer[BME280_DIG_H4_H5_REG - offset] & 0x0F); + dev->calibration.dig_H5 = (((int16_t)buffer[BME280_DIG_H5_MSB_REG - offset]) << 4) + + ((buffer[BME280_DIG_H4_H5_REG - offset] & 0xF0) >> 4); + dev->calibration.dig_H6 = buffer[BME280_DIG_H6_REG - offset]; + + DEBUG("[INFO] Chip ID = 0x%02X\n", buffer[BME280_CHIP_ID_REG - offset]); + + /* Config is only be writable in sleep mode */ + (void)i2c_write_reg(dev->params.i2c_dev, dev->params.i2c_addr, + BME280_CTRL_MEAS_REG, 0); + + uint8_t b; + + /* Config Register */ + /* spi3w_en unused */ + b = ((dev->params.t_sb & 7) << 5) | ((dev->params.filter & 7) << 2); + write_u8_reg(dev, BME280_CONFIG_REG, b); + + /* + * Note from the datasheet about ctrl_hum: "Changes to this register only become effective + * after a write operation to "ctrl_meas". + * So, set ctrl_hum first. + */ + b = dev->params.humid_oversample & 7; + write_u8_reg(dev, BME280_CTRL_HUMIDITY_REG, b); + + b = ((dev->params.temp_oversample & 7) << 5) | + ((dev->params.press_oversample & 7) << 2) | + (dev->params.run_mode & 3); + write_u8_reg(dev, BME280_CTRL_MEAS_REG, b); + + return 0; +} + +/** + * @brief Start a measurement and read the registers + */ +static int do_measurement(bme280_t* dev) +{ + /* + * If settings has FORCED mode, then the device go to sleep after + * it finished the measurement. To read again we have to set the + * run_mode back to FORCED. + */ + uint8_t ctrl_meas = get_ctrl_meas(dev); + uint8_t run_mode = ctrl_meas & 3; + if (run_mode != dev->params.run_mode) { + /* Set the run_mode back to what we want. */ + ctrl_meas &= ~3; + ctrl_meas |= dev->params.run_mode; + write_u8_reg(dev, BME280_CTRL_MEAS_REG, ctrl_meas); + + /* Wait for measurement ready? */ + size_t count = 0; + while (count < 10 && (get_status(dev) & 0x08) != 0) { + ++count; + } + /* What to do when measuring is still on? */ + } + int nr_bytes; + int nr_bytes_to_read = sizeof(measurement_regs); + uint8_t offset = BME280_PRESSURE_MSB_REG; + + nr_bytes = i2c_read_regs(dev->params.i2c_dev, dev->params.i2c_addr, + offset, measurement_regs, nr_bytes_to_read); + if (nr_bytes != nr_bytes_to_read) { + LOG_ERROR("Unable to read temperature data\n"); + return -1; + } + DUMP_BUFFER("Raw Sensor Data", measurement_regs, nr_bytes); + + return 0; +} + +static uint8_t get_ctrl_meas(bme280_t* dev) +{ + return read_u8_reg(dev, BME280_CTRL_MEAS_REG); +} + +static uint8_t get_status(bme280_t* dev) +{ + return read_u8_reg(dev, BME280_STAT_REG); +} + +static uint8_t read_u8_reg(bme280_t* dev, uint8_t reg) +{ + uint8_t b; + /* Assuming device is correct, it should return 1 (nr bytes) */ + (void)i2c_read_reg(dev->params.i2c_dev, dev->params.i2c_addr, reg, &b); + return b; +} + +static void write_u8_reg(bme280_t* dev, uint8_t reg, uint8_t b) +{ + /* Assuming device is correct, it should return 1 (nr bytes) */ + (void)i2c_write_reg(dev->params.i2c_dev, dev->params.i2c_addr, reg, b); +} + +static uint16_t get_uint16_le(const uint8_t *buffer, size_t offset) +{ + return (((uint16_t)buffer[offset + 1]) << 8) + buffer[offset]; +} + +static int16_t get_int16_le(const uint8_t *buffer, size_t offset) +{ + return (((int16_t)buffer[offset + 1]) << 8) + buffer[offset]; +} + +#if ENABLE_DEBUG +static void dump_buffer(const char *txt, uint8_t *buffer, size_t size) +{ + size_t ix; + DEBUG("%s\n", txt); + for (ix = 0; ix < size; ix++) { + DEBUG("%02X", buffer[ix]); + if ((ix + 1) == size || (((ix + 1) % 16) == 0)) { + DEBUG("\n"); + } + } +} +#endif diff --git a/drivers/bme280/bme280_saul.c b/drivers/bme280/bme280_saul.c new file mode 100644 index 0000000000000000000000000000000000000000..e37c9ed82f389d719710cbf38a10c8abcd1cee55 --- /dev/null +++ b/drivers/bme280/bme280_saul.c @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2016 Kees Bakker, SODAQ + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup driver_bme280 + * @{ + * + * @file + * @brief SAUL adoption for BME280 sensor. + * + * @author Kees Bakker <kees@sodaq.com> + * + * @} + */ + +#include "saul.h" + +#include "bme280.h" + +static int read_temperature(void *dev, phydat_t *res) +{ + bme280_t *d = (bme280_t *)dev; + + res->val[0] = bme280_read_temperature(d); + res->unit = UNIT_TEMP_C; + res->scale = -2; + + return 1; +} + +static int read_relative_humidity(void *dev, phydat_t *res) +{ + bme280_t *d = (bme280_t *)dev; + + res->val[0] = bme280_read_humidity(d); + res->unit = UNIT_PERCENT; + res->scale = -2; + + return 1; +} + +static int read_pressure(void *dev, phydat_t *res) +{ + bme280_t *d = (bme280_t *)dev; + + res->val[0] = bme280_read_pressure(d) / 100; + res->unit = UNIT_PA; + res->scale = 2; + + return 1; +} + +const saul_driver_t bme280_temperature_saul_driver = { + .read = read_temperature, + .write = saul_notsup, + .type = SAUL_SENSE_TEMP, +}; + +const saul_driver_t bme280_relative_humidity_saul_driver = { + .read = read_relative_humidity, + .write = saul_notsup, + .type = SAUL_SENSE_HUM, +}; + +const saul_driver_t bme280_pressure_saul_driver = { + .read = read_pressure, + .write = saul_notsup, + .type = SAUL_SENSE_PRESS, +}; diff --git a/drivers/bme280/include/bme280_internals.h b/drivers/bme280/include/bme280_internals.h new file mode 100644 index 0000000000000000000000000000000000000000..2c28b676e584ae90b612647716e877862a83a116 --- /dev/null +++ b/drivers/bme280/include/bme280_internals.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 Kees Bakker, SODAQ + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_bme280 + * @brief Internal addresses, registers, constants for the BME280 sensor. + * @{ + * @file + * @brief Internal addresses, registers, constants for the BME280 sensor. + * + * @author Kees Bakker <kees@sodaq.com> + */ + +#ifndef BME280_INTERNALS_H +#define BME280_INTERNALS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name BME280 registers + * @{ + */ +#define BME280_DIG_T1_LSB_REG 0x88 +#define BME280_DIG_T1_MSB_REG 0x89 +#define BME280_DIG_T2_LSB_REG 0x8A +#define BME280_DIG_T2_MSB_REG 0x8B +#define BME280_DIG_T3_LSB_REG 0x8C +#define BME280_DIG_T3_MSB_REG 0x8D +#define BME280_DIG_P1_LSB_REG 0x8E +#define BME280_DIG_P1_MSB_REG 0x8F +#define BME280_DIG_P2_LSB_REG 0x90 +#define BME280_DIG_P2_MSB_REG 0x91 +#define BME280_DIG_P3_LSB_REG 0x92 +#define BME280_DIG_P3_MSB_REG 0x93 +#define BME280_DIG_P4_LSB_REG 0x94 +#define BME280_DIG_P4_MSB_REG 0x95 +#define BME280_DIG_P5_LSB_REG 0x96 +#define BME280_DIG_P5_MSB_REG 0x97 +#define BME280_DIG_P6_LSB_REG 0x98 +#define BME280_DIG_P6_MSB_REG 0x99 +#define BME280_DIG_P7_LSB_REG 0x9A +#define BME280_DIG_P7_MSB_REG 0x9B +#define BME280_DIG_P8_LSB_REG 0x9C +#define BME280_DIG_P8_MSB_REG 0x9D +#define BME280_DIG_P9_LSB_REG 0x9E +#define BME280_DIG_P9_MSB_REG 0x9F + +#define BME280_DIG_H1_REG 0xA1 + +#define BME280_CHIP_ID 0x60 /* The identifier of the BME280 */ +#define BME280_CHIP_ID_REG 0xD0 +#define BME280_RST_REG 0xE0 /* Softreset Reg */ + +#define BME280_DIG_H2_LSB_REG 0xE1 +#define BME280_DIG_H2_MSB_REG 0xE2 +#define BME280_DIG_H3_REG 0xE3 +#define BME280_DIG_H4_MSB_REG 0xE4 /* H4[11:4] */ +#define BME280_DIG_H4_H5_REG 0xE5 /* H5[3:0] H4[3:0] */ +#define BME280_DIG_H5_MSB_REG 0xE6 /* H5[11:4] */ +#define BME280_DIG_H6_REG 0xE7 + +#define BME280_CTRL_HUMIDITY_REG 0xF2 /* Ctrl Humidity Reg */ +#define BME280_STAT_REG 0xF3 /* Status Reg */ +#define BME280_CTRL_MEAS_REG 0xF4 /* Ctrl Measure Reg */ +#define BME280_CONFIG_REG 0xF5 /* Configuration Reg */ +#define BME280_PRESSURE_MSB_REG 0xF7 /* Pressure MSB */ +#define BME280_PRESSURE_LSB_REG 0xF8 /* Pressure LSB */ +#define BME280_PRESSURE_XLSB_REG 0xF9 /* Pressure XLSB */ +#define BME280_TEMPERATURE_MSB_REG 0xFA /* Temperature MSB */ +#define BME280_TEMPERATURE_LSB_REG 0xFB /* Temperature LSB */ +#define BME280_TEMPERATURE_XLSB_REG 0xFC /* Temperature XLSB */ +#define BME280_HUMIDITY_MSB_REG 0xFD /* Humidity MSB */ +#define BME280_HUMIDITY_LSB_REG 0xFE /* Humidity LSB */ +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* BME280_INTERNALS_H */ +/** @} */ diff --git a/drivers/bme280/include/bme280_params.h b/drivers/bme280/include/bme280_params.h new file mode 100644 index 0000000000000000000000000000000000000000..8f1f7c6180c95f1a9d7ee49e735ed765aff6adff --- /dev/null +++ b/drivers/bme280/include/bme280_params.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2016 Kees Bakker, SODAQ + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_bme280 + * + * @{ + * @file + * @brief Default configuration for BME280 + * + * @author Kees Bakker <kees@sodaq.com> + */ + +#ifndef BME280_PARAMS_H +#define BME280_PARAMS_H + +#include "bme280.h" +#include "saul_reg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Set default configuration parameters for the BME280 + * @{ + */ +#ifndef BME280_PARAM_I2C_DEV +#define BME280_PARAM_I2C_DEV I2C_DEV(0) +#endif +#ifndef BME280_PARAM_I2C_ADDR +#define BME280_PARAM_I2C_ADDR (0x77) +#endif + +/* Defaults for Weather Monitoring */ +#define BME280_PARAMS_DEFAULT \ + { \ + .i2c_dev = BME280_PARAM_I2C_DEV, \ + .i2c_addr = BME280_PARAM_I2C_ADDR, \ + .t_sb = BME280_SB_0_5, \ + .filter = BME280_FILTER_OFF, \ + .run_mode = BME280_MODE_FORCED, \ + .temp_oversample = BME280_OSRS_X1, \ + .press_oversample = BME280_OSRS_X1, \ + .humid_oversample = BME280_OSRS_X1, \ + } +/**@}*/ + +/** + * @brief Configure BME280 + */ +static const bme280_params_t bme280_params[] = +{ +#ifdef BME280_PARAMS_BOARD + BME280_PARAMS_BOARD, +#else + BME280_PARAMS_DEFAULT, +#endif +}; + +/** + * @brief The number of configured sensors + */ +#define BME280_NUMOF (sizeof(bme280_params) / sizeof(bme280_params[0])) + +/** + * @brief Configuration details of SAUL registry entries + * + * This two dimensional array contains static details of the sensors + * for each device. Please be awar that the indexes are used in + * auto_init_bme280, so make sure the indexes match. + */ +static const saul_reg_info_t bme280_saul_reg_info[BME280_NUMOF][3] = +{ + { + { .name = "bme280-temp" }, + { .name = "bme280-humidity" }, + { .name = "bme280-press" }, + }, +}; + +#ifdef __cplusplus +} +#endif + +#endif /* BME280_PARAMS_H */ +/** @} */ diff --git a/drivers/include/bme280.h b/drivers/include/bme280.h new file mode 100644 index 0000000000000000000000000000000000000000..3a336414bf28c7c101cad39ee8b222477f8da861 --- /dev/null +++ b/drivers/include/bme280.h @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2016 Kees Bakker, SODAQ + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_bme280 BME280 + * @ingroup drivers_sensors + * @brief Device driver interface for the BME280 sensor + * + * @{ + * @file + * @brief Device driver interface for the BME280 sensor. + * + * @details There are three sensor values that can be read: temperature, + * pressure and humidity. The BME280 device usually measures them + * all at once. It is possible to skip measuring either of the + * values by changing the oversampling settings. The driver is + * written in such a way that a measurement is only started from + * the function that reads the temperature. In other words, you + * always have to call bme280_read_temperature, and then optionally + * you can call the other two. + * + * @author Kees Bakker <kees@sodaq.com> + */ + +#ifndef BME280_H +#define BME280_H + +#include "saul.h" +#include "periph/i2c.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Calibration struct for the BME280 sensor + * + * This must be read from the device at startup. + */ +typedef struct { + uint16_t dig_T1; /**< T1 coefficient */ + int16_t dig_T2; /**< T2 coefficient */ + int16_t dig_T3; /**< T3 coefficient */ + + uint16_t dig_P1; /**< P1 coefficient */ + int16_t dig_P2; /**< P2 coefficient */ + int16_t dig_P3; /**< P3 coefficient */ + int16_t dig_P4; /**< P4 coefficient */ + int16_t dig_P5; /**< P5 coefficient */ + int16_t dig_P6; /**< P6 coefficient */ + int16_t dig_P7; /**< P7 coefficient */ + int16_t dig_P8; /**< P8 coefficient */ + int16_t dig_P9; /**< P9 coefficient */ + + uint8_t dig_H1; /**< H1 coefficient */ + int16_t dig_H2; /**< H2 coefficient */ + uint8_t dig_H3; /**< H3 coefficient */ + int16_t dig_H4; /**< H4 coefficient */ + int16_t dig_H5; /**< H5 coefficient */ + int8_t dig_H6; /**< H6 coefficient */ +} bme280_calibration_t; + +/** + * @brief Values for t_sb field of the BME280 config register + */ +typedef enum { + BME280_SB_0_5 = 0, + BME280_SB_62_5 = 1, + BME280_SB_125 = 2, + BME280_SB_250 = 3, + BME280_SB_500 = 4, + BME280_SB_1000 = 5, + BME280_SB_10 = 6, + BME280_SB_20 = 7 +} bme280_t_sb_t; + +/** + * @brief Values for filter field of the BME280 config register + */ +typedef enum { + BME280_FILTER_OFF = 0, + BME280_FILTER_2 = 1, + BME280_FILTER_4 = 2, + BME280_FILTER_8 = 3, + BME280_FILTER_16 = 4, +} bme280_filter_t; + +/** + * @brief Values for mode field of the BME280 ctrl_meas register + */ +typedef enum { + BME280_MODE_SLEEP = 0, + BME280_MODE_FORCED = 1, + BME280_MODE_FORCED2 = 2, /* Same as FORCED */ + BME280_MODE_NORMAL = 3 +} bme280_mode_t; + +/** + * @brief Values for oversampling settings + * + * These values are used for: + * - osrs_h field of the BME280 ctrl_hum register + * - osrs_t field of the BME280 ctrl_meas register + * - osrs_p field of the BME280 ctrl_meas register + */ +typedef enum { + BME280_OSRS_SKIPPED = 0, + BME280_OSRS_X1 = 1, + BME280_OSRS_X2 = 2, + BME280_OSRS_X4 = 3, + BME280_OSRS_X8 = 4, + BME280_OSRS_X16 = 5, +} bme280_osrs_t; + +/** + * @brief Parameters for the BME280 sensor + * + * These parameters are needed to configure the device at startup. + */ +typedef struct { + /* I2C details */ + i2c_t i2c_dev; /**< I2C device which is used */ + uint8_t i2c_addr; /**< I2C address */ + + /* Config Register */ + bme280_t_sb_t t_sb; /**< standby */ + bme280_filter_t filter; /**< filter coefficient */ + uint8_t spi3w_en; /**< Enables 3-wire SPI interface */ + + /* ctrl_meas */ + bme280_mode_t run_mode; /**< ctrl_meas mode */ + bme280_osrs_t temp_oversample; /**< ctrl_meas osrs_t */ + bme280_osrs_t press_oversample; /**< ctrl_meas osrs_p */ + + /* ctrl_hum */ + bme280_osrs_t humid_oversample; /**< ctrl_hum osrs_h */ +} bme280_params_t; + +/** + * @brief Device descriptor for the BME280 sensor + */ +typedef struct { + bme280_params_t params; /**< Device Parameters */ + bme280_calibration_t calibration; /**< Calibration Data */ +} bme280_t; + +/** + * @brief Status and error return codes + */ +enum { + BME280_OK = 0, /**< everything was fine */ + BME280_ERR_I2C = -1, /**< error initializing the I2C bus */ + BME280_ERR_NODEV = -2, /**< did not detect BME280 */ + BME280_ERR_NOCAL = -3, /**< could not read calibration data */ +}; + +/** + * @brief export SAUL endpoints + * @{ + */ +extern const saul_driver_t bme280_temperature_saul_driver; +extern const saul_driver_t bme280_relative_humidity_saul_driver; +extern const saul_driver_t bme280_pressure_saul_driver; +/** @} */ + +/** + * @brief Initialize the given BME280 device + * + * @param[out] dev Initialized device descriptor of BME280 device + * @param[in] params The parameters for the BME280 device (sampling rate, etc) + * + * @return BME280_OK on success + * @return BME280_ERR_I2C + * @return BME280_ERR_NODEV + * @return BME280_ERR_NOCAL + */ +int bme280_init(bme280_t* dev, const bme280_params_t* params); + +/** + * @brief Read temperature value from the given BME280 device, returned in centi °C + * + * @param[in] dev Device descriptor of BME280 device to read from + * + * @returns The temperature in centi Celsius. In case of an error + * it returns INT16_MIN. + */ +int16_t bme280_read_temperature(bme280_t* dev); + +/** + * @brief Read humidity value from the given BME280 device, returned in centi %RH + * + * @details This function should only be called after doing bme280_read_temperature + * first. + * + * @param[in] dev Device descriptor of BME280 device to read from + * + * @returns Humidity in centi %RH (i.e. the percentage times 100) + */ +uint16_t bme280_read_humidity(bme280_t *dev); + +/** + * @brief Read air pressure value from the given BME280 device, returned in PA + * + * @details This function should only be called after doing bme280_read_temperature + * first. + * + * @param[in] dev Device descriptor of BME280 device to read from + * + * @returns The air pressure in Pa + */ +uint32_t bme280_read_pressure(bme280_t *dev); + +#ifdef __cplusplus +} +#endif + +#endif /* BME280_H */ +/** @} */ diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index 5af0e8f00592db6e0e1d33274da05190b57207fb..9ce5db858fef23cdf4f8fcd216aa8afbe1bb2bdc 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -294,6 +294,10 @@ void auto_init(void) extern void auto_init_bmp180(void); auto_init_bmp180(); #endif +#ifdef MODULE_BME280 + extern void auto_init_bme280(void); + auto_init_bme280(); +#endif #ifdef MODULE_JC42 extern void auto_init_jc42(void); auto_init_jc42(); diff --git a/sys/auto_init/saul/auto_init_bme280.c b/sys/auto_init/saul/auto_init_bme280.c new file mode 100644 index 0000000000000000000000000000000000000000..8b8657e2361ba80bdfb3c82b7d497116ead2722b --- /dev/null +++ b/sys/auto_init/saul/auto_init_bme280.c @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 Kees Bakker, SODAQ + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup auto_init_saul + * @{ + * + * @file + * @brief Auto initialization of BME280 driver. + * + * @author Kees Bakker <kees@sodaq.com> + * + * @} + */ + +#ifdef MODULE_BME280 + +#include "log.h" +#include "saul_reg.h" + +#include "bme280_params.h" +#include "bme280.h" + +/** + * @brief Allocation of memory for device descriptors + */ +static bme280_t bme280_devs[BME280_NUMOF]; + +/** + * @brief Memory for the SAUL registry entries + */ +static saul_reg_t saul_entries[BME280_NUMOF * 3]; + +void auto_init_bme280(void) +{ + size_t se_ix = 0; + for (size_t i = 0; i < BME280_NUMOF; i++) { + LOG_DEBUG("[auto_init_saul] initializing BME280 #%u\n", i); + int res = bme280_init(&bme280_devs[i], &bme280_params[i]); + if (res < 0) { + LOG_ERROR("[auto_init_saul] error initializing BME280 #%i\n", i); + } + else { + /* temperature */ + saul_entries[se_ix].dev = &bme280_devs[i]; + saul_entries[se_ix].name = bme280_saul_reg_info[i][0].name; + saul_entries[se_ix].driver = &bme280_temperature_saul_driver; + saul_reg_add(&saul_entries[se_ix]); + se_ix++; + + /* relative humidity */ + saul_entries[se_ix].dev = &bme280_devs[i]; + saul_entries[se_ix].name = bme280_saul_reg_info[i][1].name; + saul_entries[se_ix].driver = &bme280_relative_humidity_saul_driver; + saul_reg_add(&saul_entries[se_ix]); + se_ix++; + + /* pressure */ + saul_entries[se_ix].dev = &bme280_devs[i]; + saul_entries[se_ix].name = bme280_saul_reg_info[i][2].name; + saul_entries[se_ix].driver = &bme280_pressure_saul_driver; + saul_reg_add(&saul_entries[se_ix]); + se_ix++; + } + } +} + +#else +typedef int dont_be_pedantic; +#endif /* MODULE_BME280 */ diff --git a/tests/driver_bme280/Makefile b/tests/driver_bme280/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..17763b6c5caf1fc42978680840dcf35feb70588e --- /dev/null +++ b/tests/driver_bme280/Makefile @@ -0,0 +1,7 @@ +APPLICATION = driver_bme280 +include ../Makefile.tests_common + +USEMODULE += bme280 +USEMODULE += xtimer + +include $(RIOTBASE)/Makefile.include diff --git a/tests/driver_bme280/Readme.md b/tests/driver_bme280/Readme.md new file mode 100644 index 0000000000000000000000000000000000000000..07d01fe1e52c1100eefa02358d1703289a3307c4 --- /dev/null +++ b/tests/driver_bme280/Readme.md @@ -0,0 +1,30 @@ +## About +This is a test application for the BME280 Pressure, Temperature and +Humidity sensor. + +## Usage +The application will initialize the BME280 device and display its +calibration coefficients. More information can be found on the +[Bosch website][1] +And in this [BST-BME280_DS001-11 datasheet] [2] + +Notice that it is necessary to first read the temperature even if only one +of the other values (humidity or pressure) is needed. This is described in +the above mentioned datasheet. + +After initialization, every 2 seconds, the application: +* reads and displays the temperature (d°C); +* reads and displays the pressure (Pa); +* reads and displays the humidity (%rH); + +## Overruling default parameters + +If your device is at a different I2C address than the default (0x77) you +can build the test as follows: + + export CFLAGS=-DBME280_PARAM_I2C_ADDR=0x76 + make -C tests/driver_bme280 BOARD=sodaq-autonomo + +[1]: http://www.bosch-sensortec.com/en/bst/products/all_products/bme280 + +[2]: https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME280_DS001-11.pdf diff --git a/tests/driver_bme280/main.c b/tests/driver_bme280/main.c new file mode 100644 index 0000000000000000000000000000000000000000..ea2dbe70f51d64524c2ec655e95a78fc2adf6cad --- /dev/null +++ b/tests/driver_bme280/main.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 Kees Bakker, SODAQ + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Test application for the BME280 temperature, pressure + * and humidity sensor. + * + * @author Kees Bakker <kees@sodaq.com> + * + * @} + */ + +#include <stdio.h> +#include <inttypes.h> + +#include "bme280_params.h" +#include "bme280.h" +#include "xtimer.h" + +#define MAINLOOP_DELAY (2 * 1000 * 1000u) /* 2 seconds delay between printf's */ + +int main(void) +{ + bme280_t dev; + int result; + + puts("BME280 test application\n"); + + printf("+------------Initializing------------+\n"); + result = bme280_init(&dev, &bme280_params[0]); + if (result == -1) { + puts("[Error] The given i2c is not enabled"); + return 1; + } + + if (result == -2) { + printf("[Error] The sensor did not answer correctly at address 0x%02X\n", bme280_params[0].i2c_addr); + return 1; + } + + printf("Initialization successful\n\n"); + + printf("+------------Calibration Data------------+\n"); + printf("dig_T1: %u\n", dev.calibration.dig_T1); + printf("dig_T2: %i\n", dev.calibration.dig_T2); + printf("dig_T3: %i\n", dev.calibration.dig_T3); + + printf("dig_P1: %u\n", dev.calibration.dig_P1); + printf("dig_P2: %i\n", dev.calibration.dig_P2); + printf("dig_P3: %i\n", dev.calibration.dig_P3); + printf("dig_P4: %i\n", dev.calibration.dig_P4); + printf("dig_P5: %i\n", dev.calibration.dig_P5); + printf("dig_P6: %i\n", dev.calibration.dig_P6); + printf("dig_P7: %i\n", dev.calibration.dig_P7); + printf("dig_P8: %i\n", dev.calibration.dig_P8); + printf("dig_P9: %i\n", dev.calibration.dig_P9); + + printf("dig_H1: %u\n", dev.calibration.dig_H1); + printf("dig_H2: %i\n", dev.calibration.dig_H2); + printf("dig_H3: %i\n", dev.calibration.dig_H3); + printf("dig_H4: %i\n", dev.calibration.dig_H4); + printf("dig_H5: %i\n", dev.calibration.dig_H5); + printf("dig_H6: %i\n", dev.calibration.dig_H6); + + printf("\n+--------Starting Measurements--------+\n"); + while (1) { + int16_t temperature; + uint32_t pressure; + uint16_t humidity; + + /* Get temperature in centi degrees Celsius */ + temperature = bme280_read_temperature(&dev); + + /* Get pressure in Pa */ + pressure = bme280_read_pressure(&dev); + + /* Get pressure in %rH */ + humidity = bme280_read_humidity(&dev); + + printf("Temperature [°C]: %d.%d\n" + "Pressure [Pa]: %lu\n" + "Humidity [%%rH]: %u.%02u\n" + "\n+-------------------------------------+\n", + temperature / 100, (temperature % 100) / 10, + (unsigned long)pressure, + (unsigned int)(humidity / 100), (unsigned int)(humidity % 100)); + + xtimer_usleep(MAINLOOP_DELAY); + } + + return 0; +}