#include #include #include #include #include // Setup Sen44 sensor. SensirionI2CSen44 sen44; // Sen44 measurements. struct { uint16_t pm1p0 = 0; uint16_t pm2p5 = 0; uint16_t pm4p0 = 0; uint16_t pm10p0 = 0; float voc = 0; float hum = 0; float temp = 0; float f_temp = 0; } sen44_mes; // Setup LCD. LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // LCD resolution. enum { LCD_WIDTH = 16, LCD_HEIGHT = 2 }; // Supported values to be displayed on the LCD. enum supported_vals { pm_1p0, pm2p5, pm4p0, pm10p0, voc, hum, temp, f_temp, empty }; // Values per screen. values[0...] is considered a screen and values[][0...] // are its values. enum supported_vals values[][4] = { {temp, f_temp, hum, voc}, {pm_1p0, pm2p5, pm4p0, pm10p0}, }; // Screens and values counts. #define SCREENS_COUNT (sizeof(values) / sizeof(values[0])) #define VALUES_COUNT (sizeof(values[0]) / sizeof(values[0][0])) // Current screen. unsigned int current_screen = 0; // Current columns and rows on the LCD. int col = 0; int row = 0; // Setup buttons. const int mode_btn = 6; const int lcd_on_off_btn = 9; // Setup HIGH/LOW output. const int lcd_on_off_output = 10; // Execution times for things that will be "parallelized". struct { unsigned long modes_btn = 0; unsigned long on_off_btn = 0; unsigned long update_lcd = 0; unsigned long sen44 = 0; } parallel; // Force write data to screen. void update_screen(void); // Cycle between screens. void cycle_screens(void); // Convert Celcius to Fahrenheit. float celsius_to_fahrenheit(float degrees); // Calculate heat index (aka "feels like") termperature. float heat_index(float t, float rh); void setup(void) { // Setup Serial. Serial.begin(115200); while (!Serial) delay(100); // Set up the LCD's number of columns and rows. lcd.begin(LCD_WIDTH, LCD_HEIGHT); // Set LCD cursor to position 0, 0. lcd.setCursor(0, 0); lcd.print(" calibrating..."); // Setup buttons. pinMode(mode_btn, INPUT_PULLUP); pinMode(lcd_on_off_btn, INPUT_PULLUP); // Setup HIGH/LOW output. pinMode(lcd_on_off_output, OUTPUT); digitalWrite(lcd_on_off_output, HIGH); // Setup Sen44 sensor. From measurement to cleaning. // Calibration takes 2 minutes. Wire.begin(); sen44.begin(Wire); uint16_t error; char errorMessage[256]; error = sen44.deviceReset(); if (error) { Serial.print("Error trying to execute getSerialNumber(): "); errorToString(error, errorMessage, 256); Serial.println(errorMessage); } error = sen44.startMeasurement(); if (error) { Serial.print("Error trying to execute startMeasurement(): "); errorToString(error, errorMessage, 256); Serial.println(errorMessage); } sen44.startFanCleaning(); delay(120000); } void loop(void) { // "Parallel" execution for button to switch modes. if (millis() - parallel.modes_btn >= 150) { parallel.modes_btn = millis(); if (digitalRead(mode_btn) == LOW) { cycle_screens(); update_screen(); } } // "Parallel" execution for button to turn on/off display. if (millis() - parallel.on_off_btn >= 150) { parallel.on_off_btn = millis(); if (digitalRead(lcd_on_off_btn) == LOW) { if (digitalRead(lcd_on_off_output) == LOW) digitalWrite(lcd_on_off_output, HIGH); else digitalWrite(lcd_on_off_output, LOW); } } // "Parallel" execution for updating display. if (millis() - parallel.update_lcd >= 2000) { parallel.update_lcd = millis(); update_screen(); } // "Parallel" execution for reading measurements from Sen44 sensor. if (millis() - parallel.sen44 >= 1000) { parallel.sen44 = millis(); uint16_t error; char errorMessage[256]; error = sen44.readMeasuredMassConcentrationAndAmbientValues( sen44_mes.pm1p0, sen44_mes.pm2p5, sen44_mes.pm4p0, sen44_mes.pm10p0, sen44_mes.voc, sen44_mes.hum, sen44_mes.temp ); if (error) { Serial.print("Error trying to execute " "readMeasuredMassConcentrationAndAmbientValues(): "); errorToString(error, errorMessage, 256); Serial.println(errorMessage); return; } sen44_mes.temp = celsius_to_fahrenheit(sen44_mes.temp); sen44_mes.f_temp = heat_index(sen44_mes.temp, sen44_mes.hum); } } // Force write data to screen. void update_screen(void) { col = row = 0; lcd.clear(); for (int f = 0; f < VALUES_COUNT; f++) { lcd.setCursor(col, row); switch (values[current_screen][f]) { case pm_1p0: lcd.print("1: "); lcd.print(sen44_mes.pm1p0); break; case pm2p5: lcd.print("2: "); lcd.print(sen44_mes.pm2p5); break; case pm4p0: lcd.print("4: "); lcd.print(sen44_mes.pm4p0); break; case pm10p0: lcd.print("10: "); lcd.print(sen44_mes.pm10p0); break; case voc: lcd.print("v: "); lcd.print(round(sen44_mes.voc)); break; case hum: lcd.print("h: "); lcd.print(round(sen44_mes.hum)); break; case temp: lcd.print("t: "); lcd.print(round(sen44_mes.temp)); break; case f_temp: lcd.print("f: "); lcd.print(round(sen44_mes.f_temp)); break; case empty: break; } // Evenly space all items on the LCD screen. int spacing = LCD_WIDTH / (int)ceil(VALUES_COUNT / 2.0f); col += spacing; if (col > LCD_WIDTH - spacing) { col = 0; row += 1; if (row > LCD_HEIGHT) row = LCD_HEIGHT; } } } // Cycle between screens. void cycle_screens(void) { current_screen += 1; if (current_screen >= SCREENS_COUNT) current_screen = 0; } // Convert Celcius to Fahrenheit. float celsius_to_fahrenheit(float degrees) { return degrees * 1.8 + 32; } // Calculate heat index (aka "feels like") termperature. float heat_index(float t, float rh) { float hi = 0.0; if (t <= 40.0) hi = t; else { float hitemp = 61.0 + ((t - 68.0) * 1.2) + (rh * 0.094); float hifinal = 0.5 * (t + hitemp); hi = -42.379 + 2.04901523 * t + 10.14333127 * rh - 0.22475541 * t * rh - 6.83783 * (pow(10, -3)) * (pow(t, 2)) - 5.481717 * (pow(10, -2)) * (pow(rh, 2)) + 1.22874 * (pow(10, -3)) * (pow(t, 2)) * rh + 8.5282 * (pow(10, -4)) * t * (pow(rh, 2)) - 1.99 * (pow(10, -6)) * (pow(t, 2)) * (pow(rh, 2)); if (hifinal > 79.0) { if ((rh <= 13) && (t >= 80.0) && (t <= 112.0)) hi = hi - ((13.0 - rh) / 4.0) * sqrt((17.0 - fabs(t - 95.0)) / 17.0); else if ((rh > 85.0) && (t >= 80.0) && (t <= 87.0)) hi = hi + ((rh - 85.0) / 10.0) * ((87.0 - t) / 5.0); } else hi = hifinal; } return hi; }