#ifndef SENS_BME280 #define SENS_BME280 #include "../../Platforms.h" #include "../../Debug.h" // https://www.mouser.com/datasheet/2/783/BST-BME280-DS002-1509607.pdf template class BME280 { I2C& i2c; static constexpr const char* NAME = "BME280"; static constexpr uint8_t ADDR7_1 = 0b1110110; // 0x76 static constexpr uint8_t ADDR7_2 = 0b1110111; // 0x77 static constexpr uint8_t MODE_SLEEP = 0b00; static constexpr uint8_t MODE_FORCED = 0b01; // manual sampling static constexpr uint8_t MODE_NORMAL = 0b11; // periodic background sampling uint8_t ADDR7; static constexpr uint8_t REG_CTRL1 = 0xF2; // humidity static constexpr uint8_t REG_CTRL2 = 0xF4; // temp, pressure, mode static constexpr uint8_t REG_CONFIG = 0xF5; static constexpr uint8_t REG_STATUS = 0xF3; static constexpr uint8_t REG_PRESSURE = 0xF7; static constexpr uint8_t REG_TEMPERATURE= 0xFA; static constexpr uint8_t REG_HUMIDITY = 0xFD; static constexpr uint8_t REG_DIG_T1 = 0x88; static constexpr uint8_t REG_DIG_T2 = 0x8A; static constexpr uint8_t REG_DIG_T3 = 0x8C; bool started = false; /** internal sensor calibration values */ struct Calibration { uint16_t dig_T1 = 0; int16_t dig_T2 = 0; int16_t dig_T3 = 0; uint16_t dig_P1 = 0; int16_t dig_P2 = 0; int16_t dig_P3 = 0; int16_t dig_P4 = 0; int16_t dig_P5 = 0; int16_t dig_P6 = 0; int16_t dig_P7 = 0; int16_t dig_P8 = 0; int16_t dig_P9 = 0; uint8_t dig_H1 = 0; int16_t dig_H2 = 0; uint8_t dig_H3 = 0; int16_t dig_H4 = 0; int16_t dig_H5 = 0; int8_t dig_H6 = 0; } cal; public: struct Result { float temp; float humi; float pres; }; enum class Interval : uint8_t { INT_500_US = 0b000, INT_62_MS = 0b001, INT_125_MS = 0b010, INT_250_MS = 0b011, INT_500_MS = 0b100, INT_1000_MS = 0b101, INT_10_MS = 0b110, INT_20_MS = 0b111, }; enum class Oversample : uint8_t { X1 = 0b001, X2 = 0b010, X4 = 0b011, X8 = 0b100, X16 = 0b101, }; private: struct Config { Oversample temp = Oversample::X2; Oversample pres = Oversample::X16; Oversample humi = Oversample::X2; uint8_t mode = MODE_NORMAL; Interval interval = Interval::INT_500_MS; uint8_t fir = 0b000; // fir filter disabled } cfg; public: BME280(I2C& i2c, uint8_t addrOffset = 0) : i2c(i2c), ADDR7(ADDR7_1 + addrOffset) { } bool isPresent() { return i2c.query(ADDR7); } void init() { readCalib(); } void setSampling(Oversample temp, Oversample pres, Oversample humi) { cfg.temp = temp; cfg.pres = pres; cfg.humi = humi; } uint8_t getStatus() { uint8_t res[1]; i2c.readReg(ADDR7, REG_STATUS, 1, res); return 0; } /** start periodic background measurement (tends to sensor-self-heating??) */ void measurePeriodic(Interval ival) { cfg.mode = MODE_NORMAL; cfg.interval = ival; commitConfig(); } /** measure only once, takes some time before NEW results are present (seems more stable in terms of temperature) */ void measureOnce() { cfg.mode = MODE_FORCED; commitConfig(); } /** get the most recent readings */ Result getAll() { Result res; res.temp = getTemperature(); res.humi = getHumidity(); res.pres = getPressure(); return res; } /** get most recent pressure reading (hPa) */ float getPressure() { uint8_t res[3]; i2c.readReg(ADDR7, REG_PRESSURE, 3, res); //os_printf("res: %d - %d - %d \n", res[0], res[1], res[2]); const uint32_t tmp = ((res[0] << 16) | (res[1] << 8) | (res[2] << 0)) >> 4; const uint32_t pres = BME280_compensate_P_int64(tmp); const float presF = pres / 256.0f / 100.0f; // convert from Q24.8 to float and from Pa to hPa //const uint32_t p0 = pres / 256; //const uint32_t p1 = (uint32_t) presF; //const uint32_t p2 = (presF - p1) * 100000; //Log::addInfo((NAME, "[pres] ADC: %d -> %d Pa | %d.%d hPa", tmp, p0, p1,p2); return presF; } /** get most recent temperature reading */ float getTemperature() { uint8_t res[3]; i2c.readReg(ADDR7, REG_TEMPERATURE, 3, res); const uint32_t tmp = ((res[0] << 16) | (res[1] << 8) | (res[2] << 0)) >> 4; const int32_t temp = BME280_compensate_T_int32(tmp); const float tempF = temp / 100.0f; return tempF; } /** get the most recent humidity reading */ float getHumidity() { uint8_t res[2]; i2c.readReg(ADDR7, REG_HUMIDITY, 2, res); //os_printf("res: %d - %d \n", res[0], res[1]); const uint32_t tmp = (res[0] << 8) | (res[1] << 0); const int32_t humi = bme280_compensate_H_int32(tmp); const float humiF = humi / 1024.0f; //const uint16_t h0 = humi / 1024; //const uint16_t h1 = (uint16_t) humiF; //const uint16_t h2 = (humiF - humi) * 10000; //Log::addInfo((NAME, "[humi] ADC: %d -> %d -> %d.%d %%", tmp, h0, h1,h2); return humiF; } private: void readCalib() { Log::addInfo(NAME, "readCalib()"); // read all 24 calibration bytes for temperature and pressure uint8_t b1[24]; i2c.readReg(ADDR7, REG_DIG_T1, 24, b1); cal.dig_T1 = (b1[1] << 8) | b1[0]; cal.dig_T2 = (b1[3] << 8) | b1[2]; cal.dig_T3 = (b1[5] << 8) | b1[4]; cal.dig_P1 = (b1[7] << 8) | b1[6]; cal.dig_P2 = (b1[9] << 8) | b1[8]; cal.dig_P3 = (b1[11] << 8) | b1[10]; cal.dig_P4 = (b1[13] << 8) | b1[12]; cal.dig_P5 = (b1[15] << 8) | b1[14]; cal.dig_P6 = (b1[17] << 8) | b1[16]; cal.dig_P7 = (b1[19] << 8) | b1[18]; cal.dig_P8 = (b1[21] << 8) | b1[20]; cal.dig_P9 = (b1[23] << 8) | b1[22]; // humidity i2c.readReg(ADDR7, 0xA1, 1, &cal.dig_H1); i2c.readReg(ADDR7, 0xE1, 7, b1); cal.dig_H2 = (b1[1] << 8) | b1[0]; cal.dig_H3 = b1[2]; cal.dig_H4 = (b1[3] << 4) | (b1[4] & 0b00001111); cal.dig_H5 = (b1[5] << 4) | ((b1[4] & 0b11110000) >> 4); cal.dig_H6 = (b1[6]); //os_printf("calib temp: %d %d %d\n", cal.dig_T1, cal.dig_T2, cal.dig_T3); //os_printf("calib pres: %d %d %d %d %d %d %d %d %d\n", cal.dig_P1, cal.dig_P2, cal.dig_P3, cal.dig_P4, cal.dig_P5, cal.dig_P6, cal.dig_P7, cal.dig_P8, cal.dig_P9); //os_printf("calib humi: %d %d %d %d %d %d\n", cal.dig_H1, cal.dig_H2, cal.dig_H3, cal.dig_H4, cal.dig_H5, cal.dig_H6); Log::addInfo(NAME, "calTemp: %d %d %d", cal.dig_T1, cal.dig_T2, cal.dig_T3); Log::addInfo(NAME, "calPres: %d %d %d %d %d %d %d %d %d", cal.dig_P1, cal.dig_P2, cal.dig_P3, cal.dig_P4, cal.dig_P5, cal.dig_P6, cal.dig_P7, cal.dig_P8, cal.dig_P9); Log::addInfo(NAME, "calHumi: %d %d %d %d %d %d", cal.dig_H1, cal.dig_H2, cal.dig_H3, cal.dig_H4, cal.dig_H5, cal.dig_H6); } void commitConfig() { const uint8_t ctrl1 = (uint8_t(cfg.humi) << 0); const uint8_t ctrl2 = (uint8_t(cfg.temp) << 5) | (uint8_t(cfg.pres) << 2) | (uint8_t(cfg.mode) << 0); const uint8_t conf = (uint8_t(cfg.interval) << 5) | (cfg.fir << 2); i2c.writeReg8(ADDR7, REG_CTRL1, MODE_SLEEP); i2c.writeReg8(ADDR7, REG_CTRL1, ctrl1); i2c.writeReg8(ADDR7, REG_CTRL2, ctrl2); i2c.writeReg8(ADDR7, REG_CONFIG, conf); } /** conversions from ADC values to real-world values. from Bosch BMP280 manual! */ using BME280_S32_t = int32_t; using BME280_U32_t = uint32_t; using BME280_S64_t = int64_t; BME280_S32_t t_fine = 0; // Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC. // t_fine carries fine temperature as global value BME280_S32_t BME280_compensate_T_int32(BME280_S32_t adc_T) { BME280_S32_t var1, var2, T; var1 = ((((adc_T>>3) - ((BME280_S32_t)cal.dig_T1<<1))) * ((BME280_S32_t)cal.dig_T2)) >> 11; var2 = (((((adc_T>>4) - ((BME280_S32_t)cal.dig_T1)) * ((adc_T>>4) - ((BME280_S32_t)cal.dig_T1))) >> 12) * ((BME280_S32_t)cal.dig_T3)) >> 14; t_fine = var1 + var2; T = (t_fine * 5 + 128) >> 8; return T; } // Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional bits). // Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa BME280_U32_t BME280_compensate_P_int64(BME280_S32_t adc_P) { BME280_S64_t var1, var2, p; var1 = ((BME280_S64_t)t_fine) - 128000; var2 = var1 * var1 * (BME280_S64_t)cal.dig_P6; var2 = var2 + ((var1*(BME280_S64_t)cal.dig_P5)<<17); var2 = var2 + (((BME280_S64_t)cal.dig_P4)<<35); var1 = ((var1 * var1 * (BME280_S64_t)cal.dig_P3)>>8) + ((var1 * (BME280_S64_t)cal.dig_P2)<<12); var1 = (((((BME280_S64_t)1l)<<47)+var1))*((BME280_S64_t)cal.dig_P1)>>33; if (var1 == 0) {return 0;} // avoid exception caused by division by zero p = 1048576-adc_P; p = (((p<<31)-var2)*3125)/var1; var1 = (((BME280_S64_t)cal.dig_P9) * (p>>13) * (p>>13)) >> 25; var2 = (((BME280_S64_t)cal.dig_P8) * p) >> 19; p = ((p + var1 + var2) >> 8) + (((BME280_S64_t)cal.dig_P7)<<4); return (BME280_U32_t)p; } // Returns humidity in %RH as unsigned 32 bit integer in Q22.10 format (22 integer and 10 fractional bits). // Output value of “47445” represents 47445/1024 = 46.333 %RH BME280_U32_t bme280_compensate_H_int32(BME280_S32_t adc_H) { BME280_S32_t v_x1_u32r; v_x1_u32r = (t_fine - ((BME280_S32_t)76800)); v_x1_u32r = (((((adc_H << 14) - (((BME280_S32_t)cal.dig_H4) << 20) - (((BME280_S32_t)cal.dig_H5) * v_x1_u32r)) + ((BME280_S32_t)16384)) >> 15) * (((((((v_x1_u32r * ((BME280_S32_t)cal.dig_H6)) >> 10) * (((v_x1_u32r * ((BME280_S32_t)cal.dig_H3)) >> 11) + ((BME280_S32_t)32768))) >> 10) + ((BME280_S32_t)2097152)) * ((BME280_S32_t)cal.dig_H2) + 8192) >> 14)); v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((BME280_S32_t)cal.dig_H1)) >> 4)); v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r); v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r); return (BME280_U32_t)(v_x1_u32r>>12); } }; #endif