#include <Wire.h>
#include <Adafruit_BME280.h>
#include <TFT_eSPI.h>
#include <SPI.h>
#include <SPIMemory.h>
#include <math.h>
// SPI dla TFT
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST 4
// SPI dla W25Q128 (pamięć)
#define FLASH_CS 5
#define FLASH_SCK 14
#define FLASH_MISO 12
#define FLASH_MOSI 13
// Przycisk (GPIO 25, zwiera do masy)
#define BUTTON_PIN 25
// Ilość linii siatki poziomej
#define GRID_LINES 10
// Czas co ile jest zapis do pamięci Flash
#define TIME_TO_SAVE 6000
// czas co ile odswieżane są dane na wyświetlaczu
#define TIME_TO_REFRESH_DISPLAY 1000
// Ilość wyświetlanych próbek na wykresie
#define NUM_SAMPLES 100
SPIClass hspi(HSPI);
SPIFlash flash(FLASH_CS, &hspi);
Adafruit_BME280 bme;
TFT_eSPI tft = TFT_eSPI();
// Struktura logowanych danych
struct DataLog {
uint32_t timestamp;
float temperature;
float humidity;
float pressure;
};
enum ScreenState {
SCREEN_DATA, // 0 - Aktualne dane
SCREEN_TEMP, // 1 - Wykres temperatury
SCREEN_HUMIDITY, // 2 - Wykres wilgotności
SCREEN_PRESSURE // 3 - Wykres ciśnienia
};
ScreenState currentState = SCREEN_DATA;
uint32_t flashAddress = 0;
bool showGraph = false;
bool graphDrawn = false; // Blokowanie ponownego rysowania wykresu
static float lastTemp = -1000, lastHumidity = -1000, lastPressure = -1000;
static uint32_t lastSaveTimeFlash = 0;
static uint32_t lastSaveTimeDisplay = 0;
void findLastFlashAddress() {
Serial.println("Sprawdzanie pamięci...");
for (uint32_t addr = 0; addr < flash.getCapacity(); addr += sizeof(DataLog)) {
DataLog log;
flash.readByteArray(addr, (uint8_t*)&log, sizeof(DataLog));
if (log.timestamp == 0xFFFFFFFF) {
flashAddress = addr;
Serial.print("Nowe dane zapiszemy pod adresem: ");
Serial.println(flashAddress);
return;
}
}
Serial.println("Pamięć pełna! Nadpisywanie od początku.");
flashAddress = 0;
}
void WriteToFlash() {
lastSaveTimeFlash = millis();
DataLog log;
log.timestamp = millis() / 1000;
log.temperature = bme.readTemperature();
log.humidity = bme.readHumidity();
log.pressure = bme.readPressure() / 100.0F;
flash.writeByteArray(flashAddress, (uint8_t*)&log, sizeof(DataLog));
flashAddress += sizeof(DataLog);
// Serial.println("Zapisano pomiar do pamięci."); DEBUG
}
void displaySensorData() {
float temperature = bme.readTemperature();
float humidity = bme.readHumidity();
float pressure = bme.readPressure() / 100.0F;
temperature = floorf(temperature * 10) / 10;
humidity = floorf(humidity * 10) / 10;
pressure = floorf(pressure * 10) / 10;
tft.setTextSize(2);
// Temperatura + dynamiczny kolor tekstu
if (temperature != lastTemp) {
tft.fillRect(70, 0, 160, 20, TFT_BLACK);
tft.setCursor(0, 0);
// Zimno - niebieski
if (temperature < 10)
tft.setTextColor(TFT_CYAN, TFT_BLACK);
// Gorąco - czerwony
else if (temperature > 30)
tft.setTextColor(TFT_RED, TFT_BLACK);
// W normie - biały
else
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.print("Temp: ");
tft.print(temperature, 1); tft.println(" C");
lastTemp = temperature;
}
// Wilgotność + dynamiczny kolor tekstu
if (humidity != lastHumidity) {
tft.fillRect(70, 30, 160, 20, TFT_BLACK);
tft.setCursor(0, 30);
// Sucho - pomarańczowy
if (humidity < 30)
tft.setTextColor(TFT_ORANGE, TFT_BLACK);
// Wilgotno - niebieski
else if (humidity > 70)
tft.setTextColor(TFT_BLUE, TFT_BLACK);
// W normie - biały
else
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.print("Wilg: "); tft.print(humidity, 1); tft.println(" %");
lastHumidity = humidity;
}
// Ciśnienie + dynamiczny kolor tekstu
if (pressure != lastPressure) {
tft.fillRect(70, 60, 160, 20, TFT_BLACK);
tft.setCursor(0, 60);
// Niskie ciśnienie - żółty
if (pressure < 980)
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
// Wysokie ciśnienie - zielony
else if (pressure > 1030)
tft.setTextColor(TFT_GREEN, TFT_BLACK);
// W normie - biały
else
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.print("Cisn: "); tft.print(pressure, 1); tft.println(" hPa");
lastPressure = pressure;
}
}
void ChangeScreen() {
tft.fillScreen(TFT_BLACK);
lastTemp = -1000;
lastHumidity = -1000;
lastPressure = -1000;
// Reset flagi, aby wykres narysował się raz
graphDrawn = false;
}
void drawGraph(const char* title, uint16_t color, float (*getValue)(DataLog)) {
tft.fillRect(10, 25, 300, 180, TFT_BLACK);
tft.setTextColor(TFT_WHITE);
tft.setCursor(100, 5);
tft.setTextSize(2);
tft.print(title);
int sampleCount = flashAddress / sizeof(DataLog);
if (sampleCount > NUM_SAMPLES) sampleCount = NUM_SAMPLES;
if (sampleCount < 2) {
tft.setCursor(50, 100);
tft.setTextSize(2);
tft.print("Brak danych!");
return;
}
int startAddress = flashAddress - sampleCount * sizeof(DataLog);
if (startAddress < 0)
startAddress = 0;
float minVal = 100000, maxVal = -100000;
float values[NUM_SAMPLES];
for (int i = 0; i < sampleCount; i++) {
DataLog log;
flash.readByteArray(startAddress + i * sizeof(DataLog), (uint8_t*)&log, sizeof(DataLog));
values[i] = getValue(log);
if (values[i] < minVal)
minVal = values[i];
if (values[i] > maxVal)
maxVal = values[i];
}
if (maxVal - minVal < 0.5) {
maxVal += 0.25;
minVal -= 0.25;
}
float valueRange = maxVal - minVal;
// Przesunięcie wykresu w prawo i w dół
int graphStartX = 45; // Start X (przesunięcie w prawo)
int graphEndX = 310; // Koniec wykresu
int graphStartY = 25; // **Przesunięcie w dół o 5px**
int graphEndY = 205; // Koniec osi Y
// Rysowanie osi X i Y (przesuniętych w dół)
tft.drawLine(graphStartX, graphStartY, graphStartX, graphEndY, TFT_WHITE);
tft.drawLine(graphStartX, graphEndY, graphEndX, graphEndY, TFT_WHITE);
// Rysowanie siatki Y + wartości (przesunięte o 5px w dół)
for (int i = 0; i <= GRID_LINES; i++) {
int y = map(i, 0, GRID_LINES, graphEndY, graphStartY);
tft.drawLine(graphStartX, y, graphEndX, y, TFT_DARKGREY);
float value = minVal + ((maxVal - minVal) / GRID_LINES) * i;
tft.setCursor(5, y - 5);
tft.setTextSize(1);
tft.print(value, 2);
}
// Etykiety osi X (przesunięte w dół)
tft.setCursor(graphStartX, 215);
tft.print("0");
tft.setCursor((graphStartX + graphEndX) / 2, 215);
tft.print(sampleCount / 2);
tft.setCursor(graphEndX - 30, 215);
tft.print(sampleCount);
// Rysowanie wykresu przesuniętego w prawo i w dół
for (int i = 0; i < sampleCount - 1; i++) {
int x1 = map(i, 0, sampleCount - 1, graphStartX, graphEndX);
int y1 = map(values[i] * 100, minVal * 100, maxVal * 100, graphEndY, graphStartY);
int x2 = map(i + 1, 0, sampleCount - 1, graphStartX, graphEndX);
int y2 = map(values[i + 1] * 100, minVal * 100, maxVal * 100, graphEndY, graphStartY);
tft.drawLine(x1, y1, x2, y2, color);
}
// Opis min/max wartości (przesunięte w dół)
tft.setCursor(50, 230);
tft.print("Min: "); tft.print(minVal, 2);
tft.setCursor(200, 230);
tft.print("Max: "); tft.print(maxVal, 2);
}
void drawGraphTemperature() {
drawGraph("Temperatura C", TFT_YELLOW, [](DataLog log) {
return log.temperature;
});
}
void drawGraphHumidity() {
drawGraph("Wilgotnosc %", TFT_CYAN, [](DataLog log) {
return log.humidity;
});
}
void drawGraphPressure() {
drawGraph("Cisnienie hPa", TFT_RED, [](DataLog log) {
return log.pressure;
});
}
void setup() {
Serial.begin(115200);
// Inicjalizacja SPI dla pamięci
hspi.begin(FLASH_SCK, FLASH_MISO, FLASH_MOSI, FLASH_CS);
pinMode(FLASH_CS, OUTPUT);
digitalWrite(FLASH_CS, HIGH);
// Inicjalizacja TFT
tft.init();
tft.setRotation(3);
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextSize(2);
// Inicjalizacja BME280
if (!bme.begin(0x76)) {
tft.println("BME280 ERROR!");
while (1);
}
tft.println("BME280 OK");
// Inicjalizacja pamięci W25Q128
if (!flash.begin()) {
tft.println("FLASH ERROR!");
while (1);
}
tft.println("FLASH OK");
findLastFlashAddress();
pinMode(BUTTON_PIN, INPUT_PULLUP);
delay(5000);
tft.fillScreen(TFT_BLACK);
}
void loop() {
static bool lastButtonState = HIGH;
bool buttonState = digitalRead(BUTTON_PIN);
// Zmiana stanu po naciśnięciu przycisku
if (buttonState == LOW && lastButtonState == HIGH) {
currentState = static_cast<ScreenState>((currentState + 1) % 4);
ChangeScreen(); // Reset ekranu i flagi wykresu
delay(200);
}
lastButtonState = buttonState;
// Jeśli ekran to `SCREEN_DATA`, odświeżaj dane co TIME_TO_REFRESH_DISPLAY
if (currentState == SCREEN_DATA) {
if (millis() - lastSaveTimeDisplay > TIME_TO_REFRESH_DISPLAY) {
displaySensorData();
lastSaveTimeDisplay = millis();
}
} else if (!graphDrawn) {
// Dla innych ekranów (wykresów), rysuj tylko raz po przełączeniu
switch (currentState) {
case SCREEN_TEMP:
drawGraphTemperature();
break;
case SCREEN_HUMIDITY:
drawGraphHumidity();
break;
case SCREEN_PRESSURE:
drawGraphPressure();
break;
default:
break;
}
// Zablokowanie ponownego rysowania wykresu
graphDrawn = true;
}
// Zapis do pamięci co `TIME_TO_SAVE`
if (millis() - lastSaveTimeFlash > TIME_TO_SAVE) {
WriteToFlash();
}
}Loading
ili9341-cap-touch
ili9341-cap-touch