aboutsummaryrefslogtreecommitdiff
path: root/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp
diff options
context:
space:
mode:
authorGeorgiy Bondarenko <69736697+nehilo@users.noreply.github.com>2021-03-04 20:54:23 +0300
committerGeorgiy Bondarenko <69736697+nehilo@users.noreply.github.com>2021-03-04 20:54:23 +0300
commite8701195e66f2d27ffe17fb514eae8173795aaf7 (patch)
tree9f519c4abf6556b9ae7190a6210d87ead1dfadde /Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp
downloadkp3s-lgvl-e8701195e66f2d27ffe17fb514eae8173795aaf7.tar.xz
kp3s-lgvl-e8701195e66f2d27ffe17fb514eae8173795aaf7.zip
Initial commit
Diffstat (limited to 'Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp')
-rw-r--r--Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp959
1 files changed, 959 insertions, 0 deletions
diff --git a/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp b/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp
new file mode 100644
index 0000000..44128cc
--- /dev/null
+++ b/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp
@@ -0,0 +1,959 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if IS_TFTGLCD_PANEL
+
+/**
+ * marlinui_TFTGLCD.cpp
+ *
+ * Implementation of the LCD display routines for a TFT GLCD displays with external controller.
+ * This display looks like a REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER but has good text font
+ * and supports color output.
+ */
+
+#if NONE(__AVR__, TARGET_LPC1768, STM32F1, STM32F4xx)
+ #warning "Selected platform not yet tested. Please contribute your good pin mappings."
+#endif
+
+#if ENABLED(TFTGLCD_PANEL_SPI)
+ #include <SPI.h>
+#else
+ #include <Wire.h>
+#endif
+
+#include "marlinui_TFTGLCD.h"
+#include "../marlinui.h"
+#include "../../libs/numtostr.h"
+
+#include "../../sd/cardreader.h"
+#include "../../module/temperature.h"
+#include "../../module/printcounter.h"
+#include "../../module/planner.h"
+#include "../../module/motion.h"
+
+#if DISABLED(LCD_PROGRESS_BAR) && BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT)
+ #include "../../feature/filwidth.h"
+ #include "../../gcode/parser.h"
+#endif
+
+#if ENABLED(AUTO_BED_LEVELING_UBL)
+ #include "../../feature/bedlevel/bedlevel.h"
+#endif
+
+TFTGLCD lcd;
+
+#define ICON_LOGO B00000001
+#define ICON_TEMP1 B00000010 //hotend 1
+#define ICON_TEMP2 B00000100 //hotend 2
+#define ICON_TEMP3 B00001000 //hotend 3
+#define ICON_BED B00010000
+#define ICON_FAN B00100000
+#define ICON_HOT B01000000 //when any T > 50deg
+#define PIC_MASK 0x7F
+
+// LEDs not used, for compatibility with Smoothieware
+#define LED_HOTEND_ON B00000001
+#define LED_BED_ON B00000010
+#define LED_FAN_ON B00000100
+#define LED_HOT B00001000
+#define LED_MASK 0x0F
+
+#define FBSIZE (LCD_WIDTH * LCD_HEIGHT + 2)
+#define MIDDLE_Y ((LCD_HEIGHT - 1) / 2)
+
+// Markers for change line colors
+#define COLOR_EDIT '#'
+#define COLOR_ERROR '!'
+
+#ifdef CONVERT_TO_EXT_ASCII //use standart pseudographic symbols in ASCII table
+ #define LR 179 //vertical line
+ #define TRC 191 //top right corner
+ #define BLC 192 //bottom left corner
+ #define GL 196 //horizontal line
+ #define BRC 217 //bottom right corner, should be replaced to 12 for some languages
+ #define TLC 218 //top left corner, should be replaced to 13 for some languages
+#else //next symbols must be present in panel font
+ #define LR 8 //equal to 179
+ #define TRC 9 //equal to 191
+ #define BLC 10 //equal to 192
+ #define GL 11 //equal to 196
+ #define BRC 12 //equal to 217
+ #define TLC 13 //equal to 218
+#endif
+
+#define Marlin 0x01
+
+enum Commands { // based on Smoothieware commands
+ GET_SPI_DATA = 0,
+ READ_BUTTONS, // read buttons
+ READ_ENCODER, // read encoder
+ LCD_WRITE, // write all screen to LCD
+ BUZZER, // beep buzzer
+ CONTRAST, // set contrast (brightnes)
+ // Other commands... 0xE0 thru 0xFF
+ GET_LCD_ROW = 0xE0, // for detect panel
+ GET_LCD_COL, // reserved for compatibility with Smoothieware, not used
+ LCD_PUT, // write one line to LCD
+ CLR_SCREEN,
+ INIT_SCREEN = 0xFE // clear panel buffer
+};
+
+static unsigned char framebuffer[FBSIZE];
+static unsigned char *fb;
+static uint8_t cour_line;
+static uint8_t picBits, ledBits, hotBits;
+static uint8_t PanelDetected = 0;
+
+// Different platforms use different SPI methods
+#if ANY(__AVR__, TARGET_LPC1768, __STM32F1__, ARDUINO_ARCH_SAM, __SAMD51__, __MK20DX256__, __MK64FX512__)
+ #define SPI_SEND_ONE(V) SPI.transfer(V);
+ #define SPI_SEND_TWO(V) SPI.transfer16(V);
+#elif EITHER(STM32F4xx, STM32F1xx)
+ #define SPI_SEND_ONE(V) SPI.transfer(V, SPI_CONTINUE);
+ #define SPI_SEND_TWO(V) SPI.transfer16(V, SPI_CONTINUE);
+#elif defined(ARDUINO_ARCH_ESP32)
+ #define SPI_SEND_ONE(V) SPI.write(V);
+ #define SPI_SEND_TWO(V) SPI.write16(V);
+#endif
+
+#if ANY(__AVR__, ARDUINO_ARCH_SAM, __SAMD51__, __MK20DX256__, __MK64FX512__)
+ #define SPI_SEND_SOME(V,L,Z) SPI.transfer(&V[Z], L);
+#elif EITHER(STM32F4xx, STM32F1xx)
+ #define SPI_SEND_SOME(V,L,Z) SPI.transfer(&V[Z], L, SPI_CONTINUE);
+#elif ANY(TARGET_LPC1768, __STM32F1__, ARDUINO_ARCH_ESP32)
+ #define SPI_SEND_SOME(V,L,Z) do{ for (uint16_t i = 0; i < L; i++) SPI_SEND_ONE(V[(Z)+i]); }while(0)
+#endif
+
+// Constructor
+TFTGLCD::TFTGLCD() {}
+
+// Clear local buffer
+void TFTGLCD::clear_buffer() {
+ memset(&framebuffer[0], ' ', FBSIZE - 2);
+ framebuffer[FBSIZE - 1] = framebuffer[FBSIZE - 2] = 0;
+ picBits = ledBits = 0;
+}
+
+// Clear panel's screen
+void TFTGLCD::clr_screen() {
+ if (!PanelDetected) return;
+ #if ENABLED(TFTGLCD_PANEL_SPI)
+ WRITE(TFTGLCD_CS, LOW);
+ SPI_SEND_ONE(CLR_SCREEN);
+ WRITE(TFTGLCD_CS, HIGH);
+ #else
+ Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS); //set I2C device address
+ Wire.write(CLR_SCREEN);
+ Wire.endTransmission(); //transmit data
+ #endif
+}
+
+// Set new text cursor position
+void TFTGLCD::setCursor(uint8_t col, uint8_t row) {
+ fb = &framebuffer[0] + col + row * LCD_WIDTH;
+ cour_line = row;
+}
+
+// Send char to buffer
+void TFTGLCD::write(char c) {
+ *fb++ = c;
+}
+
+// Send text line to buffer
+void TFTGLCD::print(const char *line) {
+ while (*line) *fb++ = *line++;
+}
+
+// For menu
+void TFTGLCD::print_line() {
+ if (!PanelDetected) return;
+ #if ENABLED(TFTGLCD_PANEL_SPI)
+ WRITE(TFTGLCD_CS, LOW);
+ SPI_SEND_ONE(LCD_PUT);
+ SPI_SEND_ONE(cour_line);
+ SPI_SEND_SOME(framebuffer, LCD_WIDTH, cour_line * LCD_WIDTH);
+ WRITE(TFTGLCD_CS, HIGH);
+ #else
+ Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS); //set I2C device address
+ Wire.write(LCD_PUT);
+ Wire.write(cour_line);
+ Wire.write(&framebuffer[cour_line * LCD_WIDTH], LCD_WIDTH); //transfer 1 line to txBuffer
+ Wire.endTransmission(); //transmit data
+ safe_delay(1);
+ #endif
+}
+
+void TFTGLCD::print_screen() {
+ if (!PanelDetected) return;
+ framebuffer[FBSIZE - 2] = picBits & PIC_MASK;
+ framebuffer[FBSIZE - 1] = ledBits;
+ #if ENABLED(TFTGLCD_PANEL_SPI)
+ // Send all framebuffer to panel
+ WRITE(TFTGLCD_CS, LOW);
+ SPI_SEND_ONE(LCD_WRITE);
+ SPI_SEND_SOME(framebuffer, FBSIZE, 0);
+ WRITE(TFTGLCD_CS, HIGH);
+ #else
+ uint8_t r;
+ // Send framebuffer to panel by line
+ Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
+ // First line
+ Wire.write(LCD_WRITE);
+ Wire.write(&framebuffer[0], LCD_WIDTH);
+ Wire.endTransmission();
+ for (r = 1; r < (LCD_HEIGHT - 1); r++) {
+ Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
+ Wire.write(&framebuffer[r * LCD_WIDTH], LCD_WIDTH);
+ Wire.endTransmission();
+ }
+ // Last line
+ Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
+ Wire.write(&framebuffer[r * LCD_WIDTH], LCD_WIDTH);
+ Wire.write(&framebuffer[FBSIZE - 2], 2);
+ Wire.endTransmission();
+ #endif
+}
+
+void TFTGLCD::setContrast(uint16_t contrast) {
+ if (!PanelDetected) return;
+ #if ENABLED(TFTGLCD_PANEL_SPI)
+ WRITE(TFTGLCD_CS, LOW);
+ SPI_SEND_ONE(CONTRAST);
+ SPI_SEND_ONE((uint8_t)contrast);
+ WRITE(TFTGLCD_CS, HIGH);
+ #else
+ Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
+ Wire.write(CONTRAST);
+ Wire.write((uint8_t)contrast);
+ Wire.endTransmission();
+ #endif
+}
+
+extern volatile int8_t encoderDiff;
+
+// Read buttons and encoder states
+uint8_t MarlinUI::read_slow_buttons(void) {
+ if (!PanelDetected) return 0;
+ #if ENABLED(TFTGLCD_PANEL_SPI)
+ uint8_t b = 0;
+ WRITE(TFTGLCD_CS, LOW);
+ SPI_SEND_ONE(READ_ENCODER);
+ #ifndef STM32F4xx
+ WRITE(TFTGLCD_CS, LOW); // for delay
+ #endif
+ encoderDiff += SPI_SEND_ONE(READ_BUTTONS);
+ #ifndef STM32F4xx
+ WRITE(TFTGLCD_CS, LOW); // for delay
+ WRITE(TFTGLCD_CS, LOW);
+ #endif
+ b = SPI_SEND_ONE(GET_SPI_DATA);
+ WRITE(TFTGLCD_CS, HIGH);
+ return b;
+ #else
+ Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
+ Wire.write(READ_ENCODER);
+ Wire.endTransmission();
+ #ifdef __AVR__
+ Wire.requestFrom((uint8_t)LCD_I2C_ADDRESS, 2, 0, 0, 1);
+ #elif defined(STM32F1)
+ Wire.requestFrom((uint8_t)LCD_I2C_ADDRESS, (uint8_t)2);
+ #elif EITHER(STM32F4xx, TARGET_LPC1768)
+ Wire.requestFrom(LCD_I2C_ADDRESS, 2);
+ #endif
+ encoderDiff += Wire.read();
+ return Wire.read(); //buttons
+ #endif
+}
+
+// Duration in ms, freq in Hz
+void MarlinUI::buzz(const long duration, const uint16_t freq) {
+ if (!PanelDetected) return;
+ if (!buzzer_enabled) return;
+ #if ENABLED(TFTGLCD_PANEL_SPI)
+ WRITE(TFTGLCD_CS, LOW);
+ SPI_SEND_ONE(BUZZER);
+ SPI_SEND_TWO((uint16_t)duration);
+ SPI_SEND_TWO(freq);
+ WRITE(TFTGLCD_CS, HIGH);
+ #else
+ Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
+ Wire.write(BUZZER);
+ Wire.write((uint8_t)(duration >> 8));
+ Wire.write((uint8_t)duration);
+ Wire.write((uint8_t)(freq >> 8));
+ Wire.write((uint8_t)freq);
+ Wire.endTransmission();
+ #endif
+}
+
+void MarlinUI::init_lcd() {
+ uint8_t t;
+ lcd.clear_buffer();
+ t = 0;
+ #if ENABLED(TFTGLCD_PANEL_SPI)
+ // SPI speed must be less 10MHz
+ SET_OUTPUT(TFTGLCD_CS);
+ WRITE(TFTGLCD_CS, HIGH);
+ spiInit(TERN(__STM32F1__, SPI_QUARTER_SPEED, SPI_FULL_SPEED));
+ WRITE(TFTGLCD_CS, LOW);
+ SPI_SEND_ONE(GET_LCD_ROW);
+ t = SPI_SEND_ONE(GET_SPI_DATA);
+ #else
+ #ifdef TARGET_LPC1768
+ Wire.begin(); //init twi/I2C
+ #else
+ Wire.begin((uint8_t)LCD_I2C_ADDRESS); //init twi/I2C
+ #endif
+ Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
+ Wire.write((uint8_t)GET_LCD_ROW); // put command to buffer
+ Wire.endTransmission(); // send buffer
+ #ifdef __AVR__
+ Wire.requestFrom((uint8_t)LCD_I2C_ADDRESS, 1, 0, 0, 1);
+ #elif ANY(STM32F1, STM32F4xx, TARGET_LPC1768)
+ Wire.requestFrom(LCD_I2C_ADDRESS, 1);
+ #endif
+ t = (uint8_t)Wire.read();
+ #endif
+
+ if (t == LCD_HEIGHT) {
+ PanelDetected = 1;
+ #if ENABLED(TFTGLCD_PANEL_SPI)
+ SPI_SEND_ONE(INIT_SCREEN);
+ SPI_SEND_ONE(Marlin);
+ WRITE(TFTGLCD_CS, HIGH);
+ #else
+ Wire.beginTransmission((uint8_t)LCD_I2C_ADDRESS);
+ Wire.write((uint8_t)INIT_SCREEN);
+ Wire.write(Marlin);
+ Wire.endTransmission();
+ #endif
+ }
+ else
+ PanelDetected = 0;
+ safe_delay(100);
+}
+
+bool MarlinUI::detected() {
+ return PanelDetected;
+}
+
+void MarlinUI::clear_lcd() {
+ if (!PanelDetected) return;
+ lcd.clr_screen();
+ lcd.clear_buffer();
+}
+
+int16_t MarlinUI::contrast; // Initialized by settings.load()
+
+void MarlinUI::set_contrast(const int16_t value) {
+ contrast = constrain(value, LCD_CONTRAST_MIN, LCD_CONTRAST_MAX);
+ lcd.setContrast(contrast);
+}
+
+static void center_text_P(PGM_P pstart, uint8_t y) {
+ uint8_t len = utf8_strlen_P(pstart);
+ if (len < LCD_WIDTH)
+ lcd.setCursor((LCD_WIDTH - len) / 2, y);
+ else
+ lcd.setCursor(0, y);
+ lcd_put_u8str_P(pstart);
+}
+
+#if ENABLED(SHOW_BOOTSCREEN)
+
+ void MarlinUI::show_bootscreen() {
+ if (!PanelDetected) return;
+ //
+ // Show the Marlin logo, splash line1, and splash line 2
+ //
+ uint8_t indent = (LCD_WIDTH - 8) / 2;
+ // symbols 217 (bottom right corner) and 218 (top left corner) are using for letters in some languages
+ // and they should be moved to begining ASCII table as spetial symbols
+ lcd.setCursor(indent, 0); lcd.write(TLC); lcd_put_u8str_P(PSTR("------")); lcd.write(TRC);
+ lcd.setCursor(indent, 1); lcd.write(LR); lcd_put_u8str_P(PSTR("Marlin")); lcd.write(LR);
+ lcd.setCursor(indent, 2); lcd.write(BLC); lcd_put_u8str_P(PSTR("------")); lcd.write(BRC);
+ center_text_P(PSTR(SHORT_BUILD_VERSION), 3);
+ center_text_P(PSTR(MARLIN_WEBSITE_URL), 4);
+ picBits = ICON_LOGO;
+ lcd.print_screen();
+ safe_delay(1500);
+ }
+
+#endif // SHOW_BOOTSCREEN
+
+void MarlinUI::draw_kill_screen() {
+ if (!PanelDetected) return;
+ lcd.clear_buffer();
+ lcd.setCursor(0, 3); lcd.write(COLOR_ERROR);
+ lcd.setCursor((LCD_WIDTH - utf8_strlen(status_message)) / 2 + 1, 3);
+ lcd_put_u8str(status_message);
+ center_text_P(GET_TEXT(MSG_HALTED), 5);
+ center_text_P(GET_TEXT(MSG_PLEASE_RESET), 6);
+ lcd.print_screen();
+}
+
+//
+// Before homing, blink '123' <-> '???'.
+// Homed but unknown... '123' <-> ' '.
+// Homed and known, display constantly.
+//
+FORCE_INLINE void _draw_axis_value(const AxisEnum axis, const char *value, const bool blink) {
+ lcd.write('X' + uint8_t(axis));
+ if (blink)
+ lcd.print(value);
+ else if (axis_should_home(axis))
+ while (const char c = *value++) lcd.write(c <= '.' ? c : '?');
+ else if (NONE(HOME_AFTER_DEACTIVATE, DISABLE_REDUCED_ACCURACY_WARNING) && !axis_is_trusted(axis))
+ lcd_put_u8str_P(axis == Z_AXIS ? PSTR(" ") : PSTR(" "));
+ else
+ lcd_put_u8str(value);
+}
+
+FORCE_INLINE void _draw_heater_status(const heater_id_t heater_id, const char *prefix, const bool blink) {
+ uint8_t pic_hot_bits;
+ #if HAS_HEATED_BED
+ const bool isBed = heater_id < 0;
+ const float t1 = (isBed ? thermalManager.degBed() : thermalManager.degHotend(heater_id));
+ const float t2 = (isBed ? thermalManager.degTargetBed() : thermalManager.degTargetHotend(heater_id));
+ #else
+ const float t1 = thermalManager.degHotend(heater_id);
+ const float t2 = thermalManager.degTargetHotend(heater_id);
+ #endif
+
+ #if HOTENDS < 2
+ if (heater_id == H_E0) {
+ lcd.setCursor(2, 5); lcd.print(prefix); //HE
+ lcd.setCursor(1, 6); lcd.print(i16tostr3rj(t1 + 0.5));
+ lcd.setCursor(1, 7);
+ }
+ else {
+ lcd.setCursor(6, 5); lcd.print(prefix); //BED
+ lcd.setCursor(6, 6); lcd.print(i16tostr3rj(t1 + 0.5));
+ lcd.setCursor(6, 7);
+ }
+ #else
+ if (heater_id > H_BED) {
+ lcd.setCursor(heater_id * 4, 5); lcd.print(prefix); //HE1 or HE2 or HE3
+ lcd.setCursor(heater_id * 4, 6); lcd.print(i16tostr3rj(t1 + 0.5));
+ lcd.setCursor(heater_id * 4, 7);
+ }
+ else {
+ lcd.setCursor(13, 5); lcd.print(prefix); //BED
+ lcd.setCursor(13, 6); lcd.print(i16tostr3rj(t1 + 0.5));
+ lcd.setCursor(13, 7);
+ }
+ #endif // HOTENDS <= 1
+
+ #if !HEATER_IDLE_HANDLER
+ UNUSED(blink);
+ #else
+ if (!blink && thermalManager.heater_idle[thermalManager.idle_index_for_id(heater_id)].timed_out) {
+ lcd.write(' ');
+ if (t2 >= 10) lcd.write(' ');
+ if (t2 >= 100) lcd.write(' ');
+ }
+ else
+ #endif // !HEATER_IDLE_HANDLER
+ lcd.print(i16tostr3rj(t2 + 0.5));
+
+ switch (heater_id) {
+ case H_BED: pic_hot_bits = ICON_BED; break;
+ case H_E0: pic_hot_bits = ICON_TEMP1; break;
+ case H_E1: pic_hot_bits = ICON_TEMP2; break;
+ case H_E2: pic_hot_bits = ICON_TEMP3;
+ default: break;
+ }
+
+ if (t2) picBits |= pic_hot_bits;
+ else picBits &= ~pic_hot_bits;
+
+ if (t1 > 50) hotBits |= pic_hot_bits;
+ else hotBits &= ~pic_hot_bits;
+
+ if (hotBits) picBits |= ICON_HOT;
+ else picBits &= ~ICON_HOT;
+}
+
+#if HAS_PRINT_PROGRESS
+
+ FORCE_INLINE void _draw_print_progress() {
+ if (!PanelDetected) return;
+ const uint8_t progress = ui._get_progress();
+ #if ENABLED(SDSUPPORT)
+ lcd_put_u8str_P(PSTR("SD"));
+ #elif ENABLED(LCD_SET_PROGRESS_MANUALLY)
+ lcd_put_u8str_P(PSTR("P:"));
+ #endif
+ if (progress)
+ lcd.print(ui8tostr3rj(progress));
+ else
+ lcd_put_u8str_P(PSTR("---"));
+ lcd.write('%');
+ }
+
+#endif // HAS_PRINT_PROGRESS
+
+#if ENABLED(LCD_PROGRESS_BAR)
+
+ void MarlinUI::draw_progress_bar(const uint8_t percent) {
+ if (!PanelDetected) return;
+ if (fb == &framebuffer[0] + LCD_WIDTH * 2) { // For status screen
+ lcd.write('%'); lcd.write(percent);
+ }
+ else { // For progress bar test
+ lcd.setCursor(LCD_WIDTH / 2 - 2, MIDDLE_Y);
+ lcd.print(i16tostr3rj(percent)); lcd.write('%');
+ lcd.print_line();
+ lcd.setCursor(0, MIDDLE_Y + 1);
+ lcd.write('%'); lcd.write(percent);
+ lcd.print_line();
+ }
+ }
+
+#endif
+
+void MarlinUI::draw_status_message(const bool blink) {
+ if (!PanelDetected) return;
+ lcd.setCursor(0, 3);
+ #if BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT)
+
+ // Alternate Status message and Filament display
+ if (ELAPSED(millis(), next_filament_display)) {
+ lcd_put_u8str_P(PSTR("Dia "));
+ lcd.print(ftostr12ns(filament_width_meas));
+ lcd_put_u8str_P(PSTR(" V"));
+ lcd.print(i16tostr3rj(100.0 * (
+ parser.volumetric_enabled
+ ? planner.volumetric_area_nominal / planner.volumetric_multiplier[FILAMENT_SENSOR_EXTRUDER_NUM]
+ : planner.volumetric_multiplier[FILAMENT_SENSOR_EXTRUDER_NUM]
+ )
+ ));
+ lcd.write('%');
+ return;
+ }
+
+ #endif // FILAMENT_LCD_DISPLAY && SDSUPPORT
+
+ // Get the UTF8 character count of the string
+ uint8_t slen = utf8_strlen(status_message);
+
+ #if ENABLED(STATUS_MESSAGE_SCROLLING)
+
+ static bool last_blink = false;
+
+ // If the string fits into the LCD, just print it and do not scroll it
+ if (slen <= LCD_WIDTH) {
+
+ // The string isn't scrolling and may not fill the screen
+ lcd_put_u8str(status_message);
+
+ // Fill the rest with spaces
+ while (slen < LCD_WIDTH) { lcd.write(' '); ++slen; }
+ }
+ else {
+ // String is larger than the available space in screen.
+
+ // Get a pointer to the next valid UTF8 character
+ // and the string remaining length
+ uint8_t rlen;
+ const char *stat = status_and_len(rlen);
+ lcd_put_u8str_max(stat, LCD_WIDTH); // The string leaves space
+
+ // If the remaining string doesn't completely fill the screen
+ if (rlen < LCD_WIDTH) {
+ lcd.write('.'); // Always at 1+ spaces left, draw a dot
+ uint8_t chars = LCD_WIDTH - rlen; // Amount of space left in characters
+ if (--chars) { // Draw a second dot if there's space
+ lcd.write('.');
+ if (--chars)
+ lcd_put_u8str_max(status_message, chars); // Print a second copy of the message
+ }
+ }
+ if (last_blink != blink) {
+ last_blink = blink;
+ advance_status_scroll();
+ }
+ }
+
+ #else
+
+ UNUSED(blink);
+
+ // Just print the string to the LCD
+ lcd_put_u8str_max(status_message, LCD_WIDTH);
+
+ // Fill the rest with spaces if there are missing spaces
+ while (slen < LCD_WIDTH) {
+ lcd.write(' ');
+ ++slen;
+ }
+
+ #endif
+}
+
+/**
+Possible status screens:
+
+Equal to 20x10 text LCD
+
+|X 000 Y 000 Z 000.00|
+|FR100% SD100% C--:--|
+| Progress bar line |
+|Status message |
+| |
+| HE BED FAN |
+| ttc ttc % | ttc - current temperature
+| tts tts %%% | tts - setted temperature, %%% - percent for FAN
+| ICO ICO ICO ICO | ICO - icon 48x48, placed in 2 text lines
+| ICO ICO ICO ICO | ICO
+
+or
+
+|X 000 Y 000 Z 000.00|
+|FR100% SD100% C--:--|
+| Progress bar line |
+|Status message |
+| |
+|HE1 HE2 HE3 BED ICO|
+|ttc ttc ttc ttc ICO|
+|tts tts tts tts %%%|
+|ICO ICO ICO ICO ICO|
+|ICO ICO ICO ICO ICO|
+
+or
+
+Equal to 24x10 text LCD
+
+|X 000 Y 000 Z 000.00 |
+|FR100% SD100% C--:--|
+| Progress bar line |
+|Status message |
+| |
+|HE1 HE2 HE3 BED FAN |
+|ttc ttc ttc ttc % |
+|tts tts tts tts %%% |
+|ICO ICO ICO ICO ICO ICO|
+|ICO ICO ICO ICO ICO ICO|
+*/
+
+void MarlinUI::draw_status_screen() {
+ if (!PanelDetected) return;
+
+ const bool blink = get_blink();
+
+ lcd.clear_buffer();
+
+ //
+ // Line 1 - XYZ coordinates
+ //
+
+ lcd.setCursor(0, 0);
+ _draw_axis_value(X_AXIS, ftostr4sign(LOGICAL_X_POSITION(current_position[X_AXIS])), blink); lcd.write(' ');
+ _draw_axis_value(Y_AXIS, ftostr4sign(LOGICAL_Y_POSITION(current_position[Y_AXIS])), blink); lcd.write(' ');
+ _draw_axis_value(Z_AXIS, ftostr52sp(LOGICAL_Z_POSITION(current_position[Z_AXIS])), blink);
+
+ #if HAS_LEVELING && !HAS_HEATED_BED
+ lcd.write(planner.leveling_active || blink ? '_' : ' ');
+ #endif
+
+ //
+ // Line 2 - feedrate, , time
+ //
+
+ lcd.setCursor(0, 1);
+ lcd_put_u8str_P(PSTR("FR")); lcd.print(i16tostr3rj(feedrate_percentage)); lcd.write('%');
+
+ #if BOTH(SDSUPPORT, HAS_PRINT_PROGRESS)
+ lcd.setCursor(LCD_WIDTH / 2 - 3, 1);
+ _draw_print_progress();
+ #endif
+
+ char buffer[10];
+ duration_t elapsed = print_job_timer.duration();
+ uint8_t len = elapsed.toDigital(buffer);
+
+ lcd.setCursor((LCD_WIDTH - 1) - len, 1);
+ lcd.write(LCD_STR_CLOCK[0]); lcd.print(buffer);
+
+ //
+ // Line 3 - progressbar
+ //
+
+ lcd.setCursor(0, 2);
+ #if ENABLED(LCD_PROGRESS_BAR)
+ draw_progress_bar(_get_progress());
+ #else
+ lcd.write('%'); lcd.write(0);
+ #endif
+
+ //
+ // Line 4 - Status Message (which may be a Filament display)
+ //
+
+ draw_status_message(blink);
+
+ //
+ // Line 5
+ //
+
+ #if HOTENDS <= 1 || (HOTENDS <= 2 && !HAS_HEATED_BED)
+ #if DUAL_MIXING_EXTRUDER
+ lcd.setCursor(0, 4);
+ // Two-component mix / gradient instead of XY
+ char mixer_messages[12];
+ const char *mix_label;
+ #if ENABLED(GRADIENT_MIX)
+ if (mixer.gradient.enabled) {
+ mixer.update_mix_from_gradient();
+ mix_label = "Gr";
+ }
+ else
+ #endif
+ {
+ mixer.update_mix_from_vtool();
+ mix_label = "Mx";
+ }
+ sprintf_P(mixer_messages, PSTR("%s %d;%d%% "), mix_label, int(mixer.mix[0]), int(mixer.mix[1]));
+ lcd_put_u8str(mixer_messages);
+ #endif
+ #endif
+
+ //
+ // Line 6..8 Temperatures, FAN
+ //
+
+ #if HOTENDS < 2
+ _draw_heater_status(H_E0, "HE", blink); // Hotend Temperature
+ #else
+ _draw_heater_status(H_E0, "HE1", blink); // Hotend 1 Temperature
+ _draw_heater_status(H_E1, "HE2", blink); // Hotend 2 Temperature
+ #if HOTENDS > 2
+ _draw_heater_status(H_E2, "HE3", blink); // Hotend 3 Temperature
+ #endif
+ #endif // HOTENDS <= 1
+
+ #if HAS_HEATED_BED
+ #if HAS_LEVELING
+ _draw_heater_status(H_BED, (planner.leveling_active && blink ? "___" : "BED"), blink);
+ #else
+ _draw_heater_status(H_BED, "BED", blink);
+ #endif
+ #endif // HAS_HEATED_BED
+
+ #if FAN_COUNT > 0
+ uint16_t spd = thermalManager.fan_speed[0];
+
+ #if ENABLED(ADAPTIVE_FAN_SLOWING)
+ if (!blink) spd = thermalManager.scaledFanSpeed(0, spd);
+ #endif
+
+ uint16_t per = thermalManager.fanPercent(spd);
+ #if HOTENDS < 2
+ #define FANX 11
+ #else
+ #define FANX 17
+ #endif
+ lcd.setCursor(FANX, 5); lcd_put_u8str_P(PSTR("FAN"));
+ lcd.setCursor(FANX + 1, 6); lcd.write('%');
+ lcd.setCursor(FANX, 7);
+ lcd.print(i16tostr3rj(per));
+
+ if (TERN0(HAS_FAN0, thermalManager.fan_speed[0]) || TERN0(HAS_FAN1, thermalManager.fan_speed[1]) || TERN0(HAS_FAN2, thermalManager.fan_speed[2]))
+ picBits |= ICON_FAN;
+ else
+ picBits &= ~ICON_FAN;
+
+ #endif // FAN_COUNT > 0
+
+ //
+ // Line 9, 10 - icons
+ //
+ lcd.print_screen();
+}
+
+#if HAS_LCD_MENU
+
+ #include "../menu/menu.h"
+
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+
+ void MarlinUI::draw_hotend_status(const uint8_t row, const uint8_t extruder) {
+ if (!PanelDetected) return;
+ lcd.setCursor((LCD_WIDTH - 14) / 2, row + 1);
+ lcd.write(LCD_STR_THERMOMETER[0]); lcd_put_u8str_P(PSTR(" E")); lcd.write('1' + extruder); lcd.write(' ');
+ lcd.print(i16tostr3rj(thermalManager.degHotend(extruder))); lcd.write(LCD_STR_DEGREE[0]); lcd.write('/');
+ lcd.print(i16tostr3rj(thermalManager.degTargetHotend(extruder))); lcd.write(LCD_STR_DEGREE[0]);
+ lcd.print_line();
+ }
+
+ #endif
+
+ // Draw a static item with no left-right margin required. Centered by default.
+ void MenuItem_static::draw(const uint8_t row, PGM_P const pstr, const uint8_t style/*=SS_DEFAULT*/, const char * const valstr/*=nullptr*/) {
+ if (!PanelDetected) return;
+ uint8_t n = LCD_WIDTH;
+ lcd.setCursor(0, row);
+ if ((style & SS_CENTER) && !valstr) {
+ int8_t pad = (LCD_WIDTH - utf8_strlen_P(pstr)) / 2;
+ while (--pad >= 0) { lcd.write(' '); n--; }
+ }
+ n = lcd_put_u8str_ind_P(pstr, itemIndex, itemString, n);
+ if (valstr) n -= lcd_put_u8str_max(valstr, n);
+ for (; n; --n) lcd.write(' ');
+ lcd.print_line();
+ }
+
+ // Draw a generic menu item with pre_char (if selected) and post_char
+ void MenuItemBase::_draw(const bool sel, const uint8_t row, PGM_P const pstr, const char pre_char, const char post_char) {
+ if (!PanelDetected) return;
+ lcd.setCursor(0, row);
+ lcd.write(sel ? pre_char : ' ');
+ uint8_t n = lcd_put_u8str_ind_P(pstr, itemIndex, itemString, LCD_WIDTH - 2);
+ for (; n; --n) lcd.write(' ');
+ lcd.write(post_char);
+ lcd.print_line();
+ }
+
+ // Draw a menu item with a (potentially) editable value
+ void MenuEditItemBase::draw(const bool sel, const uint8_t row, PGM_P const pstr, const char* const data, const bool pgm) {
+ if (!PanelDetected) return;
+ const uint8_t vlen = data ? (pgm ? utf8_strlen_P(data) : utf8_strlen(data)) : 0;
+ lcd.setCursor(0, row);
+ lcd.write(sel ? LCD_STR_ARROW_RIGHT[0] : ' ');
+ uint8_t n = lcd_put_u8str_ind_P(pstr, itemIndex, itemString, LCD_WIDTH - 2 - vlen);
+ if (vlen) {
+ lcd.write(':');
+ for (; n; --n) lcd.write(' ');
+ if (pgm) lcd_put_u8str_P(data); else lcd_put_u8str(data);
+ }
+ lcd.print_line();
+ }
+
+ // Low-level draw_edit_screen can be used to draw an edit screen from anyplace
+ // This line moves to the last line of the screen for UBL plot screen on the panel side
+ void MenuEditItemBase::draw_edit_screen(PGM_P const pstr, const char* const value/*=nullptr*/) {
+ if (!PanelDetected) return;
+ ui.encoder_direction_normal();
+ const uint8_t y = TERN0(AUTO_BED_LEVELING_UBL, ui.external_control) ? LCD_HEIGHT - 1 : MIDDLE_Y;
+ lcd.setCursor(0, y);
+ lcd.write(COLOR_EDIT);
+ lcd_put_u8str_P(pstr);
+ if (value) {
+ lcd.write(':');
+ lcd.setCursor((LCD_WIDTH - 1) - (utf8_strlen(value) + 1), y); // Right-justified, padded by spaces
+ lcd.write(' '); // Overwrite char if value gets shorter
+ lcd.print(value);
+ lcd.write(' ');
+ lcd.print_line();
+ }
+ }
+
+ // The Select Screen presents a prompt and two "buttons"
+ void MenuItem_confirm::draw_select_screen(PGM_P const yes, PGM_P const no, const bool yesno, PGM_P const pref, const char * const string, PGM_P const suff) {
+ if (!PanelDetected) return;
+ ui.draw_select_screen_prompt(pref, string, suff);
+ lcd.setCursor(0, MIDDLE_Y);
+ lcd.write(COLOR_EDIT);
+ lcd.write(yesno ? ' ' : '['); lcd_put_u8str_P(no); lcd.write(yesno ? ' ' : ']');
+ lcd.setCursor(LCD_WIDTH - utf8_strlen_P(yes) - 3, MIDDLE_Y);
+ lcd.write(yesno ? '[' : ' '); lcd_put_u8str_P(yes); lcd.write(yesno ? ']' : ' ');
+ lcd.print_line();
+ }
+
+ #if ENABLED(SDSUPPORT)
+
+ void MenuItem_sdbase::draw(const bool sel, const uint8_t row, PGM_P const, CardReader &theCard, const bool isDir) {
+ if (!PanelDetected) return;
+ lcd.setCursor(0, row);
+ lcd.write(sel ? LCD_STR_ARROW_RIGHT[0] : ' ');
+ constexpr uint8_t maxlen = LCD_WIDTH - 2;
+ uint8_t n = maxlen - lcd_put_u8str_max(ui.scrolled_filename(theCard, maxlen, row, sel), maxlen);
+ for (; n; --n) lcd.write(' ');
+ lcd.write(isDir ? LCD_STR_FOLDER[0] : ' ');
+ lcd.print_line();
+ }
+
+ #endif // SDSUPPORT
+
+ #if ENABLED(LCD_HAS_STATUS_INDICATORS)
+
+ void MarlinUI::update_indicators() {}
+
+ #endif // LCD_HAS_STATUS_INDICATORS
+
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+ /**
+ * Map screen:
+ * |/---------\ (00,00) |
+ * || . . . . | X:000.00|
+ * || . . . . | Y:000.00|
+ * || . . . . | Z:00.000|
+ * || . . . . | |
+ * || . . . . | |
+ * || . . . . | |
+ * |+---------/ |
+ * | |
+ * |____________________|
+ */
+ void MarlinUI::ubl_plot(const uint8_t x_plot, const uint8_t y_plot) {
+ if (!PanelDetected) return;
+
+ #define _LCD_W_POS 12
+
+ lcd.clear_buffer();
+
+ //print only top left corner. All frame with grid points will be printed by panel
+ lcd.setCursor(0, 0);
+ *fb++ = TLC; //top left corner - marker for plot parameters
+ *fb = (GRID_MAX_POINTS_X << 4) + GRID_MAX_POINTS_Y; //set mesh size
+
+ // Print plot position
+ lcd.setCursor(_LCD_W_POS, 0);
+ *fb++ = '('; lcd.print(i16tostr3left(x_plot));
+ *fb++ = ','; lcd.print(i16tostr3left(y_plot)); *fb = ')';
+
+ // Show all values
+ lcd.setCursor(_LCD_W_POS, 1); lcd_put_u8str_P(PSTR("X:"));
+ lcd.print(ftostr52(LOGICAL_X_POSITION(pgm_read_float(&ubl._mesh_index_to_xpos[x_plot]))));
+ lcd.setCursor(_LCD_W_POS, 2); lcd_put_u8str_P(PSTR("Y:"));
+ lcd.print(ftostr52(LOGICAL_Y_POSITION(pgm_read_float(&ubl._mesh_index_to_ypos[y_plot]))));
+
+ // Show the location value
+ lcd.setCursor(_LCD_W_POS, 3); lcd_put_u8str_P(PSTR("Z:"));
+
+ if (!isnan(ubl.z_values[x_plot][y_plot]))
+ lcd.print(ftostr43sign(ubl.z_values[x_plot][y_plot]));
+ else
+ lcd_put_u8str_P(PSTR(" -----"));
+
+ center_text_P(GET_TEXT(MSG_UBL_FINE_TUNE_MESH), 8);
+
+ lcd.print_screen();
+ }
+
+ #endif // AUTO_BED_LEVELING_UBL
+
+#endif // HAS_LCD_MENU
+
+#endif // IS_TFTGLCD_PANEL