// ----------------------------------------------------------------------------- // BME280/BMP280 Sensor over I2C // Copyright (C) 2017-2019 by Xose Pérez // ----------------------------------------------------------------------------- #if SENSOR_SUPPORT && BMX280_SUPPORT #pragma once #undef I2C_SUPPORT #define I2C_SUPPORT 1 // Explicitly request I2C support. #include #include "../utils.h" #include "I2CSensor.h" #define BMX280_CHIP_BMP280 0x58 #define BMX280_CHIP_BME280 0x60 #define BMX280_REGISTER_DIG_T1 0x88 #define BMX280_REGISTER_DIG_T2 0x8A #define BMX280_REGISTER_DIG_T3 0x8C #define BMX280_REGISTER_DIG_P1 0x8E #define BMX280_REGISTER_DIG_P2 0x90 #define BMX280_REGISTER_DIG_P3 0x92 #define BMX280_REGISTER_DIG_P4 0x94 #define BMX280_REGISTER_DIG_P5 0x96 #define BMX280_REGISTER_DIG_P6 0x98 #define BMX280_REGISTER_DIG_P7 0x9A #define BMX280_REGISTER_DIG_P8 0x9C #define BMX280_REGISTER_DIG_P9 0x9E #define BMX280_REGISTER_DIG_H1 0xA1 #define BMX280_REGISTER_DIG_H2 0xE1 #define BMX280_REGISTER_DIG_H3 0xE3 #define BMX280_REGISTER_DIG_H4 0xE4 #define BMX280_REGISTER_DIG_H5 0xE5 #define BMX280_REGISTER_DIG_H6 0xE7 #define BMX280_REGISTER_CHIPID 0xD0 #define BMX280_REGISTER_VERSION 0xD1 #define BMX280_REGISTER_SOFTRESET 0xE0 #define BMX280_REGISTER_CAL26 0xE1 #define BMX280_REGISTER_CONTROLHUMID 0xF2 #define BMX280_REGISTER_CONTROL 0xF4 #define BMX280_REGISTER_CONFIG 0xF5 #define BMX280_REGISTER_PRESSUREDATA 0xF7 #define BMX280_REGISTER_TEMPDATA 0xFA #define BMX280_REGISTER_HUMIDDATA 0xFD class BMX280Sensor : public I2CSensor { public: static unsigned char addresses[2]; // --------------------------------------------------------------------- // Public // --------------------------------------------------------------------- BMX280Sensor(): I2CSensor() { _sensor_id = SENSOR_BMX280_ID; } // --------------------------------------------------------------------- // Sensor API // --------------------------------------------------------------------- // Initialization method, must be idempotent void begin() { if (!_dirty) return; _init(); _dirty = !_ready; } // Descriptive name of the sensor String description() { char buffer[20]; snprintf(buffer, sizeof(buffer), "%s @ I2C (0x%02X)", _chip == BMX280_CHIP_BME280 ? "BME280" : "BMP280", _address); return String(buffer); } // Type for slot # index unsigned char type(unsigned char index) { unsigned char i = 0; #if BMX280_TEMPERATURE > 0 if (index == i++) return MAGNITUDE_TEMPERATURE; #endif #if BMX280_PRESSURE > 0 if (index == i++) return MAGNITUDE_PRESSURE; #endif #if BMX280_HUMIDITY > 0 if (_chip == BMX280_CHIP_BME280) { if (index == i) return MAGNITUDE_HUMIDITY; } #endif return MAGNITUDE_NONE; } // Number of decimals for a magnitude (or -1 for default) signed char decimals(unsigned char type) { // These numbers of decimals correspond to maximum sensor resolution settings switch (type) { case MAGNITUDE_TEMPERATURE: return 3; case MAGNITUDE_PRESSURE: return 4; case MAGNITUDE_HUMIDITY: return 2; } return -1; } // Pre-read hook (usually to populate registers with up-to-date data) virtual void pre() { if (_run_init) { i2cClearBus(); _init(); } if (_chip == 0) { _error = SENSOR_ERROR_UNKNOWN_ID; return; } _error = SENSOR_ERROR_OK; #if BMX280_MODE == 1 _forceRead(); #endif _error = _read(); if (_error != SENSOR_ERROR_OK) { _run_init = true; } } // Current value for slot # index double value(unsigned char index) { unsigned char i = 0; #if BMX280_TEMPERATURE > 0 if (index == i++) return _temperature; #endif #if BMX280_PRESSURE > 0 if (index == i++) return _pressure / 100; #endif #if BMX280_HUMIDITY > 0 if (_chip == BMX280_CHIP_BME280) { if (index == i) return _humidity; } #endif return 0; } // Load the configuration manifest static void manifest(JsonArray& sensors) { char buffer[10]; JsonObject& sensor = sensors.createNestedObject(); sensor["sensor_id"] = SENSOR_BMX280_ID; JsonArray& fields = sensor.createNestedArray("fields"); { JsonObject& field = fields.createNestedObject(); field["tag"] = UI_TAG_SELECT; field["name"] = "address"; field["label"] = "Address"; JsonArray& options = field.createNestedArray("options"); { JsonObject& option = options.createNestedObject(); option["name"] = "auto"; option["value"] = 0; } for (unsigned char i=0; i< sizeof(BMX280Sensor::addresses); i++) { JsonObject& option = options.createNestedObject(); snprintf(buffer, sizeof(buffer), "0x%02X", BMX280Sensor::addresses[i]); option["name"] = String(buffer); option["value"] = BMX280Sensor::addresses[i]; } } }; void getConfig(JsonObject& root) { root["sensor_id"] = _sensor_id; root["address"] = _address; }; void setConfig(JsonObject& root) { if (root.containsKey("address")) setAddress(root["address"]); }; protected: void _init() { // Make sure sensor had enough time to turn on. BMX280 requires 2ms to start up nice_delay(10); // No chip ID by default _chip = 0; // I2C auto-discover _address = _begin_i2c(_address, sizeof(BMX280Sensor::addresses), BMX280Sensor::addresses); if (_address == 0) return; // Check sensor correctly initialized _chip = i2c_read_uint8(_address, BMX280_REGISTER_CHIPID); if ((_chip != BMX280_CHIP_BME280) && (_chip != BMX280_CHIP_BMP280)) { _chip = 0; i2cReleaseLock(_address); _previous_address = 0; _error = SENSOR_ERROR_UNKNOWN_ID; // Setting _address to 0 forces auto-discover // This might be necessary at this stage if there is a // different sensor in the hardcoded address _address = 0; return; } _count = 0; #if BMX280_TEMPERATURE > 0 ++_count; #endif #if BMX280_PRESSURE > 0 ++_count; #endif #if BMX280_HUMIDITY > 0 if (_chip == BMX280_CHIP_BME280) ++_count; #endif _readCoefficients(); unsigned char data = 0; i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, data); data = (BMX280_STANDBY << 0x5) & 0xE0; data |= (BMX280_FILTER << 0x02) & 0x1C; i2c_write_uint8(_address, BMX280_REGISTER_CONFIG, data); data = (BMX280_HUMIDITY) & 0x07; i2c_write_uint8(_address, BMX280_REGISTER_CONTROLHUMID, data); data = (BMX280_TEMPERATURE << 5) & 0xE0; data |= (BMX280_PRESSURE << 2) & 0x1C; data |= (BMX280_MODE) & 0x03; i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, data); _measurement_delay = _measurementTime(); _run_init = false; _ready = true; } void _readCoefficients() { _bmx280_calib.dig_T1 = i2c_read_uint16_le(_address, BMX280_REGISTER_DIG_T1); _bmx280_calib.dig_T2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_T2); _bmx280_calib.dig_T3 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_T3); _bmx280_calib.dig_P1 = i2c_read_uint16_le(_address, BMX280_REGISTER_DIG_P1); _bmx280_calib.dig_P2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P2); _bmx280_calib.dig_P3 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P3); _bmx280_calib.dig_P4 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P4); _bmx280_calib.dig_P5 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P5); _bmx280_calib.dig_P6 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P6); _bmx280_calib.dig_P7 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P7); _bmx280_calib.dig_P8 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P8); _bmx280_calib.dig_P9 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P9); _bmx280_calib.dig_H1 = i2c_read_uint8(_address, BMX280_REGISTER_DIG_H1); _bmx280_calib.dig_H2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_H2); _bmx280_calib.dig_H3 = i2c_read_uint8(_address, BMX280_REGISTER_DIG_H3); _bmx280_calib.dig_H4 = (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H4) << 4) | (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H4+1) & 0xF); _bmx280_calib.dig_H5 = (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H5+1) << 4) | (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H5) >> 4); _bmx280_calib.dig_H6 = (int8_t) i2c_read_uint8(_address, BMX280_REGISTER_DIG_H6); } unsigned long _measurementTime() { // Measurement Time (as per BMX280 datasheet section 9.1) // T_max(ms) = 1.25 // + (2.3 * T_oversampling) // + (2.3 * P_oversampling + 0.575) // + (2.4 * H_oversampling + 0.575) // ~ 9.3ms for current settings double t = 1.25; #if BMX280_TEMPERATURE > 0 t += (2.3 * BMX280_TEMPERATURE); #endif #if BMX280_PRESSURE > 0 t += (2.3 * BMX280_PRESSURE + 0.575); #endif #if BMX280_HUMIDITY > 0 if (_chip == BMX280_CHIP_BME280) { t += (2.4 * BMX280_HUMIDITY + 0.575); } #endif return round(t + 1); // round up } void _forceRead() { // We set the sensor in "forced mode" to force a reading. // After the reading the sensor will go back to sleep mode. uint8_t value = i2c_read_uint8(_address, BMX280_REGISTER_CONTROL); value = (value & 0xFC) + 0x01; i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, value); nice_delay(_measurement_delay); } unsigned char _read() { #if BMX280_TEMPERATURE > 0 int32_t adc_T = i2c_read_uint16(_address, BMX280_REGISTER_TEMPDATA); if (0xFFFF == adc_T) return SENSOR_ERROR_OUT_OF_RANGE; adc_T <<= 8; adc_T |= i2c_read_uint8(_address, BMX280_REGISTER_TEMPDATA+2); adc_T >>= 4; int32_t var1t = ((((adc_T>>3) - ((int32_t)_bmx280_calib.dig_T1 <<1))) * ((int32_t)_bmx280_calib.dig_T2)) >> 11; int32_t var2t = (((((adc_T>>4) - ((int32_t)_bmx280_calib.dig_T1)) * ((adc_T>>4) - ((int32_t)_bmx280_calib.dig_T1))) >> 12) * ((int32_t)_bmx280_calib.dig_T3)) >> 14; int32_t t_fine = var1t + var2t; double T = (t_fine * 5 + 128) >> 8; _temperature = T / 100; #else int32_t t_fine = 102374; // ~20ºC #endif // ----------------------------------------------------------------- #if BMX280_PRESSURE > 0 int64_t var1, var2, p; int32_t adc_P = i2c_read_uint16(_address, BMX280_REGISTER_PRESSUREDATA); if (0xFFFF == adc_P) return SENSOR_ERROR_OUT_OF_RANGE; adc_P <<= 8; adc_P |= i2c_read_uint8(_address, BMX280_REGISTER_PRESSUREDATA+2); adc_P >>= 4; var1 = ((int64_t)t_fine) - 128000; var2 = var1 * var1 * (int64_t)_bmx280_calib.dig_P6; var2 = var2 + ((var1*(int64_t)_bmx280_calib.dig_P5)<<17); var2 = var2 + (((int64_t)_bmx280_calib.dig_P4)<<35); var1 = ((var1 * var1 * (int64_t)_bmx280_calib.dig_P3)>>8) + ((var1 * (int64_t)_bmx280_calib.dig_P2)<<12); var1 = (((((int64_t)1)<<47)+var1))*((int64_t)_bmx280_calib.dig_P1)>>33; if (var1 == 0) return SENSOR_ERROR_OUT_OF_RANGE; // avoid exception caused by division by zero p = 1048576 - adc_P; p = (((p<<31) - var2)*3125) / var1; var1 = (((int64_t)_bmx280_calib.dig_P9) * (p>>13) * (p>>13)) >> 25; var2 = (((int64_t)_bmx280_calib.dig_P8) * p) >> 19; p = ((p + var1 + var2) >> 8) + (((int64_t)_bmx280_calib.dig_P7)<<4); _pressure = (double) p / 256; #endif // ----------------------------------------------------------------- #if BMX280_HUMIDITY > 0 if (_chip == BMX280_CHIP_BME280) { int32_t adc_H = i2c_read_uint16(_address, BMX280_REGISTER_HUMIDDATA); if (0xFFFF == adc_H) return SENSOR_ERROR_OUT_OF_RANGE; int32_t v_x1_u32r; v_x1_u32r = (t_fine - ((int32_t)76800)); v_x1_u32r = (((((adc_H << 14) - (((int32_t)_bmx280_calib.dig_H4) << 20) - (((int32_t)_bmx280_calib.dig_H5) * v_x1_u32r)) + ((int32_t)16384)) >> 15) * (((((((v_x1_u32r * ((int32_t)_bmx280_calib.dig_H6)) >> 10) * (((v_x1_u32r * ((int32_t)_bmx280_calib.dig_H3)) >> 11) + ((int32_t)32768))) >> 10) + ((int32_t)2097152)) * ((int32_t)_bmx280_calib.dig_H2) + 8192) >> 14)); v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((int32_t)_bmx280_calib.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; double h = (v_x1_u32r >> 12); _humidity = h / 1024.0; } #endif return SENSOR_ERROR_OK; } // --------------------------------------------------------------------- unsigned char _chip; unsigned long _measurement_delay; bool _run_init = false; double _temperature = 0; double _pressure = 0; double _humidity = 0; typedef struct { uint16_t dig_T1; int16_t dig_T2; int16_t dig_T3; uint16_t dig_P1; int16_t dig_P2; int16_t dig_P3; int16_t dig_P4; int16_t dig_P5; int16_t dig_P6; int16_t dig_P7; int16_t dig_P8; int16_t dig_P9; uint8_t dig_H1; int16_t dig_H2; uint8_t dig_H3; int16_t dig_H4; int16_t dig_H5; int8_t dig_H6; } bmx280_calib_t; bmx280_calib_t _bmx280_calib; }; // Static inizializations unsigned char BMX280Sensor::addresses[2] = {0x76, 0x77}; #endif // SENSOR_SUPPORT && BMX280_SUPPORT