Table of Contents

Afisaje multiple LCD

Autor: Mustatea Radu-Ioan

Grupa: 333CA

Introducere

Descriere generală

  1. Schema bloc

Hardware Design

Componente:

Software Design

Cod sursa

Cod sursa

#include "Ucglib.h"
#include <Adafruit_BME280.h>
#include "RTClib.h"
#include <SoftwareSerial.h>

#define SPI_SCK 13
#define SPI_MOSI 11
#define SPI_CS 10
#define SPI_CD 9
#define SPI_RES 8

/* Latimea fontului folosit pentru afisarile pe ecran
 * (difera in functie de font si e fixa doar pentru
 * fonturile monospace)
 */
#define FONT_WIDTH 12

/* Adresa senzorului BME280 (trebuie mentionata explicit
 * in constructorul begin, altfel senzorul nu va functiona
 */
#define BME_ADDRESS 0X76

/*
 * Culorile pentru fundalul, respectiv scrisul ecranului
 */
#define BACKGROUND_RED 0
#define BACKGROUND_GREEN 0
#define BACKGROUND_BLUE 0

#define WRITE_RED 255
#define WRITE_GREEN 255
#define WRITE_BLUE 255

/*
 * Valoare folosita pentru a plasa afisarile pe ecran
 * (nu vrem sa afisam direct in colturi din motive de estetica)
 */
#define OFFSET 20

#define AIR_PIN 0

/*
 * Valori pentru plasarea datelor preluate de la senzori
 * pe ecran
 */
#define TEMP_X 20
#define BAR_X 40
#define HUM_X 60
#define AIR_X 80

#define TX_PIN 3
#define RX_PIN 2

/*
 * Durata de timp intre doua transmii prin Bluetooth
 */
#define BLUE_TIME 5000

/*
 * Pinii la care sunt conectati butoanele
 */
#define MODE_BUTTON 4
#define INC_BUTTON 6
#define DEC_BUTTON 5

/*
 * Pentru cate ore retinem date preluate de la senzori
 */
#define HIST_COUNT 40

/*
 * Starile prin care poate cicla afisarea pe ecran
 */
#define NORMAL_STATE 0
#define MAX_STATE 1
#define MIN_STATE 2
#define AVG_STATE 3

Ucglib_ILI9341_18x240x320_HWSPI ucg(SPI_CD, SPI_CS, SPI_RES);

Adafruit_BME280 bme;

String oldTemp = "", oldHum = "", oldBar = "", oldAir = "";

RTC_DS3231 rtc;

SoftwareSerial Blue(TX_PIN, RX_PIN);
unsigned long transmissionTime; /* Variabila folosita pentru a determina cand sa trimitem date prin Bluetooth */

float temp[HIST_COUNT], bar[HIST_COUNT], hum[HIST_COUNT];
int air[HIST_COUNT];

/*
 * Determina daca suntem la momentul in care sa scriem in istoric
 */
bool writeToHistory = false;

/*
 * Ne spune cate scrieri avem in istoric
 */
short int writeCount = 0;
short int state = NORMAL_STATE, oldState = NORMAL_STATE;

/*
 * Stari pentru butoane plus pentru cate ore se va face afisarea pe ecran in cazul in care nu ne aflam in
 * starea uzuala
 */
bool oldModeState = HIGH, oldIncState = HIGH, oldDecState = HIGH;
short int hourCount = 1;

void setup() {
  Serial.begin(9600);
  Blue.begin(9600);

  pinMode(MODE_BUTTON, INPUT_PULLUP);
  pinMode(INC_BUTTON, INPUT_PULLUP);
  pinMode(DEC_BUTTON, INPUT_PULLUP);

  rtc.begin();
  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

  bme.begin(BME_ADDRESS);

  ucg.begin(UCG_FONT_MODE_SOLID);
  ucg.clearScreen();

  /*
   * Alegerea fontului s-a facut in primul rand monospace (m-ul din nume) pentru ca orice caracter sa aiba
   * aceeasi latime pe ecran. Acest lucru ne permite ca atunci cand modificam un sir pe ecran sa putem sterge
   * si rescrie doar caracterele care s-au schimbat, deoarece stim sigur pozitia la care se afla
   * 
   * Afisarea e rotita pentru a se afisa pe orizontala
   */
  ucg.setFont(ucg_font_courB14_mf);
  ucg.setRotate90();

  transmissionTime = millis();
}

