bmp280, bme280: Initial implementation

This library adds support for both the BMP280 and BME280 sesnors from
Bosch.  The BME is virtually identical to the BMP280 aside from some
slight register changes and adding support for a humidity sensor.

The BMP280 is an absolute barometric pressure sensor especially
designed for mobile applications. The sensor module is housed in an
extremely compact 8-pin metal-lid LGA package with a footprint of only
2.0 × 2.5 mm2 and 0.95 mm package height. Its small dimensions and its
low power consumption of 2.7 μA @1Hz allow the implementation in
battery driven devices such as mobile phones, GPS modules or watches.

The BME280 is as combined digital humidity, pressure and temperature
sensor based on proven sensing principles. The sensor module is housed
in an extremely compact metal-lid LGA package with a footprint of only
2.5 × 2.5 mm2 with a height of 0.93 mm. Its small dimensions and its
low power consumption allow the implementation in battery driven
devices such as handsets, GPS modules or watches. The BME280 is
register and performance compatible to the Bosch Sensortec BMP280
digital pressure sensor

These drivers support both I2C and SPI operation.

When using SPI on an Edison with the arduino breakout board, you
must specify a CS of -1 to the constructor, and connect the chip's
CS pin to arduino pin 10.

Signed-off-by: Jon Trulson <jtrulson@ics.com>
Signed-off-by: Noel Eck <noel.eck@intel.com>
This commit is contained in:
Jon Trulson
2016-05-04 12:47:39 -06:00
committed by Noel Eck
parent 84144d0c8f
commit 6241e20dda
18 changed files with 2073 additions and 0 deletions

576
src/bmp280/bmp280.cxx Normal file
View File

