// Include Air quality sensor libraries //
#include "../lib/SGP30/src/Adafruit_SPIDevice.h"
#include "../lib/SGP30/src/Adafruit_I2CDevice.h"
#include "../lib/SGP30/src/Adafruit_BusIO_Register.h"
#include "../lib/SGP30/src/Adafruit_SGP30.h"
// Include ST7789 TFT Display libraries //
#include "../lib/Adafruit_GFX_RK/src/Adafruit_GFX.h"
#include "../lib/Adafruit_ST7735_RK/src/Adafruit_ST7789.h"
#include "../lib/Adafruit_GFX_RK/src/FreeSansBold12pt7b.h"
#include "../lib/Adafruit_GFX_RK/src/FreeSansOblique12pt7b.h"
#include <SPI.h>
// ST7789 TFT definitions //
#define TFT_CS S3 // Define CS pin for TFT display
#define TFT_RST D6 // Define RST pin for TFT display
#define TFT_DC D5 // Define DC pin for TFT display
int tvoc_state[4] = {0,0,0,0}; // initialise array to handle TVOC readings display
int co2_state[4] = {0,0,0,0}; // initialise array to handle CO2 readings display
unsigned long previousMillis = 0; // Timer that will used in print_values() funtion.
const long interval = 10000; // Set duration between readings in millis e.g. 10 000 = 10s
int counter = 0; // start counter. used in SPG30 fucntion to call baseline readings
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); // Hardware SPI
float p = 3.1415926;
Adafruit_SGP30 sgp; // call SPG30 sensor
/* return absolute humidity [mg/m^3] with approximation formula
* @param temperature [°C]
* @param humidity [%RH]
*/
uint32_t getAbsoluteHumidity(float temperature, float humidity) {
// approximation formula from Sensirion SGP30 Driver Integration chapter 3.15
const float absoluteHumidity = 216.7f * ((humidity / 100.0f) * 6.112f * exp((17.62f * temperature) / (243.12f + temperature)) / (273.15f + temperature)); // [g/m^3]
const uint32_t absoluteHumidityScaled = static_cast<uint32_t>(1000.0f * absoluteHumidity); // [mg/m^3]
return absoluteHumidityScaled;
}
void setup() {
Serial.begin(115200); // Start serial
pinMode(A2, OUTPUT); // sets the pin as output
analogWriteResolution(A2, 12); // sets analogWrite resolution to 12 bits
int maxFreq = analogWriteMaxFrequency(A2);
// analogWrite (A2,255,maxFreq);
analogWrite(A2, 3000, maxFreq / 2); // 2000/4095 = ~50% duty cycle
tft.init(240, 320); // Init ST7789 320x240
tft.fillScreen(ST77XX_BLACK); // creates black background in dsiplay
while (!Serial) { delay(10); } // Wait for serial console to open!
//Particle.publish("SGP30 test", PRIVATE); // DEBUG
if (! sgp.begin()){ // initialise SGP30 sensor
Particle.publish("Sensor not found", PRIVATE); // DEBUG -- uncomment if you are not sure whether your sensor is working.
while (1);
}
delay(50);
// If you have a baseline measurement from before you can assign it to start, to 'self-calibrate'
//sgp.setIAQBaseline(0x8E68, 0x8F41); // Will vary for each sensor!
}
void measure() {
// If you have a temperature / humidity sensor, you can set the absolute humidity to enable the humditiy compensation for the air quality signals
//float temperature = 22.1; // [°C]
//float humidity = 45.2; // [%RH]
//sgp.setHumidity(getAbsoluteHumidity(temperature, humidity));
if (! sgp.IAQmeasure()) {
Serial.println("Measurement failed"); // cofirm wiring if this fails
return;
}
//Particle.publish("eCO2 " + String(sgp.eCO2) +" ppm", PRIVATE); // DEBUG
if ((sgp.TVOC >= 0) && (sgp.TVOC <= 220) && (tvoc_state[0] == 0)) { // TVOC measurement brackets to define warning indicator colour.
Serial.print("G_TVOC "); Serial.print(sgp.TVOC); Serial.print(" ppb\t"); // DEBUG
tft.fillRect(0,165,240,95,ST77XX_GREEN);
tvoc_state[0] = 1; // Array is contructed to prevent code from writing the images consitently if there has been no change
tvoc_state[1] = 0; // This applies to the entire IF stament for both TVOC and CO2
tvoc_state[2] = 0;
tvoc_state[3] = 0;
print_values();
} else if ((sgp.TVOC >= 221) && (sgp.TVOC <= 660) && (tvoc_state[1] == 0)) {
Serial.print("G_TVOC "); Serial.print(sgp.TVOC); Serial.print(" ppb\t"); // DEBUG
tft.fillRect(0,165,240,95,ST77XX_YELLOW);
tvoc_state[0] = 0;
tvoc_state[1] = 1;
tvoc_state[2] = 0;
tvoc_state[3] = 0;
} else if ((sgp.TVOC >= 661) && (sgp.TVOC <= 1430) && (tvoc_state[2] == 0)) {
Serial.print("G_TVOC "); Serial.print(sgp.TVOC); Serial.print(" ppb\t"); // DEBUG
tft.fillRect(0,165,240,95,ST77XX_ORANGE);
tvoc_state[0] = 0;
tvoc_state[1] = 0;
tvoc_state[2] = 1;
tvoc_state[3] = 0;
} else if ((sgp.TVOC >= 1431) && (tvoc_state[3] == 0)) {
Serial.print("G_TVOC "); Serial.print(sgp.TVOC); Serial.print(" ppb\t"); // DEBUG
tft.fillRect(0,165,240,95,ST77XX_RED);
tvoc_state[0] = 0;
tvoc_state[1] = 0;
tvoc_state[2] = 0;
tvoc_state[3] = 1;
}
if ((sgp.eCO2 >= 0) && (sgp.eCO2 <= 1000) && (co2_state[0] == 0)) {
Serial.print("G_CO2 "); Serial.print(sgp.eCO2); Serial.print(" ppm"); // DEBUG
tft.fillRect(0,0,240,95,ST77XX_GREEN);
co2_state[0] = 1;
co2_state[1] = 0;
co2_state[2] = 0;
co2_state[3] = 0;
} else if ((sgp.eCO2 >= 1001) && (sgp.eCO2 <= 2000) && (co2_state[1] == 0)) {
Serial.print("G_CO2 "); Serial.print(sgp.eCO2); Serial.print(" ppm"); // DEBUG
tft.fillRect(0,0,240,95,ST77XX_YELLOW);
co2_state[0] = 0;
co2_state[1] = 1;
co2_state[2] = 0;
co2_state[3] = 0;
} else if ((sgp.eCO2 >= 2001) && (sgp.eCO2 <= 5000) && (co2_state[2] == 0)) {
Serial.print("G_CO2 "); Serial.print(sgp.eCO2); Serial.print(" ppm"); // DEBUG
tft.fillRect(0,0,240,95,ST77XX_ORANGE);
co2_state[0] = 0;
co2_state[1] = 0;
co2_state[2] = 1;
co2_state[3] = 0;
} else if ((sgp.eCO2 >= 5000) && (co2_state[3] == 0)) {
Serial.print("G_CO2 "); Serial.print(sgp.eCO2); Serial.print(" ppm"); // DEBUG
tft.fillRect(0,0,240,95,ST77XX_RED);
co2_state[0] = 0;
co2_state[1] = 0;
co2_state[2] = 0;
co2_state[3] = 1;
}
//Particle.publish("Raw H2 " + String(sgp.rawH2) + " \t", PRIVATE); // additional paramenters
//Particle.publish("Raw Ethanol "+ String (sgp.rawEthanol) + "", PRIVATE); // additional paramenters
counter++;
if (counter == 30) {
counter = 0;
uint16_t TVOC_base, eCO2_base;
if (! sgp.getIAQBaseline(&eCO2_base, &TVOC_base)) {
Particle.publish("Failed to get baseline readings", PRIVATE);
return;
}
//Particle.publish("****Baseline values: eCO2: 0x" + String(eCO2_base, HEX), PRIVATE);
//Particle.publish(" & TVOC: 0x" + String(TVOC_base, HEX), PRIVATE);
}
draw_screen(); // calls draw_screen() function
}
void draw_screen() {
tft.setFont(&FreeSansBold12pt7b); // set font
tft.setTextColor(ST77XX_WHITE); // set font colour
tft.setTextWrap(false);
tft.setTextSize(3); // set font size
tft.setCursor(45, 70); // set sursor to start writing text
tft.print("CO2");
tft.setCursor(15, 235);
tft.print("TVOC");
delay(50);
}
void print_values() {
tft.fillRect(0,95,120,60,ST77XX_WHITE); // draws background fills for readings
tft.fillRect(121,95,120,60,ST77XX_BLUE);
tft.fillRect(0,260,120,60,ST77XX_WHITE);
tft.fillRect(121,260,120,60,ST77XX_BLUE);
tft.setTextWrap(false);
tft.setCursor(25, 120);
tft.setTextColor(ST77XX_BLUE);
tft.setFont(&FreeSansOblique12pt7b);
tft.setTextSize(1);
tft.println(sgp.eCO2);
tft.setCursor(25, 145);
tft.println("ppm");
//Serial.print("G_eCO2 "); Serial.print(sgp.eCO2); Serial.print(" ppm"); // DEBUG
tft.setCursor(25, 285);
tft.println(sgp.TVOC);
tft.setCursor(25, 310);
tft.println("ppb\\t");
//Serial.print("G_TVOC "); Serial.print(sgp.TVOC); Serial.print(" ppb\t"); // DEBUG
tft.setCursor(150, 120);
tft.setTextColor(ST77XX_WHITE);
tft.println(sgp.rawH2);
tft.setCursor(150, 145);
tft.println("H2 \\t");
tft.setCursor(150, 285);
tft.println(sgp.rawEthanol);
tft.setCursor(145, 310);
tft.print("Ethanol");
delay(50);
}
void loop() {
measure(); // calls measure() function
unsigned long currentMillis = millis(); // starts timer. when timer has ellapsed, print_values() function is called.
if (currentMillis - previousMillis >= interval) {
print_values();
previousMillis = currentMillis;
}
}
Comments