Add support and sample of use of BME280 temp/humidity/pressure sensor

This commit is contained in:
Schoumi 2022-08-29 11:39:54 +02:00
parent b90795782a
commit fb82a6f0b2
3 changed files with 588 additions and 0 deletions

View file

@ -0,0 +1,304 @@
/****************************************************************************
* extdrv/bme280_humidity_sensor.c
*
* BME280 I2C Barometric, humidity and temperature sensor driver
*
* Copyright 2016 Nathael Pajani <nathael.pajani@ed3l.fr>
*
*
* 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 3 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 <http://www.gnu.org/licenses/>.
*
*************************************************************************** */
#include <cstdint>
#include <errno.h>
#include "bme280.h"
/* 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.
*/
#define CONF_BUF_SIZE 6
bme280::bme280(MicroBit* uB, MicroBitI2C* uBi2c, uint8_t addr, uint8_t humidity_os, uint8_t temp_os,
uint8_t pressure_os, uint8_t sensor_mode, uint8_t standby, uint8_t coeff)
:
uBit(uB), i2c(uBi2c), address(addr), probe_ok(0), humidity_oversampling(humidity_os), temp_oversampling(temp_os), pressure_oversampling(pressure_os), mode(sensor_mode), standby_len(standby), filter_coeff(coeff)
{
int ret = 0;
char cmd_buf[CONF_BUF_SIZE] = {
BME280_REGS(ctrl_humidity), (uint8_t)BME280_CTRL_HUM(humidity_oversampling),
BME280_REGS(ctrl_measure), (uint8_t)BME280_CTRL_MEA(pressure_oversampling, temp_oversampling, mode),
BME280_REGS(config), (uint8_t)BME280_CONFIG(standby_len, filter_coeff),
};
if (probe_sensor() != 1) {
probe_ok = 0;
uBit->display.scroll("No Device");
}
/* Send the configuration */
ret = i2c->write(address, cmd_buf, CONF_BUF_SIZE);
if (ret != MICROBIT_OK) {
probe_ok = 0;
uBit->display.scroll("Conf Error");
}
/* Get the calibration data */
if (get_calibration_data() != MICROBIT_OK)
uBit->display.scroll("Calibration Error");
}
/* Check the sensor presence, return 1 if found */
#define PROBE_BUF_SIZE 1
int bme280::probe_sensor()
{
char cmd_buf[PROBE_BUF_SIZE] = {BME280_REGS(chip_id)};
uint8_t id = 0;
/* Did we already probe the sensor ? */
if (probe_ok != 1) {
i2c->write(address, cmd_buf,1,true);
int ret = i2c->read(address, (char*)&id, 1);
if (ret == MICROBIT_OK && id != BME280_ID) {
probe_ok = 0;
} else {
probe_ok = 1;
}
}
return 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 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 1
int bme280::get_calibration_data()
{
int ret = 0;
char cmd_buf[CAL_CMD_SIZE] = { BME280_CAL_REGS(T)};
/* Give some time for I2C bus to go idle */
uBit->sleep(1);
/* Easy part : Temperature and Presure calibration data is packed, aligned, and
* in little endian byte order */
/* Start by reading temperature calibration data */
i2c->write(address,cmd_buf,1,true);
ret = i2c->read(address, (char*)&(cal.T1), BME280_CAL_REGS_T_LEN);
if (ret != MICROBIT_OK) {
probe_ok = 0;
return ret;
}
uBit->sleep(1); /* Again some time for I2C bus to go idle */
/* Read pressure calibration data */
cmd_buf[0] = BME280_CAL_REGS(P);
i2c->write(address,cmd_buf,1,true);
ret = i2c->read(address, (char*)&(cal.P1), BME280_CAL_REGS_P_LEN);
if (ret != MICROBIT_OK) {
probe_ok = 0;
return ret;
}
uBit->sleep(1); /* ... */
/* Read humidity calibration data. This one is split among bytes and not aligned. use
* temporary data buffer and then copy to calibration structure. */
/* First part */
uint8_t data[BME280_CAL_REGS_H_LEN];
cmd_buf[0] = BME280_CAL_REGS(Ha);
i2c->write(address,cmd_buf,1,true);
ret = i2c->read(address, (char*)data, BME280_CAL_REGS_Ha_LEN);
if (ret != MICROBIT_OK) {
probe_ok = 0;
return ret;
}
uBit->sleep(1); /* ... */
/* Second part */
cmd_buf[0] = BME280_CAL_REGS(Hb);
i2c->write(address,cmd_buf,1,true);
ret = i2c->read(address, (char*)data+BME280_CAL_REGS_Ha_LEN, BME280_CAL_REGS_Hb_LEN);
if (ret != MICROBIT_OK) {
probe_ok = 0;
return ret;
}
/* And store in calibration structure */
cal.H1 = data[0];
cal.H2 = ((data[1] & 0xFF) | ((data[2] & 0xFF) << 8));
cal.H3 = data[3];
cal.H4 = (((data[4] & 0xFF) << 4) | (data[5] & 0x0F));
cal.H5 = (((data[6] & 0xFF) << 4) | ((data[5] & 0xF0) >> 4));
cal.H6 = data[7];
return MICROBIT_OK;
}
/* 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.
*/
#define READ_CMD_SIZE 1
int bme280::sensor_read(uint32_t* pressure, int32_t* temp, uint16_t* hum)
{
int ret = 0;
char cmd_buf[READ_CMD_SIZE] = { BME280_REGS(raw_data)};
uint8_t data[BME280_DATA_SIZE];
if (probe_ok != 1) {
if (probe_sensor() != 1) {
return -1;
}
uBit->sleep(1);
}
/* Start by reading all data */
i2c->write(address,cmd_buf,1,true);
ret = i2c->read(address, (char*)data, BME280_DATA_SIZE);
if (ret != MICROBIT_OK) {
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 :
* - 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(int utemp)
{
int tmp1 = 0, tmp2 = 0;
int temperature = 0;
/* Calculate tmp1 */
tmp1 = ((((utemp >> 3) - ((int)cal.T1 << 1))) * cal.T2) >> 11;
/* Calculate tmp2 */
tmp2 = (((utemp >> 4) - (int)cal.T1) * ((utemp >> 4) - (int)cal.T1)) >> 12;
tmp2 = (tmp2 * cal.T3) >> 14;
/* Calculate t_fine */
fine_temp = tmp1 + tmp2;
/* Calculate temperature */
temperature = (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(int uncomp_pressure)
{
int tmp1 = 0, tmp2 = 0, tmp3 = 0;
uint32_t pressure = 0;
/* Calculate tmp1 */
tmp1 = (fine_temp >> 1) - 64000;
/* Calculate tmp2 */
tmp2 = (((tmp1 >> 2) * (tmp1 >> 2)) >> 11) * cal.P6;
tmp2 = tmp2 + ((tmp1 * cal.P5) << 1);
tmp2 = (tmp2 >> 2) + (cal.P4 << 16);
/* Update tmp1 */
tmp3 = (cal.P3 * (((tmp1 >> 2) * (tmp1 >> 2)) >> 13)) >> 3;
tmp1 = (tmp3 + ((cal.P2 * tmp1) >> 1)) >> 18;
tmp1 = (((32768 + tmp1)) * (int)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 = (cal.P9 * ((int)(((pressure >> 3) * (pressure >> 3)) >> 13))) >> 12;
tmp2 = (((int)(pressure >> 2)) * cal.P8) >> 13;
pressure = (uint32_t)((int)pressure + ((tmp1 + tmp2 + 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(int uncomp_humidity)
{
int tmp1 = 0, tmp2 = 0, tmp3 = 0;
uint32_t humidity = 0;
/* Calculate tmp1 */
tmp1 = fine_temp - 76800;
/* Calculate tmp2 */
tmp2 = ((uncomp_humidity << 14) - (cal.H4 << 20) - (cal.H5 * tmp1) + 16384) >> 15;
/* Calculate tmp3 */
tmp3 = ((((tmp1 * cal.H6) >> 10) * (((tmp1 * (int)cal.H3) >> 11) + 32768)) >> 10) + 2097152;
/* Update tmp1 */
tmp1 = tmp2 * ((tmp3 * cal.H2 + 8192) >> 14);
tmp1 = tmp1 - (((((tmp1 >> 15) * (tmp1 >> 15)) >> 7) * (int)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;
}

View file

@ -0,0 +1,224 @@
/****************************************************************************
* bme280.h
*
* BME280 I2C Barometric, humidity and temperature sensor driver
*
* Copyright 2016 Nathael Pajani <nathael.pajani@ed3l.fr>
* Copyright 2022 Anthony Chomienne <anthony@mob-dev.fr>
*
*
* 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 3 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 <http://www.gnu.org/licenses/>.
*
*************************************************************************** */
/**
* This code is mainly an adaptation of code provided by Techno-Innov (Nathael Pajani <nathael.pajani@ed3l.fr>)
* www.techno-innov.fr
* http://git.techno-innov.fr
*/
#ifndef BME280_H
#define BME280_H
#include <cstdint>
#include <MicroBit.h>
#define BME280_ADDR 0xEC
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 */
};
#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))
class bme280 {
public:
/* Sensor config
* Performs configuration of the sensor and recovers calibration data from sensor's internal
* memory.
*/
bme280(MicroBit* uB, MicroBitI2C* uBi2c, uint8_t addr = BME280_ADDR, uint8_t humidity_oversampling = BME280_OS_x16,
uint8_t temp_oversampling = BME280_OS_x16, uint8_t pressure_oversampling = BME280_OS_x16, uint8_t mode = BME280_NORMAL,
uint8_t standby_len = BME280_SB_62ms, uint8_t filter_coeff = BME280_FILT_OFF);
/* Check the sensor presence, return 1 if found */
int probe_sensor();
/* 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.
*/
int sensor_read(uint32_t* pressure, int32_t* temp, 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 compensate_temperature(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 compensate_pressure(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 compensate_humidity(int uncomp_humidity);
private:
int get_calibration_data();
MicroBit* uBit;
MicroBitI2C* i2c;
uint8_t address;
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;
};
#endif /* BME280_H */

View file

@ -0,0 +1,60 @@
/*
The MIT License (MIT)
Copyright (c) 2016 British Broadcasting Corporation.
This software is provided by Lancaster University by arrangement with the BBC.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "MicroBit.h"
#include "bme280.h"
MicroBit uBit;
MicroBitI2C i2c(I2C_SDA0,I2C_SCL0);
int main()
{
// Initialise the micro:bit runtime.
uBit.init();
bme280 bme(&uBit,&i2c);
uint32_t pressure = 0;
int32_t temp = 0;
uint16_t humidite = 0;
while(true)
{
bme.sensor_read(&pressure, &temp, &humidite);
int tmp = bme.compensate_temperature(temp);
int pres = bme.compensate_pressure(pressure)/100;
int hum = bme.compensate_humidity(humidite);
ManagedString display = "Temp:" + ManagedString(tmp/100) + "." + (tmp > 0 ? ManagedString(tmp%100): ManagedString((-tmp)%100))+" C";
uBit.display.scroll(display.toCharArray());
display = "Humi:" + ManagedString(hum/100) + "." + ManagedString(tmp%100)+" rH";
uBit.display.scroll(display.toCharArray());
display = "Pres:" + ManagedString(pres)+" hPa";
uBit.display.scroll(display.toCharArray());
uBit.sleep(1000);
}
release_fiber();
}