/* * Author: Jon Trulson * 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 #include #include #include #include #include #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 celsius and back 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; ireadBytesReg(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; }