Skip to content
Snippets Groups Projects
BME280.cpp 12.3 KiB
Newer Older
Alexander Sparkowsky's avatar
Alexander Sparkowsky committed
/*
* BME280.cpp
*
*  Created on: Sep 10, 2017
*      Author: Alexander Sparkowsky
*/

#include "BME280.h"
#include <esp_log.h>
#include <math.h>
#include <cstdint>
#include <I2C.h>

static char LOG_TAG[] = "bme280";
static bool debug = false;

/**
* @brief Class constructor.
*
* The address is the address of the device on the %I2C bus. By default this address is set to 0x77.
*
* @param [in] address The %I2C address of the device on the %I2C bus.
*/
BME280::BME280(uint8_t address) {
    i2c.setAddress(address);
}

/**
* @brief Class instance destructor.
*/
BME280::~BME280() {
}

uint8_t BME280::readRegister8(uint8_t reg) {
    i2c.beginTransaction();
    i2c.write(reg, true);
    i2c.endTransaction();

	uint8_t value;
    i2c.beginTransaction();
    i2c.read(&value, false);
    i2c.endTransaction();

	return value;
}

uint16_t BME280::read16BitLittleEndianRegister(uint8_t register_address) {
    uint16_t temp = read16BitBigEndianRegister(register_address);

    return (temp >> 8) | (temp << 8);
}

int16_t BME280::read16BitSignedLittleEndianRegister(uint8_t register_address) {
    return (int16_t) read16BitLittleEndianRegister(register_address);
}

uint16_t BME280::read16BitBigEndianRegister(uint8_t reg) {
    i2c.beginTransaction();
    i2c.write(reg, true);
    i2c.endTransaction();

    uint8_t msb;
	uint8_t lsb;
    i2c.beginTransaction();
    i2c.read(&msb, true);
    i2c.read(&lsb, false);
    i2c.endTransaction();

    uint16_t ret = (uint16_t)((msb << 8) | lsb);
	return ret;
}

uint32_t BME280::readRegister24(uint8_t reg) {
    i2c.beginTransaction();
    i2c.write(reg, true);
    i2c.endTransaction();

    uint8_t msb;
	uint8_t lsb;
	uint8_t xlsb;
    i2c.beginTransaction();
    i2c.read(&msb, true);
    i2c.read(&lsb, true);
    i2c.read(&xlsb, false);
    i2c.endTransaction();

    uint32_t ret = (uint32_t)((msb << 16) | (lsb << 8) | xlsb);
	return ret;
}

esp_err_t BME280::writeRegister8(uint8_t register_address, uint8_t data) {
    i2c.beginTransaction();
    i2c.write(register_address, true);
    i2c.write(data, true);
    i2c.endTransaction();

    return ESP_OK;
}

bme280_adc_data BME280::burstReadMeasurement() {
    uint8_t buffer[8];

    uint8_t reg = BME280_REGISTER_PRESSUREDATA;

    i2c.beginTransaction();
    i2c.write(reg, true);
    i2c.endTransaction();

    i2c.beginTransaction();
    i2c.read(buffer, 7, true);
    i2c.read(&buffer[7], false);
    i2c.endTransaction();

    bme280_adc_data adc_data;

Alexander Sparkowsky's avatar
Alexander Sparkowsky committed
    adc_data.buffer.pressure.xmsb = 0;
    adc_data.buffer.pressure.msb = buffer[0];
    adc_data.buffer.pressure.lsb = buffer[1];
    adc_data.buffer.pressure.xlsb = buffer[2];
Alexander Sparkowsky's avatar
Alexander Sparkowsky committed

    adc_data.buffer.temperature.xmsb = 0;
    adc_data.buffer.temperature.msb = buffer[3];
    adc_data.buffer.temperature.lsb = buffer[4];
    adc_data.buffer.temperature.xlsb = buffer[5];

    adc_data.buffer.humidity.xmsb = 0;
    adc_data.buffer.humidity.msb = 0;
    adc_data.buffer.humidity.lsb = buffer[6];
    adc_data.buffer.humidity.xlsb = buffer[7];
    // adc_data.adc_data.adc_P = (BME280_S32_t)((buffer[0] << 16) | (buffer[1] << 8) | buffer[2]);
    // adc_data.adc_data.adc_T = (BME280_S32_t)((buffer[3] << 16) | (buffer[4] << 8) | buffer[5]);
    // adc_data.adc_data.adc_H = (BME280_S32_t)((buffer[6] << 8) | buffer[7]);
    return adc_data;
}

uint8_t BME280::readChipId() {
    if (debug) {
        ESP_LOGD(LOG_TAG, "readChipId()");
    }

    uint8_t chip_id = readRegister8(BME280_REGISTER_CHIPID);

    return chip_id;
}