@ -0,0 +1,576 @@
/*
* Author: Jon Trulson <jtrulson@ics.com>
* Copyright (c) 2016 Intel Corporation.
*
* 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 <unistd.h>
#include <iostream>
#include <stdexcept>
#include <string>
#include <math.h>
#include <string.h>
#include "bmp280.hpp"
using namespace upm;
using namespace std;
// Uncomment the following to use test data as specified in the
// datasheet, section 3.12. This really only tests the compensation
// algorithm.
// #define BMP280_USE_TEST_DATA
// conversion from fahrenheit to celcius and back
static float f2c(float f)
{
return ((f - 32.0) / (9.0 / 5.0));
}
static float c2f(float c)
{
return (c * (9.0 / 5.0) + 32.0);
}
BMP280::BMP280(int bus, int addr, int cs, uint8_t theChipID) :
m_i2c(0), m_spi(0), m_gpioCS(0)
{
m_addr = addr;
m_temperature = 0;
m_pressure = 0;
m_isSPI = false;
clearData();
if (addr < 0)
m_isSPI = true;
mraa::Result rv;
if (m_isSPI)
{
m_spi = new mraa::Spi(bus);
// Only create cs context if we are actually using a valid pin.
// A hardware controlled pin should specify cs as -1.
if (cs >= 0)
{
m_gpioCS = new mraa::Gpio(cs);
m_gpioCS->dir(mraa::DIR_OUT);
}
m_spi->mode(mraa::SPI_MODE0);
m_spi->frequency(5000000);
// toggle it on/off so chip switches into SPI mode. For a hw CS
// pin, the first SPI transaction should accomplish this.
csOn();
usleep(10000);
csOff();
}
else
{
// I2C
m_i2c = new mraa::I2c(bus);
if ((rv = m_i2c->address(m_addr)) != mraa::SUCCESS)
{
throw std::runtime_error(string(__FUNCTION__) +
": I2c.address() failed");
}
}
// check the chip id
uint8_t chipID = readReg(REG_CHIPID);
if (chipID != theChipID)
{
throw std::runtime_error(string(__FUNCTION__)
+ ": invalid chip ID. Expected "
+ std::to_string(int(theChipID))
+ ", got "
+ std::to_string(int(chipID)));
}
// set sleep mode for now
setMeasureMode(MODE_SLEEP);
// read calibration data
readCalibrationData();
// set the default mode to the highest resolution mode
setUsageMode(USAGE_MODE_INDOOR_NAV);
}
BMP280::~BMP280()
{
if (m_i2c)
delete m_i2c;
if (m_spi)
delete m_spi;
if (m_gpioCS)
delete m_gpioCS;
}
void BMP280::update()
{
int32_t temp = 0;
int32_t pres = 0;
const int dataLen = 6;
uint8_t data[dataLen];
memset(data, 0, dataLen);
// If we are using a forced mode, then we need to manually trigger
// the measurement, and wait for it to complete.
if (m_mode == MODE_FORCED)
{
// bmp280 measure mode will return to sleep after completion...
setMeasureMode(MODE_FORCED);
uint8_t stat;
do
{
usleep(10000); // 10ms
stat = readReg(REG_STATUS);
} while (stat & STATUS_MEASURING);
}
int rv;
if ((rv = readRegs(REG_PRESSURE_MSB, data, dataLen)) != dataLen)
{
throw std::runtime_error(std::string(__FUNCTION__)
+ ": readRegs() failed, returned "
+ std::to_string(rv));
}
// 20 bits unsigned stored in a 32bit signed quanty
#if defined(BMP280_USE_TEST_DATA)
// taken from datasheet, section 3.12
temp = 519888;
pres = 415148;
#else
temp = ( (data[5] >> 4) | (data[4] << 4) | (data[3] << 12) );
pres = ( (data[2] >> 4) | (data[1] << 4) | (data[0] << 12) );
#endif
m_temperature = float(bmp280_compensate_T_int32(temp));
m_temperature /= 100.0;
m_pressure = float(bmp280_compensate_P_int64(pres));
m_pressure /= 256.0;
}
float BMP280::getAltitude(float sealLevelhPA)
{
// adapted from the NOAA pdf: pressureAltitude.pdf
return 44307.69 * (1.0 - pow((m_pressure/100.0) / sealLevelhPA, 0.190284));
}
uint8_t BMP280::readReg(uint8_t reg)
{
if (m_isSPI)
{
reg |= 0x80; // needed for read
uint8_t pkt[2] = {reg, 0};
csOn();
if (m_spi->transfer(pkt, pkt, 2))
{
csOff();
throw std::runtime_error(string(__FUNCTION__)
+ ": Spi.transfer() failed");
}
csOff();
#if 0
cerr << "readReg: " << std::hex << "p0: " << (int)pkt[0] << " p1: "
<< (int)pkt[1] << endl;
#endif // 0
return pkt[1];
}
else
return m_i2c->readReg(reg);
}
int BMP280::readRegs(uint8_t reg, uint8_t *buffer, int len)
{
if (m_isSPI)
{
reg |= 0x80; // needed for read
uint8_t sbuf[len + 1];
memset((char *)sbuf, 0, len + 1);
sbuf[0] = reg;
// We need to do it this way for edison - ie: use a single
// transfer rather than breaking it up into two like we used to.
// This means a buffer copy is now required, but that's the way
// it goes.
csOn();
if (m_spi->transfer(sbuf, sbuf, len + 1))
{
csOff();
throw std::runtime_error(string(__FUNCTION__)
+ ": Spi.transfer(buf) failed");
}
csOff();
// now copy it into user buffer
for (int i=0; i<len; i++)
buffer[i] = sbuf[i + 1];
// so... did this work on edison????
#if 0
cerr << "readRegs(): " << std::hex;
for (int i=0; i<len; i++)
cerr << (int)buffer[i] << " ";
cerr << endl;
#endif // 0
return len;
}
else
return m_i2c->readBytesReg(reg, buffer, len);
}
void BMP280::writeReg(uint8_t reg, uint8_t val)
{
if (m_isSPI)
{
reg &= 0x7f; // mask off 0x80 for writing
uint8_t pkt[2] = {reg, val};
csOn();
if (m_spi->transfer(pkt, NULL, 2))
{
csOff();
throw std::runtime_error(string(__FUNCTION__)
+ ": Spi.transfer() failed");
}
csOff();
}
else
{
mraa::Result rv;
if ((rv = m_i2c->writeReg(reg, val)) != mraa::SUCCESS)
{
throw std::runtime_error(std::string(__FUNCTION__)
+ ": I2c.writeReg() failed");
}
}
}
void BMP280::clearData()
{
m_t_fine = 0;
m_dig_T1 = 0;
m_dig_T2 = 0;
m_dig_T3 = 0;
m_dig_P1 = 0;
m_dig_P2 = 0;
m_dig_P3 = 0;
m_dig_P4 = 0;
m_dig_P5 = 0;
m_dig_P6 = 0;
m_dig_P7 = 0;
m_dig_P8 = 0;
m_dig_P9 = 0;
}
uint8_t BMP280::getChipID()
{
return readReg(REG_CHIPID);
}
void BMP280::reset()
{
writeReg(REG_RESET, BMP280_RESET_BYTE);
sleep(1);
}
void BMP280::readCalibrationData()
{
#if defined(BMP280_USE_TEST_DATA)
cerr << "WARNING: Test data is being used" << endl;
// This data is taken from the datasheet, section 3.12
m_dig_T1 = 27504;
m_dig_T2 = 26435;
m_dig_T3 = -1000;
m_dig_P1 = 36477;
m_dig_P2 = -10685;
m_dig_P3 = 3024;
m_dig_P4 = 2855;
m_dig_P5 = 140;
m_dig_P6 = -7;
m_dig_P7 = 15500;
m_dig_P8 = -14600;
m_dig_P9 = 6000;
#else
uint8_t calibData[CALIBRATION_BYTES];
readRegs(REG_CALIB00, calibData, CALIBRATION_BYTES);
m_dig_T1 = uint16_t((calibData[1] << 8) | calibData[0]);
m_dig_T2 = int16_t((calibData[3] << 8) | calibData[2]);
m_dig_T3 = int16_t((calibData[5] << 8) | calibData[4]);
# if 0
cerr << std::dec << "T1: " << (int)m_dig_T1
<< " T2: " << (int)m_dig_T2
<< " T3: " << (int)m_dig_T3
<< endl;
# endif // 0
m_dig_P1 = uint16_t((calibData[7] << 8) | calibData[6]);
m_dig_P2 = int16_t((calibData[9] << 8) | calibData[8]);
m_dig_P3 = int16_t((calibData[11] << 8) | calibData[10]);
m_dig_P4 = int16_t((calibData[13] << 8) | calibData[12]);
m_dig_P5 = int16_t((calibData[15] << 8) | calibData[14]);
m_dig_P6 = int16_t((calibData[17] << 8) | calibData[16]);
m_dig_P7 = int16_t((calibData[19] << 8) | calibData[18]);
m_dig_P8 = int16_t((calibData[21] << 8) | calibData[20]);
m_dig_P9 = int16_t((calibData[23] << 8) | calibData[22]);
# if 0
cerr << std::dec << "P1: " << (int)m_dig_P1
<< " P2: " << (int)m_dig_P2
<< " P3: " << (int)m_dig_P3
<< " P4: " << (int)m_dig_P4
<< " P5: " << (int)m_dig_P5
<< endl;
cerr << std::dec << "P6: " << (int)m_dig_P6
<< " P7: " << (int)m_dig_P7
<< " P8: " << (int)m_dig_P8
<< " P9: " << (int)m_dig_P9
<< endl;
# endif // 0
#endif
}
float BMP280::getTemperature(bool fahrenheit)
{
if (fahrenheit)
return c2f(m_temperature);
else
return m_temperature;
}
float BMP280::getPressure()
{
return m_pressure;
}
void BMP280::setFilter(FILTER_T filter)
{
uint8_t reg = readReg(REG_CONFIG);
reg &= ~(_CONFIG_FILTER_MASK << _CONFIG_FILTER_SHIFT);
reg |= (filter << _CONFIG_FILTER_SHIFT);
writeReg(REG_CONFIG, reg);
}
void BMP280::setTimerStandby(T_SB_T tsb)
{
uint8_t reg = readReg(REG_CONFIG);
reg &= ~(_CONFIG_T_SB_MASK << _CONFIG_T_SB_SHIFT);
reg |= (tsb << _CONFIG_T_SB_SHIFT);
writeReg(REG_CONFIG, reg);
}
void BMP280::setMeasureMode(MODES_T mode)
{
uint8_t reg = readReg(REG_CTRL_MEAS);
reg &= ~(_CTRL_MEAS_MODE_MASK << _CTRL_MEAS_MODE_SHIFT);
reg |= (mode << _CTRL_MEAS_MODE_SHIFT);
writeReg(REG_CTRL_MEAS, reg);
m_mode = mode;
}
void BMP280::setOversampleRatePressure(OSRS_P_T rate)
{
uint8_t reg = readReg(REG_CTRL_MEAS);
reg &= ~(_CTRL_MEAS_OSRS_P_MASK << _CTRL_MEAS_OSRS_P_SHIFT);
reg |= (rate << _CTRL_MEAS_OSRS_P_SHIFT);
writeReg(REG_CTRL_MEAS, reg);
}
void BMP280::setOversampleRateTemperature(OSRS_T_T rate)
{
uint8_t reg = readReg(REG_CTRL_MEAS);
reg &= ~(_CTRL_MEAS_OSRS_T_MASK << _CTRL_MEAS_OSRS_T_SHIFT);
reg |= (rate << _CTRL_MEAS_OSRS_T_SHIFT);
writeReg(REG_CTRL_MEAS, reg);
}
uint8_t BMP280::getStatus()
{
return readReg(REG_STATUS);
}
void BMP280::setUsageMode(USAGE_MODE_T mode)
{
// set up the regs for the given usage mode. These settings come
// from the recomendations in the BMP280 datasheet, section 3.4
// Filter Selection.
m_temperature = 0;
m_pressure = 0;
// set sleep mode first
setMeasureMode(MODE_SLEEP);
switch (mode)
{
case USAGE_MODE_HANDHELD_LOW_POWER:
setOversampleRatePressure(OSRS_P_OVERSAMPLING_16);
setOversampleRateTemperature(OSRS_T_OVERSAMPLING_2);
setFilter(FILTER_4);
setMeasureMode(MODE_NORMAL);
break;
case USAGE_MODE_HANDHELD_DYNAMIC:
setOversampleRatePressure(OSRS_P_OVERSAMPLING_4);
setOversampleRateTemperature(OSRS_T_OVERSAMPLING_1);
setFilter(FILTER_16);
setMeasureMode(MODE_NORMAL);
break;
case USAGE_MODE_WEATHER_MONITOR:
setOversampleRatePressure(OSRS_P_OVERSAMPLING_1);
setOversampleRateTemperature(OSRS_T_OVERSAMPLING_1);
setFilter(FILTER_OFF);
setMeasureMode(MODE_FORCED);
break;
case USAGE_MODE_FLOOR_CHG_DETECT:
setOversampleRatePressure(OSRS_P_OVERSAMPLING_4);
setOversampleRateTemperature(OSRS_T_OVERSAMPLING_1);
setFilter(FILTER_4);
setMeasureMode(MODE_NORMAL);
break;
case USAGE_MODE_DROP_DETECT:
setOversampleRatePressure(OSRS_P_OVERSAMPLING_2);
setOversampleRateTemperature(OSRS_T_OVERSAMPLING_1);
setFilter(FILTER_OFF);
setMeasureMode(MODE_NORMAL);
break;
case USAGE_MODE_INDOOR_NAV:
setOversampleRatePressure(OSRS_P_OVERSAMPLING_16);
setOversampleRateTemperature(OSRS_T_OVERSAMPLING_2);
setFilter(FILTER_16);
setMeasureMode(MODE_NORMAL);
break;
default:
throw std::logic_error(string(__FUNCTION__)
+ ": invalid mode specified");
}
}
void BMP280::csOn()
{
if (m_gpioCS)
m_gpioCS->write(0);
}
void BMP280::csOff()
{
if (m_gpioCS)
m_gpioCS->write(1);
}
// These functions come from the BMP280 datasheet, section 3.11.3
// 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
int32_t BMP280::bmp280_compensate_T_int32(int32_t adc_T)
{
int32_t var1, var2, T;
var1 = ((((adc_T>>3) - ((int32_t)m_dig_T1<<1))) * ((int32_t)m_dig_T2)) >> 11;
var2 = (((((adc_T>>4) - ((int32_t)m_dig_T1)) * ((adc_T>>4) - ((int32_t)m_dig_T1))) >> 12) *
((int32_t)m_dig_T3)) >> 14;
m_t_fine = var1 + var2;
T = (m_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
uint32_t BMP280::bmp280_compensate_P_int64(int32_t adc_P)
{
int64_t var1, var2, p;
var1 = ((int64_t)m_t_fine) - 128000;
var2 = var1 * var1 * (int64_t)m_dig_P6;
var2 = var2 + ((var1*(int64_t)m_dig_P5)<<17);
var2 = var2 + (((int64_t)m_dig_P4)<<35);
var1 = ((var1 * var1 * (int64_t)m_dig_P3)>>8) + ((var1 * (int64_t)m_dig_P2)<<12);
var1 = (((((int64_t)1)<<47)+var1))*((int64_t)m_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 = (((int64_t)m_dig_P9) * (p>>13) * (p>>13)) >> 25;
var2 = (((int64_t)m_dig_P8) * p) >> 19;
p = ((p + var1 + var2) >> 8) + (((int64_t)m_dig_P7)<<4);
return (uint32_t)p;
}