/* * Display.cpp * * Created on: 05.01.2026 * Author: FSmilari */ #include "Display.h" #include "Adafruit_ST7735.h" #include "images/SFToolsLogo.h" #include "fonts/titillium_web_regular16pt7b.h" // Add a custom font #include "fonts/titillium_web_regular10pt7b.h" // Add a custom font #include "fonts/titillium_web_semibold10pt7b.h" // Add a custom font #include "fonts/titillium_web_regular8pt7b.h" // Add a custom font #include "fonts/titillium_web_semibold8pt7b.h" // Add a custom font #include // Add a custom font #include "images/stm32duino_logo.h" #include "images/thermometer_32.h" #include "images/gear.h" #include "images/zzz.h" #include "images/ruler.h" #include "images/air.h" #include "images/water.h" #include "images/water_s.h" //#define LED -1 // to +5v //#define SCK PA5 //#define SDA PA7 //#define MISO -1 //#define MOSI SDA #define CS PA2 #define DC PA1 #define RST PA0 #define ST7735_ARDUINOGREEN 0x04b3 #define ST7735_STM32BLUE 0x03d6 #define ST7735_DODGERBLUE 0x249f /***************** ** Constructors. ****************/ Display::Display() : tft(Adafruit_ST7735(CS, DC, RST)) { // @suppress("Abstract class cannot be instantiated") starttime = 0; configUnit = "m/s"; distance = 0; environment = 1; waterTemp = 20.0; airTemp = 20.0; refreshScreen = true; doMeasureAnimation = false; } void Display::init(void) { tft.initR(INITR_BLACKTAB); tft.fillScreen(ST7735_BLACK); tft.setRotation(1); tft.setTextWrap(false); tft.setTextColor(ST7735_WHITE); refreshScreen = true; } void Display::setRefreshScreen() { refreshScreen = true; } void Display::drawXCenteredText(String txt, const GFXfont *font, uint8_t size, uint16_t col, int16_t y) { int16_t x1, y1; // Top-left corner of text uint16_t w, h; // Width and height tft.setFont(font); tft.setTextSize(size); // Set text size. Goes from 0 (the smallest) to 20 (very big) tft.setTextColor(col); // Get bounds starting from (0, 0) tft.getTextBounds(txt, 0, 0, &x1, &y1, &w, &h); // Calculate centered position int16_t centered_x = (SCREEN_WIDTH - w) / 2 - 3; // Set cursor and print tft.setCursor(centered_x, y); tft.print(txt); } void Display::drawRightAlignedText(String txt, const GFXfont *font, uint8_t size, uint16_t col, int16_t xOffset, int16_t y) { int16_t x1, y1; // Top-left corner of text uint16_t w, h; // Width and height tft.setFont(font); tft.setTextSize(size); // Set text size. Goes from 0 (the smallest) to 20 (very big) tft.setTextColor(col); // Get bounds starting from (0, 0) tft.getTextBounds(txt, 0, 0, &x1, &y1, &w, &h); // Calculate position int16_t rightAligned_x = (SCREEN_WIDTH - xOffset) - w; // Set cursor and print tft.setCursor(rightAligned_x, y); tft.print(txt); } void Display::drawText(String txt, const GFXfont *font, uint8_t size, uint16_t col, int16_t x, int16_t y) { tft.setFont(font); tft.setTextSize(size); // Set text size. Goes from 0 (the smallest) to 20 (very big) tft.setTextColor(col); tft.setCursor(x, y); tft.print(txt); } void Display::drawCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color) { tft.drawCircle(x0, y0, r, color); } void Display::clearDisplay(void) { tft.fillScreen(ST7735_BLACK); } void Display::showBrand(int16_t x, int16_t y) { drawBitmap(x, y, 128, 34, SFTools_Logo, ST7735_ORANGE); } void Display::showInitialization(String version) { redrawFrame(); drawXCenteredText("EchoLoT", &titillium_web_regular16pt7b, 1, ST7735_CYAN, 36); drawXCenteredText("designed by", &FreeSans9pt7b, 1, ST7735_WHITE, 64); showBrand(16, 84); delay(2000); redrawFrame(); drawText("powered by", &FreeSans9pt7b, 1, ST7735_CYAN, 35, 20); tft.fillRoundRect(46, 32, 68, 68, 7, ST7735_STM32BLUE); drawBitmap(48, 34, 64, 64, epd_bitmap_stm32duino_logo_icon, ST7735_WHITE); drawXCenteredText("Ver: " + version, &titillium_web_regular8pt7b, 1, ST7735_CYAN, 118); delay(5000); } void Display::display(void) { if (!refreshScreen) { return; } refreshScreen = false; } void Display::showFrame(Status status) { switch (status) { case Status::INITIALIZATION: redrawFrame(); break; case Status::IDLE: if (refreshScreen) { redrawIdleStatus(); refreshScreen = false; } break; case Status::CONFIGURATION: if (refreshScreen) { redrawConfigStatus(); refreshScreen = false; } break; default: break; } } void Display::setStatusValues(float airTemp, float waterTemp, float sonicSpeedAir, uint8_t saltPromilleWater) { this->airTemp = airTemp; this->waterTemp = waterTemp; this->sonicSpeedAir = sonicSpeedAir; this->saltPromilleWater = saltPromilleWater; } void Display::setEnvironment(int environment) { if (this->environment != environment) { this->environment = environment; redrawEnvIcon(); redrawTempIcon(); } } void Display::setDistance(uint32_t distance) { if (this->distance != distance) { this->distance = distance; redrawDistance(); } } void Display::setConfigOption(String configOption) { if (!this->configOption.equals(configOption)) { this->configOption = configOption; redrawConfigOption(); } } void Display::setConfigText(String configText) { if (!this->configValue.equals(configText)) { this->configValue = configText; redrawConfigValue(); } } void Display::setConfigUnit(String configUnit) { if (!this->configUnit.equals(configUnit)) { this->configUnit = configUnit; redrawConfigUnit(); } } /* ===== Private methods ===== */ void Display::drawBitmap(int x, int y, int w, int h, const uint8_t bitmap[], uint16_t col) { tft.drawBitmap(x, y, bitmap, w, h, col); } void Display::drawRGBBitmap(int x, int y, int w, int h, const uint16_t bitmap[]) { tft.drawRGBBitmap(x, y, bitmap, w, h); } void Display::redrawFrame() { clearDisplay(); tft.drawRoundRect(0, 0, 160, 128, 9, ST7735_CYAN); tft.drawRoundRect(1, 1, 158, 126, 8, ST7735_CYAN); } void Display::redrawGrid(bool cfg) { redrawFrame(); tft.drawLine(0, 31, 160, 31, ST7735_CYAN); tft.drawLine(0, 32, 160, 32, ST7735_CYAN); tft.drawLine(0, 89, 160, 89, ST7735_CYAN); tft.drawLine(0, 90, 160, 90, ST7735_CYAN); tft.drawLine(cfg ? 120 : 80, 90, cfg ? 120 : 80, 128, ST7735_CYAN); tft.drawLine(cfg ? 121 : 81, 90, cfg ? 121 : 81, 128, ST7735_CYAN); } void Display::redrawIdleStatus() { redrawGrid(); tft.drawBitmap(25, 4, epd_bitmap_Zzz_24, 24, 24, ST7735_CYAN); drawText("OPERATIV", &titillium_web_semibold8pt7b, 1, ST7735_WHITE, 55, 21); // float temp = environment == 1 ? airTemp : waterTemp; // uint16_t col = temp <= 17.0 ? ST7735_WHITE : temp <= 24.0 ? ST7735_GREEN : ST7735_ORANGE; // uint16_t thH = temp <= 17.0 ? 0 : temp <= 24.0 ? 5 : 9; // tft.drawBitmap(0, 92, thermometer_icon_32, 32, 32, col); // tft.drawRect(15, 107 - thH, 2, thH, col); // drawRightAlignedText(String(temp, 1), &titillium_web_semibold8pt7b, 1, col, 96, 114); // drawCircle(72, 104, 2, col); redrawTempIcon(); redrawEnvIcon(); redrawDistance(); } void Display::redrawConfigStatus() { redrawGrid(true); tft.drawBitmap(35, 4, epd_bitmap_gear, 24, 24, ST7735_YELLOW); drawText("KONFIG.", &titillium_web_semibold8pt7b, 1, ST7735_YELLOW, 65, 21); redrawConfigValue(); redrawConfigOption(); redrawConfigUnit(); } void Display::redrawEnvIcon() { const uint8_t *environmentIcon = environment == 1 ? epd_bitmap_air : environment == 2 ? epd_bitmap_water : epd_bitmap_water_s; uint16_t col = environment == 1 ? ST7735_WHITE : ST7735_DODGERBLUE; tft.fillRect(83, 92, 72, 33, ST7735_BLACK); tft.drawBitmap(108, 97, environmentIcon, 24, 24, col); } void Display::redrawTempIcon() { float temp = environment == 1 ? airTemp : waterTemp; uint16_t col = temp <= 17.0 ? ST7735_WHITE : temp <= 24.0 ? ST7735_GREEN : ST7735_ORANGE; uint16_t thH = temp <= 17.0 ? 0 : temp <= 24.0 ? 5 : 9; tft.fillRect(4, 92, 75, 30, ST7735_BLACK); tft.drawBitmap(0, 92, thermometer_icon_32, 32, 32, col); tft.drawRect(15, 107 - thH, 2, thH, col); drawRightAlignedText(String(temp, 1), &titillium_web_semibold8pt7b, 1, col, 96, 114); drawCircle(72, 104, 2, col); } void Display::runMeasureAnimation(bool run) { if (!this->doMeasureAnimation && run) { this->starttime = millis(); this->doMeasureAnimation = run; } else if (this->doMeasureAnimation && !run) { this->starttime = 0; this->doMeasureAnimation = run; } tft.fillRect(24, 3, 28, 28, ST7735_BLACK); if (this->doMeasureAnimation && starttime != 0) { uint8_t lvl = ((millis() - starttime) / 1000) % 4; tft.drawBitmap(25, 4, epd_bitmap_Measure_25, 25, 25, ST7735_RED); switch (lvl) { case 0: tft.fillTriangle(25, 4, 30, 9, 25, 13, ST7735_RED); tft.fillTriangle(32, 4, 37, 9, 32, 13, ST7735_BLACK); tft.fillTriangle(39, 4, 44, 9, 39, 13, ST7735_BLACK); tft.fillTriangle(46, 4, 51, 9, 46, 13, ST7735_BLACK); break; case 1: tft.fillTriangle(25, 4, 30, 9, 25, 13, ST7735_BLACK); tft.fillTriangle(32, 4, 37, 9, 32, 13, ST7735_RED); tft.fillTriangle(39, 4, 44, 9, 39, 13, ST7735_BLACK); tft.fillTriangle(46, 4, 51, 9, 46, 13, ST7735_BLACK); break; case 2: tft.fillTriangle(25, 4, 30, 9, 25, 13, ST7735_BLACK); tft.fillTriangle(32, 4, 37, 9, 32, 13, ST7735_BLACK); tft.fillTriangle(39, 4, 44, 9, 39, 13, ST7735_RED); tft.fillTriangle(46, 4, 51, 9, 46, 13, ST7735_BLACK); break; case 3: tft.fillTriangle(25, 4, 30, 9, 25, 13, ST7735_BLACK); tft.fillTriangle(32, 4, 37, 9, 32, 13, ST7735_BLACK); tft.fillTriangle(39, 4, 44, 9, 39, 13, ST7735_BLACK); tft.fillTriangle(46, 4, 51, 9, 46, 13, ST7735_RED); break; default: break; } } else { tft.drawBitmap(25, 4, epd_bitmap_Zzz_24, 24, 24, ST7735_CYAN); } } void Display::redrawDistance() { tft.fillRect(4, 34, 152, 54, ST7735_BLACK); if (distance == 0) { drawXCenteredText("- - -", &titillium_web_regular16pt7b, 1, ST7735_WHITE, 69); } else { if (environment == 1) { uint16_t dist_CM = distance / 10; drawXCenteredText(String(dist_CM) + " cm", &titillium_web_regular16pt7b, 1, ST7735_WHITE, 69); } else { float dist_M = float(distance) / 1000; drawXCenteredText(String(dist_M, 1) + " m", &titillium_web_regular16pt7b, 1, ST7735_WHITE, 69); } } } void Display::redrawConfigValue() { tft.fillRect(4, 34, 152, 54, ST7735_BLACK); drawXCenteredText(configValue, &titillium_web_regular16pt7b, 1, ST7735_ORANGE, 69); } void Display::redrawConfigOption() { tft.fillRect(4, 92, 116, 30, ST7735_BLACK); drawText(configOption, &titillium_web_semibold8pt7b, 1, ST7735_WHITE, 6, 113); } void Display::redrawConfigUnit() { int16_t x1, y1; // Top-left corner of text uint16_t w, h; // Width and height tft.getTextBounds(configUnit, 0, 0, &x1, &y1, &w, &h); tft.fillRect(123, 92, 35, 30, ST7735_BLACK); drawRightAlignedText(configUnit, &titillium_web_semibold8pt7b, 1, ST7735_WHITE, (37 - w) / 2 + 2, 113); if (configUnit.equals(" C")) { drawCircle(121 + ((37 - w) / 2), 105, 2, ST7735_WHITE); } else if (configUnit.equals(" / ")) { drawCircle(124 + ((37 - w) / 2), 105, 2, ST7735_WHITE); drawCircle(124 + ((37 - w) / 2) + 10, 110, 2, ST7735_WHITE); drawCircle(124 + ((37 - w) / 2) + 15, 110, 2, ST7735_WHITE); } }