// 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(BME280_S32_t adc_T) {
    BME280_S32_t var1, var2, T;

    var1 = ((((adc_T>>3) - ((BME280_S32_t)_bme280_calib.dig_T1<<1))) * ((BME280_S32_t)_bme280_calib.dig_T2)) >> 11;
    var2 = (((((adc_T>>4) - ((BME280_S32_t)_bme280_calib.dig_T1)) * ((adc_T>>4) - ((BME280_S32_t)_bme280_calib.dig_T1))) >> 12) *
        ((BME280_S32_t)_bme280_calib.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(BME280_S32_t adc_P) {
    BME280_S64_t var1, var2, p;

    var1 = ((BME280_S64_t)_t_fine) - 128000;
    var2 = var1 * var1 * (BME280_S64_t)_bme280_calib.dig_P6;
    var2 = var2 + ((var1*(BME280_S64_t)_bme280_calib.dig_P5) << 17);
    var2 = var2 + (((BME280_S64_t)_bme280_calib.dig_P4) << 35);
    var1 = ((var1 * var1 * (BME280_S64_t)_bme280_calib.dig_P3) >> 8) + ((var1 * (BME280_S64_t)_bme280_calib.dig_P2) << 12);
    var1 = (((((BME280_S64_t)1) << 47) + var1)) * ((BME280_S64_t)_bme280_calib.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)_bme280_calib.dig_P9) * (p >> 13) * (p >> 13)) >> 25;
    var2 = (((BME280_S64_t)_bme280_calib.dig_P8) * p) >> 19;
    p = ((p + var1 + var2) >> 8) + (((BME280_S64_t)_bme280_calib.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(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)_bme280_calib.dig_H4) << 20) - (((BME280_S32_t)_bme280_calib.dig_H5) * v_x1_u32r)) +
        ((BME280_S32_t)16384)) >> 15) * (((((((v_x1_u32r * ((BME280_S32_t)_bme280_calib.dig_H6)) >> 10) * (((v_x1_u32r *
        ((BME280_S32_t)_bme280_calib.dig_H3)) >> 11) + ((BME280_S32_t)32768))) >> 10) + ((BME280_S32_t)2097152)) *
        ((BME280_S32_t)_bme280_calib.dig_H2) + 8192) >> 14));
    v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((BME280_S32_t)_bme280_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);

    return (BME280_U32_t)(v_x1_u32r >> 12);
}

float BME280::convertUncompensatedTemperature(int32_t adc_T) {
    adc_T >>= 4;
    BME280_S32_t T = compensate_T(adc_T);

    return (float) T / 100.0;
}

float BME280::convertUncompensatedPressure(BME280_S32_t adc_P) {
    adc_P >>= 4;
    BME280_U32_t p = compensate_P(adc_P);

    return (float) p / 256;
}

float BME280::convertUncompensatedHumidity(BME280_S32_t adc_H) {
    BME280_U32_t h = compensate_H(adc_H);

    return h / 1024.0;
}

/**************************************************************************/
/*!
Calculates the altitude (in meters) from the specified atmospheric
pressure (in hPa), and sea-level pressure (in hPa).
Alexander Sparkowsky's avatar
Alexander Sparkowsky committed
@param  pressure     preassure in Pa
Alexander Sparkowsky's avatar
Alexander Sparkowsky committed
@param  seaLevel      Sea-level pressure in hPa
@param  atmospheric   Atmospheric pressure in hPa
*/
/**************************************************************************/
Alexander Sparkowsky's avatar
Alexander Sparkowsky committed
float BME280::altitudeOfPressure(float pressure, float seaLevel) {
Alexander Sparkowsky's avatar
Alexander Sparkowsky committed
    // Equation taken from BMP180 datasheet (page 16):
    //  http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf

    // Note that using the equation from wikipedia can give bad results
    // at high altitude.  See this thread for more information:
    //  http://forums.adafruit.com/viewtopic.php?f=22&t=58064

Alexander Sparkowsky's avatar
Alexander Sparkowsky committed
    float atmospheric = pressure / 100.0F;
Alexander Sparkowsky's avatar
Alexander Sparkowsky committed
    return 44330.0 * (1.0 - pow((double) atmospheric / seaLevel, (double) 0.1903));
}

/**************************************************************************/
/*!
Calculates the pressure at sea level (in hPa) from the specified altitude
(in meters), and atmospheric pressure (in hPa).
@param  altitude      Altitude in meters
@param  atmospheric   Atmospheric pressure in hPa
*/
/**************************************************************************/
float BME280::seaLevelForAltitude(float altitude, float atmospheric) {
    // Equation taken from BMP180 datasheet (page 17):
    //  http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf

    // Note that using the equation from wikipedia can give bad results
    // at high altitude.  See this thread for more information:
    //  http://forums.adafruit.com/viewtopic.php?f=22&t=58064

    return atmospheric / pow(1.0 - (altitude / 44330.0), 5.255);
}

bme280_reading_data BME280::readSensorData() {
    if (debug) {
        ESP_LOGD(LOG_TAG, "readSensorData()");
    }
    bme280_reading_data reading_data;

    bme280_adc_data adc_data = burstReadMeasurement();

    reading_data.temperature = convertUncompensatedTemperature(adc_data.adc_data.adc_T);
Alexander Sparkowsky's avatar
Alexander Sparkowsky committed
    reading_data.pressure = convertUncompensatedPressure(adc_data.adc_data.adc_P);
Alexander Sparkowsky's avatar
Alexander Sparkowsky committed
    reading_data.humidity = convertUncompensatedHumidity(adc_data.adc_data.adc_H);

    return reading_data;
}

