diff --git a/src/lcd/TFTGLCD/lcdprint_TFTGLCD.cpp b/src/lcd/TFTGLCD/lcdprint_TFTGLCD.cpp
new file mode 100644
index 0000000..e681ff0
--- /dev/null
+++ b/src/lcd/TFTGLCD/lcdprint_TFTGLCD.cpp
@@ -0,0 +1,1142 @@
+/**
+ * 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 .
+ *
+ */
+
+/**
+ * @file lcdprint_TFTGLCD.cpp
+ * @brief LCD print API for TFT-GLCD interface
+ * @author Yunhui Fu (yhfudev@gmail.com)
+ * @version 1.0
+ * @date 2016-08-19
+ * @copyright GPL/BSD
+ */
+
+/**
+ * The TFTGLCD only supports ??? languages.
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if IS_TFTGLCD_PANEL
+
+#include "../marlinui.h"
+#include "../../MarlinCore.h"
+#include "../../libs/numtostr.h"
+
+#include "marlinui_TFTGLCD.h"
+
+#include
+
+int lcd_glyph_height() { return 1; }
+
+typedef struct _TFTGLCD_charmap_t {
+ lchar_t uchar; // the unicode char
+ uint8_t idx; // the glyph of the char in the ROM
+ uint8_t idx2; // the char used to be combined with the idx to simulate a single char
+} TFTGLCD_charmap_t;
+
+#ifdef __AVR__
+ #define IV(a) U##a
+#else
+ #define IV(a) L##a
+#endif
+
+static const TFTGLCD_charmap_t g_TFTGLCD_charmap_device[] PROGMEM = {
+ // sorted by uchar:
+ #if DISPLAY_CHARSET_HD44780 == JAPANESE
+
+ {IV('¢'), 0xEC, 0}, // A2
+ {IV('°'), 0xDF, 0}, // B0, Marlin special: '°' LCD_STR_DEGREE (0x09)
+ {IV('ä'), 0xE1, 0}, // E4
+ {IV('ö'), 0xEF, 0}, // F6
+ {IV('÷'), 0xFD, 0}, // 00F7
+ {IV('ü'), 0xF5, 0}, // 00FC
+ {IV('ˣ'), 0xEB, 0}, // 02E3
+
+ {IV('·'), 0xA5, 0}, // 0387
+ {IV('Ώ'), 0xF4, 0}, // 038F
+ {IV('Θ'), 0xF2, 0}, // 0398, Theta
+ {IV('Ξ'), 0xE3, 0}, // 039E, Xi
+ {IV('Σ'), 0xF6, 0}, // 03A3, Sigma
+ {IV('Ω'), 0xF4, 0}, // 03A9, Omega
+ {IV('ά'), 0xE0, 0}, // 03AC
+ {IV('έ'), 0xE3, 0}, // 03AD
+ {IV('α'), 0xE0, 0}, // 03B1, alpha
+ {IV('β'), 0xE2, 0}, // 03B2, beta
+ {IV('ε'), 0xE3, 0}, // 03B5, epsilon
+ {IV('θ'), 0xF2, 0}, // 03B8, theta
+ {IV('μ'), 0xE4, 0}, // 03BC, mu
+ {IV('ξ'), 0xE3, 0}, // 03BE, xi
+ {IV('π'), 0xF7, 0}, // 03C0, pi
+ {IV('ρ'), 0xE6, 0}, // 03C1, rho
+ {IV('σ'), 0xE5, 0}, // 03C3, sigma
+
+ {IV('←'), 0x7F, 0}, // 2190
+ {IV('→'), 0x7E, 0}, // 2192, Marlin special: '⮈⮉⮊⮋➤→' LCD_STR_ARROW_RIGHT (0x03)
+ {IV('√'), 0xE8, 0}, // 221A
+ {IV('∞'), 0xF3, 0}, // 221E
+ {IV('█'), 0xFF, 0}, // 2588
+
+ //{IV(''), 0xA0, 0},
+ {IV('。'), 0xA1, 0},
+ {IV('「'), 0xA2, 0},
+ {IV('」'), 0xA3, 0},
+ {IV('゛'), 0xDE, 0}, // ‶
+ {IV('゜'), 0xDF, 0}, // '〫'
+ {IV('゠'), '=', 0},
+ {IV('ァ'), 0xA7, 0},
+ {IV('ア'), 0xB1, 0},
+ {IV('ィ'), 0xA8, 0},
+ {IV('イ'), 0xB2, 0},
+ {IV('ゥ'), 0xA9, 0},
+ {IV('ウ'), 0xB3, 0},
+ {IV('ェ'), 0xAA, 0},
+ {IV('エ'), 0xB4, 0},
+ {IV('ォ'), 0xAB, 0},
+
+ {IV('オ'), 0xB5, 0},
+ {IV('カ'), 0xB6, 0},
+ {IV('ガ'), 0xB6, 0xDE},
+ {IV('キ'), 0xB7, 0},
+ {IV('ギ'), 0xB7, 0xDE}, //
+ {IV('ク'), 0xB8, 0},
+ {IV('グ'), 0xB8, 0xDE},
+ {IV('ケ'), 0xB9, 0},
+ {IV('ゲ'), 0xB9, 0xDE},
+ {IV('コ'), 0xBA, 0},
+ {IV('ゴ'), 0xBA, 0xDE},
+ {IV('サ'), 0xBB, 0},
+ {IV('ザ'), 0xBB, 0xDE},
+ {IV('シ'), 0xBC, 0},
+ {IV('ジ'), 0xBC, 0xDE},
+ {IV('ス'), 0xBD, 0},
+ {IV('ズ'), 0xBD, 0xDE},
+ {IV('セ'), 0xBE, 0},
+ {IV('ゼ'), 0xBE, 0xDE},
+ {IV('ソ'), 0xBF, 0},
+ {IV('ゾ'), 0xBF, 0xDE},
+
+ {IV('タ'), 0xC0, 0},
+ {IV('ダ'), 0xC0, 0xDE},
+ {IV('チ'), 0xC1, 0},
+ {IV('ヂ'), 0xC1, 0xDE},
+ {IV('ッ'), 0xAF, 0},
+ {IV('ツ'), 0xC2, 0},
+ {IV('ヅ'), 0xC2, 0xDE},
+ {IV('テ'), 0xC3, 0},
+ {IV('デ'), 0xC3, 0xDE},
+ {IV('ト'), 0xC4, 0},
+ {IV('ド'), 0xC4, 0xDE},
+ {IV('ナ'), 0xC5, 0},
+ {IV('ニ'), 0xC6, 0},
+ {IV('ヌ'), 0xC7, 0},
+ {IV('ネ'), 0xC8, 0},
+ {IV('ノ'), 0xC9, 0},
+ {IV('ハ'), 0xCA, 0},
+ {IV('バ'), 0xCA, 0xDE},
+ {IV('パ'), 0xCA, 0xDF},
+ {IV('ヒ'), 0xCB, 0},
+ {IV('ビ'), 0xCB, 0xDE},
+ {IV('ピ'), 0xCB, 0xDF},
+ {IV('フ'), 0xCC, 0},
+ {IV('ブ'), 0xCC, 0xDE},
+ {IV('プ'), 0xCC, 0xDF},
+ {IV('ヘ'), 0xCD, 0},
+ {IV('ベ'), 0xCD, 0xDE},
+ {IV('ペ'), 0xCD, 0xDF},
+ {IV('ホ'), 0xCE, 0},
+ {IV('ボ'), 0xCE, 0xDE},
+ {IV('ポ'), 0xCE, 0xDF},
+ {IV('マ'), 0xCF, 0},
+
+ {IV('ミ'), 0xD0, 0},
+ {IV('ム'), 0xD1, 0},
+ {IV('メ'), 0xD2, 0},
+ {IV('モ'), 0xD3, 0},
+ {IV('ャ'), 0xAC, 0},
+ {IV('ヤ'), 0xD4, 0},
+ {IV('ュ'), 0xAD, 0},
+ {IV('ユ'), 0xD5, 0},
+ {IV('ョ'), 0xAE, 0},
+ {IV('ヨ'), 0xD6, 0},
+ {IV('ラ'), 0xD7, 0},
+ {IV('リ'), 0xD8, 0},
+ {IV('ル'), 0xD9, 0},
+ {IV('レ'), 0xDA, 0},
+ {IV('ロ'), 0xDB, 0},
+ {IV('ワ'), 0xDC, 0},
+ {IV('ヲ'), 0xA6, 0},
+ {IV('ン'), 0xDD, 0},
+ {IV('ヴ'), 0xB3, 0xDE},
+ {IV('ヷ'), 0xDC, 0xDE},
+ {IV('ヺ'), 0xA6, 0xDE},
+ {IV('・'), 0xA5, 0},
+ {IV('ー'), 0xB0, 0},
+ {IV('ヽ'), 0xA4, 0},
+
+ //{IV('g'), 0xE7, 0}, // error
+ //{IV(''), 0xE9, 0},
+ //{IV('j'), 0xEA, 0}, // error
+ //{IV(''), 0xED, 0},
+ //{IV(''), 0xEE, 0},
+
+ //{IV('p'), 0xF0, 0}, // error
+ //{IV('q'), 0xF1, 0}, // error
+ //{IV(''), 0xF8, 0},
+ //{IV('y'), 0xF9, 0}, // error
+ {IV('万'), 0xFB, 0},
+ {IV('円'), 0xFC, 0},
+ {IV('千'), 0xFA, 0},
+ //{IV(''), 0xFE, 0},
+
+ //、・ヲァィゥェォャュョッー
+ {IV('、'), 0xA4, 0}, //ヽ
+ {IV('・'), 0xA5, 0}, //・
+ {IV('ヲ'), 0xA6, 0}, //ヲ
+ {IV('ァ'), 0xA7, 0}, //ァ
+ {IV('ィ'), 0xA8, 0}, //ィ
+ {IV('ゥ'), 0xA9, 0}, //ゥ
+ {IV('ェ'), 0xAA, 0}, //ェ
+ {IV('ォ'), 0xAB, 0}, //ォ
+ {IV('ャ'), 0xAC, 0}, //ャ
+ {IV('ュ'), 0xAD, 0}, //ュ
+ {IV('ョ'), 0xAE, 0}, //ョ
+ {IV('ッ'), 0xAF, 0}, //ッ
+ {IV('ー'), 0xB0, 0}, //ー
+
+ //アイウエオカキクケコサシスセ
+ {IV('ア'), 0xB1, 0}, //ア
+ {IV('イ'), 0xB2, 0}, //イ
+ {IV('ウ'), 0xB3, 0}, //ウ
+ {IV('エ'), 0xB4, 0}, //エ
+ {IV('オ'), 0xB5, 0}, //オ
+ {IV('カ'), 0xB6, 0}, //カ
+ {IV('キ'), 0xB7, 0}, //キ
+ {IV('ク'), 0xB8, 0}, //ク
+ {IV('ケ'), 0xB9, 0}, //ケ
+ {IV('コ'), 0xBA, 0}, //コ
+ {IV('サ'), 0xBB, 0}, //サ
+ {IV('シ'), 0xBC, 0}, //シ
+ {IV('ス'), 0xBD, 0}, //ス
+ {IV('セ'), 0xBE, 0}, //セ
+
+ //ソタチツテトナニヌネノハヒフ
+ {IV('ソ'), 0xBF, 0}, //ソ
+ {IV('タ'), 0xC0, 0}, //タ
+ {IV('チ'), 0xC1, 0}, //チ
+ {IV('ツ'), 0xC2, 0}, //ツ
+ {IV('テ'), 0xC3, 0}, //テ
+ {IV('ト'), 0xC4, 0}, //ト
+ {IV('ナ'), 0xC5, 0}, //ナ
+ {IV('ニ'), 0xC6, 0}, //ニ
+ {IV('ヌ'), 0xC7, 0}, //ヌ
+ {IV('ネ'), 0xC8, 0}, //ネ
+ {IV('ノ'), 0xC9, 0}, //ノ
+ {IV('ハ'), 0xCA, 0}, //ハ
+ {IV('ヒ'), 0xCB, 0}, //ヒ
+ {IV('フ'), 0xCC, 0}, //フ
+
+ //ヘホマミムメモヤユヨラリルレロワン゙゚
+ {IV('ヘ'), 0xCD, 0}, //ヘ
+ {IV('ホ'), 0xCE, 0}, //ホ
+ {IV('マ'), 0xCF, 0}, //マ
+ {IV('ミ'), 0xD0, 0}, //ミ
+ {IV('ム'), 0xD1, 0}, //ム
+ {IV('メ'), 0xD2, 0}, //メ
+ {IV('モ'), 0xD3, 0}, //モ
+ {IV('ヤ'), 0xD4, 0}, //ヤ
+ {IV('ユ'), 0xD5, 0}, //ユ
+ {IV('ヨ'), 0xD6, 0}, //ヨ
+ {IV('ラ'), 0xD7, 0}, //ラ
+ {IV('リ'), 0xD8, 0}, //リ
+ {IV('ル'), 0xD9, 0}, //ル
+ {IV('レ'), 0xDA, 0}, //レ
+ {IV('ロ'), 0xDB, 0}, //ロ
+ {IV('ワ'), 0xDC, 0}, //ワ
+ {IV('ン'), 0xDD, 0}, //ン
+ {IV('゙'), 0xDE, 0}, // ゛
+ {IV('゚'), 0xDF, 0}, // ゜
+
+ {IV('¥'), 0x5C, 0},
+
+ #elif DISPLAY_CHARSET_HD44780 == WESTERN
+ // 0x10 -- 0x1F (except 0x1C)
+ // 0x80 -- 0xFF (except 0xA7,0xB0,0xB1,0xB3,0xB4,0xBF,0xD1,0xF8,0xFA,0xFC-0xFF)
+
+ {IV('¡'), 0xA9, 0},
+ {IV('¢'), 0xA4, 0},
+ {IV('£'), 0xA5, 0},
+ {IV('¥'), 0xA6, 0},
+ {IV('§'), 0xD2, 0}, // section sign
+ {IV('©'), 0xCF, 0},
+
+ {IV('ª'), 0x9D, 0},
+ {IV('«'), 0xBB, 0},
+ {IV('®'), 0xCE, 0},
+
+ {IV('°'), 0xB2, 0}, // Marlin special: '°' LCD_STR_DEGREE (0x09)
+ //{IV(''), 0xD1, 0},
+ {IV('±'), 0x10, 0}, //∓±
+ //{'='), 0x1C, 0}, // error
+ {IV('²'), 0x1E, 0},
+ {IV('³'), 0x1F, 0},
+ {IV('¶'), 0xD3, 0}, // pilcrow sign
+ {IV('º'), 0x9E, 0},
+ {IV('»'), 0xBC, 0}, // 00BB
+ //{IV(''), 0xB3, 0}, // error
+ //{IV(''), 0xB4, 0}, // error
+ {IV('¼'), 0xB6, 0}, // 00BC
+ {IV('½'), 0xB5, 0}, // 00BD
+ //{IV('¾'), '3', 0}, // 00BE
+ {IV('¿'), 0x9F, 0}, // 00BF
+
+ {IV('Â'), 0x8F, 0},
+ {IV('Ã'), 0xAA, 0},
+ {IV('Ä'), 0x8E, 0},
+ {IV('Æ'), 0x92, 0},
+ {IV('Ç'), 0x80, 0},
+ {IV('É'), 0x90, 0},
+ {IV('Ñ'), 0x9C, 0},
+ {IV('Õ'), 0xAC, 0},
+ {IV('Ö'), 0x99, 0},
+ {IV('×'), 0xB7, 0},
+ {IV('Ø'), 0xAE, 0},
+ {IV('Ü'), 0x9A, 0},
+ {IV('à'), 0x85, 0},
+ {IV('á'), 0xA0, 0},
+ {IV('â'), 0x83, 0},
+ {IV('ã'), 0xAB, 0},
+ {IV('ä'), 0x84, 0},
+ {IV('å'), 0x86, 0},
+ {IV('æ'), 0x91, 0},
+ {IV('ç'), 0x87, 0},
+ {IV('è'), 0x8A, 0},
+ {IV('é'), 0x82, 0},
+ {IV('ê'), 0x88, 0},
+ {IV('ë'), 0x89, 0},
+ {IV('ì'), 0x8D, 0},
+ {IV('í'), 0xA1, 0},
+ {IV('î'), 0x8C, 0},
+ {IV('ï'), 0x8B, 0},
+
+ {IV('ñ'), 0x9B, 0},
+ {IV('ò'), 0x95, 0},
+ {IV('ó'), 0xA2, 0},
+ {IV('ô'), 0x93, 0},
+ {IV('õ'), 0xAD, 0},
+ {IV('ö'), 0x94, 0},
+ {IV('÷'), 0xB8, 0},
+ {IV('ø'), 0xAF, 0},
+ {IV('ù'), 0x97, 0},
+ {IV('ú'), 0xA3, 0},
+ {IV('û'), 0x96, 0},
+ {IV('ü'), 0x81, 0},
+ {IV('ÿ'), 0x98, 0},
+
+ //{IV(''), 0xB0, 0}, // error
+ //{IV(''), 0xB1, 0}, // error
+ {IV('ƒ'), 0xA8, 0}, // 0192
+
+ {IV('Ύ'), 0xDB, 0}, // 038E
+ {IV('Ώ'), 0xDE, 0}, // 038F
+ {IV('ΐ'), 0xE7, 0}, // 0390
+
+ {IV('Γ'), 0xD4, 0}, // 0393, Gamma
+ {IV('Δ'), 0xD5, 0}, // 0394, Delta, ◿
+ {IV('Θ'), 0xD6, 0}, // 0398, Theta
+ {IV('Λ'), 0xD7, 0}, // 039B, Lambda
+ {IV('Ξ'), 0xD8, 0}, // 039E, Xi
+ {IV('Π'), 0xD9, 0}, // Pi
+ {IV('Σ'), 0xDA, 0}, // Sigma
+ {IV('Υ'), 0xDB, 0}, // Upsilon
+ {IV('Φ'), 0xDC, 0}, // Phi
+ {IV('Ψ'), 0xDD, 0}, // Psi
+ {IV('Ω'), 0xDE, 0}, // Omega
+
+ {IV('ά'), 0xDF, 0}, // 03AC
+ {IV('έ'), 0xE3, 0}, // 03AD
+ {IV('ή'), 0xE5, 0}, // 03AE
+ {IV('ί'), 0xE7, 0}, // 03AF
+ {IV('ΰ'), 0xF1, 0}, // 03B0
+
+ {IV('α'), 0xDF, 0}, // alpha
+ {IV('β'), 0xE0, 0}, // beta
+ {IV('γ'), 0xE1, 0}, // gamma
+ {IV('δ'), 0xE2, 0}, // delta
+ {IV('ε'), 0xE3, 0}, // epsilon
+ {IV('ζ'), 0xE4, 0}, // zeta
+ {IV('η'), 0xE5, 0}, // eta
+ {IV('θ'), 0xE6, 0}, // theta
+ {IV('ι'), 0xE7, 0}, // lota
+ {IV('κ'), 0xE8, 0}, // kappa
+ {IV('λ'), 0xE9, 0}, // lambda
+ {IV('μ'), 0xEA, 0}, // mu
+ {IV('ν'), 0xEB, 0}, // nu
+ {IV('ξ'), 0xEC, 0}, // xi
+ {IV('π'), 0xED, 0}, // pi
+ {IV('ρ'), 0xEE, 0}, // rho
+ {IV('σ'), 0xEF, 0}, // sigma
+
+ {IV('τ'), 0xF0, 0}, // tau
+ {IV('υ'), 0xF1, 0}, // upsilon
+ {IV('χ'), 0xF2, 0}, // chi
+ {IV('ψ'), 0xF3, 0}, // psi
+ {IV('ω'), 0xF4, 0}, // 03C9, omega
+ {IV('ϊ'), 0xE7, 0}, // 03CA
+ {IV('ϋ'), 0xF1, 0}, // 03CB
+ {IV('ύ'), 0xF1, 0}, // 03CD
+ {IV('ώ'), 0xF4, 0}, // 03CE
+
+ {IV('•'), 0xCD, 0}, // ·
+ {IV('℞'), 0xA7, 0}, // ℞ Pt ASCII 158
+ {IV('™'), 0xD0, 0},
+ {IV('↤'), 0xF9, 0}, // ⟻
+ {IV('↵'), 0xC4, 0},
+ {IV('↻'), 0x04, 0}, // Marlin special: '↻↺⟳⟲' LCD_STR_REFRESH (0x01)
+ {IV('⇥'), 0xFB, 0},
+ {IV('√'), 0xBE, 0}, // √
+ {IV('∞'), 0xC2, 0}, // infinity
+ {IV('∫'), 0x1B, 0},
+ {IV('∼'), 0x1D, 0},
+ {IV('≈'), 0x1A, 0},
+ {IV('≠'), 0xBD, 0},
+ {IV('≡'), 0x11, 0},
+ {IV('≤'), 0xB9, 0},// ≤≥ ⩽⩾
+ {IV('≥'), 0xBA, 0},
+ //{IV(''), 0xBF, 0}, // error
+
+ {IV('⌠'), 0xC0, 0},
+ {IV('⌡'), 0xC1, 0},
+
+ {IV('⎧'), 0x14, 0},
+ {IV('⎩'), 0x15, 0},
+ {IV('⎫'), 0x16, 0},
+ {IV('⎭'), 0x17, 0},
+ {IV('⎰'), 0x18, 0},
+ {IV('⎱'), 0x19, 0},
+ {IV('⎲'), 0x12, 0},
+ {IV('⎳'), 0x13, 0},
+
+ {IV('⏱'), 0x07, 0}, // Marlin special: '🕐🕑🕒🕓🕔🕕🕖🕗🕘🕙🕚🕛🕜🕝🕞🕟🕠🕡🕢🕣🕤🕥🕦🕧 ⌚⌛⏰⏱⏳⧖⧗' LCD_STR_CLOCK (0x05)
+ {IV('┌'), 0xC9, 0},
+ {IV('┐'), 0xCA, 0},
+ {IV('└'), 0xCB, 0},
+ {IV('┘'), 0xCC, 0},
+ {IV('◸'), 0xC3, 0}, // ◿
+ {IV('⭠'), 0xC8, 0},
+ {IV('⭡'), 0xC5, 0},
+ {IV('⭢'), 0xC7, 0},
+ {IV('⭣'), 0xC6, 0},
+
+
+ {IV('⯆'), 0xF5, 0},
+ {IV('⯇'), 0xF7, 0}, // ⯅
+ {IV('⯈'), 0xF6, 0},
+ //{IV(''), 0xF8, 0}, // error
+ //{IV(''), 0xFA, 0}, // error
+ //{IV(''), 0xFC, 0}, // error
+ //{IV(''), 0xFD, 0}, // error
+ //{IV(''), 0xFE, 0}, // error
+ //{IV(''), 0xFF, 0}, // error
+
+ #elif DISPLAY_CHARSET_HD44780 == CYRILLIC
+
+ #ifdef CONVERT_TO_EXT_ASCII
+ {IV('°'), 0x01, 0}, // 00B0, Marlin special: '°' LCD_STR_DEGREE (0x09)
+ {IV('²'), 0x0E, 0}, // 0x32 if no special symbol in panel font
+ {IV('³'), 0x0F, 0}, // 0x33 if no special symbol in panel font
+
+ // translate to cp866 codepage
+ //first ASCII symbols in panel font must be replaced with Marlin special symbols
+ {IV('Ё'), 0xF0, 0}, // 0401
+ {IV('Є'), 0xF2, 0}, // 0404
+ {IV('І'), 'I', 0}, // 0406
+ {IV('Ї'), 0xF4, 0}, // 0407
+ {IV('Ў'), 0xF6, 0}, // 040E
+ {IV('А'), 0x80, 0}, // 0410
+ {IV('Б'), 0x81, 0},
+ {IV('В'), 0x82, 0},
+ {IV('Г'), 0x83, 0},
+ {IV('Д'), 0x84, 0},
+ {IV('Е'), 0x85, 0},
+ {IV('Ж'), 0x86, 0},
+ {IV('З'), 0x87, 0},
+ {IV('И'), 0x88, 0},
+ {IV('Й'), 0x89, 0},
+ {IV('К'), 0x8A, 0},
+ {IV('Л'), 0x8B, 0},
+ {IV('М'), 0x8C, 0},
+ {IV('Н'), 0x8D, 0},
+ {IV('О'), 0x8E, 0},
+ {IV('П'), 0x8F, 0},
+ {IV('Р'), 0x90, 0},
+ {IV('С'), 0x91, 0},
+ {IV('Т'), 0x92, 0},
+ {IV('У'), 0x93, 0},
+ {IV('Ф'), 0x94, 0},
+ {IV('Х'), 0x95, 0},
+ {IV('Ц'), 0x96, 0},
+ {IV('Ч'), 0x97, 0},
+ {IV('Ш'), 0x98, 0},
+ {IV('Щ'), 0x99, 0},
+ {IV('Ъ'), 0x9A, 0},
+ {IV('Ы'), 0x9B, 0},
+ {IV('Ь'), 0x9C, 0},
+ {IV('Э'), 0x9D, 0},
+ {IV('Ю'), 0x9E, 0},
+ {IV('Я'), 0x9F, 0},
+
+ {IV('а'), 0xA0, 0},
+ {IV('б'), 0xA1, 0},
+ {IV('в'), 0xA2, 0},
+ {IV('г'), 0xA3, 0},
+ {IV('д'), 0xA4, 0},
+ {IV('е'), 0xA5, 0},
+ {IV('ж'), 0xA6, 0},
+ {IV('з'), 0xA7, 0},
+ {IV('и'), 0xA8, 0},
+ {IV('й'), 0xA9, 0},
+ {IV('к'), 0xAA, 0},
+ {IV('л'), 0xAB, 0},
+ {IV('м'), 0xAC, 0},
+ {IV('н'), 0xAD, 0},
+ {IV('о'), 0xAE, 0},
+ {IV('п'), 0xAF, 0},
+ {IV('р'), 0xE0, 0},
+ {IV('с'), 0xE1, 0},
+ {IV('т'), 0xE2, 0},
+ {IV('у'), 0xE3, 0},
+ {IV('ф'), 0xE4, 0},
+ {IV('х'), 0xE5, 0},
+ {IV('ц'), 0xE6, 0},
+ {IV('ч'), 0xE7, 0},
+ {IV('ш'), 0xE8, 0},
+ {IV('щ'), 0xE9, 0},
+ {IV('ъ'), 0xEA, 0},
+ {IV('ы'), 0xEB, 0},
+ {IV('ь'), 0xEC, 0},
+ {IV('э'), 0xED, 0},
+ {IV('ю'), 0xEE, 0},
+ {IV('я'), 0xEF, 0}, // 044F
+ {IV('ё'), 0xF1, 0}, // 0451
+ {IV('є'), 0xF3, 0}, // 0454
+ {IV('і'), 'i', 0}, // 0456
+ {IV('ї'), 0xF5, 0}, // 0457
+ {IV('ў'), 0xF7, 0}, // 045E
+
+ #else
+
+ {IV('¢'), 0x5C, 0}, // 00A2
+ {IV('£'), 0xCF, 0}, // 00A3
+ {IV('°'), 0x01, 0}, // 00B0, Marlin special: '°' LCD_STR_DEGREE (0x09)
+
+ //{IV(''), 0x80, 0},
+ //{IV(''), 0x81, 0},
+ //{IV(''), 0x82, 0},
+ //{IV(''), 0x83, 0},
+ //{IV(''), 0x84, 0},
+ //{IV(''), 0x85, 0},
+ //{IV(''), 0x86, 0},
+ //{IV(''), 0x87, 0},
+ //{IV(''), 0x88, 0},
+ //{IV(''), 0x89, 0},
+ //{IV(''), 0x8A, 0},
+ //{IV(''), 0x8B, 0},
+ //{IV(''), 0x8C, 0},
+ //{IV(''), 0x8D, 0},
+ //{IV(''), 0x8E, 0},
+ //{IV(''), 0x8F, 0},
+
+ //{IV(''), 0x90, 0},
+ //{IV(''), 0x91, 0},
+ //{IV(''), 0x92, 0},
+ //{IV(''), 0x93, 0},
+ //{IV(''), 0x94, 0},
+ //{IV(''), 0x95, 0},
+ //{IV(''), 0x96, 0},
+ //{IV(''), 0x97, 0},
+ //{IV(''), 0x98, 0},
+ //{IV(''), 0x99, 0},
+ //{IV(''), 0x9A, 0},
+ //{IV(''), 0x9B, 0},
+ //{IV(''), 0x9C, 0},
+ //{IV(''), 0x9D, 0},
+ //{IV(''), 0x9E, 0},
+ //{IV(''), 0x9F, 0},
+
+ {IV('¼'), 0xF0, 0}, // 00BC
+ {IV('⅓'), 0xF1, 0},
+ {IV('½'), 0xF2, 0}, // 00BD
+ {IV('¾'), 0xF3, 0}, // 00BE
+ {IV('¿'), 0xCD, 0}, // 00BF
+
+ {IV('Ё'), 0xA2, 0}, // 0401
+ {IV('А'), 'A', 0}, // 0410
+ {IV('Б'), 0xA0, 0},
+ {IV('В'), 'B', 0},
+ {IV('Г'), 0xA1, 0},
+ {IV('Д'), 0xE0, 0},
+ {IV('Е'), 'E', 0},
+ {IV('Ж'), 0xA3, 0},
+ {IV('З'), 0xA4, 0},
+ {IV('И'), 0xA5, 0},
+ {IV('Й'), 0xA6, 0},
+ {IV('К'), 'K', 0},
+ {IV('Л'), 0xA7, 0},
+ {IV('М'), 'M', 0},
+ {IV('Н'), 'H', 0},
+ {IV('О'), 'O', 0},
+ {IV('П'), 0xA8, 0},
+ {IV('Р'), 'P', 0},
+ {IV('С'), 'C', 0},
+ {IV('Т'), 'T', 0},
+ {IV('У'), 0xA9, 0},
+ {IV('Ф'), 0xAA, 0},
+ {IV('Х'), 'X', 0},
+ {IV('Ц'), 0xE1, 0},
+ {IV('Ч'), 0xAB, 0},
+ {IV('Ш'), 0xAC, 0},
+ {IV('Щ'), 0xE2, 0},
+ {IV('Ъ'), 0xAD, 0},
+ {IV('Ы'), 0xAE, 0},
+ {IV('Ь'), 'b', 0},
+ {IV('Э'), 0xAF, 0},
+ {IV('Ю'), 0xB0, 0},
+ {IV('Я'), 0xB1, 0},
+ {IV('а'), 'a', 0},
+
+ {IV('б'), 0xB2, 0},
+ {IV('в'), 0xB3, 0},
+ {IV('г'), 0xB4, 0},
+ {IV('д'), 0xE3, 0},
+ {IV('е'), 'e', 0},
+ {IV('ж'), 0xB6, 0},
+ {IV('з'), 0xB7, 0},
+ {IV('и'), 0xB8, 0},
+ {IV('й'), 0xB9, 0},
+ {IV('к'), 0xBA, 0},
+ {IV('л'), 0xBB, 0},
+ {IV('м'), 0xBC, 0},
+ {IV('н'), 0xBD, 0},
+ {IV('о'), 'o', 0},
+ {IV('п'), 0xBE, 0},
+ {IV('р'), 'p', 0},
+ {IV('с'), 'c', 0},
+ {IV('т'), 0xBF, 0},
+
+ {IV('у'), 'y', 0},
+ {IV('ф'), 0xE4, 0},
+ {IV('х'), 'x', 0},
+ {IV('ц'), 0xE5, 0},
+ {IV('ч'), 0xC0, 0},
+ {IV('ш'), 0xC1, 0},
+ {IV('щ'), 0xE6, 0},
+ {IV('ъ'), 0xC2, 0},
+ {IV('ы'), 0xC3, 0},
+ {IV('ь'), 0xC4, 0},
+ {IV('э'), 0xC5, 0},
+ {IV('ю'), 0xC6, 0},
+ {IV('я'), 0xC7, 0}, // 044F
+ {IV('ё'), 0xB5, 0}, // 0451
+ //{IV(''), 0xC8, 0},
+ //{IV(''), 0xC9, 0},
+ //{IV(''), 0xCA, 0},
+ //{IV(''), 0xCB, 0},
+ //{IV(''), 0xCC, 0},
+ //{IV(''), 0xCD, 0},
+ //{IV(''), 0xCE, 0},
+
+ //{IV(''), 0xD0, 0},
+ //{IV(''), 0xD1, 0},
+ //{IV(''), 0xD2, 0},
+ //{IV(''), 0xD3, 0},
+ //{IV(''), 0xD4, 0},
+ //{IV(''), 0xD5, 0},
+ //{IV(''), 0xD6, 0},
+ //{IV(''), 0xD7, 0},
+ //{IV(''), 0xD8, 0},
+ //{IV(''), 0xDB, 0},
+ //{IV(''), 0xDC, 0},
+ //{IV(''), 0xDD, 0},
+ //{IV(''), 0xDE, 0},
+ //{IV(''), 0xDF, 0},
+
+ //{IV(''), 0xE7, 0},
+ //{IV(''), 0xE8, 0},
+ //{IV(''), 0xE9, 0},
+ //{IV(''), 0xEA, 0},
+ //{IV(''), 0xEB, 0},
+ //{IV(''), 0xEC, 0},
+ //{IV(''), 0xED, 0},
+ //{IV(''), 0xEE, 0},
+ //{IV(''), 0xEF, 0},
+
+ //{IV(''), 0xF4, 0},
+ //{IV(''), 0xF5, 0},
+ //{IV(''), 0xF6, 0},
+ //{IV(''), 0xF7, 0},
+ //{IV(''), 0xF8, 0},
+ //{IV(''), 0xF9, 0},
+ //{IV(''), 0xFA, 0},
+ //{IV(''), 0xFB, 0},
+ //{IV(''), 0xFC, 0},
+ //{IV(''), 0xFD, 0},
+ //{IV(''), 0xFE, 0},
+ //{IV(''), 0xFF, 0},
+
+ {IV('↑'), 0xD9, 0}, // 2191 ←↑→↓
+ {IV('↓'), 0xDA, 0}, // 2193
+
+ #endif
+
+ #endif
+};
+
+// the plain ASCII replacement for various char
+static const TFTGLCD_charmap_t g_TFTGLCD_charmap_common[] PROGMEM = {
+ {IV('¡'), 'i', 0}, // A1
+ {IV('¢'), 'c', 0}, // A2
+ {IV('°'), 0x09, 0}, // B0 Marlin special: '°' LCD_STR_DEGREE (0x09)
+
+ #ifndef CONVERT_TO_EXT_ASCII //this time CONVERT_TO_EXT_ASCII works only with en, ru and uk languages
+
+ // map WESTERN code to the plain ASCII
+ {IV('Á'), 'A', 0}, // C1
+ {IV('Â'), 'A', 0}, // C2
+ {IV('Ã'), 'A', 0}, // C3
+ {IV('Ä'), 'A', 0}, // C4
+ {IV('Å'), 'A', 0}, // C5
+ {IV('Æ'), 'A', 'E'}, // C6
+ {IV('Ç'), 'C', 0}, // C7
+ {IV('È'), 'E', 0}, // C8
+ {IV('É'), 'E', 0}, // C9
+ {IV('Í'), 'I', 0}, // CD
+ {IV('Ñ'), 'N', 0}, // D1
+ {IV('Õ'), 'O', 0}, // D5
+ {IV('Ö'), 'O', 0}, // D6
+ {IV('×'), 'x', 0}, // D7
+ {IV('Ü'), 'U', 0}, // DC
+ {IV('Ý'), 'Y', 0}, // DD
+ {IV('à'), 'a', 0}, // E0
+ {IV('á'), 'a', 0},
+ {IV('â'), 'a', 0},
+ {IV('ã'), 'a', 0},
+ {IV('ä'), 'a', 0},
+ {IV('å'), 'a', 0},
+ {IV('æ'), 'a', 'e'},
+ {IV('ç'), 'c', 0},
+ {IV('è'), 'e', 0}, // 00E8
+ {IV('é'), 'e', 0},
+ {IV('ê'), 'e', 0},
+ {IV('ë'), 'e', 0},
+ {IV('ì'), 'i', 0}, // 00EC
+ {IV('í'), 'i', 0},
+ {IV('î'), 'i', 0},
+ {IV('ï'), 'i', 0}, // 00EF
+
+ {IV('ñ'), 'n', 0}, // 00F1
+ {IV('ò'), 'o', 0},
+ {IV('ó'), 'o', 0},
+ {IV('ô'), 'o', 0},
+ {IV('õ'), 'o', 0},
+ {IV('ö'), 'o', 0},
+ //{IV('÷'), 0xB8, 0},
+ {IV('ø'), 'o', 0},
+ {IV('ù'), 'u', 0},
+ {IV('ú'), 'u', 0},
+ {IV('û'), 'u', 0},
+ {IV('ü'), 'u', 0}, // FC
+ {IV('ý'), 'y', 0}, // FD
+ {IV('ÿ'), 'y', 0}, // FF
+
+ {IV('Ą'), 'A', 0}, // 0104
+ {IV('ą'), 'a', 0}, // 0105
+ {IV('Ć'), 'C', 0}, // 0106
+ {IV('ć'), 'c', 0}, // 0107
+ {IV('Č'), 'C', 0}, // 010C
+ {IV('č'), 'c', 0}, // 010D
+ {IV('Ď'), 'D', 0}, // 010E
+ {IV('ď'), 'd', 0}, // 010F
+ {IV('đ'), 'd', 0}, // 0111
+ {IV('ę'), 'e', 0}, // 0119
+ {IV('ğ'), 'g', 0}, // 011F
+ {IV('İ'), 'I', 0}, // 0130
+ {IV('ı'), 'i', 0}, // 0131
+
+ {IV('Ł'), 'L', 0}, // 0141
+ {IV('ł'), 'l', 0}, // 0142
+ {IV('Ń'), 'N', 0}, // 0143
+ {IV('ń'), 'n', 0}, // 0144
+ {IV('ň'), 'n', 0}, // 0148
+
+ {IV('ř'), 'r', 0}, // 0159
+ {IV('Ś'), 'S', 0}, // 015A
+ {IV('ś'), 's', 0}, // 015B
+ {IV('ş'), 's', 0}, // 015F
+ {IV('Š'), 'S', 0}, // 0160
+ {IV('š'), 's', 0}, // 0161
+ {IV('ť'), 't', 0}, // 0165
+ {IV('ů'), 'u', 0}, // 016F
+ {IV('ż'), 'z', 0}, // 017C
+ {IV('Ž'), 'Z', 0}, // 017D
+ {IV('ž'), 'z', 0}, // 017E
+ {IV('ƒ'), 'f', 0}, // 0192
+
+ {IV('ˣ'), 'x', 0}, // 02E3
+
+ {IV('΄'), '\'', 0}, // 0384
+ {IV('΅'), '\'', 0}, // 0385
+ {IV('Ά'), 'A', 0}, // 0386
+ {IV('·'), '.', 0}, // 0387
+ {IV('Έ'), 'E', 0}, // 0388
+ {IV('Ή'), 'H', 0}, // 0389
+ {IV('Ί'), 'I', 0}, // 038A
+ {IV('Ό'), 'O', 0}, // 038C
+ {IV('Ύ'), 'Y', 0}, // 038E
+ {IV('Ώ'), 'O', 0}, // 038F
+ {IV('ΐ'), 'i', 0}, // 0390
+ {IV('Α'), 'A', 0}, // 0391
+ {IV('Β'), 'B', 0}, // 0392
+ {IV('Γ'), 'T', 0}, // 0393, Gamma
+ {IV('Δ'), '4', 0}, // 0394, Delta, ◿
+ {IV('Ε'), 'E', 0}, // 0395
+ {IV('Ζ'), 'Z', 0}, // 0396
+ {IV('Η'), 'H', 0}, // 0397
+ {IV('Θ'), '0', 0}, // 0398, Theta
+ {IV('Ι'), 'I', 0}, // 0399
+ {IV('Κ'), 'K', 0}, // 039A
+ {IV('Λ'), '^', 0}, // 039B, Lambda
+ {IV('Μ'), 'M', 0}, // 039C
+ {IV('Ν'), 'N', 0}, // 039D
+ {IV('Ξ'), '3', 0}, // 039E, Xi
+ {IV('Ο'), 'O', 0}, // 039F
+ {IV('Π'), 'n', 0}, // 03A0, Pi
+ {IV('Ρ'), 'P', 0}, // 03A1
+ {IV('Σ'), 'E', 0}, // 03A3, Sigma
+ {IV('Τ'), 'T', 0}, // 03A4
+ {IV('Υ'), 'Y', 0}, // 03A5, Upsilon
+ {IV('Φ'), 'p', 0}, // 03A6, Phi
+ {IV('Χ'), 'X', 0}, // 03A7
+ {IV('Ψ'), 'P', 0}, // 03A8, Psi
+ {IV('Ω'), 'O', 0}, // 03A9, Omega
+ {IV('Ϊ'), 'I', 0}, // 03AA
+ {IV('Ϋ'), 'Y', 0}, // 03AB
+ {IV('ά'), 'a', 0}, // 03AC
+ {IV('έ'), 'e', 0}, // 03AD
+ {IV('ή'), 'n', 0}, // 03AE
+ {IV('ί'), 'i', 0}, // 03AF
+ {IV('ΰ'), 'v', 0}, // 03B0
+ {IV('α'), 'a', 0}, // 03B1, alpha
+ {IV('β'), 'B', 0}, // 03B2, beta
+ {IV('γ'), 'v', 0}, // 03B3, gamma
+ {IV('δ'), 'd', 0}, // 03B4, delta
+ {IV('ε'), 'e', 0}, // 03B5, epsilon
+ {IV('ζ'), 'Z', 0}, // 03B6, zeta
+ {IV('η'), 'n', 0}, // 03B7, eta
+ {IV('θ'), '0', 0}, // 03B8, theta
+ {IV('ι'), 'i', 0}, // 03B9, lota
+ {IV('κ'), 'k', 0}, // 03BA, kappa
+ {IV('λ'), 'L', 0}, // 03BB, lambda
+ {IV('μ'), 'u', 0}, // 03BC, mu
+ {IV('ν'), 'v', 0}, // 03BD, nu
+ {IV('ξ'), 'e', 0}, // 03BE, xi
+ {IV('ο'), 'o', 0}, // 03BF
+ {IV('π'), 'n', 0}, // 03C0, pi
+ {IV('ρ'), 'p', 0}, // 03C1, rho
+ {IV('ς'), 'c', 0}, // 03C2
+ {IV('σ'), 'o', 0}, // 03C3, sigma
+ {IV('τ'), 't', 0}, // 03C4, tau
+ {IV('υ'), 'v', 0}, // 03C5, upsilon
+ {IV('φ'), 'p', 0}, // 03C6
+ {IV('χ'), 'X', 0}, // 03C7, chi
+ {IV('ψ'), 'W', 0}, // 03C8, psi
+ {IV('ω'), 'w', 0}, // 03C9, omega
+ {IV('ϊ'), 'i', 0}, // 03CA
+ {IV('ϋ'), 'v', 0}, // 03CB
+ {IV('ό'), 'o', 0}, // 03CC
+ {IV('ύ'), 'v', 0}, // 03CD
+ {IV('ώ'), 'w', 0}, // 03CE
+
+ // map CYRILLIC code to the plain ASCII
+ {IV('А'), 'A', 0}, // 0410
+ {IV('Б'), 'b', 0}, // 0411
+ {IV('В'), 'B', 0}, // 0412
+ {IV('Г'), 'T', 0}, // 0413
+ {IV('Д'), 'Q', 0}, // 0414
+ {IV('Е'), 'E', 0}, // 0415
+ {IV('Ж'), '*', 0}, // 0416
+ {IV('З'), 'E', 0}, // 0417
+ {IV('И'), 'N', 0}, // 0418
+ {IV('Й'), 'N', 0}, // 0419
+ {IV('К'), 'K', 0}, // 041A
+ {IV('Л'), 'T', 0}, // 041B
+ {IV('М'), 'M', 0}, // 041C
+ {IV('Н'), 'H', 0}, // 041D
+ {IV('О'), 'O', 0}, // 041E
+ {IV('П'), 'n', 0}, // 041F
+ {IV('Р'), 'P', 0}, // 0420
+ {IV('С'), 'C', 0}, // 0421
+ {IV('Т'), 'T', 0}, // 0422
+ {IV('У'), 'Y', 0},
+ {IV('Ф'), 'o', 0},
+ {IV('Х'), 'X', 0},
+ {IV('Ц'), 'U', 0},
+ {IV('Ч'), 'y', 0},
+ {IV('Ш'), 'W', 0},
+ {IV('Щ'), 'W', 0},
+ {IV('Ъ'), 'b', 0},
+ {IV('Ы'), 'b', '|'},
+ {IV('Ь'), 'b'},
+ {IV('Э'), 'e'},
+ {IV('Ю'), '|', 'O'},
+ {IV('Я'), '9', '|'}, // 042F
+
+ {IV('а'), 'a', 0}, // 0430
+ {IV('б'), '6', 0}, // 0431
+ {IV('в'), 'B', 0}, // 0432,
+ {IV('г'), 'r', 0}, // 0433
+ {IV('д'), 'a', 0}, // 0434,
+ {IV('е'), 'e', 0}, // 0435
+ {IV('ж'), '*', 0}, // 0436
+ {IV('з'), 'e', 0}, // 0437,
+ {IV('и'), 'u', 0}, // 0438
+ {IV('й'), 'u', 0}, // 0439,
+ {IV('к'), 'k', 0}, // 043A
+ {IV('л'), 'n', 0},
+ {IV('м'), 'm', 0},
+ {IV('н'), 'H', 0},
+ {IV('о'), 'o', 0},
+ {IV('п'), 'n', 0},
+ {IV('р'), 'p', 0},
+ {IV('с'), 'c', 0},
+ {IV('т'), 't', 0},
+ {IV('у'), 'y', 0},
+ {IV('ф'), 'q', 'p'},
+ {IV('х'), 'x', 0},
+ {IV('ц'), 'u', 0},
+ {IV('ч'), 'y', 0},
+ {IV('ш'), 'w', 0},
+ {IV('щ'), 'w', 0},
+ {IV('ъ'), 'b', 0},
+ {IV('ы'), 'b', '|'},
+ {IV('ь'), 'b', 0},
+ {IV('э'), 'e', 0},
+ {IV('ю'), '|', 'o'},
+ {IV('я'), 'g', 0}, // 044F
+
+ #endif
+
+ {IV('•'), '.', 0}, // 2022 ·
+ {IV('℞'), 'P', 'x'}, // 211E ℞ Pt ASCII 158
+ {IV('™'), 'T', 'M'}, // 2122
+ {IV('←'), '<', '-'}, // 2190
+ {IV('→'), '-', '>'}, // 2192, Marlin special: '⮈⮉⮊⮋➤→⏵➟➠➡' LCD_STR_ARROW_RIGHT (0x03)
+ //{IV('↰'), '<', 0}, // 21B0, Marlin special: '⮥⮭⮉⇧↑↰⤴' LCD_STR_UPLEVEL (0x04)
+ {IV('↰'), 0x03, 0}, // 21B0, Marlin special: '⮥⮭⮉⇧↑↰⤴' LCD_STR_UPLEVEL (0x04)
+ {IV('↻'), 0x04, 0}, // 21BB Marlin special: '↻↺⟳⟲' LCD_STR_REFRESH (0x01)
+ {IV('∼'), '~', 0}, // 223C
+ {IV('≈'), '~', '='}, // 2248
+ {IV('≠'), '!', '='}, // 2260
+ {IV('≡'), '=', 0}, // 2261
+ {IV('≤'), '<', '='},// 2264, ≤≥ ⩽⩾
+ {IV('≥'), '>', '='}, // 2265
+ {IV('⏱'), 0x07, 0}, // 23F1, Marlin special: '🕐🕑🕒🕓🕔🕕🕖🕗🕘🕙🕚🕛🕜🕝🕞🕟🕠🕡🕢🕣🕤🕥🕦🕧 ⌚⌛⏰⏱⏳⧖⧗' LCD_STR_CLOCK (0x05)
+
+ {IV('゠'), '=', 0}, // 30A0
+
+ // ⏰⏱⏲⏳◴◵◶◷
+ // ⏻⏼♁♂
+ //{IV(''), 0x00, 0}, // Marlin special: '' LCD_STR_BEDTEMP (0x07)
+ {IV('🌡'), 0x02, 0}, // D83CDF21 Marlin special: '🌡' LCD_STR_THERMOMETER (0x08)
+ {IV('📂'), 0x05, 0}, // D83DDCC2 Marlin special: '📁📂' LCD_STR_FOLDER (0x02)
+ //{IV(''), 0x06, 0}, // Marlin special: '' LCD_STR_FEEDRATE (0x06)
+};
+
+/* return v1 - v2 */
+static int TFTGLCD_charmap_compare(TFTGLCD_charmap_t * v1, TFTGLCD_charmap_t * v2) {
+ return (v1->uchar < v2->uchar) ? -1 : (v1->uchar > v2->uchar) ? 1 : 0;
+}
+
+static int pf_bsearch_cb_comp_hd4map_pgm(void *userdata, size_t idx, void * data_pin) {
+ TFTGLCD_charmap_t localval;
+ TFTGLCD_charmap_t *p_TFTGLCD_charmap = (TFTGLCD_charmap_t *)userdata;
+ memcpy_P(&localval, p_TFTGLCD_charmap + idx, sizeof(localval));
+ return TFTGLCD_charmap_compare(&localval, (TFTGLCD_charmap_t *)data_pin);
+}
+
+void lcd_moveto(const lcd_uint_t col, const lcd_uint_t row) { lcd.setCursor(col, row); }
+
+void lcd_put_int(const int i) {
+ const char* str = i16tostr3left(i);
+ while (*str) lcd.write(*str++);
+}
+
+// return < 0 on error
+// return the advanced cols
+int lcd_put_lchar_max(const lchar_t &c, const pixel_len_t max_length) {
+
+ // find the HD44780 internal ROM first
+ int ret;
+ size_t idx = 0;
+ TFTGLCD_charmap_t pinval;
+ TFTGLCD_charmap_t *copy_address = nullptr;
+ pinval.uchar = c;
+ pinval.idx = -1;
+
+ if (max_length < 1) return 0;
+
+ if (c < 128) {
+ lcd.write((uint8_t)c);
+ return 1;
+ }
+ copy_address = nullptr;
+ ret = pf_bsearch_r((void *)g_TFTGLCD_charmap_device, COUNT(g_TFTGLCD_charmap_device), pf_bsearch_cb_comp_hd4map_pgm, (void *)&pinval, &idx);
+ if (ret >= 0) {
+ copy_address = (TFTGLCD_charmap_t *)(g_TFTGLCD_charmap_device + idx);
+ }
+ else {
+ ret = pf_bsearch_r((void *)g_TFTGLCD_charmap_common, COUNT(g_TFTGLCD_charmap_common), pf_bsearch_cb_comp_hd4map_pgm, (void *)&pinval, &idx);
+ if (ret >= 0) copy_address = (TFTGLCD_charmap_t *)(g_TFTGLCD_charmap_common + idx);
+ }
+
+ if (ret >= 0) {
+ TFTGLCD_charmap_t localval;
+ // found
+ memcpy_P(&localval, copy_address, sizeof(localval));
+ lcd.write(localval.idx);
+ if (max_length >= 2 && localval.idx2 > 0) {
+ lcd.write(localval.idx2);
+ return 2;
+ }
+ return 1;
+ }
+
+ // Not found, print '?' instead
+ lcd.write((uint8_t)'?');
+ return 1;
+}
+
+/**
+ * @brief Draw a UTF-8 string
+ *
+ * @param utf8_str : the UTF-8 string
+ * @param cb_read_byte : the callback function to read one byte from the utf8_str (from RAM or ROM)
+ * @param max_length : the pixel length of the string allowed (or number of slots in HD44780)
+ *
+ * @return the number of pixels advanced
+ *
+ * Draw a UTF-8 string
+ */
+static int lcd_put_u8str_max_cb(const char * utf8_str, read_byte_cb_t cb_read_byte, const pixel_len_t max_length) {
+ pixel_len_t ret = 0;
+ const uint8_t *p = (uint8_t *)utf8_str;
+ while (ret < max_length) {
+ lchar_t wc;
+ p = get_utf8_value_cb(p, cb_read_byte, wc);
+ if (!wc) break;
+ ret += lcd_put_lchar_max(wc, max_length - ret);
+ }
+ return (int)ret;
+}
+
+int lcd_put_u8str_max(const char * utf8_str, const pixel_len_t max_length) {
+ return lcd_put_u8str_max_cb(utf8_str, read_byte_ram, max_length);
+}
+
+int lcd_put_u8str_max_P(PGM_P utf8_pstr, const pixel_len_t max_length) {
+ return lcd_put_u8str_max_cb(utf8_pstr, read_byte_rom, max_length);
+}
+
+#if ENABLED(DEBUG_LCDPRINT)
+
+ int test_TFTGLCD_charmap(TFTGLCD_charmap_t *data, size_t size, char *name, char flg_show_contents) {
+ int ret;
+ size_t idx = 0;
+ TFTGLCD_charmap_t preval = {0, 0, 0};
+ TFTGLCD_charmap_t pinval = {0, 0, 0};
+ char flg_error = 0;
+
+ int i;
+
+ TRACE("Test %s\n", name);
+
+ for (i = 0; i < size; i ++) {
+ memcpy_P(&pinval, &(data[i]), sizeof(pinval));
+
+ if (flg_show_contents) {
+ #if 1
+ TRACE("[% 4d] % 6" PRIu32 "(0x%04" PRIX32 ") --> 0x%02X,0x%02X%s\n", i, pinval.uchar, pinval.uchar, (unsigned int)(pinval.idx), (unsigned int)(pinval.idx2), (preval.uchar < pinval.uchar?"":" <--- ERROR"));
+ #else
+ TRACE("[% 4d]", i);
+ TRACE("% 6" PRIu32 "(0x%04" PRIX32 "),", pinval.uchar, pinval.uchar);
+ TRACE("0x%02X,", (unsigned int)(pinval.idx));
+ TRACE("0x%02X,", (unsigned int)(pinval.idx2));
+ TRACE("%s", (preval.uchar < pinval.uchar?"":" <--- ERROR"));
+ #endif
+ }
+ if (preval.uchar >= pinval.uchar) {
+ flg_error = 1;
+ //TRACE("Error: out of order in array %s: idx=%d, val=%d(0x%x)\n", name, i, pinval.uchar, pinval.uchar);
+ //return -1;
+ }
+ memcpy(&preval, &pinval, sizeof(pinval));
+
+ ret = pf_bsearch_r((void *)data, size, pf_bsearch_cb_comp_hd4map_pgm, (void *)&pinval, &idx);
+ if (ret < 0) {
+ flg_error = 1;
+ TRACE("Error: not found item in array %s: idx=%d, val=%d(0x%x)\n", name, i, pinval.uchar, pinval.uchar);
+ //return -1;
+ }
+ if (idx != i) {
+ flg_error = 1;
+ TRACE("Error: wrong index found item in array %s: idx=%d, val=%d(0x%x)\n", name, i, pinval.uchar, pinval.uchar);
+ //return -1;
+ }
+ }
+ if (flg_error) {
+ TRACE("\nError: in array %s\n\n", name);
+ return -1;
+ }
+ TRACE("\nPASS array %s\n\n", name);
+ return 0;
+ }
+
+ int test_TFTGLCD_charmap_all() {
+ int flg_error = 0;
+ if (test_TFTGLCD_charmap(g_TFTGLCD_charmap_device, COUNT(g_TFTGLCD_charmap_device), "g_TFTGLCD_charmap_device", 0) < 0) {
+ flg_error = 1;
+ test_TFTGLCD_charmap(g_TFTGLCD_charmap_device, COUNT(g_TFTGLCD_charmap_device), "g_TFTGLCD_charmap_device", 1);
+ }
+ if (test_TFTGLCD_charmap(g_TFTGLCD_charmap_common, COUNT(g_TFTGLCD_charmap_common), "g_TFTGLCD_charmap_common", 0) < 0) {
+ flg_error = 1;
+ test_TFTGLCD_charmap(g_TFTGLCD_charmap_common, COUNT(g_TFTGLCD_charmap_common), "g_TFTGLCD_charmap_common", 1);
+ }
+ if (flg_error) {
+ TRACE("\nFAILED in hd44780 tests!\n");
+ return -1;
+ }
+ TRACE("\nPASS in hd44780 tests.\n");
+ return 0;
+ }
+
+#endif // DEBUG_LCDPRINT
+
+#endif // IS_TFTGLCD_PANEL
diff --git a/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp b/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp
new file mode 100644
index 0000000..f3d98ec
--- /dev/null
+++ b/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp
@@ -0,0 +1,1094 @@
+/**
+ * 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 .
+ *
+ */
+
+#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
+#else
+ #include
+#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 EITHER(HAS_COOLER, LASER_COOLANT_FLOW_METER)
+ #include "../../feature/cooler.h"
+#endif
+
+#if ENABLED(I2C_AMMETER)
+ #include "../../feature/ammeter.h"
+#endif
+
+#if HAS_CUTTER
+ #include "../../feature/spindle_laser.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 standard 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() {
+ 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 (!sound_on) 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();
+}
+
+#if HAS_LCD_CONTRAST
+ void MarlinUI::_set_contrast() { lcd.setContrast(contrast); }
+#endif
+
+#if !IS_TFTGLCD_PANEL
+ void lcd_moveto(const uint8_t col, const uint8_t row) { lcd.setCursor(col, row); }
+#endif
+
+static void center_text(FSTR_P const fstart, const uint8_t y) {
+ const uint8_t len = utf8_strlen(fstart);
+ lcd_moveto(len < LCD_WIDTH ? (LCD_WIDTH - len) / 2 : 0, y);
+ lcd_put_u8str(fstart);
+}
+
+#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 beginning ASCII table as special symbols
+ lcd_moveto(indent, 0); lcd.write(TLC); lcd_put_u8str(F("------")); lcd.write(TRC);
+ lcd_moveto(indent, 1); lcd.write(LR); lcd_put_u8str(F("Marlin")); lcd.write(LR);
+ lcd_moveto(indent, 2); lcd.write(BLC); lcd_put_u8str(F("------")); lcd.write(BRC);
+ center_text(F(SHORT_BUILD_VERSION), 3);
+ center_text(F(MARLIN_WEBSITE_URL), 4);
+ picBits = ICON_LOGO;
+ lcd.print_screen();
+ }
+
+ void MarlinUI::bootscreen_completion(const millis_t sofar) {
+ if ((BOOTSCREEN_TIMEOUT) > sofar) safe_delay((BOOTSCREEN_TIMEOUT) - sofar);
+ }
+
+#endif // SHOW_BOOTSCREEN
+
+void MarlinUI::draw_kill_screen() {
+ if (!PanelDetected) return;
+ lcd.clear_buffer();
+ lcd_moveto(0, 3); lcd.write(COLOR_ERROR);
+ lcd_moveto((LCD_WIDTH - utf8_strlen(status_message)) / 2 + 1, 3);
+ lcd_put_u8str(status_message);
+ center_text(GET_TEXT_F(MSG_HALTED), 5);
+ center_text(GET_TEXT_F(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(axis == Z_AXIS ? F(" ") : F(" "));
+ else
+ lcd_put_u8str(value);
+}
+
+#if HAS_HOTEND || HAS_HEATED_BED
+
+ 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 celsius_t t1 = (isBed ? thermalManager.wholeDegBed() : thermalManager.wholeDegHotend(heater_id)),
+ t2 = (isBed ? thermalManager.degTargetBed() : thermalManager.degTargetHotend(heater_id));
+ #else
+ const celsius_t t1 = thermalManager.wholeDegHotend(heater_id), t2 = thermalManager.degTargetHotend(heater_id);
+ #endif
+
+ #if HOTENDS < 2
+ if (heater_id == H_E0) {
+ lcd_moveto(2, 5); lcd.print(prefix); //HE
+ lcd_moveto(1, 6); lcd.print(i16tostr3rj(t1));
+ lcd_moveto(1, 7);
+ }
+ else {
+ lcd_moveto(6, 5); lcd.print(prefix); //BED
+ lcd_moveto(6, 6); lcd.print(i16tostr3rj(t1));
+ lcd_moveto(6, 7);
+ }
+ #else
+ if (heater_id > H_BED) {
+ lcd_moveto(heater_id * 4, 5); lcd.print(prefix); // HE1 or HE2 or HE3
+ lcd_moveto(heater_id * 4, 6); lcd.print(i16tostr3rj(t1));
+ lcd_moveto(heater_id * 4, 7);
+ }
+ else {
+ lcd_moveto(13, 5); lcd.print(prefix); //BED
+ lcd_moveto(13, 6); lcd.print(i16tostr3rj(t1));
+ lcd_moveto(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));
+
+ 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;
+ }
+
+#endif // HAS_HOTEND || HAS_HEATED_BED
+
+#if HAS_COOLER
+
+ FORCE_INLINE void _draw_cooler_status(const bool blink) {
+ const celsius_t t2 = thermalManager.degTargetCooler();
+
+ lcd_moveto(0, 5); lcd_put_u8str(F("COOL"));
+ lcd_moveto(1, 6); lcd_put_u8str(i16tostr3rj(thermalManager.wholeDegCooler()));
+ lcd_moveto(1, 7);
+
+ #if !HEATER_IDLE_HANDLER
+ UNUSED(blink);
+ #else
+ if (!blink && thermalManager.heater_idle[thermalManager.idle_index_for_id(heater_id)].timed_out) {
+ lcd_put_lchar(' ');
+ if (t2 >= 10) lcd_put_lchar(' ');
+ if (t2 >= 100) lcd_put_lchar(' ');
+ }
+ else
+ #endif
+ lcd_put_u8str(i16tostr3left(t2));
+
+ lcd_put_lchar(' ');
+ if (t2 < 10) lcd_put_lchar(' ');
+
+ if (t2) picBits |= ICON_TEMP1;
+ else picBits &= ~ICON_TEMP1;
+ }
+
+#endif // HAS_COOLER
+
+#if ENABLED(LASER_COOLANT_FLOW_METER)
+
+ FORCE_INLINE void _draw_flowmeter_status() {
+ lcd_moveto(5, 5); lcd_put_u8str(F("FLOW"));
+ lcd_moveto(7, 6); lcd_put_lchar('L');
+ lcd_moveto(6, 7); lcd_put_u8str(ftostr11ns(cooler.flowrate));
+
+ if (cooler.flowrate) picBits |= ICON_FAN;
+ else picBits &= ~ICON_FAN;
+ }
+
+#endif
+
+#if ENABLED(I2C_AMMETER)
+
+ FORCE_INLINE void _draw_ammeter_status() {
+ lcd_moveto(10, 5); lcd_put_u8str(F("ILAZ"));
+ ammeter.read();
+ lcd_moveto(11, 6);
+ if (ammeter.current <= 0.999f)
+ {
+ lcd_put_u8str("mA");
+ lcd_moveto(10, 7);
+ lcd_put_lchar(' '); lcd_put_u8str(ui16tostr3rj(uint16_t(ammeter.current * 1000 + 0.5f)));
+ }
+ else {
+ lcd_put_u8str(" A");
+ lcd_moveto(10, 7);
+ lcd_put_u8str(ftostr12ns(ammeter.current));
+ }
+
+ if (ammeter.current) picBits |= ICON_BED;
+ else picBits &= ~ICON_BED;
+ }
+
+#endif // I2C_AMMETER
+
+#if HAS_CUTTER
+
+ FORCE_INLINE void _draw_cutter_status() {
+ lcd_moveto(15, 5); lcd_put_u8str(F("CUTT"));
+ #if CUTTER_UNIT_IS(RPM)
+ lcd_moveto(16, 6); lcd_put_u8str(F("RPM"));
+ lcd_moveto(15, 7); lcd_put_u8str(ftostr31ns(float(cutter.unitPower) / 1000));
+ lcd_put_lchar('K');
+ #elif CUTTER_UNIT_IS(PERCENT)
+ lcd_moveto(17, 6); lcd_put_lchar('%');
+ lcd_moveto(18, 7); lcd_put_u8str(cutter_power2str(cutter.unitPower));
+ #else
+ lcd_moveto(17, 7); lcd_put_u8str(cutter_power2str(cutter.unitPower));
+ #endif
+
+ if (cutter.unitPower) picBits |= ICON_HOT;
+ else picBits &= ~ICON_HOT;
+ }
+
+#endif // HAS_CUTTER
+
+#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(F("SD"));
+ #elif ENABLED(LCD_SET_PROGRESS_MANUALLY)
+ lcd_put_u8str(F("P:"));
+ #endif
+ if (progress)
+ lcd.print(ui8tostr3rj(progress));
+ else
+ lcd_put_u8str(F("---"));
+ 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_moveto(LCD_WIDTH / 2 - 2, MIDDLE_Y);
+ lcd.print(i16tostr3rj(percent)); lcd.write('%');
+ lcd.print_line();
+ lcd_moveto(0, MIDDLE_Y + 1);
+ lcd.write('%'); lcd.write(percent);
+ lcd.print_line();
+ }
+ }
+
+#endif // LCD_PROGRESS_BAR
+
+void MarlinUI::draw_status_message(const bool blink) {
+ if (!PanelDetected) return;
+ lcd_moveto(0, 3);
+ #if BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT)
+
+ // Alternate Status message and Filament display
+ if (ELAPSED(millis(), next_filament_display)) {
+ lcd_put_u8str(F("Dia "));
+ lcd.print(ftostr12ns(filament_width_meas));
+ lcd_put_u8str(F(" 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) {
+ uint8_t chars = LCD_WIDTH - rlen; // Amount of space left in characters
+ lcd.write(' '); // Always at 1+ spaces left, draw a space
+ if (--chars) { // Draw a second space if there's room
+ lcd.write(' ');
+ if (--chars) { // Draw a third space if there's room
+ 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 - set 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
+
+|X 000 Y 000 Z 000.00|
+|FR100% SD100% C--:--|
+| Progress bar line |
+|Status message |
+| |
+|COOL FLOW ILAZ CUTT |
+| ttc L mA RPM |
+| tts f.f aaa rr.rK|
+| 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_moveto(0, 0);
+ const xyz_pos_t lpos = current_position.asLogical();
+ _draw_axis_value(X_AXIS, ftostr4sign(lpos.x), blink); lcd.write(' ');
+ _draw_axis_value(Y_AXIS, ftostr4sign(lpos.y), blink); lcd.write(' ');
+ _draw_axis_value(Z_AXIS, ftostr52sp(lpos.z), blink);
+
+ #if HAS_LEVELING && !HAS_HEATED_BED
+ lcd.write(planner.leveling_active || blink ? '_' : ' ');
+ #endif
+
+ //
+ // Line 2 - feedrate, , time
+ //
+
+ lcd_moveto(0, 1);
+ lcd_put_u8str(F("FR")); lcd.print(i16tostr3rj(feedrate_percentage)); lcd.write('%');
+
+ #if BOTH(SDSUPPORT, HAS_PRINT_PROGRESS)
+ lcd_moveto(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_moveto((LCD_WIDTH - 1) - len, 1);
+ lcd.write(LCD_STR_CLOCK[0]); lcd.print(buffer);
+
+ //
+ // Line 3 - progressbar
+ //
+
+ lcd_moveto(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_moveto(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 for printer or Cooler, Flowmetter, Ampermeter, Cutter for laser/spindle
+ //
+
+ #if HAS_HOTEND
+
+ #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
+
+ #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
+
+ #if HAS_FAN
+ uint16_t spd = thermalManager.fan_speed[0];
+ #if ENABLED(ADAPTIVE_FAN_SLOWING)
+ if (!blink) spd = thermalManager.scaledFanSpeed(0, spd);
+ #endif
+ uint16_t per = thermalManager.pwmToPercent(spd);
+
+ #if HOTENDS < 2
+ #define FANX 11
+ #else
+ #define FANX 17
+ #endif
+ lcd_moveto(FANX, 5); lcd_put_u8str(F("FAN"));
+ lcd_moveto(FANX + 1, 6); lcd.write('%');
+ lcd_moveto(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 // HAS_FAN
+
+ #else
+
+ TERN_(HAS_COOLER, _draw_cooler_status(blink));
+ TERN_(LASER_COOLANT_FLOW_METER, _draw_flowmeter_status());
+ TERN_(I2C_AMMETER, _draw_ammeter_status());
+ TERN_(HAS_CUTTER, _draw_cutter_status());
+
+ #endif
+
+ //
+ // Line 9, 10 - icons
+ //
+ lcd.print_screen();
+}
+
+#if HAS_MARLINUI_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_moveto((LCD_WIDTH - 14) / 2, row + 1);
+ lcd.write(LCD_STR_THERMOMETER[0]); lcd_put_u8str(F(" E")); lcd.write('1' + extruder); lcd.write(' ');
+ lcd.print(i16tostr3rj(thermalManager.wholeDegHotend(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, FSTR_P const fstr, const uint8_t style/*=SS_DEFAULT*/, const char * const valstr/*=nullptr*/) {
+ if (!PanelDetected) return;
+ uint8_t n = LCD_WIDTH;
+ lcd_moveto(0, row);
+ if ((style & SS_CENTER) && !valstr) {
+ int8_t pad = (LCD_WIDTH - utf8_strlen(fstr)) / 2;
+ while (--pad >= 0) { lcd.write(' '); n--; }
+ }
+ n = lcd_put_u8str(fstr, itemIndex, itemStringC, itemStringF, 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, FSTR_P const fstr, const char pre_char, const char post_char) {
+ if (!PanelDetected) return;
+ lcd_moveto(0, row);
+ lcd.write(sel ? pre_char : ' ');
+ uint8_t n = lcd_put_u8str(fstr, itemIndex, itemStringC, itemStringF, 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, FSTR_P const fstr, const char * const inStr, const bool pgm) {
+ if (!PanelDetected) return;
+ const uint8_t vlen = inStr ? (pgm ? utf8_strlen_P(inStr) : utf8_strlen(inStr)) : 0;
+ lcd_moveto(0, row);
+ lcd.write(sel ? LCD_STR_ARROW_RIGHT[0] : ' ');
+ uint8_t n = lcd_put_u8str(fstr, itemIndex, itemStringC, itemStringF, LCD_WIDTH - 2 - vlen);
+ if (vlen) {
+ lcd.write(':');
+ for (; n; --n) lcd.write(' ');
+ if (pgm) lcd_put_u8str_P(inStr); else lcd_put_u8str(inStr);
+ }
+ 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(FSTR_P const fstr, 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_moveto(0, y);
+ lcd.write(COLOR_EDIT);
+ lcd_put_u8str(fstr);
+ if (value) {
+ lcd.write(':');
+ lcd_moveto((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(FSTR_P const yes, FSTR_P const no, const bool yesno, FSTR_P const pref, const char * const string, FSTR_P const suff) {
+ if (!PanelDetected) return;
+ ui.draw_select_screen_prompt(pref, string, suff);
+ lcd.write(COLOR_EDIT);
+ if (no) {
+ lcd_moveto(0, MIDDLE_Y);
+ lcd.write(yesno ? ' ' : '['); lcd_put_u8str(no); lcd.write(yesno ? ' ' : ']');
+ }
+ if (yes) {
+ lcd_moveto(LCD_WIDTH - utf8_strlen(yes) - 3, MIDDLE_Y);
+ lcd.write(yesno ? '[' : ' '); lcd_put_u8str(yes); lcd.write(yesno ? ']' : ' ');
+ }
+ lcd.print_line();
+ }
+
+ #if ENABLED(SDSUPPORT)
+
+ void MenuItem_sdbase::draw(const bool sel, const uint8_t row, FSTR_P const, CardReader &theCard, const bool isDir) {
+ if (!PanelDetected) return;
+ lcd_moveto(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_moveto(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_moveto(_LCD_W_POS, 0);
+ *fb++ = '('; lcd.print(i16tostr3left(x_plot));
+ *fb++ = ','; lcd.print(i16tostr3left(y_plot)); *fb = ')';
+
+ // Show all values
+ lcd_moveto(_LCD_W_POS, 1); lcd_put_u8str(F("X:"));
+ lcd.print(ftostr52(LOGICAL_X_POSITION(pgm_read_float(&bedlevel._mesh_index_to_xpos[x_plot]))));
+ lcd_moveto(_LCD_W_POS, 2); lcd_put_u8str(F("Y:"));
+ lcd.print(ftostr52(LOGICAL_Y_POSITION(pgm_read_float(&bedlevel._mesh_index_to_ypos[y_plot]))));
+
+ // Show the location value
+ lcd_moveto(_LCD_W_POS, 3); lcd_put_u8str(F("Z:"));
+
+ if (!isnan(bedlevel.z_values[x_plot][y_plot]))
+ lcd.print(ftostr43sign(bedlevel.z_values[x_plot][y_plot]));
+ else
+ lcd_put_u8str(F(" -----"));
+
+ center_text(GET_TEXT_F(MSG_UBL_FINE_TUNE_MESH), 8);
+
+ lcd.print_screen();
+ }
+
+ #endif // AUTO_BED_LEVELING_UBL
+
+#endif // HAS_MARLINUI_MENU
+
+#endif // IS_TFTGLCD_PANEL
diff --git a/src/lcd/TFTGLCD/marlinui_TFTGLCD.h b/src/lcd/TFTGLCD/marlinui_TFTGLCD.h
new file mode 100644
index 0000000..c399b90
--- /dev/null
+++ b/src/lcd/TFTGLCD/marlinui_TFTGLCD.h
@@ -0,0 +1,72 @@
+/**
+ * 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 .
+ *
+ */
+#pragma once
+
+/**
+ * Implementation of the LCD display routines for a TFT GLCD displays with external controller.
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if IS_TFTGLCD_PANEL
+
+#include "../../libs/duration_t.h"
+
+////////////////////////////////////
+// Set up button and encode mappings for each panel (into 'buttons' variable)
+//
+// This is just to map common functions (across different panels) onto the same
+// macro name. The mapping is independent of whether the button is directly connected or
+// via a shift/i2c register.
+
+////////////////////////////////////
+// Create LCD class instance and chipset-specific information
+class TFTGLCD {
+ private:
+ public:
+ TFTGLCD();
+ void clear_buffer();
+ void clr_screen();
+ void setCursor(uint8_t col, uint8_t row);
+ void write(char c);
+ void print(const char *line);
+ void print_line();
+ void print_screen();
+ void redraw_screen();
+ void setContrast(uint16_t contrast);
+};
+
+extern TFTGLCD lcd;
+
+#include "../fontutils.h"
+#include "../lcdprint.h"
+
+// Use panel encoder - free old encoder pins
+#undef BTN_EN1
+#undef BTN_EN2
+#undef BTN_ENC
+
+#ifndef EN_C
+ #define EN_C 4 // for click
+#endif
+
+#endif // IS_TFTGLCD_PANEL
diff --git a/src/lcd/buttons.h b/src/lcd/buttons.h
new file mode 100644
index 0000000..2580a71
--- /dev/null
+++ b/src/lcd/buttons.h
@@ -0,0 +1,226 @@
+/**
+ * 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 .
+ *
+ */
+#pragma once
+
+#include "../inc/MarlinConfig.h"
+
+#if ((!HAS_ADC_BUTTONS && IS_NEWPANEL) || BUTTONS_EXIST(EN1, EN2)) && !IS_TFTGLCD_PANEL
+ #define HAS_ENCODER_WHEEL 1
+#endif
+#if (HAS_ENCODER_WHEEL || ANY_BUTTON(ENC, BACK, UP, DWN, LFT, RT)) && DISABLED(TOUCH_UI_FTDI_EVE)
+ #define HAS_DIGITAL_BUTTONS 1
+#endif
+#if !HAS_ADC_BUTTONS && (IS_RRW_KEYPAD || (HAS_WIRED_LCD && !IS_NEWPANEL))
+ #define HAS_SHIFT_ENCODER 1
+#endif
+
+// I2C buttons must be read in the main thread
+#if ANY(LCD_I2C_VIKI, LCD_I2C_PANELOLU2, IS_TFTGLCD_PANEL)
+ #define HAS_SLOW_BUTTONS 1
+#endif
+
+#if HAS_ENCODER_WHEEL
+ #define ENCODER_PHASE_0 0
+ #define ENCODER_PHASE_1 2
+ #define ENCODER_PHASE_2 3
+ #define ENCODER_PHASE_3 1
+#endif
+
+#if EITHER(HAS_DIGITAL_BUTTONS, HAS_DWIN_E3V2)
+ // Wheel spin pins where BA is 00, 10, 11, 01 (1 bit always changes)
+ #define BLEN_A 0
+ #define BLEN_B 1
+
+ #define EN_A _BV(BLEN_A)
+ #define EN_B _BV(BLEN_B)
+
+ #define _BUTTON_PRESSED(BN) !READ(BTN_##BN)
+
+ #if BUTTON_EXISTS(ENC) || HAS_TOUCH_BUTTONS
+ #define BLEN_C 2
+ #define EN_C _BV(BLEN_C)
+ #endif
+
+ #if ENABLED(LCD_I2C_VIKI)
+ #include
+ #define B_I2C_BTN_OFFSET 3 // (the first three bit positions reserved for EN_A, EN_B, EN_C)
+
+ // button and encoder bit positions within 'buttons'
+ #define B_LE (BUTTON_LEFT << B_I2C_BTN_OFFSET) // The remaining normalized buttons are all read via I2C
+ #define B_UP (BUTTON_UP << B_I2C_BTN_OFFSET)
+ #define B_MI (BUTTON_SELECT << B_I2C_BTN_OFFSET)
+ #define B_DW (BUTTON_DOWN << B_I2C_BTN_OFFSET)
+ #define B_RI (BUTTON_RIGHT << B_I2C_BTN_OFFSET)
+
+ #if BUTTON_EXISTS(ENC) // The pause/stop/restart button is connected to BTN_ENC when used
+ #define B_ST (EN_C) // Map the pause/stop/resume button into its normalized functional name
+ #define BUTTON_CLICK() (buttons & (B_MI|B_RI|B_ST)) // Pause/stop also acts as click until a proper pause/stop is implemented.
+ #else
+ #define BUTTON_CLICK() (buttons & (B_MI|B_RI))
+ #endif
+
+ // I2C buttons take too long to read inside an interrupt context and so we read them during lcd_update
+
+ #elif ENABLED(LCD_I2C_PANELOLU2)
+ #if !BUTTON_EXISTS(ENC) // Use I2C if not directly connected to a pin
+ #define B_I2C_BTN_OFFSET 3 // (the first three bit positions reserved for EN_A, EN_B, EN_C)
+
+ #define B_MI (PANELOLU2_ENCODER_C << B_I2C_BTN_OFFSET) // requires LiquidTWI2 library v1.2.3 or later
+
+ #define BUTTON_CLICK() (buttons & B_MI)
+ #endif
+ #endif
+#else
+ #undef BUTTON_EXISTS
+ #define BUTTON_EXISTS(...) false
+
+ // Dummy button, never pressed
+ #define _BUTTON_PRESSED(BN) false
+
+ // Shift register bits correspond to buttons:
+ #define BL_LE 7 // Left
+ #define BL_UP 6 // Up
+ #define BL_MI 5 // Middle
+ #define BL_DW 4 // Down
+ #define BL_RI 3 // Right
+ #define BL_ST 2 // Red Button
+ #define B_LE _BV(BL_LE)
+ #define B_UP _BV(BL_UP)
+ #define B_MI _BV(BL_MI)
+ #define B_DW _BV(BL_DW)
+ #define B_RI _BV(BL_RI)
+ #define B_ST _BV(BL_ST)
+
+ #ifndef BUTTON_CLICK
+ #if EN_C
+ #define BUTTON_CLICK() (buttons & (B_MI|B_ST))
+ #endif
+ #endif
+#endif
+
+#if IS_RRW_KEYPAD
+ #define BTN_OFFSET 0 // Bit offset into buttons for shift register values
+
+ #define BLEN_KEYPAD_F3 0
+ #define BLEN_KEYPAD_F2 1
+ #define BLEN_KEYPAD_F1 2
+ #define BLEN_KEYPAD_DOWN 3
+ #define BLEN_KEYPAD_RIGHT 4
+ #define BLEN_KEYPAD_MIDDLE 5
+ #define BLEN_KEYPAD_UP 6
+ #define BLEN_KEYPAD_LEFT 7
+
+ #define EN_KEYPAD_F1 _BV(BTN_OFFSET + BLEN_KEYPAD_F1)
+ #define EN_KEYPAD_F2 _BV(BTN_OFFSET + BLEN_KEYPAD_F2)
+ #define EN_KEYPAD_F3 _BV(BTN_OFFSET + BLEN_KEYPAD_F3)
+ #define EN_KEYPAD_DOWN _BV(BTN_OFFSET + BLEN_KEYPAD_DOWN)
+ #define EN_KEYPAD_RIGHT _BV(BTN_OFFSET + BLEN_KEYPAD_RIGHT)
+ #define EN_KEYPAD_MIDDLE _BV(BTN_OFFSET + BLEN_KEYPAD_MIDDLE)
+ #define EN_KEYPAD_UP _BV(BTN_OFFSET + BLEN_KEYPAD_UP)
+ #define EN_KEYPAD_LEFT _BV(BTN_OFFSET + BLEN_KEYPAD_LEFT)
+
+ #define RRK(B) (keypad_buttons & (B))
+
+ #ifdef EN_C
+ #define BUTTON_CLICK() ((buttons & EN_C) || RRK(EN_KEYPAD_MIDDLE))
+ #else
+ #define BUTTON_CLICK() RRK(EN_KEYPAD_MIDDLE)
+ #endif
+#endif
+
+#ifndef EN_A
+ #define EN_A 0
+#endif
+#ifndef EN_B
+ #define EN_B 0
+#endif
+#ifndef EN_C
+ #define EN_C 0
+#endif
+#if BUTTON_EXISTS(BACK) || EITHER(HAS_TOUCH_BUTTONS, IS_TFTGLCD_PANEL)
+ #define BLEN_D 3
+ #define EN_D _BV(BLEN_D)
+#else
+ #define EN_D 0
+#endif
+
+#define BUTTON_PRESSED(BN) (_BUTTON_PRESSED_##BN)
+
+#if BUTTON_EXISTS(EN1)
+ #define _BUTTON_PRESSED_EN1 _BUTTON_PRESSED(EN1)
+#else
+ #define _BUTTON_PRESSED_EN1 false
+#endif
+#if BUTTON_EXISTS(EN2)
+ #define _BUTTON_PRESSED_EN2 _BUTTON_PRESSED(EN2)
+#else
+ #define _BUTTON_PRESSED_EN2 false
+#endif
+#if BUTTON_EXISTS(ENC_EN)
+ #define _BUTTON_PRESSED_ENC_EN _BUTTON_PRESSED(ENC_EN)
+#else
+ #define _BUTTON_PRESSED_ENC_EN false
+#endif
+#if BUTTON_EXISTS(ENC)
+ #define _BUTTON_PRESSED_ENC _BUTTON_PRESSED(ENC)
+#else
+ #define _BUTTON_PRESSED_ENC false
+#endif
+#if BUTTON_EXISTS(UP)
+ #define _BUTTON_PRESSED_UP _BUTTON_PRESSED(UP)
+#else
+ #define _BUTTON_PRESSED_UP false
+#endif
+#if BUTTON_EXISTS(DWN)
+ #define _BUTTON_PRESSED_DWN _BUTTON_PRESSED(DWN)
+#else
+ #define _BUTTON_PRESSED_DWN false
+#endif
+#if BUTTON_EXISTS(LFT)
+ #define _BUTTON_PRESSED_LFT _BUTTON_PRESSED(LFT)
+#else
+ #define _BUTTON_PRESSED_LFT false
+#endif
+#if BUTTON_EXISTS(RT)
+ #define _BUTTON_PRESSED_RT _BUTTON_PRESSED(RT)
+#else
+ #define _BUTTON_PRESSED_RT false
+#endif
+#if BUTTON_EXISTS(BACK)
+ #define _BUTTON_PRESSED_BACK _BUTTON_PRESSED(BACK)
+#else
+ #define _BUTTON_PRESSED_BACK false
+#endif
+
+#ifndef BUTTON_CLICK
+ #if EN_C > 0
+ #define BUTTON_CLICK() (buttons & EN_C)
+ #else
+ #define BUTTON_CLICK() false
+ #endif
+#endif
+
+#if EN_D > 0
+ #define LCD_BACK_CLICKED() (buttons & EN_D)
+#else
+ #define LCD_BACK_CLICKED() false
+#endif
diff --git a/src/lcd/fontutils.cpp b/src/lcd/fontutils.cpp
new file mode 100644
index 0000000..46329fd
--- /dev/null
+++ b/src/lcd/fontutils.cpp
@@ -0,0 +1,205 @@
+/**
+ * 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 .
+ *
+ */
+
+/**
+ * @file fontutils.cpp
+ * @brief help functions for font and char
+ * @author Yunhui Fu (yhfudev@gmail.com)
+ * @version 1.0
+ * @date 2016-08-19
+ * @copyright GPL/BSD
+ */
+
+#include "../inc/MarlinConfig.h"
+
+#if HAS_WIRED_LCD
+ #include "marlinui.h"
+ #include "../MarlinCore.h"
+#endif
+
+#include "fontutils.h"
+
+uint8_t read_byte_ram(const uint8_t *str) { return *str; }
+uint8_t read_byte_rom(const uint8_t *str) { return pgm_read_byte(str); }
+
+/**
+ * @brief Using binary search to find the position by data_pin
+ *
+ * @param userdata : User's data
+ * @param num_data : the item number of the sorted data
+ * @param cb_comp : the callback function to compare the user's data and pin
+ * @param data_pin : The reference data to be found
+ * @param ret_idx : the position of the required data; If failed, then it is the failed position, which is the insert position if possible.
+ *
+ * @return 0 on found, <0 on failed(fail position is saved by ret_idx)
+ *
+ * Using binary search to find the position by data_pin. The user's data should be sorted.
+ */
+int pf_bsearch_r(void *userdata, size_t num_data, pf_bsearch_cb_comp_t cb_comp, void *data_pinpoint, size_t *ret_idx) {
+ int retcomp;
+
+ if (num_data < 1) {
+ *ret_idx = 0;
+ return -1;
+ }
+
+ size_t i = 0, ileft = 1, iright = num_data;
+ bool flg_found = false;
+ for (; ileft <= iright;) {
+ i = (ileft + iright) / 2 - 1;
+ /* cb_comp should return the *userdata[i] - *data_pinpoint */
+ retcomp = cb_comp (userdata, i, data_pinpoint);
+ if (retcomp > 0)
+ iright = i;
+ else if (retcomp < 0)
+ ileft = i + 2;
+ else {
+ /* found ! */
+ flg_found = true;
+ break;
+ }
+ }
+
+ if (flg_found) {
+ *ret_idx = i;
+ return 0;
+ }
+ if (iright <= i)
+ *ret_idx = i;
+ else if (ileft >= i + 2)
+ *ret_idx = i + 1;
+ return -1;
+}
+
+/* Returns true if passed byte is first byte of UTF-8 char sequence */
+static inline bool utf8_is_start_byte_of_char(const uint8_t b) {
+ return 0x80 != (b & 0xC0);
+}
+
+/* This function gets the character at the pstart position, interpreting UTF8 multibyte sequences
+ and returns the pointer to the next character */
+const uint8_t* get_utf8_value_cb(const uint8_t *pstart, read_byte_cb_t cb_read_byte, lchar_t &pval) {
+ uint32_t val = 0;
+ const uint8_t *p = pstart;
+
+ #define NEXT_6_BITS() do{ val <<= 6; p++; valcur = cb_read_byte(p); val |= (valcur & 0x3F); }while(0)
+
+ uint8_t valcur = cb_read_byte(p);
+ if (0 == (0x80 & valcur)) {
+ val = valcur;
+ p++;
+ }
+ else if (0xC0 == (0xE0 & valcur)) {
+ val = valcur & 0x1F;
+ NEXT_6_BITS();
+ p++;
+ }
+ #if MAX_UTF8_CHAR_SIZE >= 3
+ else if (0xE0 == (0xF0 & valcur)) {
+ val = valcur & 0x0F;
+ NEXT_6_BITS();
+ NEXT_6_BITS();
+ p++;
+ }
+ #endif
+ #if MAX_UTF8_CHAR_SIZE >= 4
+ else if (0xF0 == (0xF8 & valcur)) {
+ val = valcur & 0x07;
+ NEXT_6_BITS();
+ NEXT_6_BITS();
+ NEXT_6_BITS();
+ p++;
+ }
+ #endif
+ #if MAX_UTF8_CHAR_SIZE >= 5
+ else if (0xF8 == (0xFC & valcur)) {
+ val = valcur & 0x03;
+ NEXT_6_BITS();
+ NEXT_6_BITS();
+ NEXT_6_BITS();
+ NEXT_6_BITS();
+ p++;
+ }
+ #endif
+ #if MAX_UTF8_CHAR_SIZE >= 6
+ else if (0xFC == (0xFE & valcur)) {
+ val = valcur & 0x01;
+ NEXT_6_BITS();
+ NEXT_6_BITS();
+ NEXT_6_BITS();
+ NEXT_6_BITS();
+ NEXT_6_BITS();
+ p++;
+ }
+ #endif
+ else if (!utf8_is_start_byte_of_char(valcur))
+ for (; !utf8_is_start_byte_of_char(valcur); ) { p++; valcur = cb_read_byte(p); }
+ else
+ for (; 0xFC < (0xFE & valcur); ) { p++; valcur = cb_read_byte(p); }
+
+ pval = val;
+
+ return p;
+}
+
+static inline uint8_t utf8_strlen_cb(const char *pstart, read_byte_cb_t cb_read_byte) {
+ uint8_t cnt = 0;
+ uint8_t *p = (uint8_t *)pstart;
+ if (p) for (;;) {
+ const uint8_t b = cb_read_byte(p);
+ if (!b) break;
+ if (utf8_is_start_byte_of_char(b)) cnt++;
+ p++;
+ }
+ return cnt;
+}
+
+uint8_t utf8_strlen(const char *pstart) {
+ return utf8_strlen_cb(pstart, read_byte_ram);
+}
+
+uint8_t utf8_strlen_P(PGM_P pstart) {
+ return utf8_strlen_cb(pstart, read_byte_rom);
+}
+
+static inline uint8_t utf8_byte_pos_by_char_num_cb(const char *pstart, read_byte_cb_t cb_read_byte, const uint8_t charnum) {
+ uint8_t *p = (uint8_t *)pstart;
+ uint8_t char_idx = 0;
+ uint8_t byte_idx = 0;
+ for (;;) {
+ const uint8_t b = cb_read_byte(p + byte_idx);
+ if (!b) return byte_idx; // Termination byte of string
+ if (utf8_is_start_byte_of_char(b)) {
+ char_idx++;
+ if (char_idx == charnum + 1) return byte_idx;
+ }
+ byte_idx++;
+ }
+}
+
+uint8_t utf8_byte_pos_by_char_num(const char *pstart, const uint8_t charnum) {
+ return utf8_byte_pos_by_char_num_cb(pstart, read_byte_ram, charnum);
+}
+
+uint8_t utf8_byte_pos_by_char_num_P(PGM_P pstart, const uint8_t charnum) {
+ return utf8_byte_pos_by_char_num_cb(pstart, read_byte_rom, charnum);
+}
diff --git a/src/lcd/fontutils.h b/src/lcd/fontutils.h
new file mode 100644
index 0000000..69edf1a
--- /dev/null
+++ b/src/lcd/fontutils.h
@@ -0,0 +1,79 @@
+/**
+ * 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 .
+ *
+ */
+
+/**
+ * @file fontutils.h
+ * @brief help functions for font and char
+ * @author Yunhui Fu (yhfudev@gmail.com)
+ * @version 1.0
+ * @date 2016-08-19
+ * @copyright GPL/BSD
+ */
+#pragma once
+
+#include
+#include // uint32_t
+
+#include "../HAL/shared/Marduino.h"
+#include "../core/macros.h"
+
+#define MAX_UTF8_CHAR_SIZE 4
+
+// Use a longer character type (if needed) because wchar_t is only 16 bits wide
+#ifdef MAX_UTF8_CHAR_SIZE
+ #if MAX_UTF8_CHAR_SIZE > 2
+ typedef uint32_t lchar_t;
+ #else
+ typedef wchar_t lchar_t;
+ #endif
+#else
+ #define wchar_t uint32_t
+#endif
+
+// read a byte from ROM or RAM
+typedef uint8_t (*read_byte_cb_t)(const uint8_t * str);
+
+uint8_t read_byte_ram(const uint8_t *str);
+uint8_t read_byte_rom(const uint8_t *str);
+
+typedef uint16_t pixel_len_t;
+#define PIXEL_LEN_NOLIMIT ((pixel_len_t)(-1))
+
+/* Perform binary search */
+typedef int (* pf_bsearch_cb_comp_t)(void *userdata, size_t idx, void * data_pin);
+int pf_bsearch_r(void *userdata, size_t num_data, pf_bsearch_cb_comp_t cb_comp, void *data_pinpoint, size_t *ret_idx);
+
+/* Get the character, decoding multibyte UTF8 characters and returning a pointer to the start of the next UTF8 character */
+const uint8_t* get_utf8_value_cb(const uint8_t *pstart, read_byte_cb_t cb_read_byte, lchar_t &pval);
+
+inline const char* get_utf8_value_cb(const char *pstart, read_byte_cb_t cb_read_byte, lchar_t &pval) {
+ return (const char *)get_utf8_value_cb((const uint8_t *)pstart, cb_read_byte, pval);
+}
+
+/* Returns length of string in CHARACTERS, NOT BYTES */
+uint8_t utf8_strlen(const char *pstart);
+uint8_t utf8_strlen_P(PGM_P pstart);
+inline uint8_t utf8_strlen(FSTR_P fstart) { return utf8_strlen_P(FTOP(fstart)); }
+
+/* Returns start byte position of desired char number */
+uint8_t utf8_byte_pos_by_char_num(const char *pstart, const uint8_t charnum);
+uint8_t utf8_byte_pos_by_char_num_P(PGM_P pstart, const uint8_t charnum);
diff --git a/src/lcd/lcdprint.cpp b/src/lcd/lcdprint.cpp
new file mode 100644
index 0000000..7757379
--- /dev/null
+++ b/src/lcd/lcdprint.cpp
@@ -0,0 +1,109 @@
+/**
+ * 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 .
+ *
+ */
+
+/**
+ * lcdprint.cpp
+ */
+
+#include "../inc/MarlinConfigPre.h"
+
+#if HAS_LCDPRINT
+
+#include "marlinui.h"
+#include "lcdprint.h"
+
+/**
+ * lcd_put_u8str_P
+ *
+ * Print a string with optional substitutions:
+ *
+ * $ displays the clipped string given by fstr or cstr
+ * = displays '0'....'10' for indexes 0 - 10
+ * ~ displays '1'....'11' for indexes 0 - 10
+ * * displays 'E1'...'E11' for indexes 0 - 10 (By default. Uses LCD_FIRST_TOOL)
+ * @ displays an axis name such as XYZUVW, or E for an extruder
+ */
+lcd_uint_t lcd_put_u8str_P(PGM_P const ptpl, const int8_t ind, const char *cstr/*=nullptr*/, FSTR_P const fstr/*=nullptr*/, const lcd_uint_t maxlen/*=LCD_WIDTH*/) {
+ const uint8_t prop = USE_WIDE_GLYPH ? 2 : 1;
+ const uint8_t *p = (uint8_t*)ptpl;
+ int8_t n = maxlen;
+ while (n > 0) {
+ lchar_t wc;
+ p = get_utf8_value_cb(p, read_byte_rom, wc);
+ if (!wc) break;
+ if (wc == '=' || wc == '~' || wc == '*') {
+ if (ind >= 0) {
+ if (wc == '*') { lcd_put_lchar('E'); n--; }
+ if (n) {
+ int8_t inum = ind + ((wc == '=') ? 0 : LCD_FIRST_TOOL);
+ if (inum >= 10) {
+ lcd_put_lchar('0' + (inum / 10)); n--;
+ inum %= 10;
+ }
+ if (n) { lcd_put_lchar('0' + inum); n--; }
+ }
+ }
+ else {
+ PGM_P const b = ind == -2 ? GET_TEXT(MSG_CHAMBER) : GET_TEXT(MSG_BED);
+ n -= lcd_put_u8str_max_P(b, n * (MENU_FONT_WIDTH)) / (MENU_FONT_WIDTH);
+ }
+ if (n) {
+ n -= lcd_put_u8str_max_P((PGM_P)p, n * (MENU_FONT_WIDTH)) / (MENU_FONT_WIDTH);
+ break;
+ }
+ }
+ else if (wc == '$' && fstr) {
+ n -= lcd_put_u8str_max_P(FTOP(fstr), n * (MENU_FONT_WIDTH)) / (MENU_FONT_WIDTH);
+ }
+ else if (wc == '$' && cstr) {
+ n -= lcd_put_u8str_max(cstr, n * (MENU_FONT_WIDTH)) / (MENU_FONT_WIDTH);
+ }
+ else if (wc == '@') {
+ lcd_put_lchar(AXIS_CHAR(ind));
+ n--;
+ }
+ else {
+ lcd_put_lchar(wc);
+ n -= wc > 255 ? prop : 1;
+ }
+ }
+ return n;
+}
+
+// Calculate UTF8 width with a simple check
+int calculateWidth(PGM_P const pstr) {
+ if (!USE_WIDE_GLYPH) return utf8_strlen_P(pstr) * MENU_FONT_WIDTH;
+ const uint8_t prop = 2;
+ const uint8_t *p = (uint8_t*)pstr;
+ int n = 0;
+
+ do {
+ lchar_t wc;
+ p = get_utf8_value_cb(p, read_byte_rom, wc);
+ if (!wc) break;
+ n += (wc > 255) ? prop : 1;
+ } while (1);
+
+ return n * MENU_FONT_WIDTH;
+}
+
+#endif // HAS_LCDPRINT
diff --git a/src/lcd/lcdprint.h b/src/lcd/lcdprint.h
new file mode 100644
index 0000000..bcf85cb
--- /dev/null
+++ b/src/lcd/lcdprint.h
@@ -0,0 +1,296 @@
+/**
+ * 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 .
+ *
+ */
+
+/**
+ * @file lcdprint.h
+ * @brief LCD print api
+ * @author Yunhui Fu (yhfudev@gmail.com)
+ * @version 1.0
+ * @date 2016-08-19
+ * @copyright GPL/BSD
+ */
+#pragma once
+
+#include "fontutils.h"
+
+#include "../inc/MarlinConfig.h"
+
+#if IS_DWIN_MARLINUI
+
+ #include "e3v2/marlinui/marlinui_dwin.h"
+
+ #define LCD_PIXEL_WIDTH DWIN_WIDTH
+ #define LCD_PIXEL_HEIGHT DWIN_HEIGHT
+ #define LCD_WIDTH ((LCD_PIXEL_WIDTH) / (MENU_FONT_WIDTH))
+ #define LCD_HEIGHT ((LCD_PIXEL_HEIGHT) / (MENU_LINE_HEIGHT))
+
+ // The DWIN lcd_moveto function uses row / column, not pixels
+ #define LCD_COL_X(col) (col)
+ #define LCD_ROW_Y(row) (row)
+ #define LCD_COL_X_RJ(len) (LCD_WIDTH - LCD_COL_X(len))
+
+#elif HAS_MARLINUI_U8GLIB
+
+ #include "dogm/u8g_fontutf8.h"
+ typedef u8g_uint_t lcd_uint_t;
+ typedef u8g_int_t lcd_int_t;
+
+ // Only Western languages support big / small fonts
+ #if DISABLED(DISPLAY_CHARSET_ISO10646_1)
+ #undef USE_BIG_EDIT_FONT
+ #undef USE_SMALL_INFOFONT
+ #endif
+
+ #define MENU_FONT_NAME ISO10646_1_5x7
+ #define MENU_FONT_WIDTH 6
+ #define MENU_FONT_ASCENT 10
+ #define MENU_FONT_DESCENT 2
+ #define MENU_FONT_HEIGHT (MENU_FONT_ASCENT + MENU_FONT_DESCENT)
+
+ #if ENABLED(USE_BIG_EDIT_FONT)
+ #define EDIT_FONT_NAME u8g_font_9x18
+ #define EDIT_FONT_WIDTH 9
+ #define EDIT_FONT_ASCENT 10
+ #define EDIT_FONT_DESCENT 3
+ #else
+ #define EDIT_FONT_NAME MENU_FONT_NAME
+ #define EDIT_FONT_WIDTH MENU_FONT_WIDTH
+ #define EDIT_FONT_ASCENT MENU_FONT_ASCENT
+ #define EDIT_FONT_DESCENT MENU_FONT_DESCENT
+ #endif
+ #define EDIT_FONT_HEIGHT (EDIT_FONT_ASCENT + EDIT_FONT_DESCENT)
+
+ // Get the Ascent, Descent, and total Height for the Info Screen font
+ #if ENABLED(USE_SMALL_INFOFONT)
+ extern const u8g_fntpgm_uint8_t u8g_font_6x9[];
+ #define INFO_FONT_ASCENT 7
+ #else
+ #define INFO_FONT_ASCENT 8
+ #endif
+ #define INFO_FONT_DESCENT 2
+ #define INFO_FONT_HEIGHT (INFO_FONT_ASCENT + INFO_FONT_DESCENT)
+ #define INFO_FONT_WIDTH 6
+
+ // Graphical LCD uses the menu font size for cursor positioning
+ #define LCD_COL_X(col) (( (col)) * (MENU_FONT_WIDTH))
+ #define LCD_ROW_Y(row) ((1 + (row)) * (MENU_LINE_HEIGHT))
+
+#else
+
+ #define _UxGT(a) a
+ typedef uint8_t lcd_uint_t;
+ typedef int8_t lcd_int_t;
+
+ #define MENU_FONT_WIDTH 1
+ #define MENU_FONT_HEIGHT 1
+ #define EDIT_FONT_WIDTH 1
+ #define EDIT_FONT_HEIGHT 1
+ #define INFO_FONT_WIDTH 1
+ #define INFO_FONT_HEIGHT 1
+ #define LCD_PIXEL_WIDTH LCD_WIDTH
+ #define LCD_PIXEL_HEIGHT LCD_HEIGHT
+
+ // Character LCD uses direct cursor positioning
+ #define LCD_COL_X(col) (col)
+ #define LCD_ROW_Y(row) (row)
+
+#endif
+
+#ifndef MENU_LINE_HEIGHT
+ #define MENU_LINE_HEIGHT MENU_FONT_HEIGHT
+#endif
+
+#ifndef LCD_COL_X_RJ
+ #define LCD_COL_X_RJ(len) (LCD_PIXEL_WIDTH - LCD_COL_X(len))
+#endif
+
+#define SETCURSOR(col, row) lcd_moveto(LCD_COL_X(col), LCD_ROW_Y(row))
+#define SETCURSOR_RJ(len, row) lcd_moveto(LCD_COL_X_RJ(len), LCD_ROW_Y(row))
+#define SETCURSOR_X(col) SETCURSOR(col, _lcdLineNr)
+#define SETCURSOR_X_RJ(len) SETCURSOR_RJ(len, _lcdLineNr)
+
+int lcd_glyph_height();
+
+/**
+ * @brief Draw a UTF-8 character
+ *
+ * @param utf8_str : the UTF-8 character
+ * @param max_length : the output width limit (in pixels on GLCD)
+ *
+ * @return the output width (in pixels on GLCD)
+ */
+int lcd_put_lchar_max(const lchar_t &c, const pixel_len_t max_length);
+
+/**
+ * @brief Draw a SRAM UTF-8 string
+ *
+ * @param utf8_str : the UTF-8 string
+ * @param max_length : the output width limit (in pixels on GLCD)
+ *
+ * @return the output width (in pixels on GLCD)
+ */
+int lcd_put_u8str_max(const char *utf8_str, const pixel_len_t max_length);
+
+/**
+ * Change the print cursor position
+ */
+void lcd_moveto(const lcd_uint_t col, const lcd_uint_t row);
+
+/**
+ * @brief Draw a ROM UTF-8 string
+ *
+ * @param pstr : the ROM UTF-8 string
+ * @param max_length : the output width limit (in pixels on GLCD)
+ *
+ * @return the output width (in pixels on GLCD)
+ */
+int lcd_put_u8str_max_P(PGM_P pstr, const pixel_len_t max_length);
+inline int lcd_put_u8str_max_P(const lcd_uint_t col, const lcd_uint_t row, PGM_P pstr, const pixel_len_t max_length) {
+ lcd_moveto(col, row);
+ return lcd_put_u8str_max_P(pstr, max_length);
+}
+inline int lcd_put_u8str_max(const lcd_uint_t col, const lcd_uint_t row, FSTR_P const fstr, const pixel_len_t max_length) {
+ return lcd_put_u8str_max_P(col, row, FTOP(fstr), max_length);
+}
+
+/**
+ * @brief Draw an integer, left-justified
+ *
+ * @param i : the integer
+ */
+void lcd_put_int(const int i);
+inline void lcd_put_int(const lcd_uint_t col, const lcd_uint_t row, const int i) {
+ lcd_moveto(col, row);
+ lcd_put_int(i);
+}
+
+/**
+ * @brief Draw a ROM UTF-8 string
+ *
+ * @param i : the integer
+ */
+inline int lcd_put_u8str_P(PGM_P const pstr) { return lcd_put_u8str_max_P(pstr, PIXEL_LEN_NOLIMIT); }
+inline int lcd_put_u8str_P(const lcd_uint_t col, const lcd_uint_t row, PGM_P const pstr) {
+ lcd_moveto(col, row);
+ return lcd_put_u8str_P(pstr);
+}
+
+/**
+ * @brief Draw a ROM UTF-8 F-string
+ *
+ * @param fstr The F-string pointer
+ * @return the output width (in pixels on GLCD)
+ */
+inline int lcd_put_u8str(FSTR_P const fstr) { return lcd_put_u8str_P(FTOP(fstr)); }
+inline int lcd_put_u8str(const lcd_uint_t col, const lcd_uint_t row, FSTR_P const fstr) {
+ return lcd_put_u8str_P(col, row, FTOP(fstr));
+}
+
+/**
+ * @brief Draw a string with optional substitution
+ * @details Print a string with optional substitutions:
+ * $ displays the clipped string given by fstr or cstr
+ * = displays '0'....'10' for indexes 0 - 10
+ * ~ displays '1'....'11' for indexes 0 - 10
+ * * displays 'E1'...'E11' for indexes 0 - 10 (By default. Uses LCD_FIRST_TOOL)
+ * @ displays an axis name such as XYZUVW, or E for an extruder
+ *
+ * @param ptpl A ROM string (template)
+ * @param ind An index value to use for = ~ * substitution
+ * @param cstr An SRAM C-string to use for $ substitution
+ * @param fstr A ROM F-string to use for $ substitution
+ * @param maxlen The maximum size of the string (in pixels on GLCD)
+ * @return the output width (in pixels on GLCD)
+ */
+lcd_uint_t lcd_put_u8str_P(PGM_P const ptpl, const int8_t ind, const char *cstr=nullptr, FSTR_P const fstr=nullptr, const lcd_uint_t maxlen=LCD_WIDTH);
+inline lcd_uint_t lcd_put_u8str_P(const lcd_uint_t col, const lcd_uint_t row, PGM_P const ptpl, const int8_t ind, const char *cstr=nullptr, FSTR_P const fstr=nullptr, const lcd_uint_t maxlen=LCD_WIDTH) {
+ lcd_moveto(col, row);
+ return lcd_put_u8str_P(ptpl, ind, cstr, fstr, maxlen);
+}
+/**
+ * @brief Draw a ROM UTF-8 F-string with optional substitution
+ * @details (See above)
+ *
+ * @param ftpl A ROM F-string (template)
+ * @param ind An index value to use for = ~ * substitution
+ * @param cstr An SRAM C-string to use for $ substitution
+ * @param fstr A ROM F-string to use for $ substitution
+ * @param maxlen The maximum size of the string (in pixels on GLCD)
+ * @return the output width (in pixels on GLCD)
+ */
+inline lcd_uint_t lcd_put_u8str(FSTR_P const ftpl, const int8_t ind, const char *cstr=nullptr, FSTR_P const fstr=nullptr, const lcd_uint_t maxlen=LCD_WIDTH) {
+ return lcd_put_u8str_P(FTOP(ftpl), ind, cstr, fstr, maxlen);
+}
+/**
+ * @param col
+ * @param row
+ */
+inline lcd_uint_t lcd_put_u8str(const lcd_uint_t col, const lcd_uint_t row, FSTR_P const ftpl, const int8_t ind, const char *cstr=nullptr, FSTR_P const fstr=nullptr, const lcd_uint_t maxlen=LCD_WIDTH) {
+ return lcd_put_u8str_P(col, row, FTOP(ftpl), ind, cstr, fstr, maxlen);
+}
+
+/**
+ * @brief Draw a SRAM string with no width limit
+ *
+ * @param str The UTF-8 string
+ * @return the output width (in pixels on GLCD)
+ */
+inline int lcd_put_u8str(const char * const str) { return lcd_put_u8str_max(str, PIXEL_LEN_NOLIMIT); }
+/**
+ * @param col
+ * @param row
+ */
+inline int lcd_put_u8str(const lcd_uint_t col, const lcd_uint_t row, const char * const str) {
+ lcd_moveto(col, row);
+ return lcd_put_u8str(str);
+}
+
+/**
+ * @brief Draw a UTF-8 character with no width limit
+ *
+ * @param c The lchar to draw
+ * @return the output width (in pixels on GLCD)
+ */
+inline int lcd_put_lchar(const lchar_t &c) { return lcd_put_lchar_max(c, PIXEL_LEN_NOLIMIT); }
+/**
+ * @param col
+ * @param row
+ */
+inline int lcd_put_lchar(const lcd_uint_t col, const lcd_uint_t row, const lchar_t &c) {
+ lcd_moveto(col, row);
+ return lcd_put_lchar(c);
+}
+
+/**
+ * @brief Calculate the width of a ROM UTF-8 string (in pixels on GLCD)
+ *
+ * @param pstr The ROM-based UTF-8 string
+ * @return the string width (in pixels on GLCD)
+ */
+int calculateWidth(PGM_P const pstr);
+/**
+ * @brief Calculate the width of a ROM UTF-8 string (in pixels on GLCD)
+ *
+ * @param pstr The ROM-based UTF-8 string
+ * @return the string width (in pixels on GLCD)
+ */
+inline int calculateWidth(FSTR_P const fstr) { return calculateWidth(FTOP(fstr)); }
diff --git a/src/lcd/marlinui.cpp b/src/lcd/marlinui.cpp
new file mode 100644
index 0000000..e03e80e
--- /dev/null
+++ b/src/lcd/marlinui.cpp
@@ -0,0 +1,1879 @@
+/**
+ * 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 .
+ *
+ */
+
+#include "../inc/MarlinConfig.h"
+
+#include "../MarlinCore.h" // for printingIsPaused
+
+#if LED_POWEROFF_TIMEOUT > 0 || BOTH(HAS_WIRED_LCD, PRINTER_EVENT_LEDS)
+ #include "../feature/leds/leds.h"
+#endif
+
+#if ENABLED(HOST_ACTION_COMMANDS)
+ #include "../feature/host_actions.h"
+#endif
+
+#if BOTH(BROWSE_MEDIA_ON_INSERT, PASSWORD_ON_SD_PRINT_MENU)
+ #include "../feature/password/password.h"
+#endif
+
+// All displays share the MarlinUI class
+#include "marlinui.h"
+MarlinUI ui;
+
+#if HAS_DISPLAY
+ #include "../gcode/queue.h"
+ #include "fontutils.h"
+#endif
+
+#if ENABLED(DWIN_CREALITY_LCD)
+ #include "e3v2/creality/dwin.h"
+#elif ENABLED(DWIN_LCD_PROUI)
+ #include "e3v2/proui/dwin.h"
+#elif ENABLED(DWIN_CREALITY_LCD_JYERSUI)
+ #include "e3v2/jyersui/dwin.h"
+#endif
+
+#if ENABLED(LCD_PROGRESS_BAR) && !IS_TFTGLCD_PANEL
+ #define BASIC_PROGRESS_BAR 1
+#endif
+
+#if ANY(HAS_DISPLAY, HAS_STATUS_MESSAGE, BASIC_PROGRESS_BAR)
+ #include "../module/printcounter.h"
+#endif
+
+#if LCD_HAS_WAIT_FOR_MOVE
+ bool MarlinUI::wait_for_move; // = false
+#endif
+
+constexpr uint8_t epps = ENCODER_PULSES_PER_STEP;
+
+#if HAS_STATUS_MESSAGE
+ #if ENABLED(STATUS_MESSAGE_SCROLLING) && EITHER(HAS_WIRED_LCD, DWIN_LCD_PROUI)
+ uint8_t MarlinUI::status_scroll_offset; // = 0
+ #endif
+ char MarlinUI::status_message[MAX_MESSAGE_LENGTH + 1];
+ uint8_t MarlinUI::alert_level; // = 0
+ #if HAS_STATUS_MESSAGE_TIMEOUT
+ millis_t MarlinUI::status_message_expire_ms; // = 0
+ #endif
+ statusResetFunc_t MarlinUI::status_reset_callback; // = nullptr
+#endif
+
+#if ENABLED(LCD_SET_PROGRESS_MANUALLY)
+ MarlinUI::progress_t MarlinUI::progress_override; // = 0
+ #if ENABLED(USE_M73_REMAINING_TIME)
+ uint32_t MarlinUI::remaining_time;
+ #endif
+#endif
+
+#if HAS_MULTI_LANGUAGE
+ uint8_t MarlinUI::language; // Initialized by settings.load()
+ void MarlinUI::set_language(const uint8_t lang) {
+ if (lang < NUM_LANGUAGES) {
+ language = lang;
+ TERN_(HAS_MARLINUI_U8GLIB, update_language_font());
+ return_to_status();
+ refresh();
+ }
+ }
+#endif
+
+#if HAS_LCD_CONTRAST
+ uint8_t MarlinUI::contrast = LCD_CONTRAST_DEFAULT; // Initialized by settings.load()
+ void MarlinUI::set_contrast(const uint8_t value) {
+ contrast = constrain(value, LCD_CONTRAST_MIN, LCD_CONTRAST_MAX);
+ _set_contrast();
+ }
+#endif
+
+#if HAS_LCD_BRIGHTNESS
+ uint8_t MarlinUI::brightness = LCD_BRIGHTNESS_DEFAULT;
+ bool MarlinUI::backlight = true;
+
+ void MarlinUI::set_brightness(const uint8_t value) {
+ backlight = !!value;
+ if (backlight) brightness = constrain(value, LCD_BRIGHTNESS_MIN, LCD_BRIGHTNESS_MAX);
+ _set_brightness();
+ }
+#endif
+
+#if ENABLED(SOUND_MENU_ITEM)
+ bool MarlinUI::sound_on = ENABLED(SOUND_ON_DEFAULT);
+#endif
+
+#if ENABLED(PCA9632_BUZZER)
+ void MarlinUI::buzz(const long duration, const uint16_t freq) {
+ if (sound_on) PCA9632_buzz(duration, freq);
+ }
+#endif
+
+#if HAS_PREHEAT
+ #include "../module/temperature.h"
+
+ preheat_t MarlinUI::material_preset[PREHEAT_COUNT]; // Initialized by settings.load()
+
+ FSTR_P MarlinUI::get_preheat_label(const uint8_t m) {
+ #define _PDEF(N) static PGMSTR(preheat_##N##_label, PREHEAT_##N##_LABEL);
+ #define _PLBL(N) preheat_##N##_label,
+ REPEAT_1(PREHEAT_COUNT, _PDEF);
+ static PGM_P const preheat_labels[PREHEAT_COUNT] PROGMEM = { REPEAT_1(PREHEAT_COUNT, _PLBL) };
+ return FPSTR((PGM_P)pgm_read_ptr(&preheat_labels[m]));
+ }
+
+ void MarlinUI::apply_preheat(const uint8_t m, const uint8_t pmask, const uint8_t e/*=active_extruder*/) {
+ const preheat_t &pre = material_preset[m];
+ TERN_(HAS_HOTEND, if (TEST(pmask, PT_HOTEND)) thermalManager.setTargetHotend(pre.hotend_temp, e));
+ TERN_(HAS_HEATED_BED, if (TEST(pmask, PT_BED)) thermalManager.setTargetBed(pre.bed_temp));
+ //TERN_(HAS_HEATED_CHAMBER, if (TEST(pmask, PT_CHAMBER)) thermalManager.setTargetBed(pre.chamber_temp));
+ TERN_(HAS_FAN, if (TEST(pmask, PT_FAN)) thermalManager.set_fan_speed(0, pre.fan_speed));
+ }
+#endif
+
+#if EITHER(HAS_MARLINUI_MENU, EXTENSIBLE_UI)
+ bool MarlinUI::lcd_clicked;
+#endif
+
+#if EITHER(HAS_WIRED_LCD, DWIN_CREALITY_LCD_JYERSUI)
+
+ bool MarlinUI::get_blink() {
+ static uint8_t blink = 0;
+ static millis_t next_blink_ms = 0;
+ millis_t ms = millis();
+ if (ELAPSED(ms, next_blink_ms)) {
+ blink ^= 0xFF;
+ next_blink_ms = ms + 1000 - (LCD_UPDATE_INTERVAL) / 2;
+ }
+ return blink != 0;
+ }
+
+#endif
+
+// Encoder Handling
+#if HAS_ENCODER_ACTION
+ uint32_t MarlinUI::encoderPosition;
+ volatile int8_t encoderDiff; // Updated in update_buttons, added to encoderPosition every LCD update
+#endif
+
+#if LCD_BACKLIGHT_TIMEOUT
+
+ uint16_t MarlinUI::lcd_backlight_timeout; // Initialized by settings.load()
+ millis_t MarlinUI::backlight_off_ms = 0;
+ void MarlinUI::refresh_backlight_timeout() {
+ backlight_off_ms = lcd_backlight_timeout ? millis() + lcd_backlight_timeout * 1000UL : 0;
+ WRITE(LCD_BACKLIGHT_PIN, HIGH);
+ }
+
+#elif HAS_DISPLAY_SLEEP
+
+ uint8_t MarlinUI::sleep_timeout_minutes; // Initialized by settings.load()
+ millis_t MarlinUI::screen_timeout_millis = 0;
+ void MarlinUI::refresh_screen_timeout() {
+ screen_timeout_millis = sleep_timeout_minutes ? millis() + sleep_timeout_minutes * 60UL * 1000UL : 0;
+ sleep_off();
+ }
+
+#endif
+
+void MarlinUI::init() {
+
+ init_lcd();
+
+ #if HAS_DIGITAL_BUTTONS
+ #if BUTTON_EXISTS(EN1)
+ SET_INPUT_PULLUP(BTN_EN1);
+ #endif
+ #if BUTTON_EXISTS(EN2)
+ SET_INPUT_PULLUP(BTN_EN2);
+ #endif
+ #if BUTTON_EXISTS(ENC)
+ SET_INPUT_PULLUP(BTN_ENC);
+ #endif
+ #if BUTTON_EXISTS(ENC_EN)
+ SET_INPUT_PULLUP(BTN_ENC_EN);
+ #endif
+ #if BUTTON_EXISTS(BACK)
+ SET_INPUT_PULLUP(BTN_BACK);
+ #endif
+ #if BUTTON_EXISTS(UP)
+ SET_INPUT(BTN_UP);
+ #endif
+ #if BUTTON_EXISTS(DWN)
+ SET_INPUT(BTN_DWN);
+ #endif
+ #if BUTTON_EXISTS(LFT)
+ SET_INPUT(BTN_LFT);
+ #endif
+ #if BUTTON_EXISTS(RT)
+ SET_INPUT(BTN_RT);
+ #endif
+ #endif
+
+ #if HAS_SHIFT_ENCODER
+
+ #if ENABLED(SR_LCD_2W_NL) // Non latching 2 wire shift register
+
+ SET_OUTPUT(SR_DATA_PIN);
+ SET_OUTPUT(SR_CLK_PIN);
+
+ #elif PIN_EXISTS(SHIFT_CLK)
+
+ SET_OUTPUT(SHIFT_CLK_PIN);
+ OUT_WRITE(SHIFT_LD_PIN, HIGH);
+ #if PIN_EXISTS(SHIFT_EN)
+ OUT_WRITE(SHIFT_EN_PIN, LOW);
+ #endif
+ SET_INPUT_PULLUP(SHIFT_OUT_PIN);
+
+ #endif
+
+ #endif // HAS_SHIFT_ENCODER
+
+ #if BOTH(HAS_ENCODER_ACTION, HAS_SLOW_BUTTONS)
+ slow_buttons = 0;
+ #endif
+
+ update_buttons();
+
+ TERN_(HAS_ENCODER_ACTION, encoderDiff = 0);
+
+ reset_status(); // Set welcome message
+}
+
+#if HAS_WIRED_LCD
+
+ #if HAS_MARLINUI_U8GLIB
+ #include "dogm/marlinui_DOGM.h"
+ #endif
+
+ #include "lcdprint.h"
+
+ #include "../module/temperature.h"
+ #include "../module/planner.h"
+ #include "../module/motion.h"
+
+ #if HAS_MARLINUI_MENU
+ #include "../module/settings.h"
+ #endif
+
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+ #include "../feature/bedlevel/bedlevel.h"
+ #endif
+
+ #if HAS_TRINAMIC_CONFIG
+ #include "../feature/tmc_util.h"
+ #endif
+
+ #if HAS_ADC_BUTTONS
+ #include "../module/thermistor/thermistors.h"
+ #endif
+
+ #if HAS_POWER_MONITOR
+ #include "../feature/power_monitor.h"
+ #endif
+
+ #if LED_POWEROFF_TIMEOUT > 0
+ #include "../feature/power.h"
+ #endif
+
+ #if HAS_ENCODER_ACTION
+ volatile uint8_t MarlinUI::buttons;
+ #if HAS_SLOW_BUTTONS
+ volatile uint8_t MarlinUI::slow_buttons;
+ #endif
+ #if HAS_TOUCH_BUTTONS
+ #include "touch/touch_buttons.h"
+ bool MarlinUI::on_edit_screen = false;
+ #endif
+ #endif
+
+ #if SCREENS_CAN_TIME_OUT
+ bool MarlinUI::defer_return_to_status;
+ millis_t MarlinUI::return_to_status_ms = 0;
+ #endif
+
+ uint8_t MarlinUI::lcd_status_update_delay = 1; // First update one loop delayed
+
+ #if BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT)
+ millis_t MarlinUI::next_filament_display; // = 0
+ #endif
+
+ millis_t MarlinUI::next_button_update_ms; // = 0
+
+ #if HAS_MARLINUI_U8GLIB
+ bool MarlinUI::drawing_screen, MarlinUI::first_page; // = false
+ #endif
+
+ #if IS_DWIN_MARLINUI
+ bool MarlinUI::did_first_redraw;
+ bool MarlinUI::old_is_printing;
+ #endif
+
+ #if ENABLED(SDSUPPORT)
+
+ #if MARLINUI_SCROLL_NAME
+ uint8_t MarlinUI::filename_scroll_pos, MarlinUI::filename_scroll_max;
+ #endif
+
+ const char * MarlinUI::scrolled_filename(CardReader &theCard, const uint8_t maxlen, uint8_t hash, const bool doScroll) {
+ const char *outstr = theCard.longest_filename();
+ if (theCard.longFilename[0]) {
+ #if MARLINUI_SCROLL_NAME
+ if (doScroll) {
+ for (uint8_t l = FILENAME_LENGTH; l--;)
+ hash = ((hash << 1) | (hash >> 7)) ^ theCard.filename[l]; // rotate, xor
+ static uint8_t filename_scroll_hash;
+ if (filename_scroll_hash != hash) { // If the hash changed...
+ filename_scroll_hash = hash; // Save the new hash
+ filename_scroll_max = _MAX(0, utf8_strlen(theCard.longFilename) - maxlen); // Update the scroll limit
+ filename_scroll_pos = 0; // Reset scroll to the start
+ lcd_status_update_delay = 8; // Don't scroll right away
+ }
+ // Advance byte position corresponding to filename_scroll_pos char position
+ outstr += TERN(UTF_FILENAME_SUPPORT, utf8_byte_pos_by_char_num(outstr, filename_scroll_pos), filename_scroll_pos);
+ }
+ #else
+ theCard.longFilename[
+ TERN(UTF_FILENAME_SUPPORT, utf8_byte_pos_by_char_num(theCard.longFilename, maxlen), maxlen)
+ ] = '\0'; // cutoff at screen edge
+ #endif
+ }
+ return outstr;
+ }
+
+ #endif
+
+ #if HAS_MARLINUI_MENU
+ #include "menu/menu.h"
+
+ screenFunc_t MarlinUI::currentScreen; // Initialized in CTOR
+ bool MarlinUI::screen_changed;
+
+ #if ENABLED(ENCODER_RATE_MULTIPLIER)
+ bool MarlinUI::encoderRateMultiplierEnabled;
+ millis_t MarlinUI::lastEncoderMovementMillis = 0;
+ void MarlinUI::enable_encoder_multiplier(const bool onoff) {
+ encoderRateMultiplierEnabled = onoff;
+ lastEncoderMovementMillis = 0;
+ }
+ #endif
+
+ #if EITHER(REVERSE_MENU_DIRECTION, REVERSE_SELECT_DIRECTION)
+ int8_t MarlinUI::encoderDirection = ENCODERBASE;
+ #endif
+
+ #if HAS_TOUCH_BUTTONS
+ uint8_t MarlinUI::touch_buttons;
+ uint8_t MarlinUI::repeat_delay;
+ #endif
+
+ #if EITHER(AUTO_BED_LEVELING_UBL, G26_MESH_VALIDATION)
+
+ bool MarlinUI::external_control; // = false
+
+ void MarlinUI::wait_for_release() {
+ while (button_pressed()) safe_delay(50);
+ safe_delay(50);
+ }
+
+ #endif
+
+ #if !HAS_GRAPHICAL_TFT
+
+ void _wrap_string(uint8_t &col, uint8_t &row, const char * const string, read_byte_cb_t cb_read_byte, bool wordwrap/*=false*/) {
+ SETCURSOR(col, row);
+ if (!string) return;
+
+ auto _newline = [&col, &row]{
+ col = 0; row++; // Move col to string len (plus space)
+ SETCURSOR(0, row); // Simulate carriage return
+ };
+
+ const uint8_t *p = (uint8_t*)string;
+ lchar_t wc;
+ if (wordwrap) {
+ const uint8_t *wrd = nullptr;
+ uint8_t c = 0;
+ // find the end of the part
+ for (;;) {
+ if (!wrd) wrd = p; // Get word start /before/ advancing
+ p = get_utf8_value_cb(p, cb_read_byte, wc);
+ const bool eol = !wc; // zero ends the string
+ // End or a break between phrases?
+ if (eol || wc == ' ' || wc == '-' || wc == '+' || wc == '.') {
+ if (!c && wc == ' ') { if (wrd) wrd++; continue; } // collapse extra spaces
+ // Past the right and the word is not too long?
+ if (col + c > LCD_WIDTH && col >= (LCD_WIDTH) / 4) _newline(); // should it wrap?
+ c += !eol; // +1 so the space will be printed
+ col += c; // advance col to new position
+ while (c) { // character countdown
+ --c; // count down to zero
+ wrd = get_utf8_value_cb(wrd, cb_read_byte, wc); // get characters again
+ lcd_put_lchar(wc); // character to the LCD
+ }
+ if (eol) break; // all done!
+ wrd = nullptr; // set up for next word
+ }
+ else c++; // count word characters
+ }
+ }
+ else {
+ for (;;) {
+ p = get_utf8_value_cb(p, cb_read_byte, wc);
+ if (!wc) break;
+ lcd_put_lchar(wc);
+ col++;
+ if (col >= LCD_WIDTH) _newline();
+ }
+ }
+ }
+
+ void MarlinUI::draw_select_screen_prompt(FSTR_P const pref, const char * const string/*=nullptr*/, FSTR_P const suff/*=nullptr*/) {
+ const uint8_t plen = utf8_strlen(pref), slen = suff ? utf8_strlen(suff) : 0;
+ uint8_t col = 0, row = 0;
+ if (!string && plen + slen <= LCD_WIDTH) {
+ col = (LCD_WIDTH - plen - slen) / 2;
+ row = LCD_HEIGHT > 3 ? 1 : 0;
+ }
+ if (LCD_HEIGHT >= 8) row = LCD_HEIGHT / 2 - 2;
+ wrap_string_P(col, row, FTOP(pref), true);
+ if (string) {
+ if (col) { col = 0; row++; } // Move to the start of the next line
+ wrap_string(col, row, string);
+ }
+ if (suff) wrap_string_P(col, row, FTOP(suff));
+ }
+
+ #endif // !HAS_GRAPHICAL_TFT
+
+ #endif // HAS_MARLINUI_MENU
+
+ ////////////////////////////////////////////
+ ///////////// Keypad Handling //////////////
+ ////////////////////////////////////////////
+
+ #if IS_RRW_KEYPAD && HAS_ENCODER_ACTION
+
+ volatile uint8_t MarlinUI::keypad_buttons;
+
+ #if HAS_MARLINUI_MENU && !HAS_ADC_BUTTONS
+
+ void _reprapworld_keypad_move(const AxisEnum axis, const int16_t dir) {
+ ui.manual_move.menu_scale = REPRAPWORLD_KEYPAD_MOVE_STEP;
+ ui.encoderPosition = dir;
+ switch (axis) {
+ case X_AXIS:
+ TERN_(HAS_Y_AXIS, case Y_AXIS:)
+ TERN_(HAS_Z_AXIS, case Z_AXIS:)
+ lcd_move_axis(axis);
+ default: break;
+ }
+ }
+
+ #endif
+
+ bool MarlinUI::handle_keypad() {
+
+ #if HAS_ADC_BUTTONS
+
+ #define ADC_MIN_KEY_DELAY 100
+ if (keypad_buttons) {
+ #if HAS_ENCODER_ACTION
+ refresh(LCDVIEW_REDRAW_NOW);
+ #if HAS_MARLINUI_MENU
+ if (encoderDirection == -(ENCODERBASE)) { // HAS_ADC_BUTTONS forces REVERSE_MENU_DIRECTION, so this indicates menu navigation
+ if (RRK(EN_KEYPAD_UP)) encoderPosition += ENCODER_STEPS_PER_MENU_ITEM;
+ else if (RRK(EN_KEYPAD_DOWN)) encoderPosition -= ENCODER_STEPS_PER_MENU_ITEM;
+ else if (RRK(EN_KEYPAD_LEFT)) { MenuItem_back::action(); quick_feedback(); }
+ else if (RRK(EN_KEYPAD_RIGHT)) { return_to_status(); quick_feedback(); }
+ }
+ else
+ #endif
+ {
+ #if HAS_MARLINUI_MENU
+ if (RRK(EN_KEYPAD_UP)) encoderPosition -= epps;
+ else if (RRK(EN_KEYPAD_DOWN)) encoderPosition += epps;
+ else if (RRK(EN_KEYPAD_LEFT)) { MenuItem_back::action(); quick_feedback(); }
+ else if (RRK(EN_KEYPAD_RIGHT)) encoderPosition = 0;
+ #else
+ if (RRK(EN_KEYPAD_UP) || RRK(EN_KEYPAD_LEFT)) encoderPosition -= epps;
+ else if (RRK(EN_KEYPAD_DOWN) || RRK(EN_KEYPAD_RIGHT)) encoderPosition += epps;
+ #endif
+ }
+ #endif
+ next_button_update_ms = millis() + ADC_MIN_KEY_DELAY;
+ return true;
+ }
+
+ #else // !HAS_ADC_BUTTONS
+
+ static uint8_t keypad_debounce = 0;
+
+ if (!RRK( EN_KEYPAD_F1 | EN_KEYPAD_F2
+ | EN_KEYPAD_F3 | EN_KEYPAD_DOWN
+ | EN_KEYPAD_RIGHT | EN_KEYPAD_MIDDLE
+ | EN_KEYPAD_UP | EN_KEYPAD_LEFT )
+ ) {
+ if (keypad_debounce > 0) keypad_debounce--;
+ }
+ else if (!keypad_debounce) {
+ keypad_debounce = 2;
+
+ const bool homed = all_axes_homed();
+
+ #if HAS_MARLINUI_MENU
+
+ if (RRK(EN_KEYPAD_MIDDLE)) goto_screen(menu_move);
+
+ #if NONE(DELTA, Z_HOME_TO_MAX)
+ if (RRK(EN_KEYPAD_F2)) _reprapworld_keypad_move(Z_AXIS, 1);
+ #endif
+
+ if (homed) {
+ #if EITHER(DELTA, Z_HOME_TO_MAX)
+ if (RRK(EN_KEYPAD_F2)) _reprapworld_keypad_move(Z_AXIS, 1);
+ #endif
+ if (RRK(EN_KEYPAD_F3)) _reprapworld_keypad_move(Z_AXIS, -1);
+ if (RRK(EN_KEYPAD_LEFT)) _reprapworld_keypad_move(X_AXIS, -1);
+ if (RRK(EN_KEYPAD_RIGHT)) _reprapworld_keypad_move(X_AXIS, 1);
+ if (RRK(EN_KEYPAD_DOWN)) _reprapworld_keypad_move(Y_AXIS, 1);
+ if (RRK(EN_KEYPAD_UP)) _reprapworld_keypad_move(Y_AXIS, -1);
+ }
+
+ #endif // HAS_MARLINUI_MENU
+
+ if (!homed && RRK(EN_KEYPAD_F1)) queue.inject_P(G28_STR);
+ return true;
+ }
+
+ #endif // !HAS_ADC_BUTTONS
+
+ return false;
+ }
+
+ #endif // IS_RRW_KEYPAD && HAS_ENCODER_ACTION
+
+ /**
+ * Status Screen
+ *
+ * This is very display-dependent, so the lcd implementation draws this.
+ */
+
+ #if BASIC_PROGRESS_BAR
+ millis_t MarlinUI::progress_bar_ms; // = 0
+ #if PROGRESS_MSG_EXPIRE > 0
+ millis_t MarlinUI::expire_status_ms; // = 0
+ #endif
+ #endif
+
+ void MarlinUI::status_screen() {
+
+ TERN_(HAS_MARLINUI_MENU, ENCODER_RATE_MULTIPLY(false));
+
+ #if BASIC_PROGRESS_BAR
+
+ //
+ // HD44780 implements the following message blinking and
+ // message expiration because Status Line and Progress Bar
+ // share the same line on the display.
+ //
+
+ #if DISABLED(PROGRESS_MSG_ONCE) || PROGRESS_MSG_EXPIRE > 0
+ #define GOT_MS
+ const millis_t ms = millis();
+ #endif
+
+ // If the message will blink rather than expire...
+ #if DISABLED(PROGRESS_MSG_ONCE)
+ if (ELAPSED(ms, progress_bar_ms + PROGRESS_BAR_MSG_TIME + PROGRESS_BAR_BAR_TIME))
+ progress_bar_ms = ms;
+ #endif
+
+ #if PROGRESS_MSG_EXPIRE > 0
+
+ // Handle message expire
+ if (expire_status_ms) {
+
+ // Expire the message if a job is active and the bar has ticks
+ if (get_progress_percent() > 2 && !print_job_timer.isPaused()) {
+ if (ELAPSED(ms, expire_status_ms)) {
+ status_message[0] = '\0';
+ expire_status_ms = 0;
+ }
+ }
+ else {
+ // Defer message expiration before bar appears
+ // and during any pause (not just SD)
+ expire_status_ms += LCD_UPDATE_INTERVAL;
+ }
+ }
+
+ #endif // PROGRESS_MSG_EXPIRE
+
+ #endif // BASIC_PROGRESS_BAR
+
+ bool did_expire = status_reset_callback && (*status_reset_callback)();
+
+ #if HAS_STATUS_MESSAGE_TIMEOUT
+ #ifndef GOT_MS
+ #define GOT_MS
+ const millis_t ms = millis();
+ #endif
+ did_expire |= status_message_expire_ms && ELAPSED(ms, status_message_expire_ms);
+ #endif
+
+ if (did_expire) reset_status();
+
+ #if HAS_MARLINUI_MENU
+ if (use_click()) {
+ #if BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT)
+ next_filament_display = millis() + 5000UL; // Show status message for 5s
+ #endif
+ goto_screen(menu_main);
+ reinit_lcd(); // Revive a noisy shared SPI LCD
+ return;
+ }
+
+ #endif
+
+ #if ENABLED(ULTIPANEL_FEEDMULTIPLY)
+
+ const int16_t old_frm = feedrate_percentage;
+ int16_t new_frm = old_frm + int16_t(encoderPosition);
+
+ // Dead zone at 100% feedrate
+ if (old_frm == 100) {
+ if (int16_t(encoderPosition) > ENCODER_FEEDRATE_DEADZONE)
+ new_frm -= ENCODER_FEEDRATE_DEADZONE;
+ else if (int16_t(encoderPosition) < -(ENCODER_FEEDRATE_DEADZONE))
+ new_frm += ENCODER_FEEDRATE_DEADZONE;
+ else
+ new_frm = old_frm;
+ }
+ else if ((old_frm < 100 && new_frm > 100) || (old_frm > 100 && new_frm < 100))
+ new_frm = 100;
+
+ LIMIT(new_frm, 10, 999);
+
+ if (old_frm != new_frm) {
+ feedrate_percentage = new_frm;
+ encoderPosition = 0;
+ #if BOTH(HAS_SOUND, BEEP_ON_FEEDRATE_CHANGE)
+ static millis_t next_beep;
+ #ifndef GOT_MS
+ const millis_t ms = millis();
+ #endif
+ if (ELAPSED(ms, next_beep)) {
+ BUZZ(FEEDRATE_CHANGE_BEEP_DURATION, FEEDRATE_CHANGE_BEEP_FREQUENCY);
+ next_beep = ms + 500UL;
+ }
+ #endif
+ }
+
+ #endif // ULTIPANEL_FEEDMULTIPLY
+
+ draw_status_screen();
+ }
+
+ void MarlinUI::kill_screen(FSTR_P const lcd_error, FSTR_P const lcd_component) {
+ init();
+ status_printf(1, F(S_FMT ": " S_FMT), FTOP(lcd_error), FTOP(lcd_component));
+ TERN_(HAS_MARLINUI_MENU, return_to_status());
+
+ // RED ALERT. RED ALERT.
+ #if ENABLED(PRINTER_EVENT_LEDS)
+ leds.set_color(LEDColorRed());
+ #ifdef NEOPIXEL_BKGD_INDEX_FIRST
+ neo.set_background_color(255, 0, 0, 0);
+ neo.show();
+ #endif
+ #endif
+
+ draw_kill_screen();
+ }
+
+ #if HAS_TOUCH_SLEEP
+ #if HAS_TOUCH_BUTTONS
+ #include "touch/touch_buttons.h"
+ #else
+ #include "tft/touch.h"
+ #endif
+ // Wake up a sleeping TFT
+ void MarlinUI::wakeup_screen() {
+ TERN(HAS_TOUCH_BUTTONS, touchBt.wakeUp(), touch.wakeUp());
+ }
+ #endif
+
+ void MarlinUI::quick_feedback(const bool clear_buttons/*=true*/) {
+ TERN_(HAS_TOUCH_SLEEP, wakeup_screen()); // Wake up the TFT with most buttons
+ TERN_(HAS_MARLINUI_MENU, refresh());
+
+ #if HAS_ENCODER_ACTION
+ if (clear_buttons)
+ TERN_(HAS_ADC_BUTTONS, keypad_buttons =) buttons = 0;
+ next_button_update_ms = millis() + 500;
+ #else
+ UNUSED(clear_buttons);
+ #endif
+
+ chirp(); // Buzz and wait. Is the delay needed for buttons to settle?
+
+ #if HAS_CHIRP && HAS_MARLINUI_MENU
+ #if HAS_BEEPER
+ for (int8_t i = 5; i--;) { buzzer.tick(); delay(2); }
+ #else
+ delay(10);
+ #endif
+ #endif
+ }
+
+ ////////////////////////////////////////////
+ /////////////// Manual Move ////////////////
+ ////////////////////////////////////////////
+
+ #if HAS_MARLINUI_MENU
+
+ ManualMove MarlinUI::manual_move{};
+
+ millis_t ManualMove::start_time = 0;
+ float ManualMove::menu_scale = 1;
+ screenFunc_t ManualMove::screen_ptr;
+ #if IS_KINEMATIC
+ float ManualMove::offset = 0;
+ xyze_pos_t ManualMove::all_axes_destination = { 0 };
+ bool ManualMove::processing = false;
+ #endif
+ #if MULTI_E_MANUAL
+ int8_t ManualMove::e_index = 0;
+ #endif
+ AxisEnum ManualMove::axis = NO_AXIS_ENUM;
+ #if ENABLED(MANUAL_E_MOVES_RELATIVE)
+ float ManualMove::e_origin = 0;
+ #endif
+
+ /**
+ * If a manual move has been posted and its time has arrived, and if the planner
+ * has a space for it, then add a linear move to current_position the planner.
+ *
+ * If any manual move needs to be interrupted, make sure to force a manual move
+ * by setting manual_move.start_time to millis() after updating current_position.
+ *
+ * To post a manual move:
+ * - Update current_position to the new place you want to go.
+ * - Set manual_move.axis to an axis like X_AXIS. Use ALL_AXES_ENUM for diagonal moves.
+ * - Set manual_move.start_time to a point in the future (in ms) when the move should be done.
+ *
+ * For kinematic machines:
+ * - Set manual_move.offset to modify one axis and post the move.
+ * This is used to achieve more rapid stepping on kinematic machines.
+ */
+ void ManualMove::task() {
+
+ if (processing) return; // Prevent re-entry from idle() calls
+
+ // Add a manual move to the queue?
+ if (axis != NO_AXIS_ENUM && ELAPSED(millis(), start_time) && !planner.is_full()) {
+
+ const feedRate_t fr_mm_s = (axis < LOGICAL_AXES) ? manual_feedrate_mm_s[axis] : XY_PROBE_FEEDRATE_MM_S;
+
+ #if IS_KINEMATIC
+
+ #if HAS_MULTI_EXTRUDER
+ REMEMBER(ae, active_extruder);
+ #if MULTI_E_MANUAL
+ if (axis == E_AXIS) active_extruder = e_index;
+ #endif
+ #endif
+
+ // Apply a linear offset to a single axis
+ if (axis == ALL_AXES_ENUM)
+ destination = all_axes_destination;
+ else if (axis <= XYZE) {
+ destination = current_position;
+ destination[axis] += offset;
+ }
+
+ // Reset for the next move
+ offset = 0;
+ axis = NO_AXIS_ENUM;
+
+ // DELTA and SCARA machines use segmented moves, which could fill the planner during the call to
+ // move_to_destination. This will cause idle() to be called, which can then call this function while the
+ // previous invocation is being blocked. Modifications to offset shouldn't be made while
+ // processing is true or the planner will get out of sync.
+ processing = true;
+ prepare_internal_move_to_destination(fr_mm_s); // will set current_position from destination
+ processing = false;
+
+ #else
+
+ // For Cartesian / Core motion simply move to the current_position
+ planner.buffer_line(current_position, fr_mm_s,
+ TERN_(MULTI_E_MANUAL, axis == E_AXIS ? e_index :) active_extruder
+ );
+
+ //SERIAL_ECHOLNPGM("Add planner.move with Axis ", AS_CHAR(AXIS_CHAR(axis)), " at FR ", fr_mm_s);
+
+ axis = NO_AXIS_ENUM;
+
+ #endif
+ }
+ }
+
+ //
+ // Tell ui.update() to start a move to current_position after a short delay.
+ //
+ void ManualMove::soon(const AxisEnum move_axis
+ OPTARG(MULTI_E_MANUAL, const int8_t eindex/*=active_extruder*/)
+ ) {
+ TERN_(MULTI_E_MANUAL, if (move_axis == E_AXIS) e_index = eindex);
+ start_time = millis() + (menu_scale < 0.99f ? 0UL : 250UL); // delay for bigger moves
+ axis = move_axis;
+ //SERIAL_ECHOLNPGM("Post Move with Axis ", AS_CHAR(AXIS_CHAR(axis)), " soon.");
+ }
+
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+
+ void MarlinUI::external_encoder() {
+ if (external_control && encoderDiff) {
+ bedlevel.encoder_diff += encoderDiff; // Encoder for UBL G29 mesh editing
+ encoderDiff = 0; // Hide encoder events from the screen handler
+ refresh(LCDVIEW_REDRAW_NOW); // ...but keep the refresh.
+ }
+ }
+
+ #endif
+
+ #endif // HAS_MARLINUI_MENU
+
+ /**
+ * Update the LCD, read encoder buttons, etc.
+ * - Read button states
+ * - Check the SD Card slot state
+ * - Act on RepRap World keypad input
+ * - Update the encoder position
+ * - Apply acceleration to the encoder position
+ * - Do refresh(LCDVIEW_CALL_REDRAW_NOW) on controller events
+ * - Reset the Info Screen timeout if there's any input
+ * - Update status indicators, if any
+ *
+ * Run the current LCD menu handler callback function:
+ * - Call the handler only if lcdDrawUpdate != LCDVIEW_NONE
+ * - Before calling the handler, LCDVIEW_CALL_NO_REDRAW => LCDVIEW_NONE
+ * - Call the menu handler. Menu handlers should do the following:
+ * - If a value changes, set lcdDrawUpdate to LCDVIEW_REDRAW_NOW and draw the value
+ * (Encoder events automatically set lcdDrawUpdate for you.)
+ * - if (should_draw()) { redraw }
+ * - Before exiting the handler set lcdDrawUpdate to:
+ * - LCDVIEW_CLEAR_CALL_REDRAW to clear screen and set LCDVIEW_CALL_REDRAW_NEXT.
+ * - LCDVIEW_REDRAW_NOW to draw now (including remaining stripes).
+ * - LCDVIEW_CALL_REDRAW_NEXT to draw now and get LCDVIEW_REDRAW_NOW on the next loop.
+ * - LCDVIEW_CALL_NO_REDRAW to draw now and get LCDVIEW_NONE on the next loop.
+ * - NOTE: For graphical displays menu handlers may be called 2 or more times per loop,
+ * so don't change lcdDrawUpdate without considering this.
+ *
+ * After the menu handler callback runs (or not):
+ * - Clear the LCD if lcdDrawUpdate == LCDVIEW_CLEAR_CALL_REDRAW
+ * - Update lcdDrawUpdate for the next loop (i.e., move one state down, usually)
+ *
+ * This function is only called from the main thread.
+ */
+
+ LCDViewAction MarlinUI::lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW;
+ millis_t next_lcd_update_ms;
+
+ inline bool can_encode() {
+ return !BUTTON_PRESSED(ENC_EN); // Update encoder only when ENC_EN is not LOW (pressed)
+ }
+
+ void MarlinUI::update() {
+
+ static uint16_t max_display_update_time = 0;
+ millis_t ms = millis();
+
+ #if LED_POWEROFF_TIMEOUT > 0
+ leds.update_timeout(powerManager.psu_on);
+ #endif
+
+ #if HAS_MARLINUI_MENU
+
+ // Handle any queued Move Axis motion
+ manual_move.task();
+
+ // Update button states for button_pressed(), etc.
+ // If the state changes the next update may be delayed 300-500ms.
+ update_buttons();
+
+ // If the action button is pressed...
+ static bool wait_for_unclick; // = false
+
+ auto do_click = [&]{
+ wait_for_unclick = true; // - Set debounce flag to ignore continuous clicks
+ lcd_clicked = !wait_for_user; // - Keep the click if not waiting for a user-click
+ wait_for_user = false; // - Any click clears wait for user
+ quick_feedback(); // - Always make a click sound
+ };
+
+ #if HAS_TOUCH_BUTTONS
+ if (touch_buttons) {
+ reset_status_timeout(ms);
+ if (touch_buttons & (EN_A | EN_B)) { // Menu arrows, in priority
+ if (ELAPSED(ms, next_button_update_ms)) {
+ encoderDiff = (ENCODER_STEPS_PER_MENU_ITEM) * epps * encoderDirection;
+ if (touch_buttons & EN_A) encoderDiff *= -1;
+ TERN_(AUTO_BED_LEVELING_UBL, external_encoder());
+ next_button_update_ms = ms + repeat_delay; // Assume the repeat delay
+ if (!wait_for_unclick) {
+ next_button_update_ms += 250; // Longer delay on first press
+ wait_for_unclick = true; // Avoid Back/Select click while repeating
+ chirp();
+ }
+ }
+ }
+ else if (!wait_for_unclick && (buttons & EN_C)) // OK button, if not waiting for a debounce release:
+ do_click();
+ }
+ // keep wait_for_unclick value
+ #endif
+
+ if (!touch_buttons) {
+ // Integrated LCD click handling via button_pressed
+ if (!external_control && button_pressed()) {
+ if (!wait_for_unclick) do_click(); // Handle the click
+ }
+ else
+ wait_for_unclick = false;
+ }
+
+ if (LCD_BACK_CLICKED()) {
+ quick_feedback();
+ goto_previous_screen();
+ }
+
+ #endif // HAS_MARLINUI_MENU
+
+ if (ELAPSED(ms, next_lcd_update_ms) || TERN0(HAS_MARLINUI_U8GLIB, drawing_screen)) {
+
+ next_lcd_update_ms = ms + LCD_UPDATE_INTERVAL;
+
+ #if HAS_TOUCH_BUTTONS
+
+ if (on_status_screen()) next_lcd_update_ms += (LCD_UPDATE_INTERVAL) * 2;
+
+ TERN_(HAS_ENCODER_ACTION, touch_buttons = touchBt.read_buttons());
+
+ #endif
+
+ TERN_(LCD_HAS_STATUS_INDICATORS, update_indicators());
+
+ #if HAS_ENCODER_ACTION
+
+ TERN_(HAS_SLOW_BUTTONS, slow_buttons = read_slow_buttons()); // Buttons that take too long to read in interrupt context
+
+ if (TERN0(IS_RRW_KEYPAD, handle_keypad()))
+ reset_status_timeout(ms);
+
+ uint8_t abs_diff = ABS(encoderDiff);
+
+ #if ENCODER_PULSES_PER_STEP > 1
+ // When reversing the encoder direction, a movement step can be missed because
+ // encoderDiff has a non-zero residual value, making the controller unresponsive.
+ // The fix clears the residual value when the encoder is idle.
+ // Also check if past half the threshold to compensate for missed single steps.
+ static int8_t lastEncoderDiff;
+
+ // Timeout? No decoder change since last check. 10 or 20 times per second.
+ if (encoderDiff == lastEncoderDiff && abs_diff <= epps / 2) // Same direction & size but not over a half-step?
+ encoderDiff = 0; // Clear residual pulses.
+ else if (WITHIN(abs_diff, epps / 2 + 1, epps - 1)) { // Past half of threshold?
+ abs_diff = epps; // Treat as a full step size
+ encoderDiff = (encoderDiff < 0 ? -1 : 1) * abs_diff; // ...in the spin direction.
+ }
+ TERN_(HAS_TOUCH_SLEEP, if (lastEncoderDiff != encoderDiff) wakeup_screen());
+ lastEncoderDiff = encoderDiff;
+ #endif
+
+ const bool encoderPastThreshold = (abs_diff >= epps);
+ if (encoderPastThreshold || lcd_clicked) {
+ if (encoderPastThreshold && TERN1(IS_TFTGLCD_PANEL, !external_control)) {
+
+ #if BOTH(HAS_MARLINUI_MENU, ENCODER_RATE_MULTIPLIER)
+
+ int32_t encoderMultiplier = 1;
+
+ if (encoderRateMultiplierEnabled) {
+ const float encoderMovementSteps = float(abs_diff) / epps;
+
+ if (lastEncoderMovementMillis) {
+ // Note that the rate is always calculated between two passes through the
+ // loop and that the abs of the encoderDiff value is tracked.
+ const float encoderStepRate = encoderMovementSteps / float(ms - lastEncoderMovementMillis) * 1000;
+
+ if (encoderStepRate >= ENCODER_100X_STEPS_PER_SEC) encoderMultiplier = 100;
+ else if (encoderStepRate >= ENCODER_10X_STEPS_PER_SEC) encoderMultiplier = 10;
+
+ // Enable to output the encoder steps per second value
+ //#define ENCODER_RATE_MULTIPLIER_DEBUG
+ #if ENABLED(ENCODER_RATE_MULTIPLIER_DEBUG)
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM("Enc Step Rate: ", encoderStepRate);
+ SERIAL_ECHOPGM(" Multiplier: ", encoderMultiplier);
+ SERIAL_ECHOPGM(" ENCODER_10X_STEPS_PER_SEC: ", ENCODER_10X_STEPS_PER_SEC);
+ SERIAL_ECHOPGM(" ENCODER_100X_STEPS_PER_SEC: ", ENCODER_100X_STEPS_PER_SEC);
+ SERIAL_EOL();
+ #endif
+ }
+
+ lastEncoderMovementMillis = ms;
+ } // encoderRateMultiplierEnabled
+
+ #else
+
+ constexpr int32_t encoderMultiplier = 1;
+
+ #endif // ENCODER_RATE_MULTIPLIER
+
+ if (can_encode()) encoderPosition += (encoderDiff * encoderMultiplier) / epps;
+
+ encoderDiff = 0;
+ }
+
+ reset_status_timeout(ms);
+
+ #if LCD_BACKLIGHT_TIMEOUT
+ refresh_backlight_timeout();
+ #elif HAS_DISPLAY_SLEEP
+ refresh_screen_timeout();
+ #endif
+
+ refresh(LCDVIEW_REDRAW_NOW);
+
+ #if LED_POWEROFF_TIMEOUT > 0
+ if (!powerManager.psu_on) leds.reset_timeout(ms);
+ #endif
+ } // encoder activity
+
+ #endif // HAS_ENCODER_ACTION
+
+ // This runs every ~100ms when idling often enough.
+ // Instead of tracking changes just redraw the Status Screen once per second.
+ if (on_status_screen() && !lcd_status_update_delay--) {
+ lcd_status_update_delay = TERN(HAS_MARLINUI_U8GLIB, 12, 9);
+ if (max_display_update_time) max_display_update_time--; // Be sure never go to a very big number
+ refresh(LCDVIEW_REDRAW_NOW);
+ }
+
+ #if BOTH(HAS_MARLINUI_MENU, SCROLL_LONG_FILENAMES)
+ // If scrolling of long file names is enabled and we are in the sd card menu,
+ // cause a refresh to occur until all the text has scrolled into view.
+ if (currentScreen == menu_media && !lcd_status_update_delay--) {
+ lcd_status_update_delay = ++filename_scroll_pos >= filename_scroll_max ? 12 : 4; // Long delay at end and start
+ if (filename_scroll_pos > filename_scroll_max) filename_scroll_pos = 0;
+ refresh(LCDVIEW_REDRAW_NOW);
+ reset_status_timeout(ms);
+ }
+ #endif
+
+ // Then we want to use only 50% of the time
+ const uint16_t bbr2 = planner.block_buffer_runtime() >> 1;
+
+ if ((should_draw() || drawing_screen) && (!bbr2 || bbr2 > max_display_update_time)) {
+
+ // Change state of drawing flag between screen updates
+ if (!drawing_screen) switch (lcdDrawUpdate) {
+ case LCDVIEW_CALL_NO_REDRAW:
+ refresh(LCDVIEW_NONE);
+ break;
+ case LCDVIEW_CLEAR_CALL_REDRAW:
+ case LCDVIEW_CALL_REDRAW_NEXT:
+ refresh(LCDVIEW_REDRAW_NOW);
+ case LCDVIEW_REDRAW_NOW: // set above, or by a handler through LCDVIEW_CALL_REDRAW_NEXT
+ case LCDVIEW_NONE:
+ break;
+ } // switch
+
+ TERN_(HAS_ADC_BUTTONS, keypad_buttons = 0);
+
+ #if HAS_MARLINUI_U8GLIB
+
+ #if ENABLED(LIGHTWEIGHT_UI)
+ const bool in_status = on_status_screen(),
+ do_u8g_loop = !in_status;
+ lcd_in_status(in_status);
+ if (in_status) status_screen();
+ #else
+ constexpr bool do_u8g_loop = true;
+ #endif
+
+ if (do_u8g_loop) {
+ if (!drawing_screen) { // If not already drawing pages
+ u8g.firstPage(); // Start the first page
+ drawing_screen = first_page = true; // Flag as drawing pages
+ }
+ set_font(FONT_MENU); // Setup font for every page draw
+ u8g.setColorIndex(1); // And reset the color
+ run_current_screen(); // Draw and process the current screen
+ first_page = false;
+
+ // The screen handler can clear drawing_screen for an action that changes the screen.
+ // If still drawing and there's another page, update max-time and return now.
+ // The nextPage will already be set up on the next call.
+ if (drawing_screen && (drawing_screen = u8g.nextPage())) {
+ if (on_status_screen())
+ NOLESS(max_display_update_time, millis() - ms);
+ return;
+ }
+ }
+
+ #else
+
+ run_current_screen();
+
+ // Apply all DWIN drawing after processing
+ TERN_(IS_DWIN_MARLINUI, DWIN_UpdateLCD());
+
+ #endif
+
+ TERN_(HAS_MARLINUI_MENU, lcd_clicked = false);
+
+ // Keeping track of the longest time for an individual LCD update.
+ // Used to do screen throttling when the planner starts to fill up.
+ if (on_status_screen())
+ NOLESS(max_display_update_time, millis() - ms);
+ }
+
+ #if SCREENS_CAN_TIME_OUT
+ // Return to Status Screen after a timeout
+ if (on_status_screen() || defer_return_to_status)
+ reset_status_timeout(ms);
+ else if (ELAPSED(ms, return_to_status_ms))
+ return_to_status();
+ #endif
+
+ #if LCD_BACKLIGHT_TIMEOUT
+ if (backlight_off_ms && ELAPSED(ms, backlight_off_ms)) {
+ WRITE(LCD_BACKLIGHT_PIN, LOW); // Backlight off
+ backlight_off_ms = 0;
+ }
+ #elif HAS_DISPLAY_SLEEP
+ if (screen_timeout_millis && ELAPSED(ms, screen_timeout_millis))
+ sleep_on();
+ #endif
+
+ // Change state of drawing flag between screen updates
+ if (!drawing_screen) switch (lcdDrawUpdate) {
+ case LCDVIEW_CLEAR_CALL_REDRAW:
+ clear_lcd(); break;
+ case LCDVIEW_REDRAW_NOW:
+ refresh(LCDVIEW_NONE);
+ case LCDVIEW_NONE:
+ case LCDVIEW_CALL_REDRAW_NEXT:
+ case LCDVIEW_CALL_NO_REDRAW:
+ default: break;
+ } // switch
+
+ } // ELAPSED(ms, next_lcd_update_ms)
+
+ TERN_(HAS_GRAPHICAL_TFT, tft_idle());
+ }
+
+ #if HAS_ADC_BUTTONS
+
+ typedef struct {
+ raw_adc_t ADCKeyValueMin, ADCKeyValueMax;
+ uint8_t ADCKeyNo;
+ } _stADCKeypadTable_;
+
+ #ifndef ADC_BUTTONS_VALUE_SCALE
+ #define ADC_BUTTONS_VALUE_SCALE 1.0 // for the power voltage equal to the reference voltage
+ #endif
+ #ifndef ADC_BUTTONS_R_PULLUP
+ #define ADC_BUTTONS_R_PULLUP 4.7 // common pull-up resistor in the voltage divider
+ #endif
+ #ifndef ADC_BUTTONS_LEFT_R_PULLDOWN
+ #define ADC_BUTTONS_LEFT_R_PULLDOWN 0.47 // pull-down resistor for LEFT button voltage divider
+ #endif
+ #ifndef ADC_BUTTONS_RIGHT_R_PULLDOWN
+ #define ADC_BUTTONS_RIGHT_R_PULLDOWN 4.7 // pull-down resistor for RIGHT button voltage divider
+ #endif
+ #ifndef ADC_BUTTONS_UP_R_PULLDOWN
+ #define ADC_BUTTONS_UP_R_PULLDOWN 1.0 // pull-down resistor for UP button voltage divider
+ #endif
+ #ifndef ADC_BUTTONS_DOWN_R_PULLDOWN
+ #define ADC_BUTTONS_DOWN_R_PULLDOWN 10.0 // pull-down resistor for DOWN button voltage divider
+ #endif
+ #ifndef ADC_BUTTONS_MIDDLE_R_PULLDOWN
+ #define ADC_BUTTONS_MIDDLE_R_PULLDOWN 2.2 // pull-down resistor for MIDDLE button voltage divider
+ #endif
+
+ // Calculate the ADC value for the voltage divider with specified pull-down resistor value
+ #define ADC_BUTTON_VALUE(r) raw_adc_t(HAL_ADC_RANGE * (ADC_BUTTONS_VALUE_SCALE) * r / (r + ADC_BUTTONS_R_PULLUP))
+
+ static constexpr raw_adc_t adc_button_tolerance = HAL_ADC_RANGE * 25 / 1024,
+ adc_other_button = raw_adc_t(uint32_t(HAL_ADC_RANGE * 1000UL) / 1024UL);
+ static const _stADCKeypadTable_ stADCKeyTable[] PROGMEM = {
+ // VALUE_MIN, VALUE_MAX, KEY
+ { adc_other_button, HAL_ADC_RANGE, 1 + BLEN_KEYPAD_F1 }, // F1
+ { adc_other_button, HAL_ADC_RANGE, 1 + BLEN_KEYPAD_F2 }, // F2
+ { adc_other_button, HAL_ADC_RANGE, 1 + BLEN_KEYPAD_F3 }, // F3
+ { ADC_BUTTON_VALUE(ADC_BUTTONS_LEFT_R_PULLDOWN) - adc_button_tolerance,
+ ADC_BUTTON_VALUE(ADC_BUTTONS_LEFT_R_PULLDOWN) + adc_button_tolerance, 1 + BLEN_KEYPAD_LEFT }, // LEFT ( 272 ... 472)
+ { ADC_BUTTON_VALUE(ADC_BUTTONS_RIGHT_R_PULLDOWN) - adc_button_tolerance,
+ ADC_BUTTON_VALUE(ADC_BUTTONS_RIGHT_R_PULLDOWN) + adc_button_tolerance, 1 + BLEN_KEYPAD_RIGHT }, // RIGHT (1948 ... 2148)
+ { ADC_BUTTON_VALUE(ADC_BUTTONS_UP_R_PULLDOWN) - adc_button_tolerance,
+ ADC_BUTTON_VALUE(ADC_BUTTONS_UP_R_PULLDOWN) + adc_button_tolerance, 1 + BLEN_KEYPAD_UP }, // UP ( 618 ... 818)
+ { ADC_BUTTON_VALUE(ADC_BUTTONS_DOWN_R_PULLDOWN) - adc_button_tolerance,
+ ADC_BUTTON_VALUE(ADC_BUTTONS_DOWN_R_PULLDOWN) + adc_button_tolerance, 1 + BLEN_KEYPAD_DOWN }, // DOWN (2686 ... 2886)
+ { ADC_BUTTON_VALUE(ADC_BUTTONS_MIDDLE_R_PULLDOWN) - adc_button_tolerance,
+ ADC_BUTTON_VALUE(ADC_BUTTONS_MIDDLE_R_PULLDOWN) + adc_button_tolerance, 1 + BLEN_KEYPAD_MIDDLE }, // ENTER (1205 ... 1405)
+ };
+
+ uint8_t get_ADC_keyValue() {
+ if (thermalManager.ADCKey_count >= 16) {
+ const raw_adc_t currentkpADCValue = thermalManager.current_ADCKey_raw;
+ thermalManager.current_ADCKey_raw = HAL_ADC_RANGE;
+ thermalManager.ADCKey_count = 0;
+ if (currentkpADCValue < adc_other_button)
+ LOOP_L_N(i, ADC_KEY_NUM) {
+ const raw_adc_t lo = pgm_read_word(&stADCKeyTable[i].ADCKeyValueMin),
+ hi = pgm_read_word(&stADCKeyTable[i].ADCKeyValueMax);
+ if (WITHIN(currentkpADCValue, lo, hi)) return pgm_read_byte(&stADCKeyTable[i].ADCKeyNo);
+ }
+ }
+ return 0;
+ }
+
+ #endif // HAS_ADC_BUTTONS
+
+ #if HAS_ENCODER_ACTION
+
+ /**
+ * Read encoder buttons from the hardware registers
+ * Warning: This function is called from interrupt context!
+ */
+ void MarlinUI::update_buttons() {
+ const millis_t now = millis();
+ if (ELAPSED(now, next_button_update_ms)) {
+
+ #if HAS_DIGITAL_BUTTONS
+
+ #if ANY_BUTTON(EN1, EN2, ENC, BACK)
+
+ uint8_t newbutton = 0;
+ if (BUTTON_PRESSED(EN1)) newbutton |= EN_A;
+ if (BUTTON_PRESSED(EN2)) newbutton |= EN_B;
+ if (can_encode() && BUTTON_PRESSED(ENC)) newbutton |= EN_C;
+ if (BUTTON_PRESSED(BACK)) newbutton |= EN_D;
+
+ #else
+
+ constexpr uint8_t newbutton = 0;
+
+ #endif
+
+ //
+ // Directional buttons
+ //
+ #if ANY_BUTTON(UP, DWN, LFT, RT)
+
+ const int8_t pulses = epps * encoderDirection;
+
+ if (BUTTON_PRESSED(UP)) {
+ encoderDiff = (ENCODER_STEPS_PER_MENU_ITEM) * pulses;
+ next_button_update_ms = now + 300;
+ }
+ else if (BUTTON_PRESSED(DWN)) {
+ encoderDiff = -(ENCODER_STEPS_PER_MENU_ITEM) * pulses;
+ next_button_update_ms = now + 300;
+ }
+ else if (BUTTON_PRESSED(LFT)) {
+ encoderDiff = -pulses;
+ next_button_update_ms = now + 300;
+ }
+ else if (BUTTON_PRESSED(RT)) {
+ encoderDiff = pulses;
+ next_button_update_ms = now + 300;
+ }
+
+ #endif // UP || DWN || LFT || RT
+
+ buttons = (newbutton | TERN0(HAS_SLOW_BUTTONS, slow_buttons)
+ #if BOTH(HAS_TOUCH_BUTTONS, HAS_ENCODER_ACTION)
+ | (touch_buttons & TERN(HAS_ENCODER_WHEEL, ~(EN_A | EN_B), 0xFF))
+ #endif
+ );
+
+ #elif HAS_ADC_BUTTONS
+
+ buttons = 0;
+
+ #endif
+
+ #if HAS_ADC_BUTTONS
+ if (keypad_buttons == 0) {
+ const uint8_t b = get_ADC_keyValue();
+ if (WITHIN(b, 1, 8)) keypad_buttons = _BV(b - 1);
+ }
+ #endif
+
+ #if HAS_SHIFT_ENCODER
+ /**
+ * Set up Rotary Encoder bit values (for two pin encoders to indicate movement).
+ * These values are independent of which pins are used for EN_A / EN_B indications.
+ * The rotary encoder part is also independent of the LCD chipset.
+ */
+ uint8_t val = 0;
+ WRITE(SHIFT_LD_PIN, LOW);
+ WRITE(SHIFT_LD_PIN, HIGH);
+ LOOP_L_N(i, 8) {
+ val >>= 1;
+ if (READ(SHIFT_OUT_PIN)) SBI(val, 7);
+ WRITE(SHIFT_CLK_PIN, HIGH);
+ WRITE(SHIFT_CLK_PIN, LOW);
+ }
+ TERN(REPRAPWORLD_KEYPAD, keypad_buttons, buttons) = ~val;
+ #endif
+
+ #if IS_TFTGLCD_PANEL
+ next_button_update_ms = now + (LCD_UPDATE_INTERVAL / 2);
+ buttons = slow_buttons;
+ TERN_(AUTO_BED_LEVELING_UBL, external_encoder());
+ #endif
+
+ } // next_button_update_ms
+
+ #if HAS_ENCODER_WHEEL
+ static uint8_t lastEncoderBits;
+
+ // Manage encoder rotation
+ #define ENCODER_SPIN(_E1, _E2) switch (lastEncoderBits) { case _E1: encoderDiff += encoderDirection; break; case _E2: encoderDiff -= encoderDirection; }
+
+ uint8_t enc = 0;
+ if (buttons & EN_A) enc |= B01;
+ if (buttons & EN_B) enc |= B10;
+ if (enc != lastEncoderBits) {
+ switch (enc) {
+ case ENCODER_PHASE_0: ENCODER_SPIN(ENCODER_PHASE_3, ENCODER_PHASE_1); break;
+ case ENCODER_PHASE_1: ENCODER_SPIN(ENCODER_PHASE_0, ENCODER_PHASE_2); break;
+ case ENCODER_PHASE_2: ENCODER_SPIN(ENCODER_PHASE_1, ENCODER_PHASE_3); break;
+ case ENCODER_PHASE_3: ENCODER_SPIN(ENCODER_PHASE_2, ENCODER_PHASE_0); break;
+ }
+ #if BOTH(HAS_MARLINUI_MENU, AUTO_BED_LEVELING_UBL)
+ external_encoder();
+ #endif
+ lastEncoderBits = enc;
+ }
+
+ #endif // HAS_ENCODER_WHEEL
+ }
+
+ #endif // HAS_ENCODER_ACTION
+
+#endif // HAS_WIRED_LCD
+
+#if HAS_STATUS_MESSAGE
+
+ ////////////////////////////////////////////
+ ////////////// Status Message //////////////
+ ////////////////////////////////////////////
+
+ #if ENABLED(EXTENSIBLE_UI)
+ #include "extui/ui_api.h"
+ #endif
+
+ bool MarlinUI::has_status() { return (status_message[0] != '\0'); }
+
+ void MarlinUI::set_status(const char * const cstr, const bool persist) {
+ if (alert_level) return;
+
+ TERN_(HOST_STATUS_NOTIFICATIONS, hostui.notify(cstr));
+
+ // Here we have a problem. The message is encoded in UTF8, so
+ // arbitrarily cutting it will be a problem. We MUST be sure
+ // that there is no cutting in the middle of a multibyte character!
+
+ // Get a pointer to the null terminator
+ const char* pend = cstr + strlen(cstr);
+
+ // If length of supplied UTF8 string is greater than
+ // our buffer size, start cutting whole UTF8 chars
+ while ((pend - cstr) > MAX_MESSAGE_LENGTH) {
+ --pend;
+ while (!START_OF_UTF8_CHAR(*pend)) --pend;
+ };
+
+ // At this point, we have the proper cut point. Use it
+ uint8_t maxLen = pend - cstr;
+ strncpy(status_message, cstr, maxLen);
+ status_message[maxLen] = '\0';
+
+ finish_status(persist);
+ }
+
+ /**
+ * Reset the status message
+ */
+
+ void MarlinUI::reset_status(const bool no_welcome) {
+ #if SERVICE_INTERVAL_1 > 0
+ static PGMSTR(service1, "> " SERVICE_NAME_1 "!");
+ #endif
+ #if SERVICE_INTERVAL_2 > 0
+ static PGMSTR(service2, "> " SERVICE_NAME_2 "!");
+ #endif
+ #if SERVICE_INTERVAL_3 > 0
+ static PGMSTR(service3, "> " SERVICE_NAME_3 "!");
+ #endif
+
+ FSTR_P msg;
+ if (printingIsPaused())
+ msg = GET_TEXT_F(MSG_PRINT_PAUSED);
+ #if ENABLED(SDSUPPORT)
+ else if (IS_SD_PRINTING())
+ return set_status(card.longest_filename(), true);
+ #endif
+ else if (print_job_timer.isRunning())
+ msg = GET_TEXT_F(MSG_PRINTING);
+
+ #if SERVICE_INTERVAL_1 > 0
+ else if (print_job_timer.needsService(1)) msg = FPSTR(service1);
+ #endif
+ #if SERVICE_INTERVAL_2 > 0
+ else if (print_job_timer.needsService(2)) msg = FPSTR(service2);
+ #endif
+ #if SERVICE_INTERVAL_3 > 0
+ else if (print_job_timer.needsService(3)) msg = FPSTR(service3);
+ #endif
+
+ else if (!no_welcome) msg = GET_TEXT_F(WELCOME_MSG);
+
+ else if (ENABLED(DWIN_LCD_PROUI))
+ msg = F("");
+ else
+ return;
+
+ set_status(msg, -1);
+ }
+
+ /**
+ * Set Status with a fixed string and alert level.
+ * @param fstr A constant F-string to set as the status.
+ * @param level Alert level. Negative to ignore and reset the level. Non-zero never expires.
+ */
+ void MarlinUI::set_status(FSTR_P const fstr, int8_t level) {
+ // Alerts block lower priority messages
+ if (level < 0) level = alert_level = 0;
+ if (level < alert_level) return;
+ alert_level = level;
+
+ PGM_P const pstr = FTOP(fstr);
+
+ // Since the message is encoded in UTF8 it must
+ // only be cut on a character boundary.
+
+ // Get a pointer to the null terminator
+ PGM_P pend = pstr + strlen_P(pstr);
+
+ // If length of supplied UTF8 string is greater than
+ // the buffer size, start cutting whole UTF8 chars
+ while ((pend - pstr) > MAX_MESSAGE_LENGTH) {
+ --pend;
+ while (!START_OF_UTF8_CHAR(pgm_read_byte(pend))) --pend;
+ };
+
+ // At this point, we have the proper cut point. Use it
+ uint8_t maxLen = pend - pstr;
+ strncpy_P(status_message, pstr, maxLen);
+ status_message[maxLen] = '\0';
+
+ TERN_(HOST_STATUS_NOTIFICATIONS, hostui.notify(fstr));
+
+ finish_status(level > 0);
+ }
+
+ void MarlinUI::set_alert_status(FSTR_P const fstr) {
+ set_status(fstr, 1);
+ TERN_(HAS_TOUCH_SLEEP, wakeup_screen());
+ TERN_(HAS_MARLINUI_MENU, return_to_status());
+ }
+
+ #include
+
+ void MarlinUI::status_printf(int8_t level, FSTR_P const fmt, ...) {
+ // Alerts block lower priority messages
+ if (level < 0) level = alert_level = 0;
+ if (level < alert_level) return;
+ alert_level = level;
+
+ va_list args;
+ va_start(args, FTOP(fmt));
+ vsnprintf_P(status_message, MAX_MESSAGE_LENGTH, FTOP(fmt), args);
+ va_end(args);
+
+ TERN_(HOST_STATUS_NOTIFICATIONS, hostui.notify(status_message));
+
+ finish_status(level > 0);
+ }
+
+ void MarlinUI::finish_status(const bool persist) {
+
+ UNUSED(persist);
+
+ set_status_reset_fn();
+
+ TERN_(HAS_STATUS_MESSAGE_TIMEOUT, status_message_expire_ms = persist ? 0 : millis() + (STATUS_MESSAGE_TIMEOUT_SEC) * 1000UL);
+
+ #if HAS_WIRED_LCD
+
+ #if BASIC_PROGRESS_BAR || BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT)
+ const millis_t ms = millis();
+ #endif
+
+ #if BASIC_PROGRESS_BAR
+ progress_bar_ms = ms;
+ #if PROGRESS_MSG_EXPIRE > 0
+ expire_status_ms = persist ? 0 : ms + PROGRESS_MSG_EXPIRE;
+ #endif
+ #endif
+
+ #if BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT)
+ next_filament_display = ms + 5000UL; // Show status message for 5s
+ #endif
+
+ #endif
+
+ #if ENABLED(STATUS_MESSAGE_SCROLLING) && EITHER(HAS_WIRED_LCD, DWIN_LCD_PROUI)
+ status_scroll_offset = 0;
+ #endif
+
+ TERN_(EXTENSIBLE_UI, ExtUI::onStatusChanged(status_message));
+ TERN_(DWIN_CREALITY_LCD, DWIN_StatusChanged(status_message));
+ TERN_(DWIN_LCD_PROUI, DWIN_CheckStatusMessage());
+ TERN_(DWIN_CREALITY_LCD_JYERSUI, CrealityDWIN.Update_Status(status_message));
+ }
+
+ #if ENABLED(STATUS_MESSAGE_SCROLLING)
+
+ void MarlinUI::advance_status_scroll() {
+ // Advance by one UTF8 code-word
+ if (status_scroll_offset < utf8_strlen(status_message))
+ while (!START_OF_UTF8_CHAR(status_message[++status_scroll_offset]));
+ else
+ status_scroll_offset = 0;
+ }
+
+ char* MarlinUI::status_and_len(uint8_t &len) {
+ char *out = status_message + status_scroll_offset;
+ len = utf8_strlen(out);
+ return out;
+ }
+
+ #endif
+
+#else // !HAS_STATUS_MESSAGE
+
+ //
+ // Send the status line as a host notification
+ //
+ void MarlinUI::set_status(const char * const cstr, const bool) {
+ TERN(HOST_PROMPT_SUPPORT, hostui.notify(cstr), UNUSED(cstr));
+ }
+ void MarlinUI::set_status(FSTR_P const fstr, const int8_t) {
+ TERN(HOST_PROMPT_SUPPORT, hostui.notify(fstr), UNUSED(fstr));
+ }
+ void MarlinUI::status_printf(int8_t, FSTR_P const fstr, ...) {
+ TERN(HOST_PROMPT_SUPPORT, hostui.notify(fstr), UNUSED(fstr));
+ }
+
+#endif // !HAS_STATUS_MESSAGE
+
+#if HAS_DISPLAY
+
+ #if ENABLED(SDSUPPORT)
+ extern bool wait_for_user, wait_for_heatup;
+ #endif
+
+ void MarlinUI::abort_print() {
+ #if ENABLED(SDSUPPORT)
+ wait_for_heatup = wait_for_user = false;
+ card.abortFilePrintSoon();
+ #endif
+ #ifdef ACTION_ON_CANCEL
+ hostui.cancel();
+ #endif
+ IF_DISABLED(SDSUPPORT, print_job_timer.stop());
+ TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_open(PROMPT_INFO, F("UI Aborted"), FPSTR(DISMISS_STR)));
+ LCD_MESSAGE(MSG_PRINT_ABORTED);
+ TERN_(HAS_MARLINUI_MENU, return_to_status());
+ }
+
+ #if BOTH(HAS_MARLINUI_MENU, PSU_CONTROL)
+
+ void MarlinUI::poweroff() {
+ queue.inject(F("M81" TERN_(POWER_OFF_WAIT_FOR_COOLDOWN, "S")));
+ return_to_status();
+ }
+
+ #endif
+
+ void MarlinUI::flow_fault() {
+ LCD_ALERTMESSAGE(MSG_FLOWMETER_FAULT);
+ BUZZ(1000, 440);
+ TERN_(HAS_MARLINUI_MENU, return_to_status());
+ }
+
+ void MarlinUI::pause_print() {
+ #if HAS_MARLINUI_MENU
+ synchronize(GET_TEXT_F(MSG_PAUSING));
+ defer_status_screen();
+ #endif
+
+ TERN_(HAS_TOUCH_SLEEP, wakeup_screen());
+ TERN_(HOST_PROMPT_SUPPORT, hostui.prompt_open(PROMPT_PAUSE_RESUME, F("UI Pause"), F("Resume")));
+
+ LCD_MESSAGE(MSG_PRINT_PAUSED);
+
+ #if ENABLED(PARK_HEAD_ON_PAUSE)
+ pause_show_message(PAUSE_MESSAGE_PARKING, PAUSE_MODE_PAUSE_PRINT); // Show message immediately to let user know about pause in progress
+ queue.inject(F("M25 P\nM24"));
+ #elif ENABLED(SDSUPPORT)
+ queue.inject(F("M25"));
+ #elif defined(ACTION_ON_PAUSE)
+ hostui.pause();
+ #endif
+ }
+
+ void MarlinUI::resume_print() {
+ reset_status();
+ TERN_(PARK_HEAD_ON_PAUSE, wait_for_heatup = wait_for_user = false);
+ TERN_(SDSUPPORT, if (IS_SD_PAUSED()) queue.inject_P(M24_STR));
+ #ifdef ACTION_ON_RESUME
+ hostui.resume();
+ #endif
+ print_job_timer.start(); // Also called by M24
+ }
+
+ #if HAS_PRINT_PROGRESS
+
+ MarlinUI::progress_t MarlinUI::_get_progress() {
+ return (
+ TERN0(LCD_SET_PROGRESS_MANUALLY, (progress_override & PROGRESS_MASK))
+ #if ENABLED(SDSUPPORT)
+ ?: TERN(HAS_PRINT_PROGRESS_PERMYRIAD, card.permyriadDone(), card.percentDone())
+ #endif
+ );
+ }
+
+ #endif
+
+ #if HAS_TOUCH_BUTTONS
+
+ //
+ // Screen Click
+ // - On menu screens move directly to the touched item
+ // - On menu screens, right side (last 3 cols) acts like a scroll - half up => prev page, half down = next page
+ // - On select screens (and others) touch the Right Half for +, Left Half for -
+ // - On edit screens, touch Up Half for -, Bottom Half to +
+ //
+ void MarlinUI::screen_click(const uint8_t row, const uint8_t col, const uint8_t, const uint8_t) {
+ const millis_t now = millis();
+ if (PENDING(now, next_button_update_ms)) return;
+ next_button_update_ms = now + repeat_delay; // Assume the repeat delay
+ const int8_t xdir = col < (LCD_WIDTH ) / 2 ? -1 : 1,
+ ydir = row < (LCD_HEIGHT) / 2 ? -1 : 1;
+ if (on_edit_screen)
+ encoderDiff = epps * ydir;
+ else if (screen_items > 0) {
+ // Last 5 cols act as a scroll :-)
+ if (col > (LCD_WIDTH) - 5)
+ // 2 * LCD_HEIGHT to scroll to bottom of next page. (LCD_HEIGHT would only go 1 item down.)
+ encoderDiff = epps * (encoderLine - encoderTopLine + 2 * (LCD_HEIGHT)) * ydir;
+ else
+ encoderDiff = epps * (row - encoderPosition + encoderTopLine);
+ }
+ else if (!on_status_screen())
+ encoderDiff = epps * xdir;
+ }
+
+ #endif
+
+#endif // HAS_DISPLAY
+
+#if ENABLED(SDSUPPORT)
+
+ #if ENABLED(EXTENSIBLE_UI)
+ #include "extui/ui_api.h"
+ #endif
+
+ void MarlinUI::media_changed(const uint8_t old_status, const uint8_t status) {
+ if (old_status == status) {
+ TERN_(EXTENSIBLE_UI, ExtUI::onMediaError()); // Failed to mount/unmount
+ return;
+ }
+
+ if (status) {
+ if (old_status < 2) {
+ #if ENABLED(EXTENSIBLE_UI)
+ ExtUI::onMediaInserted();
+ #elif ENABLED(BROWSE_MEDIA_ON_INSERT)
+ clear_menu_history();
+ quick_feedback();
+ goto_screen(MEDIA_MENU_GATEWAY);
+ #else
+ LCD_MESSAGE(MSG_MEDIA_INSERTED);
+ #endif
+ }
+ }
+ else {
+ if (old_status < 2) {
+ #if ENABLED(EXTENSIBLE_UI)
+ ExtUI::onMediaRemoved();
+ #elif HAS_SD_DETECT
+ LCD_MESSAGE(MSG_MEDIA_REMOVED);
+ #if HAS_MARLINUI_MENU
+ if (!defer_return_to_status) return_to_status();
+ #endif
+ #endif
+ }
+ }
+
+ reinit_lcd(); // Revive a noisy shared SPI LCD
+
+ refresh();
+
+ #if HAS_WIRED_LCD || LED_POWEROFF_TIMEOUT > 0
+ const millis_t ms = millis();
+ #endif
+
+ TERN_(HAS_WIRED_LCD, next_lcd_update_ms = ms + LCD_UPDATE_INTERVAL); // Delay LCD update for SD activity
+
+ #if LED_POWEROFF_TIMEOUT > 0
+ leds.reset_timeout(ms);
+ #endif
+ }
+
+#endif // SDSUPPORT
+
+#if HAS_MARLINUI_MENU
+ void MarlinUI::reset_settings() {
+ settings.reset();
+ completion_feedback();
+ #if ENABLED(TOUCH_SCREEN_CALIBRATION)
+ if (touch_calibration.need_calibration()) ui.goto_screen(touch_screen_calibration);
+ #endif
+ }
+
+ #if EITHER(BABYSTEP_ZPROBE_GFX_OVERLAY, MESH_EDIT_GFX_OVERLAY)
+ void MarlinUI::zoffset_overlay(const_float_t zvalue) {
+ // Determine whether the user is raising or lowering the nozzle.
+ static int8_t dir;
+ static float old_zvalue;
+ if (zvalue != old_zvalue) {
+ dir = zvalue ? zvalue < old_zvalue ? -1 : 1 : 0;
+ old_zvalue = zvalue;
+ }
+ zoffset_overlay(dir);
+ }
+ #endif
+
+#endif
+
+#if BOTH(EXTENSIBLE_UI, ADVANCED_PAUSE_FEATURE)
+
+ void MarlinUI::pause_show_message(
+ const PauseMessage message,
+ const PauseMode mode/*=PAUSE_MODE_SAME*/,
+ const uint8_t extruder/*=active_extruder*/
+ ) {
+ pause_mode = mode;
+ ExtUI::pauseModeStatus = message;
+ switch (message) {
+ case PAUSE_MESSAGE_PARKING: ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_PAUSE_PRINT_PARKING)); break;
+ case PAUSE_MESSAGE_CHANGING: ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_FILAMENT_CHANGE_INIT)); break;
+ case PAUSE_MESSAGE_UNLOAD: ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_FILAMENT_CHANGE_UNLOAD)); break;
+ case PAUSE_MESSAGE_WAITING: ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_ADVANCED_PAUSE_WAITING)); break;
+ case PAUSE_MESSAGE_INSERT: ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_FILAMENT_CHANGE_INSERT)); break;
+ case PAUSE_MESSAGE_LOAD: ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_FILAMENT_CHANGE_LOAD)); break;
+ case PAUSE_MESSAGE_PURGE:
+ ExtUI::onUserConfirmRequired(GET_TEXT_F(TERN(ADVANCED_PAUSE_CONTINUOUS_PURGE, MSG_FILAMENT_CHANGE_CONT_PURGE, MSG_FILAMENT_CHANGE_PURGE)));
+ break;
+ case PAUSE_MESSAGE_RESUME: ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_FILAMENT_CHANGE_RESUME)); break;
+ case PAUSE_MESSAGE_HEAT: ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_FILAMENT_CHANGE_HEAT)); break;
+ case PAUSE_MESSAGE_HEATING: ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_FILAMENT_CHANGE_HEATING)); break;
+ case PAUSE_MESSAGE_OPTION: ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_FILAMENT_CHANGE_OPTION_HEADER)); break;
+ case PAUSE_MESSAGE_STATUS: break;
+ default: break;
+ }
+ }
+
+#endif
+
+#if ENABLED(EEPROM_SETTINGS)
+
+ #if HAS_MARLINUI_MENU
+ void MarlinUI::init_eeprom() {
+ const bool good = settings.init_eeprom();
+ completion_feedback(good);
+ return_to_status();
+ }
+ void MarlinUI::load_settings() {
+ const bool good = settings.load();
+ completion_feedback(good);
+ }
+ void MarlinUI::store_settings() {
+ const bool good = settings.save();
+ completion_feedback(good);
+ }
+ #endif
+
+ #if DISABLED(EEPROM_AUTO_INIT)
+
+ static inline FSTR_P eeprom_err(const uint8_t msgid) {
+ switch (msgid) {
+ default:
+ case 0: return GET_TEXT_F(MSG_ERR_EEPROM_CRC);
+ case 1: return GET_TEXT_F(MSG_ERR_EEPROM_INDEX);
+ case 2: return GET_TEXT_F(MSG_ERR_EEPROM_VERSION);
+ }
+ }
+
+ void MarlinUI::eeprom_alert(const uint8_t msgid) {
+ #if HAS_MARLINUI_MENU
+ editable.uint8 = msgid;
+ goto_screen([]{
+ FSTR_P const restore_msg = GET_TEXT_F(MSG_INIT_EEPROM);
+ char msg[utf8_strlen(restore_msg) + 1];
+ strcpy_P(msg, FTOP(restore_msg));
+ MenuItem_confirm::select_screen(
+ GET_TEXT_F(MSG_BUTTON_RESET), GET_TEXT_F(MSG_BUTTON_IGNORE),
+ init_eeprom, return_to_status,
+ eeprom_err(editable.uint8), msg, F("?")
+ );
+ });
+ #else
+ set_status(eeprom_err(msgid));
+ #endif
+ }
+
+ #endif // EEPROM_AUTO_INIT
+
+#endif // EEPROM_SETTINGS
diff --git a/src/lcd/marlinui.h b/src/lcd/marlinui.h
new file mode 100644
index 0000000..6bd9ec8
--- /dev/null
+++ b/src/lcd/marlinui.h
@@ -0,0 +1,791 @@
+/**
+ * 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 .
+ *
+ */
+#pragma once
+
+#include "../inc/MarlinConfig.h"
+#include "../sd/cardreader.h"
+#include "../module/motion.h"
+#include "../libs/buzzer.h"
+
+#include "buttons.h"
+
+#if ENABLED(TOUCH_SCREEN_CALIBRATION)
+ #include "tft_io/touch_calibration.h"
+#endif
+
+#if E_MANUAL > 1
+ #define MULTI_E_MANUAL 1
+#endif
+
+#if HAS_DISPLAY
+ #include "../module/printcounter.h"
+#endif
+
+#if ENABLED(ADVANCED_PAUSE_FEATURE)
+ #include "../feature/pause.h"
+#endif
+
+#if ENABLED(DWIN_CREALITY_LCD)
+ #include "e3v2/creality/dwin.h"
+#elif ENABLED(DWIN_LCD_PROUI)
+ #include "e3v2/proui/dwin.h"
+#endif
+
+#define START_OF_UTF8_CHAR(C) (((C) & 0xC0u) != 0x80U)
+
+typedef bool (*statusResetFunc_t)();
+
+#if HAS_WIRED_LCD
+
+ enum LCDViewAction : uint8_t {
+ LCDVIEW_NONE,
+ LCDVIEW_REDRAW_NOW,
+ LCDVIEW_CALL_REDRAW_NEXT,
+ LCDVIEW_CLEAR_CALL_REDRAW,
+ LCDVIEW_CALL_NO_REDRAW
+ };
+
+ #if HAS_ADC_BUTTONS
+ uint8_t get_ADC_keyValue();
+ #endif
+
+ #if HAS_MARLINUI_MENU
+
+ #include "lcdprint.h"
+
+ #if !HAS_GRAPHICAL_TFT
+ void _wrap_string(uint8_t &col, uint8_t &row, const char * const string, read_byte_cb_t cb_read_byte, const bool wordwrap=false);
+ inline void wrap_string_P(uint8_t &col, uint8_t &row, PGM_P const pstr, const bool wordwrap=false) { _wrap_string(col, row, pstr, read_byte_rom, wordwrap); }
+ inline void wrap_string(uint8_t &col, uint8_t &row, const char * const string, const bool wordwrap=false) { _wrap_string(col, row, string, read_byte_ram, wordwrap); }
+ #endif
+
+ typedef void (*screenFunc_t)();
+ typedef void (*menuAction_t)();
+
+ #endif // HAS_MARLINUI_MENU
+
+#endif // HAS_WIRED_LCD
+
+#if EITHER(HAS_WIRED_LCD, DWIN_CREALITY_LCD_JYERSUI)
+ #define LCD_UPDATE_INTERVAL TERN(HAS_TOUCH_BUTTONS, 50, 100)
+#endif
+
+#if HAS_MARLINUI_U8GLIB
+ enum MarlinFont : uint8_t {
+ FONT_STATUSMENU = 1,
+ FONT_EDIT,
+ FONT_MENU
+ };
+#else
+ enum HD44780CharSet : uint8_t {
+ CHARSET_MENU,
+ CHARSET_INFO,
+ CHARSET_BOOT
+ };
+#endif
+
+#if HAS_PREHEAT
+ typedef struct {
+ #if HAS_HOTEND
+ celsius_t hotend_temp;
+ #endif
+ #if HAS_HEATED_BED
+ celsius_t bed_temp;
+ #endif
+ #if HAS_FAN
+ uint16_t fan_speed;
+ #endif
+ } preheat_t;
+#endif
+
+#if HAS_MARLINUI_MENU
+
+ // Manual Movement class
+ class ManualMove {
+ private:
+ static AxisEnum axis;
+ #if MULTI_E_MANUAL
+ static int8_t e_index;
+ #else
+ static int8_t constexpr e_index = 0;
+ #endif
+ static millis_t start_time;
+ #if IS_KINEMATIC
+ static xyze_pos_t all_axes_destination;
+ #endif
+ public:
+ static screenFunc_t screen_ptr;
+ static float menu_scale;
+ #if IS_KINEMATIC
+ static float offset;
+ #endif
+ #if ENABLED(MANUAL_E_MOVES_RELATIVE)
+ static float e_origin;
+ #endif
+ template
+ static void set_destination(const T& dest) {
+ #if IS_KINEMATIC
+ // Moves are segmented, so the entire move is not submitted at once.
+ // Using a separate variable prevents corrupting the in-progress move.
+ all_axes_destination = current_position;
+ all_axes_destination.set(dest);
+ #else
+ // Moves are submitted as single line to the planner using buffer_line.
+ current_position.set(dest);
+ #endif
+ }
+ static float axis_value(const AxisEnum axis) {
+ return NATIVE_TO_LOGICAL(processing ? destination[axis] : SUM_TERN(IS_KINEMATIC, current_position[axis], offset), axis);
+ }
+ static bool apply_diff(const AxisEnum axis, const_float_t diff, const_float_t min, const_float_t max) {
+ #if IS_KINEMATIC
+ float &valref = offset;
+ const float rmin = min - current_position[axis], rmax = max - current_position[axis];
+ #else
+ float &valref = current_position[axis];
+ const float rmin = min, rmax = max;
+ #endif
+ valref += diff;
+ const float pre = valref;
+ if (min != max) { if (diff < 0) NOLESS(valref, rmin); else NOMORE(valref, rmax); }
+ return pre != valref;
+ }
+ #if IS_KINEMATIC
+ static bool processing;
+ #else
+ static bool constexpr processing = false;
+ #endif
+ static void task();
+ static void soon(const AxisEnum axis OPTARG(MULTI_E_MANUAL, const int8_t eindex=active_extruder));
+ };
+
+ void lcd_move_axis(const AxisEnum);
+
+#endif
+
+////////////////////////////////////////////
+//////////// MarlinUI Singleton ////////////
+////////////////////////////////////////////
+
+class MarlinUI;
+extern MarlinUI ui;
+
+class MarlinUI {
+public:
+
+ MarlinUI() {
+ TERN_(HAS_MARLINUI_MENU, currentScreen = status_screen);
+ }
+
+ static void init();
+
+ #if HAS_DISPLAY || HAS_DWIN_E3V2
+ static void init_lcd();
+ #else
+ static void init_lcd() {}
+ #endif
+
+ static void reinit_lcd() { TERN_(REINIT_NOISY_LCD, init_lcd()); }
+
+ #if HAS_WIRED_LCD
+ static bool detected();
+ #else
+ static bool detected() { return true; }
+ #endif
+
+ #if HAS_MULTI_LANGUAGE
+ static uint8_t language;
+ static void set_language(const uint8_t lang);
+ #endif
+
+ #if HAS_MARLINUI_U8GLIB
+ static void update_language_font();
+ #endif
+
+ #if ENABLED(SOUND_MENU_ITEM)
+ static bool sound_on; // Initialized by settings.load()
+ #else
+ static constexpr bool sound_on = true;
+ #endif
+
+ #if USE_MARLINUI_BUZZER
+ static void buzz(const long duration, const uint16_t freq);
+ #endif
+
+ static void chirp() {
+ TERN_(HAS_CHIRP, TERN(USE_MARLINUI_BUZZER, buzz, BUZZ)(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ));
+ }
+
+ #if ENABLED(LCD_HAS_STATUS_INDICATORS)
+ static void update_indicators();
+ #endif
+
+ // LCD implementations
+ static void clear_lcd();
+
+ #if BOTH(HAS_MARLINUI_MENU, TOUCH_SCREEN_CALIBRATION)
+ static void check_touch_calibration() {
+ if (touch_calibration.need_calibration()) currentScreen = touch_calibration_screen;
+ }
+ #endif
+
+ #if ENABLED(SDSUPPORT)
+ #define MEDIA_MENU_GATEWAY TERN(PASSWORD_ON_SD_PRINT_MENU, password.media_gatekeeper, menu_media)
+ static void media_changed(const uint8_t old_stat, const uint8_t stat);
+ #endif
+
+ #if HAS_LCD_BRIGHTNESS
+ #ifndef LCD_BRIGHTNESS_MIN
+ #define LCD_BRIGHTNESS_MIN 1
+ #endif
+ #ifndef LCD_BRIGHTNESS_MAX
+ #define LCD_BRIGHTNESS_MAX 255
+ #endif
+ #ifndef LCD_BRIGHTNESS_DEFAULT
+ #define LCD_BRIGHTNESS_DEFAULT LCD_BRIGHTNESS_MAX
+ #endif
+ static uint8_t brightness;
+ static bool backlight;
+ static void _set_brightness(); // Implementation-specific
+ static void set_brightness(const uint8_t value);
+ FORCE_INLINE static void refresh_brightness() { set_brightness(brightness); }
+ #endif
+
+ #if LCD_BACKLIGHT_TIMEOUT
+ #define LCD_BKL_TIMEOUT_MIN 1u
+ #define LCD_BKL_TIMEOUT_MAX UINT16_MAX // Slightly more than 18 hours
+ static uint16_t lcd_backlight_timeout;
+ static millis_t backlight_off_ms;
+ static void refresh_backlight_timeout();
+ #elif HAS_DISPLAY_SLEEP
+ #define SLEEP_TIMEOUT_MIN 0
+ #define SLEEP_TIMEOUT_MAX 99
+ static uint8_t sleep_timeout_minutes;
+ static millis_t screen_timeout_millis;
+ static void refresh_screen_timeout();
+ static void sleep_on();
+ static void sleep_off();
+ #endif
+
+ #if HAS_DWIN_E3V2_BASIC
+ static void refresh();
+ #else
+ FORCE_INLINE static void refresh() {
+ TERN_(HAS_WIRED_LCD, refresh(LCDVIEW_CLEAR_CALL_REDRAW));
+ }
+ #endif
+
+ #if HAS_PRINT_PROGRESS
+ #if HAS_PRINT_PROGRESS_PERMYRIAD
+ typedef uint16_t progress_t;
+ #define PROGRESS_SCALE 100U
+ #define PROGRESS_MASK 0x7FFF
+ #else
+ typedef uint8_t progress_t;
+ #define PROGRESS_SCALE 1U
+ #define PROGRESS_MASK 0x7F
+ #endif
+ #if ENABLED(LCD_SET_PROGRESS_MANUALLY)
+ static progress_t progress_override;
+ static void set_progress(const progress_t p) { progress_override = _MIN(p, 100U * (PROGRESS_SCALE)); }
+ static void set_progress_done() { progress_override = (PROGRESS_MASK + 1U) + 100U * (PROGRESS_SCALE); }
+ static void progress_reset() { if (progress_override & (PROGRESS_MASK + 1U)) set_progress(0); }
+ #endif
+ #if ENABLED(SHOW_REMAINING_TIME)
+ static uint32_t _calculated_remaining_time() {
+ const duration_t elapsed = print_job_timer.duration();
+ const progress_t progress = _get_progress();
+ return progress ? elapsed.value * (100 * (PROGRESS_SCALE) - progress) / progress : 0;
+ }
+ #if ENABLED(USE_M73_REMAINING_TIME)
+ static uint32_t remaining_time;
+ FORCE_INLINE static void set_remaining_time(const uint32_t r) { remaining_time = r; }
+ FORCE_INLINE static uint32_t get_remaining_time() { return remaining_time ?: _calculated_remaining_time(); }
+ FORCE_INLINE static void reset_remaining_time() { set_remaining_time(0); }
+ #else
+ FORCE_INLINE static uint32_t get_remaining_time() { return _calculated_remaining_time(); }
+ #endif
+ #endif
+ static progress_t _get_progress();
+ #if HAS_PRINT_PROGRESS_PERMYRIAD
+ FORCE_INLINE static uint16_t get_progress_permyriad() { return _get_progress(); }
+ #endif
+ static uint8_t get_progress_percent() { return uint8_t(_get_progress() / (PROGRESS_SCALE)); }
+ #else
+ static constexpr uint8_t get_progress_percent() { return 0; }
+ #endif
+
+ #if HAS_STATUS_MESSAGE
+
+ #if EITHER(HAS_WIRED_LCD, DWIN_LCD_PROUI)
+ #if ENABLED(STATUS_MESSAGE_SCROLLING)
+ #define MAX_MESSAGE_LENGTH _MAX(LONG_FILENAME_LENGTH, MAX_LANG_CHARSIZE * 2 * (LCD_WIDTH))
+ #else
+ #define MAX_MESSAGE_LENGTH (MAX_LANG_CHARSIZE * (LCD_WIDTH))
+ #endif
+ #else
+ #define MAX_MESSAGE_LENGTH 63
+ #endif
+
+ static char status_message[];
+ static uint8_t alert_level; // Higher levels block lower levels
+
+ #if HAS_STATUS_MESSAGE_TIMEOUT
+ static millis_t status_message_expire_ms; // Reset some status messages after a timeout
+ #endif
+
+ #if ENABLED(STATUS_MESSAGE_SCROLLING)
+ static uint8_t status_scroll_offset;
+ static void advance_status_scroll();
+ static char* status_and_len(uint8_t &len);
+ #endif
+
+ static bool has_status();
+ static void reset_status(const bool no_welcome=false);
+ static void set_alert_status(FSTR_P const fstr);
+ static void reset_alert_level() { alert_level = 0; }
+
+ static statusResetFunc_t status_reset_callback;
+ static void set_status_reset_fn(const statusResetFunc_t fn=nullptr) { status_reset_callback = fn; }
+ #else
+ static constexpr bool has_status() { return false; }
+ static void reset_status(const bool=false) {}
+ static void set_alert_status(FSTR_P const) {}
+ static void reset_alert_level() {}
+ static void set_status_reset_fn(const statusResetFunc_t=nullptr) {}
+ #endif
+
+ static void set_status(const char * const cstr, const bool persist=false);
+ static void set_status(FSTR_P const fstr, const int8_t level=0);
+ static void status_printf(int8_t level, FSTR_P const fmt, ...);
+
+ #if HAS_DISPLAY
+
+ static void update();
+
+ static void abort_print();
+ static void pause_print();
+ static void resume_print();
+ static void flow_fault();
+
+ #if BOTH(HAS_MARLINUI_MENU, PSU_CONTROL)
+ static void poweroff();
+ #endif
+
+ #if EITHER(HAS_WIRED_LCD, DWIN_CREALITY_LCD_JYERSUI)
+ static bool get_blink();
+ #endif
+
+ #if HAS_WIRED_LCD
+
+ static millis_t next_button_update_ms;
+
+ static LCDViewAction lcdDrawUpdate;
+ FORCE_INLINE static bool should_draw() { return bool(lcdDrawUpdate); }
+ FORCE_INLINE static void refresh(const LCDViewAction type) { lcdDrawUpdate = type; }
+
+ #if ENABLED(SHOW_CUSTOM_BOOTSCREEN)
+ static void draw_custom_bootscreen(const uint8_t frame=0);
+ static void show_custom_bootscreen();
+ #endif
+
+ #if ENABLED(SHOW_BOOTSCREEN)
+ #ifndef BOOTSCREEN_TIMEOUT
+ #define BOOTSCREEN_TIMEOUT 2500
+ #endif
+ static void draw_marlin_bootscreen(const bool line2=false);
+ static void show_marlin_bootscreen();
+ static void show_bootscreen();
+ static void bootscreen_completion(const millis_t sofar);
+ #endif
+
+ #if HAS_MARLINUI_U8GLIB
+ static void set_font(const MarlinFont font_nr);
+ #elif IS_DWIN_MARLINUI
+ static void set_font(const uint8_t font_nr);
+ #endif
+
+ #if HAS_MARLINUI_HD44780
+ static void set_custom_characters(const HD44780CharSet screen_charset=CHARSET_INFO);
+ #endif
+
+ #if ENABLED(LCD_PROGRESS_BAR) && !HAS_MARLINUI_U8GLIB
+ static millis_t progress_bar_ms; // Start time for the current progress bar cycle
+ static void draw_progress_bar(const uint8_t percent);
+ #if PROGRESS_MSG_EXPIRE > 0
+ static millis_t expire_status_ms; // = 0
+ FORCE_INLINE static void reset_progress_bar_timeout() { expire_status_ms = 0; }
+ #endif
+ #endif
+
+ static uint8_t lcd_status_update_delay;
+
+ #if HAS_LCD_CONTRAST
+ static uint8_t contrast;
+ static void _set_contrast(); // Implementation-specific
+ static void set_contrast(const uint8_t value);
+ FORCE_INLINE static void refresh_contrast() { set_contrast(contrast); }
+ #endif
+
+ #if BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT)
+ static millis_t next_filament_display;
+ #endif
+
+ #if HAS_TOUCH_SLEEP
+ static void wakeup_screen();
+ #endif
+
+ static void quick_feedback(const bool clear_buttons=true);
+ #if HAS_SOUND
+ static void completion_feedback(const bool good=true);
+ #else
+ static void completion_feedback(const bool=true) { TERN_(HAS_TOUCH_SLEEP, wakeup_screen()); }
+ #endif
+
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ static void draw_hotend_status(const uint8_t row, const uint8_t extruder);
+ #endif
+
+ #if HAS_TOUCH_BUTTONS
+ static bool on_edit_screen;
+ static void screen_click(const uint8_t row, const uint8_t col, const uint8_t x, const uint8_t y);
+ #endif
+
+ static void status_screen();
+
+ #endif
+
+ #if HAS_MARLINUI_U8GLIB
+ static bool drawing_screen, first_page;
+ #else
+ static constexpr bool drawing_screen = false, first_page = true;
+ #endif
+
+ #if IS_DWIN_MARLINUI
+ static bool did_first_redraw;
+ static bool old_is_printing;
+ #endif
+
+ #if EITHER(BABYSTEP_ZPROBE_GFX_OVERLAY, MESH_EDIT_GFX_OVERLAY)
+ static void zoffset_overlay(const int8_t dir);
+ static void zoffset_overlay(const_float_t zvalue);
+ #endif
+
+ static void draw_kill_screen();
+ static void kill_screen(FSTR_P const lcd_error, FSTR_P const lcd_component);
+ #if DISABLED(LIGHTWEIGHT_UI)
+ static void draw_status_message(const bool blink);
+ #endif
+
+ #else // No LCD
+
+ static void update() {}
+ static void kill_screen(FSTR_P const, FSTR_P const) {}
+
+ #endif
+
+ #if ENABLED(SDSUPPORT)
+ #if BOTH(SCROLL_LONG_FILENAMES, HAS_MARLINUI_MENU)
+ #define MARLINUI_SCROLL_NAME 1
+ #endif
+ #if MARLINUI_SCROLL_NAME
+ static uint8_t filename_scroll_pos, filename_scroll_max;
+ #endif
+ static const char * scrolled_filename(CardReader &theCard, const uint8_t maxlen, uint8_t hash, const bool doScroll);
+ #endif
+
+ #if HAS_PREHEAT
+ enum PreheatTarget : uint8_t { PT_HOTEND, PT_BED, PT_FAN, PT_CHAMBER, PT_ALL = 0xFF };
+ static preheat_t material_preset[PREHEAT_COUNT];
+ static FSTR_P get_preheat_label(const uint8_t m);
+ static void apply_preheat(const uint8_t m, const uint8_t pmask, const uint8_t e=active_extruder);
+ static void preheat_set_fan(const uint8_t m) { TERN_(HAS_FAN, apply_preheat(m, _BV(PT_FAN))); }
+ static void preheat_hotend(const uint8_t m, const uint8_t e=active_extruder) { TERN_(HAS_HOTEND, apply_preheat(m, _BV(PT_HOTEND))); }
+ static void preheat_hotend_and_fan(const uint8_t m, const uint8_t e=active_extruder) { preheat_hotend(m, e); preheat_set_fan(m); }
+ static void preheat_bed(const uint8_t m) { TERN_(HAS_HEATED_BED, apply_preheat(m, _BV(PT_BED))); }
+ static void preheat_all(const uint8_t m) { apply_preheat(m, PT_ALL); }
+ #endif
+
+ static void reset_status_timeout(const millis_t ms) {
+ TERN(SCREENS_CAN_TIME_OUT, return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS, UNUSED(ms));
+ }
+
+ #if HAS_MARLINUI_MENU
+
+ #if HAS_TOUCH_BUTTONS
+ static uint8_t touch_buttons;
+ static uint8_t repeat_delay;
+ #else
+ static constexpr uint8_t touch_buttons = 0;
+ #endif
+
+ #if ENABLED(ENCODER_RATE_MULTIPLIER)
+ static bool encoderRateMultiplierEnabled;
+ static millis_t lastEncoderMovementMillis;
+ static void enable_encoder_multiplier(const bool onoff);
+ #define ENCODER_RATE_MULTIPLY(F) (ui.encoderRateMultiplierEnabled = F)
+ #else
+ #define ENCODER_RATE_MULTIPLY(F) NOOP
+ #endif
+
+ // Manual Movement
+ static ManualMove manual_move;
+ static bool can_show_slider() { return !external_control && currentScreen != manual_move.screen_ptr; }
+
+ // Select Screen (modal NO/YES style dialog)
+ static bool selection;
+ static void set_selection(const bool sel) { selection = sel; }
+ static bool update_selection();
+
+ static void synchronize(FSTR_P const msg=nullptr);
+
+ static screenFunc_t currentScreen;
+ static bool screen_changed;
+ static void goto_screen(const screenFunc_t screen, const uint16_t encoder=0, const uint8_t top=0, const uint8_t items=0);
+ static void push_current_screen();
+
+ // goto_previous_screen and go_back may also be used as menu item callbacks
+ static void _goto_previous_screen(TERN_(TURBO_BACK_MENU_ITEM, const bool is_back));
+ static void goto_previous_screen() { _goto_previous_screen(TERN_(TURBO_BACK_MENU_ITEM, false)); }
+ static void go_back() { _goto_previous_screen(TERN_(TURBO_BACK_MENU_ITEM, true)); }
+
+ static void return_to_status();
+ static bool on_status_screen() { return currentScreen == status_screen; }
+ FORCE_INLINE static void run_current_screen() { (*currentScreen)(); }
+
+ #if ENABLED(LIGHTWEIGHT_UI)
+ static void lcd_in_status(const bool inStatus);
+ #endif
+
+ FORCE_INLINE static bool screen_is_sticky() {
+ return TERN1(SCREENS_CAN_TIME_OUT, defer_return_to_status);
+ }
+
+ FORCE_INLINE static void defer_status_screen(const bool defer=true) {
+ TERN(SCREENS_CAN_TIME_OUT, defer_return_to_status = defer, UNUSED(defer));
+ }
+
+ static void goto_previous_screen_no_defer() {
+ defer_status_screen(false);
+ goto_previous_screen();
+ }
+
+ #if ENABLED(SD_REPRINT_LAST_SELECTED_FILE)
+ static void reselect_last_file();
+ #endif
+
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+ static void ubl_plot(const uint8_t x_plot, const uint8_t y_plot);
+ #endif
+
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+ static void ubl_mesh_edit_start(const_float_t initial);
+ static float ubl_mesh_value();
+ #endif
+
+ static void draw_select_screen_prompt(FSTR_P const pref, const char * const string=nullptr, FSTR_P const suff=nullptr);
+
+ #else
+
+ static void return_to_status() {}
+
+ static constexpr bool on_status_screen() { return true; }
+
+ #if HAS_WIRED_LCD
+ FORCE_INLINE static void run_current_screen() { status_screen(); }
+ #endif
+
+ #endif
+
+ #if EITHER(HAS_MARLINUI_MENU, EXTENSIBLE_UI)
+ static bool lcd_clicked;
+ static bool use_click() {
+ const bool click = lcd_clicked;
+ lcd_clicked = false;
+ return click;
+ }
+ #else
+ static constexpr bool lcd_clicked = false;
+ static bool use_click() { return false; }
+ #endif
+
+ #if ENABLED(ADVANCED_PAUSE_FEATURE) && ANY(HAS_MARLINUI_MENU, EXTENSIBLE_UI, DWIN_LCD_PROUI, DWIN_CREALITY_LCD_JYERSUI)
+ static void pause_show_message(const PauseMessage message, const PauseMode mode=PAUSE_MODE_SAME, const uint8_t extruder=active_extruder);
+ #else
+ static void _pause_show_message() {}
+ #define pause_show_message(...) _pause_show_message()
+ #endif
+
+ //
+ // EEPROM: Reset / Init / Load / Store
+ //
+ #if HAS_MARLINUI_MENU
+ static void reset_settings();
+ #endif
+
+ #if ENABLED(EEPROM_SETTINGS)
+ #if HAS_MARLINUI_MENU
+ static void init_eeprom();
+ static void load_settings();
+ static void store_settings();
+ #endif
+ #if DISABLED(EEPROM_AUTO_INIT)
+ static void eeprom_alert(const uint8_t msgid);
+ static void eeprom_alert_crc() { eeprom_alert(0); }
+ static void eeprom_alert_index() { eeprom_alert(1); }
+ static void eeprom_alert_version() { eeprom_alert(2); }
+ #endif
+ #endif
+
+ //
+ // Special handling if a move is underway
+ //
+ #if ANY(DELTA_CALIBRATION_MENU, DELTA_AUTO_CALIBRATION, PROBE_OFFSET_WIZARD, X_AXIS_TWIST_COMPENSATION) || (ENABLED(LCD_BED_LEVELING) && EITHER(PROBE_MANUALLY, MESH_BED_LEVELING))
+ #define LCD_HAS_WAIT_FOR_MOVE 1
+ static bool wait_for_move;
+ #else
+ static constexpr bool wait_for_move = false;
+ #endif
+
+ //
+ // Block interaction while under external control
+ //
+ #if HAS_MARLINUI_MENU && EITHER(AUTO_BED_LEVELING_UBL, G26_MESH_VALIDATION)
+ static bool external_control;
+ FORCE_INLINE static void capture() { external_control = true; }
+ FORCE_INLINE static void release() { external_control = false; }
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+ static void external_encoder();
+ #endif
+ #else
+ static constexpr bool external_control = false;
+ #endif
+
+ #if HAS_ENCODER_ACTION
+
+ static volatile uint8_t buttons;
+ #if IS_RRW_KEYPAD
+ static volatile uint8_t keypad_buttons;
+ static bool handle_keypad();
+ #endif
+ #if HAS_SLOW_BUTTONS
+ static volatile uint8_t slow_buttons;
+ static uint8_t read_slow_buttons();
+ #endif
+
+ static void update_buttons();
+
+ #if HAS_ENCODER_NOISE
+ #ifndef ENCODER_SAMPLES
+ #define ENCODER_SAMPLES 10
+ #endif
+
+ /**
+ * Some printers may have issues with EMI noise especially using a motherboard with 3.3V logic levels
+ * it may cause the logical LOW to float into the undefined region and register as a logical HIGH
+ * causing it to erroneously register as if someone clicked the button and in worst case make the
+ * printer unusable in practice.
+ */
+ static bool hw_button_pressed() {
+ LOOP_L_N(s, ENCODER_SAMPLES) {
+ if (!BUTTON_CLICK()) return false;
+ safe_delay(1);
+ }
+ return true;
+ }
+ #else
+ static bool hw_button_pressed() { return BUTTON_CLICK(); }
+ #endif
+
+ #if EITHER(AUTO_BED_LEVELING_UBL, G26_MESH_VALIDATION)
+ static void wait_for_release();
+ #endif
+
+ static uint32_t encoderPosition;
+
+ #define ENCODERBASE (TERN(REVERSE_ENCODER_DIRECTION, -1, +1))
+
+ #if EITHER(REVERSE_MENU_DIRECTION, REVERSE_SELECT_DIRECTION)
+ static int8_t encoderDirection;
+ #else
+ static constexpr int8_t encoderDirection = ENCODERBASE;
+ #endif
+
+ FORCE_INLINE static void encoder_direction_normal() {
+ #if EITHER(REVERSE_MENU_DIRECTION, REVERSE_SELECT_DIRECTION)
+ encoderDirection = ENCODERBASE;
+ #endif
+ }
+
+ FORCE_INLINE static void encoder_direction_menus() {
+ TERN_(REVERSE_MENU_DIRECTION, encoderDirection = -(ENCODERBASE));
+ }
+
+ FORCE_INLINE static void encoder_direction_select() {
+ TERN_(REVERSE_SELECT_DIRECTION, encoderDirection = -(ENCODERBASE));
+ }
+
+ #else
+
+ static void update_buttons() {}
+ static bool hw_button_pressed() { return false; }
+
+ #endif
+
+ static bool button_pressed() { return hw_button_pressed() || TERN0(TOUCH_SCREEN, touch_pressed()); }
+
+ #if ENABLED(TOUCH_SCREEN_CALIBRATION)
+ static void touch_calibration_screen();
+ #endif
+
+ #if HAS_GRAPHICAL_TFT
+ static void move_axis_screen();
+ #endif
+
+private:
+
+ #if SCREENS_CAN_TIME_OUT
+ static millis_t return_to_status_ms;
+ static bool defer_return_to_status;
+ #else
+ static constexpr bool defer_return_to_status = false;
+ #endif
+
+ #if HAS_STATUS_MESSAGE
+ static void finish_status(const bool persist);
+ #endif
+
+ #if HAS_WIRED_LCD
+ static void draw_status_screen();
+ #if HAS_GRAPHICAL_TFT
+ static void tft_idle();
+ #if ENABLED(TOUCH_SCREEN)
+ static bool touch_pressed();
+ #endif
+ #endif
+ #endif
+};
+
+#define LCD_MESSAGE_F(S) ui.set_status(F(S))
+#define LCD_MESSAGE(M) ui.set_status(GET_TEXT_F(M))
+#define LCD_ALERTMESSAGE_F(S) ui.set_alert_status(F(S))
+#define LCD_ALERTMESSAGE(M) ui.set_alert_status(GET_TEXT_F(M))
diff --git a/src/lcd/scaled_tft.h b/src/lcd/scaled_tft.h
new file mode 100644
index 0000000..54bf6f8d
--- /dev/null
+++ b/src/lcd/scaled_tft.h
@@ -0,0 +1,55 @@
+/**
+ * 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 .
+ *
+ */
+#pragma once
+
+#include "../inc/MarlinConfig.h"
+
+#ifndef GRAPHICAL_TFT_UPSCALE
+ #define GRAPHICAL_TFT_UPSCALE 2
+#endif
+
+#ifndef TFT_WIDTH
+ #if GRAPHICAL_TFT_UPSCALE == 3
+ #define TFT_WIDTH 480
+ #else
+ #define TFT_WIDTH 320
+ #endif
+#endif
+#ifndef TFT_HEIGHT
+ #if GRAPHICAL_TFT_UPSCALE == 3
+ #define TFT_HEIGHT 320
+ #else
+ #define TFT_HEIGHT 240
+ #endif
+#endif
+
+#ifndef TFT_PIXEL_OFFSET_X
+ #if GRAPHICAL_TFT_UPSCALE == 2
+ #define TFT_PIXEL_OFFSET_X 32
+ #else
+ #define TFT_PIXEL_OFFSET_X 48
+ #endif
+#endif
+
+#ifndef TFT_PIXEL_OFFSET_Y
+ #define TFT_PIXEL_OFFSET_Y 32 // 32 is best for both 320x240 and 480x320
+#endif
diff --git a/src/lcd/tft_io/ili9328.h b/src/lcd/tft_io/ili9328.h
new file mode 100644
index 0000000..b50517a
--- /dev/null
+++ b/src/lcd/tft_io/ili9328.h
@@ -0,0 +1,173 @@
+/**
+ * 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 .
+ *
+ */
+#pragma once
+
+#include "tft_io.h"
+
+#include "../../inc/MarlinConfig.h"
+
+#define ILI9328_DRVCTL_SM 0x0400
+#define ILI9328_DRVCTL_SS 0x0100 // Select the shift direction of outputs from the source driver. 0 - from S1 to S720 / 1 - from S720 to S1
+
+#define ILI9328_GATE_SCANCTL_GS 0x8000 // Sets the direction of scan by the gate driver in the range determined by SCN[4:0] and NL[4:0].
+
+#define ILI9328_ETMOD_TRI 0x8000
+#define ILI9328_ETMOD_DFM 0x4000
+#define ILI9328_ETMOD_BGR 0x1000 // RGB-BGR ORDER
+#define ILI9328_ETMOD_RGB 0x0000
+#define ILI9328_ETMOD_ORG 0x0080
+#define ILI9328_ETMOD_ID1 0x0020 // 0 - Vertical Decrement / 1 - Vertical Increment
+#define ILI9328_ETMOD_ID0 0x0010 // 0 - Horizontal Decrement / 1 - Horizontal Increment
+#define ILI9328_ETMOD_AM 0x0008 // 0 - Horizontal / 1 - Vertical
+
+// MKS Robin TFT v1.1 - 320x240 ; Cable on the left side
+
+#if TFT_ROTATION == TFT_ROTATE_180
+ #define ILI9328_DRVCTL_DATA 0x0000
+ #define ILI9328_GATE_SCANCTL1_DATA 0xA700
+#else
+ #define ILI9328_DRVCTL_DATA ILI9328_DRVCTL_SS
+ #define ILI9328_GATE_SCANCTL1_DATA 0x2700
+#endif
+
+/*
+#define ILI9328_ETMOD_ORIENTATION IF_0((TFT_ORIENTATION) & TFT_EXCHANGE_XY, ILI9328_ETMOD_AM) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_X, ILI9328_ETMOD_ID1) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_Y, ILI9328_ETMOD_ID0)
+*/
+
+#define ILI9328_ETMOD_ORIENTATION (ILI9328_ETMOD_AM | ILI9328_ETMOD_ID1 | ILI9328_ETMOD_ID0)
+
+#if !defined(TFT_COLOR) || TFT_COLOR == TFT_COLOR_BGR
+ #define ILI9328_ETMOD_COLOR ILI9328_ETMOD_BGR
+#elif TFT_COLOR == TFT_COLOR_RGB
+ #define ILI9328_ETMOD_COLOR ILI9328_ETMOD_RGB
+#endif
+
+#define ILI9328_ETMOD_DATA (ILI9328_ETMOD_ORIENTATION) | (ILI9328_ETMOD_COLOR)
+
+
+#define ILI9328_RDDID 0x00 // ID code - 0x9328
+#define ILI9328_DRVCTL 0x01 // Driver Output Control
+#define ILI9328_LCDCTL 0x02 // LCD Driving Wave Control
+#define ILI9328_ETMOD 0x03 // Entry Mode - Control the GRAM update direction
+#define ILI9328_RESIZECTL 0x04 // Resizing Control Register
+#define ILI9328_DISCTRL1 0x07 // Display Control 1
+#define ILI9328_DISCTRL2 0x08 // Display Control 2
+#define ILI9328_DISCTRL3 0x09 // Display Control 3
+#define ILI9328_DISCTRL4 0x0A // Display Control 4
+#define ILI9328_RGBCTRL1 0x0C // RGB Display Interface Control 1
+#define ILI9328_FMARKERPOS 0x0D // Frame Marker Position
+#define ILI9328_RGBCTRL2 0x0F // RGB Display Interface Control 2
+#define ILI9328_PWCTRL1 0x10 // Power Control 1
+#define ILI9328_PWCTRL2 0x11 // Power Control 2
+#define ILI9328_PWCTRL3 0x12 // Power Control 3
+#define ILI9328_PWCTRL4 0x13 // Power Control 4
+
+// With landscape screen orientation 'Horizontal' is Y and 'Vertical' is X
+#define ILI9328_HASET 0x20 // GRAM Horizontal Address Set (0-255)
+#define ILI9328_VASET 0x21 // GRAM Vertical Address Set (0-511)
+#define ILI9328_RAMWR 0x22 // Write data to GRAM
+#define ILI9328_RAMRD 0x22 // Read Data from GRAM
+
+#define ILI9328_PWCTRL7 0x29 // Power Control 7
+#define ILI9328_FRMCTR 0x2B // Frame Rate and Color Control
+#define ILI9328_GAMCTRL1 0x30 // Gamma Control
+#define ILI9328_GAMCTRL2 0x31 // Gamma Control
+#define ILI9328_GAMCTRL3 0x32 // Gamma Control
+#define ILI9328_GAMCTRL4 0x35 // Gamma Control
+#define ILI9328_GAMCTRL5 0x36 // Gamma Control
+#define ILI9328_GAMCTRL6 0x37 // Gamma Control
+#define ILI9328_GAMCTRL7 0x38 // Gamma Control
+#define ILI9328_GAMCTRL8 0x39 // Gamma Control
+#define ILI9328_GAMCTRL9 0x3C // Gamma Control
+#define ILI9328_GAMCTRLA 0x3D // Gamma Control
+
+// With landscape screen orientation 'Horizontal' is Y and 'Vertical' is X
+#define ILI9328_HASTART 0x50 // Horizontal Address Start Position (0-255)
+#define ILI9328_HAEND 0x51 // Horizontal Address End Position (0-255)
+#define ILI9328_VASTART 0x52 // Vertical Address Start Position (0-511)
+#define ILI9328_VAEND 0x53 // Vertical Address End Position (0-511)
+
+#define ILI9328_GATE_SCANCTL1 0x60 // Gate Scan Control
+#define ILI9328_GATE_SCANCTL2 0x61 // Gate Scan Control
+#define ILI9328_GATE_SCANCTL3 0x6A // Gate Scan Control
+
+#define ILI9328_PLTPOS1 0x80 // Partial Image 1 Display Position
+#define ILI9328_PLTSTART1 0x81 // Partial Image 1 RAM Start Address
+#define ILI9328_PLTEND1 0x82 // Partial Image 1 RAM End Address
+#define ILI9328_PLTPOS2 0x83 // Partial Image 2 Display Position
+#define ILI9328_PLTSTART2 0x84 // Partial Image 2 RAM Start Address
+#define ILI9328_PLTEND2 0x85 // Partial Image 2 RAM End Address
+
+#define ILI9328_IFCTL1 0x90 // Panel Interface Control 1
+#define ILI9328_IFCTL2 0x92 // Panel Interface Control 2
+#define ILI9328_IFCTL4 0x95 // Panel Interface Control 4
+#define ILI9328_IFCTL5 0x97 // Panel Interface Control 5
+
+#define ILI9328_OTPWR 0xA1 // OTP VCM Programming Control
+#define ILI9328_RDOTP 0xA2 // OTP VCM Status and Enable
+#define ILI9328_OTPPKEY 0xA5 // OTP Programming ID Key
+
+
+static const uint16_t ili9328_init[] = {
+ DATASIZE_16BIT,
+ ESC_REG(ILI9328_DRVCTL), ILI9328_DRVCTL_DATA,
+ ESC_REG(ILI9328_LCDCTL), 0x0400, // LCD Driving Wave Control
+ ESC_REG(ILI9328_ETMOD), ILI9328_ETMOD_DATA,
+
+ ESC_REG(ILI9328_RESIZECTL), 0x0000,
+ ESC_REG(ILI9328_DISCTRL2), 0x0202,
+ ESC_REG(ILI9328_DISCTRL3), 0x0000,
+ ESC_REG(ILI9328_DISCTRL4), 0x0000,
+ ESC_REG(ILI9328_RGBCTRL1), 0x0000,
+ ESC_REG(ILI9328_FMARKERPOS), 0x0000,
+ ESC_REG(ILI9328_RGBCTRL2), 0x0000,
+ ESC_REG(ILI9328_PWCTRL1), 0x0000,
+ ESC_REG(ILI9328_PWCTRL2), 0x0007,
+ ESC_REG(ILI9328_PWCTRL3), 0x0000,
+ ESC_REG(ILI9328_PWCTRL4), 0x0000,
+ ESC_REG(ILI9328_DISCTRL1), 0x0001,
+ ESC_DELAY(200),
+ ESC_REG(ILI9328_PWCTRL1), 0x1690,
+ ESC_REG(ILI9328_PWCTRL2), 0x0227,
+ ESC_DELAY(50),
+ ESC_REG(ILI9328_PWCTRL3), 0x008C,
+ ESC_DELAY(50),
+ ESC_REG(ILI9328_PWCTRL4), 0x1500,
+ ESC_REG(ILI9328_PWCTRL7), 0x0004,
+ ESC_REG(ILI9328_FRMCTR), 0x000D,
+ ESC_DELAY(50),
+ ESC_REG(ILI9328_GATE_SCANCTL1), ILI9328_GATE_SCANCTL1_DATA,
+ ESC_REG(ILI9328_GATE_SCANCTL2), 0x0001,
+ ESC_REG(ILI9328_GATE_SCANCTL3), 0x0000,
+ ESC_REG(ILI9328_PLTPOS1), 0x0000,
+ ESC_REG(ILI9328_PLTSTART1), 0x0000,
+ ESC_REG(ILI9328_PLTEND1), 0x0000,
+ ESC_REG(ILI9328_PLTPOS2), 0x0000,
+ ESC_REG(ILI9328_PLTSTART2), 0x0000,
+ ESC_REG(ILI9328_PLTEND2), 0x0000,
+ ESC_REG(ILI9328_IFCTL1), 0x0010,
+ ESC_REG(ILI9328_IFCTL2), 0x0600,
+ ESC_REG(ILI9328_DISCTRL1), 0x0133,
+ ESC_END
+};
diff --git a/src/lcd/tft_io/ili9341.h b/src/lcd/tft_io/ili9341.h
new file mode 100644
index 0000000..dda326d
--- /dev/null
+++ b/src/lcd/tft_io/ili9341.h
@@ -0,0 +1,170 @@
+/**
+ * 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 .
+ *
+ */
+#pragma once
+
+#include "tft_io.h"
+
+#include "../../inc/MarlinConfig.h"
+
+#define ILI9341_MADCTL_MY 0x80 // Row Address Order
+#define ILI9341_MADCTL_MX 0x40 // Column Address Order
+#define ILI9341_MADCTL_MV 0x20 // Row/Column Exchange
+#define ILI9341_MADCTL_ML 0x10 // Vertical Refresh Order
+#define ILI9341_MADCTL_BGR 0x08 // RGB-BGR ORDER
+#define ILI9341_MADCTL_RGB 0x00
+#define ILI9341_MADCTL_MH 0x04 // Horizontal Refresh Order
+
+#define ILI9341_ORIENTATION_UP ILI9341_MADCTL_MY // 240x320 ; Cable on the upper side
+#define ILI9341_ORIENTATION_RIGHT ILI9341_MADCTL_MV // 320x240 ; Cable on the right side
+#define ILI9341_ORIENTATION_LEFT ILI9341_MADCTL_MY | ILI9341_MADCTL_MX | ILI9341_MADCTL_MV // 320x240 ; Cable on the left side
+#define ILI9341_ORIENTATION_DOWN ILI9341_MADCTL_MX // 240x320 ; Cable on the upper side
+
+#define ILI9341_ORIENTATION IF_0((TFT_ORIENTATION) & TFT_EXCHANGE_XY, ILI9341_MADCTL_MV) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_X, ILI9341_MADCTL_MX) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_Y, ILI9341_MADCTL_MY)
+
+#if !defined(TFT_COLOR) || TFT_COLOR == TFT_COLOR_BGR
+ #define ILI9341_COLOR ILI9341_MADCTL_BGR
+#elif TFT_COLOR == TFT_COLOR_RGB
+ #define ILI9341_COLOR ILI9341_MADCTL_RGB
+#endif
+
+#define ILI9341_MADCTL_DATA (ILI9341_ORIENTATION) | (ILI9341_COLOR)
+
+#define ILI9341_NOP 0x00 // No Operation
+#define ILI9341_SWRESET 0x01 // Software Reset
+#define ILI9341_RDDIDIF 0x04 // Read display identification information
+#define ILI9341_RDDST 0x09 // Read Display Status
+#define ILI9341_RDDPM 0x0A // Read Display Power Mode
+#define ILI9341_RDDMADCTL 0x0B // Read Display MADCTL
+#define ILI9341_RDDCOLMOD 0x0C // Read Display Pixel Format
+#define ILI9341_RDDIM 0x0D // Read Display Image Format
+#define ILI9341_RDDSM 0x0E // Read Display Signal Mode
+#define ILI9341_RDDSDR 0x0F // Read Display Self-Diagnostic Result
+#define ILI9341_SPLIN 0x10 // Enter Sleep Mode
+#define ILI9341_SLPOUT 0x11 // Sleep Out
+#define ILI9341_PTLON 0x12 // Partial Mode ON
+#define ILI9341_NORON 0x13 // Normal Display Mode ON
+#define ILI9341_DINVOFF 0x20 // Display Inversion OFF
+#define ILI9341_DINVON 0x21 // Display Inversion ON
+#define ILI9341_GAMSET 0x26 // Gamma Set
+#define ILI9341_DISPOFF 0x28 // Display OFF
+#define ILI9341_DISPON 0x29 // Display ON
+#define ILI9341_CASET 0x2A // Column Address Set
+#define ILI9341_PASET 0x2B // Page Address Set
+#define ILI9341_RAMWR 0x2C // Memory Write
+#define ILI9341_RGBSET 0x2D // Color Set
+#define ILI9341_RAMRD 0x2E // Memory Read
+#define ILI9341_PLTAR 0x30 // Partial Area
+#define ILI9341_VSCRDEF 0x33 // Vertical Scrolling Definition
+#define ILI9341_TEOFF 0x34 // Tearing Effect Line OFF
+#define ILI9341_TEON 0x35 // Tearing Effect Line ON
+#define ILI9341_MADCTL 0x36 // Memory Access Control
+#define ILI9341_VSCRSADD 0x37 // Vertical Scrolling Start Address
+#define ILI9341_IDMOFF 0x38 // Idle Mode OFF
+#define ILI9341_IDMON 0x39 // Idle Mode ON
+#define ILI9341_PIXSET 0x3A // COLMOD: Pixel Format Set
+#define ILI9341_WRMEMC 0x3C // Write Memory Continue
+#define ILI9341_RDMEMC 0x3E // Read Memory Continue
+#define ILI9341_STE 0x44 // Set Tear Scanline
+#define ILI9341_GSCAN 0x45 // Get Scanline
+#define ILI9341_WRDISBV 0x51 // Write Display Brightness
+#define ILI9341_RDDISBV 0x52 // Read Display Brightness
+#define ILI9341_WRCTRLD 0x53 // Write CTRL Display
+#define ILI9341_RDCTRLD 0x54 // Read CTRL Display
+#define ILI9341_WRCABC 0x55 // Write Content Adaptive Brightness Control
+#define ILI9341_RDCABC 0x56 // Read Content Adaptive Brightness Control
+#define ILI9341_WRCABCMB 0x5E // Write CABC Minimum Brightness / Backlight Control 1
+#define ILI9341_RDCABCMB 0x5F // Read CABC Minimum Brightness / Backlight Control 1
+#define ILI9341_RDID1 0xDA // Read ID1
+#define ILI9341_RDID2 0xDB // Read ID2
+#define ILI9341_RDID3 0xDC // Read ID3
+
+#define ILI9341_IFMODE 0xB0 // RGB Interface Signal Control
+#define ILI9341_FRMCTR1 0xB1 // Frame Rate Control (In Normal Mode/Full Colors)
+#define ILI9341_FRMCTR2 0xB2 // Frame Rate Control (In Idle Mode/8 colors)
+#define ILI9341_FRMCTR3 0xB3 // Frame Rate control (In Partial Mode/Full Colors)
+#define ILI9341_INVTR 0xB4 // Display Inversion Control
+#define ILI9341_PRCTR 0xB5 // Blanking Porch Control
+#define ILI9341_DISCTRL 0xB6 // Display Function Control
+#define ILI9341_ETMOD 0xB7 // Entry Mode Set
+#define ILI9341_BLCTL1 0xB8 // Backlight Control 1
+#define ILI9341_BLCTL2 0xB9 // Backlight Control 2
+#define ILI9341_BLCTL3 0xBA // Backlight Control 3
+#define ILI9341_BLCTL4 0xBB // Backlight Control 4
+#define ILI9341_BLCTL5 0xBC // Backlight Control 5
+#define ILI9341_BLCTL7 0xBE // Backlight Control 7
+#define ILI9341_BLCTL8 0xBF // Backlight Control 8
+#define ILI9341_PWCTRL1 0xC0 // Power Control 1
+#define ILI9341_PWCTRL2 0xC1 // Power Control 2
+#define ILI9341_VMCTRL1 0xC5 // VCOM Control 1
+#define ILI9341_VMCTRL2 0xC7 // VCOM Control 2
+#define ILI9341_PWCTRLA 0xCB // Power control A
+#define ILI9341_PWCTRLB 0xCF // Power control B
+#define ILI9341_NVMWR 0xD0 // NV Memory Write
+#define ILI9341_NVMPKEY 0xD1 // NV Memory Protection Key
+#define ILI9341_RDNVM 0xD2 // NV Memory Status Read
+#define ILI9341_RDID4 0xD3 // Read ID4 - 0x009341
+#define ILI9341_PGAMCTRL 0xE0 // Positive Gamma Correction
+#define ILI9341_NGAMCTRL 0xE1 // Negative Gamma Correction
+#define ILI9341_DGAMCTRL1 0xE2 // Digital Gamma Control 1
+#define ILI9341_DGAMCTRL2 0xE3 // Digital Gamma Control 2
+#define ILI9341_DRVTCTLA1 0xE8 // Driver timing control A
+#define ILI9341_DRVTCTLA2 0xE9 // Driver timing control A
+#define ILI9341_DRVTCTLB 0xEA // Driver timing control B
+#define ILI9341_PONSEQCTL 0xED // Power on sequence control
+#define ILI9341_EN3G 0xF2 // Enable 3G - 3 gamma control
+#define ILI9341_IFCTL 0xF6 // Interface Control
+#define ILI9341_PUMPRCTL 0xF7 // Pump ratio control
+
+
+static const uint16_t ili9341_init[] = {
+ DATASIZE_8BIT,
+ ESC_REG(ILI9341_SWRESET), ESC_DELAY(100),
+ ESC_REG(ILI9341_SLPOUT), ESC_DELAY(20),
+/*
+ ESC_REG(ILI9341_PWCTRLA), 0x0039, 0x002C, 0x0000, 0x0034, 0x0002, // Power control A
+ ESC_REG(ILI9341_PWCTRLB), 0x0000, 0x00C1, 0x0030, // Power control B
+ ESC_REG(ILI9341_DRVTCTLA1), 0x0085, 0x0000, 0x0078, // Driver timing control A
+ ESC_REG(ILI9341_DRVTCTLB), 0x0000, 0x0000, // Driver timing control B
+ ESC_REG(ILI9341_PONSEQCTL), 0x0064, 0x0003, 0x0012, 0x0081, // Power on sequence control
+ ESC_REG(ILI9341_DISCTRL), 0x0008, 0x0082, 0x0027, // Display Function Control
+ ESC_REG(ILI9341_PUMPRCTL), 0x0020, // Pump ratio control
+ ESC_REG(ILI9341_VMCTRL1), 0x003E, 0x0028, // VCOM Control 1
+ ESC_REG(ILI9341_VMCTRL2), 0x0086, // VCOM Control 2
+ ESC_REG(ILI9341_FRMCTR1), 0x0000, 0x0018, // Frame Rate Control (In Normal Mode/Full Colors)
+ ESC_REG(ILI9341_PWCTRL1), 0x0023, // Power Control 1
+ ESC_REG(ILI9341_PWCTRL2), 0x0010, // Power Control 2
+*/
+ ESC_REG(ILI9341_MADCTL), ILI9341_MADCTL_DATA,
+ ESC_REG(ILI9341_PIXSET), 0x0055,
+
+ /* Gamma Correction */
+ ESC_REG(ILI9341_EN3G), 0x0000, // 3Gamma Function Disable
+ ESC_REG(ILI9341_GAMSET), 0x0001, // Gamma curve selected
+ ESC_REG(ILI9341_PGAMCTRL), 0x000F, 0x0031, 0x002B, 0x000C, 0x000E, 0x0008, 0x004E, 0x00F1, 0x0037, 0x0007, 0x0010, 0x0003, 0x000E, 0x0009, 0x0000,
+ ESC_REG(ILI9341_NGAMCTRL), 0x0000, 0x000E, 0x0014, 0x0003, 0x0011, 0x0007, 0x0031, 0x00C1, 0x0048, 0x0008, 0x000F, 0x000C, 0x0031, 0x0036, 0x000F,
+
+ ESC_REG(ILI9341_NORON),
+ ESC_REG(ILI9341_DISPON),
+ ESC_END
+};
diff --git a/src/lcd/tft_io/ili9488.h b/src/lcd/tft_io/ili9488.h
new file mode 100644
index 0000000..e71c0d1
--- /dev/null
+++ b/src/lcd/tft_io/ili9488.h
@@ -0,0 +1,164 @@
+/**
+ * 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 .
+ *
+ */
+#pragma once
+
+#include "tft_io.h"
+
+#include "../../inc/MarlinConfig.h"
+
+#define ILI9488_MADCTL_MY 0x80 // Row Address Order
+#define ILI9488_MADCTL_MX 0x40 // Column Address Order
+#define ILI9488_MADCTL_MV 0x20 // Row/Column Exchange
+#define ILI9488_MADCTL_ML 0x10 // Vertical Refresh Order
+#define ILI9488_MADCTL_BGR 0x08 // RGB-BGR ORDER
+#define ILI9488_MADCTL_RGB 0x00
+#define ILI9488_MADCTL_MH 0x04 // Horizontal Refresh Order
+
+#define ILI9488_ORIENTATION_UP ILI9488_MADCTL_MY // 320x480 ; Cable on the upper side
+#define ILI9488_ORIENTATION_RIGHT ILI9488_MADCTL_MV // 480x320 ; Cable on the right side
+#define ILI9488_ORIENTATION_LEFT ILI9488_MADCTL_MY | ILI9488_MADCTL_MX | ILI9488_MADCTL_MV // 480x320 ; Cable on the left side
+#define ILI9488_ORIENTATION_DOWN ILI9488_MADCTL_MX // 320x480 ; Cable on the upper side
+
+#define ILI9488_ORIENTATION IF_0((TFT_ORIENTATION) & TFT_EXCHANGE_XY, ILI9488_MADCTL_MV) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_X, ILI9488_MADCTL_MX) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_Y, ILI9488_MADCTL_MY)
+
+#if !defined(TFT_COLOR) || TFT_COLOR == TFT_COLOR_BGR
+ #define ILI9488_COLOR ILI9488_MADCTL_BGR
+#elif TFT_COLOR == TFT_COLOR_RGB
+ #define ILI9488_COLOR ILI9488_MADCTL_RGB
+#endif
+
+#define ILI9488_MADCTL_DATA (ILI9488_ORIENTATION) | (ILI9488_COLOR)
+
+#define ILI9488_NOP 0x00 // No Operation
+#define ILI9488_SWRESET 0x01 // Software Reset
+#define ILI9488_RDDIDIF 0x04 // Read Display Identification Information
+#define ILI9488_RDNUMED 0x05 // Read Number of the Errors on DSI
+#define ILI9488_RDDST 0x09 // Read Display Status
+#define ILI9488_RDDPM 0x0A // Read Display Power Mode
+#define ILI9488_RDDMADCTL 0x0B // Read Display MADCTL
+#define ILI9488_RDDCOLMOD 0x0C // Read Display COLMOD
+#define ILI9488_RDDIM 0x0D // Read Display Image Mode
+#define ILI9488_RDDSM 0x0E // Read Display Signal Mode
+#define ILI9488_RDDSDR 0x0F // Read Display Self-Diagnostic Result
+#define ILI9488_SLPIN 0x10 // Sleep IN
+#define ILI9488_SLPOUT 0x11 // Sleep OUT
+#define ILI9488_PTLON 0x12 // Partial Mode ON
+#define ILI9488_NORON 0x13 // Normal Display Mode ON
+#define ILI9488_INVOFF 0x20 // Display Inversion OFF
+#define ILI9488_INVON 0x21 // Display Inversion ON
+#define ILI9488_ALLPOFF 0x22 // All Pixels OFF
+#define ILI9488_ALLPON 0x23 // All Pixels ON
+#define ILI9488_DISOFF 0x28 // Display OFF
+#define ILI9488_DISON 0x29 // Display ON
+#define ILI9488_CASET 0x2A // Column Address Set
+#define ILI9488_PASET 0x2B // Page Address Set
+#define ILI9488_RAMWR 0x2C // Memory Write
+#define ILI9488_RAMRD 0x2E // Memory Read
+#define ILI9488_PLTAR 0x30 // Partial Area
+#define ILI9488_VSCRDEF 0x33 // Vertical Scrolling Definition
+#define ILI9488_TEOFF 0x34 // Tearing Effect Line OFF
+#define ILI9488_TEON 0x35 // Tearing Effect Line ON
+#define ILI9488_MADCTL 0x36 // Memory Access Control
+#define ILI9488_VSCRSADD 0x37 // Vertical Scrolling Start Address
+#define ILI9488_IDMOFF 0x38 // Idle Mode OFF
+#define ILI9488_IDMON 0x39 // Idle Mode ON
+#define ILI9488_COLMOD 0x3A // Interface Pixel Format
+#define ILI9488_RAMWRC 0x3C // Memory Write Continue
+#define ILI9488_RAMRDRC 0x3E // Memory Read Continue
+#define ILI9488_TESLWR 0x44 // Write Tear Scan Line
+#define ILI9488_TESLRD 0x45 // Read Scan Line
+#define ILI9488_WRDISBV 0x51 // Write Display Brightness Value
+#define ILI9488_RDDISBV 0x52 // Read Display Brightness Value
+#define ILI9488_WRCTRLD 0x53 // Write Control Display Value
+#define ILI9488_RDCTRLD 0x54 // Read Control Display Value
+#define ILI9488_WRCABC 0x55 // Write Content Adaptive Brightness Control Value
+#define ILI9488_RDCABC 0x56 // Read Content Adaptive Brightness Control Value
+#define ILI9488_WRCABCMB 0x5E // Write CABC Minimum Brightness
+#define ILI9488_RDCABCMB 0x5F // Read CABC Minimum Brightness
+#define ILI9488_RDABCSDR 0x68 // Read Automatic Brightness Control Self-diagnostic Result
+#define ILI9488_RDID1 0xDA // Read ID1
+#define ILI9488_RDID2 0xDB // Read ID2
+#define ILI9488_RDID3 0xDC // Read ID3
+
+#define ILI9488_IFMODE 0xB0 // Interface Mode Control
+#define ILI9488_FRMCTR1 0xB1 // Frame Rate Control (In Normal Mode/Full Colors)
+#define ILI9488_FRMCTR2 0xB2 // Frame Rate Control (In Idle Mode/8 Colors)
+#define ILI9488_FRMCTR3 0xB3 // Frame Rate Control (In Partial Mode/Full Colors)
+#define ILI9488_INVTR 0xB4 // Display Inversion Control
+#define ILI9488_PRCTR 0xB5 // Blanking Porch Control
+#define ILI9488_DISCTRL 0xB6 // Display Function Control
+#define ILI9488_ETMOD 0xB7 // Entry Mode Set
+#define ILI9488_CECTRL1 0xB9 // Color Enhancement Control 1
+#define ILI9488_CECTRL2 0xBA // Color Enhancement Control 2
+#define ILI9488_HSLCTRL 0xBE // HS Lanes Control
+#define ILI9488_PWCTRL1 0xC0 // Power Control 1
+#define ILI9488_PWCTRL2 0xC1 // Power Control 2
+#define ILI9488_PWCTRL3 0xC2 // Power Control 3 (For Normal Mode)
+#define ILI9488_PWCTRL4 0xC3 // Power Control 4 (For Idle Mode)
+#define ILI9488_PWCTRL5 0xC4 // Power Control 5 (For Partial Mode)
+#define ILI9488_VMCTRL 0xC5 // VCOM Control
+#define ILI9488_CABCCTRL1 0xC6 // CABC Control 1
+#define ILI9488_CABCCTRL2 0xC8 // CABC Control 2
+#define ILI9488_CABCCTRL3 0xC9 // CABC Control 3
+#define ILI9488_CABCCTRL4 0xCA // CABC Control 4
+#define ILI9488_CABCCTRL5 0xCB // CABC Control 5
+#define ILI9488_CABCCTRL6 0xCC // CABC Control 6
+#define ILI9488_CABCCTRL7 0xCD // CABC Control 7
+#define ILI9488_CABCCTRL8 0xCE // CABC Control 8
+#define ILI9488_CABCCTRL9 0xCF // CABC Control 9
+#define ILI9488_NVMWR 0xD0 // NV Memory Write
+#define ILI9488_NVMPKEY 0xD1 // NV Memory Protection Key
+#define ILI9488_RDNVM 0xD2 // NV Memory Status Read
+#define ILI9488_RDID4 0xD3 // Read ID4 - 0x009488
+#define ILI9488_ADJCTL1 0xD7 // Adjust Control 1
+#define ILI9488_RDIDV 0xD8 // Read ID Version
+#define ILI9488_PGAMCTRL 0xE0 // Positive Gamma Control
+#define ILI9488_NGAMCTRL 0xE1 // Negative Gamma Control
+#define ILI9488_DGAMCTRL1 0xE2 // Ditigal Gamma Control 1
+#define ILI9488_DGAMCTRL2 0xE3 // Ditigal Gamma Control 2
+#define ILI9488_SETIMAGE 0xE9 // Set Image Function
+#define ILI9488_ADJCTL2 0xF2 // Adjust Control 2
+#define ILI9488_ADJCTL3 0xF7 // Adjust Control 3
+#define ILI9488_ADJCTL4 0xF8 // Adjust Control 4
+#define ILI9488_ADJCTL5 0xF9 // Adjust Control 5
+#define ILI9488_RDEXTC 0xFB // Read EXTC command is SPI mode
+#define ILI9488_ADJCTL6 0xFC // Adjust Control 6
+#define ILI9488_ADJCTL7 0xFF // Adjust Control 7
+
+static const uint16_t ili9488_init[] = {
+ DATASIZE_8BIT,
+ ESC_REG(ILI9488_SWRESET), ESC_DELAY(120),
+ ESC_REG(ILI9488_SLPOUT), ESC_DELAY(20),
+
+ ESC_REG(ILI9488_MADCTL), ILI9488_MADCTL_DATA,
+ ESC_REG(ILI9488_COLMOD), 0x0055,
+
+ /* Gamma Correction. */
+ ESC_REG(ILI9488_PGAMCTRL), 0x0000, 0x0003, 0x0009, 0x0008, 0x0016, 0x000A, 0x003F, 0x0078, 0x004C, 0x0009, 0x000A, 0x0008, 0x0016, 0x001A, 0x000F,
+ ESC_REG(ILI9488_NGAMCTRL), 0x0000, 0x0016, 0x0019, 0x0003, 0x000F, 0x0005, 0x0032, 0x0045, 0x0046, 0x0004, 0x000E, 0x000D, 0x0035, 0x0037, 0x000F,
+
+ ESC_REG(ILI9488_NORON),
+ ESC_REG(ILI9488_DISON),
+ ESC_END
+};
diff --git a/src/lcd/tft_io/r65105.h b/src/lcd/tft_io/r65105.h
new file mode 100644
index 0000000..8be2afe
--- /dev/null
+++ b/src/lcd/tft_io/r65105.h
@@ -0,0 +1,176 @@
+/**
+ * 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 .
+ *
+ */
+#pragma once
+
+#include "tft_io.h"
+
+#include "../../inc/MarlinConfig.h"
+
+#define R61505_DRVCTL_SM 0x0400
+#define R61505_DRVCTL_SS 0x0100 // Select the shift direction of outputs from the source driver. 0 - from S1 to S720 / 1 - from S720 to S1
+
+#define R61505_ETMOD_TRIGGER 0x8000 // 18-bit RAM when set; 16-bit RAM when not set
+#define R61505_ETMOD_DFM 0x4000
+#define R61505_ETMOD_BGR 0x1000 // RGB-BGR ORDER
+#define R61505_ETMOD_RGB 0x0000
+
+#define R61505_ETMOD_HWM 0x0020
+#define R61505_ETMOD_ORG 0x0080
+#define R61505_ETMOD_ID1 0x0020 // 0 - Vertical Decrement / 1 - Vertical Increment
+#define R61505_ETMOD_ID0 0x0010 // 0 - Horizontal Decrement / 1 - Horizontal Increment
+#define R61505_ETMOD_AM 0x0008 // 0 - Horizontal / 1 - Vertical
+
+#define R61505_DRVCTRL_GS 0x8000 // Gate Scan direction
+
+// MKS Robin TFT v1.1 - 320x240 ; Cable on the left side
+
+#if TFT_ROTATION == TFT_ROTATE_180
+ #define R61505_DRVCTL_DATA 0x0000
+ #define R61505_DRVCTRL_DATA (0x2700 | R61505_DRVCTRL_GS)
+#else
+ #define R61505_DRVCTL_DATA R61505_DRVCTL_SS
+ #define R61505_DRVCTRL_DATA 0x2700
+#endif
+
+/*
+#define R61505_ETMOD_ORIENTATION IF_0((TFT_ORIENTATION) & TFT_EXCHANGE_XY, R61505_ETMOD_AM) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_X, R61505_ETMOD_ID0) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_Y, R61505_ETMOD_ID1)
+*/
+
+#define R61505_ETMOD_ORIENTATION (R61505_ETMOD_AM | R61505_ETMOD_ID0 | R61505_ETMOD_ID1)
+
+#if !defined(TFT_COLOR) || TFT_COLOR == TFT_COLOR_BGR
+ #define R61505_ETMOD_COLOR R61505_ETMOD_BGR
+#elif TFT_COLOR == TFT_COLOR_RGB
+ #define R61505_ETMOD_COLOR R61505_ETMOD_RGB
+#endif
+
+#define R61505_ETMOD_DATA (R61505_ETMOD_ORIENTATION) | (R61505_ETMOD_COLOR)
+
+
+#define R61505_RDDID 0x00 // ID code - 0x1505
+#define R61505_DRVCTL 0x01 // Driver Output Control
+#define R61505_LCDCTL 0x02 // LCD Driving Wave Control
+#define R61505_ETMOD 0x03 // Entry Mode - Control the GRAM update direction
+#define R61505_RESIZECTL 0x04 // Resizing Control Register
+#define R61505_DISCTRL1 0x07 // Display Control 1
+#define R61505_DISCTRL2 0x08 // Display Control 2
+#define R61505_DISCTRL3 0x09 // Display Control 3
+#define R61505_DISCTRL4 0x0A // Display Control 4
+#define R61505_RGBCTRL1 0x0C // RGB Display Interface Control 1
+#define R61505_FMARKERPOS 0x0D // Frame Marker Position
+#define R61505_RGBCTRL2 0x0F // RGB Display Interface Control 2
+#define R61505_PWCTRL1 0x10 // Power Control 1
+#define R61505_PWCTRL2 0x11 // Power Control 2
+#define R61505_PWCTRL3 0x12 // Power Control 3
+#define R61505_PWCTRL4 0x13 // Power Control 4
+
+// With landscape screen orientation 'Horizontal' is Y and 'Vertical' is X
+#define R61505_HASET 0x20 // GRAM Horizontal Address Set (0-255)
+#define R61505_VASET 0x21 // GRAM Vertical Address Set (0-511)
+#define R61505_RAMWR 0x22 // Write data to GRAM
+#define R61505_RAMRD 0x22 // Read Data from GRAM
+
+#define R61505_PWCTRL7 0x29 // Power Control 7
+#define R61505_GAMCTRL1 0x30 // Gamma Control
+#define R61505_GAMCTRL2 0x31 // Gamma Control
+#define R61505_GAMCTRL3 0x32 // Gamma Control
+#define R61505_GAMCTRL4 0x35 // Gamma Control
+#define R61505_GAMCTRL5 0x36 // Gamma Control
+#define R61505_GAMCTRL6 0x37 // Gamma Control
+#define R61505_GAMCTRL7 0x38 // Gamma Control
+#define R61505_GAMCTRL8 0x39 // Gamma Control
+#define R61505_GAMCTRL9 0x3C // Gamma Control
+#define R61505_GAMCTRLA 0x3D // Gamma Control
+
+// With landscape screen orientation 'Horizontal' is Y and 'Vertical' is X
+#define R61505_HASTART 0x50 // Horizontal Address Start Position (0-255)
+#define R61505_HAEND 0x51 // Horizontal Address End Position (0-255)
+#define R61505_VASTART 0x52 // Vertical Address Start Position (0-511)
+#define R61505_VAEND 0x53 // Vertical Address End Position (0-511)
+
+#define R61505_DRVCTRL 0x60 // Driver Output Control
+#define R61505_BASE_IMAGE_CTRL 0x61 // Base Image Display Control
+#define R61505_VSCROLL_CTRL 0x6A // Vertical Scroll Control
+
+#define R61505_PLTPOS1 0x80 // Partial Image 1 Display Position
+#define R61505_PLTSTART1 0x81 // Partial Image 1 RAM Start Address
+#define R61505_PLTEND1 0x82 // Partial Image 1 RAM End Address
+#define R61505_PLTPOS2 0x83 // Partial Image 2 Display Position
+#define R61505_PLTSTART2 0x84 // Partial Image 2 RAM Start Address
+#define R61505_PLTEND2 0x85 // Partial Image 2 RAM End Address
+
+#define R61505_IFCTL1 0x90 // Panel Interface Control 1
+#define R61505_IFCTL2 0x92 // Panel Interface Control 2
+#define R61505_IFCTL3 0x93 // Panel Interface Control 3
+#define R61505_IFCTL4 0x95 // Panel Interface Control 4
+#define R61505_IFCTL5 0x97 // Panel Interface Control 5
+#define R61505_IFCTL6 0x98 // Panel Interface Control 6
+
+#define R61505_OSC_CTRL 0xA4 // Oscillation Control
+
+
+static const uint16_t r61505_init[] = {
+ DATASIZE_16BIT,
+ ESC_REG(R61505_DRVCTL), R61505_DRVCTL_DATA,
+ ESC_REG(R61505_LCDCTL), 0x0700, // LCD Driving Wave Control
+ ESC_REG(R61505_ETMOD), R61505_ETMOD_DATA,
+
+ ESC_REG(R61505_RESIZECTL), 0x0000,
+ ESC_REG(R61505_DISCTRL1), 0x0173,
+ ESC_REG(R61505_DISCTRL2), 0x0202,
+ ESC_REG(R61505_DISCTRL3), 0x0000,
+ ESC_REG(R61505_DISCTRL4), 0x0000,
+ ESC_REG(R61505_RGBCTRL1), 0x0000,
+ ESC_REG(R61505_FMARKERPOS), 0x0000,
+ ESC_REG(R61505_RGBCTRL2), 0x0000,
+
+ ESC_REG(R61505_PWCTRL1), 0x17B0,
+ ESC_REG(R61505_PWCTRL2), 0x0037,
+ ESC_REG(R61505_PWCTRL3), 0x0138,
+ ESC_REG(R61505_PWCTRL4), 0x1700,
+ ESC_REG(R61505_PWCTRL7), 0x000D,
+
+ ESC_REG(R61505_GAMCTRL1), 0x0001,
+ ESC_REG(R61505_GAMCTRL2), 0x0606,
+ ESC_REG(R61505_GAMCTRL3), 0x0304,
+ ESC_REG(R61505_GAMCTRL4), 0x0103,
+ ESC_REG(R61505_GAMCTRL5), 0x011D,
+ ESC_REG(R61505_GAMCTRL6), 0x0404,
+ ESC_REG(R61505_GAMCTRL7), 0x0404,
+ ESC_REG(R61505_GAMCTRL8), 0x0404,
+ ESC_REG(R61505_GAMCTRL9), 0x0700,
+ ESC_REG(R61505_GAMCTRLA), 0x0A1F,
+
+ ESC_REG(R61505_DRVCTRL), R61505_DRVCTRL_DATA,
+ ESC_REG(R61505_BASE_IMAGE_CTRL), 0x0001,
+ ESC_REG(R61505_VSCROLL_CTRL), 0x0000,
+
+ ESC_REG(R61505_IFCTL1), 0x0010,
+ ESC_REG(R61505_IFCTL2), 0x0000,
+ ESC_REG(R61505_IFCTL3), 0x0003,
+ ESC_REG(R61505_IFCTL4), 0x0101,
+ ESC_REG(R61505_IFCTL5), 0x0000,
+ ESC_REG(R61505_IFCTL6), 0x0000,
+ ESC_END
+};
diff --git a/src/lcd/tft_io/ssd1963.h b/src/lcd/tft_io/ssd1963.h
new file mode 100644
index 0000000..8564b28
--- /dev/null
+++ b/src/lcd/tft_io/ssd1963.h
@@ -0,0 +1,131 @@
+/**
+ * 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 .
+ *
+ */
+#pragma once
+
+#include "tft_io.h"
+
+#include "../../inc/MarlinConfig.h"
+
+#define SSD1963_MADCTL_MY 0x80 // Row Address Order
+#define SSD1963_MADCTL_MX 0x40 // Column Address Order
+#define SSD1963_MADCTL_MV 0x20 // Row/Column Exchange
+#define SSD1963_MADCTL_MH 0x10 // Horizontal Refresh Order
+#define SSD1963_MADCTL_BGR 0x08 // RGB-BGR ORDER
+#define SSD1963_MADCTL_RGB 0x00
+#define SSD1963_MADCTL_ML 0x04 // Vertical Refresh Order
+#define SSD1963_MADCTL_FH 0x02 // Flip Horizontal
+#define SSD1963_MADCTL_FV 0x01 // Flip Vertical
+
+#define SSD1963_ORIENTATION IF_0((TFT_ORIENTATION) & TFT_EXCHANGE_XY, SSD1963_MADCTL_MV) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_X, SSD1963_MADCTL_FH) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_Y, SSD1963_MADCTL_FV)
+
+#if !defined(TFT_COLOR) || TFT_COLOR == TFT_COLOR_RGB
+ #define SSD1963_COLOR SSD1963_MADCTL_RGB
+#elif TFT_COLOR == TFT_COLOR_BGR
+ #define SSD1963_COLOR SSD1963_MADCTL_BGR
+#endif
+
+#define SSD1963_MADCTL_DATA (SSD1963_ORIENTATION) | (SSD1963_COLOR)
+
+#define SSD1963_NOP 0x00 // No Operation
+#define SSD1963_SWRESET 0x01 // Software reset
+#define SSD1963_RDDPM 0x0A // Read Display Power Mode
+#define SSD1963_RDDMADCTL 0x0B // Read Display MADCTL
+#define SSD1963_RDDCOLMOD 0x0C // Read Display Pixel Format
+#define SSD1963_RDDIM 0x0D // Read Display Image Mode
+#define SSD1963_RDDSM 0x0E // Read Display Signal Mode
+#define SSD1963_SLPIN 0x10 // Sleep In
+#define SSD1963_SLPOUT 0x11 // Sleep Out
+#define SSD1963_PTLON 0x12 // Partial Display Mode On
+#define SSD1963_NORON 0x13 // Normal Display Mode On
+#define SSD1963_INVOFF 0x20 // Display Inversion Off
+#define SSD1963_INVON 0x21 // Display Inversion On
+#define SSD1963_GAMSET 0x26 // Gamma Set
+#define SSD1963_DISPOFF 0x28 // Display Off
+#define SSD1963_DISPON 0x29 // Display On
+#define SSD1963_CASET 0x2A // Column Address Set
+#define SSD1963_RASET 0x2B // Row Address Set
+#define SSD1963_RAMWR 0x2C // Memory Write
+#define SSD1963_RAMRD 0x2E // Memory Read
+#define SSD1963_PTLAR 0x30 // Partial Area
+#define SSD1963_VSCRDEF 0x33 // Vertical Scrolling Definition
+#define SSD1963_TEOFF 0x34 // Tearing Effect Line OFF
+#define SSD1963_TEON 0x35 // Tearing Effect Line ON
+#define SSD1963_MADCTL 0x36 // Memory Data Access Control
+#define SSD1963_VSCSAD 0x37 // Vertical Scroll Start Address of RAM
+#define SSD1963_IDMOFF 0x38 // Idle Mode Off
+#define SSD1963_IDMON 0x39 // Idle Mode On
+#define SSD1963_WRMEMC 0x3C // Write Memory Continue
+#define SSD1963_RDMEMC 0x3E // Read Memory Continue
+#define SSD1963_STE 0x44 // Set Tear Scanline
+#define SSD1963_GSCAN 0x45 // Get Scanline
+#define SSD1963_WRDISBV 0x51 // Write Display Brightness
+#define SSD1963_RDDISBV 0x52 // Read Display Brightness
+#define SSD1963_WRCTRLD 0x53 // Write CTRL Display
+#define SSD1963_RDCTRLD 0x54 // Read CTRL Value Display
+#define SSD1963_WRCACE 0x55 // Write Content Adaptive Brightness Control and Color Enhancement
+#define SSD1963_RDCABC 0x56 // Read Content Adaptive Brightness Control
+#define SSD1963_WRCABCMB 0x5E // Write CABC Minimum Brightness
+#define SSD1963_RDCABCMB 0x5F // Read CABC Minimum Brightness
+#define SSD1963_RDABCSDR 0x68 // Read Automatic Brightness Control Self-Diagnostic Result
+#define SSD1963_RDDDB 0xA1 // Read Device Descriptor Block
+#define SSD1963_SLCDMODE 0xB0 // Set the LCD panel mode and resolution
+#define SSD1963_SHSYNC 0xB4 // Set HSYNC
+#define SSD1963_GHSYNC 0xB5 // Get HSYNC
+#define SSD1963_SVSYNC 0xB6 // Set VSYNC
+#define SSD1963_GVSYNC 0xB7 // Get VSYNC
+#define SSD1963_SGPIOCFG 0xB8 // Set GPIO Conf
+#define SSD1963_SGPIOV 0xBA // Set GPIO Value
+#define SSD1963_SPWMCFG 0xBE // Set PWM Conf
+#define SSD1963_GPWMCFG 0xBF // Get PWM Conf
+#define SSD1963_SDBCCFG 0xD0 // Set Dynamic Back Light Config
+#define SSD1963_GDBCCFG 0xD1 // Get Dynamic Back Light Config
+#define SSD1963_PLLON 0xE0 // PLL Enable
+#define SSD1963_PLLMN 0xE2 // Set PLL Multiplier
+#define SSD1963_SLSHIFT 0xE6 // Set the LSHIFT (pixel clock) frequency
+#define SSD1963_COLMOD 0xF0 // Interface Pixel Format
+
+static const uint16_t ssd1963_init[] = {
+ DATASIZE_8BIT,
+ ESC_REG(SSD1963_PLLMN), 0x0023, 0x0002, 0x0054,
+ ESC_REG(SSD1963_PLLON), 0x0001, ESC_DELAY(10),
+ ESC_REG(SSD1963_PLLON), 0x0003, ESC_DELAY(10),
+ ESC_REG(SSD1963_SWRESET), ESC_DELAY(100),
+
+ ESC_REG(SSD1963_SLSHIFT), 0x0001, 0x001F, 0x00FF,
+ ESC_REG(SSD1963_SLCDMODE), 0x0020, 0x0000, 0x0001, 0x00DF, 0x0001, 0x000F, 0x0000,
+ ESC_REG(SSD1963_SHSYNC), 0x0002, 0x0013, 0x0000, 0x0008, 0x002B, 0x0000, 0x0002, 0x0000,
+ ESC_REG(SSD1963_SVSYNC), 0x0001, 0x0020, 0x0000, 0x0004, 0x000C, 0x0000, 0x0002,
+ ESC_REG(SSD1963_SGPIOV), 0x000F,
+ ESC_REG(SSD1963_SGPIOCFG), 0x0007, 0x0001,
+
+ ESC_REG(SSD1963_MADCTL), SSD1963_MADCTL_DATA,
+ ESC_REG(SSD1963_COLMOD), 0x0003, ESC_DELAY(1),//RBG 565
+
+ ESC_REG(SSD1963_NORON),
+ ESC_REG(SSD1963_DISPON),
+
+ ESC_REG(SSD1963_SPWMCFG), 0x0006, 0x00F0, 0x0001, 0x00F0, 0x0000, 0x0000,
+ ESC_REG(SSD1963_SDBCCFG), 0x000D,
+ ESC_END
+};
diff --git a/src/lcd/tft_io/st7735.h b/src/lcd/tft_io/st7735.h
new file mode 100644
index 0000000..1b0d23b
--- /dev/null
+++ b/src/lcd/tft_io/st7735.h
@@ -0,0 +1,136 @@
+/**
+ * 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 .
+ *
+ */
+#pragma once
+
+#include "tft_io.h"
+
+#include "../../inc/MarlinConfig.h"
+
+#define ST7735_MADCTL_MY 0x80 // Row Address Order
+#define ST7735_MADCTL_MX 0x40 // Column Address Order
+#define ST7735_MADCTL_MV 0x20 // Row/Column Exchange
+#define ST7735_MADCTL_ML 0x10 // Vertical Refresh Order
+#define ST7735_MADCTL_BGR 0x08 // RGB-BGR ORDER
+#define ST7735_MADCTL_RGB 0x00
+#define ST7735_MADCTL_MH 0x04 // Horizontal Refresh Order
+
+#define ST7735_ORIENTATION_UP 0x00 // 128x160 ; Cable on the upper side
+#define ST7735_ORIENTATION_RIGHT ST7735_MADCTL_MV | ST7735_MADCTL_MY // 160x128 ; Cable on the right side
+#define ST7735_ORIENTATION_LEFT ST7735_MADCTL_MV | ST7735_MADCTL_MX // 160x128 ; Cable on the left side
+#define ST7735_ORIENTATION_DOWN ST7735_MADCTL_MX | ST7735_MADCTL_MY // 128x160 ; Cable on the lower side
+
+#define ST7735_ORIENTATION IF_0((TFT_ORIENTATION) & TFT_EXCHANGE_XY, ST7735_MADCTL_MV) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_X, ST7735_MADCTL_MX) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_Y, ST7735_MADCTL_MY)
+
+#if !defined(TFT_COLOR) || TFT_COLOR == TFT_COLOR_RGB
+ #define ST7735_COLOR ST7735_MADCTL_RGB
+#elif TFT_COLOR == TFT_COLOR_BGR
+ #define ST7735_COLOR ST7735_MADCTL_BGR
+#endif
+
+#define ST7735_MADCTL_DATA (ST7735_ORIENTATION) | (ST7735_COLOR)
+
+#define ST7735_NOP 0x00 // No Operation
+#define ST7735_SWRESET 0x01 // Software reset
+#define ST7735_RDDID 0x04 // Read Display ID
+#define ST7735_RDDST 0x09 // Read Display Status
+#define ST7735_RDDPM 0x0A // Read Display Power Mode
+#define ST7735_RDDMADCTL 0x0B // Read Display MADCTL
+#define ST7735_RDDCOLMOD 0x0C // Read Display Pixel Format
+#define ST7735_RDDIM 0x0D // Read Display Image Mode
+#define ST7735_RDDSM 0x0E // Read Display Signal Mode
+#define ST7735_SLPIN 0x10 // Sleep In
+#define ST7735_SLPOUT 0x11 // Sleep Out
+#define ST7735_PTLON 0x12 // Partial Display Mode On
+#define ST7735_NORON 0x13 // Normal Display Mode On
+#define ST7735_INVOFF 0x20 // Display Inversion Off
+#define ST7735_INVON 0x21 // Display Inversion On
+#define ST7735_GAMSET 0x26 // Gamma Set
+#define ST7735_DISPOFF 0x28 // Display Off // The delay time between DISPON and DISPOFF needs 120ms at least.
+#define ST7735_DISPON 0x29 // Display On
+#define ST7735_CASET 0x2A // Column Address Set
+#define ST7735_RASET 0x2B // Row Address Set
+#define ST7735_RAMWR 0x2C // Memory Write
+#define ST7735_RAMRD 0x2E // Memory Read
+#define ST7735_PTLAR 0x30 // Partial Area
+#define ST7735_TEOFF 0x34 // Tearing Effect Line OFF
+#define ST7735_TEON 0x35 // Tearing Effect Line ON
+#define ST7735_MADCTL 0x36 // Memory Data Access Control
+#define ST7735_IDMOFF 0x38 // Idle Mode Off
+#define ST7735_IDMON 0x39 // Idle Mode On
+#define ST7735_COLMOD 0x3A // Interface Pixel Format
+#define ST7735_RDID1 0xDA // Read ID1 Value
+#define ST7735_RDID2 0xDB // Read ID2 Value
+#define ST7735_RDID3 0xDC // Read ID3 Value
+
+#define ST7735_FRMCTR1 0xB1 // Frame Rate Control (In normal mode/ Full colors)
+#define ST7735_FRMCTR2 0xB2 // Frame Rate Control (In Idle mode/ 8-colors)
+#define ST7735_FRMCTR3 0xB3 // Frame Rate Control (In Partial mode/ full colors)
+#define ST7735_INVCTR 0xB4 // Display Inversion Control
+#define ST7735_DISSET5 0xB6 // Display Function set 5
+#define ST7735_PWCTR1 0xC0 // Power Control 1
+#define ST7735_PWCTR2 0xC1 // Power Control 2
+#define ST7735_PWCTR3 0xC2 // Power Control 3 (in Normal mode/ Full colors)
+#define ST7735_PWCTR4 0xC3 // Power Control 4 (in Idle mode/ 8-colors)
+#define ST7735_PWCTR5 0xC4 // Power Control 5 (in Partial mode/ full-colors)
+#define ST7735_VMCTR1 0xC5 // VCOM Control 1
+#define ST7735_VMOFCTR 0xC7 // VCOM Offset Control
+#define ST7735_WRID2 0xD1 // Write ID2 Value
+#define ST7735_WRID3 0xD2 // Write ID3 Value
+#define ST7735_PWCTR6 0xFC // Power Control 5 (in Partial mode + Idle mode)
+#define ST7735_NVFCTR1 0xD9 // EEPROM Control Status
+#define ST7735_NVFCTR2 0xDE // EEPROM Read Command
+#define ST7735_NVFCTR3 0xDF // EEPROM Write Command
+#define ST7735_GMCTRP1 0xE0 // Gamma (‘+’polarity) Correction Characteristics Setting
+#define ST7735_GMCTRN1 0xE1 // GMCTRN1 (E1h): Gamma ‘-’polarity Correction Characteristics Setting
+#define ST7735_EXTCTRL 0xF0 // Extension Command Control
+#define ST7735_VCOM4L 0xFF // Vcom 4 Level Control
+
+static const uint16_t st7735_init[] = {
+ DATASIZE_8BIT,
+ ESC_REG(ST7735_SWRESET), ESC_DELAY(100),
+ ESC_REG(ST7735_SLPOUT), ESC_DELAY(20),
+/*
+ ESC_REG(ST7735_FRMCTR1), 0x0001, 0x002C, 0x002D,
+ ESC_REG(ST7735_FRMCTR2), 0x0001, 0x002C, 0x002D,
+ ESC_REG(ST7735_FRMCTR3), 0x0001, 0x002C, 0x002D, 0x0001, 0x002C, 0x002D,
+ ESC_REG(ST7735_INVCTR), 0x0007,
+ ESC_REG(ST7735_PWCTR1), 0x00A2, 0x0002, 0x0084,
+ ESC_REG(ST7735_PWCTR2), 0x00C5,
+ ESC_REG(ST7735_PWCTR3), 0x000A, 0x0000,
+ ESC_REG(ST7735_PWCTR4), 0x008A, 0x002A,
+ ESC_REG(ST7735_PWCTR5), 0x008A, 0x00EE,
+ ESC_REG(ST7735_VMCTR1), 0x000E,
+ ESC_REG(ST7735_INVOFF),
+*/
+ ESC_REG(ST7735_MADCTL), ST7735_MADCTL_DATA,
+ ESC_REG(ST7735_COLMOD), 0x0005,
+
+ /* Gamma Correction. Colors with 'after-reset' settings are bleak */
+ ESC_REG(ST7735_GMCTRP1), 0x0002, 0x001C, 0x0007, 0x0012, 0x0037, 0x0032, 0x0029, 0x002D, 0x0029, 0x0025, 0x002B, 0x0039, 0x0000, 0x0001, 0x0003, 0x0010,
+ ESC_REG(ST7735_GMCTRN1), 0x0003, 0x001D, 0x0007, 0x0006, 0x002E, 0x002C, 0x0029, 0x002D, 0x002E, 0x002E, 0x0037, 0x003F, 0x0000, 0x0000, 0x0002, 0x0010,
+
+ ESC_REG(ST7735_NORON),
+ ESC_REG(ST7735_DISPON),
+ ESC_END
+};
diff --git a/src/lcd/tft_io/st7789v.h b/src/lcd/tft_io/st7789v.h
new file mode 100644
index 0000000..d0cf969
--- /dev/null
+++ b/src/lcd/tft_io/st7789v.h
@@ -0,0 +1,156 @@
+/**
+ * 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 .
+ *
+ */
+#pragma once
+
+#include "tft_io.h"
+
+#include "../../inc/MarlinConfig.h"
+
+#define ST7789V_MADCTL_MY 0x80 // Row Address Order
+#define ST7789V_MADCTL_MX 0x40 // Column Address Order
+#define ST7789V_MADCTL_MV 0x20 // Row/Column Exchange
+#define ST7789V_MADCTL_ML 0x10 // Vertical Refresh Order
+#define ST7789V_MADCTL_BGR 0x08 // RGB-BGR ORDER
+#define ST7789V_MADCTL_RGB 0x00
+#define ST7789V_MADCTL_MH 0x04 // Horizontal Refresh Order
+
+#define ST7789V_ORIENTATION_UP ST7789V_MADCTL_MX | ST7789V_MADCTL_MY // 240x320 ; Cable on the upper side
+#define ST7789V_ORIENTATION_RIGHT ST7789V_MADCTL_MX | ST7789V_MADCTL_MV // 320x240 ; Cable on the right side
+#define ST7789V_ORIENTATION_LEFT ST7789V_MADCTL_MY | ST7789V_MADCTL_MV // 320x240 ; Cable on the left side
+#define ST7789V_ORIENTATION_DOWN 0 // 240x320 ; Cable on the lower side
+
+#define ST7789V_ORIENTATION IF_0((TFT_ORIENTATION) & TFT_EXCHANGE_XY, ST7789V_MADCTL_MV) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_X, ST7789V_MADCTL_MX) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_Y, ST7789V_MADCTL_MY)
+
+#if !defined(TFT_COLOR) || TFT_COLOR == TFT_COLOR_RGB
+ #define ST7789V_COLOR ST7789V_MADCTL_RGB
+#elif TFT_COLOR == TFT_COLOR_BGR
+ #define ST7789V_COLOR ST7789V_MADCTL_BGR
+#endif
+
+#define ST7789V_MADCTL_DATA (ST7789V_ORIENTATION) | (ST7789V_COLOR)
+
+#define ST7789V_NOP 0x00 // No Operation
+#define ST7789V_SWRESET 0x01 // Software reset
+#define ST7789V_RDDID 0x04 // Read Display ID
+#define ST7789V_RDDST 0x09 // Read Display Status
+#define ST7789V_RDDPM 0x0A // Read Display Power Mode
+#define ST7789V_RDDMADCTL 0x0B // Read Display MADCTL
+#define ST7789V_RDDCOLMOD 0x0C // Read Display Pixel Format
+#define ST7789V_RDDIM 0x0D // Read Display Image Mode
+#define ST7789V_RDDSM 0x0E // Read Display Signal Mode
+#define ST7789V_RDDSDR 0x0F // Read Display Self-Diagnostic Result
+#define ST7789V_SLPIN 0x10 // Sleep In
+#define ST7789V_SLPOUT 0x11 // Sleep Out
+#define ST7789V_PTLON 0x12 // Partial Display Mode On
+#define ST7789V_NORON 0x13 // Normal Display Mode On
+#define ST7789V_INVOFF 0x20 // Display Inversion Off
+#define ST7789V_INVON 0x21 // Display Inversion On
+#define ST7789V_GAMSET 0x26 // Gamma Set
+#define ST7789V_DISPOFF 0x28 // Display Off
+#define ST7789V_DISPON 0x29 // Display On
+#define ST7789V_CASET 0x2A // Column Address Set
+#define ST7789V_RASET 0x2B // Row Address Set
+#define ST7789V_RAMWR 0x2C // Memory Write
+#define ST7789V_RAMRD 0x2E // Memory Read
+#define ST7789V_PTLAR 0x30 // Partial Area
+#define ST7789V_VSCRDEF 0x33 // Vertical Scrolling Definition
+#define ST7789V_TEOFF 0x34 // Tearing Effect Line OFF
+#define ST7789V_TEON 0x35 // Tearing Effect Line ON
+#define ST7789V_MADCTL 0x36 // Memory Data Access Control
+#define ST7789V_VSCSAD 0x37 // Vertical Scroll Start Address of RAM
+#define ST7789V_IDMOFF 0x38 // Idle Mode Off
+#define ST7789V_IDMON 0x39 // Idle Mode On
+#define ST7789V_COLMOD 0x3A // Interface Pixel Format
+#define ST7789V_WRMEMC 0x3C // Write Memory Continue
+#define ST7789V_RDMEMC 0x3E // Read Memory Continue
+#define ST7789V_STE 0x44 // Set Tear Scanline
+#define ST7789V_GSCAN 0x45 // Get Scanline
+#define ST7789V_WRDISBV 0x51 // Write Display Brightness
+#define ST7789V_RDDISBV 0x52 // Read Display Brightness
+#define ST7789V_WRCTRLD 0x53 // Write CTRL Display
+#define ST7789V_RDCTRLD 0x54 // Read CTRL Value Display
+#define ST7789V_WRCACE 0x55 // Write Content Adaptive Brightness Control and Color Enhancement
+#define ST7789V_RDCABC 0x56 // Read Content Adaptive Brightness Control
+#define ST7789V_WRCABCMB 0x5E // Write CABC Minimum Brightness
+#define ST7789V_RDCABCMB 0x5F // Read CABC Minimum Brightness
+#define ST7789V_RDABCSDR 0x68 // Read Automatic Brightness Control Self-Diagnostic Result
+#define ST7789V_RDID1 0xDA // Read ID1 Value
+#define ST7789V_RDID2 0xDB // Read ID2 Value
+#define ST7789V_RDID3 0xDC // Read ID3 Value
+
+#define ST7789V_RAMCTRL 0xB0 // RAM Control
+#define ST7789V_RGBCTRL 0xB1 // RGB Interface Control
+#define ST7789V_PORCTRL 0xB2 // Porch Setting
+#define ST7789V_FRCTRL1 0xB3 // Frame Rate Control 1 (In partial mode/ idle colors)
+#define ST7789V_GCTRL 0xB7 // Gate Control
+#define ST7789V_DGMEN 0xBA // Digital Gamma Enable
+#define ST7789V_VCOMS 0xBB // VCOM Setting
+#define ST7789V_LCMCTRL 0xC0 // LCM Control
+#define ST7789V_IDSET 0xC1 // ID Code Setting
+#define ST7789V_VDVVRHEN 0xC2 // VDV and VRH Command Enable
+#define ST7789V_VRHS 0xC3 // VRH Set
+#define ST7789V_VDVS 0xC4 // VDV Set
+#define ST7789V_VCMOFSET 0xC5 // VCOM Offset Set
+#define ST7789V_FRCTRL2 0xC6 // Frame Rate Control in Normal Mode
+#define ST7789V_CABCCTRL 0xC7 // CABC Control
+#define ST7789V_REGSEL1 0xC8 // Register Value Selection 1
+#define ST7789V_REGSEL2 0xCA // Register Value Selection 2
+#define ST7789V_PWMFRSEL 0xCC // PWM Frequency Selection
+#define ST7789V_PWCTRL1 0xD0 // Power Control 1
+#define ST7789V_VAPVANEN 0xD2 // Enable VAP/VAN signal output
+#define ST7789V_CMD2EN 0xDF // Command 2 Enable
+#define ST7789V_PVGAMCTRL 0xE0 // Positive Voltage Gamma Control
+#define ST7789V_NVGAMCTRL 0xE1 // Negative Voltage Gamma Control
+#define ST7789V_DGMLUTR 0xE2 // Digital Gamma Look-up Table for Red
+#define ST7789V_DGMLUTB 0xE3 // Digital Gamma Look-up Table for Blue
+#define ST7789V_GATECTRL 0xE4 // Gate Control
+#define ST7789V_SPI2EN 0xE7 // SPI2 Enable
+#define ST7789V_PWCTRL2 0xE8 // Power Control 2
+#define ST7789V_EQCTRL 0xE9 // Equalize time control
+#define ST7789V_PROMCTRL 0xEC // Program Mode Control
+#define ST7789V_PROMEN 0xFA // Program Mode Enable
+#define ST7789V_NVMSET 0xFC // NVM Setting
+#define ST7789V_PROMACT 0xFE // Program action
+
+static const uint16_t st7789v_init[] = {
+ DATASIZE_8BIT,
+ ESC_REG(ST7789V_SWRESET), ESC_DELAY(100),
+ ESC_REG(ST7789V_SLPOUT), ESC_DELAY(20),
+
+ ESC_REG(ST7789V_PORCTRL), 0x000C, 0x000C, 0x0000, 0x0033, 0x0033,
+ ESC_REG(ST7789V_GCTRL), 0x0035,
+ ESC_REG(ST7789V_VCOMS), 0x001F,
+ ESC_REG(ST7789V_LCMCTRL), 0x002C,
+ ESC_REG(ST7789V_VDVVRHEN), 0x0001, 0x00C3,
+ ESC_REG(ST7789V_VDVS), 0x0020,
+ ESC_REG(ST7789V_FRCTRL2), 0x000F,
+ ESC_REG(ST7789V_PWCTRL1), 0x00A4, 0x00A1,
+
+ ESC_REG(ST7789V_MADCTL), ST7789V_MADCTL_DATA,
+ ESC_REG(ST7789V_COLMOD), 0x0055,
+
+ ESC_REG(ST7789V_NORON),
+ ESC_REG(ST7789V_DISPON),
+ ESC_END
+};
diff --git a/src/lcd/tft_io/st7796s.h b/src/lcd/tft_io/st7796s.h
new file mode 100644
index 0000000..6d79dd8
--- /dev/null
+++ b/src/lcd/tft_io/st7796s.h
@@ -0,0 +1,157 @@
+/**
+ * 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 .
+ *
+ */
+#pragma once
+
+#include "tft_io.h"
+
+#include "../../inc/MarlinConfig.h"
+
+#define ST7796S_MADCTL_MY 0x80 // Row Address Order
+#define ST7796S_MADCTL_MX 0x40 // Column Address Order
+#define ST7796S_MADCTL_MV 0x20 // Row/Column Exchange
+#define ST7796S_MADCTL_ML 0x10 // Vertical Refresh Order
+#define ST7796S_MADCTL_BGR 0x08 // RGB-BGR ORDER
+#define ST7796S_MADCTL_RGB 0x00
+#define ST7796S_MADCTL_MH 0x04 // Horizontal Refresh Order
+
+#define ST7796S_ORIENTATION IF_0((TFT_ORIENTATION) & TFT_EXCHANGE_XY, ST7796S_MADCTL_MV) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_X, ST7796S_MADCTL_MX) | \
+ IF_0((TFT_ORIENTATION) & TFT_INVERT_Y, ST7796S_MADCTL_MY)
+
+#if !defined(TFT_COLOR) || TFT_COLOR == TFT_COLOR_BGR
+ #define ST7796S_COLOR ST7796S_MADCTL_BGR
+#elif TFT_COLOR == TFT_COLOR_RGB
+ #define ST7796S_COLOR ST7796S_MADCTL_RGB
+#endif
+
+#define ST7796S_MADCTL_DATA (ST7796S_ORIENTATION) | (ST7796S_COLOR)
+
+#define ST7796S_NOP 0x00 // No Operation
+#define ST7796S_SWRESET 0x01 // Software reset
+#define ST7796S_RDDID 0x04 // Read Display ID
+#define ST7796S_RDNUMED 0x05 // Read Number of the Errors on DSI
+#define ST7796S_RDDST 0x09 // Read Display Status
+#define ST7796S_RDDPM 0x0A // Read Display Power Mode
+#define ST7796S_RDDMADCTL 0x0B // Read Display MADCTL
+#define ST7796S_RDDCOLMOD 0x0C // Read Display Pixel Format
+#define ST7796S_RDDIM 0x0D // Read Display Image Mode
+#define ST7796S_RDDSM 0x0E // Read Display Signal Status
+#define ST7796S_RDDSDR 0x0F // Read Display Self-Diagnostic Result
+#define ST7796S_SLPIN 0x10 // Sleep In
+#define ST7796S_SLPOUT 0x11 // Sleep Out
+#define ST7796S_PTLON 0x12 // Partial Display Mode On
+#define ST7796S_NORON 0x13 // Normal Display Mode On
+#define ST7796S_INVOFF 0x20 // Display Inversion Off
+#define ST7796S_INVON 0x21 // Display Inversion On
+#define ST7796S_DISPOFF 0x28 // Display Off
+#define ST7796S_DISPON 0x29 // Display On
+#define ST7796S_CASET 0x2A // Column Address Set
+#define ST7796S_RASET 0x2B // Row Address Set
+#define ST7796S_RAMWR 0x2C // Memory Write
+#define ST7796S_RAMRD 0x2E // Memory Read
+#define ST7796S_PTLAR 0x30 // Partial Area
+#define ST7796S_VSCRDEF 0x33 // Vertical Scrolling Definition
+#define ST7796S_TEOFF 0x34 // Tearing Effect Line OFF
+#define ST7796S_TEON 0x35 // Tearing Effect Line On
+#define ST7796S_MADCTL 0x36 // Memory Data Access Control
+#define ST7796S_VSCSAD 0x37 // Vertical Scroll Start Address of RAM
+#define ST7796S_IDMOFF 0x38 // Idle Mode Off
+#define ST7796S_IDMON 0x39 // Idle Mode On
+#define ST7796S_COLMOD 0x3A // Interface Pixel Format
+#define ST7796S_WRMEMC 0x3C // Write Memory Continue
+#define ST7796S_RDMEMC 0x3E // Read Memory Continue
+#define ST7796S_STE 0x44 // Set Tear ScanLine
+#define ST7796S_GSCAN 0x45 // Get ScanLine
+#define ST7796S_WRDISBV 0x51 // Write Display Brightness
+#define ST7796S_RDDISBV 0x52 // Read Display Brightness Value
+#define ST7796S_WRCTRLD 0x53 // Write CTRL Display
+#define ST7796S_RDCTRLD 0x54 // Read CTRL value Display
+#define ST7796S_WRCABC 0x55 // Write Adaptive Brightness Control
+#define ST7796S_RDCABC 0x56 // Read Content Adaptive Brightness Control
+#define ST7796S_WRCABCMB 0x5E // Write CABC Minimum Brightness
+#define ST7796S_RDCABCMB 0x5F // Read CABC Minimum Brightness
+#define ST7796S_RDFCS 0xAA // Read First Checksum
+#define ST7796S_RDCFCS 0xAF // Read Continue Checksum
+#define ST7796S_RDID1 0xDA // Read ID1
+#define ST7796S_RDID2 0xDB // Read ID2
+#define ST7796S_RDID3 0xDC // Read ID3
+
+#define ST7796S_IFMODE 0xB0 // Interface Mode Control
+#define ST7796S_FRMCTR1 0xB1 // Frame Rate Control (In Normal Mode/Full Colors)
+#define ST7796S_FRMCTR2 0xB2 // Frame Rate Control 2 (In Idle Mode/8 colors)
+#define ST7796S_FRMCTR3 0xB3 // Frame Rate Control 3(In Partial Mode/Full Colors)
+#define ST7796S_DIC 0xB4 // Display Inversion Control
+#define ST7796S_BPC 0xB5 // Blanking Porch Control
+#define ST7796S_DFC 0xB6 // Display Function Control
+#define ST7796S_EM 0xB7 // Entry Mode Set
+#define ST7796S_PWR1 0xC0 // Power Control 1
+#define ST7796S_PWR2 0xC1 // Power Control 2
+#define ST7796S_PWR3 0xC2 // Power Control 3
+#define ST7796S_VCMPCTL 0xC5 // VCOM Control
+#define ST7796S_VCMOST 0xC6 // VCOM Offset Register
+#define ST7796S_NVMADW 0xD0 // NVM Address/Data Write
+#define ST7796S_NVMBPROG 0xD1 // NVM Byte Program
+#define ST7796S_NVMSTRD 0xD2 // NVM Status Read
+#define ST7796S_RDID4 0xD3 // Read ID4
+#define ST7796S_PGC 0xE0 // Positive Gamma Control
+#define ST7796S_NGC 0xE1 // Negative Gamma Control
+#define ST7796S_DGC1 0xE2 // Digital Gamma Control 1
+#define ST7796S_DGC2 0xE3 // Digital Gamma Control 2
+#define ST7796S_DOCA 0xE8 // Display Output Ctrl Adjust
+#define ST7796S_CSCON 0xF0 // Command Set Control
+#define ST7796S_SPIRC 0xFB // SPI Read Control
+
+static const uint16_t st7796s_init[] = {
+ DATASIZE_8BIT,
+ ESC_REG(ST7796S_SWRESET), ESC_DELAY(100),
+ ESC_REG(ST7796S_SLPOUT), ESC_DELAY(20),
+
+ ESC_REG(ST7796S_CSCON), 0x00C3, // enable command 2 part I
+ ESC_REG(ST7796S_CSCON), 0x0096, // enable command 2 part II
+
+ ESC_REG(ST7796S_MADCTL), ST7796S_MADCTL_DATA,
+ ESC_REG(ST7796S_COLMOD), 0x0055,
+
+ ESC_REG(ST7796S_DIC), 0x0001, // 1-dot inversion
+ ESC_REG(ST7796S_EM), 0x00C6,
+
+ ESC_REG(ST7796S_PWR2), 0x0015,
+ ESC_REG(ST7796S_PWR3), 0x00AF,
+ ESC_REG(ST7796S_VCMPCTL), 0x0022,
+ ESC_REG(ST7796S_VCMOST), 0x0000,
+ ESC_REG(ST7796S_DOCA), 0x0040, 0x008A, 0x0000, 0x0000, 0x0029, 0x0019, 0x00A5, 0x0033,
+
+ /* Gamma Correction. */
+ ESC_REG(ST7796S_PGC), 0x00F0, 0x0004, 0x0008, 0x0009, 0x0008, 0x0015, 0x002F, 0x0042, 0x0046, 0x0028, 0x0015, 0x0016, 0x0029, 0x002D,
+ ESC_REG(ST7796S_NGC), 0x00F0, 0x0004, 0x0009, 0x0009, 0x0008, 0x0015, 0x002E, 0x0046, 0x0046, 0x0028, 0x0015, 0x0015, 0x0029, 0x002D,
+
+ #if ENABLED(ST7796S_INVERTED)
+ ESC_REG(ST7796S_INVON), // Display inversion ON
+ #else
+ ESC_REG(ST7796S_NORON),
+ #endif
+ ESC_REG(ST7796S_WRCTRLD), 0x0024,
+ ESC_REG(ST7796S_CSCON), 0x003C, // disable command 2 part I
+ ESC_REG(ST7796S_CSCON), 0x0069, // disable command 2 part II
+ ESC_REG(ST7796S_DISPON),
+ ESC_END
+};
diff --git a/src/lcd/tft_io/tft_ids.h b/src/lcd/tft_io/tft_ids.h
new file mode 100644
index 0000000..2de1113
--- /dev/null
+++ b/src/lcd/tft_io/tft_ids.h
@@ -0,0 +1,34 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2021 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 .
+ *
+ */
+#pragma once
+
+#define LTDC_RGB 0xABAB
+#define SSD1963 0x5761
+#define ST7735 0x89F0
+#define ST7789 0x8552
+#define ST7796 0x7796
+#define R61505 0x1505
+#define ILI9328 0x9328
+#define ILI9341 0x9341
+#define ILI9488 0x9488
+#define ILI9488_ID1 0x8066 // Some ILI9488 have 0x8066 in the 0x04
+#define AUTO 0xFFFF
diff --git a/src/lcd/tft_io/tft_io.cpp b/src/lcd/tft_io/tft_io.cpp
new file mode 100644
index 0000000..acb78c3
--- /dev/null
+++ b/src/lcd/tft_io/tft_io.cpp
@@ -0,0 +1,258 @@
+/**
+ * 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 .
+ *
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_SPI_TFT || HAS_FSMC_TFT || HAS_LTDC_TFT
+
+#include "tft_io.h"
+#include "tft_ids.h"
+
+#if TFT_DRIVER == ST7735 || TFT_DRIVER == AUTO
+ #include "st7735.h"
+#endif
+#if TFT_DRIVER == ST7789 || TFT_DRIVER == AUTO
+ #include "st7789v.h"
+#endif
+#if TFT_DRIVER == ST7796 || TFT_DRIVER == AUTO
+ #include "st7796s.h"
+#endif
+#if TFT_DRIVER == R61505 || TFT_DRIVER == AUTO
+ #include "r65105.h"
+#endif
+#if TFT_DRIVER == ILI9488 || TFT_DRIVER == ILI9488_ID1 || TFT_DRIVER == AUTO
+ #include "ili9488.h"
+#endif
+#if TFT_DRIVER == SSD1963 || TFT_DRIVER == AUTO
+ #include "ssd1963.h"
+#endif
+
+#include "ili9341.h"
+#include "ili9328.h"
+
+#if HAS_LCD_BRIGHTNESS
+ #include "../marlinui.h"
+#endif
+
+#define DEBUG_OUT ENABLED(DEBUG_GRAPHICAL_TFT)
+#include "../../core/debug_out.h"
+
+TFT_IO_DRIVER TFT_IO::io;
+uint32_t TFT_IO::lcd_id = 0xFFFFFFFF;
+
+void TFT_IO::InitTFT() {
+if (lcd_id != 0xFFFFFFFF) return;
+
+ #if PIN_EXISTS(TFT_BACKLIGHT)
+ OUT_WRITE(TFT_BACKLIGHT_PIN, LOW);
+ #endif
+
+ #if PIN_EXISTS(TFT_RESET)
+ OUT_WRITE(TFT_RESET_PIN, HIGH);
+ delay(10);
+ WRITE(TFT_RESET_PIN, LOW);
+ delay(10);
+ WRITE(TFT_RESET_PIN, HIGH);
+ #endif
+
+ #if PIN_EXISTS(TFT_BACKLIGHT)
+ WRITE(TFT_BACKLIGHT_PIN, DISABLED(DELAYED_BACKLIGHT_INIT));
+ #if HAS_LCD_BRIGHTNESS && DISABLED(DELAYED_BACKLIGHT_INIT)
+ ui._set_brightness();
+ #endif
+ #endif
+
+ // io.Init();
+ delay(100);
+
+ #if TFT_DRIVER != AUTO
+ lcd_id = TFT_DRIVER;
+ #endif
+
+ #if TFT_DRIVER == ST7735
+ write_esc_sequence(st7735_init);
+ #elif TFT_DRIVER == SSD1963
+ write_esc_sequence(ssd1963_init);
+ #elif TFT_DRIVER == ST7789
+ write_esc_sequence(st7789v_init);
+ #elif TFT_DRIVER == ST7796
+ write_esc_sequence(st7796s_init);
+ #elif TFT_DRIVER == R61505
+ write_esc_sequence(r61505_init);
+ #elif TFT_DRIVER == ILI9328
+ write_esc_sequence(ili9328_init);
+ #elif TFT_DRIVER == ILI9341
+ write_esc_sequence(ili9341_init);
+ #elif TFT_DRIVER == ILI9488
+ write_esc_sequence(ili9488_init);
+ #elif TFT_DRIVER == AUTO // autodetect
+
+ lcd_id = io.GetID() & 0xFFFF;
+
+ switch (lcd_id) {
+ case LTDC_RGB:
+ break;
+ case ST7796: // ST7796S 480x320
+ DEBUG_ECHO_MSG(" ST7796S");
+ write_esc_sequence(st7796s_init);
+ break;
+ case ST7789: // ST7789V 320x240
+ DEBUG_ECHO_MSG(" ST7789V");
+ write_esc_sequence(st7789v_init);
+ break;
+ case SSD1963: // SSD1963
+ DEBUG_ECHO_MSG(" SSD1963");
+ write_esc_sequence(ssd1963_init);
+ break;
+ case ST7735: // ST7735 160x128
+ DEBUG_ECHO_MSG(" ST7735");
+ write_esc_sequence(st7735_init);
+ break;
+ case R61505: // R61505U 320x240
+ DEBUG_ECHO_MSG(" R61505U");
+ write_esc_sequence(r61505_init);
+ break;
+ case ILI9328: // ILI9328 320x240
+ DEBUG_ECHO_MSG(" ILI9328");
+ write_esc_sequence(ili9328_init);
+ break;
+ case ILI9341: // ILI9341 320x240
+ DEBUG_ECHO_MSG(" ILI9341");
+ write_esc_sequence(ili9341_init);
+ break;
+ case ILI9488: // ILI9488 480x320
+ case ILI9488_ID1: // 0x8066 ILI9488 480x320
+ DEBUG_ECHO_MSG(" ILI9488");
+ write_esc_sequence(ili9488_init);
+ break;
+ default:
+ lcd_id = 0;
+ }
+ #else
+ #error "Unsupported TFT driver"
+ #endif
+
+ #if PIN_EXISTS(TFT_BACKLIGHT) && ENABLED(DELAYED_BACKLIGHT_INIT)
+ TERN(HAS_LCD_BRIGHTNESS, ui._set_brightness(), WRITE(TFT_BACKLIGHT_PIN, HIGH));
+ #endif
+}
+
+void TFT_IO::set_window(uint16_t Xmin, uint16_t Ymin, uint16_t Xmax, uint16_t Ymax) {
+ #ifdef OFFSET_X
+ Xmin += OFFSET_X; Xmax += OFFSET_X;
+ #endif
+ #ifdef OFFSET_Y
+ Ymin += OFFSET_Y; Ymax += OFFSET_Y;
+ #endif
+
+ switch (lcd_id) {
+ case LTDC_RGB:
+ io.WriteReg(0x01);
+ io.WriteData(Xmin);
+ io.WriteReg(0x02);
+ io.WriteData(Xmax);
+ io.WriteReg(0x03);
+ io.WriteData(Ymin);
+ io.WriteReg(0x04);
+ io.WriteData(Ymax);
+ io.WriteReg(0x00);
+ break;
+ case ST7735: // ST7735 160x128
+ case ST7789: // ST7789V 320x240
+ case ST7796: // ST7796 480x320
+ case ILI9341: // ILI9341 320x240
+ case ILI9488: // ILI9488 480x320
+ case SSD1963: // SSD1963
+ case ILI9488_ID1: // 0x8066 ILI9488 480x320
+ io.DataTransferBegin(DATASIZE_8BIT);
+
+ // CASET: Column Address Set
+ io.WriteReg(ILI9341_CASET);
+ io.WriteData((Xmin >> 8) & 0xFF);
+ io.WriteData(Xmin & 0xFF);
+ io.WriteData((Xmax >> 8) & 0xFF);
+ io.WriteData(Xmax & 0xFF);
+
+ // RASET: Row Address Set
+ io.WriteReg(ILI9341_PASET);
+ io.WriteData((Ymin >> 8) & 0xFF);
+ io.WriteData(Ymin & 0xFF);
+ io.WriteData((Ymax >> 8) & 0xFF);
+ io.WriteData(Ymax & 0xFF);
+
+ // RAMWR: Memory Write
+ io.WriteReg(ILI9341_RAMWR);
+ break;
+ case R61505: // R61505U 320x240
+ case ILI9328: // ILI9328 320x240
+ io.DataTransferBegin(DATASIZE_16BIT);
+
+ // Mind the mess: with landscape screen orientation 'Horizontal' is Y and 'Vertical' is X
+ io.WriteReg(ILI9328_HASTART);
+ io.WriteData(Ymin);
+ io.WriteReg(ILI9328_HAEND);
+ io.WriteData(Ymax);
+ io.WriteReg(ILI9328_VASTART);
+ io.WriteData(Xmin);
+ io.WriteReg(ILI9328_VAEND);
+ io.WriteData(Xmax);
+
+ io.WriteReg(ILI9328_HASET);
+ io.WriteData(Ymin);
+ io.WriteReg(ILI9328_VASET);
+ io.WriteData(Xmin);
+
+ io.WriteReg(ILI9328_RAMWR);
+ break;
+ default:
+ break;
+ }
+
+ io.DataTransferEnd();
+}
+
+void TFT_IO::write_esc_sequence(const uint16_t *Sequence) {
+ uint16_t dataWidth, data;
+
+ dataWidth = *Sequence++;
+ io.DataTransferBegin(dataWidth);
+
+ for (;;) {
+ data = *Sequence++;
+ if (data != 0xFFFF) {
+ io.WriteData(data);
+ continue;
+ }
+ data = *Sequence++;
+ if (data == 0x7FFF) return;
+ if (data == 0xFFFF)
+ io.WriteData(0xFFFF);
+ else if (data & 0x8000)
+ delay(data & 0x7FFF);
+ else if ((data & 0xFF00) == 0)
+ io.WriteReg(data);
+ }
+
+ io.DataTransferEnd();
+}
+
+#endif // HAS_SPI_TFT || HAS_FSMC_TFT || HAS_LTDC_TFT
diff --git a/src/lcd/tft_io/tft_io.h b/src/lcd/tft_io/tft_io.h
new file mode 100644
index 0000000..50b921c
--- /dev/null
+++ b/src/lcd/tft_io/tft_io.h
@@ -0,0 +1,134 @@
+/**
+ * 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 .
+ *
+ */
+#pragma once
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_SPI_TFT
+ #include HAL_PATH(../../HAL, tft/tft_spi.h)
+#elif HAS_FSMC_TFT
+ #include HAL_PATH(../../HAL, tft/tft_fsmc.h)
+#elif HAS_LTDC_TFT
+ #include HAL_PATH(../../HAL, tft/tft_ltdc.h)
+#else
+ #error "TFT IO only supports SPI, FSMC or LTDC interface"
+#endif
+
+#define TFT_EXCHANGE_XY _BV32(1)
+#define TFT_INVERT_X _BV32(2)
+#define TFT_INVERT_Y _BV32(3)
+
+#define TFT_NO_ROTATION (0x00)
+#define TFT_ROTATE_90 (TFT_EXCHANGE_XY | TFT_INVERT_X)
+#define TFT_ROTATE_180 (TFT_INVERT_X | TFT_INVERT_Y)
+#define TFT_ROTATE_270 (TFT_EXCHANGE_XY | TFT_INVERT_Y)
+
+#define TFT_MIRROR_X (TFT_INVERT_Y)
+#define TFT_MIRROR_Y (TFT_INVERT_X)
+
+#define TFT_ROTATE_90_MIRROR_X (TFT_ROTATE_90 ^ TFT_INVERT_Y)
+#define TFT_ROTATE_90_MIRROR_Y (TFT_ROTATE_90 ^ TFT_INVERT_X)
+
+#define TFT_ROTATE_180_MIRROR_X (TFT_ROTATE_180 ^ TFT_INVERT_Y)
+#define TFT_ROTATE_180_MIRROR_Y (TFT_ROTATE_180 ^ TFT_INVERT_X)
+
+#define TFT_ROTATE_270_MIRROR_X (TFT_ROTATE_270 ^ TFT_INVERT_Y)
+#define TFT_ROTATE_270_MIRROR_Y (TFT_ROTATE_270 ^ TFT_INVERT_X)
+
+// TFT_ROTATION is user configurable
+#ifndef TFT_ROTATION
+ #define TFT_ROTATION TFT_NO_ROTATION
+#endif
+
+// TFT_ORIENTATION is the "sum" of TFT_DEFAULT_ORIENTATION plus user TFT_ROTATION
+#define TFT_ORIENTATION ((TFT_DEFAULT_ORIENTATION) ^ (TFT_ROTATION))
+
+#define TFT_COLOR_RGB _BV32(3)
+#define TFT_COLOR_BGR _BV32(4)
+
+// Each TFT Driver is responsible for its default color mode.
+// #ifndef TFT_COLOR
+// #define TFT_COLOR TFT_COLOR_RGB
+// #endif
+
+#define TOUCH_ORIENTATION_NONE 0
+#define TOUCH_LANDSCAPE 1
+#define TOUCH_PORTRAIT 2
+
+#ifndef TOUCH_CALIBRATION_X
+ #define TOUCH_CALIBRATION_X 0
+#endif
+#ifndef TOUCH_CALIBRATION_Y
+ #define TOUCH_CALIBRATION_Y 0
+#endif
+#ifndef TOUCH_OFFSET_X
+ #define TOUCH_OFFSET_X 0
+#endif
+#ifndef TOUCH_OFFSET_Y
+ #define TOUCH_OFFSET_Y 0
+#endif
+#ifndef TOUCH_ORIENTATION
+ #define TOUCH_ORIENTATION TOUCH_LANDSCAPE
+#endif
+
+#ifndef TFT_DRIVER
+ #define TFT_DRIVER AUTO
+#endif
+
+#define ESC_REG(x) 0xFFFF, 0x00FF & (uint16_t)x
+#define ESC_DELAY(x) 0xFFFF, 0x8000 | (x & 0x7FFF)
+#define ESC_END 0xFFFF, 0x7FFF
+#define ESC_FFFF 0xFFFF, 0xFFFF
+
+class TFT_IO {
+public:
+ static TFT_IO_DRIVER io;
+
+ static void InitTFT();
+ static void set_window(uint16_t Xmin, uint16_t Ymin, uint16_t Xmax, uint16_t Ymax);
+ static void write_esc_sequence(const uint16_t *Sequence);
+
+ // Deletaged methods
+ inline static void Init() { io.Init(); io.Abort(); };
+ inline static bool isBusy() { return io.isBusy(); };
+ inline static void Abort() { io.Abort(); };
+ inline static uint32_t GetID() { return io.GetID(); };
+
+ inline static void DataTransferBegin(uint16_t DataWidth = DATASIZE_16BIT) { io.DataTransferBegin(DataWidth); }
+ inline static void DataTransferEnd() { io.DataTransferEnd(); };
+ // inline static void DataTransferAbort() { io.DataTransferAbort(); };
+
+ inline static void WriteData(uint16_t Data) { io.WriteData(Data); };
+ inline static void WriteReg(uint16_t Reg) { io.WriteReg(Reg); };
+
+ inline static void WriteSequence(uint16_t *Data, uint16_t Count) { io.WriteSequence(Data, Count); };
+
+ #if ENABLED(USE_SPI_DMA_TC)
+ inline static void WriteSequenceIT(uint16_t *Data, uint16_t Count) { io.WriteSequenceIT(Data, Count); };
+ #endif
+
+ // static void WriteMultiple(uint16_t Color, uint16_t Count) { static uint16_t Data; Data = Color; TransmitDMA(DMA_MINC_DISABLE, &Data, Count); }
+ inline static void WriteMultiple(uint16_t Color, uint32_t Count) { io.WriteMultiple(Color, Count); };
+
+protected:
+ static uint32_t lcd_id;
+};
diff --git a/src/lcd/tft_io/touch_calibration.cpp b/src/lcd/tft_io/touch_calibration.cpp
new file mode 100644
index 0000000..44ebc73
--- /dev/null
+++ b/src/lcd/tft_io/touch_calibration.cpp
@@ -0,0 +1,115 @@
+/**
+ * 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 .
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(TOUCH_SCREEN_CALIBRATION)
+
+#include "touch_calibration.h"
+
+#define TOUCH_CALIBRATION_MAX_RETRIES 5
+
+#define DEBUG_OUT ENABLED(DEBUG_TOUCH_CALIBRATION)
+#include "../../core/debug_out.h"
+
+#if ENABLED(TOUCH_CALIBRATION_AUTO_SAVE)
+ #include "../../module/settings.h"
+#endif
+
+TouchCalibration touch_calibration;
+
+touch_calibration_t TouchCalibration::calibration;
+calibrationState TouchCalibration::calibration_state = CALIBRATION_NONE;
+touch_calibration_point_t TouchCalibration::calibration_points[4];
+uint8_t TouchCalibration::failed_count;
+
+void TouchCalibration::validate_calibration() {
+ #define VALIDATE_PRECISION(XY, A, B) validate_precision_##XY(CALIBRATION_##A, CALIBRATION_##B)
+ const bool landscape = VALIDATE_PRECISION(x, TOP_LEFT, BOTTOM_LEFT)
+ && VALIDATE_PRECISION(x, TOP_RIGHT, BOTTOM_RIGHT)
+ && VALIDATE_PRECISION(y, TOP_LEFT, TOP_RIGHT)
+ && VALIDATE_PRECISION(y, BOTTOM_LEFT, BOTTOM_RIGHT);
+ const bool portrait = VALIDATE_PRECISION(y, TOP_LEFT, BOTTOM_LEFT)
+ && VALIDATE_PRECISION(y, TOP_RIGHT, BOTTOM_RIGHT)
+ && VALIDATE_PRECISION(x, TOP_LEFT, TOP_RIGHT)
+ && VALIDATE_PRECISION(x, BOTTOM_LEFT, BOTTOM_RIGHT);
+ #undef VALIDATE_PRECISION
+
+ #define CAL_PTS(N) calibration_points[CALIBRATION_##N]
+ if (landscape) {
+ calibration_state = CALIBRATION_SUCCESS;
+ calibration.x = ((CAL_PTS(TOP_RIGHT).x - CAL_PTS(TOP_LEFT).x) << 17) / (CAL_PTS(BOTTOM_RIGHT).raw_x + CAL_PTS(TOP_RIGHT).raw_x - CAL_PTS(BOTTOM_LEFT).raw_x - CAL_PTS(TOP_LEFT).raw_x);
+ calibration.y = ((CAL_PTS(BOTTOM_LEFT).y - CAL_PTS(TOP_LEFT).y) << 17) / (CAL_PTS(BOTTOM_RIGHT).raw_y - CAL_PTS(TOP_RIGHT).raw_y + CAL_PTS(BOTTOM_LEFT).raw_y - CAL_PTS(TOP_LEFT).raw_y);
+ calibration.offset_x = CAL_PTS(TOP_LEFT).x - int16_t(((CAL_PTS(TOP_LEFT).raw_x + CAL_PTS(BOTTOM_LEFT).raw_x) * calibration.x) >> 17);
+ calibration.offset_y = CAL_PTS(TOP_LEFT).y - int16_t(((CAL_PTS(TOP_LEFT).raw_y + CAL_PTS(TOP_RIGHT).raw_y) * calibration.y) >> 17);
+ calibration.orientation = TOUCH_LANDSCAPE;
+ }
+ else if (portrait) {
+ calibration_state = CALIBRATION_SUCCESS;
+ calibration.x = ((CAL_PTS(TOP_RIGHT).x - CAL_PTS(TOP_LEFT).x) << 17) / (CAL_PTS(BOTTOM_RIGHT).raw_y + CAL_PTS(TOP_RIGHT).raw_y - CAL_PTS(BOTTOM_LEFT).raw_y - CAL_PTS(TOP_LEFT).raw_y);
+ calibration.y = ((CAL_PTS(BOTTOM_LEFT).y - CAL_PTS(TOP_LEFT).y) << 17) / (CAL_PTS(BOTTOM_RIGHT).raw_x - CAL_PTS(TOP_RIGHT).raw_x + CAL_PTS(BOTTOM_LEFT).raw_x - CAL_PTS(TOP_LEFT).raw_x);
+ calibration.offset_x = CAL_PTS(TOP_LEFT).x - int16_t(((CAL_PTS(TOP_LEFT).raw_y + CAL_PTS(BOTTOM_LEFT).raw_y) * calibration.x) >> 17);
+ calibration.offset_y = CAL_PTS(TOP_LEFT).y - int16_t(((CAL_PTS(TOP_LEFT).raw_x + CAL_PTS(TOP_RIGHT).raw_x) * calibration.y) >> 17);
+ calibration.orientation = TOUCH_PORTRAIT;
+ }
+ else {
+ calibration_state = CALIBRATION_FAIL;
+ calibration_reset();
+ if (need_calibration() && failed_count++ < TOUCH_CALIBRATION_MAX_RETRIES) calibration_state = CALIBRATION_TOP_LEFT;
+ }
+ #undef CAL_PTS
+
+ if (calibration_state == CALIBRATION_SUCCESS) {
+ SERIAL_ECHOLNPGM("Touch screen calibration completed");
+ SERIAL_ECHOLNPGM("TOUCH_CALIBRATION_X ", calibration.x);
+ SERIAL_ECHOLNPGM("TOUCH_CALIBRATION_Y ", calibration.y);
+ SERIAL_ECHOLNPGM("TOUCH_OFFSET_X ", calibration.offset_x);
+ SERIAL_ECHOLNPGM("TOUCH_OFFSET_Y ", calibration.offset_y);
+ SERIAL_ECHO_TERNARY(calibration.orientation == TOUCH_LANDSCAPE, "TOUCH_ORIENTATION ", "TOUCH_LANDSCAPE", "TOUCH_PORTRAIT", "\n");
+ TERN_(TOUCH_CALIBRATION_AUTO_SAVE, settings.save());
+ }
+}
+
+bool TouchCalibration::handleTouch(uint16_t x, uint16_t y) {
+ static millis_t next_button_update_ms = 0;
+ const millis_t now = millis();
+ if (PENDING(now, next_button_update_ms)) return false;
+ next_button_update_ms = now + BUTTON_DELAY_MENU;
+
+ if (calibration_state < CALIBRATION_SUCCESS) {
+ calibration_points[calibration_state].raw_x = x;
+ calibration_points[calibration_state].raw_y = y;
+ DEBUG_ECHOLNPGM("TouchCalibration - State: ", calibration_state, ", x: ", calibration_points[calibration_state].x, ", raw_x: ", x, ", y: ", calibration_points[calibration_state].y, ", raw_y: ", y);
+ }
+
+ switch (calibration_state) {
+ case CALIBRATION_TOP_LEFT: calibration_state = CALIBRATION_BOTTOM_LEFT; break;
+ case CALIBRATION_BOTTOM_LEFT: calibration_state = CALIBRATION_TOP_RIGHT; break;
+ case CALIBRATION_TOP_RIGHT: calibration_state = CALIBRATION_BOTTOM_RIGHT; break;
+ case CALIBRATION_BOTTOM_RIGHT: validate_calibration(); break;
+ default: break;
+ }
+
+ return true;
+}
+
+#endif // TOUCH_SCREEN_CALIBRATION
diff --git a/src/lcd/tft_io/touch_calibration.h b/src/lcd/tft_io/touch_calibration.h
new file mode 100644
index 0000000..abd5667
--- /dev/null
+++ b/src/lcd/tft_io/touch_calibration.h
@@ -0,0 +1,95 @@
+/**
+ * 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 .
+ *
+ */
+#pragma once
+
+#include "../../inc/MarlinConfigPre.h"
+#include "tft_io.h"
+
+#ifndef TOUCH_SCREEN_CALIBRATION_PRECISION
+ #define TOUCH_SCREEN_CALIBRATION_PRECISION 80
+#endif
+
+#ifndef TOUCH_SCREEN_HOLD_TO_CALIBRATE_MS
+ #define TOUCH_SCREEN_HOLD_TO_CALIBRATE_MS 2500
+#endif
+
+typedef struct __attribute__((__packed__)) {
+ int32_t x, y;
+ int16_t offset_x, offset_y;
+ uint8_t orientation;
+} touch_calibration_t;
+
+typedef struct __attribute__((__packed__)) {
+ uint16_t x, y;
+ int16_t raw_x, raw_y;
+} touch_calibration_point_t;
+
+enum calibrationState : uint8_t {
+ CALIBRATION_TOP_LEFT = 0x00,
+ CALIBRATION_BOTTOM_LEFT,
+ CALIBRATION_TOP_RIGHT,
+ CALIBRATION_BOTTOM_RIGHT,
+ CALIBRATION_SUCCESS,
+ CALIBRATION_FAIL,
+ CALIBRATION_NONE,
+};
+
+class TouchCalibration {
+public:
+ static calibrationState calibration_state;
+ static touch_calibration_point_t calibration_points[4];
+
+ static bool validate_precision(int32_t a, int32_t b) { return (a > b ? (100 * b) / a : (100 * a) / b) > TOUCH_SCREEN_CALIBRATION_PRECISION; }
+ static bool validate_precision_x(uint8_t a, uint8_t b) { return validate_precision(calibration_points[a].raw_x, calibration_points[b].raw_x); }
+ static bool validate_precision_y(uint8_t a, uint8_t b) { return validate_precision(calibration_points[a].raw_y, calibration_points[b].raw_y); }
+ static void validate_calibration();
+
+ static touch_calibration_t calibration;
+ static uint8_t failed_count;
+ static void calibration_reset() { calibration = { TOUCH_CALIBRATION_X, TOUCH_CALIBRATION_Y, TOUCH_OFFSET_X, TOUCH_OFFSET_Y, TOUCH_ORIENTATION }; }
+ static bool need_calibration() { return !calibration.offset_x && !calibration.offset_y && !calibration.x && !calibration.y; }
+
+ static calibrationState calibration_start() {
+ calibration = { 0, 0, 0, 0, TOUCH_ORIENTATION_NONE };
+ calibration_state = CALIBRATION_TOP_LEFT;
+ calibration_points[CALIBRATION_TOP_LEFT].x = 30;
+ calibration_points[CALIBRATION_TOP_LEFT].y = 30;
+ calibration_points[CALIBRATION_BOTTOM_LEFT].x = 30;
+ calibration_points[CALIBRATION_BOTTOM_LEFT].y = TFT_HEIGHT - 31;
+ calibration_points[CALIBRATION_TOP_RIGHT].x = TFT_WIDTH - 31;
+ calibration_points[CALIBRATION_TOP_RIGHT].y = 30;
+ calibration_points[CALIBRATION_BOTTOM_RIGHT].x = TFT_WIDTH - 31;
+ calibration_points[CALIBRATION_BOTTOM_RIGHT].y = TFT_HEIGHT - 31;
+ failed_count = 0;
+ return calibration_state;
+ }
+ static void calibration_end() { calibration_state = CALIBRATION_NONE; }
+ static calibrationState get_calibration_state() { return calibration_state; }
+ static bool calibration_loaded() {
+ if (need_calibration()) calibration_reset();
+ return !need_calibration();
+ }
+
+ static bool handleTouch(uint16_t x, uint16_t y);
+};
+
+extern TouchCalibration touch_calibration;
diff --git a/src/lcd/thermistornames.h b/src/lcd/thermistornames.h
new file mode 100644
index 0000000..2571efe
--- /dev/null
+++ b/src/lcd/thermistornames.h
@@ -0,0 +1,154 @@
+/**
+ * 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 .
+ *
+ */
+
+/**
+ * thermistornames.h
+ *
+ * Used by LCD code to obtain a thermistor name
+ *
+ * Usage: Set THERMISTOR_ID then #include this file
+ * to set a new value for THERMISTOR_NAME.
+ */
+
+#undef THERMISTOR_NAME
+
+// User-specified thermistor parameters
+#if THERMISTOR_ID == 1000
+ #define THERMISTOR_NAME "User Parameters"
+
+// Thermcouples
+#elif THERMISTOR_ID == -5
+ #define THERMISTOR_NAME "MAX31865"
+#elif THERMISTOR_ID == -4
+ #define THERMISTOR_NAME "AD8495"
+#elif THERMISTOR_ID == -3
+ #define THERMISTOR_NAME "MAX31855"
+#elif THERMISTOR_ID == -2
+ #define THERMISTOR_NAME "MAX6675"
+#elif THERMISTOR_ID == -1
+ #define THERMISTOR_NAME "AD595"
+
+// Standard thermistors
+#elif THERMISTOR_ID == 1
+ #define THERMISTOR_NAME "EPCOS 100K"
+#elif THERMISTOR_ID == 331
+ #define THERMISTOR_NAME "3.3V EPCOS 100K (MEGA)"
+#elif THERMISTOR_ID == 332
+ #define THERMISTOR_NAME "3.3V EPCOS 100K (DUE)"
+#elif THERMISTOR_ID == 2
+ #define THERMISTOR_NAME "ATC 204GT-2"
+#elif THERMISTOR_ID == 202
+ #define THERMISTOR_NAME "200k Copymaster 3D"
+#elif THERMISTOR_ID == 3
+ #define THERMISTOR_NAME "Mendel-parts"
+#elif THERMISTOR_ID == 4
+ #define THERMISTOR_NAME "Generic 10K"
+#elif THERMISTOR_ID == 5
+ #define THERMISTOR_NAME "ATC 104GT-2"
+#elif THERMISTOR_ID == 501
+ #define THERMISTOR_NAME "Zonestar (Tronxy X3A)"
+#elif THERMISTOR_ID == 502
+ #define THERMISTOR_NAME "Zonestar (P802M Hot Bed)"
+#elif THERMISTOR_ID == 503
+ #define THERMISTOR_NAME "Zonestar (Z8XM2 Bed)"
+#elif THERMISTOR_ID == 504
+ #define THERMISTOR_NAME "Zonestar (P802QR2 Hot End)"
+#elif THERMISTOR_ID == 505
+ #define THERMISTOR_NAME "Zonestar (P802QR2 Bed)"
+#elif THERMISTOR_ID == 512
+ #define THERMISTOR_NAME "RPW-Ultra"
+#elif THERMISTOR_ID == 6
+ #define THERMISTOR_NAME "EPCOS (alt)"
+#elif THERMISTOR_ID == 7
+ #define THERMISTOR_NAME "HW 104LAG"
+#elif THERMISTOR_ID == 71
+ #define THERMISTOR_NAME "HW 104LAF"
+#elif THERMISTOR_ID == 8
+ #define THERMISTOR_NAME "E3104FXT"
+#elif THERMISTOR_ID == 9
+ #define THERMISTOR_NAME "GE AL03006"
+#elif THERMISTOR_ID == 10
+ #define THERMISTOR_NAME "RS 198-961"
+#elif THERMISTOR_ID == 11
+ #define THERMISTOR_NAME "1% beta 3950"
+#elif THERMISTOR_ID == 12
+ #define THERMISTOR_NAME "E3104FXT (alt)"
+#elif THERMISTOR_ID == 13
+ #define THERMISTOR_NAME "Hisens 3950"
+#elif THERMISTOR_ID == 15
+ #define THERMISTOR_NAME "100k JGAurora A5"
+#elif THERMISTOR_ID == 18
+ #define THERMISTOR_NAME "ATC Semitec 204GT-2"
+#elif THERMISTOR_ID == 20
+ #define THERMISTOR_NAME "Pt100 UltiMB 5v"
+#elif THERMISTOR_ID == 21
+ #define THERMISTOR_NAME "Pt100 UltiMB 3.3v"
+#elif THERMISTOR_ID == 201
+ #define THERMISTOR_NAME "Pt100 OverLord"
+#elif THERMISTOR_ID == 60
+ #define THERMISTOR_NAME "Makers Tool"
+#elif THERMISTOR_ID == 70
+ #define THERMISTOR_NAME "Hephestos 2"
+#elif THERMISTOR_ID == 75
+ #define THERMISTOR_NAME "MGB18"
+#elif THERMISTOR_ID == 99
+ #define THERMISTOR_NAME "100k with 10k pull-up"
+
+// Modified thermistors
+#elif THERMISTOR_ID == 30
+ #define THERMISTOR_NAME "Kis3d EN AW NTC100K/3950"
+#elif THERMISTOR_ID == 51
+ #define THERMISTOR_NAME "EPCOS 1K"
+#elif THERMISTOR_ID == 52
+ #define THERMISTOR_NAME "ATC204GT-2 1K"
+#elif THERMISTOR_ID == 55
+ #define THERMISTOR_NAME "ATC104GT-2 1K"
+#elif THERMISTOR_ID == 1047
+ #define THERMISTOR_NAME "PT1000 4K7"
+#elif THERMISTOR_ID == 1010
+ #define THERMISTOR_NAME "PT1000 1K"
+#elif THERMISTOR_ID == 147
+ #define THERMISTOR_NAME "Pt100 4K7"
+#elif THERMISTOR_ID == 110
+ #define THERMISTOR_NAME "Pt100 1K"
+#elif THERMISTOR_ID == 666
+ #define THERMISTOR_NAME "Einstart S"
+#elif THERMISTOR_ID == 2000
+ #define THERMISTOR_NAME "TDK NTCG104LH104JT1"
+
+// High Temperature thermistors
+#elif THERMISTOR_ID == 61
+ #define THERMISTOR_NAME "Formbot 350°C"
+#elif THERMISTOR_ID == 66
+ #define THERMISTOR_NAME "Dyze 4.7M"
+#elif THERMISTOR_ID == 67
+ #define THERMISTOR_NAME "SliceEng 450°C"
+
+// Dummies for dev testing
+#elif THERMISTOR_ID == 998
+ #define THERMISTOR_NAME "Dummy 1"
+#elif THERMISTOR_ID == 999
+ #define THERMISTOR_NAME "Dummy 2"
+#elif THERMISTOR_ID == 1000
+ #define THERMISTOR_NAME "Custom"
+
+#endif // THERMISTOR_ID
diff --git a/src/lcd/touch/touch_buttons.cpp b/src/lcd/touch/touch_buttons.cpp
new file mode 100644
index 0000000..604f366
--- /dev/null
+++ b/src/lcd/touch/touch_buttons.cpp
@@ -0,0 +1,143 @@
+/**
+ * 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 .
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_TOUCH_BUTTONS
+
+#include "touch_buttons.h"
+#include "../scaled_tft.h"
+
+#if ENABLED(TFT_TOUCH_DEVICE_GT911)
+ #include HAL_PATH(../../HAL, tft/gt911.h)
+ GT911 touchIO;
+#elif ENABLED(TFT_TOUCH_DEVICE_XPT2046)
+ #include HAL_PATH(../../HAL, tft/xpt2046.h)
+ XPT2046 touchIO;
+#else
+ #error "Unknown Touch Screen Type."
+#endif
+
+#if ENABLED(TOUCH_SCREEN_CALIBRATION)
+ #include "../tft_io/touch_calibration.h"
+#endif
+
+#if HAS_TOUCH_SLEEP
+ millis_t TouchButtons::next_sleep_ms;
+#endif
+
+#include "../buttons.h" // For EN_C bit mask
+#include "../marlinui.h" // For ui.refresh
+#include "../tft_io/tft_io.h"
+
+#define DOGM_AREA_LEFT TFT_PIXEL_OFFSET_X
+#define DOGM_AREA_TOP TFT_PIXEL_OFFSET_Y
+#define DOGM_AREA_WIDTH (GRAPHICAL_TFT_UPSCALE) * (LCD_PIXEL_WIDTH)
+#define DOGM_AREA_HEIGHT (GRAPHICAL_TFT_UPSCALE) * (LCD_PIXEL_HEIGHT)
+
+#define BUTTON_AREA_TOP BUTTON_Y_LO
+#define BUTTON_AREA_BOT BUTTON_Y_HI
+
+TouchButtons touchBt;
+
+void TouchButtons::init() {
+ touchIO.Init();
+ TERN_(HAS_TOUCH_SLEEP, next_sleep_ms = millis() + SEC_TO_MS(TOUCH_IDLE_SLEEP));
+}
+
+uint8_t TouchButtons::read_buttons() {
+ #ifdef HAS_WIRED_LCD
+ int16_t x, y;
+
+ #if ENABLED(TFT_TOUCH_DEVICE_XPT2046)
+ const bool is_touched = (TERN(TOUCH_SCREEN_CALIBRATION, touch_calibration.calibration.orientation, TOUCH_ORIENTATION) == TOUCH_PORTRAIT ? touchIO.getRawPoint(&y, &x) : touchIO.getRawPoint(&x, &y));
+ #if HAS_TOUCH_SLEEP
+ if (is_touched)
+ wakeUp();
+ else if (!isSleeping() && ELAPSED(millis(), next_sleep_ms) && ui.on_status_screen())
+ sleepTimeout();
+ #endif
+ if (!is_touched) return 0;
+
+ #if ENABLED(TOUCH_SCREEN_CALIBRATION)
+ const calibrationState state = touch_calibration.get_calibration_state();
+ if (WITHIN(state, CALIBRATION_TOP_LEFT, CALIBRATION_BOTTOM_RIGHT)) {
+ if (touch_calibration.handleTouch(x, y)) ui.refresh();
+ return 0;
+ }
+ x = int16_t((int32_t(x) * touch_calibration.calibration.x) >> 16) + touch_calibration.calibration.offset_x;
+ y = int16_t((int32_t(y) * touch_calibration.calibration.y) >> 16) + touch_calibration.calibration.offset_y;
+ #else
+ x = uint16_t((uint32_t(x) * TOUCH_CALIBRATION_X) >> 16) + TOUCH_OFFSET_X;
+ y = uint16_t((uint32_t(y) * TOUCH_CALIBRATION_Y) >> 16) + TOUCH_OFFSET_Y;
+ #endif
+ #elif ENABLED(TFT_TOUCH_DEVICE_GT911)
+ bool is_touched = (TOUCH_ORIENTATION == TOUCH_PORTRAIT ? touchIO.getPoint(&y, &x) : touchIO.getPoint(&x, &y));
+ if (!is_touched) return 0;
+ #endif
+
+ // Touch within the button area simulates an encoder button
+ if (y > BUTTON_AREA_TOP && y < BUTTON_AREA_BOT)
+ return WITHIN(x, BUTTOND_X_LO, BUTTOND_X_HI) ? EN_D
+ : WITHIN(x, BUTTONA_X_LO, BUTTONA_X_HI) ? EN_A
+ : WITHIN(x, BUTTONB_X_LO, BUTTONB_X_HI) ? EN_B
+ : WITHIN(x, BUTTONC_X_LO, BUTTONC_X_HI) ? EN_C
+ : 0;
+
+ if ( !WITHIN(x, DOGM_AREA_LEFT, DOGM_AREA_LEFT + DOGM_AREA_WIDTH)
+ || !WITHIN(y, DOGM_AREA_TOP, DOGM_AREA_TOP + DOGM_AREA_HEIGHT)
+ ) return 0;
+
+ // Column and row above BUTTON_AREA_TOP
+ int8_t col = (x - (DOGM_AREA_LEFT)) * (LCD_WIDTH) / (DOGM_AREA_WIDTH),
+ row = (y - (DOGM_AREA_TOP)) * (LCD_HEIGHT) / (DOGM_AREA_HEIGHT);
+
+ // Send the touch to the UI (which will simulate the encoder wheel)
+ MarlinUI::screen_click(row, col, x, y);
+ #endif
+ return 0;
+}
+
+#if HAS_TOUCH_SLEEP
+
+ void TouchButtons::sleepTimeout() {
+ #if HAS_LCD_BRIGHTNESS
+ ui.set_brightness(0);
+ #elif PIN_EXISTS(TFT_BACKLIGHT)
+ WRITE(TFT_BACKLIGHT_PIN, LOW);
+ #endif
+ next_sleep_ms = TSLP_SLEEPING;
+ }
+ void TouchButtons::wakeUp() {
+ if (isSleeping()) {
+ #if HAS_LCD_BRIGHTNESS
+ ui.set_brightness(ui.brightness);
+ #elif PIN_EXISTS(TFT_BACKLIGHT)
+ WRITE(TFT_BACKLIGHT_PIN, HIGH);
+ #endif
+ }
+ next_sleep_ms = millis() + SEC_TO_MS(TOUCH_IDLE_SLEEP);
+ }
+
+#endif // HAS_TOUCH_SLEEP
+
+#endif // HAS_TOUCH_BUTTONS
diff --git a/src/lcd/touch/touch_buttons.h b/src/lcd/touch/touch_buttons.h
new file mode 100644
index 0000000..39768f2
--- /dev/null
+++ b/src/lcd/touch/touch_buttons.h
@@ -0,0 +1,71 @@
+/**
+ * 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 .
+ *
+ */
+#pragma once
+
+#include
+
+#include "../../inc/MarlinConfig.h"
+#include "../scaled_tft.h"
+
+#define UPSCALE0(M) ((M) * (GRAPHICAL_TFT_UPSCALE))
+#define UPSCALE(A,M) (UPSCALE0(M) + (A))
+
+#define BUTTON_DRAW_WIDTH 32
+#define BUTTON_DRAW_HEIGHT 20
+
+#define BUTTON_WIDTH UPSCALE0(BUTTON_DRAW_WIDTH)
+#define BUTTON_HEIGHT UPSCALE0(BUTTON_DRAW_HEIGHT)
+
+// calc the space between buttons
+#define BUTTON_SPACING (((TFT_WIDTH) - (BUTTON_WIDTH * 4)) / 5)
+
+#define BUTTOND_X_LO BUTTON_SPACING
+#define BUTTOND_X_HI BUTTOND_X_LO + BUTTON_WIDTH - 1
+
+#define BUTTONA_X_LO BUTTOND_X_HI + BUTTON_SPACING
+#define BUTTONA_X_HI BUTTONA_X_LO + BUTTON_WIDTH - 1
+
+#define BUTTONB_X_LO BUTTONA_X_HI + BUTTON_SPACING
+#define BUTTONB_X_HI BUTTONB_X_LO + BUTTON_WIDTH - 1
+
+#define BUTTONC_X_LO BUTTONB_X_HI + BUTTON_SPACING
+#define BUTTONC_X_HI BUTTONC_X_LO + BUTTON_WIDTH - 1
+
+#define BUTTON_Y_HI (TFT_HEIGHT) - BUTTON_SPACING
+#define BUTTON_Y_LO BUTTON_Y_HI - BUTTON_HEIGHT
+
+#define TSLP_PREINIT 0
+#define TSLP_SLEEPING 1
+
+class TouchButtons {
+public:
+ static void init();
+ static uint8_t read_buttons();
+ #if HAS_TOUCH_SLEEP
+ static millis_t next_sleep_ms;
+ static bool isSleeping() { return next_sleep_ms == TSLP_SLEEPING; }
+ static void sleepTimeout();
+ static void wakeUp();
+ #endif
+};
+
+extern TouchButtons touchBt;