/**
* 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_wchar(' ');
if (t2 >= 10) lcd_put_wchar(' ');
if (t2 >= 100) lcd_put_wchar(' ');
}
else
#endif
lcd_put_u8str(i16tostr3left(t2));
lcd_put_wchar(' ');
if (t2 < 10) lcd_put_wchar(' ');
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_wchar('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_wchar(' '); 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_wchar('K');
#elif CUTTER_UNIT_IS(PERCENT)
lcd_moveto(17, 6); lcd_put_wchar('%');
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