// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Ported from https://github.com/adafruit/Adafruit_BMP280_Library/blob/master/Adafruit_BMP280.cpp
// Formulas and code examples can also be found in the datasheet http://www.adafruit.com/datasheets/BST-BMP280-DS001-11.pdf
using System;
using System.Device.I2c;
using System.IO;
using NucuCar.Sensors.Modules.Environment.Bmxx80.FilteringMode;
using NucuCar.Sensors.Modules.Environment.Bmxx80.Register;
using NucuCar.Sensors.Modules.Environment.Bmxx80.Units;
using Bmx280PowerMode = NucuCar.Sensors.Modules.Environment.Bmxx80.PowerMode.Bmx280PowerMode;
namespace NucuCar.Sensors.Modules.Environment.Bmxx80
{
///
/// Represents the core functionality of the Bmx280 family.
///
public abstract class Bmx280Base : Bmxx80Base
{
///
/// Default I2C bus address.
///
public const byte DefaultI2cAddress = 0x77;
///
/// Secondary I2C bus address.
///
public const byte SecondaryI2cAddress = 0x76;
///
/// Converts oversampling to needed measurement cycles for that oversampling.
///
protected static readonly int[] s_osToMeasCycles = { 0, 7, 9, 14, 23, 44 };
private Bmx280FilteringMode _filteringMode;
private StandbyTime _standbyTime;
///
/// Initializes a new instance of the class.
///
/// The ID of the device.
/// The to create with.
protected Bmx280Base(byte deviceId, I2cDevice i2cDevice)
: base(deviceId, i2cDevice)
{
}
///
/// Gets or sets the IIR filter mode.
///
/// Thrown when the is set to an undefined mode.
public Bmx280FilteringMode FilterMode
{
get => _filteringMode;
set
{
byte current = Read8BitsFromRegister((byte)Bmx280Register.CONFIG);
current = (byte)((current & 0b_1110_0011) | (byte)value << 2);
Span command = stackalloc[]
{
(byte)Bmx280Register.CONFIG, current
};
_i2cDevice.Write(command);
_filteringMode = value;
}
}
///
/// Gets or sets the standby time between two consecutive measurements.
///
/// Thrown when the is set to an undefined mode.
public StandbyTime StandbyTime
{
get => _standbyTime;
set
{
byte current = Read8BitsFromRegister((byte)Bmx280Register.CONFIG);
current = (byte)((current & 0b_0001_1111) | (byte)value << 5);
Span command = stackalloc[]
{
(byte)Bmx280Register.CONFIG, current
};
_i2cDevice.Write(command);
_standbyTime = value;
}
}
///
/// Reads the temperature. A return value indicates whether the reading succeeded.
///
///
/// Contains the measured temperature if the was not set to .
/// Contains otherwise.
///
/// true
if measurement was not skipped, otherwise false
.
public override bool TryReadTemperature(out Temperature temperature)
{
if (TemperatureSampling == Sampling.Skipped)
{
temperature = Temperature.FromCelsius(double.NaN);
return false;
}
var temp = (int)Read24BitsFromRegister((byte)Bmx280Register.TEMPDATA_MSB, Endianness.BigEndian);
temperature = CompensateTemperature(temp >> 4);
return true;
}
///
/// Read the state.
///
/// The current .
/// Thrown when the power mode does not match a defined mode in .
public Bmx280PowerMode ReadPowerMode()
{
byte read = Read8BitsFromRegister(_controlRegister);
// Get only the power mode bits.
var powerMode = (byte)(read & 0b_0000_0011);
if (Enum.IsDefined(typeof(Bmx280PowerMode), powerMode) == false)
{
throw new IOException("Read unexpected power mode");
}
return powerMode switch
{
0b00 => Bmx280PowerMode.Sleep,
0b10 => Bmx280PowerMode.Forced,
0b11 => Bmx280PowerMode.Normal,
_ => throw new NotImplementedException($"Read power mode not defined by specification.")
};
}
///
/// Reads the pressure. A return value indicates whether the reading succeeded.
///
///
/// Contains the measured pressure in Pa if the was not set to .
/// Contains otherwise.
///
/// true
if measurement was not skipped, otherwise false
.
public override bool TryReadPressure(out Pressure pressure)
{
if (PressureSampling == Sampling.Skipped)
{
pressure = Pressure.FromPascal(double.NaN);
return false;
}
// Read the temperature first to load the t_fine value for compensation.
TryReadTemperature(out _);
// Read pressure data.
var press = (int)Read24BitsFromRegister((byte)Bmx280Register.PRESSUREDATA, Endianness.BigEndian);
// Convert the raw value to the pressure in Pa.
var pressPa = CompensatePressure(press >> 4);
// Return the pressure as a Pressure instance.
pressure = Pressure.FromHectopascal(pressPa.Hectopascal / 256);
return true;
}
///
/// Calculates the altitude in meters from the specified sea-level pressure(in hPa).
///
/// Sea-level pressure
///
/// Contains the calculated metres above sea-level if the was not set to .
/// Contains otherwise.
///
/// true
if pressure measurement was not skipped, otherwise false
.
public bool TryReadAltitude(Pressure seaLevelPressure, out double altitude)
{
// Read the pressure first.
var success = TryReadPressure(out var pressure);
if (!success)
{
altitude = double.NaN;
return false;
}
// Calculate and return the altitude using the international barometric formula.
altitude = 44330.0 * (1.0 - Math.Pow(pressure.Hectopascal / seaLevelPressure.Hectopascal, 0.1903));
return true;
}
///
/// Calculates the altitude in meters from the mean sea-level pressure.
///
///
/// Contains the calculated metres above sea-level if the was not set to .
/// Contains otherwise.
///
/// true
if pressure measurement was not skipped, otherwise false
.
public bool TryReadAltitude(out double altitude)
{
return TryReadAltitude(Pressure.MeanSeaLevel, out altitude);
}
///
/// Get the current status of the device.
///
/// The .
public DeviceStatus ReadStatus()
{
var status = Read8BitsFromRegister((byte)Bmx280Register.STATUS);
// Bit 3.
var measuring = ((status >> 3) & 1) == 1;
// Bit 0.
var imageUpdating = (status & 1) == 1;
return new DeviceStatus
{
ImageUpdating = imageUpdating,
Measuring = measuring
};
}
///
/// Sets the power mode to the given mode
///
/// The to set.
public void SetPowerMode(Bmx280PowerMode powerMode)
{
byte read = Read8BitsFromRegister(_controlRegister);
// Clear last 2 bits.
var cleared = (byte)(read & 0b_1111_1100);
Span command = stackalloc[]
{
_controlRegister, (byte)(cleared | (byte)powerMode)
};
_i2cDevice.Write(command);
}
///
/// Gets the required time in ms to perform a measurement with the current sampling modes.
///
/// The time it takes for the chip to read data in milliseconds rounded up.
public virtual int GetMeasurementDuration()
{
return s_osToMeasCycles[(int)PressureSampling] + s_osToMeasCycles[(int)TemperatureSampling];
}
///
/// Sets the default configuration for the sensor.
///
protected override void SetDefaultConfiguration()
{
base.SetDefaultConfiguration();
FilterMode = Bmx280FilteringMode.Off;
StandbyTime = StandbyTime.Ms125;
}
///
/// Compensates the pressure in Pa, in Q24.8 format (24 integer bits and 8 fractional bits).
///
/// The pressure value read from the device.
/// Pressure in Hectopascals (hPa).
///
/// Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa.
///
private Pressure CompensatePressure(long adcPressure)
{
// Formula from the datasheet http://www.adafruit.com/datasheets/BST-BMP280-DS001-11.pdf
// The pressure is calculated using the compensation formula in the BMP280 datasheet
long var1 = TemperatureFine - 128000;
long var2 = var1 * var1 * (long)_calibrationData.DigP6;
var2 = var2 + ((var1 * (long)_calibrationData.DigP5) << 17);
var2 = var2 + ((long)_calibrationData.DigP4 << 35);
var1 = ((var1 * var1 * (long)_calibrationData.DigP3) >> 8) + ((var1 * (long)_calibrationData.DigP2) << 12);
var1 = ((((1L << 47) + var1)) * (long)_calibrationData.DigP1) >> 33;
if (var1 == 0)
{
return Pressure.FromPascal(0); // Avoid exception caused by division by zero
}
// Perform calibration operations
long p = 1048576 - adcPressure;
p = (((p << 31) - var2) * 3125) / var1;
var1 = ((long)_calibrationData.DigP9 * (p >> 13) * (p >> 13)) >> 25;
var2 = ((long)_calibrationData.DigP8 * p) >> 19;
p = ((p + var1 + var2) >> 8) + ((long)_calibrationData.DigP7 << 4);
return Pressure.FromPascal(p);
}
}
}