Sensirion Environmental Sensor Shield - Review

Table of contents

RoadTest: Sensirion Environmental Sensor Shield - Industrial Sensing

Author: pullrich

Creation date:

Evaluation Type: Semiconductors

Did you receive all parts the manufacturer stated would be included in the package?: True

What other parts do you consider comparable to this product?:

What were the biggest problems encountered?: I had no problems with the sensor board and the driver. I lost one week as I had no success with the SD card library that came with the Arduino IDE - I had to use an older library called SDFAT-

Detailed Review:

Sensirion Environmental Sensor Shield



During unboxing I found nothing more than expected: a small paper box containing the Sensirion environmental sensor board stored in an antistatic envelope.

There is no user manual or anything else included. Instead of any paper there is a link printed on the silk screen that leads to the board information:


They also printed a QR field on the silk screen that leads to the same link - for fast browsing on a smartphone.

The link directs you to the Environmental Sensor Shield web page:


So let us have a look at the PCB itself first:



The green environmental sensor board itself is designed as Arduino shield - suitable for the connection to the Arduino Uno boards for example - but also other hardware platforms can be used.

The left picture shows the bottom of the PCB containing the pin rows to the Arduino board and also a 4 pin I2C bus connector. This I2C bus connector is intended for users that don't want to control the shield with an Arduino board but with another microcontroller board that can be connected here. Beside the I2C bus signal SDA and SCL there is the GND refernce pin and also a VDD pin that can be powered with 3.3V or 5V.


Reengineering of the block diagram


The right picture shows the the top side of the PCB. The temperature and humidity sensor SHTC1 sits in a small pcb area that is thermically isolated from the Arduino shield by a quite wide milled slot so that the temperature influence due to the heat of components on the Arduino board is minimized. This may disturb the integration of the shield in ready made Arduino housings but improves the temperature performance. The "multipixel gas sensor" SGP30 sits in the area between the two Arduino pin rows. Both sensors are well marked with their part name and also the I2C addresses are printed on the silk screen (0x70 fo the SHTC1 and 0x58 for the SGP30 chip). There are also 3 LEDs on the PCB that are connected to the digital i/o pins #9, #10 and #11. The rest of the components seem to be the necessary level shifters as the sensors are 1.8V type while the Arduino board work with 3.3V or 5V. As there are no official schematics available on the internet (status mid of July 2018) I reengineered the board and made a block diagram showing all the necessary blocks and connections.



About one week later the official schematics appeared on the internet and can be loaded from ess-hardware-docs repository  at Johannes Winkelmann's GitHub page. Thank you very much for organizing these schematics!

It showed that my block diagram was about 90% correct - only small differences which supply voltage powers the different circuit blocks.


Arduino IDE setup and driver installation


I limit my information about the installation to the list of used versions and libraries as I am sure that most of you know how to install the libraries. Otherwise there are lots of tutorials on Youtube or other platforms that can help you here.


Arduino IDE 1.8.5 (I loaded the Windows version as I am wokring on a Windows PC)

Arduino library for Sensirion Environmental Sensor Shield


Testing some software examples installed with the ESS library


To make sure that the sensor board is working together with my Arduino Uno board and as a software base for my further tests I tried some of the installed software examples. I had a look at the output in the Arduino serial window but also in the plotter window that can be used for displaying delivered numbers as multi color graphs (like oscillograms).

There is the standard "ess" example with simple formatted measurement value output and error checking and the "ess-minimal" version without error checking. It is not recommended to use this minimal version - it is meant purely to explain the API.

Together with a working sensor board you will not notice any difference in the plotter window but minor formatting differences in the serial window.


"ess" example output: temperature(blue) / humidity(red) / VOC(green) / CO2eq(orange)

The peak in the orange curve has been forced by "bad" breath from about 30cm distance.


"ess-minimal" example output: temperature(blue) / humidity(red) / VOC(green) / CO2eq(orange)

The peaks in the orange curve has been forced by "bad" breaths from about 30cm and 50cm distance.