/*
 * Functie de actualizare a afisarii pe ecran in cazul starii uzuale. Stiind care este
 * latimea fontului, ne putem pozitiona acolo unde caracterele s-au modificat
 * Avem 3 cazuri:
 * - fie avem de schimbat o litera din sirul vechi cu una din sirul nou
 * - fie scriem o parte din sirul nou acolo unde inainte nu aveam de scris nimic
 * - fie trebuie sa stergem partea din sirul vechi aflata dincolo de sirul nou
 * 
 * De asemenea, aici verificam si daca e momentul sa trimitem date prin Bluetooth
 */
void updateString(String& oldString, String& newString, unsigned char y, bool printBlue) {
  char i, oldLength = oldString.length(), newLength = newString.length();

  if (state == NORMAL_STATE) {
    for (i = 0; i < oldLength && i < newLength; ++i) {
      if (oldString[i] != newString[i]) {
          ucg.setPrintPos(OFFSET + FONT_WIDTH * i, y);
          ucg.setColor(BACKGROUND_RED, BACKGROUND_GREEN, BACKGROUND_BLUE);
          ucg.print(oldString[i]);
          ucg.setPrintPos(OFFSET + FONT_WIDTH * i, y);
          ucg.setColor(WRITE_RED, WRITE_GREEN, WRITE_BLUE);
          ucg.print(newString[i]);
      }
    }
  
    ucg.setPrintPos(OFFSET + FONT_WIDTH * oldLength, y);
    ucg.setColor(WRITE_RED, WRITE_GREEN, WRITE_BLUE);
    if (newLength > oldLength) {
      ucg.print(newString.substring(oldLength));
    }
  
    ucg.setPrintPos(OFFSET + FONT_WIDTH * newLength, y);
    ucg.setColor(BACKGROUND_RED, BACKGROUND_GREEN, BACKGROUND_BLUE);
    if (oldLength > newLength) {
      ucg.print(oldString.substring(newLength));
    }
  }

  if (printBlue) {
    Blue.println(newString);
  }

  oldString = state == NORMAL_STATE ? newString : "";
}

/*
 * Functie pentru calcularea dupa caz a valorilor maxime, minime sau medii din istoric
 */
void getRezValues(float &rezTemp, float &rezBar, float &rezHum, int &rezAir) {
  char i;

  ucg.setPrintPos(OFFSET, OFFSET);

  if (state == MAX_STATE) {
    ucg.print((String)"Max Values " + (String)hourCount + (String)" hours");
    rezTemp = -85.0;
    rezBar = 0.0;
    rezHum = 0.0;
    rezAir = 0;
    for (i = writeCount - 1; i >= writeCount - hourCount; --i) {
      rezTemp = temp[i] > rezTemp ? temp[i] : rezTemp;
      rezBar = bar[i] > rezBar ? bar[i] : rezBar;
      rezHum = hum[i] > rezHum ? hum[i] : rezHum;
      rezAir = air[i] > rezAir ? air[i] : rezAir;
    }
  }

  if (state == MIN_STATE) {
    ucg.print((String)"Min Values " + (String)hourCount + (String)" hours");
    rezTemp = 45.0;
    rezBar = 200000.0;
    rezHum = 200.0;
    rezAir = 1000;
    for (i = writeCount - 1; i >= writeCount - hourCount; --i) {
      rezTemp = temp[i] < rezTemp ? temp[i] : rezTemp;
      rezBar = bar[i] < rezBar ? bar[i] : rezBar;
      rezHum = hum[i] < rezHum ? hum[i] : rezHum;
      rezAir = air[i] < rezAir ? air[i] : rezAir;
    }
  }

  if (state == AVG_STATE) {
    ucg.print((String)"Avg Values " + (String)hourCount + (String)" hours");
    rezTemp = 0.0;
    rezBar = 0.0;
    rezHum = 0.0;
    rezAir = 0;
    for (i = writeCount - 1; i >= writeCount - hourCount; --i) {
      rezTemp += temp[i];
      rezBar += bar[i];
      rezHum += hum[i];
      rezAir += air[i];
    }
    rezTemp /= (float)hourCount;
    rezBar /= (float)hourCount;
    rezHum /= (float)hourCount;
    rezAir /= hourCount;
  }
}

/*
 * Aici actualizam scrisul de pe ecran daca ne aflam in starea uzuala sau preluam valorile dorite din istoric
 * daca este necesar (nu ne aflam in starea uzuala), De asemenea, tot aici scriem si noi valori in istoric
 * daca e cazul
 */
