Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SGP40 sensor driver #345

Merged
merged 6 commits into from
Jul 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Meadow.Sdk/1.1.0">
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageIcon>icon.png</PackageIcon>
<Authors>Wilderness Labs, Inc</Authors>
<TargetFramework>netstandard2.1</TargetFramework>
<OutputType>Library</OutputType>
<AssemblyName>Sgp40</AssemblyName>
<Company>Wilderness Labs, Inc</Company>
<PackageProjectUrl>http://developer.wildernesslabs.co/Meadow/Meadow.Foundation/</PackageProjectUrl>
<PackageId>Meadow.Foundation.Sensors.Atmospheric.Sgp40</PackageId>
<RepositoryUrl>https://github.com/WildernessLabs/Meadow.Foundation</RepositoryUrl>
<PackageTags>Meadow.Foundation, Atmospheric, SGP40, Sensiron</PackageTags>
<Version>0.1.44</Version>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Description>SGP40 VOC sensor driver</Description>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<LangVersion>8.0</LangVersion>
<DefineConstants>TRACE;JETSON</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\..\icon.png" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Meadow.Foundation" Version="0.33.1" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;

namespace Meadow.Foundation.Sensors.Atmospheric
{
public partial class Sgp40
{
/// <summary>
/// Valid addresses for the sensor
/// </summary>
public enum Address : byte
{
/// <summary>
/// Bus address 0x59
/// </summary>
Address_0x59 = 0x59,
/// <summary>
/// Bus address 0x59
/// </summary>
Default = Address_0x59
}

private static byte[] sgp40_measure_raw_signal = { 0x26, 0x0f };
private static byte[] sgp40_measure_raw_signal_uncompensated = { 0x26, 0x0F, 0x80, 0x00, 0xA2, 0x66, 0x66, 0x93 };
private static byte[] sgp40_execute_self_test = { 0x28, 0x0e };
private static byte[] sgp4x_turn_heater_off = { 0x36, 0x15 };
private static byte[] sgp4x_get_serial_number = { 0x36, 0x82 };

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Meadow.Hardware;
using Meadow.Peripherals.Sensors;
using Meadow.Units;
using HU = Meadow.Units.RelativeHumidity.UnitType;
using TU = Meadow.Units.Temperature.UnitType;

namespace Meadow.Foundation.Sensors.Atmospheric
{
/// <summary>
/// Provides access to the Sensiron SGP40 VOC sensor
/// </summary>
public partial class Sgp40 :
ByteCommsSensorBase<int>
{
/// <summary>
/// </summary>
public event EventHandler<ChangeResult<int>> VocIndexUpdated = delegate { };

/// <summary>
/// The VOC Index, from the last reading.
/// </summary>
public int VocIndex => Conditions;

/// <summary>
/// Serial number of the device.
/// </summary>
public ulong SerialNumber { get; private set; }

private byte[]? _compensationData = null;

/// <summary>
/// Creates a new SGP40 VOC sensor.
/// </summary>
/// <param name="address">Sensor address (default to 0x40).</param>
/// <param name="i2cBus">I2CBus.</param>
public Sgp40(II2cBus i2cBus, byte address = (byte)Address.Default)
: base(i2cBus, address, 9, 8)
{
Initialize();
}

protected void Initialize()
{
// write buffer for initialization commands only can be two bytes.
Span<byte> tx = WriteBuffer.Span[0..2];

Peripheral.Write(sgp4x_get_serial_number);

Thread.Sleep(1); // per the data sheet

Peripheral.Read(ReadBuffer.Span[0..9]);

var bytes = ReadBuffer.ToArray();

SerialNumber = (ulong)(bytes[0] << 40 | bytes[1] << 32 | bytes[3] << 24 | bytes[4] << 16 | bytes[6] << 8 | bytes[7] << 0);
}

/// <summary>
/// This command triggers the built-in self-test checking for integrity of both hotplate and MOX material
/// </summary>
/// <returns>true on sucessful test, otherwise false</returns>
public bool RunSelfTest()
{
Peripheral.Write(sgp40_execute_self_test);

Thread.Sleep(325); // test requires 320ms to complete

Peripheral.Read(ReadBuffer.Span[0..3]);

return ReadBuffer.Span[0..1][0] == 0xd4;
}

protected async override Task<int> ReadSensor()
{
return await Task.Run(() =>
{
if(_compensationData != null)
{
Peripheral.Write(_compensationData);
}
else
{
Peripheral.Write(sgp40_measure_raw_signal_uncompensated);
}

Thread.Sleep(30); // per the data sheet

Peripheral.Read(ReadBuffer.Span[0..3]);

var data = ReadBuffer.Span[0..3].ToArray();

return data[0] << 8 | data[1];
});
}

/// <summary>
/// Inheritance-safe way to raise events and notify observers.
/// </summary>
/// <param name="changeResult"></param>
protected override void RaiseEventsAndNotify(IChangeResult<int> changeResult)
{
VocIndexUpdated?.Invoke(this, new ChangeResult<int>(VocIndex, changeResult.Old));

base.RaiseEventsAndNotify(changeResult);
}

/// <summary>
/// This command turns the hotplate off and stops the measurement. Subsequently, the sensor enters idle mode.
/// </summary>
public void TurnHeaterOff()
{
Peripheral.Write(sgp4x_turn_heater_off);
}

public void SetCompensationData(RelativeHumidity humidity, Meadow.Units.Temperature temperature)
{
_compensationData = new byte[8];

Array.Copy(sgp40_measure_raw_signal, 0, _compensationData, 0, 2);

var rh = BitConverter.GetBytes(System.Net.IPAddress.HostToNetworkOrder((ushort)(humidity.Percent * 65535 / 100)));
_compensationData[2] = rh[0];
_compensationData[3] = rh[1];
_compensationData[4] = Crc(rh);

var t = BitConverter.GetBytes(System.Net.IPAddress.HostToNetworkOrder((ushort)(temperature.Celsius * 65535 / 175)));
_compensationData[5] = t[0];
_compensationData[6] = t[1];
_compensationData[7] = Crc(t);

}

public void ClearCompensationData()
{
_compensationData = null;
}

private byte Crc(byte[] data)
{
if (data.Length != 2) throw new ArgumentException();

byte crc = 0xFF;
for (int i = 0; i < 2; i++)
{
crc ^= data[i];
for (byte bit = 8; bit > 0; --bit)
{
if ((crc & 0x80) != 0)
{
crc = (byte)((crc << 1) ^ 0x31);
}
else
{
crc = (byte)(crc << 1);
}
}
}
return crc;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Meadow;
using Meadow.Devices;
using Meadow.Foundation.Sensors.Atmospheric;
using System;
using System.Threading.Tasks;

namespace BasicSensors.Atmospheric.SI7021_Sample
{
public class MeadowApp : App<F7FeatherV1>
{
//<!=SNIP=>

Sgp40 sensor;

public MeadowApp()
{
}

public override Task Initialize()
{
Resolver.Log.Info("Initializing...");

sensor = new Sgp40(Device.CreateI2cBus());

Resolver.Log.Info($"Sensor SN: {sensor.SerialNumber:x6}");

if (sensor.RunSelfTest())
{
Resolver.Log.Info("Self test successful");
}
else
{
Resolver.Log.Warn("Self test failed");
}

var consumer = Sgp40.CreateObserver(
handler: result =>
{
Resolver.Log.Info($"Observer: VOC changed by threshold; new index: {result.New}");
},
filter: result =>
{
//c# 8 pattern match syntax. checks for !null and assigns var.
return Math.Abs(result.New - result.Old ?? 0) > 10;
}
);
sensor.Subscribe(consumer);

sensor.Updated += (sender, result) =>
{
Resolver.Log.Info($" VOC: {result.New}");
};

return base.Initialize();
}

public override async Task Run()
{
await ReadConditions();

sensor.StartUpdating(TimeSpan.FromSeconds(1));
}

async Task ReadConditions()
{
var result = await sensor.Read();
Resolver.Log.Info("Initial Readings:");
Resolver.Log.Info($" Temperature: {result}");
}

//<!=SNOP=>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Meadow;
using System.Threading;

namespace BasicSensors.Atmospheric.SI7021_Sample
{
class Program
{
static IApp? app;

public static void Main(string[] args)
{
app = new MeadowApp();

Thread.Sleep(Timeout.Infinite);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Meadow.Sdk/1.1.0">
<PropertyGroup>
<RepositoryUrl>https://github.com/WildernessLabs/Meadow.Foundation</RepositoryUrl>
<Company>Wilderness Labs, Inc</Company>
<Authors>Wilderness Labs, Inc</Authors>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<TargetFramework>netstandard2.1</TargetFramework>
<OutputType>Exe</OutputType>
<AssemblyName>App</AssemblyName>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\..\Meadow.Core\source\Meadow.F7\Meadow.F7.csproj" />
<ProjectReference Include="..\..\Driver\Sensors.Atmospheric.Sgp40.csproj" />
</ItemGroup>
</Project>
24 changes: 24 additions & 0 deletions Source/Meadow.Foundation.sln
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{494A
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bh1900Nux_Sample", "Meadow.Foundation.Peripherals\Sensors.Atmospheric.Bh1900Nux\Samples\Bh1900Nux_Sample\Bh1900Nux_Sample.csproj", "{43DA872D-F7E2-4CD6-A5F9-3E50FFBE73B3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sensors.Atmospheric.Sgp40", "Sensors.Atmospheric.Sgp40", "{B98533B9-CA48-4F0D-961B-C95BD5158391}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{F2ECD777-3B3B-4EF1-B68C-E31AAADBF2B5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sensors.Atmospheric.Sgp40", "Meadow.Foundation.Peripherals\Sensors.Atmospheric.Sgp40\Driver\Sensors.Atmospheric.Sgp40.csproj", "{71EBC24D-5B32-4E76-ADF3-DE0017946108}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sgp40_Sample", "Meadow.Foundation.Peripherals\Sensors.Atmospheric.Sgp40\Samples\Sgp40_Sample\Sgp40_Sample.csproj", "{1ACC6BA0-8108-4BFA-9A75-9063B5E07493}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -2446,6 +2454,18 @@ Global
{43DA872D-F7E2-4CD6-A5F9-3E50FFBE73B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{43DA872D-F7E2-4CD6-A5F9-3E50FFBE73B3}.Release|Any CPU.Build.0 = Release|Any CPU
{43DA872D-F7E2-4CD6-A5F9-3E50FFBE73B3}.Release|Any CPU.Deploy.0 = Release|Any CPU
{71EBC24D-5B32-4E76-ADF3-DE0017946108}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{71EBC24D-5B32-4E76-ADF3-DE0017946108}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71EBC24D-5B32-4E76-ADF3-DE0017946108}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{71EBC24D-5B32-4E76-ADF3-DE0017946108}.Release|Any CPU.ActiveCfg = Release|Any CPU
{71EBC24D-5B32-4E76-ADF3-DE0017946108}.Release|Any CPU.Build.0 = Release|Any CPU
{71EBC24D-5B32-4E76-ADF3-DE0017946108}.Release|Any CPU.Deploy.0 = Release|Any CPU
{1ACC6BA0-8108-4BFA-9A75-9063B5E07493}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1ACC6BA0-8108-4BFA-9A75-9063B5E07493}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1ACC6BA0-8108-4BFA-9A75-9063B5E07493}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{1ACC6BA0-8108-4BFA-9A75-9063B5E07493}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1ACC6BA0-8108-4BFA-9A75-9063B5E07493}.Release|Any CPU.Build.0 = Release|Any CPU
{1ACC6BA0-8108-4BFA-9A75-9063B5E07493}.Release|Any CPU.Deploy.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -2966,6 +2986,10 @@ Global
{71FE0FFA-F897-4067-8687-A033B5D0BB5D} = {6E207804-A991-42DD-BE14-AB8E18940162}
{494AAADA-7CF7-44A4-AFB2-4AEC664151E3} = {6E207804-A991-42DD-BE14-AB8E18940162}
{43DA872D-F7E2-4CD6-A5F9-3E50FFBE73B3} = {494AAADA-7CF7-44A4-AFB2-4AEC664151E3}
{B98533B9-CA48-4F0D-961B-C95BD5158391} = {64623FCA-6086-4F0A-A59D-2BF372EA38AA}
{F2ECD777-3B3B-4EF1-B68C-E31AAADBF2B5} = {B98533B9-CA48-4F0D-961B-C95BD5158391}
{71EBC24D-5B32-4E76-ADF3-DE0017946108} = {B98533B9-CA48-4F0D-961B-C95BD5158391}
{1ACC6BA0-8108-4BFA-9A75-9063B5E07493} = {F2ECD777-3B3B-4EF1-B68C-E31AAADBF2B5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AF7CA16F-8C38-4546-87A2-5DAAF58A1520}
Expand Down