// 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.Environment.Bmxx80.FilteringMode; using NucuCar.Sensors.Environment.Bmxx80.Register; using NucuCar.Sensors.Environment.Bmxx80.Units; using Bmx280PowerMode = NucuCar.Sensors.Environment.Bmxx80.PowerMode.Bmx280PowerMode; namespace NucuCar.Sensors.Environment.Bmxx80 { /// /// Represents the core functionality of the Bmx280 family. /// public abstract class Bmx280Base : NucuCar.Sensors.Environment.Bmxx80.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 NucuCar.Sensors.Environment.Bmxx80.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 NucuCar.Sensors.Environment.Bmxx80.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 == NucuCar.Sensors.Environment.Bmxx80.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 == NucuCar.Sensors.Environment.Bmxx80.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 NucuCar.Sensors.Environment.Bmxx80.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 NucuCar.Sensors.Environment.Bmxx80.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 = NucuCar.Sensors.Environment.Bmxx80.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); } } }