Jeremy
Published © CC BY-SA

Seeed-based SEN55 Air Quality Display

Using off the shelf parts with grove connectors, wire up a SEN55 sensor to be able to read PM, NOX, VOC, Temp, and Humidity.

BeginnerProtip-60 minutes773
Seeed-based SEN55 Air Quality Display

Things used in this project

Hardware components

Wio Terminal
Seeed Studio Wio Terminal
×1
Seeed Studio Grove SEN55 All-in-one environmental sensor
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Code

Wio Terminal SEN55 Grove Sketch

Arduino
Main sketch to read SEN55 and output the results to the Wio Terminal LCD using TFT eSPI.
#include <Arduino.h>
#include <SensirionI2CSen5x.h>
#include <Wire.h>
#include "TFT_eSPI.h"


// The used commands use up to 48 bytes. On some Arduino's the default buffer
// space is not large enough
#define MAXBUF_REQUIREMENT 48

#if (defined(I2C_BUFFER_LENGTH) && (I2C_BUFFER_LENGTH >= MAXBUF_REQUIREMENT)) || (defined(BUFFER_LENGTH) && BUFFER_LENGTH >= MAXBUF_REQUIREMENT)
#define USE_PRODUCT_INFO
#endif

#define NICE_GREEN 0x7650
#define NICE_YELLOW 0xFF70
#define NICE_ORANGE 0xFE8E
#define NICE_RED 0xFB8A
#define NICE_DARKBORDER 0x3A08

#define LCD_BACKLIGHT (72Ul) // Control Pin of LCD

SensirionI2CSen5x sen5x;

TFT_eSPI tft;
TFT_eSprite spr = TFT_eSprite(&tft);  // Sprite

bool ALERT = false;

void printModuleVersions() {
  uint16_t error;
  char errorMessage[256];

  unsigned char productName[32];
  uint8_t productNameSize = 32;

  error = sen5x.getProductName(productName, productNameSize);

  if (error) {
    Serial.print("Error trying to execute getProductName(): ");
    errorToString(error, errorMessage, 256);
    Serial.println(errorMessage);
  } else {
    Serial.print("ProductName:");
    Serial.println((char*)productName);
  }

  uint8_t firmwareMajor;
  uint8_t firmwareMinor;
  bool firmwareDebug;
  uint8_t hardwareMajor;
  uint8_t hardwareMinor;
  uint8_t protocolMajor;
  uint8_t protocolMinor;

  error = sen5x.getVersion(firmwareMajor, firmwareMinor, firmwareDebug,
                           hardwareMajor, hardwareMinor, protocolMajor,
                           protocolMinor);
  if (error) {
    Serial.print("Error trying to execute getVersion(): ");
    errorToString(error, errorMessage, 256);
    Serial.println(errorMessage);
  } else {
    Serial.print("Firmware: ");
    Serial.print(firmwareMajor);
    Serial.print(".");
    Serial.print(firmwareMinor);
    Serial.print(", ");

    Serial.print("Hardware: ");
    Serial.print(hardwareMajor);
    Serial.print(".");
    Serial.println(hardwareMinor);
  }
}

void printSerialNumber() {
  uint16_t error;
  char errorMessage[256];
  unsigned char serialNumber[32];
  uint8_t serialNumberSize = 32;

  error = sen5x.getSerialNumber(serialNumber, serialNumberSize);
  if (error) {
    Serial.print("Error trying to execute getSerialNumber(): ");
    errorToString(error, errorMessage, 256);
    Serial.println(errorMessage);
  } else {
    Serial.print("SerialNumber:");
    Serial.println((char*)serialNumber);
  }
}

bool inRange(float myfloat, float minimum, float maximum) {
  return ((minimum <= myfloat) && (myfloat <= maximum));
}