As you can see the plotter windows is a nice and simple method for first tests to see if the sensor result changes seem to be realistic or maybe useless as the output curve may look like a randomizes signal like white noise. But you can also see that all 4 curves work with the same scale. As our orange CO2 signal delivers the highest numbers the autoscaling function of the plotting window adjusts to the maximum received value for CO2. So the other three curves that deliver values in a much lower number range can not be drawn in a useful scale.


Building a sensor data logging setup


As I need a setup that can log sensor data over hours but without a connected PC I extended the standard Arduino Uno / Sensirion ESS setup with a data logger board (for example from Adafruit).

For necessary inputs I added a rotary encoder with push button and also added a monochrome character liquid crystal display that is controlled via a small I²C controller.


Shopping list


Necessary wiring (shown with Fritzing)

The Fritzing wiring plan shows the 3 Arduino modules separated. For proper operation it is necessary to mount all modules: the Arduino Uno is the bottom module, the data logger shield goes next and the Sensirion ESS board is on top.

If the data logger shield has no stacking headers mounted you have to solder in these stack headers before assembly.

I soldered header in the prototyping area of the data logger board for the connection of the Keyes-040 Encoder board but also the I2C-interface cable going to the LCD module. As you can see on my photos my connector is much bigger. I already prepared my data logger board for the connection of a small 1,8 inch TFT module that I maybe want to connect later. The wiring of this connector is not shown as it is not yet supported in my software.


My modified data logger shield

Board stackup



Complete setup



The control software for the Sensirion data logger is based on the Sensirion ESS library written by Johannes Winkelmann. All sensor values are read once a second, written on the LCD, output to the serial interface and written to a log file on the SD card.

As the Sensor Data Logger had to be used also during night near my sons bed I included a timeout routine for the LCD backlight. If you want to have a look at the currently displayed values on the LCD simply turn the Encoder in any direction and the backlight will be switched on for some seconds before it is switched off again. I also had to cover the 3 LEDs on the Sensirion board with black tape as I couldn't simply switch them off as the connected digital I/O lines are in use by the connected peripheral devices. After the splash screen that is shown for 3 seconds the display is cleared and a reminder tells you that the record can be started by pushing the encoder. After pushing the data logger the version code of the Sensirion library is shown for 3 seconds. Afterwards the code is checking which log file number comes next and generates the logfile and starts conversion.


Used libraries


My code

// SENSIRION DATA LOGGER for the Element14 RoadTest
// by Peter ULLRICH
// homepage:
// email:

