NucuCar.Sensors: Add hack for reading gas resistance
This commit is contained in:
parent
0bd282f5db
commit
d9823f2af8
29 changed files with 2443 additions and 11 deletions
|
@ -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; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 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(
|
||||
|
|
145
NucuCar.Sensors/Environment/Bmxx80/Bme280.cs
Normal file
145
NucuCar.Sensors/Environment/Bmxx80/Bme280.cs
Normal file
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a BME280 temperature, barometric pressure and humidity sensor.
|
||||
/// </summary>
|
||||
public class Bme280 : Bmx280Base
|
||||
{
|
||||
/// <summary>
|
||||
/// The expected chip ID of the BME280.
|
||||
/// </summary>
|
||||
private const byte DeviceId = 0x60;
|
||||
|
||||
/// <summary>
|
||||
/// Calibration data for the <see cref="Bme680"/>.
|
||||
/// </summary>
|
||||
private Bme280CalibrationData _bme280Calibration;
|
||||
|
||||
private Sampling _humiditySampling;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Bme280"/> class.
|
||||
/// </summary>
|
||||
/// <param name="i2cDevice">The <see cref="I2cDevice"/> to create with.</param>
|
||||
public Bme280(I2cDevice i2cDevice)
|
||||
: base(DeviceId, i2cDevice)
|
||||
{
|
||||
_bme280Calibration = (Bme280CalibrationData)_calibrationData;
|
||||
_communicationProtocol = CommunicationProtocol.I2c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the humidity sampling.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <see cref="Sampling"/> is set to an undefined mode.</exception>
|
||||
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<byte> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the humidity. A return value indicates whether the reading succeeded.
|
||||
/// </summary>
|
||||
/// <param name="humidity">
|
||||
/// Contains the measured humidity as %rH if the <see cref="HumiditySampling"/> was not set to <see cref="Sampling.Skipped"/>.
|
||||
/// Contains <see cref="double.NaN"/> otherwise.
|
||||
/// </param>
|
||||
/// <returns><code>true</code> if measurement was not skipped, otherwise <code>false</code>.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the required time in ms to perform a measurement with the current sampling modes.
|
||||
/// </summary>
|
||||
/// <returns>The time it takes for the chip to read data in milliseconds rounded up.</returns>
|
||||
public override int GetMeasurementDuration()
|
||||
{
|
||||
return s_osToMeasCycles[(int)PressureSampling] + s_osToMeasCycles[(int)TemperatureSampling] + s_osToMeasCycles[(int)HumiditySampling];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default configuration for the sensor.
|
||||
/// </summary>
|
||||
protected override void SetDefaultConfiguration()
|
||||
{
|
||||
base.SetDefaultConfiguration();
|
||||
HumiditySampling = Sampling.UltraLowPower;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compensates the humidity.
|
||||
/// </summary>
|
||||
/// <param name="adcHumidity">The humidity value read from the device.</param>
|
||||
/// <returns>The percentage relative humidity.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
587
NucuCar.Sensors/Environment/Bmxx80/Bme680.cs
Normal file
587
NucuCar.Sensors/Environment/Bmxx80/Bme680.cs
Normal file
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a BME680 temperature, pressure, relative humidity and VOC gas sensor.
|
||||
/// </summary>
|
||||
public class Bme680 : Bmxx80Base
|
||||
{
|
||||
/// <summary>
|
||||
/// Default I2C bus address.
|
||||
/// </summary>
|
||||
public const byte DefaultI2cAddress = 0x76;
|
||||
|
||||
/// <summary>
|
||||
/// Secondary I2C bus address.
|
||||
/// </summary>
|
||||
public const byte SecondaryI2cAddress = 0x77;
|
||||
|
||||
/// <summary>
|
||||
/// The expected chip ID of the BME680.
|
||||
/// </summary>
|
||||
private const byte DeviceId = 0x61;
|
||||
|
||||
/// <summary>
|
||||
/// Calibration data for the <see cref="Bme680"/>.
|
||||
/// </summary>
|
||||
private Bme680CalibrationData _bme680Calibration;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override int TempCalibrationFactor => 16;
|
||||
|
||||
private readonly List<Bme680HeaterProfileConfig> _heaterConfigs = new List<Bme680HeaterProfileConfig>();
|
||||
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 };
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new instance of the <see cref="Bme680"/> class.
|
||||
/// </summary>
|
||||
/// <param name="i2cDevice">The <see cref="I2cDevice"/> to create with.</param>
|
||||
public Bme680(I2cDevice i2cDevice)
|
||||
: base(DeviceId, i2cDevice)
|
||||
{
|
||||
_communicationProtocol = CommunicationProtocol.I2c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the humidity sampling.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <see cref="Sampling"/> is set to an undefined mode.</exception>
|
||||
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<byte> command = stackalloc[]
|
||||
{
|
||||
(byte)Bme680Register.CTRL_HUM, status
|
||||
};
|
||||
_i2cDevice.Write(command);
|
||||
_humiditySampling = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the heater profile to be used for measurements.
|
||||
/// Current heater profile is only set if the chosen profile is configured.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <see cref="Bme680HeaterProfile"/> is set to an undefined profile.</exception>
|
||||
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<byte> command = stackalloc[]
|
||||
{
|
||||
(byte)Bme680Register.CTRL_GAS_1, heaterProfile
|
||||
};
|
||||
_i2cDevice.Write(command);
|
||||
_heaterProfile = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the filtering mode to be used for measurements.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <see cref="Bme680FilteringMode"/> is set to an undefined mode.</exception>
|
||||
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<byte> command = stackalloc[]
|
||||
{
|
||||
(byte)Bme680Register.CONFIG, filter
|
||||
};
|
||||
_i2cDevice.Write(command);
|
||||
_filterMode = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the heater is enabled.
|
||||
/// </summary>
|
||||
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<byte> command = stackalloc[]
|
||||
{
|
||||
(byte)Bme680Register.CTRL_GAS_0, heaterStatus
|
||||
};
|
||||
_i2cDevice.Write(command);
|
||||
_heaterIsEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether gas conversions are enabled.
|
||||
/// </summary>
|
||||
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<byte> command = stackalloc[]
|
||||
{
|
||||
(byte)Bme680Register.CTRL_GAS_1, gasConversion
|
||||
};
|
||||
_i2cDevice.Write(command);
|
||||
_gasConversionIsEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads whether new data is available.
|
||||
/// </summary>
|
||||
public bool ReadNewDataIsAvailable()
|
||||
{
|
||||
var newData = Read8BitsFromRegister((byte)Bme680Register.STATUS);
|
||||
newData = (byte)(newData >> 7);
|
||||
|
||||
return Convert.ToBoolean(newData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads whether a gas measurement is in process.
|
||||
/// </summary>
|
||||
public bool ReadGasMeasurementInProcess()
|
||||
{
|
||||
var gasMeasInProcess = Read8BitsFromRegister((byte)Bme680Register.STATUS);
|
||||
gasMeasInProcess = (byte)((gasMeasInProcess & (byte)Bme680Mask.GAS_MEASURING) >> 6);
|
||||
|
||||
return Convert.ToBoolean(gasMeasInProcess);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads whether a measurement of any kind is in process.
|
||||
/// </summary>
|
||||
public bool ReadMeasurementInProcess()
|
||||
{
|
||||
var measInProcess = Read8BitsFromRegister((byte)Bme680Register.STATUS);
|
||||
measInProcess = (byte)((measInProcess & (byte)Bme680Mask.MEASURING) >> 5);
|
||||
|
||||
return Convert.ToBoolean(measInProcess);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads whether the target heater temperature is reached.
|
||||
/// </summary>
|
||||
public bool ReadHeaterIsStable()
|
||||
{
|
||||
var heaterStable = Read8BitsFromRegister((byte)Bme680Register.GAS_RANGE);
|
||||
heaterStable = (byte)((heaterStable & (byte)Bme680Mask.HEAT_STAB) >> 4);
|
||||
|
||||
return Convert.ToBoolean(heaterStable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the power mode to the given mode
|
||||
/// </summary>
|
||||
/// <param name="powerMode">The <see cref="Bme680PowerMode"/> to set.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the power mode does not match a defined mode in <see cref="Bme680PowerMode"/>.</exception>
|
||||
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<byte> command = stackalloc[]
|
||||
{
|
||||
(byte)Bme680Register.CTRL_MEAS, status
|
||||
};
|
||||
_i2cDevice.Write(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures a heater profile, making it ready for use.
|
||||
/// </summary>
|
||||
/// <param name="profile">The <see cref="Bme680HeaterProfile"/> to configure.</param>
|
||||
/// <param name="targetTemperature">The target temperature in °C. Ranging from 0-400.</param>
|
||||
/// <param name="duration">The duration in ms. Ranging from 0-4032.</param>
|
||||
/// <param name="ambientTemperature">The ambient temperature in °C.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the heating profile does not match a defined profile in <see cref="Bme680HeaterProfile"/>.</exception>
|
||||
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<byte> resistanceCommand = stackalloc[]
|
||||
{
|
||||
(byte)((byte)Bme680Register.RES_HEAT_0 + profile), heaterResistance
|
||||
};
|
||||
Span<byte> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the <see cref="Bme680PowerMode"/> state.
|
||||
/// </summary>
|
||||
/// <returns>The current <see cref="Bme680PowerMode"/>.</returns>
|
||||
public Bme680PowerMode ReadPowerMode()
|
||||
{
|
||||
var status = Read8BitsFromRegister((byte)Bme680Register.CTRL_MEAS);
|
||||
|
||||
return (Bme680PowerMode)(status & (byte)Bme680Mask.PWR_MODE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the required time in ms to perform a measurement. The duration of the gas
|
||||
/// measurement is not considered if <see cref="GasConversionIsEnabled"/> is set to false
|
||||
/// or the chosen <see cref="Bme680HeaterProfile"/> is not configured.
|
||||
/// The precision of this duration is within 1ms of the actual measurement time.
|
||||
/// </summary>
|
||||
/// <param name="profile">The used <see cref="Bme680HeaterProfile"/>. </param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the humidity. A return value indicates whether the reading succeeded.
|
||||
/// </summary>
|
||||
/// <param name="humidity">
|
||||
/// Contains the measured humidity as %rH if the <see cref="HumiditySampling"/> was not set to <see cref="Sampling.Skipped"/>.
|
||||
/// Contains <see cref="double.NaN"/> otherwise.
|
||||
/// </param>
|
||||
/// <returns><code>true</code> if measurement was not skipped, otherwise <code>false</code>.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the pressure. A return value indicates whether the reading succeeded.
|
||||
/// </summary>
|
||||
/// <param name="pressure">
|
||||
/// Contains the measured pressure if the <see cref="Bmxx80Base.PressureSampling"/> was not set to <see cref="Sampling.Skipped"/>.
|
||||
/// Contains <see cref="double.NaN"/> otherwise.
|
||||
/// </param>
|
||||
/// <returns><code>true</code> if measurement was not skipped, otherwise <code>false</code>.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the temperature. A return value indicates whether the reading succeeded.
|
||||
/// </summary>
|
||||
/// <param name="temperature">
|
||||
/// Contains the measured temperature if the <see cref="Bmxx80Base.TemperatureSampling"/> was not set to <see cref="Sampling.Skipped"/>.
|
||||
/// Contains <see cref="double.NaN"/> otherwise.
|
||||
/// </param>
|
||||
/// <returns><code>true</code> if measurement was not skipped, otherwise <code>false</code>.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the gas resistance. A return value indicates whether the reading succeeded.
|
||||
/// </summary>
|
||||
/// <param name="gasResistance">
|
||||
/// Contains the measured gas resistance in Ohm if the heater module reached the target temperature and
|
||||
/// the measurement was valid. Contains <see cref="double.NaN"/> otherwise.
|
||||
/// </param>
|
||||
/// <returns><code>true</code> if measurement was not skipped, otherwise <code>false</code>.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default configuration for the sensor.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compensates the humidity.
|
||||
/// </summary>
|
||||
/// <param name="adcHumidity">The humidity value read from the device.</param>
|
||||
/// <returns>The percentage relative humidity.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compensates the pressure.
|
||||
/// </summary>
|
||||
/// <param name="adcPressure">The pressure value read from the device.</param>
|
||||
/// <returns>The pressure in Pa.</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
62
NucuCar.Sensors/Environment/Bmxx80/Bme680HeaterProfile.cs
Normal file
62
NucuCar.Sensors/Environment/Bmxx80/Bme680HeaterProfile.cs
Normal file
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 10 addressable heater profiles stored on the Bme680.
|
||||
/// </summary>
|
||||
public enum Bme680HeaterProfile : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Heater Profile 1.
|
||||
/// </summary>
|
||||
Profile1 = 0b0000,
|
||||
|
||||
/// <summary>
|
||||
/// Heater Profile 2.
|
||||
/// </summary>
|
||||
Profile2 = 0b0001,
|
||||
|
||||
/// <summary>
|
||||
/// Heater Profile 3.
|
||||
/// </summary>
|
||||
Profile3 = 0b0010,
|
||||
|
||||
/// <summary>
|
||||
/// Heater Profile 4.
|
||||
/// </summary>
|
||||
Profile4 = 0b0011,
|
||||
|
||||
/// <summary>
|
||||
/// Heater Profile 5.
|
||||
/// </summary>
|
||||
Profile5 = 0b0100,
|
||||
|
||||
/// <summary>
|
||||
/// Heater Profile 6.
|
||||
/// </summary>
|
||||
Profile6 = 0b0101,
|
||||
|
||||
/// <summary>
|
||||
/// Heater Profile 7.
|
||||
/// </summary>
|
||||
Profile7 = 0b0110,
|
||||
|
||||
/// <summary>
|
||||
/// Heater Profile 8.
|
||||
/// </summary>
|
||||
Profile8 = 0b0111,
|
||||
|
||||
/// <summary>
|
||||
/// Heater Profile 9.
|
||||
/// </summary>
|
||||
Profile9 = 0b1000,
|
||||
|
||||
/// <summary>
|
||||
/// Heater Profile 10.
|
||||
/// </summary>
|
||||
Profile10 = 0b1001
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// The heater profile configuration saved on the device.
|
||||
/// </summary>
|
||||
public class Bme680HeaterProfileConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The chosen heater profile slot, ranging from 0-9.
|
||||
/// </summary>
|
||||
public Bme680HeaterProfile HeaterProfile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The heater resistance.
|
||||
/// </summary>
|
||||
public ushort HeaterResistance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The heater duration in the internally used format.
|
||||
/// </summary>
|
||||
public ushort HeaterDuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="Bme680HeaterProfileConfig"/>.
|
||||
/// </summary>
|
||||
/// <param name="profile">The used heater profile.</param>
|
||||
/// <param name="heaterResistance">The heater resistance in Ohm.</param>
|
||||
/// <param name="heaterDuration">The heating duration in ms.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Unknown profile setting used</exception>
|
||||
public Bme680HeaterProfileConfig(Bme680HeaterProfile profile, ushort heaterResistance, ushort heaterDuration)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(Bme680HeaterProfile), profile))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
HeaterProfile = profile;
|
||||
HeaterResistance = heaterResistance;
|
||||
HeaterDuration = heaterDuration;
|
||||
}
|
||||
}
|
||||
}
|
28
NucuCar.Sensors/Environment/Bmxx80/Bme680Mask.cs
Normal file
28
NucuCar.Sensors/Environment/Bmxx80/Bme680Mask.cs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
30
NucuCar.Sensors/Environment/Bmxx80/Bmp280.cs
Normal file
30
NucuCar.Sensors/Environment/Bmxx80/Bmp280.cs
Normal file
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a BME280 temperature and barometric pressure sensor.
|
||||
/// </summary>
|
||||
public class Bmp280 : Bmx280Base
|
||||
{
|
||||
/// <summary>
|
||||
/// The expected chip ID of the BMP280.
|
||||
/// </summary>
|
||||
private const byte DeviceId = 0x58;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Bmp280"/> class.
|
||||
/// </summary>
|
||||
/// <param name="i2cDevice">The <see cref="I2cDevice"/> to create with.</param>
|
||||
public Bmp280(I2cDevice i2cDevice)
|
||||
: base(DeviceId, i2cDevice)
|
||||
{
|
||||
_communicationProtocol = CommunicationProtocol.I2c;
|
||||
}
|
||||
}
|
||||
}
|
299
NucuCar.Sensors/Environment/Bmxx80/Bmx280Base.cs
Normal file
299
NucuCar.Sensors/Environment/Bmxx80/Bmx280Base.cs
Normal file
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the core functionality of the Bmx280 family.
|
||||
/// </summary>
|
||||
public abstract class Bmx280Base : NucuCar.Sensors.Environment.Bmxx80.Bmxx80Base
|
||||
{
|
||||
/// <summary>
|
||||
/// Default I2C bus address.
|
||||
/// </summary>
|
||||
public const byte DefaultI2cAddress = 0x77;
|
||||
|
||||
/// <summary>
|
||||
/// Secondary I2C bus address.
|
||||
/// </summary>
|
||||
public const byte SecondaryI2cAddress = 0x76;
|
||||
|
||||
/// <summary>
|
||||
/// Converts oversampling to needed measurement cycles for that oversampling.
|
||||
/// </summary>
|
||||
protected static readonly int[] s_osToMeasCycles = { 0, 7, 9, 14, 23, 44 };
|
||||
|
||||
private Bmx280FilteringMode _filteringMode;
|
||||
private NucuCar.Sensors.Environment.Bmxx80.StandbyTime _standbyTime;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Bmx280Base"/> class.
|
||||
/// </summary>
|
||||
/// <param name="deviceId">The ID of the device.</param>
|
||||
/// <param name="i2cDevice">The <see cref="I2cDevice"/> to create with.</param>
|
||||
protected Bmx280Base(byte deviceId, I2cDevice i2cDevice)
|
||||
: base(deviceId, i2cDevice)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the IIR filter mode.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <see cref="Bmx280FilteringMode"/> is set to an undefined mode.</exception>
|
||||
public Bmx280FilteringMode FilterMode
|
||||
{
|
||||
get => _filteringMode;
|
||||
set
|
||||
{
|
||||
byte current = Read8BitsFromRegister((byte)Bmx280Register.CONFIG);
|
||||
current = (byte)((current & 0b_1110_0011) | (byte)value << 2);
|
||||
|
||||
Span<byte> command = stackalloc[]
|
||||
{
|
||||
(byte)Bmx280Register.CONFIG, current
|
||||
};
|
||||
_i2cDevice.Write(command);
|
||||
_filteringMode = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the standby time between two consecutive measurements.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <see cref="NucuCar.Sensors.Environment.Bmxx80.StandbyTime"/> is set to an undefined mode.</exception>
|
||||
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<byte> command = stackalloc[]
|
||||
{
|
||||
(byte)Bmx280Register.CONFIG, current
|
||||
};
|
||||
_i2cDevice.Write(command);
|
||||
_standbyTime = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the temperature. A return value indicates whether the reading succeeded.
|
||||
/// </summary>
|
||||
/// <param name="temperature">
|
||||
/// Contains the measured temperature if the <see cref="NucuCar.Sensors.Environment.Bmxx80.Bmxx80Base.TemperatureSampling"/> was not set to <see cref="NucuCar.Sensors.Environment.Bmxx80.Sampling.Skipped"/>.
|
||||
/// Contains <see cref="double.NaN"/> otherwise.
|
||||
/// </param>
|
||||
/// <returns><code>true</code> if measurement was not skipped, otherwise <code>false</code>.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the <see cref="Bmx280PowerMode"/> state.
|
||||
/// </summary>
|
||||
/// <returns>The current <see cref="Bmx280PowerMode"/>.</returns>
|
||||
/// <exception cref="NotImplementedException">Thrown when the power mode does not match a defined mode in <see cref="Bmx280PowerMode"/>.</exception>
|
||||
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.")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the pressure. A return value indicates whether the reading succeeded.
|
||||
/// </summary>
|
||||
/// <param name="pressure">
|
||||
/// Contains the measured pressure in Pa if the <see cref="NucuCar.Sensors.Environment.Bmxx80.Bmxx80Base.PressureSampling"/> was not set to <see cref="NucuCar.Sensors.Environment.Bmxx80.Sampling.Skipped"/>.
|
||||
/// Contains <see cref="double.NaN"/> otherwise.
|
||||
/// </param>
|
||||
/// <returns><code>true</code> if measurement was not skipped, otherwise <code>false</code>.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the altitude in meters from the specified sea-level pressure(in hPa).
|
||||
/// </summary>
|
||||
/// <param name="seaLevelPressure">Sea-level pressure</param>
|
||||
/// <param name="altitude">
|
||||
/// Contains the calculated metres above sea-level if the <see cref="NucuCar.Sensors.Environment.Bmxx80.Bmxx80Base.PressureSampling"/> was not set to <see cref="NucuCar.Sensors.Environment.Bmxx80.Sampling.Skipped"/>.
|
||||
/// Contains <see cref="double.NaN"/> otherwise.
|
||||
/// </param>
|
||||
/// <returns><code>true</code> if pressure measurement was not skipped, otherwise <code>false</code>.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the altitude in meters from the mean sea-level pressure.
|
||||
/// </summary>
|
||||
/// <param name="altitude">
|
||||
/// Contains the calculated metres above sea-level if the <see cref="NucuCar.Sensors.Environment.Bmxx80.Bmxx80Base.PressureSampling"/> was not set to <see cref="NucuCar.Sensors.Environment.Bmxx80.Sampling.Skipped"/>.
|
||||
/// Contains <see cref="double.NaN"/> otherwise.
|
||||
/// </param>
|
||||
/// <returns><code>true</code> if pressure measurement was not skipped, otherwise <code>false</code>.</returns>
|
||||
public bool TryReadAltitude(out double altitude)
|
||||
{
|
||||
return TryReadAltitude(Pressure.MeanSeaLevel, out altitude);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current status of the device.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="NucuCar.Sensors.Environment.Bmxx80.DeviceStatus"/>.</returns>
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the power mode to the given mode
|
||||
/// </summary>
|
||||
/// <param name="powerMode">The <see cref="Bmx280PowerMode"/> to set.</param>
|
||||
public void SetPowerMode(Bmx280PowerMode powerMode)
|
||||
{
|
||||
byte read = Read8BitsFromRegister(_controlRegister);
|
||||
|
||||
// Clear last 2 bits.
|
||||
var cleared = (byte)(read & 0b_1111_1100);
|
||||
|
||||
Span<byte> command = stackalloc[]
|
||||
{
|
||||
_controlRegister, (byte)(cleared | (byte)powerMode)
|
||||
};
|
||||
_i2cDevice.Write(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the required time in ms to perform a measurement with the current sampling modes.
|
||||
/// </summary>
|
||||
/// <returns>The time it takes for the chip to read data in milliseconds rounded up.</returns>
|
||||
public virtual int GetMeasurementDuration()
|
||||
{
|
||||
return s_osToMeasCycles[(int)PressureSampling] + s_osToMeasCycles[(int)TemperatureSampling];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default configuration for the sensor.
|
||||
/// </summary>
|
||||
protected override void SetDefaultConfiguration()
|
||||
{
|
||||
base.SetDefaultConfiguration();
|
||||
FilterMode = Bmx280FilteringMode.Off;
|
||||
StandbyTime = NucuCar.Sensors.Environment.Bmxx80.StandbyTime.Ms125;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compensates the pressure in Pa, in Q24.8 format (24 integer bits and 8 fractional bits).
|
||||
/// </summary>
|
||||
/// <param name="adcPressure">The pressure value read from the device.</param>
|
||||
/// <returns>Pressure in Hectopascals (hPa).</returns>
|
||||
/// <remarks>
|
||||
/// Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa.
|
||||
/// </remarks>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
42
NucuCar.Sensors/Environment/Bmxx80/Bmxx80.csproj
Normal file
42
NucuCar.Sensors/Environment/Bmxx80/Bmxx80.csproj
Normal file
|
@ -0,0 +1,42 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<EnableDefaultItems>false</EnableDefaultItems>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Bme280.cs" />
|
||||
<Compile Include="Bme680.cs" />
|
||||
<Compile Include="Bme680HeaterProfile.cs" />
|
||||
<Compile Include="Bme680HeaterProfileConfig.cs" />
|
||||
<Compile Include="Bme680Mask.cs" />
|
||||
<Compile Include="Bmp280.cs" />
|
||||
<Compile Include="Bmx280Base.cs" />
|
||||
<Compile Include="Bmxx80Base.cs" />
|
||||
<Compile Include="CalibrationData\Bme680CalibrationData.cs" />
|
||||
<Compile Include="CalibrationData\Bme280CalibrationData.cs" />
|
||||
<Compile Include="CalibrationData\Bmp280CalibrationData.cs" />
|
||||
<Compile Include="CalibrationData\Bmxx80CalibrationData.cs" />
|
||||
<Compile Include="DeviceStatus.cs" />
|
||||
<Compile Include="FilteringMode\Bme680FilteringMode.cs" />
|
||||
<Compile Include="FilteringMode\Bmx280FilteringMode.cs" />
|
||||
<Compile Include="PowerMode\Bme680PowerMode.cs" />
|
||||
<Compile Include="PowerMode\Bmx280PowerMode.cs" />
|
||||
<Compile Include="Register\Bme680Register.cs" />
|
||||
<Compile Include="Register\Bmx280Register.cs" />
|
||||
<Compile Include="Register\Bmxx80Register.cs" />
|
||||
<Compile Include="Register\Bme280Register.cs" />
|
||||
<Compile Include="Sampling.cs" />
|
||||
<Compile Include="StandbyTime.cs" />
|
||||
<ProjectReference Include="$(MainLibraryPath)System.Device.Gpio.csproj">
|
||||
<AdditionalProperties>$(AdditionalProperties);RuntimeIdentifier=linux</AdditionalProperties>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Units\Units.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="README.md" />
|
||||
</ItemGroup>
|
||||
</Project>
|
345
NucuCar.Sensors/Environment/Bmxx80/Bmxx80Base.cs
Normal file
345
NucuCar.Sensors/Environment/Bmxx80/Bmxx80Base.cs
Normal file
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the core functionality of the Bmxx80 family.
|
||||
/// </summary>
|
||||
public abstract class Bmxx80Base : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Calibration data for the sensor.
|
||||
/// </summary>
|
||||
internal Bmxx80CalibrationData _calibrationData;
|
||||
|
||||
/// <summary>
|
||||
/// I2C device used to communicate with the device.
|
||||
/// </summary>
|
||||
protected I2cDevice _i2cDevice;
|
||||
|
||||
/// <summary>
|
||||
/// Chosen communication protocol.
|
||||
/// </summary>
|
||||
protected CommunicationProtocol _communicationProtocol;
|
||||
|
||||
/// <summary>
|
||||
/// The control register of the sensor.
|
||||
/// </summary>
|
||||
protected byte _controlRegister;
|
||||
|
||||
/// <summary>
|
||||
/// Bmxx80 communication protocol.
|
||||
/// </summary>
|
||||
public enum CommunicationProtocol
|
||||
{
|
||||
/// <summary>
|
||||
/// I²C communication protocol.
|
||||
/// </summary>
|
||||
I2c
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The variable TemperatureFine carries a fine resolution temperature value over to the
|
||||
/// pressure compensation formula and could be implemented as a global variable.
|
||||
/// </summary>
|
||||
protected int TemperatureFine
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The temperature calibration factor.
|
||||
/// </summary>
|
||||
protected virtual int TempCalibrationFactor => 1;
|
||||
|
||||
private Sampling _temperatureSampling;
|
||||
private Sampling _pressureSampling;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Bmxx80Base"/> class.
|
||||
/// </summary>
|
||||
/// <param name="deviceId">The ID of the device.</param>
|
||||
/// <param name="i2cDevice">The <see cref="I2cDevice"/> to create with.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the given <see cref="I2cDevice"/> is null.</exception>
|
||||
/// <exception cref="IOException">Thrown when the device cannot be found on the bus.</exception>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pressure sampling.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <see cref="Sampling"/> is set to an undefined mode.</exception>
|
||||
public Sampling PressureSampling
|
||||
{
|
||||
get => _pressureSampling;
|
||||
set
|
||||
{
|
||||
byte status = Read8BitsFromRegister(_controlRegister);
|
||||
status = (byte)(status & 0b1110_0011);
|
||||
status = (byte)(status | (byte)value << 2);
|
||||
|
||||
Span<byte> command = stackalloc[]
|
||||
{
|
||||
_controlRegister, status
|
||||
};
|
||||
_i2cDevice.Write(command);
|
||||
_pressureSampling = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the temperature sampling.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <see cref="Sampling"/> is set to an undefined mode.</exception>
|
||||
public Sampling TemperatureSampling
|
||||
{
|
||||
get => _temperatureSampling;
|
||||
set
|
||||
{
|
||||
byte status = Read8BitsFromRegister(_controlRegister);
|
||||
status = (byte)(status & 0b0001_1111);
|
||||
status = (byte)(status | (byte)value << 5);
|
||||
|
||||
Span<byte> command = stackalloc[]
|
||||
{
|
||||
_controlRegister, status
|
||||
};
|
||||
_i2cDevice.Write(command);
|
||||
_temperatureSampling = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When called, the device is reset using the complete power-on-reset procedure.
|
||||
/// The device will reset to the default configuration.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
const byte resetCommand = 0xB6;
|
||||
Span<byte> command = stackalloc[]
|
||||
{
|
||||
(byte)Bmxx80Register.RESET, resetCommand
|
||||
};
|
||||
_i2cDevice.Write(command);
|
||||
|
||||
SetDefaultConfiguration();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the temperature. A return value indicates whether the reading succeeded.
|
||||
/// </summary>
|
||||
/// <param name="temperature">
|
||||
/// Contains the measured temperature if the <see cref="TemperatureSampling"/> was not set to <see cref="Sampling.Skipped"/>.
|
||||
/// Contains <see cref="double.NaN"/> otherwise.
|
||||
/// </param>
|
||||
/// <returns><code>true</code> if measurement was not skipped, otherwise <code>false</code>.</returns>
|
||||
public abstract bool TryReadTemperature(out Temperature temperature);
|
||||
|
||||
/// <summary>
|
||||
/// Reads the pressure. A return value indicates whether the reading succeeded.
|
||||
/// </summary>
|
||||
/// <param name="pressure">
|
||||
/// Contains the measured pressure if the <see cref="PressureSampling"/> was not set to <see cref="Sampling.Skipped"/>.
|
||||
/// Contains <see cref="double.NaN"/> otherwise.
|
||||
/// </param>
|
||||
/// <returns><code>true</code> if measurement was not skipped, otherwise <code>false</code>.</returns>
|
||||
public abstract bool TryReadPressure(out Pressure pressure);
|
||||
|
||||
/// <summary>
|
||||
/// Compensates the temperature.
|
||||
/// </summary>
|
||||
/// <param name="adcTemperature">The temperature value read from the device.</param>
|
||||
/// <returns>The <see cref="Temperature"/>.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an 8 bit value from a register.
|
||||
/// </summary>
|
||||
/// <param name="register">Register to read from.</param>
|
||||
/// <returns>Value from register.</returns>
|
||||
protected internal byte Read8BitsFromRegister(byte register)
|
||||
{
|
||||
if (_communicationProtocol == CommunicationProtocol.I2c)
|
||||
{
|
||||
_i2cDevice.WriteByte(register);
|
||||
byte value = _i2cDevice.ReadByte();
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 16 bit value over I2C.
|
||||
/// </summary>
|
||||
/// <param name="register">Register to read from.</param>
|
||||
/// <param name="endianness">Interpretation of the bytes (big or little endian).</param>
|
||||
/// <returns>Value from register.</returns>
|
||||
protected internal ushort Read16BitsFromRegister(byte register, Endianness endianness = Endianness.LittleEndian)
|
||||
{
|
||||
Span<byte> 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)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 24 bit value over I2C.
|
||||
/// </summary>
|
||||
/// <param name="register">Register to read from.</param>
|
||||
/// <param name="endianness">Interpretation of the bytes (big or little endian).</param>
|
||||
/// <returns>Value from register.</returns>
|
||||
protected internal uint Read24BitsFromRegister(byte register, Endianness endianness = Endianness.LittleEndian)
|
||||
{
|
||||
Span<byte> 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)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts byte to <see cref="Sampling"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to convert.</param>
|
||||
/// <returns><see cref="Sampling"/></returns>
|
||||
protected Sampling ByteToSampling(byte value)
|
||||
{
|
||||
// Values >=5 equals UltraHighResolution.
|
||||
if (value >= 5)
|
||||
{
|
||||
return Sampling.UltraHighResolution;
|
||||
}
|
||||
|
||||
return (Sampling)value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the default configuration for the sensor.
|
||||
/// </summary>
|
||||
protected virtual void SetDefaultConfiguration()
|
||||
{
|
||||
PressureSampling = Sampling.UltraLowPower;
|
||||
TemperatureSampling = Sampling.UltraLowPower;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the Endianness of a device.
|
||||
/// </summary>
|
||||
protected internal enum Endianness
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates little endian.
|
||||
/// </summary>
|
||||
LittleEndian,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates big endian.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanup.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the Bmxx80 and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">True to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
_i2cDevice?.Dispose();
|
||||
_i2cDevice = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Calibration data for the BME280.
|
||||
/// </summary>
|
||||
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; }
|
||||
|
||||
/// <summary>
|
||||
/// Read coefficient data from device.
|
||||
/// </summary>
|
||||
/// <param name="bmxx80Base">The <see cref="Bmxx80Base"/> to read coefficient data from.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Calibration data for the <see cref="Bme680"/>.
|
||||
/// </summary>
|
||||
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; }
|
||||
|
||||
/// <summary>
|
||||
/// Read coefficient data from device.
|
||||
/// </summary>
|
||||
/// <param name="bmxx80Base">The <see cref="Bmxx80Base"/> to read coefficient data from.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Calibration data for the BMP280.
|
||||
/// </summary>
|
||||
internal class Bmp280CalibrationData : Bmxx80CalibrationData
|
||||
{
|
||||
/// <summary>
|
||||
/// Read coefficient data from device.
|
||||
/// </summary>
|
||||
/// <param name="bmxx80Base">The <see cref="Bmxx80Base"/> to read coefficient data from.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Calibration data for the Bmxx80 family.
|
||||
/// </summary>
|
||||
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; }
|
||||
|
||||
/// <summary>
|
||||
/// Read coefficient data from device.
|
||||
/// </summary>
|
||||
/// <param name="bmxx80Base">The <see cref="Bmxx80Base"/> to read coefficient data from.</param>
|
||||
protected internal abstract void ReadFromDevice(Bmxx80Base bmxx80Base);
|
||||
}
|
||||
}
|
23
NucuCar.Sensors/Environment/Bmxx80/DeviceStatus.cs
Normal file
23
NucuCar.Sensors/Environment/Bmxx80/DeviceStatus.cs
Normal file
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the status of the device.
|
||||
/// </summary>
|
||||
public class DeviceStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// True whenever a conversion is running and False when the results have been transferred to the data registers.
|
||||
/// </summary>
|
||||
public bool Measuring { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public bool ImageUpdating { get; set; }
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// IIR filter coefficient. The higher the coefficient, the slower the sensors
|
||||
/// responds to external inputs.
|
||||
/// </summary>
|
||||
public enum Bme680FilteringMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Filter coefficient of 0.
|
||||
/// </summary>
|
||||
C0 = 0b000,
|
||||
|
||||
/// <summary>
|
||||
/// Filter coefficient of 1.
|
||||
/// </summary>
|
||||
C1 = 0b001,
|
||||
|
||||
/// <summary>
|
||||
/// Filter coefficient of 3.
|
||||
/// </summary>
|
||||
C3 = 0b010,
|
||||
|
||||
/// <summary>
|
||||
/// Filter coefficient of 7.
|
||||
/// </summary>
|
||||
C7 = 0b011,
|
||||
|
||||
/// <summary>
|
||||
/// Filter coefficient of 15.
|
||||
/// </summary>
|
||||
C15 = 0b100,
|
||||
|
||||
/// <summary>
|
||||
/// Filter coefficient of 31.
|
||||
/// </summary>
|
||||
C31 = 0b101,
|
||||
|
||||
/// <summary>
|
||||
/// Filter coefficient of 63.
|
||||
/// </summary>
|
||||
C63 = 0b110,
|
||||
|
||||
/// <summary>
|
||||
/// Filter coefficient of 127.
|
||||
/// </summary>
|
||||
C127 = 0b111
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Bmx280 devices feature an internal IIR filter.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
public enum Bmx280FilteringMode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Filter off.
|
||||
/// </summary>
|
||||
Off = 0b000,
|
||||
|
||||
/// <summary>
|
||||
/// Coefficient x2.
|
||||
/// </summary>
|
||||
X2 = 0b001,
|
||||
|
||||
/// <summary>
|
||||
/// Coefficient x4.
|
||||
/// </summary>
|
||||
X4 = 0b010,
|
||||
|
||||
/// <summary>
|
||||
/// Coefficient x8.
|
||||
/// </summary>
|
||||
X8 = 0b011,
|
||||
|
||||
/// <summary>
|
||||
/// Coefficient x16.
|
||||
/// </summary>
|
||||
X16 = 0b100
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Sensor power mode.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Section 3.1 in the datasheet.
|
||||
/// </remarks>
|
||||
public enum Bme680PowerMode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// No measurements are performed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Minimal power consumption.
|
||||
/// </remarks>
|
||||
Sleep = 0b00,
|
||||
|
||||
/// <summary>
|
||||
/// Single TPHG cycle is performed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sensor automatically returns to sleep mode afterwards.
|
||||
/// Gas sensor heater only operates during gas measurement.
|
||||
/// </remarks>
|
||||
Forced = 0b01
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Sensor power mode.
|
||||
/// </summary>
|
||||
public enum Bmx280PowerMode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// No operations, all registers accessible, lowest power mode, selected after startup.
|
||||
/// </summary>
|
||||
Sleep = 0b00,
|
||||
|
||||
/// <summary>
|
||||
/// Perform one measurement, store results, and return to sleep mode.
|
||||
/// </summary>
|
||||
Forced = 0b10,
|
||||
|
||||
/// <summary>
|
||||
/// Perpetual cycling of measurements and inactive periods.
|
||||
/// This interval is determined by the combination of IIR filter and standby time options.
|
||||
/// </summary>
|
||||
Normal = 0b11
|
||||
}
|
||||
}
|
44
NucuCar.Sensors/Environment/Bmxx80/README.md
Normal file
44
NucuCar.Sensors/Environment/Bmxx80/README.md
Normal file
|
@ -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
|
||||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// General control registers for the BME280.
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// General control registers for the BME680.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See section 5.2 Memory map.
|
||||
/// </remarks>
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Register shared by the Bmx280 family.
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers shared in the Bmxx80 family.
|
||||
/// </summary>
|
||||
internal enum Bmxx80Register : byte
|
||||
{
|
||||
CHIPID = 0xD0,
|
||||
RESET = 0xE0
|
||||
}
|
||||
}
|
45
NucuCar.Sensors/Environment/Bmxx80/Sampling.cs
Normal file
45
NucuCar.Sensors/Environment/Bmxx80/Sampling.cs
Normal file
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Oversampling settings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Maximum of x2 is recommended for temperature.
|
||||
/// </remarks>
|
||||
public enum Sampling : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Skipped (output set to 0x80000).
|
||||
/// </summary>
|
||||
Skipped = 0b000,
|
||||
|
||||
/// <summary>
|
||||
/// Oversampling x1.
|
||||
/// </summary>
|
||||
UltraLowPower = 0b001,
|
||||
|
||||
/// <summary>
|
||||
/// Oversampling x2.
|
||||
/// </summary>
|
||||
LowPower = 0b010,
|
||||
|
||||
/// <summary>
|
||||
/// Oversampling x4.
|
||||
/// </summary>
|
||||
Standard = 0b011,
|
||||
|
||||
/// <summary>
|
||||
/// Oversampling x8.
|
||||
/// </summary>
|
||||
HighResolution = 0b100,
|
||||
|
||||
/// <summary>
|
||||
/// Oversampling x16.
|
||||
/// </summary>
|
||||
UltraHighResolution = 0b101,
|
||||
}
|
||||
}
|
52
NucuCar.Sensors/Environment/Bmxx80/StandbyTime.cs
Normal file
52
NucuCar.Sensors/Environment/Bmxx80/StandbyTime.cs
Normal file
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls the inactive duration in normal mode.
|
||||
/// </summary>
|
||||
public enum StandbyTime : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// 0.5 ms.
|
||||
/// </summary>
|
||||
Ms0_5 = 0b000,
|
||||
|
||||
/// <summary>
|
||||
/// 62.5 ms.
|
||||
/// </summary>
|
||||
Ms62_5 = 0b001,
|
||||
|
||||
/// <summary>
|
||||
/// 125 ms.
|
||||
/// </summary>
|
||||
Ms125 = 0b010,
|
||||
|
||||
/// <summary>
|
||||
/// 250 ms.
|
||||
/// </summary>
|
||||
Ms250 = 0b011,
|
||||
|
||||
/// <summary>
|
||||
/// 500 ms.
|
||||
/// </summary>
|
||||
Ms500 = 0b100,
|
||||
|
||||
/// <summary>
|
||||
/// 1,000 ms.
|
||||
/// </summary>
|
||||
Ms1000 = 0b101,
|
||||
|
||||
/// <summary>
|
||||
/// 10 ms.
|
||||
/// </summary>
|
||||
Ms10 = 0b110,
|
||||
|
||||
/// <summary>
|
||||
/// 20 ms.
|
||||
/// </summary>
|
||||
Ms20 = 0b111,
|
||||
}
|
||||
}
|
115
NucuCar.Sensors/Environment/Bmxx80/Units/Pressure.cs
Normal file
115
NucuCar.Sensors/Environment/Bmxx80/Units/Pressure.cs
Normal file
|
@ -0,0 +1,115 @@
|
|||
namespace NucuCar.Sensors.Environment.Bmxx80.Units
|
||||
{
|
||||
/// <summary>
|
||||
/// Structure representing pressure
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The mean sea-level pressure (MSLP) is the average atmospheric pressure at mean sea level
|
||||
/// </summary>
|
||||
public static Pressure MeanSeaLevel => Pressure.FromPascal(101325);
|
||||
|
||||
/// <summary>
|
||||
/// Pressure in Pa
|
||||
/// </summary>
|
||||
public double Pascal => _pascal;
|
||||
|
||||
/// <summary>
|
||||
/// Pressure in mbar
|
||||
/// </summary>
|
||||
public double Millibar => MillibarRatio * _pascal;
|
||||
|
||||
/// <summary>
|
||||
/// Pressure in kPa
|
||||
/// </summary>
|
||||
public double Kilopascal => KilopascalRatio * _pascal;
|
||||
|
||||
/// <summary>
|
||||
/// Pressure in hPa
|
||||
/// </summary>
|
||||
public double Hectopascal => HectopascalRatio * _pascal;
|
||||
|
||||
/// <summary>
|
||||
/// Pressure in inHg
|
||||
/// </summary>
|
||||
public double InchOfMercury => InchOfMercuryRatio * _pascal;
|
||||
|
||||
/// <summary>
|
||||
/// Pressure in mmHg
|
||||
/// </summary>
|
||||
public double MillimeterOfMercury => MillimeterOfMercuryRatio * _pascal;
|
||||
|
||||
/// <summary>
|
||||
/// Creates Pressure instance from pressure in Pa
|
||||
/// </summary>
|
||||
/// <param name="value">Pressure value in Pa</param>
|
||||
/// <returns>Pressure instance</returns>
|
||||
public static Pressure FromPascal(double value)
|
||||
{
|
||||
return new Pressure(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates Pressure instance from pressure in mbar
|
||||
/// </summary>
|
||||
/// <param name="value">Pressure value in mbar</param>
|
||||
/// <returns>Pressure instance</returns>
|
||||
public static Pressure FromMillibar(double value)
|
||||
{
|
||||
return new Pressure(value / MillibarRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates Pressure instance from pressure in kPa
|
||||
/// </summary>
|
||||
/// <param name="value">Pressure value in kPa</param>
|
||||
/// <returns>Pressure instance</returns>
|
||||
public static Pressure FromKilopascal(double value)
|
||||
{
|
||||
return new Pressure(value / KilopascalRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates Pressure instance from pressure in hPa
|
||||
/// </summary>
|
||||
/// <param name="value">Pressure value in hPa</param>
|
||||
/// <returns>Pressure instance</returns>
|
||||
public static Pressure FromHectopascal(double value)
|
||||
{
|
||||
return new Pressure(value / HectopascalRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates Pressure instance from pressure in inHg
|
||||
/// </summary>
|
||||
/// <param name="value">Pressure value in inHg</param>
|
||||
/// <returns>Pressure instance</returns>
|
||||
public static Pressure FromInchOfMercury(double value)
|
||||
{
|
||||
return new Pressure(value / InchOfMercuryRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates Pressure instance from pressure in mmHg
|
||||
/// </summary>
|
||||
/// <param name="value">Pressure value in mmHg</param>
|
||||
/// <returns>Pressure instance</returns>
|
||||
public static Pressure FromMillimeterOfMercury(double value)
|
||||
{
|
||||
return new Pressure(value / MillimeterOfMercuryRatio);
|
||||
}
|
||||
}
|
||||
}
|
60
NucuCar.Sensors/Environment/Bmxx80/Units/Temperature.cs
Normal file
60
NucuCar.Sensors/Environment/Bmxx80/Units/Temperature.cs
Normal file
|
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Temperature in Celsius
|
||||
/// </summary>
|
||||
public double Celsius => _celsius;
|
||||
|
||||
/// <summary>
|
||||
/// Temperature in Fahrenheit
|
||||
/// </summary>
|
||||
public double Fahrenheit => FahrenheitRatio * _celsius + FahrenheitOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Temperature in Kelvin
|
||||
/// </summary>
|
||||
public double Kelvin => _celsius + KelvinOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Creates Temperature instance from temperature in Celsius
|
||||
/// </summary>
|
||||
/// <param name="value">Temperature value in Celsius</param>
|
||||
/// <returns>Temperature instance</returns>
|
||||
public static Temperature FromCelsius(double value)
|
||||
{
|
||||
return new Temperature(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates Temperature instance from temperature in Fahrenheit
|
||||
/// </summary>
|
||||
/// <param name="value">Temperature value in Fahrenheit</param>
|
||||
/// <returns>Temperature instance</returns>
|
||||
public static Temperature FromFahrenheit(double value)
|
||||
{
|
||||
return new Temperature((value - FahrenheitOffset) / FahrenheitRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates Temperature instance from temperature in Kelvin
|
||||
/// </summary>
|
||||
/// <param name="value">Temperature value in Kelvin</param>
|
||||
/// <returns>Temperature instance</returns>
|
||||
public static Temperature FromKelvin(double value)
|
||||
{
|
||||
return new Temperature(value - KelvinOffset);
|
||||
}
|
||||
}
|
||||
}
|
6
NucuCar.Sensors/Environment/Bmxx80/category.txt
Normal file
6
NucuCar.Sensors/Environment/Bmxx80/category.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
barometer
|
||||
altimeter
|
||||
thermometer
|
||||
voc
|
||||
gas
|
||||
hygrometer
|
Loading…
Reference in a new issue