void setup() {

  Serial.begin(115200);

  Wire.begin();

  sen5x.begin(Wire);

  pinMode(WIO_KEY_A, INPUT_PULLUP);
  pinMode(WIO_KEY_C, INPUT_PULLUP);

  tft.begin();
  digitalWrite(LCD_BACKLIGHT, HIGH);
  tft.fillScreen(TFT_BLACK);
  tft.setRotation(3);
  spr.createSprite(tft.width(), tft.height());
  uint16_t error;
  char errorMessage[256];
  error = sen5x.deviceReset();
  if (error) {
    Serial.print("Error trying to execute deviceReset(): ");
    errorToString(error, errorMessage, 256);
    Serial.println(errorMessage);
  }

// Print SEN55 module information if i2c buffers are large enough
#ifdef USE_PRODUCT_INFO
  printSerialNumber();
  printModuleVersions();
#endif

  // set a temperature offset in degrees celsius
  // Note: supported by SEN54 and SEN55 sensors
  // By default, the temperature and humidity outputs from the sensor
  // are compensated for the modules self-heating. If the module is
  // designed into a device, the temperature compensation might need
  // to be adapted to incorporate the change in thermal coupling and
  // self-heating of other device components.
  //
  // A guide to achieve optimal performance, including references
  // to mechanical design-in examples can be found in the app note
  // SEN5x  Temperature Compensation Instruction at www.sensirion.com.
  // Please refer to those application notes for further information
  // on the advanced compensation settings used
  // in `setTemperatureOffsetParameters`, `setWarmStartParameter` and
  // `setRhtAccelerationMode`.
  //
  // Adjust tempOffset to account for additional temperature offsets
  // exceeding the SEN module's self heating.
  float tempOffset = 0.0;
  error = sen5x.setTemperatureOffsetSimple(tempOffset);
  if (error) {
    Serial.print("Error trying to execute setTemperatureOffsetSimple(): ");
    errorToString(error, errorMessage, 256);
    Serial.println(errorMessage);
  } else {
    Serial.print("Temperature Offset set to ");
    Serial.print(tempOffset);
    Serial.println(" deg. Celsius (SEN54/SEN55 only");
  }

  // Start Measurement
  error = sen5x.startMeasurement();
  if (error) {
    Serial.print("Error trying to execute startMeasurement(): ");
    errorToString(error, errorMessage, 256);
    Serial.println(errorMessage);
  }
}

