Indoor air quality (IAQ) refers to the air quality within and around buildings and structures and how it relates to the building occupants physical and psychological health. IAQ is known to affect the health, comfort, and well-being of persons staying in this buildings - poor indoor air quality has been linked to issues like reduced productivity at workplaces and impaired learning in schools.
IAQ can be affected by gases (including carbon dioxide and volatile organic compounds = VOCs), particulates, microbial contaminants (mold, bacteria), or any mass or energy stressor (e.g. temperature and/or relative humidity) that can induce adverse health conditions. There are many different air quality indexes developed and used by various organizations and government agencies out there.
IAQ Index / RatingAn indoor air quality index (IAQI) is a descriptive scale used to show how polluted the air is and is calculated based on several key indicators. It translates the numerical data of these key indicators into a descriptive rating scale, which makes it easier to understand the level of pollution in the air.
ProjectThis project implements a simple proof-of-concept (PoC) / demo for a personal device to determine and monitor the actual air quality for it's user, based on the continuous measurement of
- temperature,
- relative humidity,
- carbon dioxide ( CO²) concentration and
- TVOC concentration.
It refers the IAQ Rating Index recommended by INDOOR AIR QUALITY UK (www.iaquk.org.uk), an independent organization with the aim of 'raising the agenda of indoor air quality within the home and workplace', for the calculation of an IAQ index and rating, to visualize the air quality base on these indicators for the user.
It's based on the nRF5340 DK + Adafruit Display, the CCS811 gas sensor and the humidity sensor BME280.
ComponentsThe Nordic SemiconductornRF5340 DK is a hardware development platform used to design and develop application firmware on the nRF5340 System on Chip (SoC).
The key features of the Development Kit (DK) are:
- nRF5340 SoC
- Support for the following wireless protocols: Bluetooth® Low Energy, NFC, 802.15.4, Thread, Zigbee, ANT™, 2.4 GHz proprietary
- Arduino Rev3 compatibility
- 2.4 GHz and NFC antennas
- SWF RF connector for direct RF measurements
- User-programmable LEDs (4) and buttons (4)
- SEGGER J-Link OB programmer/debugger
- Pins for measuring power consumption
- Drag-and-drop Mass Storage Device (MSD) programming
- 1.7-5.0 V power supply from USB, external Li-Po battery, or CR2032 coin cell battery
For more information, full specification and documentation refer to
- the product page at https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF5340-DK,
- the documentation at https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrf5340_dk%2FUG%2Fdk%2Fintro.html and
- the at nRF53 Series: Developing with nRF Connect SDK Getting Started Guide at https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_gsg_ncs%2FUG%2Fgsg%2Ffirst_test.html.
The Adafruit 2.8" TFT Touch Shield for Arduino is 2.8" TFT display shield for Arduino Uno Rev3 compatible hardware with built in microSD card connection and a capacitive touchscreen controller using I2C.
The ScioSense CCS811 is an ultra-low power digital gas sensor solution which integrates a metal oxide (MOX) gas sensor to detect a wide range of Volatile Organic Compounds (VOCs) for indoor air quality monitoring with a microcontroller unit (MCU), which includes an Analog-to-Digital converter (ADC), and an I²C interface. VOCs are often categorized as pollutants and/or sensory irritants. They can come from a variety of sources such as construction materials (e.g. paint, carpet, glue), machines (e.g. printers, copiers etc.), and even people (breathing and smoking). The sensor estimates carbon dioxide (CO²) levels where the main source of VOCs is human presence.
The Bosch BME280 is precision low-cost sensor from Bosch for measuring humidity with ±3% accuracy, barometric pressure with ±1 hPa absolute accuraccy, and temperature with ±1.0°C accuracy. It can be used for all sorts of indoor environmental sensing in both I²C and SPI.
The software implementation is based the nRF Connect SDK, the Zephyr RTOS and the Light and Versatile Embedded Graphics Library (LVGL).
The nRF Connect SDK by Nordic Semiconductors is a scalable and unified software development kit for building products based on the nRF52, nRF53 and nRF91 Series wireless devices. It integrates the Zephyr RTOS - a small, scalable, real-time operating system (RTOS) optimized for resource-constrained devices - and a wide range of samples (e.g. for using the e.g. for the CCS811 and BME280 sensors), application protocols, protocol stacks, libraries (like LVGL) and hardware drivers (e.g. for the CCS811 and BME280 sensors).
nRF Connect SDK is publicly hosted on GitHub, offers source code management with Git and has free SEGGER Embedded Studio IDE support.
For download, installation and getting started see the documentation and the getting started guide. The installation is quite straightforward, thanks to the availability of nRF Connect for Desktop, a cross-platform tool available for Windows, Linux, and macOS, provides different apps that simplify installing the nRF Connect SDK. On Windows, the Toolchain manager performs most installation steps automatically, on Linux or macOS, the Getting Started Assistant provides step-by-step guidance trough the installation of the components.
LVGL is an open-source graphics library providing easy-to-use graphical elements, visual effects etc. for creating embedded GUIs with low memory footprint. It has been integrated into Zephyr RTOS. The full documentation for the LVGL library can be found here.
A good starting point for getting started with the nRF5320 DK in connection with the Adafruit 2, 8" TFT is the ncs-display-ble-example which combines the LVGL display example with the peripheral_lbs example from the nRF Connect SDK.
For building the example and for building the firmware for this project, it is necessary to apply the patch included in the example as follows:
- Move the nrf5340_display.patch file from this repo into your Zephyr base directory
- From the Unix bash (or Git command line in Windows) run "git apply nrf5340_display.patch"
The hardware setup for this project is quite easy and pretty straightforward:
- The Adafruit 2.8" TFT Touch Shield for Arduino is placed on top and connected to the Arduino compatible connectors of the nRF5340 DK board.
- The CCS811 is connected to the nrRF5340 DK board as follows:SCL to P1.03 (on connector P4),SDA to P1.02 (P4), 3V3 (or VIN) to VDD on connector P1 and GND to GND on connector P1.The WAKE pin needs to be pulled to ground in order to communicate with the sensor.
- The BME280 is connected to the nrRF5340 DK board as follows: SCL to P1.03 (on connector P4),SDA to P1.02 (P4),3V3 (or VIN) to VDD on connector P1 and GND to GND on connector P1.
The only issue is, that the display shield covers and blocks the I²C pin connectors when placed onto the nRF5340 DK board. But this issue can be easily resolved by soldering some additional connectors to the available solder points beside the existing connectors.
The application consists of four functional units:
- Sensor handling & reading
- IAQI calculation
- GUI
- BLE beacon
and three flow blocks:
- Initialization
- Sensor reading
- IQUI calculation
The application flow is as follows:
BEGIN;
block Initialization {
try {
Initialize Bluetooth beacon;
Initialize BME280 sensor;
Initialize CS811 sensor;
} catch (Error) {
goto exit;
}
}
while ( True? ) {
/Determine time for calibration/;
block Sensors Reading {
Read BME280 sensor data;
if ( OK? ) {
Update GUI;
Update CSS811 ENV_DATA register;
}
Read CS811 sensor data;
if ( OK? ) {
Update GUI;
}
}
block IAQ Index & Rating {
if ( Valid sensor data? & Calibration finished? ) {
Calculate IAQI and get rating;
Update GUI with IAQI rating;
Update Bluetooth beacon with IAQI rating;
} else if ( Calibration? ) {
Update GUI with timer;
}
}
Sleep;
}
exit:
END;
Both sensors - the BME280 and the CCS811 - are fully supported by the sensor subsystem of Zephyr RTOS with drivers and examples for using.
The handling for the BME280 is pretty straight forward:
const struct device *bme280 = device_get_binding(BME280_LABEL);
struct sensor_value temp, press, humidity;
int rc = sensor_sample_fetch(bme280);
if (rc == 0) {
sensor_channel_get(bme280, SENSOR_CHAN_AMBIENT_TEMP, &temp);
sensor_channel_get(bme280, SENSOR_CHAN_PRESS, &press);
sensor_channel_get(bme280, SENSOR_CHAN_HUMIDITY, &humidity);
printk("\n[%s]: BME280: temp: %d.%06d; press: %d.%06d; humidity: %d.%06d\n",
now_str(),
temp.val1, temp.val2,
press.val1, press.val2,
humidity.val1, humidity.val2);
}
Since the CCS811 has different measurement modes with measurement latencies varying between 1 s, 10 s or 60 s, a timed fetch can return stale data. In this case the fetch has to be repeated:
int ccs811_sample_fetch(struct device *dev) {
static bool first = true;
static bool ccs811_fw_app_v2 = false;
int rc;
if (first) {
struct ccs811_configver_type cfgver;
rc = ccs811_configver_fetch(dev, &cfgver);
if (rc == 0) {
ccs811_fw_app_v2 = (cfgver.fw_app_version >> 8) > 0x11;
}
first = false;
}
rc = sensor_sample_fetch(dev);
while (rc != 0) {
const struct ccs811_result_type *rp = ccs811_result(dev);
if (ccs811_fw_app_v2 && !(rp->status & CCS811_STATUS_DATA_READY)) {
continue;
}
if (rp->status & CCS811_STATUS_ERROR) {
break;
}
k_sleep(K_MSEC(10));
rc = sensor_sample_fetch(dev);
}
return rc;
}
void main(void) {
const struct device *ccs811 = device_get_binding(CCS811_LABEL);
int rc = ccs811_sample_fetch(ccs811);
if (rc == 0) {
sensor_channel_get(ccs811, SENSOR_CHAN_CO2, &co2);
sensor_channel_get(ccs811, SENSOR_CHAN_VOC, &tvoc);
printk("\n[%s]: CCS811: %u ppm eCO2; %u ppb eTVOC\n",
now_str(),
co2.val1,
tvoc.val1);
}
}
The CCS811 has also an ENV_DATA (Environmental Data) register for temperature and humidity compensation. If an external sensor is available this information can be written to CCS811 so that they will be used to compensate gas readings due to temperature and humidity changes. With the environmental data available from the BME280 readings, it makes sense to update the ENV_DATA register with this values before fetching the samples from the CCS811:
struct sensor_value temp, press, humidity;
...
sensor_channel_get(bme280, SENSOR_CHAN_AMBIENT_TEMP, &temp);
sensor_channel_get(bme280, SENSOR_CHAN_PRESS, &press);
sensor_channel_get(bme280, SENSOR_CHAN_HUMIDITY, &humidity);
...
ccs811_envdata_update(ccs811, &temp, &humidity);
IAQ Index & Rating CalculationThe quality rating is determined based on four key indicators referenced in IAQ Rating Index recommended by INDOOR AIR QUALITY UK (www.iaquk.org.uk):
- Temperature,
- Relative Humidity,
- Carbone Dioxide (CO²) and
- TVOC
derived from the according sensor readings.
The IAQ Rating Index defines five categories (Excellent, Good, Fair, Poor and Inadequate) for each indicator, represented by a number of points from 5 to 1.
E.g., for the temperature the category / number of points can be determined as follows:
/*
Temperature (°C)
Excellent: 18 - 21°C
Good: Plus or minus 1°C
Fair: Plus or minus 2°C
Poor: Plus or minus 3°C
Inadequate: Plus or minus 4°C or more
*/
unsigned points_temperature(int temperature) {
unsigned points = 5;
const unsigned excellent_low = 18;
const unsigned excellent_high = 21;
if (temperature < excellent_low) {
points -= excellent_low - temperature > 4 ? 4 : excellent_low - temperature;
} else if (temperature > excellent_high) {
points -= temperature - excellent_high > 4 ? 4 : temperature - excellent_high;
}
return points;
}
The sum of all points from each indicator is then used to determine the overall category for the air quality:
#define IAQ_REGARDED_MEASUREMENTS 4
/* IAQI: The sum of all calculated points for each given indicator / sensor value.
*/
unsigned get_iaq_index(
uint32_t temperature,
uint32_t humidity,
uint32_t eco2,
uint32_t tvoc) {
unsigned points = 0;
points += points_temperature(temperature);
points += points_humidity(humidity);
points += points_co2(eco2);
points += points_tvoc(tvoc);
return points;
}
/* IAQI rating: 5 levels based on the given IAQI.
*/
const char *get_iaq_rating(unsigned iaq_index)
{
if (iaq_index < 2 * IAQ_REGARDED_MEASUREMENTS) {
return "Inadequate";
} else if (iaq_index < 3 * IAQ_REGARDED_MEASUREMENTS) {
return "Poor";
} else if (iaq_index < 4 * IAQ_REGARDED_MEASUREMENTS) {
return "Fair";
} else if (iaq_index < 5 * IAQ_REGARDED_MEASUREMENTS) {
return "Good";
} else {
return "Excellent";
}
}
Since the IAQ Rating Index references the categories for TVOC only for units of mg/m3 and most of the available sensors produce this value only in units of ppb, it would have been necessary to convert measurements in ppb to values in mg/m3. In order to map the TVOC measurement in ppb to mg/m3 a gas mixture would have to be assumed which represents a typical TVOC mixture. Based on this mixture, an average molar mass could be calculated which could be further used to directly convert ppb into mg/m3. As an alternative to this approach, the evaluation of the TVOC measurement in ppb is done by referencing a table published by the German Federal Environmental Agency.
Following the human perception, the German Federal Environmental Agency (Bundesgesundheitsblatt – Gesundheitsforschung Gesundheitsschutz 2007, 50:990–1005, Springer Medizin Verlag 2007. (DOI 10.1007/s00103-007-0290-y) translates TVOC concentration (parts per billion) on a logarithmic scale into five indoor air quality levels (IAQ):
- Excellent: 0 - 0.065 ppm (<= 65 ppb)
- Good: 0.065 - 0.22 ppm (< =220 ppb)
- Moderate: 0.22 - 0.66 ppm (<= 660 ppb)
- Poor: 0.66 - 2.2 ppm (<= 2200 ppb)
- Unhealthy: 2.2 - 5.5 ppm (> 2200 ppb)
This rating is used for the categorization of the TVOC indicator.
GUIThe simple graphical user interface is implemented using the Light and Versatile Embedded Graphics Library (LVGL). It features only text labels and a line meter:
The application starts a simple Bluetooth beacon (Eddystone-URL), updates the advertisement data in each cycle and misuses BT_DATA_NAME_COMPLETE in the the scan response to transmit the IAQ rating:
struct bt_data new_sd[] = {
BT_DATA(
BT_DATA_NAME_COMPLETE,
get_iaq_rating(iaq_index),
strlen(get_iaq_rating(iaq_index))),
BT_DATA(BT_DATA_NAME_SHORTENED, DEVICE_NAME, DEVICE_NAME_LEN),
};
bt_err = bt_le_adv_update_data(ad, ARRAY_SIZE(ad), new_sd, ARRAY_SIZE(new_sd));
An external application could use this feature to scan for multiple beacons of IAQ monitor devices and create a dynamic map for indoor air quality spots in large buildings (e.g. an airport).
CalibrationThe application implements a simple calibration or burn-in timer. For defined time after startup, the IAQ rating is suppressed.
For the CCS811 the manufacturer recommends that this sensor is run for 48 hours when first received it to "burn it in", and then 20 minutes in the desired mode every time it is in use. This is because the sensitivity levels of the sensor will change during early use.
For testing a 'convenient' duration of 20 seconds has been chosen, for 'production' possibly a higher value should be considered.
#define CALIBRATION_TIME_SECONDS 20
Project SourceThe nRF Connect project for this application has 9 files:
boards/nrf5340dk_nrf5340_cpuapp.overlay
src/gui.c
src/gui.h
src/iaq.c
src/iaq.h
src/main.c
CMakeLists.txt
prj.conf
boards/nrf5340dk_nrf5340_cpuapp.overlay
Overlay file for the device tree for this project.
&i2c1 {
status = "okay";
sda-pin = <34>; // P1.02 (34)
scl-pin = <35>; // P1.03 (35)
ccs811: ccs811@5b {
compatible = "ams,ccs811";
reg = <0x5a>;
label = "CCS811";
irq-gpios = <&gpio0 36 GPIO_ACTIVE_LOW>; // P1.04 (36)
wake-gpios = <&gpio0 5 GPIO_ACTIVE_LOW>;
reset-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>;
};
bme280@76 {
compatible = "bosch,bme280";
reg = <0x76>;
label = "BME280";
};
};
src/gui.c
src/gui.h
Implementation of the user interface - see Code section.
src/iaq.c
src/iaq.h
Implementation of the IAQ index & rating calculation - see Code section.
src/main.c
Implementation of sensor handling, Bluetooth beacon start & update and application flow - see Code section.
CMakeLists.txt
CMake configuration for this project:
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.13.1)
set(SHIELD adafruit_2_8_tft_touch_v2)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(lvgl)
FILE(GLOB app_sources src/*.c src/images/*.c src/fonts/*.c)
target_sources(app PRIVATE ${app_sources})
proj.conf
Project configuration file:
CONFIG_HEAP_MEM_POOL_SIZE=16384
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_NEWLIB_LIBC=y
CONFIG_LOG=y
CONFIG_BT=y
CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_DEVICE_NAME="IAQ"
CONFIG_SENSOR=y
CONFIG_BME280=y
CONFIG_CCS811=y
CONFIG_DISPLAY=y
CONFIG_DISPLAY_LOG_LEVEL_ERR=y
CONFIG_LVGL=y
CONFIG_LVGL_USE_THEME_MATERIAL=y
CONFIG_LVGL_USE_LABEL=y
CONFIG_LVGL_USE_LINEMETER=y
CONFIG_LVGL_FONT_MONTSERRAT_14=y
CONFIG_LVGL_FONT_MONTSERRAT_16=y
CONFIG_LVGL_FONT_MONTSERRAT_18=y
CONFIG_LVGL_FONT_MONTSERRAT_22=y
BuildTo build the application with the nRF Connect SDK and SEGGER Embedded Studio follow this steps:
- Connect the nRF5340 DK to the development computer.
- Copy the files (preserving the directory structure) into a directory or clone the repository given Code section.
- Open SEGGER Embedded Studio.
- In menu File select Open nRF Connect SDK Project.
- Select in Projects select the project directory and in Board Name choose nrf5340dk_nrf5340_cpuapp.
- After the project has been opened, select Build and Run in the Build menu.
- Confirm the Project out ofdate dialog.
- After the device has been flashed successfully, connect a terminal emulation to view log / debug messages.
Testing was done with nRF Connect for Mobile, to check & verify the BLE beacon advertisement and scan response data.
A short demo shows the device being tested with nRF Connect for Desktop (Bluetooth Low Energy) and the nRF52840 dongle. The demo shows also a very simple Node-RED flow, running on a Raspberry Pi 3, which scans for the BLE beacon and displays the IAQ rating in a simple dashboard.
Bad indoor air quality was simulated by putting a cotton swab soaked with alcohol-based hand sanitizer near the CCS811 sensor.
To DoWell, it's a PoC, so there is obviously a lot of room for improvement...
- Smaller form factor...
- Encasement...
- Power consumption...
- More sophisticated BT communication
- Mobile app (e.g. for logging/recording)...
- ...
- ...
- ...
Comments