401 lines
13 KiB
C++
401 lines
13 KiB
C++
/******************************************************************************
|
|
SparkFunBME280.cpp
|
|
BME280 Arduino and Teensy Driver
|
|
Marshall Taylor @ SparkFun Electronics
|
|
May 20, 2015
|
|
https://github.com/sparkfun/BME280_Breakout
|
|
|
|
Resources:
|
|
Uses Wire.h for i2c operation
|
|
Uses SPI.h for SPI operation
|
|
|
|
Development environment specifics:
|
|
Arduino IDE 1.6.4
|
|
Teensy loader 1.23
|
|
|
|
This code is released under the [MIT License](http://opensource.org/licenses/MIT).
|
|
Please review the LICENSE.md file included with this example. If you have any questions
|
|
or concerns with licensing, please contact techsupport@sparkfun.com.
|
|
Distributed as-is; no warranty is given.
|
|
******************************************************************************/
|
|
//See SparkFunBME280.h for additional topology notes.
|
|
|
|
#include "SparkFunBME280.h"
|
|
#include "stdint.h"
|
|
#include <math.h>
|
|
#include <Arduino.h>
|
|
|
|
#include "Wire.h"
|
|
#include "SPI.h"
|
|
|
|
//****************************************************************************//
|
|
//
|
|
// Settings and configuration
|
|
//
|
|
//****************************************************************************//
|
|
|
|
//Constructor -- Specifies default configuration
|
|
BME280::BME280( void )
|
|
{
|
|
//Construct with these default settings if nothing is specified
|
|
|
|
//Select interface mode
|
|
settings.commInterface = I2C_MODE; //Can be I2C_MODE, SPI_MODE
|
|
//Select address for I2C. Does nothing for SPI
|
|
settings.I2CAddress = 0x77; //Ignored for SPI_MODE
|
|
//Select CS pin for SPI. Does nothing for I2C
|
|
settings.chipSelectPin = 10;
|
|
settings.runMode = 0;
|
|
settings.tempOverSample = 0;
|
|
settings.pressOverSample = 0;
|
|
settings.humidOverSample = 0;
|
|
|
|
}
|
|
|
|
|
|
//****************************************************************************//
|
|
//
|
|
// Configuration section
|
|
//
|
|
// This uses the stored SensorSettings to start the IMU
|
|
// Use statements such as "mySensor.settings.commInterface = SPI_MODE;" to
|
|
// configure before calling .begin();
|
|
//
|
|
//****************************************************************************//
|
|
uint8_t BME280::begin()
|
|
{
|
|
//Check the settings structure values to determine how to setup the device
|
|
uint8_t dataToWrite = 0; //Temporary variable
|
|
switch (settings.commInterface)
|
|
{
|
|
|
|
case I2C_MODE:
|
|
Wire.begin();
|
|
break;
|
|
|
|
case SPI_MODE:
|
|
// start the SPI library:
|
|
SPI.begin();
|
|
#ifdef ARDUINO_ARCH_ESP32
|
|
SPI.setFrequency(1000000);
|
|
// Data is read and written MSb first.
|
|
SPI.setBitOrder(SPI_MSBFIRST);
|
|
// Like the standard arduino/teensy comment below, mode0 seems wrong according to standards
|
|
// but conforms to the timing diagrams when used for the ESP32
|
|
SPI.setDataMode(SPI_MODE0);
|
|
#else
|
|
// Maximum SPI frequency is 10MHz, could divide by 2 here:
|
|
SPI.setClockDivider(SPI_CLOCK_DIV32);
|
|
// Data is read and written MSb first.
|
|
SPI.setBitOrder(MSBFIRST);
|
|
// Data is captured on rising edge of clock (CPHA = 0)
|
|
// Base value of the clock is HIGH (CPOL = 1)
|
|
// This was SPI_MODE3 for RedBoard, but I had to change to
|
|
// MODE0 for Teensy 3.1 operation
|
|
SPI.setDataMode(SPI_MODE3);
|
|
#endif
|
|
// initalize the data ready and chip select pins:
|
|
pinMode(settings.chipSelectPin, OUTPUT);
|
|
digitalWrite(settings.chipSelectPin, HIGH);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//Reading all compensation data, range 0x88:A1, 0xE1:E7
|
|
|
|
calibration.dig_T1 = ((uint16_t)((readRegister(BME280_DIG_T1_MSB_REG) << 8) + readRegister(BME280_DIG_T1_LSB_REG)));
|
|
calibration.dig_T2 = ((int16_t)((readRegister(BME280_DIG_T2_MSB_REG) << 8) + readRegister(BME280_DIG_T2_LSB_REG)));
|
|
calibration.dig_T3 = ((int16_t)((readRegister(BME280_DIG_T3_MSB_REG) << 8) + readRegister(BME280_DIG_T3_LSB_REG)));
|
|
|
|
calibration.dig_P1 = ((uint16_t)((readRegister(BME280_DIG_P1_MSB_REG) << 8) + readRegister(BME280_DIG_P1_LSB_REG)));
|
|
calibration.dig_P2 = ((int16_t)((readRegister(BME280_DIG_P2_MSB_REG) << 8) + readRegister(BME280_DIG_P2_LSB_REG)));
|
|
calibration.dig_P3 = ((int16_t)((readRegister(BME280_DIG_P3_MSB_REG) << 8) + readRegister(BME280_DIG_P3_LSB_REG)));
|
|
calibration.dig_P4 = ((int16_t)((readRegister(BME280_DIG_P4_MSB_REG) << 8) + readRegister(BME280_DIG_P4_LSB_REG)));
|
|
calibration.dig_P5 = ((int16_t)((readRegister(BME280_DIG_P5_MSB_REG) << 8) + readRegister(BME280_DIG_P5_LSB_REG)));
|
|
calibration.dig_P6 = ((int16_t)((readRegister(BME280_DIG_P6_MSB_REG) << 8) + readRegister(BME280_DIG_P6_LSB_REG)));
|
|
calibration.dig_P7 = ((int16_t)((readRegister(BME280_DIG_P7_MSB_REG) << 8) + readRegister(BME280_DIG_P7_LSB_REG)));
|
|
calibration.dig_P8 = ((int16_t)((readRegister(BME280_DIG_P8_MSB_REG) << 8) + readRegister(BME280_DIG_P8_LSB_REG)));
|
|
calibration.dig_P9 = ((int16_t)((readRegister(BME280_DIG_P9_MSB_REG) << 8) + readRegister(BME280_DIG_P9_LSB_REG)));
|
|
|
|
calibration.dig_H1 = ((uint8_t)(readRegister(BME280_DIG_H1_REG)));
|
|
calibration.dig_H2 = ((int16_t)((readRegister(BME280_DIG_H2_MSB_REG) << 8) + readRegister(BME280_DIG_H2_LSB_REG)));
|
|
calibration.dig_H3 = ((uint8_t)(readRegister(BME280_DIG_H3_REG)));
|
|
calibration.dig_H4 = ((int16_t)((readRegister(BME280_DIG_H4_MSB_REG) << 4) + (readRegister(BME280_DIG_H4_LSB_REG) & 0x0F)));
|
|
calibration.dig_H5 = ((int16_t)((readRegister(BME280_DIG_H5_MSB_REG) << 4) + ((readRegister(BME280_DIG_H4_LSB_REG) >> 4) & 0x0F)));
|
|
calibration.dig_H6 = ((uint8_t)readRegister(BME280_DIG_H6_REG));
|
|
|
|
//Set the oversampling control words.
|
|
//config will only be writeable in sleep mode, so first insure that.
|
|
writeRegister(BME280_CTRL_MEAS_REG, 0x00);
|
|
|
|
//Set the config word
|
|
dataToWrite = (settings.tStandby << 0x5) & 0xE0;
|
|
dataToWrite |= (settings.filter << 0x02) & 0x1C;
|
|
writeRegister(BME280_CONFIG_REG, dataToWrite);
|
|
|
|
//Set ctrl_hum first, then ctrl_meas to activate ctrl_hum
|
|
dataToWrite = settings.humidOverSample & 0x07; //all other bits can be ignored
|
|
writeRegister(BME280_CTRL_HUMIDITY_REG, dataToWrite);
|
|
|
|
//set ctrl_meas
|
|
//First, set temp oversampling
|
|
dataToWrite = (settings.tempOverSample << 0x5) & 0xE0;
|
|
//Next, pressure oversampling
|
|
dataToWrite |= (settings.pressOverSample << 0x02) & 0x1C;
|
|
//Last, set mode
|
|
dataToWrite |= (settings.runMode) & 0x03;
|
|
//Load the byte
|
|
writeRegister(BME280_CTRL_MEAS_REG, dataToWrite);
|
|
|
|
return readRegister(0xD0);
|
|
}
|
|
|
|
//Strictly resets. Run .begin() afterwards
|
|
void BME280::reset( void )
|
|
{
|
|
writeRegister(BME280_RST_REG, 0xB6);
|
|
|
|
}
|
|
|
|
//****************************************************************************//
|
|
//
|
|
// Pressure Section
|
|
//
|
|
//****************************************************************************//
|
|
float BME280::readFloatPressure( void )
|
|
{
|
|
|
|
// 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
|
|
int32_t adc_P = ((uint32_t)readRegister(BME280_PRESSURE_MSB_REG) << 12) | ((uint32_t)readRegister(BME280_PRESSURE_LSB_REG) << 4) | ((readRegister(BME280_PRESSURE_XLSB_REG) >> 4) & 0x0F);
|
|
|
|
int64_t var1, var2, p_acc;
|
|
var1 = ((int64_t)t_fine) - 128000;
|
|
var2 = var1 * var1 * (int64_t)calibration.dig_P6;
|
|
var2 = var2 + ((var1 * (int64_t)calibration.dig_P5)<<17);
|
|
var2 = var2 + (((int64_t)calibration.dig_P4)<<35);
|
|
var1 = ((var1 * var1 * (int64_t)calibration.dig_P3)>>8) + ((var1 * (int64_t)calibration.dig_P2)<<12);
|
|
var1 = (((((int64_t)1)<<47)+var1))*((int64_t)calibration.dig_P1)>>33;
|
|
if (var1 == 0)
|
|
{
|
|
return 0; // avoid exception caused by division by zero
|
|
}
|
|
p_acc = 1048576 - adc_P;
|
|
p_acc = (((p_acc<<31) - var2)*3125)/var1;
|
|
var1 = (((int64_t)calibration.dig_P9) * (p_acc>>13) * (p_acc>>13)) >> 25;
|
|
var2 = (((int64_t)calibration.dig_P8) * p_acc) >> 19;
|
|
p_acc = ((p_acc + var1 + var2) >> 8) + (((int64_t)calibration.dig_P7)<<4);
|
|
|
|
return (float)p_acc / 256.0;
|
|
|
|
}
|
|
|
|
float BME280::readFloatAltitudeMeters( void )
|
|
{
|
|
float heightOutput = 0;
|
|
|
|
heightOutput = ((float)-45846.2)*(pow(((float)readFloatPressure()/(float)101325), 0.190263) - (float)1);
|
|
return heightOutput;
|
|
|
|
}
|
|
|
|
float BME280::readFloatAltitudeFeet( void )
|
|
{
|
|
float heightOutput = 0;
|
|
|
|
heightOutput = readFloatAltitudeMeters() * 3.28084;
|
|
return heightOutput;
|
|
|
|
}
|
|
|
|
//****************************************************************************//
|
|
//
|
|
// Humidity Section
|
|
//
|
|
//****************************************************************************//
|
|
float BME280::readFloatHumidity( void )
|
|
{
|
|
|
|
// 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
|
|
int32_t adc_H = ((uint32_t)readRegister(BME280_HUMIDITY_MSB_REG) << 8) | ((uint32_t)readRegister(BME280_HUMIDITY_LSB_REG));
|
|
|
|
int32_t var1;
|
|
var1 = (t_fine - ((int32_t)76800));
|
|
var1 = (((((adc_H << 14) - (((int32_t)calibration.dig_H4) << 20) - (((int32_t)calibration.dig_H5) * var1)) +
|
|
((int32_t)16384)) >> 15) * (((((((var1 * ((int32_t)calibration.dig_H6)) >> 10) * (((var1 * ((int32_t)calibration.dig_H3)) >> 11) + ((int32_t)32768))) >> 10) + ((int32_t)2097152)) *
|
|
((int32_t)calibration.dig_H2) + 8192) >> 14));
|
|
var1 = (var1 - (((((var1 >> 15) * (var1 >> 15)) >> 7) * ((int32_t)calibration.dig_H1)) >> 4));
|
|
var1 = (var1 < 0 ? 0 : var1);
|
|
var1 = (var1 > 419430400 ? 419430400 : var1);
|
|
|
|
return (float)(var1>>12) / 1024.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//****************************************************************************//
|
|
//
|
|
// Temperature Section
|
|
//
|
|
//****************************************************************************//
|
|
|
|
float BME280::readTempC( void )
|
|
{
|
|
// 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
|
|
|
|
//get the reading (adc_T);
|
|
int32_t adc_T = ((uint32_t)readRegister(BME280_TEMPERATURE_MSB_REG) << 12) | ((uint32_t)readRegister(BME280_TEMPERATURE_LSB_REG) << 4) | ((readRegister(BME280_TEMPERATURE_XLSB_REG) >> 4) & 0x0F);
|
|
|
|
//By datasheet, calibrate
|
|
int64_t var1, var2;
|
|
|
|
var1 = ((((adc_T>>3) - ((int32_t)calibration.dig_T1<<1))) * ((int32_t)calibration.dig_T2)) >> 11;
|
|
var2 = (((((adc_T>>4) - ((int32_t)calibration.dig_T1)) * ((adc_T>>4) - ((int32_t)calibration.dig_T1))) >> 12) *
|
|
((int32_t)calibration.dig_T3)) >> 14;
|
|
t_fine = var1 + var2;
|
|
float output = (t_fine * 5 + 128) >> 8;
|
|
|
|
output = output / 100;
|
|
|
|
return output;
|
|
}
|
|
|
|
float BME280::readTempF( void )
|
|
{
|
|
float output = readTempC();
|
|
output = (output * 9) / 5 + 32;
|
|
|
|
return output;
|
|
}
|
|
|
|
//****************************************************************************//
|
|
//
|
|
// Utility
|
|
//
|
|
//****************************************************************************//
|
|
void BME280::readRegisterRegion(uint8_t *outputPointer , uint8_t offset, uint8_t length)
|
|
{
|
|
//define pointer that will point to the external space
|
|
uint8_t i = 0;
|
|
char c = 0;
|
|
|
|
switch (settings.commInterface)
|
|
{
|
|
|
|
case I2C_MODE:
|
|
Wire.beginTransmission(settings.I2CAddress);
|
|
Wire.write(offset);
|
|
Wire.endTransmission();
|
|
|
|
// request bytes from slave device
|
|
Wire.requestFrom(settings.I2CAddress, length);
|
|
while ( (Wire.available()) && (i < length)) // slave may send less than requested
|
|
{
|
|
c = Wire.read(); // receive a byte as character
|
|
*outputPointer = c;
|
|
outputPointer++;
|
|
i++;
|
|
}
|
|
break;
|
|
|
|
case SPI_MODE:
|
|
// take the chip select low to select the device:
|
|
digitalWrite(settings.chipSelectPin, LOW);
|
|
// send the device the register you want to read:
|
|
SPI.transfer(offset | 0x80); //Ored with "read request" bit
|
|
while ( i < length ) // slave may send less than requested
|
|
{
|
|
c = SPI.transfer(0x00); // receive a byte as character
|
|
*outputPointer = c;
|
|
outputPointer++;
|
|
i++;
|
|
}
|
|
// take the chip select high to de-select:
|
|
digitalWrite(settings.chipSelectPin, HIGH);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
uint8_t BME280::readRegister(uint8_t offset)
|
|
{
|
|
//Return value
|
|
uint8_t result;
|
|
uint8_t numBytes = 1;
|
|
switch (settings.commInterface) {
|
|
|
|
case I2C_MODE:
|
|
Wire.beginTransmission(settings.I2CAddress);
|
|
Wire.write(offset);
|
|
Wire.endTransmission();
|
|
|
|
Wire.requestFrom(settings.I2CAddress, numBytes);
|
|
while ( Wire.available() ) // slave may send less than requested
|
|
{
|
|
result = Wire.read(); // receive a byte as a proper uint8_t
|
|
}
|
|
break;
|
|
|
|
case SPI_MODE:
|
|
// take the chip select low to select the device:
|
|
digitalWrite(settings.chipSelectPin, LOW);
|
|
// send the device the register you want to read:
|
|
SPI.transfer(offset | 0x80); //Ored with "read request" bit
|
|
// send a value of 0 to read the first byte returned:
|
|
result = SPI.transfer(0x00);
|
|
// take the chip select high to de-select:
|
|
digitalWrite(settings.chipSelectPin, HIGH);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int16_t BME280::readRegisterInt16( uint8_t offset )
|
|
{
|
|
uint8_t myBuffer[2];
|
|
readRegisterRegion(myBuffer, offset, 2); //Does memory transfer
|
|
int16_t output = (int16_t)myBuffer[0] | int16_t(myBuffer[1] << 8);
|
|
|
|
return output;
|
|
}
|
|
|
|
void BME280::writeRegister(uint8_t offset, uint8_t dataToWrite)
|
|
{
|
|
switch (settings.commInterface)
|
|
{
|
|
case I2C_MODE:
|
|
//Write the byte
|
|
Wire.beginTransmission(settings.I2CAddress);
|
|
Wire.write(offset);
|
|
Wire.write(dataToWrite);
|
|
Wire.endTransmission();
|
|
break;
|
|
|
|
case SPI_MODE:
|
|
// take the chip select low to select the device:
|
|
digitalWrite(settings.chipSelectPin, LOW);
|
|
// send the device the register you want to read:
|
|
SPI.transfer(offset & 0x7F);
|
|
// send a value of 0 to read the first byte returned:
|
|
SPI.transfer(dataToWrite);
|
|
// decrement the number of bytes left to read:
|
|
// take the chip select high to de-select:
|
|
digitalWrite(settings.chipSelectPin, HIGH);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|