From 7f126e3550d4ee0dd023ece248b50fc07a55bac4 Mon Sep 17 00:00:00 2001 From: Nathael Pajani Date: Thu, 15 Sep 2016 05:13:50 +0200 Subject: [PATCH] Add support for BME280 I2C Barometric, humidity and temperature sensor --- extdrv/bme280_humidity_sensor.c | 322 ++++++++++++++++++++++++ include/extdrv/bme280_humidity_sensor.h | 222 ++++++++++++++++ 2 files changed, 544 insertions(+) create mode 100644 extdrv/bme280_humidity_sensor.c create mode 100644 include/extdrv/bme280_humidity_sensor.h diff --git a/extdrv/bme280_humidity_sensor.c b/extdrv/bme280_humidity_sensor.c new file mode 100644 index 0000000..fe8fd24 --- /dev/null +++ b/extdrv/bme280_humidity_sensor.c @@ -0,0 +1,322 @@ +/**************************************************************************** + * extdrv/bme280_humidity_sensor.c + * + * BME280 I2C Barometric, humidity and temperature sensor driver + * + * Copyright 2016 Nathael Pajani + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + *************************************************************************** */ + + +#include "lib/stdint.h" +#include "core/system.h" +#include "lib/errno.h" +#include "drivers/i2c.h" + +#include "extdrv/bme280_humidity_sensor.h" + + + +/* Check the sensor presence, return 1 if found */ +#define PROBE_BUF_SIZE 3 +int bme280_probe_sensor(struct bme280_sensor_config* conf) +{ + char cmd_buf[PROBE_BUF_SIZE] = { conf->addr, BME280_REGS(chip_id), (conf->addr | I2C_READ_BIT), }; + char ctrl_buf[PROBE_BUF_SIZE] = { I2C_CONT, I2C_DO_REPEATED_START, I2C_CONT, }; + uint8_t id = 0; + + /* Did we already probe the sensor ? */ + if (conf->probe_ok != 1) { + conf->probe_ok = i2c_read(conf->bus_num, &cmd_buf, PROBE_BUF_SIZE, ctrl_buf, &id, 1); + if ((conf->probe_ok == 1) && (id != BME280_ID)) { + conf->probe_ok = 0; + } + } + return conf->probe_ok; +} + + +/* Get calibration data from internal sensor memory + * These values are required to compute the pressure, temperature and humidity values + * from the uncompensated "raw" values read from the sensor ADC result registers. + * Calibration data for pressure and temperature is aligned, packed, and in little + * endian byte order, so it is stored directly to the conf->cal structure. + * Calibration data for humidity is split among different registers and not aligned, + * so we use a temporary data buffer. + * Note : On the LPC822 the I2C bus requires some time to go idle, so we need to + * sleep some time between two conscutive I2C access. + * this should be done in the I2C driver, but is not yet implemented, so it is + * done here. These msleep() calls may be removed for other micro-controllers, and + * when the I2C driver for the LPC822 is imporved. + * Return value: + * Upon successfull completion, returns 0. On error, returns a negative integer + * equivalent to errors from glibc. + */ +#define CAL_CMD_SIZE 3 +int bme280_get_calibration_data(struct bme280_sensor_config* conf) +{ + int ret = 0; + char cmd_buf[CAL_CMD_SIZE] = { conf->addr, BME280_CAL_REGS(T), (conf->addr | I2C_READ_BIT), }; + char ctrl_buf[CAL_CMD_SIZE] = { I2C_CONT, I2C_DO_REPEATED_START, I2C_CONT, }; + uint8_t data[BME280_CAL_REGS_H_LEN]; + + if (bme280_probe_sensor(conf) != 1) { + return -ENODEV; + } + + /* Give some time for I2C bus to go idle */ + msleep(1); + /* Easy part : Temperature and Presure calibration data is packed, aligned, and + * in little endian byte order */ + /* Start by reading temperature calibration data */ + ret = i2c_read(conf->bus_num, cmd_buf, CAL_CMD_SIZE, ctrl_buf, &(conf->cal.T1), BME280_CAL_REGS_T_LEN); + if (ret != BME280_CAL_REGS_T_LEN) { + conf->probe_ok = 0; + return ret; + } + msleep(1); /* Again some time for I2C bus to go idle */ + /* Read pressure calibration data */ + cmd_buf[1] = BME280_CAL_REGS(P); + ret = i2c_read(conf->bus_num, cmd_buf, CAL_CMD_SIZE, ctrl_buf, &(conf->cal.P1), BME280_CAL_REGS_P_LEN); + if (ret != BME280_CAL_REGS_P_LEN) { + conf->probe_ok = 0; + return ret; + } + msleep(1); /* ... */ + + /* Read humidity calibration data. This one is split among bytes and not aligned. use + * temporary data buffer and then copy to conf structure. */ + /* First part */ + cmd_buf[1] = BME280_CAL_REGS(Ha); + ret = i2c_read(conf->bus_num, cmd_buf, CAL_CMD_SIZE, ctrl_buf, data, BME280_CAL_REGS_Ha_LEN); + if (ret != BME280_CAL_REGS_Ha_LEN) { + conf->probe_ok = 0; + return ret; + } + msleep(1); /* ... */ + /* Second part */ + cmd_buf[1] = BME280_CAL_REGS(Hb); + ret = i2c_read(conf->bus_num, cmd_buf, CAL_CMD_SIZE, ctrl_buf, (data + BME280_CAL_REGS_Ha_LEN), BME280_CAL_REGS_Hb_LEN); + if (ret != BME280_CAL_REGS_Hb_LEN) { + conf->probe_ok = 0; + return ret; + } + /* And store in calibration structure */ + conf->cal.H1 = data[0]; + conf->cal.H2 = ((data[1] & 0xFF) | ((data[2] & 0xFF) << 8)); + conf->cal.H3 = data[3]; + conf->cal.H4 = (((data[4] & 0xFF) << 4) | (data[5] & 0x0F)); + conf->cal.H5 = (((data[6] & 0xFF) << 4) | ((data[5] & 0xF0) >> 4)); + conf->cal.H6 = data[7]; + + return 0; +} + +/* Sensor config + * Performs configuration of the sensor and recovers calibration data from sensor's internal + * memory. + * Return value: + * Upon successfull completion, returns 0. On error, returns a negative integer + * equivalent to errors from glibc. + * -EBADFD : I2C not initialized + * -EBUSY : Device or ressource Busy or Arbitration lost + * -EINVAL : Invalid argument (buf) + * -ENODEV : No such device + * -EREMOTEIO : Device did not acknowledge : Any device present ? + * -EIO : Bad one: Illegal start or stop, or illegal state in i2c state machine + */ +#define CONF_BUF_SIZE 7 +int bme280_configure(struct bme280_sensor_config* conf) +{ + int ret = 0; + char cmd_buf[CONF_BUF_SIZE] = { + conf->addr, + BME280_REGS(ctrl_humidity), BME280_CTRL_HUM(conf->humidity_oversampling), + BME280_REGS(ctrl_measure), + BME280_CTRL_MEA(conf->pressure_oversampling, conf->temp_oversampling, conf->mode), + BME280_REGS(config), BME280_CONFIG(conf->standby_len, conf->filter_coeff), + }; + + if (bme280_probe_sensor(conf) != 1) { + return -ENODEV; + } + /* The call to bme280_configure() was certainly the first access to the sensor, so + * the probe part made I2C access, thus leave some delay for I2C to become available again. */ + msleep(1); + /* Send the configuration */ + ret = i2c_write(conf->bus_num, cmd_buf, CONF_BUF_SIZE, NULL); + if (ret != CONF_BUF_SIZE) { + conf->probe_ok = 0; + return -EIO; + } + + ret = bme280_get_calibration_data(conf); + + return ret; +} + + +/* Humidity, Temperature and Pressure Read + * Performs a read of the data from the sensor. + * 'hum', 'temp' and 'pressure': integer addresses for conversion result. + * Return value(s): + * Upon successfull completion, returns 0 and the raw sensor values read are placed in the + * provided integer(s). On error, returns a negative integer equivalent to errors from + * glibc. + * -EBADFD : I2C not initialized + * -EBUSY : Device or ressource Busy or Arbitration lost + * -EINVAL : Invalid argument (buf) + * -ENODEV : No such device + * -EREMOTEIO : Device did not acknowledge : Any device present ? + * -EIO : Bad one: Illegal start or stop, or illegal state in i2c state machine + */ +#define READ_CMD_SIZE 3 +int bme280_sensor_read(struct bme280_sensor_config* conf, uint32_t* pressure, uint32_t* temp, uint16_t* hum) +{ + int ret = 0; + char cmd_buf[READ_CMD_SIZE] = { conf->addr, BME280_REGS(raw_data), (conf->addr | I2C_READ_BIT), }; + char ctrl_buf[READ_CMD_SIZE] = { I2C_CONT, I2C_DO_REPEATED_START, I2C_CONT, }; + uint8_t data[BME280_DATA_SIZE]; + + if (conf->probe_ok != 1) { + if (bme280_probe_sensor(conf) != 1) { + return -ENODEV; + } + msleep(1); + } + + /* Start by reading all data */ + ret = i2c_read(conf->bus_num, cmd_buf, READ_CMD_SIZE, ctrl_buf, data, BME280_DATA_SIZE); + if (ret != BME280_DATA_SIZE) { + conf->probe_ok = 0; + return ret; + } + if (pressure != NULL) { + *pressure = BME280_DATA_20(data[0], data[1], data[2]); + } + if (temp != NULL) { + *temp = BME280_DATA_20(data[3], data[4], data[5]); + } + if (hum != NULL) { + *hum = BME280_DATA_16(data[6], data[7]); + } + + return 0; +} + + + +/* Compute actual temperature from uncompensated temperature + * Param : + * - conf : bme280_sensor_configuration structure, with calibration data read from sensor + * - utemp : uncompensated (raw) temperature value read from sensor + * Returns the value in 0.01 degree Centigrade + * Output value of "5123" equals 51.23 DegC. + */ +int bme280_compensate_temperature(struct bme280_sensor_config* conf, int utemp) +{ + int tmp1 = 0, tmp2 = 0; + int temperature = 0; + + /* Calculate tmp1 */ + tmp1 = ((((utemp >> 3) - ((int)conf->cal.T1 << 1))) * conf->cal.T2) >> 11; + /* Calculate tmp2 */ + tmp2 = (((utemp >> 4) - (int)conf->cal.T1) * ((utemp >> 4) - (int)conf->cal.T1)) >> 12; + tmp2 = (tmp2 * conf->cal.T3) >> 14; + /* Calculate t_fine */ + conf->fine_temp = tmp1 + tmp2; + /* Calculate temperature */ + temperature = (conf->fine_temp * 5 + 128) >> 8; + return temperature; +} + +/* Compute actual pressure from uncompensated pressure + * Returns the value in Pascal(Pa) or 0 on error (invalid value which would cause division by 0). + * Output value of "96386" equals 96386 Pa = 963.86 hPa = 963.86 millibar + */ +uint32_t bme280_compensate_pressure(struct bme280_sensor_config* conf, int uncomp_pressure) +{ + int tmp1 = 0, tmp2 = 0, tmp3 = 0; + uint32_t pressure = 0; + + /* Calculate tmp1 */ + tmp1 = (conf->fine_temp >> 1) - 64000; + /* Calculate tmp2 */ + tmp2 = (((tmp1 >> 2) * (tmp1 >> 2)) >> 11) * conf->cal.P6; + tmp2 = tmp2 + ((tmp1 * conf->cal.P5) << 1); + tmp2 = (tmp2 >> 2) + (conf->cal.P4 << 16); + /* Update tmp1 */ + tmp3 = (conf->cal.P3 * (((tmp1 >> 2) * (tmp1 >> 2)) >> 13)) >> 3; + tmp1 = (tmp3 + ((conf->cal.P2 * tmp1) >> 1)) >> 18; + tmp1 = (((32768 + tmp1)) * (int)conf->cal.P1) >> 15; + /* Calculate pressure */ + pressure = ((uint32_t)(1048576 - uncomp_pressure) - (tmp2 >> 12)) * 3125; + + /* Avoid exception caused by division by zero */ + if (tmp1 == 0) { + return 0; + } + if (pressure < 0x80000000) { + pressure = (pressure << 1) / ((uint32_t)tmp1); + } else { + pressure = (pressure / (uint32_t)tmp1) * 2; + } + + tmp1 = (conf->cal.P9 * ((int)(((pressure >> 3) * (pressure >> 3)) >> 13))) >> 12; + tmp2 = (((int)(pressure >> 2)) * conf->cal.P8) >> 13; + pressure = (uint32_t)((int)pressure + ((tmp1 + tmp2 + conf->cal.P7) >> 4)); + + return pressure; +} + + +/* Compute actual humidity from uncompensated humidity + * Returns the value in 0.01 %rH + * Output value of "4132" equals 41.32 %rH. + */ +uint32_t bme280_compensate_humidity(struct bme280_sensor_config* conf, int uncomp_humidity) +{ + int tmp1 = 0, tmp2 = 0, tmp3 = 0; + uint32_t humidity = 0; + + /* Calculate tmp1 */ + tmp1 = conf->fine_temp - 76800; + /* Calculate tmp2 */ + tmp2 = ((uncomp_humidity << 14) - (conf->cal.H4 << 20) - (conf->cal.H5 * tmp1) + 16384) >> 15; + /* Calculate tmp3 */ + tmp3 = ((((tmp1 * conf->cal.H6) >> 10) * (((tmp1 * (int)conf->cal.H3) >> 11) + 32768)) >> 10) + 2097152; + /* Update tmp1 */ + tmp1 = tmp2 * ((tmp3 * conf->cal.H2 + 8192) >> 14); + tmp1 = tmp1 - (((((tmp1 >> 15) * (tmp1 >> 15)) >> 7) * (int)conf->cal.H1) >> 4); + if (tmp1 < 0) { + tmp1 = 0; + } + if (tmp1 > 419430400) { + tmp1 = 419430400; + } + humidity = (uint32_t)(tmp1 >> 12); + /* Convert from 32bit integer in Q22.10 format (22 integer 10 fractional bits) to a value + * in 0.01 %rH : + * A value of 42313 represents 42313 / 1024 = 41.321 %rH, convert it to 4132, which is 41.32 %rH. + */ + humidity = ((humidity >> 10) * 100) + ((((humidity & 0x3FF) * 1000) >> 10) / 10); + return humidity; + +} + + + diff --git a/include/extdrv/bme280_humidity_sensor.h b/include/extdrv/bme280_humidity_sensor.h new file mode 100644 index 0000000..b8a5208 --- /dev/null +++ b/include/extdrv/bme280_humidity_sensor.h @@ -0,0 +1,222 @@ +/**************************************************************************** + * extdrv/bme280_humidity_sensor.h + * + * BME280 I2C Barometric, humidity and temperature sensor driver + * + * Copyright 2016 Nathael Pajani + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + *************************************************************************** */ + + +#include "lib/stdint.h" +#include "core/system.h" + +struct bme280_calibration_data { + /* Temperature */ + uint16_t T1; /* 0x88 */ + int16_t T2; /* 0x8A */ + int16_t T3; /* 0x8C */ + /* Pressure */ + uint16_t P1; /* 0x8E */ + int16_t P2; /* 0x90 */ + int16_t P3; /* 0x92 */ + int16_t P4; /* 0x94 */ + int16_t P5; /* 0x96 */ + int16_t P6; /* 0x98 */ + int16_t P7; /* 0x9A */ + int16_t P8; /* 0x9C */ + int16_t P9; /* 0x9E */ + /* Humidity */ + uint8_t H1; /* 0xA1 */ + int16_t H2; /* 0xE1 .. 0xE2 */ + uint8_t H3; /* 0xE3 */ + int16_t H4; /* 0xE4 .. 0xE5[3:0] */ + int16_t H5; /* 0xE5[7:4] .. 0xE6 */ + int8_t H6; /* 0xE7 */ +}; + +/* BME280 sensor instance data. */ +struct bme280_sensor_config { + uint8_t addr; + uint8_t bus_num; + uint8_t probe_ok; + uint8_t humidity_oversampling; + uint8_t temp_oversampling; + uint8_t pressure_oversampling; + uint8_t mode; + uint8_t standby_len; + uint8_t filter_coeff; + struct bme280_calibration_data cal; + int fine_temp; +}; + +#define BME280_DATA_SIZE 8 +struct bme280_data { + uint8_t pressure[3]; /* 0xF7 .. 0xF9 */ + uint8_t temperature[3]; /* 0xFA .. 0xFC */ + uint8_t humidity[2]; /* 0xFD .. 0xFE */ +} __attribute__ ((__packed__)); + + +struct bme280_calibration_regs { + /* Temperature */ + uint16_t T[3]; /* 0x88 .. 0x8D */ + /* Pressure */ + uint16_t P[9]; /* 0x8E .. 0x9F */ + /* Humidity */ + uint8_t res0[1]; + uint8_t Ha; /* 0xA1 */ + uint8_t res1[63]; + uint8_t Hb[7]; /* 0xE1 .. 0xE7 */ +}; +#define BME280_CAL_REGS(x) (((uint8_t)offsetof(struct bme280_calibration_regs, x)) + 0x88) +#define BME280_CAL_REGS_T_LEN 6 +#define BME280_CAL_REGS_P_LEN 18 +#define BME280_CAL_REGS_Ha_LEN 1 +#define BME280_CAL_REGS_Hb_LEN 7 +#define BME280_CAL_REGS_H_LEN (BME280_CAL_REGS_Ha_LEN + BME280_CAL_REGS_Hb_LEN) + +struct bme280_internal_regs { + uint8_t chip_id; /* 0xD0 - Value should be 0x60 */ + uint8_t reserved0[15]; + uint8_t reset; /* 0xE0 */ + uint8_t reserved1[17]; + uint8_t ctrl_humidity; /* 0xF2 */ + uint8_t status; /* 0xF3 */ + uint8_t ctrl_measure; /* 0xF4 */ + uint8_t config; /* 0xF5 */ + uint8_t reserved2[1]; + union { + uint8_t raw_data[8]; /* 0xF7 .. 0xFE */ + struct bme280_data data; + }; +} __attribute__ ((__packed__)); +#define BME280_REGS(x) (((uint8_t)offsetof(struct bme280_internal_regs, x)) + 0xD0) + + +#define BME280_ID 0x60 +#define BME280_RESET_MAGIC 0xB6 + +/* Oversampling values */ +#define BME280_SKIP 0x00 +#define BME280_OS_x1 0x01 +#define BME280_OS_x2 0x02 +#define BME280_OS_x4 0x03 +#define BME280_OS_x8 0x04 +#define BME280_OS_x16 0x05 + +/* Mode values */ +#define BME280_SLEEP 0x00 +#define BME280_FORCED 0x01 +#define BME280_NORMAL 0x03 + +/* Control registers helpers */ +#define BME280_CTRL_HUM(hum) ((hum) & 0x07) +#define BME280_CTRL_MEA(pres, temp, mode) \ + ( (((temp) & 0x07) << 5) | (((pres) & 0x07) << 2) | ((mode) & 0x03) ) + + +/* Standby */ +#define BME280_SB_05ms 0x00 +#define BME280_SB_10ms 0x06 +#define BME280_SB_20ms 0x07 +#define BME280_SB_62ms 0x01 +#define BME280_SB_125ms 0x02 +#define BME280_SB_250ms 0x03 +#define BME280_SB_500ms 0x04 +#define BME280_SB_1000ms 0x05 + +/* Filter */ +#define BME280_FILT_OFF 0x00 +#define BME280_FILT_2 0x01 +#define BME280_FILT_4 0x02 +#define BME280_FILT_8 0x03 +#define BME280_FILT_16 0x04 + +/* Config register helper */ +#define BME280_CONFIG(stb, filt) ( (((stb) & 0x07) << 5) | (((filt) & 0x07) << 2) ) + + +/* Data helpers */ +#define BME280_DATA_20(msb, lsb, xlsb) ((((msb) & 0xFF) << 12) | (((lsb) & 0xFF) << 4) | (((xlsb) & 0xF0) >> 4)) +#define BME280_DATA_16(msb, lsb) ((((msb) & 0xFF) << 8) | ((lsb) & 0xFF)) + + + +/* Check the sensor presence, return 1 if found */ +int bme280_probe_sensor(struct bme280_sensor_config* conf); + + +/* Sensor config + * Performs configuration of the sensor and recovers calibration data from sensor's internal + * memory. + * Return value: + * Upon successfull completion, returns 0. On error, returns a negative integer + * equivalent to errors from glibc. + * -EBADFD : I2C not initialized + * -EBUSY : Device or ressource Busy or Arbitration lost + * -EINVAL : Invalid argument (buf) + * -ENODEV : No such device + * -EREMOTEIO : Device did not acknowledge : Any device present ? + * -EIO : Bad one: Illegal start or stop, or illegal state in i2c state machine + */ +int bme280_configure(struct bme280_sensor_config* conf); + + + +/* Humidity, Temperature and Pressure Read + * Performs a read of the data from the sensor. + * 'hum', 'temp' and 'pressure': integer addresses for conversion result. + * Return value(s): + * Upon successfull completion, returns 0 and the raw sensor values read are placed in the + * provided integer(s). On error, returns a negative integer equivalent to errors from + * glibc. + * -EBADFD : I2C not initialized + * -EBUSY : Device or ressource Busy or Arbitration lost + * -EINVAL : Invalid argument (buf) + * -ENODEV : No such device + * -EREMOTEIO : Device did not acknowledge : Any device present ? + * -EIO : Bad one: Illegal start or stop, or illegal state in i2c state machine + */ +int bme280_sensor_read(struct bme280_sensor_config* conf, uint32_t* temp, uint32_t* pressure, uint16_t* hum); + + +/* Compute actual temperature from uncompensated temperature + * Param : + * - conf : bme280_sensor_configuration structure, with calibration data read from sensor + * - utemp : uncompensated (raw) temperature value read from sensor + * Returns the value in 0.01 degree Centigrade + * Output value of "5123" equals 51.23 DegC. + */ +int bme280_compensate_temperature(struct bme280_sensor_config* conf, int utemp); + + +/* Compute actual pressure from uncompensated pressure + * Returns the value in Pascal(Pa) or 0 on error (invalid value which would cause division by 0). + * Output value of "96386" equals 96386 Pa = 963.86 hPa = 963.86 millibar + */ +uint32_t bme280_compensate_pressure(struct bme280_sensor_config* conf, int uncomp_pressure); + + +/* Compute actual humidity from uncompensated humidity + * Returns the value in 0.01 %rH + * Output value of "4132" equals 41.32 %rH. + */ +uint32_t bme280_compensate_humidity(struct bme280_sensor_config* conf, int uncomp_humidity); + + + -- 2.43.0