void loop() {\
  loop1();
}
void loop1() {
  uint16_t error;
  char errorMessage[256];

  delay(1000);

  spr.setTextDatum(TL_DATUM);
  spr.fillSprite(TFT_WHITE);
  spr.setFreeFont(&FreeSansBoldOblique18pt7b);
  spr.setTextColor(TFT_BLACK);

  spr.drawString("AIR QUALITY", 50, 10, 1);  // Print the test text in the custom font 1


  // Prepare for showing the sensor values
  spr.setTextDatum(TL_DATUM);
  spr.setTextColor(TFT_BLACK);
  spr.setTextFont(2);






  //Serial.println(tft.height());   // 320x240
  // Read Measurement
  float massConcentrationPm1p0;
  float massConcentrationPm2p5;
  float massConcentrationPm4p0;
  float massConcentrationPm10p0;
  float ambientHumidity;
  float ambientTemperature;
  float vocIndex;
  float noxIndex;

  error = sen5x.readMeasuredValues(
    massConcentrationPm1p0, massConcentrationPm2p5, massConcentrationPm4p0,
    massConcentrationPm10p0, ambientHumidity, ambientTemperature, vocIndex,
    noxIndex);

  if (error) {
    Serial.print("Error trying to execute readMeasuredValues(): ");
    errorToString(error, errorMessage, 256);
    Serial.println(errorMessage);
  } else {

    if (inRange(massConcentrationPm2p5, 0, 50)) {
      spr.fillRect(tft.width() / 2 - 128, 50, 80, 60, NICE_GREEN);
    } else if (inRange(massConcentrationPm2p5, 51, 150)) {
      spr.fillRect(tft.width() / 2 - 128, 50, 80, 60, NICE_YELLOW);
    } else if (inRange(massConcentrationPm2p5, 151, 300)) {
      spr.fillRect(tft.width() / 2 - 128, 50, 80, 60, NICE_ORANGE);
    } else if (inRange(massConcentrationPm2p5, 301, 10000)) {
      spr.fillRect(tft.width() / 2 - 128, 50, 80, 60, NICE_RED);
      ALERT = true;
    }

    spr.setTextColor(NICE_DARKBORDER);
    spr.setTextDatum(MC_DATUM);
    spr.setFreeFont(&FreeSansBold12pt7b);
    spr.drawString((String)massConcentrationPm2p5, tft.width() / 2 - 90, 77);


    // Draw colored rects based on sensor reading. Based on researchgate.net chart google images
    if (inRange(massConcentrationPm10p0, 0, 199)) {
      spr.fillRect(tft.width() / 2 - 38, 50, 80, 60, NICE_GREEN);
    } else if (inRange(massConcentrationPm10p0, 200, 349)) {
      spr.fillRect(tft.width() / 2 - 38, 50, 80, 60, NICE_YELLOW);
    } else if (inRange(massConcentrationPm10p0, 350, 549)) {
      spr.fillRect(tft.width() / 2 - 38, 50, 80, 60, NICE_ORANGE);
    } else if (inRange(massConcentrationPm10p0, 550, 10000)) {
      spr.fillRect(tft.width() / 2 - 38, 50, 80, 60, NICE_RED);
      ALERT = true;
    }

    spr.setTextDatum(MC_DATUM);
    spr.setFreeFont(&FreeSansBold12pt7b);
    spr.drawString((String)massConcentrationPm10p0, tft.width() / 2, 77);
    // Serial.print("MassConcentrationPm1p0:");
    // Serial.print(massConcentrationPm1p0);
    // Serial.print("\t");
    // Serial.print("MassConcentrationPm2p5:");
    // Serial.print(massConcentrationPm2p5);
    // Serial.print("\t");
    // Serial.print("MassConcentrationPm4p0:");
    // Serial.print(massConcentrationPm4p0);
    // Serial.print("\t");
    // Serial.print("MassConcentrationPm10p0:");
    // Serial.print(massConcentrationPm10p0);
    // Serial.print("\t");
    // Serial.print("AmbientHumidity:");
    if (isnan(ambientHumidity)) {
      Serial.print("n/a");
    } else {
      //Serial.print(ambientHumidity);
      spr.setTextDatum(MC_DATUM);
      spr.setFreeFont(&FreeSansBold12pt7b);
      spr.drawString((String)ambientHumidity, tft.width() / 2, 159);
    }
    // Serial.print("\t");
    // Serial.print("AmbientTemperature:");
    if (isnan(ambientTemperature)) {
      Serial.print("n/a");
    } else {
      // Serial.print(ambientTemperature);
      spr.setTextDatum(MC_DATUM);
      spr.setFreeFont(&FreeSansBold12pt7b);
      spr.drawString((String)(ambientTemperature * 9 / 5 + 32), tft.width() / 2 - 90, 159);
    }
    // Serial.print("\t");
    // Serial.print("VocIndex:");
    // Draw rectangles
    if (inRange(vocIndex, 0, 150)) {
      spr.fillRect(tft.width() / 2 + 52, 50, 80, 60, NICE_GREEN);
    } else if (inRange(vocIndex, 151, 250)) {
      spr.fillRect(tft.width() / 2 + 52, 50, 80, 60, NICE_YELLOW);
    } else if (inRange(vocIndex, 251, 400)) {
      spr.fillRect(tft.width() / 2 + 52, 50, 80, 60, NICE_ORANGE);
    } else if (inRange(vocIndex, 401, 1000)) {
      spr.fillRect(tft.width() / 2 + 52, 50, 80, 60, NICE_RED);
      ALERT = true;
    }
    if (isnan(vocIndex)) {
      //Serial.print("n/a");
      spr.setTextDatum(MC_DATUM);
      spr.setFreeFont(&FreeSansBold12pt7b);
      spr.drawString("n/a", tft.width() / 2 + 90, 77);
    } else {
      spr.setTextDatum(MC_DATUM);
      spr.setFreeFont(&FreeSansBold12pt7b);
      spr.drawString((String)vocIndex, tft.width() / 2 + 90, 77);
      // Serial.print(vocIndex);
    }
    // Serial.print("\t");
    // Serial.print("NoxIndex:");
    if (inRange(noxIndex, 0, 20)) {
      spr.fillRect(tft.width() / 2 + 52, 130, 80, 60, NICE_GREEN);
    } else if (inRange(noxIndex, 21, 150)) {
      spr.fillRect(tft.width() / 2 + 52, 130, 80, 60, NICE_YELLOW);
    } else if (inRange(noxIndex, 151, 300)) {
      spr.fillRect(tft.width() / 2 + 52, 130, 80, 60, NICE_ORANGE);
    } else if (inRange(noxIndex, 301, 1000)) {
      spr.fillRect(tft.width() / 2 + 52, 130, 80, 60, NICE_RED);
      ALERT = true;
    }
    if (isnan(noxIndex)) {
      //Serial.println("n/a");
      spr.setTextDatum(MC_DATUM);
      spr.setFreeFont(&FreeSansBold12pt7b);
      spr.drawString("n/a", tft.width() / 2 + 90, 159);
    } else {
      //Serial.println(noxIndex);
      spr.setTextDatum(MC_DATUM);
      spr.setFreeFont(&FreeSansBold12pt7b);
      spr.drawString((String)noxIndex, tft.width() / 2 + 90, 159);
    }
  }

  spr.setTextDatum(TL_DATUM);
  spr.setTextColor(TFT_BLACK);
  spr.setTextFont(2);

  // Draw rectangles row 1
  spr.drawRect(tft.width() / 2 - 128, 50, 80, 60, NICE_DARKBORDER);  //draw rectangle with border
  spr.drawRect(tft.width() / 2 - 38, 50, 80, 60, NICE_DARKBORDER);   //draw rectangle with border
  spr.drawRect(tft.width() / 2 + 52, 50, 80, 60, NICE_DARKBORDER);   //draw rectangle with border

  // Draw rectangles row 2
  spr.drawRect(tft.width() / 2 - 128, 130, 80, 60, NICE_DARKBORDER);  //draw rectangle with border
  spr.drawRect(tft.width() / 2 - 38, 130, 80, 60, NICE_DARKBORDER);   //draw rectangle with border
  spr.drawRect(tft.width() / 2 + 52, 130, 80, 60, NICE_DARKBORDER);   //draw rectangle with border

  // Draw Metric Text row 1
  spr.drawString("PM2.5", tft.width() / 2 - 122, 50);
  spr.drawString("PM10.0", tft.width() / 2 - 32, 50);
  spr.drawString("VOC", tft.width() / 2 + 58, 50);

  // Draw Metric Text row 2
  spr.drawString("Temp", tft.width() / 2 - 122, 130);
  spr.drawString("Humidity", tft.width() / 2 - 32, 130);
  spr.drawString("NOX", tft.width() / 2 + 58, 130);

  // Draw Unit of Measure Text row 1
  spr.drawString("ug/m3", tft.width() / 2 - 88, 90);
  spr.drawString("ug/m3", tft.width() / 2 + 2, 90);
  spr.drawString("Index", tft.width() / 2 + 92, 90);

  // Draw Unit of Measure Text row 2
  spr.drawString("*F", tft.width() / 2 - 76, 170);
  spr.drawString("%", tft.width() / 2 + 15, 170);
  spr.drawString("Index", tft.width() / 2 + 95, 170);

  spr.pushSprite(0, 0);

  if (ALERT) {
    analogWrite(WIO_BUZZER, 128);
    delay(100);
    analogWrite(WIO_BUZZER, 0);
    ALERT = false;
  }

  if (digitalRead(WIO_KEY_A) == LOW) {
    Serial.println("A Key pressed");
    digitalWrite(LCD_BACKLIGHT, LOW);
  }

  if (digitalRead(WIO_KEY_C) == LOW) {
    Serial.println("C Key pressed");
    digitalWrite(LCD_BACKLIGHT, HIGH);
  }

}

Credits

Jeremy
1 project • 4 followers
Contact

Comments

Please log in or sign up to comment.