void updateData(bool printBlue, bool writeToHistory) {
  float bmeTemp = bme.readTemperature(), bmeBar = bme.readPressure(), bmeHum = bme.readHumidity();
  int airData = analogRead(AIR_PIN);
  float rezTemp, rezBar, rezHum;
  int rezAir;

  updateString(oldTemp, (String)"Temp = " + bmeTemp + (String)" *C", TEMP_X, printBlue);
  updateString(oldBar, (String)"Pressure = " + bmeBar / 100.0F + (String)" hPa", BAR_X, printBlue);
  updateString(oldHum, (String)"Humidity = " + bmeHum + (String)" %", HUM_X, printBlue);
  updateString(oldAir, (String)"Air PPM = " + airData + (String)" PPM", AIR_X, printBlue);

  if (state != NORMAL_STATE) {
    getRezValues(rezTemp, rezBar, rezHum, rezAir);
    ucg.setPrintPos(OFFSET, TEMP_X + OFFSET);
    ucg.print((String)"Temp = " + (String)rezTemp + (String)" *C");
    ucg.setPrintPos(OFFSET, BAR_X + OFFSET);
    ucg.print((String)"Pressure = " + rezBar / 100.0F + (String)" hPa");
    ucg.setPrintPos(OFFSET, HUM_X + OFFSET);
    ucg.print((String)"Humidity = " + rezHum + (String)" %");
    ucg.setPrintPos(OFFSET, AIR_X + OFFSET);
    ucg.print((String)"Air PPM = " + rezAir + (String)" PPM");
  }

  if (writeToHistory) {
    temp[writeCount - 1] = bmeTemp;
    bar[writeCount - 1] = bmeBar;
    hum[writeCount - 1] = bmeHum;
    air[writeCount - 1] = airData;
  }
}

void loop() {
  bool printBlue = abs(millis() - transmissionTime) >= BLUE_TIME;
  DateTime now = rtc.now();
  char i, rtcMin = now.minute();
  bool currentModeState = digitalRead(MODE_BUTTON), currentIncState = digitalRead(INC_BUTTON), currentDecState = digitalRead(DEC_BUTTON);

  /*
   * Daca nu am scris nimic in istoric, nu are rost sa verificam daca butoanele sunt apasate
   */
  if (writeCount) {
    if (currentModeState == LOW && oldModeState == HIGH) {
      if (state == NORMAL_STATE) {
        state = MAX_STATE;
      } else {
        if (state == MAX_STATE) {
          state = MIN_STATE;
        } else {
          if (state == MIN_STATE) {
            state = AVG_STATE;
          } else {
            state = NORMAL_STATE;
          }
        }
      }
      ucg.clearScreen();
    } else {
      if (currentIncState == LOW && oldIncState == HIGH && state != NORMAL_STATE) {
        hourCount += (hourCount < writeCount);
        ucg.clearScreen();
      } else {
        if (currentDecState == LOW && oldDecState == HIGH && state != NORMAL_STATE) {
          hourCount -= (hourCount > 1);
          ucg.clearScreen();
        }
      }
    }

    oldModeState = currentModeState;
    oldIncState = currentIncState;
    oldDecState = currentDecState;

    oldState = state;
  }

  /*
   * Conditia echivalenta pentru salvarea a noi date in istoric
   */
  if (!rtcMin && !writeToHistory) {
    if (writeCount >= HIST_COUNT) {
      for (i = 0; i < HIST_COUNT - 1; ++i) {
        temp[i] = temp[i + 1];
        bar[i] = bar[i + 1];
        hum[i] = hum[i + 1];
        air[i] = air[i + 1];
      }
    }
    writeCount += (writeCount < HIST_COUNT);
    if (state != NORMAL_STATE) {
      ucg.clearScreen();
    }
  }
  updateData(printBlue, !rtcMin && !writeToHistory);

  writeToHistory = !rtcMin;

  transmissionTime = printBlue ? millis() : transmissionTime;

  if (printBlue) {
    Blue.println();
  }
}

Rezultate Obţinute

Demo ecran fizic
Demo Bluetooth

Concluzii

Download

333ca_radu-ioanmustatea_pm_proiect.zip

Bibliografie/Resurse

https://www.electronicshub.org/arduino-ds3231-rtc-module-tutorial/
https://randomnerdtutorials.com/bme280-sensor-arduino-pressure-temperature-humidity/
https://create.arduino.cc/projecthub/m_karim02/arduino-and-mq-135-gas-sensor-with-arduino-code-a8c1c6
https://www.instructables.com/Setting-Up-Bluetooth-HC-05-With-Arduino/
https://www.youtube.com/watch?v=4DtuOeeYHys
https://github.com/olikraus/ucglib/blob/master/sys/arduino/HelloWorld/HelloWorld.ino
https://github.com/olikraus/ucglib/wiki/reference
https://github.com/olikraus/ucglib/wiki/fontsize