diff --git a/NucuCar.Sensors/Environment/Bme680Sensor.cs b/NucuCar.Sensors/Environment/Bme680Sensor.cs index edb2ac5..94d1fd3 100644 --- a/NucuCar.Sensors/Environment/Bme680Sensor.cs +++ b/NucuCar.Sensors/Environment/Bme680Sensor.cs @@ -2,13 +2,15 @@ using System; using System.Collections.Generic; using System.Device.I2c; using System.Threading.Tasks; -using Iot.Device.Bmxx80; -using Iot.Device.Bmxx80.PowerMode; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; using NucuCar.Domain.Sensors; +using NucuCar.Sensors.Environment.Bmxx80; using NucuCarSensorsProto; +using Bme680 = NucuCar.Sensors.Environment.Bmxx80.Bme680; +using Bme680PowerMode = NucuCar.Sensors.Environment.Bmxx80.PowerMode.Bme680PowerMode; +using Sampling = NucuCar.Sensors.Environment.Bmxx80.Sampling; namespace NucuCar.Sensors.Environment { @@ -19,7 +21,7 @@ namespace NucuCar.Sensors.Environment public double Pressure { get; set; } public double VolatileOrganicCompounds { get; set; } } - + /// /// Abstraction for the BME680 sensor. /// See: https://www.bosch-sensortec.com/bst/products/all_products/bme680 @@ -88,9 +90,11 @@ namespace NucuCar.Sensors.Environment /* Initialize measurement */ _bme680.Reset(); - _bme680.SetHumiditySampling(Sampling.UltraLowPower); - _bme680.SetTemperatureSampling(Sampling.UltraHighResolution); - _bme680.SetPressureSampling(Sampling.UltraLowPower); + _bme680.TemperatureSampling = Sampling.HighResolution; + _bme680.HumiditySampling = Sampling.HighResolution; + _bme680.PressureSampling = Sampling.HighResolution; + _bme680.HeaterProfile = Bme680HeaterProfile.Profile2; + CurrentState = SensorStateEnum.Initialized; Logger?.LogInformation($"{DateTimeOffset.Now}:BME680 Sensor initialization OK."); @@ -109,14 +113,24 @@ namespace NucuCar.Sensors.Environment { throw new InvalidOperationException("Can't take measurement on uninitialized sensor!"); } - + + _bme680.ConfigureHeatingProfile(Bme680HeaterProfile.Profile2, + 280, 80, _lastMeasurement.Temperature); + var measurementDuration = _bme680.GetMeasurementDuration(_bme680.HeaterProfile); + /* Force the sensor to take a measurement. */ _bme680.SetPowerMode(Bme680PowerMode.Forced); + await Task.Delay(measurementDuration); - _lastMeasurement.Temperature = (await _bme680.ReadTemperatureAsync()).Celsius; - _lastMeasurement.Pressure = await _bme680.ReadPressureAsync(); - _lastMeasurement.Humidity = await _bme680.ReadHumidityAsync(); - _lastMeasurement.VolatileOrganicCompounds = 0.0; // Not implemented. + _bme680.TryReadTemperature(out var temp); + _bme680.TryReadHumidity(out var humidity); + _bme680.TryReadPressure(out var pressure); + _bme680.TryReadGasResistance(out var gasResistance); + + _lastMeasurement.Temperature = temp.Celsius; + _lastMeasurement.Pressure = pressure.Hectopascal; + _lastMeasurement.Humidity = humidity; + _lastMeasurement.VolatileOrganicCompounds = gasResistance; Logger?.LogDebug($"{DateTimeOffset.Now}:BME680: reading"); Logger?.LogInformation( diff --git a/NucuCar.Sensors/Environment/Bmxx80/Bme280.cs b/NucuCar.Sensors/Environment/Bmxx80/Bme280.cs new file mode 100644 index 0000000..3b51d56 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Bme280.cs @@ -0,0 +1,145 @@ +// 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. + +using System; +using System.Device.I2c; +using NucuCar.Sensors.Environment.Bmxx80.CalibrationData; +using NucuCar.Sensors.Environment.Bmxx80.Register; + +namespace NucuCar.Sensors.Environment.Bmxx80 +{ + /// + /// Represents a BME280 temperature, barometric pressure and humidity sensor. + /// + public class Bme280 : Bmx280Base + { + /// + /// The expected chip ID of the BME280. + /// + private const byte DeviceId = 0x60; + + /// + /// Calibration data for the . + /// + private Bme280CalibrationData _bme280Calibration; + + private Sampling _humiditySampling; + + /// + /// Initializes a new instance of the class. + /// + /// The to create with. + public Bme280(I2cDevice i2cDevice) + : base(DeviceId, i2cDevice) + { + _bme280Calibration = (Bme280CalibrationData)_calibrationData; + _communicationProtocol = CommunicationProtocol.I2c; + } + + /// + /// Gets or sets the humidity sampling. + /// + /// Thrown when the is set to an undefined mode. + public Sampling HumiditySampling + { + get => _humiditySampling; + set + { + if (!Enum.IsDefined(typeof(Sampling), value)) + { + throw new ArgumentOutOfRangeException(); + } + + byte status = Read8BitsFromRegister((byte)Bme280Register.CTRL_HUM); + status = (byte)(status & 0b_1111_1000); + status = (byte)(status | (byte)value); + + Span command = stackalloc[] + { + (byte)Bme280Register.CTRL_HUM, status + }; + _i2cDevice.Write(command); + + // Changes to the above register only become effective after a write operation to "CTRL_MEAS". + byte measureState = Read8BitsFromRegister((byte)Bmx280Register.CTRL_MEAS); + + command = stackalloc[] + { + (byte)Bmx280Register.CTRL_MEAS, measureState + }; + _i2cDevice.Write(command); + _humiditySampling = value; + } + } + + /// + /// Reads the humidity. A return value indicates whether the reading succeeded. + /// + /// + /// Contains the measured humidity as %rH if the was not set to . + /// Contains otherwise. + /// + /// true if measurement was not skipped, otherwise false. + public bool TryReadHumidity(out double humidity) + { + if (HumiditySampling == Sampling.Skipped) + { + humidity = double.NaN; + return false; + } + + // Read the temperature first to load the t_fine value for compensation. + TryReadTemperature(out _); + + var hum = Read16BitsFromRegister((byte)Bme280Register.HUMIDDATA, Endianness.BigEndian); + + humidity = CompensateHumidity(hum); + return true; + } + + /// + /// 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 override int GetMeasurementDuration() + { + return s_osToMeasCycles[(int)PressureSampling] + s_osToMeasCycles[(int)TemperatureSampling] + s_osToMeasCycles[(int)HumiditySampling]; + } + + /// + /// Sets the default configuration for the sensor. + /// + protected override void SetDefaultConfiguration() + { + base.SetDefaultConfiguration(); + HumiditySampling = Sampling.UltraLowPower; + } + + /// + /// Compensates the humidity. + /// + /// The humidity value read from the device. + /// The percentage relative humidity. + private double CompensateHumidity(int adcHumidity) + { + // The humidity is calculated using the compensation formula in the BME280 datasheet. + double varH = TemperatureFine - 76800.0; + varH = (adcHumidity - (_bme280Calibration.DigH4 * 64.0 + _bme280Calibration.DigH5 / 16384.0 * varH)) * + (_bme280Calibration.DigH2 / 65536.0 * (1.0 + _bme280Calibration.DigH6 / 67108864.0 * varH * + (1.0 + _bme280Calibration.DigH3 / 67108864.0 * varH))); + varH *= 1.0 - _bme280Calibration.DigH1 * varH / 524288.0; + + if (varH > 100) + { + varH = 100; + } + else if (varH < 0) + { + varH = 0; + } + + return varH; + } + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/Bme680.cs b/NucuCar.Sensors/Environment/Bmxx80/Bme680.cs new file mode 100644 index 0000000..c2adc1f --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Bme680.cs @@ -0,0 +1,587 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.Device.I2c; +using System.Linq; +using NucuCar.Sensors.Environment.Bmxx80.CalibrationData; +using NucuCar.Sensors.Environment.Bmxx80.FilteringMode; +using NucuCar.Sensors.Environment.Bmxx80.PowerMode; +using NucuCar.Sensors.Environment.Bmxx80.Register; +using NucuCar.Sensors.Environment.Bmxx80.Units; + +namespace NucuCar.Sensors.Environment.Bmxx80 +{ + /// + /// Represents a BME680 temperature, pressure, relative humidity and VOC gas sensor. + /// + public class Bme680 : Bmxx80Base + { + /// + /// Default I2C bus address. + /// + public const byte DefaultI2cAddress = 0x76; + + /// + /// Secondary I2C bus address. + /// + public const byte SecondaryI2cAddress = 0x77; + + /// + /// The expected chip ID of the BME680. + /// + private const byte DeviceId = 0x61; + + /// + /// Calibration data for the . + /// + private Bme680CalibrationData _bme680Calibration; + + /// + protected override int TempCalibrationFactor => 16; + + private readonly List _heaterConfigs = new List(); + private bool _gasConversionIsEnabled; + private bool _heaterIsEnabled; + + private Bme680HeaterProfile _heaterProfile; + private Bme680FilteringMode _filterMode; + private Sampling _humiditySampling; + + private static readonly byte[] s_osToMeasCycles = { 0, 1, 2, 4, 8, 16 }; + private static readonly byte[] s_osToSwitchCount = { 0, 1, 1, 1, 1, 1 }; + private static readonly double[] s_k1Lookup = { 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, -0.8, 0.0, 0.0, -0.2, -0.5, 0.0, -1.0, 0.0, 0.0 }; + private static readonly double[] s_k2Lookup = { 0.0, 0.0, 0.0, 0.0, 0.1, 0.7, 0.0, -0.8, -0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; + + /// + /// Initialize a new instance of the class. + /// + /// The to create with. + public Bme680(I2cDevice i2cDevice) + : base(DeviceId, i2cDevice) + { + _communicationProtocol = CommunicationProtocol.I2c; + } + + /// + /// Gets or sets the humidity sampling. + /// + /// Thrown when the is set to an undefined mode. + public Sampling HumiditySampling + { + get => _humiditySampling; + set + { + if (!Enum.IsDefined(typeof(Sampling), value)) + { + throw new ArgumentOutOfRangeException(); + } + + var status = Read8BitsFromRegister((byte)Bme680Register.CTRL_HUM); + status = (byte)((status & (byte)~Bme680Mask.HUMIDITY_SAMPLING) | (byte)value); + + Span command = stackalloc[] + { + (byte)Bme680Register.CTRL_HUM, status + }; + _i2cDevice.Write(command); + _humiditySampling = value; + } + } + + /// + /// Gets or sets the heater profile to be used for measurements. + /// Current heater profile is only set if the chosen profile is configured. + /// + /// Thrown when the is set to an undefined profile. + public Bme680HeaterProfile HeaterProfile + { + get => _heaterProfile; + set + { + if (_heaterConfigs.Exists(config => config.HeaterProfile == value)) + { + if (!Enum.IsDefined(typeof(Bme680HeaterProfile), value)) + { + throw new ArgumentOutOfRangeException(); + } + + var heaterProfile = Read8BitsFromRegister((byte)Bme680Register.CTRL_GAS_1); + heaterProfile = (byte)((heaterProfile & (byte)~Bme680Mask.NB_CONV) | (byte)value); + + Span command = stackalloc[] + { + (byte)Bme680Register.CTRL_GAS_1, heaterProfile + }; + _i2cDevice.Write(command); + _heaterProfile = value; + } + } + } + + /// + /// Gets or sets the filtering mode to be used for measurements. + /// + /// Thrown when the is set to an undefined mode. + public Bme680FilteringMode FilterMode + { + get => _filterMode; + set + { + if (!Enum.IsDefined(typeof(Bme680FilteringMode), value)) + { + throw new ArgumentOutOfRangeException(); + } + + var filter = Read8BitsFromRegister((byte)Bme680Register.CONFIG); + filter = (byte)((filter & (byte)~Bme680Mask.FILTER_COEFFICIENT) | (byte)value << 2); + + Span command = stackalloc[] + { + (byte)Bme680Register.CONFIG, filter + }; + _i2cDevice.Write(command); + _filterMode = value; + } + } + + /// + /// Gets or sets whether the heater is enabled. + /// + public bool HeaterIsEnabled + { + get => _heaterIsEnabled; + set + { + var heaterStatus = Read8BitsFromRegister((byte)Bme680Register.CTRL_GAS_0); + heaterStatus = (byte)((heaterStatus & (byte)~Bme680Mask.HEAT_OFF) | Convert.ToByte(!value) << 3); + + Span command = stackalloc[] + { + (byte)Bme680Register.CTRL_GAS_0, heaterStatus + }; + _i2cDevice.Write(command); + _heaterIsEnabled = value; + } + } + + /// + /// Gets or sets whether gas conversions are enabled. + /// + public bool GasConversionIsEnabled + { + get => _gasConversionIsEnabled; + set + { + var gasConversion = Read8BitsFromRegister((byte)Bme680Register.CTRL_GAS_1); + gasConversion = (byte)((gasConversion & (byte)~Bme680Mask.RUN_GAS) | Convert.ToByte(value) << 4); + + Span command = stackalloc[] + { + (byte)Bme680Register.CTRL_GAS_1, gasConversion + }; + _i2cDevice.Write(command); + _gasConversionIsEnabled = value; + } + } + + /// + /// Reads whether new data is available. + /// + public bool ReadNewDataIsAvailable() + { + var newData = Read8BitsFromRegister((byte)Bme680Register.STATUS); + newData = (byte)(newData >> 7); + + return Convert.ToBoolean(newData); + } + + /// + /// Reads whether a gas measurement is in process. + /// + public bool ReadGasMeasurementInProcess() + { + var gasMeasInProcess = Read8BitsFromRegister((byte)Bme680Register.STATUS); + gasMeasInProcess = (byte)((gasMeasInProcess & (byte)Bme680Mask.GAS_MEASURING) >> 6); + + return Convert.ToBoolean(gasMeasInProcess); + } + + /// + /// Reads whether a measurement of any kind is in process. + /// + public bool ReadMeasurementInProcess() + { + var measInProcess = Read8BitsFromRegister((byte)Bme680Register.STATUS); + measInProcess = (byte)((measInProcess & (byte)Bme680Mask.MEASURING) >> 5); + + return Convert.ToBoolean(measInProcess); + } + + /// + /// Reads whether the target heater temperature is reached. + /// + public bool ReadHeaterIsStable() + { + var heaterStable = Read8BitsFromRegister((byte)Bme680Register.GAS_RANGE); + heaterStable = (byte)((heaterStable & (byte)Bme680Mask.HEAT_STAB) >> 4); + + return Convert.ToBoolean(heaterStable); + } + + /// + /// Sets the power mode to the given mode + /// + /// The to set. + /// Thrown when the power mode does not match a defined mode in . + public void SetPowerMode(Bme680PowerMode powerMode) + { + if (!Enum.IsDefined(typeof(Bme680PowerMode), powerMode)) + { + throw new ArgumentOutOfRangeException(); + } + + var status = Read8BitsFromRegister((byte)Bme680Register.CTRL_MEAS); + status = (byte)((status & (byte)~Bme680Mask.PWR_MODE) | (byte)powerMode); + + Span command = stackalloc[] + { + (byte)Bme680Register.CTRL_MEAS, status + }; + _i2cDevice.Write(command); + } + + /// + /// Configures a heater profile, making it ready for use. + /// + /// The to configure. + /// The target temperature in °C. Ranging from 0-400. + /// The duration in ms. Ranging from 0-4032. + /// The ambient temperature in °C. + /// Thrown when the heating profile does not match a defined profile in . + public void ConfigureHeatingProfile(Bme680HeaterProfile profile, ushort targetTemperature, ushort duration, double ambientTemperature) + { + if (!Enum.IsDefined(typeof(Bme680HeaterProfile), profile)) + { + throw new ArgumentOutOfRangeException(); + } + + // read ambient temperature for resistance calculation + var heaterResistance = CalculateHeaterResistance(targetTemperature, (short)ambientTemperature); + var heaterDuration = CalculateHeaterDuration(duration); + + Span resistanceCommand = stackalloc[] + { + (byte)((byte)Bme680Register.RES_HEAT_0 + profile), heaterResistance + }; + Span durationCommand = stackalloc[] + { + (byte)((byte)Bme680Register.GAS_WAIT_0 + profile), heaterDuration + }; + _i2cDevice.Write(resistanceCommand); + _i2cDevice.Write(durationCommand); + + // cache heater configuration + if (_heaterConfigs.Exists(config => config.HeaterProfile == profile)) + { + _heaterConfigs.Remove(_heaterConfigs.Single(config => config.HeaterProfile == profile)); + } + + _heaterConfigs.Add(new Bme680HeaterProfileConfig(profile, heaterResistance, duration)); + } + + /// + /// Read the state. + /// + /// The current . + public Bme680PowerMode ReadPowerMode() + { + var status = Read8BitsFromRegister((byte)Bme680Register.CTRL_MEAS); + + return (Bme680PowerMode)(status & (byte)Bme680Mask.PWR_MODE); + } + + /// + /// Gets the required time in ms to perform a measurement. The duration of the gas + /// measurement is not considered if is set to false + /// or the chosen is not configured. + /// The precision of this duration is within 1ms of the actual measurement time. + /// + /// The used . + /// + public int GetMeasurementDuration(Bme680HeaterProfile profile) + { + var measCycles = s_osToMeasCycles[(int)TemperatureSampling]; + measCycles += s_osToMeasCycles[(int)PressureSampling]; + measCycles += s_osToMeasCycles[(int)HumiditySampling]; + + var switchCount = s_osToSwitchCount[(int)TemperatureSampling]; + switchCount += s_osToSwitchCount[(int)PressureSampling]; + switchCount += s_osToSwitchCount[(int)HumiditySampling]; + + double measDuration = measCycles * 1963; + measDuration += 477 * switchCount; // TPH switching duration + + if (GasConversionIsEnabled) + { + measDuration += 477 * 5; // Gas measurement duration + } + + measDuration += 500; // get it to the closest whole number + measDuration /= 1000.0; // convert to ms + measDuration += 1; // wake up duration of 1ms + + if (GasConversionIsEnabled && _heaterConfigs.Exists(config => config.HeaterProfile == profile)) + { + measDuration += _heaterConfigs.Single(config => config.HeaterProfile == profile).HeaterDuration; + } + + return (int)Math.Ceiling(measDuration); + } + + /// + /// Reads the humidity. A return value indicates whether the reading succeeded. + /// + /// + /// Contains the measured humidity as %rH if the was not set to . + /// Contains otherwise. + /// + /// true if measurement was not skipped, otherwise false. + public bool TryReadHumidity(out double humidity) + { + if (HumiditySampling == Sampling.Skipped) + { + humidity = double.NaN; + return false; + } + + // Read humidity data. + var hum = Read16BitsFromRegister((byte)Bme680Register.HUMIDITYDATA, Endianness.BigEndian); + + TryReadTemperature(out _); + humidity = CompensateHumidity(hum); + return true; + } + + /// + /// Reads the pressure. A return value indicates whether the reading succeeded. + /// + /// + /// Contains the measured pressure 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 pressure data. + var press = (int)Read24BitsFromRegister((byte)Bme680Register.PRESSUREDATA, Endianness.BigEndian); + + // Read the temperature first to load the t_fine value for compensation. + TryReadTemperature(out _); + + pressure = CompensatePressure(press >> 4); + return true; + } + + /// + /// 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)Bme680Register.TEMPDATA, Endianness.BigEndian); + + temperature = CompensateTemperature(temp >> 4); + return true; + } + + /// + /// Reads the gas resistance. A return value indicates whether the reading succeeded. + /// + /// + /// Contains the measured gas resistance in Ohm if the heater module reached the target temperature and + /// the measurement was valid. Contains otherwise. + /// + /// true if measurement was not skipped, otherwise false. + public bool TryReadGasResistance(out double gasResistance) + { + if (!ReadGasMeasurementIsValid() || !ReadHeaterIsStable()) + { + gasResistance = double.NaN; + return false; + } + + // Read 10 bit gas resistance value from registers + var gasResRaw = Read8BitsFromRegister((byte)Bme680Register.GAS_RES); + var gasRange = Read8BitsFromRegister((byte)Bme680Register.GAS_RANGE); + + var gasRes = (ushort)((ushort)(gasResRaw << 2) + (byte)(gasRange >> 6)); + gasRange &= (byte)Bme680Mask.GAS_RANGE; + + gasResistance = CalculateGasResistance(gasRes, gasRange); + return true; + } + + /// + /// Sets the default configuration for the sensor. + /// + protected override void SetDefaultConfiguration() + { + base.SetDefaultConfiguration(); + HumiditySampling = Sampling.UltraLowPower; + FilterMode = Bme680FilteringMode.C0; + + _bme680Calibration = (Bme680CalibrationData)_calibrationData; + TryReadTemperature(out var temp); + ConfigureHeatingProfile(Bme680HeaterProfile.Profile1, 320, 150, temp.Celsius); + HeaterProfile = Bme680HeaterProfile.Profile1; + + HeaterIsEnabled = true; + GasConversionIsEnabled = true; + } + + /// + /// Compensates the humidity. + /// + /// The humidity value read from the device. + /// The percentage relative humidity. + private double CompensateHumidity(int adcHumidity) + { + // Calculate the humidity. + var temperature = TemperatureFine / 5120.0; + var var1 = adcHumidity - ((_bme680Calibration.DigH1 * 16.0) + ((_bme680Calibration.DigH3 / 2.0) * temperature)); + var var2 = var1 * ((_bme680Calibration.DigH2 / 262144.0) * (1.0 + ((_bme680Calibration.DigH4 / 16384.0) * temperature) + + ((_bme680Calibration.DigH5 / 1048576.0) * temperature * temperature))); + var var3 = _bme680Calibration.DigH6 / 16384.0; + var var4 = _bme680Calibration.DigH7 / 2097152.0; + var calculatedHumidity = var2 + ((var3 + (var4 * temperature)) * var2 * var2); + + if (calculatedHumidity > 100.0) + { + calculatedHumidity = 100.0; + } + else if (calculatedHumidity < 0.0) + { + calculatedHumidity = 0.0; + } + + return calculatedHumidity; + } + + /// + /// Compensates the pressure. + /// + /// The pressure value read from the device. + /// The pressure in Pa. + private Pressure CompensatePressure(long adcPressure) + { + // Calculate the pressure. + var var1 = (TemperatureFine / 2.0) - 64000.0; + var var2 = var1 * var1 * (_bme680Calibration.DigP6 / 131072.0); + var2 += (var1 * _bme680Calibration.DigP5 * 2.0); + var2 = (var2 / 4.0) + (_bme680Calibration.DigP4 * 65536.0); + var1 = ((_bme680Calibration.DigP3 * var1 * var1 / 16384.0) + (_bme680Calibration.DigP2 * var1)) / 524288.0; + var1 = (1.0 + (var1 / 32768.0)) * _bme680Calibration.DigP1; + var calculatedPressure = 1048576.0 - adcPressure; + + // Avoid exception caused by division by zero. + if (var1 != 0) + { + calculatedPressure = (calculatedPressure - (var2 / 4096.0)) * 6250.0 / var1; + var1 = _bme680Calibration.DigP9 * calculatedPressure * calculatedPressure / 2147483648.0; + var2 = calculatedPressure * (_bme680Calibration.DigP8 / 32768.0); + var var3 = (calculatedPressure / 256.0) * (calculatedPressure / 256.0) * (calculatedPressure / 256.0) + * (_bme680Calibration.DigP10 / 131072.0); + calculatedPressure += (var1 + var2 + var3 + (_bme680Calibration.DigP7 * 128.0)) / 16.0; + } + else + { + calculatedPressure = 0; + } + + return Pressure.FromPascal(calculatedPressure); + } + + private bool ReadGasMeasurementIsValid() + { + var gasMeasValid = Read8BitsFromRegister((byte)Bme680Register.GAS_RANGE); + gasMeasValid = (byte)((gasMeasValid & (byte)Bme680Mask.GAS_VALID) >> 5); + + return Convert.ToBoolean(gasMeasValid); + } + + private double CalculateGasResistance(ushort adcGasRes, byte gasRange) + { + var var1 = 1340.0 + 5.0 * _bme680Calibration.RangeSwErr; + var var2 = var1 * (1.0 + s_k1Lookup[gasRange] / 100.0); + var var3 = 1.0 + s_k2Lookup[gasRange] / 100.0; + var gasResistance = 1.0 / (var3 * 0.000000125 * (1 << gasRange) * ((adcGasRes - 512.0) / var2 + 1.0)); + + return gasResistance; + } + + private byte CalculateHeaterResistance(ushort setTemp, short ambientTemp) + { + // limit maximum temperature to 400°C + if (setTemp > 400) + { + setTemp = 400; + } + + var var1 = _bme680Calibration.DigGh1 / 16.0 + 49.0; + var var2 = _bme680Calibration.DigGh2 / 32768.0 * 0.0005 + 0.00235; + var var3 = _bme680Calibration.DigGh3 / 1024.0; + var var4 = var1 * (1.0 + var2 * setTemp); + var var5 = var4 + var3 * ambientTemp; + var heaterResistance = (byte)(3.4 * (var5 * (4.0 / (4.0 + _bme680Calibration.ResHeatRange)) * (1.0 / (1.0 + _bme680Calibration.ResHeatVal * 0.002)) - 25)); + + return heaterResistance; + } + + // The duration is interpreted as follows: + // Byte [7:6]: multiplication factor of 1, 4, 16 or 64 + // Byte [5:0]: 64 timer values, 1ms step size + // Values are rounded down + private byte CalculateHeaterDuration(ushort duration) + { + byte factor = 0; + byte durationValue; + + // check if value exceeds maximum duration + if (duration > 0xFC0) + { + durationValue = 0xFF; + } + else + { + while (duration > 0x3F) + { + duration = (ushort)(duration >> 2); + factor += 1; + } + + durationValue = (byte)(duration + factor * 64); + } + + return durationValue; + } + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/Bme680HeaterProfile.cs b/NucuCar.Sensors/Environment/Bmxx80/Bme680HeaterProfile.cs new file mode 100644 index 0000000..5c0abb9 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Bme680HeaterProfile.cs @@ -0,0 +1,62 @@ +// 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. + +namespace NucuCar.Sensors.Environment.Bmxx80 +{ + /// + /// 10 addressable heater profiles stored on the Bme680. + /// + public enum Bme680HeaterProfile : byte + { + /// + /// Heater Profile 1. + /// + Profile1 = 0b0000, + + /// + /// Heater Profile 2. + /// + Profile2 = 0b0001, + + /// + /// Heater Profile 3. + /// + Profile3 = 0b0010, + + /// + /// Heater Profile 4. + /// + Profile4 = 0b0011, + + /// + /// Heater Profile 5. + /// + Profile5 = 0b0100, + + /// + /// Heater Profile 6. + /// + Profile6 = 0b0101, + + /// + /// Heater Profile 7. + /// + Profile7 = 0b0110, + + /// + /// Heater Profile 8. + /// + Profile8 = 0b0111, + + /// + /// Heater Profile 9. + /// + Profile9 = 0b1000, + + /// + /// Heater Profile 10. + /// + Profile10 = 0b1001 + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/Bme680HeaterProfileConfig.cs b/NucuCar.Sensors/Environment/Bmxx80/Bme680HeaterProfileConfig.cs new file mode 100644 index 0000000..d962c96 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Bme680HeaterProfileConfig.cs @@ -0,0 +1,48 @@ +// 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. + +using System; + +namespace NucuCar.Sensors.Environment.Bmxx80 +{ + /// + /// The heater profile configuration saved on the device. + /// + public class Bme680HeaterProfileConfig + { + /// + /// The chosen heater profile slot, ranging from 0-9. + /// + public Bme680HeaterProfile HeaterProfile { get; set; } + + /// + /// The heater resistance. + /// + public ushort HeaterResistance { get; set; } + + /// + /// The heater duration in the internally used format. + /// + public ushort HeaterDuration { get; set; } + + /// + /// Creates a new instance of . + /// + /// The used heater profile. + /// The heater resistance in Ohm. + /// The heating duration in ms. + /// Unknown profile setting used + public Bme680HeaterProfileConfig(Bme680HeaterProfile profile, ushort heaterResistance, ushort heaterDuration) + { + if (!Enum.IsDefined(typeof(Bme680HeaterProfile), profile)) + { + throw new ArgumentOutOfRangeException(); + } + + HeaterProfile = profile; + HeaterResistance = heaterResistance; + HeaterDuration = heaterDuration; + } + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/Bme680Mask.cs b/NucuCar.Sensors/Environment/Bmxx80/Bme680Mask.cs new file mode 100644 index 0000000..30cdaa9 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Bme680Mask.cs @@ -0,0 +1,28 @@ +// 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. + +namespace NucuCar.Sensors.Environment.Bmxx80 +{ + internal enum Bme680Mask : byte + { + BIT_H1_DATA_MSK = 0x0F, + + PWR_MODE = 0x03, + HEAT_OFF = 0x08, + RUN_GAS = 0x10, + + HUMIDITY_SAMPLING = 0x07, + FILTER_COEFFICIENT = 0x1C, + NB_CONV = 0x0F, + + GAS_RANGE = 0x0F, + RH_RANGE = 0x30, + RS_ERROR = 0xF0, + + GAS_MEASURING = 0x40, + MEASURING = 0x20, + GAS_VALID = 0x20, + HEAT_STAB = 0x10 + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/Bmp280.cs b/NucuCar.Sensors/Environment/Bmxx80/Bmp280.cs new file mode 100644 index 0000000..d264fb9 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Bmp280.cs @@ -0,0 +1,30 @@ +// 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. + +using System.Device.I2c; +using Iot.Device.Bmxx80; + +namespace NucuCar.Sensors.Environment.Bmxx80 +{ + /// + /// Represents a BME280 temperature and barometric pressure sensor. + /// + public class Bmp280 : Bmx280Base + { + /// + /// The expected chip ID of the BMP280. + /// + private const byte DeviceId = 0x58; + + /// + /// Initializes a new instance of the class. + /// + /// The to create with. + public Bmp280(I2cDevice i2cDevice) + : base(DeviceId, i2cDevice) + { + _communicationProtocol = CommunicationProtocol.I2c; + } + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/Bmx280Base.cs b/NucuCar.Sensors/Environment/Bmxx80/Bmx280Base.cs new file mode 100644 index 0000000..8758579 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Bmx280Base.cs @@ -0,0 +1,299 @@ +// 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); + } + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/Bmxx80.csproj b/NucuCar.Sensors/Environment/Bmxx80/Bmxx80.csproj new file mode 100644 index 0000000..3a94229 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Bmxx80.csproj @@ -0,0 +1,42 @@ + + + + netcoreapp2.1 + false + 8.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(AdditionalProperties);RuntimeIdentifier=linux + + + + + + + + diff --git a/NucuCar.Sensors/Environment/Bmxx80/Bmxx80Base.cs b/NucuCar.Sensors/Environment/Bmxx80/Bmxx80Base.cs new file mode 100644 index 0000000..ca08946 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Bmxx80Base.cs @@ -0,0 +1,345 @@ +// 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. + +using System; +using System.Buffers.Binary; +using System.Device.I2c; +using System.IO; +using NucuCar.Sensors.Environment.Bmxx80.CalibrationData; +using NucuCar.Sensors.Environment.Bmxx80.Register; +using NucuCar.Sensors.Environment.Bmxx80.Units; + +namespace NucuCar.Sensors.Environment.Bmxx80 +{ + /// + /// Represents the core functionality of the Bmxx80 family. + /// + public abstract class Bmxx80Base : IDisposable + { + /// + /// Calibration data for the sensor. + /// + internal Bmxx80CalibrationData _calibrationData; + + /// + /// I2C device used to communicate with the device. + /// + protected I2cDevice _i2cDevice; + + /// + /// Chosen communication protocol. + /// + protected CommunicationProtocol _communicationProtocol; + + /// + /// The control register of the sensor. + /// + protected byte _controlRegister; + + /// + /// Bmxx80 communication protocol. + /// + public enum CommunicationProtocol + { + /// + /// I²C communication protocol. + /// + I2c + } + + /// + /// The variable TemperatureFine carries a fine resolution temperature value over to the + /// pressure compensation formula and could be implemented as a global variable. + /// + protected int TemperatureFine + { + get; + set; + } + + /// + /// The temperature calibration factor. + /// + protected virtual int TempCalibrationFactor => 1; + + private Sampling _temperatureSampling; + private Sampling _pressureSampling; + + /// + /// Initializes a new instance of the class. + /// + /// The ID of the device. + /// The to create with. + /// Thrown when the given is null. + /// Thrown when the device cannot be found on the bus. + protected Bmxx80Base(byte deviceId, I2cDevice i2cDevice) + { + _i2cDevice = i2cDevice ?? throw new ArgumentNullException(nameof(i2cDevice)); + _i2cDevice.WriteByte((byte)Bmxx80Register.CHIPID); + + byte readSignature = _i2cDevice.ReadByte(); + + if (readSignature != deviceId) + { + throw new IOException($"Unable to find a chip with id {deviceId}"); + } + + ReadCalibrationData(); + SetDefaultConfiguration(); + } + + /// + /// Gets or sets the pressure sampling. + /// + /// Thrown when the is set to an undefined mode. + public Sampling PressureSampling + { + get => _pressureSampling; + set + { + byte status = Read8BitsFromRegister(_controlRegister); + status = (byte)(status & 0b1110_0011); + status = (byte)(status | (byte)value << 2); + + Span command = stackalloc[] + { + _controlRegister, status + }; + _i2cDevice.Write(command); + _pressureSampling = value; + } + } + + /// + /// Gets or sets the temperature sampling. + /// + /// Thrown when the is set to an undefined mode. + public Sampling TemperatureSampling + { + get => _temperatureSampling; + set + { + byte status = Read8BitsFromRegister(_controlRegister); + status = (byte)(status & 0b0001_1111); + status = (byte)(status | (byte)value << 5); + + Span command = stackalloc[] + { + _controlRegister, status + }; + _i2cDevice.Write(command); + _temperatureSampling = value; + } + } + + /// + /// When called, the device is reset using the complete power-on-reset procedure. + /// The device will reset to the default configuration. + /// + public void Reset() + { + const byte resetCommand = 0xB6; + Span command = stackalloc[] + { + (byte)Bmxx80Register.RESET, resetCommand + }; + _i2cDevice.Write(command); + + SetDefaultConfiguration(); + } + + /// + /// 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 abstract bool TryReadTemperature(out Temperature temperature); + + /// + /// Reads the pressure. A return value indicates whether the reading succeeded. + /// + /// + /// Contains the measured pressure if the was not set to . + /// Contains otherwise. + /// + /// true if measurement was not skipped, otherwise false. + public abstract bool TryReadPressure(out Pressure pressure); + + /// + /// Compensates the temperature. + /// + /// The temperature value read from the device. + /// The . + protected Temperature CompensateTemperature(int adcTemperature) + { + // The temperature is calculated using the compensation formula in the BMP280 datasheet. + // See: https://cdn-shop.adafruit.com/datasheets/BST-BMP280-DS001-11.pdf + double var1 = ((adcTemperature / 16384.0) - (_calibrationData.DigT1 / 1024.0)) * _calibrationData.DigT2; + double var2 = (adcTemperature / 131072.0) - (_calibrationData.DigT1 / 8192.0); + var2 *= var2 * _calibrationData.DigT3 * TempCalibrationFactor; + + TemperatureFine = (int)(var1 + var2); + + double temp = (var1 + var2) / 5120.0; + return Temperature.FromCelsius(temp); + } + + /// + /// Reads an 8 bit value from a register. + /// + /// Register to read from. + /// Value from register. + protected internal byte Read8BitsFromRegister(byte register) + { + if (_communicationProtocol == CommunicationProtocol.I2c) + { + _i2cDevice.WriteByte(register); + byte value = _i2cDevice.ReadByte(); + return value; + } + else + { + throw new NotImplementedException(); + } + } + + /// + /// Reads a 16 bit value over I2C. + /// + /// Register to read from. + /// Interpretation of the bytes (big or little endian). + /// Value from register. + protected internal ushort Read16BitsFromRegister(byte register, Endianness endianness = Endianness.LittleEndian) + { + Span bytes = stackalloc byte[2]; + switch (_communicationProtocol) + { + case CommunicationProtocol.I2c: + _i2cDevice.WriteByte(register); + _i2cDevice.Read(bytes); + break; + default: + throw new NotImplementedException(); + } + + return endianness switch + { + Endianness.LittleEndian => BinaryPrimitives.ReadUInt16LittleEndian(bytes), + Endianness.BigEndian => BinaryPrimitives.ReadUInt16BigEndian(bytes), + _ => throw new ArgumentOutOfRangeException(nameof(endianness), endianness, null) + }; + } + + /// + /// Reads a 24 bit value over I2C. + /// + /// Register to read from. + /// Interpretation of the bytes (big or little endian). + /// Value from register. + protected internal uint Read24BitsFromRegister(byte register, Endianness endianness = Endianness.LittleEndian) + { + Span bytes = stackalloc byte[4]; + switch (_communicationProtocol) + { + case CommunicationProtocol.I2c: + _i2cDevice.WriteByte(register); + _i2cDevice.Read(bytes.Slice(1)); + break; + default: + throw new NotImplementedException(); + } + + return endianness switch + { + Endianness.LittleEndian => BinaryPrimitives.ReadUInt32LittleEndian(bytes), + Endianness.BigEndian => BinaryPrimitives.ReadUInt32BigEndian(bytes), + _ => throw new ArgumentOutOfRangeException(nameof(endianness), endianness, null) + }; + } + + /// + /// Converts byte to . + /// + /// Value to convert. + /// + protected Sampling ByteToSampling(byte value) + { + // Values >=5 equals UltraHighResolution. + if (value >= 5) + { + return Sampling.UltraHighResolution; + } + + return (Sampling)value; + } + + /// + /// Sets the default configuration for the sensor. + /// + protected virtual void SetDefaultConfiguration() + { + PressureSampling = Sampling.UltraLowPower; + TemperatureSampling = Sampling.UltraLowPower; + } + + /// + /// Specifies the Endianness of a device. + /// + protected internal enum Endianness + { + /// + /// Indicates little endian. + /// + LittleEndian, + + /// + /// Indicates big endian. + /// + BigEndian + } + + private void ReadCalibrationData() + { + switch (this) + { + case NucuCar.Sensors.Environment.Bmxx80.Bme280 _: + _calibrationData = new Bme280CalibrationData(); + _controlRegister = (byte)Bmx280Register.CTRL_MEAS; + break; + case NucuCar.Sensors.Environment.Bmxx80.Bmp280 _: + _calibrationData = new Bmp280CalibrationData(); + _controlRegister = (byte)Bmx280Register.CTRL_MEAS; + break; + case NucuCar.Sensors.Environment.Bmxx80.Bme680 _: + _calibrationData = new Bme680CalibrationData(); + _controlRegister = (byte)Bme680Register.CTRL_MEAS; + break; + } + + _calibrationData.ReadFromDevice(this); + } + + /// + /// Cleanup. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases the unmanaged resources used by the Bmxx80 and optionally releases the managed resources. + /// + /// True to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + _i2cDevice?.Dispose(); + _i2cDevice = null; + } + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/CalibrationData/Bme280CalibrationData.cs b/NucuCar.Sensors/Environment/Bmxx80/CalibrationData/Bme280CalibrationData.cs new file mode 100644 index 0000000..52155b7 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/CalibrationData/Bme280CalibrationData.cs @@ -0,0 +1,41 @@ +// 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. + +using Iot.Device.Bmxx80; +using Iot.Device.Bmxx80.CalibrationData; +using Iot.Device.Bmxx80.Register; +using NucuCar.Sensors.Environment.Bmxx80.Register; + +namespace NucuCar.Sensors.Environment.Bmxx80.CalibrationData +{ + /// + /// Calibration data for the BME280. + /// + internal class Bme280CalibrationData : Bmp280CalibrationData + { + public byte DigH1 { get; set; } + public short DigH2 { get; set; } + public ushort DigH3 { get; set; } + public short DigH4 { get; set; } + public short DigH5 { get; set; } + public sbyte DigH6 { get; set; } + + /// + /// Read coefficient data from device. + /// + /// The to read coefficient data from. + protected internal override void ReadFromDevice(Bmxx80Base bmxx80Base) + { + // Read humidity calibration data. + DigH1 = bmxx80Base.Read8BitsFromRegister((byte)Bme280Register.DIG_H1); + DigH2 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bme280Register.DIG_H2); + DigH3 = bmxx80Base.Read8BitsFromRegister((byte)Bme280Register.DIG_H3); + DigH4 = (short)((bmxx80Base.Read8BitsFromRegister((byte)Bme280Register.DIG_H4) << 4) | (bmxx80Base.Read8BitsFromRegister((byte)Bme280Register.DIG_H4 + 1) & 0xF)); + DigH5 = (short)((bmxx80Base.Read8BitsFromRegister((byte)Bme280Register.DIG_H5 + 1) << 4) | (bmxx80Base.Read8BitsFromRegister((byte)Bme280Register.DIG_H5) >> 4)); + DigH6 = (sbyte)bmxx80Base.Read8BitsFromRegister((byte)Bme280Register.DIG_H6); + + base.ReadFromDevice(bmxx80Base); + } + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/CalibrationData/Bme680CalibrationData.cs b/NucuCar.Sensors/Environment/Bmxx80/CalibrationData/Bme680CalibrationData.cs new file mode 100644 index 0000000..df7eba0 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/CalibrationData/Bme680CalibrationData.cs @@ -0,0 +1,78 @@ +// 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. + +using Iot.Device.Bmxx80; +using Iot.Device.Bmxx80.CalibrationData; +using Iot.Device.Bmxx80.Register; +using NucuCar.Sensors.Environment.Bmxx80.Register; + +namespace NucuCar.Sensors.Environment.Bmxx80.CalibrationData +{ + /// + /// Calibration data for the . + /// + internal class Bme680CalibrationData : Bmxx80CalibrationData + { + public byte DigP10 { get; set; } + + public ushort DigH1 { get; set; } + public ushort DigH2 { get; set; } + public sbyte DigH3 { get; set; } + public sbyte DigH4 { get; set; } + public sbyte DigH5 { get; set; } + public byte DigH6 { get; set; } + public sbyte DigH7 { get; set; } + + public sbyte DigGh1 { get; set; } + public short DigGh2 { get; set; } + public sbyte DigGh3 { get; set; } + + public byte ResHeatRange { get; set; } + public sbyte ResHeatVal { get; set; } + public sbyte RangeSwErr { get; set; } + + /// + /// Read coefficient data from device. + /// + /// The to read coefficient data from. + protected internal override void ReadFromDevice(Bmxx80Base bmxx80Base) + { + // Read temperature calibration data. + DigT1 = bmxx80Base.Read16BitsFromRegister((byte)Bme680Register.DIG_T1); + DigT2 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bme680Register.DIG_T2); + DigT3 = bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_T3); + + // Read humidity calibration data. + DigH1 = (ushort)((bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_H1_MSB) << 4) | (bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_H1_LSB) & (byte)Bme680Mask.BIT_H1_DATA_MSK)); + DigH2 = (ushort)((bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_H2_MSB) << 4) | (bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_H2_LSB) >> 4)); + DigH3 = (sbyte)bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_H3); + DigH4 = (sbyte)bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_H4); + DigH5 = (sbyte)bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_H5); + DigH6 = bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_H6); + DigH7 = (sbyte)(bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_H7)); + + // Read pressure calibration data. + DigP1 = bmxx80Base.Read16BitsFromRegister((byte)Bme680Register.DIG_P1_LSB); + DigP2 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bme680Register.DIG_P2_LSB); + DigP3 = bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_P3); + DigP4 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bme680Register.DIG_P4_LSB); + DigP5 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bme680Register.DIG_P5_LSB); + DigP6 = bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_P6); + DigP7 = bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_P7); + DigP8 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bme680Register.DIG_P8_LSB); + DigP9 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bme680Register.DIG_P9_LSB); + DigP10 = bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_P10); + + // read gas calibration data. + DigGh1 = (sbyte)bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_GH1); + DigGh2 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bme680Register.DIG_GH2); + DigGh3 = (sbyte)bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.DIG_GH3); + + // read heater calibration data + ResHeatRange = (byte)((bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.RES_HEAT_RANGE) & (byte)Bme680Mask.RH_RANGE) >> 4); + RangeSwErr = (sbyte)((bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.RANGE_SW_ERR) & (byte)Bme680Mask.RS_ERROR) >> 4); + ResHeatVal = (sbyte)bmxx80Base.Read8BitsFromRegister((byte)Bme680Register.RES_HEAT_VAL); + } + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/CalibrationData/Bmp280CalibrationData.cs b/NucuCar.Sensors/Environment/Bmxx80/CalibrationData/Bmp280CalibrationData.cs new file mode 100644 index 0000000..6ca4207 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/CalibrationData/Bmp280CalibrationData.cs @@ -0,0 +1,40 @@ +// 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. + +using Iot.Device.Bmxx80; +using Iot.Device.Bmxx80.CalibrationData; +using Iot.Device.Bmxx80.Register; +using NucuCar.Sensors.Environment.Bmxx80.Register; + +namespace NucuCar.Sensors.Environment.Bmxx80.CalibrationData +{ + /// + /// Calibration data for the BMP280. + /// + internal class Bmp280CalibrationData : Bmxx80CalibrationData + { + /// + /// Read coefficient data from device. + /// + /// The to read coefficient data from. + protected internal override void ReadFromDevice(Bmxx80Base bmxx80Base) + { + // Read temperature calibration data + DigT1 = bmxx80Base.Read16BitsFromRegister((byte)Bmx280Register.DIG_T1); + DigT2 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bmx280Register.DIG_T2); + DigT3 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bmx280Register.DIG_T3); + + // Read pressure calibration data + DigP1 = bmxx80Base.Read16BitsFromRegister((byte)Bmx280Register.DIG_P1); + DigP2 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bmx280Register.DIG_P2); + DigP3 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bmx280Register.DIG_P3); + DigP4 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bmx280Register.DIG_P4); + DigP5 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bmx280Register.DIG_P5); + DigP6 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bmx280Register.DIG_P6); + DigP7 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bmx280Register.DIG_P7); + DigP8 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bmx280Register.DIG_P8); + DigP9 = (short)bmxx80Base.Read16BitsFromRegister((byte)Bmx280Register.DIG_P9); + } + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/CalibrationData/Bmxx80CalibrationData.cs b/NucuCar.Sensors/Environment/Bmxx80/CalibrationData/Bmxx80CalibrationData.cs new file mode 100644 index 0000000..1cf1d49 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/CalibrationData/Bmxx80CalibrationData.cs @@ -0,0 +1,34 @@ +// 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. + +using Iot.Device.Bmxx80; + +namespace NucuCar.Sensors.Environment.Bmxx80.CalibrationData +{ + /// + /// Calibration data for the Bmxx80 family. + /// + internal abstract class Bmxx80CalibrationData + { + public ushort DigT1 { get; set; } + public short DigT2 { get; set; } + public short DigT3 { get; set; } + + public ushort DigP1 { get; set; } + public short DigP2 { get; set; } + public short DigP3 { get; set; } + public short DigP4 { get; set; } + public short DigP5 { get; set; } + public short DigP6 { get; set; } + public short DigP7 { get; set; } + public short DigP8 { get; set; } + public short DigP9 { get; set; } + + /// + /// Read coefficient data from device. + /// + /// The to read coefficient data from. + protected internal abstract void ReadFromDevice(Bmxx80Base bmxx80Base); + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/DeviceStatus.cs b/NucuCar.Sensors/Environment/Bmxx80/DeviceStatus.cs new file mode 100644 index 0000000..72d4db0 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/DeviceStatus.cs @@ -0,0 +1,23 @@ +// 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. + +namespace NucuCar.Sensors.Environment.Bmxx80 +{ + /// + /// Indicates the status of the device. + /// + public class DeviceStatus + { + /// + /// True whenever a conversion is running and False when the results have been transferred to the data registers. + /// + public bool Measuring { get; set; } + + /// + /// True when the NVM data is being copied to images registers and False when the copying is done. + /// The data is copied at power-on-reset and before every conversion. + /// + public bool ImageUpdating { get; set; } + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/FilteringMode/Bme680FilteringMode.cs b/NucuCar.Sensors/Environment/Bmxx80/FilteringMode/Bme680FilteringMode.cs new file mode 100644 index 0000000..946e290 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/FilteringMode/Bme680FilteringMode.cs @@ -0,0 +1,53 @@ +// 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. + +namespace NucuCar.Sensors.Environment.Bmxx80.FilteringMode +{ + /// + /// IIR filter coefficient. The higher the coefficient, the slower the sensors + /// responds to external inputs. + /// + public enum Bme680FilteringMode + { + /// + /// Filter coefficient of 0. + /// + C0 = 0b000, + + /// + /// Filter coefficient of 1. + /// + C1 = 0b001, + + /// + /// Filter coefficient of 3. + /// + C3 = 0b010, + + /// + /// Filter coefficient of 7. + /// + C7 = 0b011, + + /// + /// Filter coefficient of 15. + /// + C15 = 0b100, + + /// + /// Filter coefficient of 31. + /// + C31 = 0b101, + + /// + /// Filter coefficient of 63. + /// + C63 = 0b110, + + /// + /// Filter coefficient of 127. + /// + C127 = 0b111 + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/FilteringMode/Bmx280FilteringMode.cs b/NucuCar.Sensors/Environment/Bmxx80/FilteringMode/Bmx280FilteringMode.cs new file mode 100644 index 0000000..36c2b9f --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/FilteringMode/Bmx280FilteringMode.cs @@ -0,0 +1,45 @@ +// 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. + +namespace NucuCar.Sensors.Environment.Bmxx80.FilteringMode +{ + /// + /// Bmx280 devices feature an internal IIR filter. + /// + /// + /// This filter effectively reduces the bandwidth of the temperature and pressure output signals + /// and increases the resolution of the pressure and temperature output data to 20 bits. + /// + /// The higher the coefficient, the slower the sensors responds to external inputs. + /// + /// See the data sheet with recommended settings for different scenarios. + /// + public enum Bmx280FilteringMode : byte + { + /// + /// Filter off. + /// + Off = 0b000, + + /// + /// Coefficient x2. + /// + X2 = 0b001, + + /// + /// Coefficient x4. + /// + X4 = 0b010, + + /// + /// Coefficient x8. + /// + X8 = 0b011, + + /// + /// Coefficient x16. + /// + X16 = 0b100 + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/PowerMode/Bme680PowerMode.cs b/NucuCar.Sensors/Environment/Bmxx80/PowerMode/Bme680PowerMode.cs new file mode 100644 index 0000000..28d7bcf --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/PowerMode/Bme680PowerMode.cs @@ -0,0 +1,32 @@ +// 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. + +namespace NucuCar.Sensors.Environment.Bmxx80.PowerMode +{ + /// + /// Sensor power mode. + /// + /// + /// Section 3.1 in the datasheet. + /// + public enum Bme680PowerMode : byte + { + /// + /// No measurements are performed. + /// + /// + /// Minimal power consumption. + /// + Sleep = 0b00, + + /// + /// Single TPHG cycle is performed. + /// + /// + /// Sensor automatically returns to sleep mode afterwards. + /// Gas sensor heater only operates during gas measurement. + /// + Forced = 0b01 + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/PowerMode/Bmx280PowerMode.cs b/NucuCar.Sensors/Environment/Bmxx80/PowerMode/Bmx280PowerMode.cs new file mode 100644 index 0000000..e779715 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/PowerMode/Bmx280PowerMode.cs @@ -0,0 +1,28 @@ +// 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. + +namespace NucuCar.Sensors.Environment.Bmxx80.PowerMode +{ + /// + /// Sensor power mode. + /// + public enum Bmx280PowerMode : byte + { + /// + /// No operations, all registers accessible, lowest power mode, selected after startup. + /// + Sleep = 0b00, + + /// + /// Perform one measurement, store results, and return to sleep mode. + /// + Forced = 0b10, + + /// + /// Perpetual cycling of measurements and inactive periods. + /// This interval is determined by the combination of IIR filter and standby time options. + /// + Normal = 0b11 + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/README.md b/NucuCar.Sensors/Environment/Bmxx80/README.md new file mode 100644 index 0000000..9fcf21e --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/README.md @@ -0,0 +1,44 @@ +# BMxx80 Device Family + +## Summary + +BMxx80 is a device family that senses temperature, barometric pressure, altitude, humidity and VOC gas. + +SPI and I2C can be used to communicate with the device (only I2C implemented so far). + +## Device Family +The implementation supports the following devices: + +- BMP280 temperature and barometric pressure sensor ([Datasheet](https://cdn-shop.adafruit.com/datasheets/BST-BMP280-DS001-11.pdf)) +- BME280 temperature, barometric pressure and humidity sensor ([Datasheet](https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME280-DS002.pdf)) +- BME680 temperature, barometric pressure, humidity and VOC gas sensor ([Datasheet](https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME680-DS001.pdf)) + +## Usage + +3 examples on how to use this device binding are available in the [samples](samples) folder. + +The following fritzing diagram illustrates one way to wire up the BMP280 with a Raspberry Pi using I2C: + +![Raspberry Pi Breadboard diagram](samples/rpi-bmp280_i2c.png) + +General: + +| Bmp280 | Raspberry | +|--------|:---------:| +|Vin| Power pin| +|GND| Ground| + +I2C: + +| Bmp280 | Raspberry | +|--------|:---------:| +|SCK| I2C clock pin| +|SDI| I2C data pin| + +### Connection Type + +The following connection types are supported by this binding. + +- [X] I2C +- [ ] SPI + diff --git a/NucuCar.Sensors/Environment/Bmxx80/Register/Bme280Register.cs b/NucuCar.Sensors/Environment/Bmxx80/Register/Bme280Register.cs new file mode 100644 index 0000000..cbe2c37 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Register/Bme280Register.cs @@ -0,0 +1,23 @@ +// 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. + +namespace NucuCar.Sensors.Environment.Bmxx80.Register +{ + /// + /// General control registers for the BME280. + /// + internal enum Bme280Register : byte + { + CTRL_HUM = 0xF2, + + DIG_H1 = 0xA1, + DIG_H2 = 0xE1, + DIG_H3 = 0xE3, + DIG_H4 = 0xE4, + DIG_H5 = 0xE5, + DIG_H6 = 0xE7, + + HUMIDDATA = 0xFD + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/Register/Bme680Register.cs b/NucuCar.Sensors/Environment/Bmxx80/Register/Bme680Register.cs new file mode 100644 index 0000000..dfbdcf3 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Register/Bme680Register.cs @@ -0,0 +1,64 @@ +// 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. + +namespace NucuCar.Sensors.Environment.Bmxx80.Register +{ + /// + /// General control registers for the BME680. + /// + /// + /// See section 5.2 Memory map. + /// + internal enum Bme680Register : byte + { + DIG_H1_LSB = 0xE2, + DIG_H1_MSB = 0xE3, + DIG_H2_LSB = 0xE2, + DIG_H2_MSB = 0xE1, + DIG_H3 = 0xE4, + DIG_H4 = 0xE5, + DIG_H5 = 0xE6, + DIG_H6 = 0xE7, + DIG_H7 = 0xE8, + + DIG_T1 = 0xE9, + DIG_T2 = 0x8A, + DIG_T3 = 0x8C, + + DIG_P1_LSB = 0x8E, + DIG_P2_LSB = 0x90, + DIG_P3 = 0x92, + DIG_P4_LSB = 0x94, + DIG_P5_LSB = 0x96, + DIG_P6 = 0x99, + DIG_P7 = 0x98, + DIG_P8_LSB = 0x9C, + DIG_P9_LSB = 0x9E, + DIG_P10 = 0xA0, + + DIG_GH1 = 0xED, + DIG_GH2 = 0xEB, + DIG_GH3 = 0xEE, + + RES_HEAT_VAL = 0x00, + RES_HEAT_RANGE = 0x02, + RANGE_SW_ERR = 0x04, + STATUS = 0x1D, + + PRESSUREDATA = 0x1F, + TEMPDATA = 0x22, + HUMIDITYDATA = 0x25, + + GAS_RES = 0x2A, + GAS_RANGE = 0x2B, + RES_HEAT_0 = 0x5A, + GAS_WAIT_0 = 0x64, + + CTRL_GAS_0 = 0x70, + CTRL_GAS_1 = 0x71, + CTRL_HUM = 0x72, + CTRL_MEAS = 0x74, + CONFIG = 0x75 + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/Register/Bmx280Register.cs b/NucuCar.Sensors/Environment/Bmxx80/Register/Bmx280Register.cs new file mode 100644 index 0000000..72a78f0 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Register/Bmx280Register.cs @@ -0,0 +1,34 @@ +// 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. + +namespace NucuCar.Sensors.Environment.Bmxx80.Register +{ + /// + /// Register shared by the Bmx280 family. + /// + internal enum Bmx280Register : byte + { + CTRL_MEAS = 0xF4, + + DIG_T1 = 0x88, + DIG_T2 = 0x8A, + DIG_T3 = 0x8C, + + DIG_P1 = 0x8E, + DIG_P2 = 0x90, + DIG_P3 = 0x92, + DIG_P4 = 0x94, + DIG_P5 = 0x96, + DIG_P6 = 0x98, + DIG_P7 = 0x9A, + DIG_P8 = 0x9C, + DIG_P9 = 0x9E, + + STATUS = 0xF3, + CONFIG = 0xF5, + + PRESSUREDATA = 0xF7, + TEMPDATA_MSB = 0xFA + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/Register/Bmxx80Register.cs b/NucuCar.Sensors/Environment/Bmxx80/Register/Bmxx80Register.cs new file mode 100644 index 0000000..a9cffcc --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Register/Bmxx80Register.cs @@ -0,0 +1,15 @@ +// 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. + +namespace NucuCar.Sensors.Environment.Bmxx80.Register +{ + /// + /// Registers shared in the Bmxx80 family. + /// + internal enum Bmxx80Register : byte + { + CHIPID = 0xD0, + RESET = 0xE0 + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/Sampling.cs b/NucuCar.Sensors/Environment/Bmxx80/Sampling.cs new file mode 100644 index 0000000..b3d54d5 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Sampling.cs @@ -0,0 +1,45 @@ +// 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. + +namespace NucuCar.Sensors.Environment.Bmxx80 +{ + /// + /// Oversampling settings. + /// + /// + /// Maximum of x2 is recommended for temperature. + /// + public enum Sampling : byte + { + /// + /// Skipped (output set to 0x80000). + /// + Skipped = 0b000, + + /// + /// Oversampling x1. + /// + UltraLowPower = 0b001, + + /// + /// Oversampling x2. + /// + LowPower = 0b010, + + /// + /// Oversampling x4. + /// + Standard = 0b011, + + /// + /// Oversampling x8. + /// + HighResolution = 0b100, + + /// + /// Oversampling x16. + /// + UltraHighResolution = 0b101, + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/StandbyTime.cs b/NucuCar.Sensors/Environment/Bmxx80/StandbyTime.cs new file mode 100644 index 0000000..3907635 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/StandbyTime.cs @@ -0,0 +1,52 @@ +// 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. + +namespace NucuCar.Sensors.Environment.Bmxx80 +{ + /// + /// Controls the inactive duration in normal mode. + /// + public enum StandbyTime : byte + { + /// + /// 0.5 ms. + /// + Ms0_5 = 0b000, + + /// + /// 62.5 ms. + /// + Ms62_5 = 0b001, + + /// + /// 125 ms. + /// + Ms125 = 0b010, + + /// + /// 250 ms. + /// + Ms250 = 0b011, + + /// + /// 500 ms. + /// + Ms500 = 0b100, + + /// + /// 1,000 ms. + /// + Ms1000 = 0b101, + + /// + /// 10 ms. + /// + Ms10 = 0b110, + + /// + /// 20 ms. + /// + Ms20 = 0b111, + } +} diff --git a/NucuCar.Sensors/Environment/Bmxx80/Units/Pressure.cs b/NucuCar.Sensors/Environment/Bmxx80/Units/Pressure.cs new file mode 100644 index 0000000..862484c --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Units/Pressure.cs @@ -0,0 +1,115 @@ +namespace NucuCar.Sensors.Environment.Bmxx80.Units +{ + /// + /// Structure representing pressure + /// + public struct Pressure + { + private const double MillibarRatio = 0.01; + private const double KilopascalRatio = 0.001; + private const double HectopascalRatio = 0.01; + private const double InchOfMercuryRatio = 0.000295301; + private const double MillimeterOfMercuryRatio = 0.00750062; + private double _pascal; + + private Pressure(double pascal) + { + _pascal = pascal; + } + + /// + /// The mean sea-level pressure (MSLP) is the average atmospheric pressure at mean sea level + /// + public static Pressure MeanSeaLevel => Pressure.FromPascal(101325); + + /// + /// Pressure in Pa + /// + public double Pascal => _pascal; + + /// + /// Pressure in mbar + /// + public double Millibar => MillibarRatio * _pascal; + + /// + /// Pressure in kPa + /// + public double Kilopascal => KilopascalRatio * _pascal; + + /// + /// Pressure in hPa + /// + public double Hectopascal => HectopascalRatio * _pascal; + + /// + /// Pressure in inHg + /// + public double InchOfMercury => InchOfMercuryRatio * _pascal; + + /// + /// Pressure in mmHg + /// + public double MillimeterOfMercury => MillimeterOfMercuryRatio * _pascal; + + /// + /// Creates Pressure instance from pressure in Pa + /// + /// Pressure value in Pa + /// Pressure instance + public static Pressure FromPascal(double value) + { + return new Pressure(value); + } + + /// + /// Creates Pressure instance from pressure in mbar + /// + /// Pressure value in mbar + /// Pressure instance + public static Pressure FromMillibar(double value) + { + return new Pressure(value / MillibarRatio); + } + + /// + /// Creates Pressure instance from pressure in kPa + /// + /// Pressure value in kPa + /// Pressure instance + public static Pressure FromKilopascal(double value) + { + return new Pressure(value / KilopascalRatio); + } + + /// + /// Creates Pressure instance from pressure in hPa + /// + /// Pressure value in hPa + /// Pressure instance + public static Pressure FromHectopascal(double value) + { + return new Pressure(value / HectopascalRatio); + } + + /// + /// Creates Pressure instance from pressure in inHg + /// + /// Pressure value in inHg + /// Pressure instance + public static Pressure FromInchOfMercury(double value) + { + return new Pressure(value / InchOfMercuryRatio); + } + + /// + /// Creates Pressure instance from pressure in mmHg + /// + /// Pressure value in mmHg + /// Pressure instance + public static Pressure FromMillimeterOfMercury(double value) + { + return new Pressure(value / MillimeterOfMercuryRatio); + } + } +} \ No newline at end of file diff --git a/NucuCar.Sensors/Environment/Bmxx80/Units/Temperature.cs b/NucuCar.Sensors/Environment/Bmxx80/Units/Temperature.cs new file mode 100644 index 0000000..763dfbf --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/Units/Temperature.cs @@ -0,0 +1,60 @@ +namespace NucuCar.Sensors.Environment.Bmxx80.Units +{ + public struct Temperature + { + private const double KelvinOffset = 273.15; + private const double FahrenheitOffset = 32.0; + private const double FahrenheitRatio = 1.8; + private double _celsius; + + private Temperature(double celsius) + { + _celsius = celsius; + } + + /// + /// Temperature in Celsius + /// + public double Celsius => _celsius; + + /// + /// Temperature in Fahrenheit + /// + public double Fahrenheit => FahrenheitRatio * _celsius + FahrenheitOffset; + + /// + /// Temperature in Kelvin + /// + public double Kelvin => _celsius + KelvinOffset; + + /// + /// Creates Temperature instance from temperature in Celsius + /// + /// Temperature value in Celsius + /// Temperature instance + public static Temperature FromCelsius(double value) + { + return new Temperature(value); + } + + /// + /// Creates Temperature instance from temperature in Fahrenheit + /// + /// Temperature value in Fahrenheit + /// Temperature instance + public static Temperature FromFahrenheit(double value) + { + return new Temperature((value - FahrenheitOffset) / FahrenheitRatio); + } + + /// + /// Creates Temperature instance from temperature in Kelvin + /// + /// Temperature value in Kelvin + /// Temperature instance + public static Temperature FromKelvin(double value) + { + return new Temperature(value - KelvinOffset); + } + } +} \ No newline at end of file diff --git a/NucuCar.Sensors/Environment/Bmxx80/category.txt b/NucuCar.Sensors/Environment/Bmxx80/category.txt new file mode 100644 index 0000000..b11aaf9 --- /dev/null +++ b/NucuCar.Sensors/Environment/Bmxx80/category.txt @@ -0,0 +1,6 @@ +barometer +altimeter +thermometer +voc +gas +hygrometer