/* Possible extensions / Improvements:

- big values of VOC and CO2eqqu don't fit on LCD display. 
  ==> output in form of 14k4?
- Time can not be set. ==> Use encoder routines and a simple menu to set the clock.
  ==> In the meantime load the RTC1307 test application that has been changed to set the clock to the compile time values.

Already implemented:
- kid friendly night version: The LED back light should go off after about 10 seconds automatically. 
  It should be turned on by rotating the encoder in any direction

// All includes for the SDFat library from Bill Greiman: 
// [I had write problems with the Arduino included SD library...]
#include <SPI.h>
#include "SdFat.h"

// SD chip select pin.  Be sure to disable any other SPI devices such as Enet.
// change this to match your SD shield or module;
// Arduino Ethernet shield: pin 4
// Adafruit SD shields and modules: pin 10
// Sparkfun SD shield: pin 8
const uint8_t chipSelect = 10;

// Error messages stored in flash.
#define error(msg) sd.errorHalt(F(msg))

// Interval between data records in milliseconds.
// The interval must be greater than the maximum SD write latency plus the
// time to acquire and write data to the SD to avoid overrun errors.
// Run the bench example to check the quality of your SD card.
const uint32_t SAMPLE_INTERVAL_MS = 1000;

// Log file base name.  Must be six characters or less.


// Encoder Library from Paul Stoffregen:
#include <Encoder.h>


// Sensirion Environmental sensor library V0.5.3
#include <sensirion_ess.h>


// Date and time functions using a DS1307 RTC connected via I2C and Wire lib
// Adafruit Datalogger and RTClib:
#include <Wire.h>
#include <RTClib.h>


// I2C LCD (20 character 4 line I2C Display QC2004A
//   with Backpack Interface labelled "YwRobot Arduino LCM1602 IIC V1" 
//   with PCF8574) I2C address:  0x27  (7bit)

// Include the LiquidCrystal LCD library from Francisco Malpartida 
#include <LiquidCrystal_I2C.h>

//LiquidCrystal_I2C lcd(0x38, BACKLIGHT_PIN, POSITIVE);  // Set the LCD I2C address

#define BACKLIGHT_PIN      13
#define LCD_BACKLIGHT_TIME 10  // back light timeout

// set the LCD address to 0x27 for a 20 chars 4 line display
// Set the pins on the I2C chip used for LCD connections:
//                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address


// Encoder pin definitions:

#define enc_a  2   // interrupt capable
#define enc_b  4
#define enc_sw 3   // interrupt capable

// Create an instance of Encoder:
// Change these two numbers to the pins connected to your encoder.
//   Best Performance: both pins have interrupt capability
//   Good Performance: only the first pin has interrupt capability
//   Low Performance:  neither pin has interrupt capability
Encoder myEnc(enc_a,enc_b);   // first pin is an interrupt capable pin...

//  Create an instance of SensirionESS
SensirionESS ess;

//  Create an instance of RTC_DS1307
RTC_DS1307 rtc;

// Create a File system object
SdFat sd;

// Create a Log file object
SdFile file;

// Time in micros for next data record.
uint32_t logTime;

  float temp, rh, tvoc, eco2 = -1;  
  char tempresult[8];  // Buffers big enough for 7-character float
  char rh_result[8];   
  char vocresult[10];  // Buffers big enough for 9-character float
  char eco2result[10]; 
  char timestamp[9];
  char log_line[80];
  long enc_old_pos  = -999;  
  boolean push_button = false;
  boolean enc_changed = false;
  // Log file name construction
  const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
  char fileName[13] = FILE_BASE_NAME "00.CSV";
  byte bg_ontime=10;  // 10s is the standard LCD back light "on" time.

void setup() {
  // Encoder initialization
  push_button = false;
  pinMode(enc_sw, OUTPUT);    // set push button input as output / HIGH level to enable pullup resistor
//  attachInterrupt(digitalPinToInterrupt(enc_sw), PushButton_INT, FALLING); 

  // initialize LCD library
  lcd.begin(20,4);  // LCD with 20 characters / 4 lines
  lcd.noBacklight();  // blink with backlight to show reset
  lcd.print(F("SENSIRION Road Test"));   // show splash screen for 3 seconds
  lcd.print(F("  by Peter ULLRICH"));
  lcd.print(F("    sponsored by"));  
  lcd.print(F("     Element14"));   
  lcd.clear();   // clear screen
  // Wait for USB Serial 
  while (!Serial) {
  lcd.print(F("Press encoder >START"));
  // wait on encoder pushbutton:
  while (digitalRead(enc_sw));   // wait until encoder push button is pressed
  // Sensirion sensor initialization
  delay(3000);    // show sensor version info for 3 seconds

  // Initialize SPI for the SD card at the highest speed supported by the board 
  // that is not over 50 MHz. Try a lower speed if SPI errors occur.
  if (!sd.begin(chipSelect, SD_SCK_MHZ(50))) {
    lcd.print(F("Can't access SD card"));
    lcd.print(F("Check SD card!"));
    lcd.print(F("Check chip select!"));
    // Output to serial interface:
    // Can't access SD card. Do not reformat.
    // No card, wrong chip select pin, or SPI problem?
    // SD errorCode: 0X20,0XFF
    // original error handler: 

  // Find an unused file name.
  if (BASE_NAME_SIZE > 6) {
    lcd.print(F("is too long!"));
    //error("FILE_BASE_NAME too long");
  while (sd.exists(fileName)) {
    if (fileName[BASE_NAME_SIZE + 1] != '9') {
      fileName[BASE_NAME_SIZE + 1]++;
    } else if (fileName[BASE_NAME_SIZE] != '9') {
      fileName[BASE_NAME_SIZE + 1] = '0';
    } else {
    lcd.print(F("Can't create"));
    lcd.print(F("file name!"));
    error("Can't create file name");
  if (!, O_CREAT | O_WRITE | O_EXCL)) {
    // original error handler: error("");
    lcd.print(F("File open error!"));
    lcd.print(F("Check SD card!"));
    lcd.print(F("Check write protect!"));

  lcd.println(F("Press encoder > STOP"));
  lcd.print(F("Temp Humi VOC CO2equ"));
  // Write data header to SD card log file
  writeHeader();  // write header of logfile to SD card
  // Start on a multiple of the sample interval.
  logTime = micros()/(1000UL*SAMPLE_INTERVAL_MS) + 1;
  logTime *= 1000UL*SAMPLE_INTERVAL_MS;
    // Search for the DS1307 RTC on the data logger board and check if it is running.
    if (! rtc.begin()) {
      lcd.print(F(" RTC not found!"));
      lcd.print(F(" Datalogger missing!"));
      //Serial.println(F("Couldn't find the DS1307 RTC - be sure to use the DataLogger shield as intended!"));
      while (1);
    enc_changed = false;      // reset this flag so that no encoder changed have been recognized until now

void loop() {
  // Time for next record.
  logTime += 1000UL*SAMPLE_INTERVAL_MS;

  // Wait for log time.
  int32_t diff;
  do {
    diff = micros() - logTime;
  } while (diff < 0);

  // Check for data rate too high.
  if (diff > 10) {
    error("Missed data record");

  encoder_read();    // encoder value changes only trigger the LCD back light "switch on" action
  if (enc_changed) {
    enc_changed = false;
    bg_ontime = LCD_BACKLIGHT_TIME;   // set LCD back light timeout in seconds => start backlight timeout

  if (bg_ontime >= 1) {
    if (bg_ontime==0){
      lcd.noBacklight();  // switch off the backlight

  // now log the read sensor values with the timestamp to SD card and serial interface
  sprintf(log_line, "%s, %s, %s, %s, %s", timestamp, tempresult, rh_result, vocresult, eco2result);
  Serial.println(log_line);  // output via serial interface / screen
  file.println(log_line); // logging to file on SD card
  // Force data to SD and update the directory entry to avoid data loss.
  if (!file.sync() || file.getWriteError()) {
    lcd.print(F("Write error!"));
    // original error routine: error("write error");
    if (!digitalRead(enc_sw)) {   // stop if encoder push button is pressed
    // Close file and stop.
    lcd.print(F("Log file closed!"));
    lcd.print(F("File: "));
    lcd.print(F("Program stopped!"));

void PushButton_INT()  {
  push_button = true;
  int r = 0;
  //Serial.println("Push button pressed!");
  for (unsigned int i=0; i<60000;i++){

void wait_on_encoder_pushbutton(){
  push_button = false;
  // Serial.println(push_button);
  while (!push_button){   // wait for encoder push button press


void encoder_read(){
  long enc_new_pos =;   // the output of my encoder needs to be devided by (-2)
                                          // so that the first encoder pin can be an interruptable pin
  if (enc_new_pos != enc_old_pos) {
    enc_old_pos = enc_new_pos;
    enc_changed = true;                   // just set this flag to show that the encoder value has changed.

// SD card: Write data header
void writeHeader() {
    file.println(F("Timestamp,  Temp, Humidity,     VOC,    CO2equ"));
    file.println(F("HH:MM:SS,   [°C],  [% RH],     [ppb],    [ppm]"));

void initialize_sensors(){
  // First step is to initialize the sensors; this should only fail if
  // the board is defect, or the connection isn't working. Since there's nothing
  // we can do if this fails, the code will loop forever if an error is detected
  if (ess.initSensors() != 0) {
      lcd.print(F(" Sensor init failed!"));
      while (1);   // loop forever

  // The SGP sensor has product type information and feature set stored
  // the following code reads it out, and prints it to the serial console.
  // This is purely to demo the function calls, and is not necessary to operate
  // the sensor
  int type = ess.getProductType();
  int fsVersion = ess.getFeatureSetVersion();
  lcd.print(F("Initializing sensors"));  
  lcd.print(F("Sensirion "));
  lcd.print((type == SensirionESS::PRODUCT_TYPE_SGP30) ? "SGP30" : "SGPC3");
  lcd.print(F("detected, running"));
  lcd.print(F("feature set V "));

void read_timestamp() {
  // read the current time from the RTC and build a timestamp string HH:MM:SS
  DateTime now =;
  // DS1321: rtc.getTimeStr()); // test it also with DS1307 !!!
  sprintf(timestamp, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());   

void lcdprint_timestamp(byte x, byte y)  {   // read the current time and output it on the current LCD position

void read_sensors(){
  // we'll start by triggering a measurement of the VOC/CO2 sensor;
  // it's important to do this first to make sure sleep timing is
  // correct. If the command succeeds, the local variables will
  // be set to the values we just read; if it fails, they'll be -1
  if (ess.measureIAQ() != 0) {
    Serial.print(F("Error while measuring IAQ: "));
  } else {
    tvoc = ess.getTVOC();
    eco2 = ess.getECO2(); // SGP30 only

  // next, we'll trigger the humidity and temperature measurement
  if (ess.measureRHT() != 0) {
    Serial.print(F("Error while measuring RHT: "));
  } else {
    temp = ess.getTemperature();
    rh = ess.getHumidity();

void convert_sensor_values_for_logfile() {
  // converting the values into char strings for further processing with SD card logging
  // Text string generation (used this method as I had problems with sprintf...):
  dtostrf(temp, 6, 2, tempresult); 
  dtostrf(rh, 6, 2, rh_result); 
  dtostrf(tvoc, 9, 2, vocresult); 
  dtostrf(eco2, 9, 2, eco2result); 

void convert_sensor_values_for_lcd() {
  // converting the values into char strings for further processing with SD card logging
  // Text string generation (used this method as I had problems with sprintf...):
  dtostrf(temp, 4, 1, tempresult); 
  dtostrf(rh, 4, 1, rh_result); 
  dtostrf(tvoc, 4, 0, vocresult); 
  dtostrf(eco2, 4, 0, eco2result); 

void   lcdprint_sensorvalues() {   // LCD columns: Temp Humi VOC CO2equ
  lcd.write(' ');
  lcd.write(' ');
  lcd.write(' ');
  lcd.write(' ');


Video showing the LCD during program execution


  • Splash screen for 3 seconds after start or reset.
  • Message that tells us that we have to start by pressing the encoder push button
  • Feature set message for 3 seconds after the encoder push button has been pressed
  • Logfile open, display of logfile name, time stamp, measurements every second and measurement value display
  • backlight auto off after 10 seconds - turn encoder in any direction to switch on the backlight for another 10 seconds
  • stop measurements and logging by pressing the encoder push button for more than one second.
  • Display of the written logfile name, program stop.
  • Restart by pressing reset or power off & on again



Video showing the output to the USB serial interface


Example of a logging file DATA79.CSV (of the USB serial measurements shown in the video above)


The CSV file (comma separated values) starts with the table header, the unit line and a line to separate the header from the measurement values. Also the header lines are separated with commas so that a CSV input leads to corrects column descriptions. Only the separating line could be erased if you want before you start your further calculations or graphics in Excel. During or after the CSV import you habe to declare the column contants and format. Especially the first colum needs to be declared as time value. For some countries the dots need to be converted into commas after the log file has been successfuly imported into Excel columns so that the values are recognized as floating values.


Timestamp,  Temp, Humidity,     VOC,    CO2equ
HH:MM:SS,   [°C],  [% RH],     [ppb],    [ppm]
22:51:03,  28.05,  48.80,      0.00,    400.00
22:51:04,  28.07,  48.77,      0.00,    400.00
22:51:05,  28.08,  48.82,      0.00,    400.00
22:51:06,  28.08,  48.84,      0.00,    400.00
22:51:07,  28.06,  48.95,      0.00,    400.00
22:51:08,  28.19,  50.50,      0.00,    400.00
22:51:09,  28.13,  54.92,      0.00,    400.00
22:51:10,  28.16,  54.99,      0.00,    400.00
22:51:11,  28.16,  55.32,      0.00,    400.00
22:51:12,  28.16,  56.32,      0.00,    400.00
22:51:13,  28.21,  55.47,      0.00,    400.00
22:51:14,  28.25,  55.64,      0.00,    400.00
22:51:15,  28.25,  55.93,     28.00,    481.00
22:51:17,  28.25,  56.01,     26.00,    400.00
22:51:18,  28.31,  55.32,     38.00,    522.00
22:51:19,  28.30,  57.00,      8.00,    400.00
22:51:20,  28.32,  56.99,     76.00,    806.00
22:51:21,  28.31,  58.33,     34.00,    400.00
22:51:22,  28.30,  57.82,      0.00,    400.00
22:51:23,  28.31,  56.13,      0.00,    400.00
22:51:24,  28.32,  54.21,      0.00,    400.00
22:51:25,  28.26,  52.62,      0.00,    400.00
22:51:26,  28.31,  51.72,      0.00,    400.00


Comparison of the measured values with values from the Continuous Glucose Measurement Sensor Dexcom G4


Dexcom G4 Platinum Continuous Glucose Monitoring

Sensirion ESS on 2018-09-02:

Dexcom G4 on 2018-09-02: the blue marked area shows the time window where the Sensirion data has been logged.

Results of 2018-09-02:


On the 2nd of September I saw some similarities in the measured curves that made me think that the Sensirion sensor could eventually be used for Diabetes hypo and hyper detection.

It seemed that the Co2eq and VOC values go up in case of hypos (<70 mg/dl) but nearly no reaction on hypers (>250 mg/dl).


Sensirion ESS on 2018-09-03:

Dexcom G4 on 2018-09-03: the blue marked area shows the time window where the Sensirion data has been logged.

Results of 2018-09-03:


On the 3rd of September there have been only very few similarities between the two sensors. The "nearly hypo" event at 10:00 can not be seen on the sensirion sensors but we have high VOC and Co2eq values at about 7:22 that can not be seen in the Dexcom G4 curve. Looking into my log book where I noticed some events and remarks I found out that at this time my son changed position in his bed so that he breathed directly to the Sensirion sensor setup.


Conclusio for the use of the Sensirion sensor board for Diabetes measurements

The measurements above (and even more measurements I have recorded) show that a setup where the sensor box just lies near the bed in an undefined distance to the test person - especially the mouth and node of the patient can't be used as position changes can be seen much stronger than eventually recognizeable diabetes influences.


Another setup where the patient has to blow into a measurement unit directly so that the distance to the sensor is well defined could eventually lead to more useful results - but this would need a special setup that can't be done with the big Environmental sensor board. And maybe the Sensirion gas sensor could be influenced or maybe even damaged if they will be in the near field of the patients mouth as the high humidity might lead to oxidation and contamination of the sensors.


So maybe the sensors can not be used for diabetes hypo and hyper detection, but they are very useful for air quality measurements. The reaction of the sensors - especial the gas sensors - is very fast! I can't tell anything about the accuracy as I have no reference to compare with. Measurements during the days when my son was sitting in front of his computer playing lots of games during his holidays showed that opening a window could drastically improve the air quality in his room - whcih of course could lead to better cencentration.


I want to thank Sensirion and Element14 that I have been selected for this RoadTest - I will continue to use my setup for air quality measurments. For that purpose I want to print a nice housing for it on my 3D printer so that it is more portable. I already bought some gas sensors from other manufacturers so that I can compare them with the Sensirion sensors.