mirror of
https://github.com/MarlinFirmware/Marlin.git
synced 2024-11-27 13:56:24 +00:00
0dd0033b33
* Save up to 94 bytes of RAM on 20x4 LCD Display machines Moved the custom screen characters out of RAM into Program Memory. With SD-Card support and the Progress Bar enabled, this saves 94 bytes of RAM memory. This was tested using the example_configurations/FolgerTech-i3-2020 files. So a couple small changes to those files snuck into this Pull Request. Probably... We can find similar savings in the Graphics LCD code it we comb through it. And if so... That is the place we really need to save RAM memory! * Tidy up white space and indentation
1071 lines
30 KiB
C
1071 lines
30 KiB
C
/**
|
|
* Marlin 3D Printer Firmware
|
|
* Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#ifndef ULTRALCD_IMPL_HD44780_H
|
|
#define ULTRALCD_IMPL_HD44780_H
|
|
|
|
/**
|
|
* Implementation of the LCD display routines for a Hitachi HD44780 display.
|
|
* These are the most common LCD character displays.
|
|
*/
|
|
|
|
#include "utility.h"
|
|
#include "duration_t.h"
|
|
|
|
extern volatile uint8_t buttons; //an extended version of the last checked buttons in a bit array.
|
|
|
|
////////////////////////////////////
|
|
// Setup 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.
|
|
|
|
#if ENABLED(ULTIPANEL)
|
|
|
|
//
|
|
// Setup other button mappings of each panel
|
|
//
|
|
#if ENABLED(LCD_I2C_VIKI)
|
|
#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
|
|
#undef LCD_CLICKED
|
|
#define LCD_CLICKED (buttons&(B_MI|B_RI|B_ST)) // pause/stop button also acts as click until we implement proper pause/stop.
|
|
#else
|
|
#undef LCD_CLICKED
|
|
#define LCD_CLICKED (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
|
|
#define LCD_HAS_SLOW_BUTTONS
|
|
|
|
#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
|
|
|
|
#undef LCD_CLICKED
|
|
#define LCD_CLICKED (buttons & B_MI)
|
|
|
|
// I2C buttons take too long to read inside an interrupt context and so we read them during lcd_update
|
|
#define LCD_HAS_SLOW_BUTTONS
|
|
|
|
#endif
|
|
|
|
#elif DISABLED(NEWPANEL) // old style ULTIPANEL
|
|
// 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))
|
|
#define LCD_CLICKED ((buttons & B_MI) || (buttons & B_ST))
|
|
#endif
|
|
|
|
#endif // ULTIPANEL
|
|
|
|
////////////////////////////////////
|
|
// Create LCD class instance and chipset-specific information
|
|
#if ENABLED(LCD_I2C_TYPE_PCF8575)
|
|
// note: these are register mapped pins on the PCF8575 controller not Arduino pins
|
|
#define LCD_I2C_PIN_BL 3
|
|
#define LCD_I2C_PIN_EN 2
|
|
#define LCD_I2C_PIN_RW 1
|
|
#define LCD_I2C_PIN_RS 0
|
|
#define LCD_I2C_PIN_D4 4
|
|
#define LCD_I2C_PIN_D5 5
|
|
#define LCD_I2C_PIN_D6 6
|
|
#define LCD_I2C_PIN_D7 7
|
|
|
|
#include <Wire.h>
|
|
#include <LCD.h>
|
|
#include <LiquidCrystal_I2C.h>
|
|
#define LCD_CLASS LiquidCrystal_I2C
|
|
LCD_CLASS lcd(LCD_I2C_ADDRESS, LCD_I2C_PIN_EN, LCD_I2C_PIN_RW, LCD_I2C_PIN_RS, LCD_I2C_PIN_D4, LCD_I2C_PIN_D5, LCD_I2C_PIN_D6, LCD_I2C_PIN_D7);
|
|
|
|
#elif ENABLED(LCD_I2C_TYPE_MCP23017)
|
|
//for the LED indicators (which maybe mapped to different things in lcd_implementation_update_indicators())
|
|
#define LED_A 0x04 //100
|
|
#define LED_B 0x02 //010
|
|
#define LED_C 0x01 //001
|
|
|
|
#define LCD_HAS_STATUS_INDICATORS
|
|
|
|
#include <Wire.h>
|
|
#include <LiquidTWI2.h>
|
|
#define LCD_CLASS LiquidTWI2
|
|
#if ENABLED(DETECT_DEVICE)
|
|
LCD_CLASS lcd(LCD_I2C_ADDRESS, 1);
|
|
#else
|
|
LCD_CLASS lcd(LCD_I2C_ADDRESS);
|
|
#endif
|
|
|
|
#elif ENABLED(LCD_I2C_TYPE_MCP23008)
|
|
#include <Wire.h>
|
|
#include <LiquidTWI2.h>
|
|
#define LCD_CLASS LiquidTWI2
|
|
#if ENABLED(DETECT_DEVICE)
|
|
LCD_CLASS lcd(LCD_I2C_ADDRESS, 1);
|
|
#else
|
|
LCD_CLASS lcd(LCD_I2C_ADDRESS);
|
|
#endif
|
|
|
|
#elif ENABLED(LCD_I2C_TYPE_PCA8574)
|
|
#include <LiquidCrystal_I2C.h>
|
|
#define LCD_CLASS LiquidCrystal_I2C
|
|
LCD_CLASS lcd(LCD_I2C_ADDRESS, LCD_WIDTH, LCD_HEIGHT);
|
|
|
|
// 2 wire Non-latching LCD SR from:
|
|
// https://bitbucket.org/fmalpartida/new-liquidcrystal/wiki/schematics#!shiftregister-connection
|
|
#elif ENABLED(SR_LCD_2W_NL)
|
|
extern "C" void __cxa_pure_virtual() { while (1); }
|
|
#include <LCD.h>
|
|
#include <LiquidCrystal_SR.h>
|
|
#define LCD_CLASS LiquidCrystal_SR
|
|
#if PIN_EXISTS(SR_STROBE)
|
|
LCD_CLASS lcd(SR_DATA_PIN, SR_CLK_PIN, SR_STROBE_PIN);
|
|
#else
|
|
LCD_CLASS lcd(SR_DATA_PIN, SR_CLK_PIN);
|
|
#endif
|
|
#elif ENABLED(LCM1602)
|
|
#include <Wire.h>
|
|
#include <LCD.h>
|
|
#include <LiquidCrystal_I2C.h>
|
|
#define LCD_CLASS LiquidCrystal_I2C
|
|
LCD_CLASS lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
|
|
#else
|
|
// Standard directly connected LCD implementations
|
|
#include <LiquidCrystal.h>
|
|
#define LCD_CLASS LiquidCrystal
|
|
LCD_CLASS lcd(LCD_PINS_RS, LCD_PINS_ENABLE, LCD_PINS_D4, LCD_PINS_D5, LCD_PINS_D6, LCD_PINS_D7); //RS,Enable,D4,D5,D6,D7
|
|
#endif
|
|
|
|
#include "utf_mapper.h"
|
|
|
|
#if ENABLED(LCD_PROGRESS_BAR)
|
|
static millis_t progress_bar_ms = 0;
|
|
#if PROGRESS_MSG_EXPIRE > 0
|
|
static millis_t expire_status_ms = 0;
|
|
#endif
|
|
#define LCD_STR_PROGRESS "\x03\x04\x05"
|
|
#endif
|
|
|
|
#if ENABLED(LCD_HAS_STATUS_INDICATORS)
|
|
static void lcd_implementation_update_indicators();
|
|
#endif
|
|
|
|
|
|
static void createChar_P(char c, PROGMEM byte *ptr) {
|
|
byte temp[8];
|
|
int8_t i;
|
|
|
|
for(i=0; i<8; i++) {
|
|
temp[i] = pgm_read_byte(&ptr[i]);
|
|
}
|
|
lcd.createChar(c, temp);
|
|
}
|
|
|
|
const static PROGMEM byte bedTemp[8] = {
|
|
B00000,
|
|
B11111,
|
|
B10101,
|
|
B10001,
|
|
B10101,
|
|
B11111,
|
|
B00000,
|
|
B00000
|
|
};
|
|
|
|
const static PROGMEM byte degree[8] = {
|
|
B01100,
|
|
B10010,
|
|
B10010,
|
|
B01100,
|
|
B00000,
|
|
B00000,
|
|
B00000,
|
|
B00000
|
|
};
|
|
|
|
const static PROGMEM byte thermometer[8] = {
|
|
B00100,
|
|
B01010,
|
|
B01010,
|
|
B01010,
|
|
B01010,
|
|
B10001,
|
|
B10001,
|
|
B01110
|
|
};
|
|
|
|
const static PROGMEM byte uplevel[8] = {
|
|
B00100,
|
|
B01110,
|
|
B11111,
|
|
B00100,
|
|
B11100,
|
|
B00000,
|
|
B00000,
|
|
B00000
|
|
};
|
|
|
|
const static PROGMEM byte feedrate[8] = {
|
|
B11100,
|
|
B10000,
|
|
B11000,
|
|
B10111,
|
|
B00101,
|
|
B00110,
|
|
B00101,
|
|
B00000
|
|
};
|
|
|
|
const static PROGMEM byte clock[8] = {
|
|
B00000,
|
|
B01110,
|
|
B10011,
|
|
B10101,
|
|
B10001,
|
|
B01110,
|
|
B00000,
|
|
B00000
|
|
};
|
|
|
|
#if ENABLED(SDSUPPORT)
|
|
const static PROGMEM byte refresh[8] = {
|
|
B00000,
|
|
B00110,
|
|
B11001,
|
|
B11000,
|
|
B00011,
|
|
B10011,
|
|
B01100,
|
|
B00000,
|
|
};
|
|
const static PROGMEM byte folder[8] = {
|
|
B00000,
|
|
B11100,
|
|
B11111,
|
|
B10001,
|
|
B10001,
|
|
B11111,
|
|
B00000,
|
|
B00000
|
|
};
|
|
|
|
#if ENABLED(LCD_PROGRESS_BAR)
|
|
const static PROGMEM byte progress[3][8] = { {
|
|
B00000,
|
|
B10000,
|
|
B10000,
|
|
B10000,
|
|
B10000,
|
|
B10000,
|
|
B10000,
|
|
B00000
|
|
}, {
|
|
B00000,
|
|
B10100,
|
|
B10100,
|
|
B10100,
|
|
B10100,
|
|
B10100,
|
|
B10100,
|
|
B00000
|
|
}, {
|
|
B00000,
|
|
B10101,
|
|
B10101,
|
|
B10101,
|
|
B10101,
|
|
B10101,
|
|
B10101,
|
|
B00000
|
|
} };
|
|
#endif
|
|
#endif
|
|
|
|
static void lcd_set_custom_characters(
|
|
#if ENABLED(LCD_PROGRESS_BAR)
|
|
const bool info_screen_charset = true
|
|
#endif
|
|
) {
|
|
|
|
createChar_P(LCD_BEDTEMP_CHAR, bedTemp);
|
|
createChar_P(LCD_DEGREE_CHAR, degree);
|
|
createChar_P(LCD_STR_THERMOMETER[0], thermometer);
|
|
createChar_P(LCD_FEEDRATE_CHAR, feedrate);
|
|
createChar_P(LCD_CLOCK_CHAR, clock);
|
|
|
|
#if ENABLED(SDSUPPORT)
|
|
#if ENABLED(LCD_PROGRESS_BAR)
|
|
static bool char_mode = false;
|
|
if (info_screen_charset != char_mode) {
|
|
char_mode = info_screen_charset;
|
|
if (info_screen_charset) { // Progress bar characters for info screen
|
|
for (int i = 3; i--;) createChar_P(LCD_STR_PROGRESS[i], progress[i]);
|
|
}
|
|
else { // Custom characters for submenus
|
|
createChar_P(LCD_UPLEVEL_CHAR, uplevel);
|
|
createChar_P(LCD_REFRESH_CHAR, refresh);
|
|
createChar_P(LCD_STR_FOLDER[0], folder);
|
|
}
|
|
}
|
|
#else
|
|
createChar_P(LCD_UPLEVEL_CHAR, uplevel);
|
|
createChar_P(LCD_REFRESH_CHAR, refresh);
|
|
createChar_P(LCD_STR_FOLDER[0], folder);
|
|
#endif
|
|
|
|
#else
|
|
createChar_P(LCD_UPLEVEL_CHAR, uplevel);
|
|
#endif
|
|
}
|
|
|
|
static void lcd_implementation_init(
|
|
#if ENABLED(LCD_PROGRESS_BAR)
|
|
const bool info_screen_charset = true
|
|
#endif
|
|
) {
|
|
|
|
#if ENABLED(LCD_I2C_TYPE_PCF8575)
|
|
lcd.begin(LCD_WIDTH, LCD_HEIGHT);
|
|
#ifdef LCD_I2C_PIN_BL
|
|
lcd.setBacklightPin(LCD_I2C_PIN_BL, POSITIVE);
|
|
lcd.setBacklight(HIGH);
|
|
#endif
|
|
|
|
#elif ENABLED(LCD_I2C_TYPE_MCP23017)
|
|
lcd.setMCPType(LTI_TYPE_MCP23017);
|
|
lcd.begin(LCD_WIDTH, LCD_HEIGHT);
|
|
lcd_implementation_update_indicators();
|
|
|
|
#elif ENABLED(LCD_I2C_TYPE_MCP23008)
|
|
lcd.setMCPType(LTI_TYPE_MCP23008);
|
|
lcd.begin(LCD_WIDTH, LCD_HEIGHT);
|
|
|
|
#elif ENABLED(LCD_I2C_TYPE_PCA8574)
|
|
lcd.init();
|
|
lcd.backlight();
|
|
|
|
#else
|
|
lcd.begin(LCD_WIDTH, LCD_HEIGHT);
|
|
#endif
|
|
|
|
lcd_set_custom_characters(
|
|
#if ENABLED(LCD_PROGRESS_BAR)
|
|
info_screen_charset
|
|
#endif
|
|
);
|
|
|
|
lcd.clear();
|
|
}
|
|
|
|
void lcd_implementation_clear() { lcd.clear(); }
|
|
|
|
void lcd_print(const char c) { charset_mapper(c); }
|
|
|
|
void lcd_print(const char *str) { while (*str) lcd.print(*str++); }
|
|
void lcd_printPGM(const char *str) { while (const char c = pgm_read_byte(str)) lcd.print(c), ++str; }
|
|
|
|
void lcd_print_utf(const char *str, uint8_t n=LCD_WIDTH) {
|
|
char c;
|
|
while (n && (c = *str)) n -= charset_mapper(c), ++str;
|
|
}
|
|
|
|
void lcd_printPGM_utf(const char *str, uint8_t n=LCD_WIDTH) {
|
|
char c;
|
|
while (n && (c = pgm_read_byte(str))) n -= charset_mapper(c), ++str;
|
|
}
|
|
|
|
#if ENABLED(SHOW_BOOTSCREEN)
|
|
|
|
void lcd_erase_line(const int line) {
|
|
lcd.setCursor(0, line);
|
|
for (uint8_t i = LCD_WIDTH + 1; --i;)
|
|
lcd.print(' ');
|
|
}
|
|
|
|
// Scroll the PSTR 'text' in a 'len' wide field for 'time' milliseconds at position col,line
|
|
void lcd_scroll(const int col, const int line, const char* const text, const int len, const int time) {
|
|
char tmp[LCD_WIDTH + 1] = {0};
|
|
int n = max(lcd_strlen_P(text) - len, 0);
|
|
for (int i = 0; i <= n; i++) {
|
|
strncpy_P(tmp, text + i, min(len, LCD_WIDTH));
|
|
lcd.setCursor(col, line);
|
|
lcd_print(tmp);
|
|
delay(time / max(n, 1));
|
|
}
|
|
}
|
|
|
|
static void logo_lines(const char* const extra) {
|
|
int indent = (LCD_WIDTH - 8 - lcd_strlen_P(extra)) / 2;
|
|
lcd.setCursor(indent, 0); lcd.print('\x00'); lcd_printPGM(PSTR( "------" )); lcd.print('\x01');
|
|
lcd.setCursor(indent, 1); lcd_printPGM(PSTR("|Marlin|")); lcd_printPGM(extra);
|
|
lcd.setCursor(indent, 2); lcd.print('\x02'); lcd_printPGM(PSTR( "------" )); lcd.print('\x03');
|
|
}
|
|
|
|
void bootscreen() {
|
|
byte top_left[8] = {
|
|
B00000,
|
|
B00000,
|
|
B00000,
|
|
B00000,
|
|
B00001,
|
|
B00010,
|
|
B00100,
|
|
B00100
|
|
};
|
|
byte top_right[8] = {
|
|
B00000,
|
|
B00000,
|
|
B00000,
|
|
B11100,
|
|
B11100,
|
|
B01100,
|
|
B00100,
|
|
B00100
|
|
};
|
|
byte botom_left[8] = {
|
|
B00100,
|
|
B00010,
|
|
B00001,
|
|
B00000,
|
|
B00000,
|
|
B00000,
|
|
B00000,
|
|
B00000
|
|
};
|
|
byte botom_right[8] = {
|
|
B00100,
|
|
B01000,
|
|
B10000,
|
|
B00000,
|
|
B00000,
|
|
B00000,
|
|
B00000,
|
|
B00000
|
|
};
|
|
lcd.createChar(0, top_left);
|
|
lcd.createChar(1, top_right);
|
|
lcd.createChar(2, botom_left);
|
|
lcd.createChar(3, botom_right);
|
|
|
|
lcd.clear();
|
|
|
|
#define LCD_EXTRA_SPACE (LCD_WIDTH-8)
|
|
|
|
#define CENTER_OR_SCROLL(STRING,DELAY) \
|
|
lcd_erase_line(3); \
|
|
if (strlen(STRING) <= LCD_WIDTH) { \
|
|
lcd.setCursor((LCD_WIDTH - lcd_strlen_P(PSTR(STRING))) / 2, 3); \
|
|
lcd_printPGM(PSTR(STRING)); \
|
|
safe_delay(DELAY); \
|
|
} \
|
|
else { \
|
|
lcd_scroll(0, 3, PSTR(STRING), LCD_WIDTH, DELAY); \
|
|
}
|
|
|
|
#ifdef STRING_SPLASH_LINE1
|
|
//
|
|
// Show the Marlin logo with splash line 1
|
|
//
|
|
if (LCD_EXTRA_SPACE >= strlen(STRING_SPLASH_LINE1) + 1) {
|
|
//
|
|
// Show the Marlin logo, splash line1, and splash line 2
|
|
//
|
|
logo_lines(PSTR(" " STRING_SPLASH_LINE1));
|
|
#ifdef STRING_SPLASH_LINE2
|
|
CENTER_OR_SCROLL(STRING_SPLASH_LINE2, 2000);
|
|
#else
|
|
safe_delay(2000);
|
|
#endif
|
|
}
|
|
else {
|
|
//
|
|
// Show the Marlin logo with splash line 1
|
|
// After a delay show splash line 2, if it exists
|
|
//
|
|
#ifdef STRING_SPLASH_LINE2
|
|
#define _SPLASH_WAIT_1 1500
|
|
#else
|
|
#define _SPLASH_WAIT_1 2000
|
|
#endif
|
|
logo_lines(PSTR(""));
|
|
CENTER_OR_SCROLL(STRING_SPLASH_LINE1, _SPLASH_WAIT_1);
|
|
#ifdef STRING_SPLASH_LINE2
|
|
CENTER_OR_SCROLL(STRING_SPLASH_LINE2, 1500);
|
|
#endif
|
|
}
|
|
#elif defined(STRING_SPLASH_LINE2)
|
|
//
|
|
// Show splash line 2 only, alongside the logo if possible
|
|
//
|
|
if (LCD_EXTRA_SPACE >= strlen(STRING_SPLASH_LINE2) + 1) {
|
|
logo_lines(PSTR(" " STRING_SPLASH_LINE2));
|
|
safe_delay(2000);
|
|
}
|
|
else {
|
|
logo_lines(PSTR(""));
|
|
CENTER_OR_SCROLL(STRING_SPLASH_LINE2, 2000);
|
|
}
|
|
#else
|
|
//
|
|
// Show only the Marlin logo
|
|
//
|
|
logo_lines(PSTR(""));
|
|
safe_delay(2000);
|
|
#endif
|
|
|
|
lcd.clear();
|
|
|
|
safe_delay(100);
|
|
|
|
lcd_set_custom_characters(
|
|
#if ENABLED(LCD_PROGRESS_BAR)
|
|
false
|
|
#endif
|
|
);
|
|
}
|
|
|
|
#endif // SHOW_BOOTSCREEN
|
|
|
|
void lcd_kill_screen() {
|
|
lcd.setCursor(0, 0);
|
|
lcd_print_utf(lcd_status_message);
|
|
#if LCD_HEIGHT < 4
|
|
lcd.setCursor(0, 2);
|
|
#else
|
|
lcd.setCursor(0, 2);
|
|
lcd_printPGM(PSTR(MSG_HALTED));
|
|
lcd.setCursor(0, 3);
|
|
#endif
|
|
lcd_printPGM(PSTR(MSG_PLEASE_RESET));
|
|
}
|
|
|
|
FORCE_INLINE void _draw_axis_label(const AxisEnum axis, const char* const pstr, const bool blink) {
|
|
if (blink)
|
|
lcd_printPGM(pstr);
|
|
else {
|
|
if (!axis_homed[axis])
|
|
lcd.print('?');
|
|
else {
|
|
#if DISABLED(DISABLE_REDUCED_ACCURACY_WARNING)
|
|
if (!axis_known_position[axis])
|
|
lcd.print(' ');
|
|
else
|
|
#endif
|
|
lcd_printPGM(pstr);
|
|
}
|
|
}
|
|
}
|
|
|
|
FORCE_INLINE void _draw_heater_status(const int8_t heater, const char prefix, const bool blink) {
|
|
const bool isBed = heater < 0;
|
|
|
|
const float t1 = (isBed ? thermalManager.degBed() : thermalManager.degHotend(heater)),
|
|
t2 = (isBed ? thermalManager.degTargetBed() : thermalManager.degTargetHotend(heater));
|
|
|
|
if (prefix >= 0) lcd.print(prefix);
|
|
|
|
lcd.print(itostr3(t1 + 0.5));
|
|
lcd.print('/');
|
|
|
|
#if ENABLED(ADVANCED_PAUSE_FEATURE)
|
|
const bool is_idle = (!isBed ? thermalManager.is_heater_idle(heater) :
|
|
#if HAS_TEMP_BED
|
|
thermalManager.is_bed_idle()
|
|
#else
|
|
false
|
|
#endif
|
|
);
|
|
|
|
if (!blink && is_idle) {
|
|
lcd.print(' ');
|
|
if (t2 >= 10) lcd.print(' ');
|
|
if (t2 >= 100) lcd.print(' ');
|
|
}
|
|
else
|
|
#endif
|
|
lcd.print(itostr3left(t2 + 0.5));
|
|
|
|
if (prefix >= 0) {
|
|
lcd.print((char)LCD_DEGREE_CHAR);
|
|
lcd.print(' ');
|
|
if (t2 < 10) lcd.print(' ');
|
|
}
|
|
}
|
|
|
|
#if ENABLED(LCD_PROGRESS_BAR)
|
|
|
|
inline void lcd_draw_progress_bar(const uint8_t percent) {
|
|
int tix = (int)(percent * (LCD_WIDTH) * 3) / 100,
|
|
cel = tix / 3, rem = tix % 3, i = LCD_WIDTH;
|
|
char msg[LCD_WIDTH + 1], b = ' ';
|
|
msg[i] = '\0';
|
|
while (i--) {
|
|
if (i == cel - 1)
|
|
b = LCD_STR_PROGRESS[2];
|
|
else if (i == cel && rem != 0)
|
|
b = LCD_STR_PROGRESS[rem - 1];
|
|
msg[i] = b;
|
|
}
|
|
lcd.print(msg);
|
|
}
|
|
|
|
#endif // LCD_PROGRESS_BAR
|
|
|
|
/**
|
|
Possible status screens:
|
|
16x2 |000/000 B000/000|
|
|
|0123456789012345|
|
|
|
|
16x4 |000/000 B000/000|
|
|
|SD100% Z 000.00|
|
|
|F100% T--:--|
|
|
|0123456789012345|
|
|
|
|
20x2 |T000/000D B000/000D |
|
|
|01234567890123456789|
|
|
|
|
20x4 |T000/000D B000/000D |
|
|
|X 000 Y 000 Z 000.00|
|
|
|F100% SD100% T--:--|
|
|
|01234567890123456789|
|
|
|
|
20x4 |T000/000D B000/000D |
|
|
|T000/000D Z 000.00|
|
|
|F100% SD100% T--:--|
|
|
|01234567890123456789|
|
|
*/
|
|
static void lcd_implementation_status_screen() {
|
|
const bool blink = lcd_blink();
|
|
|
|
//
|
|
// Line 1
|
|
//
|
|
|
|
lcd.setCursor(0, 0);
|
|
|
|
#if LCD_WIDTH < 20
|
|
|
|
//
|
|
// Hotend 0 Temperature
|
|
//
|
|
_draw_heater_status(0, -1, blink);
|
|
|
|
//
|
|
// Hotend 1 or Bed Temperature
|
|
//
|
|
#if HOTENDS > 1 || TEMP_SENSOR_BED != 0
|
|
|
|
lcd.setCursor(8, 0);
|
|
#if HOTENDS > 1
|
|
lcd.print((CHAR)LCD_STR_THERMOMETER[0]);
|
|
_draw_heater_status(1, -1, blink);
|
|
#else
|
|
lcd.print((CHAR)LCD_BEDTEMP_CHAR);
|
|
_draw_heater_status(-1, -1, blink);
|
|
#endif
|
|
|
|
#endif // HOTENDS > 1 || TEMP_SENSOR_BED != 0
|
|
|
|
#else // LCD_WIDTH >= 20
|
|
|
|
//
|
|
// Hotend 0 Temperature
|
|
//
|
|
_draw_heater_status(0, LCD_STR_THERMOMETER[0], blink);
|
|
|
|
//
|
|
// Hotend 1 or Bed Temperature
|
|
//
|
|
#if HOTENDS > 1 || TEMP_SENSOR_BED != 0
|
|
lcd.setCursor(10, 0);
|
|
#if HOTENDS > 1
|
|
_draw_heater_status(1, LCD_STR_THERMOMETER[0], blink);
|
|
#else
|
|
_draw_heater_status(-1, LCD_BEDTEMP_CHAR, blink);
|
|
#endif
|
|
|
|
#endif // HOTENDS > 1 || TEMP_SENSOR_BED != 0
|
|
|
|
#endif // LCD_WIDTH >= 20
|
|
|
|
//
|
|
// Line 2
|
|
//
|
|
|
|
#if LCD_HEIGHT > 2
|
|
|
|
#if LCD_WIDTH < 20
|
|
|
|
#if ENABLED(SDSUPPORT)
|
|
lcd.setCursor(0, 2);
|
|
lcd_printPGM(PSTR("SD"));
|
|
if (IS_SD_PRINTING)
|
|
lcd.print(itostr3(card.percentDone()));
|
|
else
|
|
lcd_printPGM(PSTR("---"));
|
|
lcd.print('%');
|
|
#endif // SDSUPPORT
|
|
|
|
#else // LCD_WIDTH >= 20
|
|
|
|
lcd.setCursor(0, 1);
|
|
|
|
#if HOTENDS > 1 && TEMP_SENSOR_BED != 0
|
|
|
|
// If we both have a 2nd extruder and a heated bed,
|
|
// show the heated bed temp on the left,
|
|
// since the first line is filled with extruder temps
|
|
_draw_heater_status(-1, LCD_BEDTEMP_CHAR, blink);
|
|
|
|
#else
|
|
// Before homing the axis letters are blinking 'X' <-> '?'.
|
|
// When axis is homed but axis_known_position is false the axis letters are blinking 'X' <-> ' '.
|
|
// When everything is ok you see a constant 'X'.
|
|
|
|
_draw_axis_label(X_AXIS, PSTR(MSG_X), blink);
|
|
lcd.print(ftostr4sign(current_position[X_AXIS]));
|
|
|
|
lcd.print(' ');
|
|
|
|
_draw_axis_label(Y_AXIS, PSTR(MSG_Y), blink);
|
|
lcd.print(ftostr4sign(current_position[Y_AXIS]));
|
|
|
|
#endif // HOTENDS > 1 || TEMP_SENSOR_BED != 0
|
|
|
|
#endif // LCD_WIDTH >= 20
|
|
|
|
lcd.setCursor(LCD_WIDTH - 8, 1);
|
|
_draw_axis_label(Z_AXIS, PSTR(MSG_Z), blink);
|
|
lcd.print(ftostr52sp(FIXFLOAT(current_position[Z_AXIS])));
|
|
|
|
#endif // LCD_HEIGHT > 2
|
|
|
|
//
|
|
// Line 3
|
|
//
|
|
|
|
#if LCD_HEIGHT > 3
|
|
|
|
lcd.setCursor(0, 2);
|
|
lcd.print((char)LCD_FEEDRATE_CHAR);
|
|
lcd.print(itostr3(feedrate_percentage));
|
|
lcd.print('%');
|
|
|
|
#if LCD_WIDTH >= 20 && ENABLED(SDSUPPORT)
|
|
|
|
lcd.setCursor(7, 2);
|
|
lcd_printPGM(PSTR("SD"));
|
|
if (IS_SD_PRINTING)
|
|
lcd.print(itostr3(card.percentDone()));
|
|
else
|
|
lcd_printPGM(PSTR("---"));
|
|
lcd.print('%');
|
|
|
|
#endif // LCD_WIDTH >= 20 && SDSUPPORT
|
|
|
|
char buffer[10];
|
|
duration_t elapsed = print_job_timer.duration();
|
|
uint8_t len = elapsed.toDigital(buffer);
|
|
|
|
lcd.setCursor(LCD_WIDTH - len - 1, 2);
|
|
lcd.print((char)LCD_CLOCK_CHAR);
|
|
lcd_print(buffer);
|
|
|
|
#endif // LCD_HEIGHT > 3
|
|
|
|
//
|
|
// Last Line
|
|
// Status Message (which may be a Progress Bar or Filament display)
|
|
//
|
|
|
|
lcd.setCursor(0, LCD_HEIGHT - 1);
|
|
|
|
#if ENABLED(LCD_PROGRESS_BAR)
|
|
|
|
// Draw the progress bar if the message has shown long enough
|
|
// or if there is no message set.
|
|
if (card.isFileOpen() && (ELAPSED(millis(), progress_bar_ms + PROGRESS_BAR_MSG_TIME) || !lcd_status_message[0])) {
|
|
const uint8_t percent = card.percentDone();
|
|
if (percent) return lcd_draw_progress_bar(percent);
|
|
}
|
|
|
|
#elif ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT)
|
|
|
|
// Show Filament Diameter and Volumetric Multiplier %
|
|
// After allowing lcd_status_message to show for 5 seconds
|
|
if (ELAPSED(millis(), previous_lcd_status_ms + 5000UL)) {
|
|
lcd_printPGM(PSTR("Dia "));
|
|
lcd.print(ftostr12ns(filament_width_meas));
|
|
lcd_printPGM(PSTR(" V"));
|
|
lcd.print(itostr3(100.0 * volumetric_multiplier[FILAMENT_SENSOR_EXTRUDER_NUM]));
|
|
lcd.print('%');
|
|
return;
|
|
}
|
|
|
|
#endif // FILAMENT_LCD_DISPLAY && SDSUPPORT
|
|
|
|
#if ENABLED(STATUS_MESSAGE_SCROLLING)
|
|
static bool last_blink = false;
|
|
lcd_print_utf(lcd_status_message + status_scroll_pos);
|
|
const uint8_t slen = lcd_strlen(lcd_status_message);
|
|
if (slen > LCD_WIDTH) {
|
|
if (last_blink != blink) {
|
|
last_blink = blink;
|
|
// Skip any non-printing bytes
|
|
while (!PRINTABLE(lcd_status_message[status_scroll_pos])) status_scroll_pos++;
|
|
if (++status_scroll_pos > slen - LCD_WIDTH) status_scroll_pos = 0;
|
|
}
|
|
}
|
|
#else
|
|
lcd_print_utf(lcd_status_message);
|
|
#endif
|
|
}
|
|
|
|
#if ENABLED(ULTIPANEL)
|
|
|
|
#if ENABLED(ADVANCED_PAUSE_FEATURE)
|
|
|
|
static void lcd_implementation_hotend_status(const uint8_t row) {
|
|
if (row < LCD_HEIGHT) {
|
|
lcd.setCursor(LCD_WIDTH - 9, row);
|
|
_draw_heater_status(active_extruder, LCD_STR_THERMOMETER[0], lcd_blink());
|
|
}
|
|
}
|
|
|
|
#endif // ADVANCED_PAUSE_FEATURE
|
|
|
|
static void lcd_implementation_drawmenu_static(const uint8_t row, const char* pstr, const bool center=true, const bool invert=false, const char *valstr=NULL) {
|
|
UNUSED(invert);
|
|
char c;
|
|
int8_t n = LCD_WIDTH;
|
|
lcd.setCursor(0, row);
|
|
if (center && !valstr) {
|
|
int8_t pad = (LCD_WIDTH - lcd_strlen_P(pstr)) / 2;
|
|
while (--pad >= 0) { lcd.print(' '); n--; }
|
|
}
|
|
while (n > 0 && (c = pgm_read_byte(pstr))) {
|
|
n -= charset_mapper(c);
|
|
pstr++;
|
|
}
|
|
if (valstr) while (n > 0 && (c = *valstr)) {
|
|
n -= charset_mapper(c);
|
|
valstr++;
|
|
}
|
|
while (n-- > 0) lcd.print(' ');
|
|
}
|
|
|
|
static void lcd_implementation_drawmenu_generic(const bool sel, const uint8_t row, const char* pstr, const char pre_char, const char post_char) {
|
|
char c;
|
|
uint8_t n = LCD_WIDTH - 2;
|
|
lcd.setCursor(0, row);
|
|
lcd.print(sel ? pre_char : ' ');
|
|
while ((c = pgm_read_byte(pstr)) && n > 0) {
|
|
n -= charset_mapper(c);
|
|
pstr++;
|
|
}
|
|
while (n--) lcd.print(' ');
|
|
lcd.print(post_char);
|
|
}
|
|
|
|
static void lcd_implementation_drawmenu_setting_edit_generic(const bool sel, const uint8_t row, const char* pstr, const char pre_char, const char* const data) {
|
|
char c;
|
|
uint8_t n = LCD_WIDTH - 2 - lcd_strlen(data);
|
|
lcd.setCursor(0, row);
|
|
lcd.print(sel ? pre_char : ' ');
|
|
while ((c = pgm_read_byte(pstr)) && n > 0) {
|
|
n -= charset_mapper(c);
|
|
pstr++;
|
|
}
|
|
lcd.print(':');
|
|
while (n--) lcd.print(' ');
|
|
lcd_print(data);
|
|
}
|
|
static void lcd_implementation_drawmenu_setting_edit_generic_P(const bool sel, const uint8_t row, const char* pstr, const char pre_char, const char* const data) {
|
|
char c;
|
|
uint8_t n = LCD_WIDTH - 2 - lcd_strlen_P(data);
|
|
lcd.setCursor(0, row);
|
|
lcd.print(sel ? pre_char : ' ');
|
|
while ((c = pgm_read_byte(pstr)) && n > 0) {
|
|
n -= charset_mapper(c);
|
|
pstr++;
|
|
}
|
|
lcd.print(':');
|
|
while (n--) lcd.print(' ');
|
|
lcd_printPGM(data);
|
|
}
|
|
|
|
#define DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(_type, _name, _strFunc) \
|
|
inline void lcd_implementation_drawmenu_setting_edit_ ## _name (const bool sel, const uint8_t row, const char* pstr, const char* pstr2, _type * const data, ...) { \
|
|
lcd_implementation_drawmenu_setting_edit_generic(sel, row, pstr, '>', _strFunc(*(data))); \
|
|
} \
|
|
inline void lcd_implementation_drawmenu_setting_edit_callback_ ## _name (const bool sel, const uint8_t row, const char* pstr, const char* pstr2, _type * const data, ...) { \
|
|
lcd_implementation_drawmenu_setting_edit_generic(sel, row, pstr, '>', _strFunc(*(data))); \
|
|
} \
|
|
inline void lcd_implementation_drawmenu_setting_edit_accessor_ ## _name (const bool sel, const uint8_t row, const char* pstr, const char* pstr2, _type (*pget)(), void (*pset)(_type), ...) { \
|
|
lcd_implementation_drawmenu_setting_edit_generic(sel, row, pstr, '>', _strFunc(pget())); \
|
|
} \
|
|
typedef void _name##_void
|
|
|
|
DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(int, int3, itostr3);
|
|
DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float3, ftostr3);
|
|
DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float32, ftostr32);
|
|
DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float43, ftostr43sign);
|
|
DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float5, ftostr5rj);
|
|
DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float51, ftostr51sign);
|
|
DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float52, ftostr52sign);
|
|
DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float62, ftostr62rj);
|
|
DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(unsigned long, long5, ftostr5rj);
|
|
|
|
#define lcd_implementation_drawmenu_setting_edit_bool(sel, row, pstr, pstr2, data) lcd_implementation_drawmenu_setting_edit_generic_P(sel, row, pstr, '>', (*(data))?PSTR(MSG_ON):PSTR(MSG_OFF))
|
|
#define lcd_implementation_drawmenu_setting_edit_callback_bool(sel, row, pstr, pstr2, data, callback) lcd_implementation_drawmenu_setting_edit_generic_P(sel, row, pstr, '>', (*(data))?PSTR(MSG_ON):PSTR(MSG_OFF))
|
|
#define lcd_implementation_drawmenu_setting_edit_accessor_bool(sel, row, pstr, pstr2, pget, pset, callback) lcd_implementation_drawmenu_setting_edit_generic_P(sel, row, pstr, '>', (*(data))?PSTR(MSG_ON):PSTR(MSG_OFF))
|
|
|
|
void lcd_implementation_drawedit(const char* pstr, const char* const value=NULL) {
|
|
lcd.setCursor(1, 1);
|
|
lcd_printPGM(pstr);
|
|
if (value != NULL) {
|
|
lcd.print(':');
|
|
lcd.setCursor(LCD_WIDTH - lcd_strlen(value), 1);
|
|
lcd_print(value);
|
|
}
|
|
}
|
|
|
|
#if ENABLED(SDSUPPORT)
|
|
|
|
static void lcd_implementation_drawmenu_sd(const bool sel, const uint8_t row, const char* const pstr, const char* filename, char* const longFilename, const uint8_t concat, const char post_char) {
|
|
UNUSED(pstr);
|
|
char c;
|
|
uint8_t n = LCD_WIDTH - concat;
|
|
lcd.setCursor(0, row);
|
|
lcd.print(sel ? '>' : ' ');
|
|
if (longFilename[0]) {
|
|
filename = longFilename;
|
|
longFilename[n] = '\0';
|
|
}
|
|
while ((c = *filename) && n > 0) {
|
|
n -= charset_mapper(c);
|
|
filename++;
|
|
}
|
|
while (n--) lcd.print(' ');
|
|
lcd.print(post_char);
|
|
}
|
|
|
|
static void lcd_implementation_drawmenu_sdfile(const bool sel, const uint8_t row, const char* pstr, const char* filename, char* const longFilename) {
|
|
lcd_implementation_drawmenu_sd(sel, row, pstr, filename, longFilename, 2, ' ');
|
|
}
|
|
|
|
static void lcd_implementation_drawmenu_sddirectory(const bool sel, const uint8_t row, const char* pstr, const char* filename, char* const longFilename) {
|
|
lcd_implementation_drawmenu_sd(sel, row, pstr, filename, longFilename, 2, LCD_STR_FOLDER[0]);
|
|
}
|
|
|
|
#endif // SDSUPPORT
|
|
|
|
#define lcd_implementation_drawmenu_back(sel, row, pstr, dummy) lcd_implementation_drawmenu_generic(sel, row, pstr, LCD_UPLEVEL_CHAR,LCD_UPLEVEL_CHAR)
|
|
#define lcd_implementation_drawmenu_submenu(sel, row, pstr, data) lcd_implementation_drawmenu_generic(sel, row, pstr, '>', LCD_STR_ARROW_RIGHT[0])
|
|
#define lcd_implementation_drawmenu_gcode(sel, row, pstr, gcode) lcd_implementation_drawmenu_generic(sel, row, pstr, '>', ' ')
|
|
#define lcd_implementation_drawmenu_function(sel, row, pstr, data) lcd_implementation_drawmenu_generic(sel, row, pstr, '>', ' ')
|
|
|
|
#if ENABLED(LCD_HAS_SLOW_BUTTONS)
|
|
|
|
extern millis_t next_button_update_ms;
|
|
|
|
static uint8_t lcd_implementation_read_slow_buttons() {
|
|
#if ENABLED(LCD_I2C_TYPE_MCP23017)
|
|
// Reading these buttons this is likely to be too slow to call inside interrupt context
|
|
// so they are called during normal lcd_update
|
|
uint8_t slow_bits = lcd.readButtons() << B_I2C_BTN_OFFSET;
|
|
#if ENABLED(LCD_I2C_VIKI)
|
|
if ((slow_bits & (B_MI | B_RI)) && PENDING(millis(), next_button_update_ms)) // LCD clicked
|
|
slow_bits &= ~(B_MI | B_RI); // Disable LCD clicked buttons if screen is updated
|
|
#endif // LCD_I2C_VIKI
|
|
return slow_bits;
|
|
#endif // LCD_I2C_TYPE_MCP23017
|
|
}
|
|
|
|
#endif // LCD_HAS_SLOW_BUTTONS
|
|
|
|
#endif // ULTIPANEL
|
|
|
|
#if ENABLED(LCD_HAS_STATUS_INDICATORS)
|
|
|
|
static void lcd_implementation_update_indicators() {
|
|
// Set the LEDS - referred to as backlights by the LiquidTWI2 library
|
|
static uint8_t ledsprev = 0;
|
|
uint8_t leds = 0;
|
|
|
|
if (thermalManager.degTargetBed() > 0) leds |= LED_A;
|
|
|
|
if (thermalManager.degTargetHotend(0) > 0) leds |= LED_B;
|
|
|
|
#if FAN_COUNT > 0
|
|
if (0
|
|
#if HAS_FAN0
|
|
|| fanSpeeds[0]
|
|
#endif
|
|
#if HAS_FAN1
|
|
|| fanSpeeds[1]
|
|
#endif
|
|
#if HAS_FAN2
|
|
|| fanSpeeds[2]
|
|
#endif
|
|
) leds |= LED_C;
|
|
#endif // FAN_COUNT > 0
|
|
|
|
#if HOTENDS > 1
|
|
if (thermalManager.degTargetHotend(1) > 0) leds |= LED_C;
|
|
#endif
|
|
|
|
if (leds != ledsprev) {
|
|
lcd.setBacklight(leds);
|
|
ledsprev = leds;
|
|
}
|
|
|
|
}
|
|
|
|
#endif // LCD_HAS_STATUS_INDICATORS
|
|
|
|
#endif // ULTRALCD_IMPL_HD44780_H
|