void BME280::readCoefficients(void) {
    if (debug) {
        ESP_LOGD(LOG_TAG, "readCoefficients()");
    }

    _bme280_calib.dig_T1 = (uint16_t)read16BitLittleEndianRegister(BME280_REGISTER_DIG_T1);
    _bme280_calib.dig_T2 = (int16_t)read16BitSignedLittleEndianRegister(BME280_REGISTER_DIG_T2);
    _bme280_calib.dig_T3 = (int16_t)read16BitSignedLittleEndianRegister(BME280_REGISTER_DIG_T3);
    if (debug) {
       ESP_LOGD(LOG_TAG, "T1: %u, T2: %d, T3: %d", _bme280_calib.dig_T1, _bme280_calib.dig_T2, _bme280_calib.dig_T3);
    }

    _bme280_calib.dig_P1 = (uint16_t)read16BitLittleEndianRegister(BME280_REGISTER_DIG_P1);
    _bme280_calib.dig_P2 = (int16_t)read16BitSignedLittleEndianRegister(BME280_REGISTER_DIG_P2);
    _bme280_calib.dig_P3 = (int16_t)read16BitSignedLittleEndianRegister(BME280_REGISTER_DIG_P3);
    _bme280_calib.dig_P4 = (int16_t)read16BitSignedLittleEndianRegister(BME280_REGISTER_DIG_P4);
    _bme280_calib.dig_P5 = (int16_t)read16BitSignedLittleEndianRegister(BME280_REGISTER_DIG_P5);
    _bme280_calib.dig_P6 = (int16_t)read16BitSignedLittleEndianRegister(BME280_REGISTER_DIG_P6);
    _bme280_calib.dig_P7 = (int16_t)read16BitSignedLittleEndianRegister(BME280_REGISTER_DIG_P7);
    _bme280_calib.dig_P8 = (int16_t)read16BitSignedLittleEndianRegister(BME280_REGISTER_DIG_P8);
    _bme280_calib.dig_P9 = (int16_t)read16BitSignedLittleEndianRegister(BME280_REGISTER_DIG_P9);
    if (debug) {
        ESP_LOGD(LOG_TAG, "P1: %u, P2: %d, P3: %d, P4: %d, P5: %d, P6: %d, P7: %d, P8: %d, P9: %d",
            _bme280_calib.dig_P1,
            _bme280_calib.dig_P2,
            _bme280_calib.dig_P3,
            _bme280_calib.dig_P4,
            _bme280_calib.dig_P5,
            _bme280_calib.dig_P6,
            _bme280_calib.dig_P7,
            _bme280_calib.dig_P8,
            _bme280_calib.dig_P9);
    }

    _bme280_calib.dig_H1 = readRegister8(BME280_REGISTER_DIG_H1);
    _bme280_calib.dig_H2 = read16BitSignedLittleEndianRegister(BME280_REGISTER_DIG_H2);
    _bme280_calib.dig_H3 = readRegister8(BME280_REGISTER_DIG_H3);
    _bme280_calib.dig_H4 = (readRegister8(BME280_REGISTER_DIG_H4) << 4) |
    (readRegister8(BME280_REGISTER_DIG_H4 + 1) & 0xF);
    _bme280_calib.dig_H5 = (readRegister8(BME280_REGISTER_DIG_H5 + 1) << 4) |
    (readRegister8(BME280_REGISTER_DIG_H5) >> 4);
    _bme280_calib.dig_H6 = (int8_t) readRegister8(BME280_REGISTER_DIG_H6);
}

/**
 * @brief enable or disable debugging.
 * @param [in] enabled Should debugging be enabled or disabled.
 * @return N/A.
 */
 void BME280::setDebug(bool enabled) {
	debug = enabled;
} // setDebug

/**
* @brief Initialize the BME280 device.
*
* @param [in] sdaPin The pin to use for the %I2C SDA functions.
* @param [in] clkPin The pin to use for the %I2C CLK functions.
*/
esp_err_t BME280::init(gpio_num_t sdaPin, gpio_num_t clkPin) {
    if (debug) {
        ESP_LOGD(LOG_TAG, "init()");
    }

    i2c.setDebug(false);
    i2c.init(BME280_ADDRESS, sdaPin, clkPin);

    _sensor_id = readChipId();
    if (_sensor_id != 0x60) {
        if (debug) {
            ESP_LOGD(LOG_TAG, "Could not read chip id during initialization");
        }

        return ESP_ERR_NOT_FOUND;
    }

    if (debug) {
        ESP_LOGD(LOG_TAG, "Sucessfully read chipId");
    }

    vTaskDelay(1000/portTICK_PERIOD_MS);

    readCoefficients();

    // Set before CONTROL_meas (DS 5.4.3)
    writeRegister8(BME280_REGISTER_CONTROLHUMID, 0x05); // 16x oversampling
    writeRegister8(BME280_REGISTER_CONTROL, 0xB7);      // 16x oversampling, normal mode

    return ESP_OK;
} // init