Building an IAQ device with Meadow
02/17/2024
6 minutes
Indoor air quality (IAQ) is increasingly important for our health, productivity and well-being. The industrialized world suffers from air pollution, and buildings are no exceptions. A room's air might be filled with pollutants such as bacteria, carbon monoxide or carbon dioxide.
Meadow is a microcontroller that can run C# code. It is designed by Wilderness Labs. Meadow can be attached to a Project Lab, which is a functional IoT prototyping platform. It has numerous sensors built-in, buttons, a screen and a wide variety of connectors.
One particular sensor on the Project Lab board is the BME688 manufactured by Bosch. This sensor is interesting as besides the regular temperature, pressure, humidity, it can measure gas resistance. It is a metal oxide-based sensor that can detect gases by adsorption on its sensitive layer. It also comes with a software library, which can distinguish the different gases using AI. It can detect Volatile Organic Compounds (VOCs), Volatile Sulfur Compounds (VSCs), carbon monoxide and carbon dioxide.
The sensor also comes with an AI feature and a comprehensive SDK that enable developers to design and customize its behavior to their use-case. One such example I found online is the Cheese vs. Meat detector.
In this post I will explore using the BME688 sensor with the ProjectLab Meadow device, using the v1.3 Meadow SDK. I have blogged previously on getting started with a meadow, but the official guide is very detailed as well.
BME688 With Meadow
In this post I am using the BME688 sensor with its data sheet. The BME680 sensor, which is also part of this sensor family, has a different data sheet, with some different registers to be addressed for the same task.
Creating a Sensor
At the time of writing the implementation of the BME688 Meadow driver does not handle gas readings correctly. To create a new driver, one can take the current implementation and customize it. There is an open PR at the time of writing this post, so hopefully this gets fixed soon. Until then follow the guide below.
I took the BME68x file as my base class and customized in the following way:
Replace
CalculateGasResistance
method with the following, that uses the calculation described in the data sheet linked above:
Resistance CalculateGasResistance_BME688(ushort adcGasRes, byte gasRange) { if (calibration == null) throw new NullReferenceException("Calibration must be defined"); var var1 = ((uint)262144) >> gasRange; var var2 = ((int)adcGasRes) - 512; var2 *= 3; var2 = 4096 + var2; var gasResistance = 1_000_000.0 * var1 / var2; return new Resistance(gasResistance, Resistance.UnitType.Ohms); }
Update
GasConversionIsEnabled
property to set the 5th bit in the control gas register:
public bool GasConversionIsEnabled { get => gasConversionIsEnabled; set { var gasConversion = busComms.ReadRegister((byte)Registers.CTRL_GAS_1); byte mask = 0x10; gasConversion = (byte)((gasConversion & (byte)~mask) | Convert.ToByte(value) << 5); busComms.WriteRegister((byte)Registers.CTRL_GAS_1, gasConversion); gasConversionIsEnabled = value; } }
In the setter of heat profile property, make sure the right bits are masked:
profile = (byte)((profile & 0xF0) | (byte)value);
Finally, update the register values of gas resistance and gas range.
GAS_RES = 0x2C, GAS_RANGE = 0x2D,
Daily Usage
The meadow sample gives a good idea on how to use the sensor. For more advanced usage, one can follow the suggestions of the datasheet. After configuring oversample modes for pressure/humidity/temperature, configure up to 10 heating profiles:
for (int i = 0; i < profiles.Length; i++) _sensor.ConfigureHeatingProfile(profiles[i], new Temperature(200 + 20 * i), TimeSpan.FromMilliseconds(100), new Temperature(24));
A heating profile sets a preconfigured temperature for the metal oxide plate and sets a preheat interval. Why would you do this? Gases react differently with the metal oxide plate when it is heated to different temperatures. This allows us to deduce the substances of the air.
The datasheet suggests a preheating phase of 20-30 ms. Configure an ambient temperature, as measurements will be compensated to that. I notice the gas resistance readings are highly impacted by setting a correct ambient temperature.
Next, iterate over the heating profiles, enable gas conversion and set the desired profile before reading the values of the sensor.
foreach (var profile in profiles) { _sensor.GasConversionIsEnabled = true; _sensor.HeaterProfile = profile; var measurement = await _sensor.Read(); // Process measurement }
The BME688 suggests waiting for a measurement duration. This code is omitted here as it is already implemented by the BME68x.cs base class that was customized in the previous section.
Key Findinds
The ambient temperature greatly impacts the measurement.
Run the sensor for at least 2 days before the readings stabilize.
Use 200C to 400C range for preheating the sensitive layer.
Let the sensor adjust right after initial readings: it might require 5-10 minutes of continuous operation before the measurements stabilize.
Repeat gas resistance measurement every 20-30 seconds when running in continuous operation mode.
BME688 also allows a parallel mode (not implemented in the BME68x base class), which mode allows a faster iteration of profiles.
Typical values reported by the sensor fall within the 70000-1000000 Ohm range. When the ambient temperature is skewed, values larger to 1 MOhm is often returned.
Next Steps
The meadow platform at the time of writing does not yet enable to load native libraries. The BME688 sensor offers a native library to perform the AI calculation, loading this library at runtime is not yet possible today on a Meadow device.
In following posts I will be discovering the collection the data on the device, so that I could build a custom AI feature using ML.NET. To achieve this, I will need to read and write data to a file. Reading the file from a PC will be available with a coming release of Meadow CLI.