From 4989bd3925b2ef003dc2d8f2d13d9ab7fa748677 Mon Sep 17 00:00:00 2001 From: Malte Reents Date: Sun, 19 May 2024 11:48:07 +0200 Subject: [PATCH] src folder --- src/lcd/TFTGLCD/lcdprint_TFTGLCD.cpp | 1142 ++++++++++++++++ src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp | 1094 +++++++++++++++ src/lcd/TFTGLCD/marlinui_TFTGLCD.h | 72 + src/lcd/buttons.h | 226 ++++ src/lcd/fontutils.cpp | 205 +++ src/lcd/fontutils.h | 79 ++ src/lcd/lcdprint.cpp | 109 ++ src/lcd/lcdprint.h | 296 ++++ src/lcd/marlinui.cpp | 1879 ++++++++++++++++++++++++++ src/lcd/marlinui.h | 791 +++++++++++ src/lcd/scaled_tft.h | 55 + src/lcd/tft_io/ili9328.h | 173 +++ src/lcd/tft_io/ili9341.h | 170 +++ src/lcd/tft_io/ili9488.h | 164 +++ src/lcd/tft_io/r65105.h | 176 +++ src/lcd/tft_io/ssd1963.h | 131 ++ src/lcd/tft_io/st7735.h | 136 ++ src/lcd/tft_io/st7789v.h | 156 +++ src/lcd/tft_io/st7796s.h | 157 +++ src/lcd/tft_io/tft_ids.h | 34 + src/lcd/tft_io/tft_io.cpp | 258 ++++ src/lcd/tft_io/tft_io.h | 134 ++ src/lcd/tft_io/touch_calibration.cpp | 115 ++ src/lcd/tft_io/touch_calibration.h | 95 ++ src/lcd/thermistornames.h | 154 +++ src/lcd/touch/touch_buttons.cpp | 143 ++ src/lcd/touch/touch_buttons.h | 71 + 27 files changed, 8215 insertions(+) create mode 100644 src/lcd/TFTGLCD/lcdprint_TFTGLCD.cpp create mode 100644 src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp create mode 100644 src/lcd/TFTGLCD/marlinui_TFTGLCD.h create mode 100644 src/lcd/buttons.h create mode 100644 src/lcd/fontutils.cpp create mode 100644 src/lcd/fontutils.h create mode 100644 src/lcd/lcdprint.cpp create mode 100644 src/lcd/lcdprint.h create mode 100644 src/lcd/marlinui.cpp create mode 100644 src/lcd/marlinui.h create mode 100644 src/lcd/scaled_tft.h create mode 100644 src/lcd/tft_io/ili9328.h create mode 100644 src/lcd/tft_io/ili9341.h create mode 100644 src/lcd/tft_io/ili9488.h create mode 100644 src/lcd/tft_io/r65105.h create mode 100644 src/lcd/tft_io/ssd1963.h create mode 100644 src/lcd/tft_io/st7735.h create mode 100644 src/lcd/tft_io/st7789v.h create mode 100644 src/lcd/tft_io/st7796s.h create mode 100644 src/lcd/tft_io/tft_ids.h create mode 100644 src/lcd/tft_io/tft_io.cpp create mode 100644 src/lcd/tft_io/tft_io.h create mode 100644 src/lcd/tft_io/touch_calibration.cpp create mode 100644 src/lcd/tft_io/touch_calibration.h create mode 100644 src/lcd/thermistornames.h create mode 100644 src/lcd/touch/touch_buttons.cpp create mode 100644 src/lcd/touch/touch_buttons.h 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;