diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index d3221f80de..3b3087e46f 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -35,7 +35,7 @@ * * Advanced settings can be found in Configuration_adv.h */ -#define CONFIGURATION_H_VERSION 02000900 +#define CONFIGURATION_H_VERSION 02000901 //=========================================================================== //============================= Getting Started ============================= @@ -1700,15 +1700,13 @@ //#define MANUAL_J_HOME_POS 0 //#define MANUAL_K_HOME_POS 0 -// Use "Z Safe Homing" to avoid homing with a Z probe outside the bed area. -// -// With this feature enabled: -// -// - Allow Z homing only after X and Y homing AND stepper drivers still enabled. -// - If stepper drivers time out, it will need X and Y homing again before Z homing. -// - Move the Z probe (or nozzle) to a defined XY point before Z Homing. -// - Prevent Z homing when the Z probe is outside bed area. -// +/** + * Use "Z Safe Homing" to avoid homing with a Z probe outside the bed area. + * + * - Moves the Z probe (or nozzle) to a defined XY point before Z homing. + * - Allows Z homing only when XY positions are known and trusted. + * - If stepper drivers sleep, XY homing may be required again before Z homing. + */ //#define Z_SAFE_HOMING #if ENABLED(Z_SAFE_HOMING) diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index a13df514b0..818edecea2 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -30,7 +30,7 @@ * * Basic settings can be found in Configuration.h */ -#define CONFIGURATION_ADV_H_VERSION 02000900 +#define CONFIGURATION_ADV_H_VERSION 02000901 //=========================================================================== //============================= Thermal Settings ============================ diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index f77f3b2f0b..254283da5b 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -617,7 +617,7 @@ inline void manage_inactivity(const bool no_stepper_sleep=false) { TERN_(USE_CONTROLLER_FAN, controllerFan.update()); // Check if fan should be turned on to cool stepper drivers down - TERN_(AUTO_POWER_CONTROL, powerManager.check()); + TERN_(AUTO_POWER_CONTROL, powerManager.check(!ui.on_status_screen() || printJobOngoing() || printingIsPaused())); TERN_(HOTEND_IDLE_TIMEOUT, hotend_idle.check()); diff --git a/Marlin/src/MarlinCore.h b/Marlin/src/MarlinCore.h index 01a1be4d59..243811d7fb 100644 --- a/Marlin/src/MarlinCore.h +++ b/Marlin/src/MarlinCore.h @@ -91,7 +91,11 @@ extern bool wait_for_heatup; #define PSU_OFF_SOON() powerManager.power_off_soon() #else #define PSU_ON() PSU_PIN_ON() - #define PSU_OFF() PSU_PIN_OFF() + #if ENABLED(PS_OFF_SOUND) + #define PSU_OFF() do{ BUZZ(1000, 659); PSU_PIN_OFF(); }while(0) + #else + #define PSU_OFF() PSU_PIN_OFF() + #endif #define PSU_OFF_SOON PSU_OFF #endif #endif diff --git a/Marlin/src/feature/power.cpp b/Marlin/src/feature/power.cpp index 519b2308d0..9b173d6ee7 100644 --- a/Marlin/src/feature/power.cpp +++ b/Marlin/src/feature/power.cpp @@ -50,6 +50,9 @@ Power powerManager; millis_t Power::lastPowerOn; bool Power::is_power_needed() { + + if (printJobOngoing() || printingIsPaused()) return true; + #if ENABLED(AUTO_POWER_FANS) FANS_LOOP(i) if (thermalManager.fan_speed[i]) return true; #endif @@ -110,9 +113,17 @@ bool Power::is_power_needed() { #define POWER_TIMEOUT 0 #endif -void Power::check() { +void Power::check(const bool pause) { + static bool _pause = false; static millis_t nextPowerCheck = 0; - millis_t now = millis(); + const millis_t now = millis(); + #if POWER_TIMEOUT > 0 + if (pause != _pause) { + lastPowerOn = now + !now; + _pause = pause; + } + if (pause) return; + #endif if (ELAPSED(now, nextPowerCheck)) { nextPowerCheck = now + 2500UL; if (is_power_needed()) @@ -123,7 +134,8 @@ void Power::check() { } void Power::power_on() { - lastPowerOn = millis(); + const millis_t now = millis(); + lastPowerOn = now + !now; if (!powersupply_on) { PSU_PIN_ON(); safe_delay(PSU_POWERUP_DELAY); @@ -152,6 +164,7 @@ void Power::power_off() { void Power::power_off_soon() { #if POWER_OFF_DELAY lastPowerOn = millis() - SEC_TO_MS(POWER_TIMEOUT) + SEC_TO_MS(POWER_OFF_DELAY); + //if (!lastPowerOn) ++lastPowerOn; #else power_off(); #endif diff --git a/Marlin/src/feature/power.h b/Marlin/src/feature/power.h index 2462b9231b..bca5432946 100644 --- a/Marlin/src/feature/power.h +++ b/Marlin/src/feature/power.h @@ -29,7 +29,7 @@ class Power { public: - static void check(); + static void check(const bool pause); static void power_on(); static void power_off(); static void power_off_soon(); diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index 224729d35f..36ccbee9b9 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -593,24 +593,29 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L #endif #ifdef PTC_SAMPLE_START - constexpr int _ptc_sample_start = PTC_SAMPLE_START; - static_assert(_test_ptc_sample_start != PTC_SAMPLE_START, "PTC_SAMPLE_START must be a whole number."); + constexpr auto _ptc_sample_start = PTC_SAMPLE_START; + constexpr decltype(_ptc_sample_start) _test_ptc_sample_start = 12.3f; + static_assert(_test_ptc_sample_start != 12.3f, "PTC_SAMPLE_START must be a whole number."); #endif #ifdef PTC_SAMPLE_RES - constexpr int _ptc_sample_res = PTC_SAMPLE_END; - static_assert(_test_ptc_sample_res != PTC_SAMPLE_END, "PTC_SAMPLE_RES must be a whole number."); + constexpr auto _ptc_sample_res = PTC_SAMPLE_RES; + constexpr decltype(_ptc_sample_res) _test_ptc_sample_res = 12.3f; + static_assert(_test_ptc_sample_res != 12.3f, "PTC_SAMPLE_RES must be a whole number."); #endif #ifdef BTC_SAMPLE_START - constexpr int _btc_sample_start = BTC_SAMPLE_START; - static_assert(_test_btc_sample_start != BTC_SAMPLE_START, "BTC_SAMPLE_START must be a whole number."); + constexpr auto _btc_sample_start = BTC_SAMPLE_START; + constexpr decltype(_btc_sample_start) _test_btc_sample_start = 12.3f; + static_assert(_test_btc_sample_start != 12.3f, "BTC_SAMPLE_START must be a whole number."); #endif #ifdef BTC_SAMPLE_RES - constexpr int _btc_sample_res = BTC_SAMPLE_END; - static_assert(_test_btc_sample_res != BTC_SAMPLE_END, "BTC_SAMPLE_RES must be a whole number."); + constexpr _btc_sample_res = BTC_SAMPLE_RES; + constexpr decltype(_btc_sample_res) _test_btc_sample_res = 12.3f; + static_assert(_test_btc_sample_res != 12.3f, "BTC_SAMPLE_RES must be a whole number."); #endif #ifdef BTC_PROBE_TEMP - constexpr int _btc_probe_temp = BTC_PROBE_TEMP; - static_assert(_test_btc_probe_temp != BTC_PROBE_TEMP, "BTC_PROBE_TEMP must be a whole number."); + constexpr auto _btc_probe_temp = BTC_PROBE_TEMP; + constexpr decltype(_btc_probe_temp) _test_btc_probe_temp = 12.3f; + static_assert(_test_btc_probe_temp != 12.3f, "BTC_PROBE_TEMP must be a whole number."); #endif #endif @@ -3583,6 +3588,13 @@ static_assert( _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2) #error "Either enable MEATPACK_ON_SERIAL_PORT_* or BINARY_FILE_TRANSFER, not both." #endif +/** + * Sanity Check for Slim LCD Menus and Probe Offset Wizard + */ +#if BOTH(SLIM_LCD_MENUS, PROBE_OFFSET_WIZARD) + #error "SLIM_LCD_MENUS disables \"Advanced Settings > Probe Offsets > PROBE_OFFSET_WIZARD.\"" +#endif + /** * Sanity check for unique start and stop values in NOZZLE_CLEAN_FEATURE */ diff --git a/Marlin/src/inc/Version.h b/Marlin/src/inc/Version.h index 77330b82e2..5fdcc7c728 100644 --- a/Marlin/src/inc/Version.h +++ b/Marlin/src/inc/Version.h @@ -42,7 +42,7 @@ * version was tagged. */ #ifndef STRING_DISTRIBUTION_DATE - #define STRING_DISTRIBUTION_DATE "2021-06-28" + #define STRING_DISTRIBUTION_DATE "2021-07-05" #endif /** @@ -52,7 +52,7 @@ * to alert users to major changes. */ -#define MARLIN_HEX_VERSION 02000900 +#define MARLIN_HEX_VERSION 02000901 #ifndef REQUIRED_CONFIGURATION_H_VERSION #define REQUIRED_CONFIGURATION_H_VERSION MARLIN_HEX_VERSION #endif diff --git a/Marlin/src/lcd/extui/ftdi_eve_touch_ui/archim2-flash/flash_storage.cpp b/Marlin/src/lcd/extui/ftdi_eve_touch_ui/archim2-flash/flash_storage.cpp index a64c237fa1..1d4711c0e2 100644 --- a/Marlin/src/lcd/extui/ftdi_eve_touch_ui/archim2-flash/flash_storage.cpp +++ b/Marlin/src/lcd/extui/ftdi_eve_touch_ui/archim2-flash/flash_storage.cpp @@ -20,7 +20,7 @@ * location: . * ****************************************************************************/ -#include "../compat.h" +#include "../config.h" #if ENABLED(TOUCH_UI_FTDI_EVE) diff --git a/Marlin/src/lcd/extui/ftdi_eve_touch_ui/archim2-flash/media_file_reader.cpp b/Marlin/src/lcd/extui/ftdi_eve_touch_ui/archim2-flash/media_file_reader.cpp index ea8d403753..b4165a742a 100644 --- a/Marlin/src/lcd/extui/ftdi_eve_touch_ui/archim2-flash/media_file_reader.cpp +++ b/Marlin/src/lcd/extui/ftdi_eve_touch_ui/archim2-flash/media_file_reader.cpp @@ -20,7 +20,7 @@ * location: . * ****************************************************************************/ -#include "../compat.h" +#include "../config.h" #if ENABLED(TOUCH_UI_FTDI_EVE) #include "media_file_reader.h" diff --git a/Marlin/src/lcd/extui/ftdi_eve_touch_ui/config.h b/Marlin/src/lcd/extui/ftdi_eve_touch_ui/config.h index 76b231536b..05e19b20e4 100644 --- a/Marlin/src/lcd/extui/ftdi_eve_touch_ui/config.h +++ b/Marlin/src/lcd/extui/ftdi_eve_touch_ui/config.h @@ -21,6 +21,10 @@ #pragma once +// Configure this display with options in Configuration_adv.h +#include "../../../inc/MarlinConfigPre.h" +#if ENABLED(TOUCH_UI_FTDI_EVE) + #include "compat.h" -// Configure this display with options in Configuration_adv.h +#endif diff --git a/Marlin/src/lcd/extui/ftdi_eve_touch_ui/screens.h b/Marlin/src/lcd/extui/ftdi_eve_touch_ui/screens.h index f118303d21..4fe4cb938a 100644 --- a/Marlin/src/lcd/extui/ftdi_eve_touch_ui/screens.h +++ b/Marlin/src/lcd/extui/ftdi_eve_touch_ui/screens.h @@ -22,7 +22,7 @@ #pragma once -#include "compat.h" +#include "config.h" #if ENABLED(TOUCH_UI_FTDI_EVE) diff --git a/Marlin/src/lcd/extui/ftdi_eve_touch_ui/theme/sounds.cpp b/Marlin/src/lcd/extui/ftdi_eve_touch_ui/theme/sounds.cpp index afbed0cab1..1ed712ded1 100644 --- a/Marlin/src/lcd/extui/ftdi_eve_touch_ui/theme/sounds.cpp +++ b/Marlin/src/lcd/extui/ftdi_eve_touch_ui/theme/sounds.cpp @@ -20,7 +20,7 @@ * location: . * ****************************************************************************/ -#include "../compat.h" +#include "../config.h" #if ENABLED(TOUCH_UI_FTDI_EVE) diff --git a/Marlin/src/lcd/marlinui.cpp b/Marlin/src/lcd/marlinui.cpp index 67e5adc0f0..6741de3e40 100644 --- a/Marlin/src/lcd/marlinui.cpp +++ b/Marlin/src/lcd/marlinui.cpp @@ -120,1169 +120,1169 @@ constexpr uint8_t epps = ENCODER_PULSES_PER_STEP; } #endif -#if HAS_WIRED_LCD - -#if HAS_MARLINUI_U8GLIB - #include "dogm/marlinui_DOGM.h" -#endif - -#include "lcdprint.h" - -#include "../sd/cardreader.h" - -#include "../module/temperature.h" -#include "../module/planner.h" -#include "../module/motion.h" - -#if HAS_LCD_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 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 - -// 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 ENABLED(SDSUPPORT) - - #include "../sd/cardreader.h" - - #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 EITHER(HAS_LCD_MENU, EXTENSIBLE_UI) bool MarlinUI::lcd_clicked; #endif -#if HAS_LCD_MENU - #include "menu/menu.h" +#if HAS_WIRED_LCD - 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; - } + #if HAS_MARLINUI_U8GLIB + #include "dogm/marlinui_DOGM.h" #endif - #if EITHER(REVERSE_MENU_DIRECTION, REVERSE_SELECT_DIRECTION) - int8_t MarlinUI::encoderDirection = ENCODERBASE; + #include "lcdprint.h" + + #include "../sd/cardreader.h" + + #include "../module/temperature.h" + #include "../module/planner.h" + #include "../module/motion.h" + + #if HAS_LCD_MENU + #include "../module/settings.h" #endif - #if HAS_TOUCH_BUTTONS - uint8_t MarlinUI::touch_buttons; - uint8_t MarlinUI::repeat_delay; + #if ENABLED(AUTO_BED_LEVELING_UBL) + #include "../feature/bedlevel/bedlevel.h" #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); - } - + #if HAS_TRINAMIC_CONFIG + #include "../feature/tmc_util.h" #endif - #if !HAS_GRAPHICAL_TFT + #if HAS_ADC_BUTTONS + #include "../module/thermistor/thermistors.h" + #endif - 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; + #if HAS_POWER_MONITOR + #include "../feature/power_monitor.h" + #endif - auto _newline = [&col, &row]{ - col = 0; row++; // Move col to string len (plus space) - SETCURSOR(0, row); // Simulate carriage return - }; + #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 - uint8_t *p = (uint8_t*)string; - wchar_t ch; - if (wordwrap) { - uint8_t *wrd = nullptr, 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, &ch); - const bool eol = !ch; // zero ends the string - // End or a break between phrases? - if (eol || ch == ' ' || ch == '-' || ch == '+' || ch == '.') { - if (!c && ch == ' ') { 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, &ch); // get characters again - lcd_put_wchar(ch); // character to the LCD + #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 + + // 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 ENABLED(SDSUPPORT) + + #include "../sd/cardreader.h" + + #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 } - 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, &ch); - if (!ch) break; - lcd_put_wchar(ch); - col++; - if (col >= LCD_WIDTH) _newline(); - } - } - } - - void MarlinUI::draw_select_screen_prompt(PGM_P const pref, const char * const string/*=nullptr*/, PGM_P const suff/*=nullptr*/) { - const uint8_t plen = utf8_strlen_P(pref), slen = suff ? utf8_strlen_P(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; - } - wrap_string_P(col, row, 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, suff); - } - - #endif // !HAS_GRAPHICAL_TFT - -#endif // HAS_LCD_MENU - -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); -} - -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; -} - -//////////////////////////////////////////// -///////////// Keypad Handling ////////////// -//////////////////////////////////////////// - -#if IS_RRW_KEYPAD && HAS_ENCODER_ACTION - - volatile uint8_t MarlinUI::keypad_buttons; - - #if HAS_LCD_MENU && !HAS_ADC_BUTTONS - - void lcd_move_x(); - void lcd_move_y(); - void lcd_move_z(); - - 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: lcd_move_x(); break; - case Y_AXIS: lcd_move_y(); break; - case Z_AXIS: lcd_move_z(); - 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_LCD_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_LCD_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 + // 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 - next_button_update_ms = millis() + ADC_MIN_KEY_DELAY; - return true; } + return outstr; + } - #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_LCD_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_LCD_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() { + #if HAS_LCD_MENU + #include "menu/menu.h" - TERN_(HAS_LCD_MENU, ENCODER_RATE_MULTIPLY(false)); + screenFunc_t MarlinUI::currentScreen; // Initialized in CTOR + bool MarlinUI::screen_changed; - #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(); + #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 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; + #if EITHER(REVERSE_MENU_DIRECTION, REVERSE_SELECT_DIRECTION) + int8_t MarlinUI::encoderDirection = ENCODERBASE; #endif - #if PROGRESS_MSG_EXPIRE > 0 + #if HAS_TOUCH_BUTTONS + uint8_t MarlinUI::touch_buttons; + uint8_t MarlinUI::repeat_delay; + #endif - // Handle message expire - if (expire_status_ms) { + #if EITHER(AUTO_BED_LEVELING_UBL, G26_MESH_VALIDATION) - // 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; + 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 + }; + + uint8_t *p = (uint8_t*)string; + wchar_t ch; + if (wordwrap) { + uint8_t *wrd = nullptr, 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, &ch); + const bool eol = !ch; // zero ends the string + // End or a break between phrases? + if (eol || ch == ' ' || ch == '-' || ch == '+' || ch == '.') { + if (!c && ch == ' ') { 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, &ch); // get characters again + lcd_put_wchar(ch); // character to the LCD + } + if (eol) break; // all done! + wrd = nullptr; // set up for next word + } + else c++; // count word characters } } 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 - - #if HAS_LCD_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); - #if DISABLED(NO_LCD_REINIT) - init_lcd(); // May revive the LCD if static electricity killed it - #endif - 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_BUZZER, 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(PGM_P lcd_error, PGM_P lcd_component) { - init(); - status_printf_P(1, PSTR(S_FMT ": " S_FMT), lcd_error, lcd_component); - TERN_(HAS_LCD_MENU, return_to_status()); - - // RED ALERT. RED ALERT. - #ifdef LED_BACKLIGHT_TIMEOUT - leds.set_color(LEDColorRed()); - #ifdef NEOPIXEL_BKGD_INDEX_FIRST - neo.set_background_color(255, 0, 0, 0); - neo.show(); - #endif - #endif - - draw_kill_screen(); -} - -void MarlinUI::quick_feedback(const bool clear_buttons/*=true*/) { - - TERN_(HAS_LCD_MENU, refresh()); - - #if HAS_ENCODER_ACTION - if (clear_buttons) buttons = 0; - next_button_update_ms = millis() + 500; - #else - UNUSED(clear_buttons); - #endif - - #if HAS_CHIRP - chirp(); // Buzz and wait. Is the delay needed for buttons to settle? - #if BOTH(HAS_LCD_MENU, USE_BEEPER) - for (int8_t i = 5; i--;) { buzzer.tick(); delay(2); } - #elif HAS_LCD_MENU - delay(10); - #endif - #endif -} - -//////////////////////////////////////////// -/////////////// Manual Move //////////////// -//////////////////////////////////////////// - -#if HAS_LCD_MENU - - ManualMove MarlinUI::manual_move{}; - - millis_t ManualMove::start_time = 0; - float ManualMove::menu_scale = 1; - #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 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. - * - * Currently used by the _lcd_move_xyz function in menu_motion.cpp - * and the ubl_map_move_to_xy funtion in menu_ubl.cpp. - */ - 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_ECHOLNPAIR("Add planner.move with Axis ", AS_CHAR(axis_codes[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_ECHOLNPAIR("Post Move with Axis ", AS_CHAR(axis_codes[axis]), " soon."); - } - - #if ENABLED(AUTO_BED_LEVELING_UBL) - - void MarlinUI::external_encoder() { - if (external_control && encoderDiff) { - ubl.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_LCD_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(); - - #ifdef LED_BACKLIGHT_TIMEOUT - leds.update_timeout(powersupply_on); - #endif - - #if HAS_LCD_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(); - } + for (;;) { + p = get_utf8_value_cb(p, cb_read_byte, &ch); + if (!ch) break; + lcd_put_wchar(ch); + col++; + if (col >= LCD_WIDTH) _newline(); } } - 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 + void MarlinUI::draw_select_screen_prompt(PGM_P const pref, const char * const string/*=nullptr*/, PGM_P const suff/*=nullptr*/) { + const uint8_t plen = utf8_strlen_P(pref), slen = suff ? utf8_strlen_P(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; + } + wrap_string_P(col, row, 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, suff); } - else - wait_for_unclick = false; - } - if (LCD_BACK_CLICKED()) { - quick_feedback(); - goto_previous_screen(); - } + #endif // !HAS_GRAPHICAL_TFT #endif // HAS_LCD_MENU - if (ELAPSED(ms, next_lcd_update_ms) || TERN0(HAS_MARLINUI_U8GLIB, drawing_screen)) { + void MarlinUI::init() { - next_lcd_update_ms = ms + LCD_UPDATE_INTERVAL; + init_lcd(); - #if HAS_TOUCH_BUTTONS + #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 (on_status_screen()) next_lcd_update_ms += (LCD_UPDATE_INTERVAL) * 2; + #if HAS_SHIFT_ENCODER - TERN_(HAS_ENCODER_ACTION, touch_buttons = touch.read_buttons()); + #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); + } + + 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; + } + + //////////////////////////////////////////// + ///////////// Keypad Handling ////////////// + //////////////////////////////////////////// + + #if IS_RRW_KEYPAD && HAS_ENCODER_ACTION + + volatile uint8_t MarlinUI::keypad_buttons; + + #if HAS_LCD_MENU && !HAS_ADC_BUTTONS + + void lcd_move_x(); + void lcd_move_y(); + void lcd_move_z(); + + 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: lcd_move_x(); break; + case Y_AXIS: lcd_move_y(); break; + case Z_AXIS: lcd_move_z(); + default: break; + } + } #endif - TERN_(LCD_HAS_STATUS_INDICATORS, update_indicators()); + bool MarlinUI::handle_keypad() { - #if HAS_ENCODER_ACTION + #if HAS_ADC_BUTTONS - 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. + #define ADC_MIN_KEY_DELAY 100 + if (keypad_buttons) { + #if HAS_ENCODER_ACTION + refresh(LCDVIEW_REDRAW_NOW); + #if HAS_LCD_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_LCD_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; } - lastEncoderDiff = encoderDiff; + + #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_LCD_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_LCD_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_LCD_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 - const bool encoderPastThreshold = (abs_diff >= epps); - if (encoderPastThreshold || lcd_clicked) { - if (encoderPastThreshold && TERN1(IS_TFTGLCD_PANEL, !external_control)) { + // 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 BOTH(HAS_LCD_MENU, ENCODER_RATE_MULTIPLIER) + #if PROGRESS_MSG_EXPIRE > 0 - int32_t encoderMultiplier = 1; + // Handle message expire + if (expire_status_ms) { - 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_ECHOPAIR("Enc Step Rate: ", encoderStepRate); - SERIAL_ECHOPAIR(" Multiplier: ", encoderMultiplier); - SERIAL_ECHOPAIR(" ENCODER_10X_STEPS_PER_SEC: ", ENCODER_10X_STEPS_PER_SEC); - SERIAL_ECHOPAIR(" 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; + // 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; + } } - reset_status_timeout(ms); + #endif // PROGRESS_MSG_EXPIRE - refresh(LCDVIEW_REDRAW_NOW); + #endif // BASIC_PROGRESS_BAR - #ifdef LED_BACKLIGHT_TIMEOUT - if (!powersupply_on) leds.reset_timeout(ms); + #if HAS_LCD_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); + #if DISABLED(NO_LCD_REINIT) + init_lcd(); // May revive the LCD if static electricity killed it + #endif + 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_BUZZER, 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(PGM_P lcd_error, PGM_P lcd_component) { + init(); + status_printf_P(1, PSTR(S_FMT ": " S_FMT), lcd_error, lcd_component); + TERN_(HAS_LCD_MENU, return_to_status()); + + // RED ALERT. RED ALERT. + #ifdef LED_BACKLIGHT_TIMEOUT + leds.set_color(LEDColorRed()); + #ifdef NEOPIXEL_BKGD_INDEX_FIRST + neo.set_background_color(255, 0, 0, 0); + neo.show(); + #endif #endif - // 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); + draw_kill_screen(); + } + + void MarlinUI::quick_feedback(const bool clear_buttons/*=true*/) { + + TERN_(HAS_LCD_MENU, refresh()); + + #if HAS_ENCODER_ACTION + if (clear_buttons) buttons = 0; + next_button_update_ms = millis() + 500; + #else + UNUSED(clear_buttons); + #endif + + #if HAS_CHIRP + chirp(); // Buzz and wait. Is the delay needed for buttons to settle? + #if BOTH(HAS_LCD_MENU, USE_BEEPER) + for (int8_t i = 5; i--;) { buzzer.tick(); delay(2); } + #elif HAS_LCD_MENU + delay(10); + #endif + #endif + } + + //////////////////////////////////////////// + /////////////// Manual Move //////////////// + //////////////////////////////////////////// + + #if HAS_LCD_MENU + + ManualMove MarlinUI::manual_move{}; + + millis_t ManualMove::start_time = 0; + float ManualMove::menu_scale = 1; + #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 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. + * + * Currently used by the _lcd_move_xyz function in menu_motion.cpp + * and the ubl_map_move_to_xy funtion in menu_ubl.cpp. + */ + 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_ECHOLNPAIR("Add planner.move with Axis ", AS_CHAR(axis_codes[axis]), " at FR ", fr_mm_s); + + axis = NO_AXIS_ENUM; + + #endif + } } - #if BOTH(HAS_LCD_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); + // + // 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_ECHOLNPAIR("Post Move with Axis ", AS_CHAR(axis_codes[axis]), " soon."); + } + + #if ENABLED(AUTO_BED_LEVELING_UBL) + + void MarlinUI::external_encoder() { + if (external_control && encoderDiff) { + ubl.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 - // Then we want to use only 50% of the time - const uint16_t bbr2 = planner.block_buffer_runtime() >> 1; + #endif // HAS_LCD_MENU - if ((should_draw() || drawing_screen) && (!bbr2 || bbr2 > max_display_update_time)) { + /** + * 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(); + + #ifdef LED_BACKLIGHT_TIMEOUT + leds.update_timeout(powersupply_on); + #endif + + #if HAS_LCD_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_LCD_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 = touch.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. + } + lastEncoderDiff = encoderDiff; + #endif + + const bool encoderPastThreshold = (abs_diff >= epps); + if (encoderPastThreshold || lcd_clicked) { + if (encoderPastThreshold && TERN1(IS_TFTGLCD_PANEL, !external_control)) { + + #if BOTH(HAS_LCD_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_ECHOPAIR("Enc Step Rate: ", encoderStepRate); + SERIAL_ECHOPAIR(" Multiplier: ", encoderMultiplier); + SERIAL_ECHOPAIR(" ENCODER_10X_STEPS_PER_SEC: ", ENCODER_10X_STEPS_PER_SEC); + SERIAL_ECHOPAIR(" 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); + + refresh(LCDVIEW_REDRAW_NOW); + + #ifdef LED_BACKLIGHT_TIMEOUT + if (!powersupply_on) leds.reset_timeout(ms); + #endif + } + + #endif + + // 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_LCD_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(); + + #endif + + TERN_(HAS_LCD_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 // 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 + clear_lcd(); break; + case LCDVIEW_REDRAW_NOW: + refresh(LCDVIEW_NONE); case LCDVIEW_NONE: - break; + case LCDVIEW_CALL_REDRAW_NEXT: + case LCDVIEW_CALL_NO_REDRAW: + default: break; } // switch - TERN_(HAS_ADC_BUTTONS, keypad_buttons = 0); + } // ELAPSED(ms, next_lcd_update_ms) - #if HAS_MARLINUI_U8GLIB + TERN_(HAS_GRAPHICAL_TFT, tft_idle()); + } - #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 HAS_ADC_BUTTONS - 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; + typedef struct { + uint16_t ADCKeyValueMin, ADCKeyValueMax; + uint8_t ADCKeyNo; + } _stADCKeypadTable_; - // 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(); - - #endif - - TERN_(HAS_LCD_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(); + #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 - // 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 + // Calculate the ADC value for the voltage divider with specified pull-down resistor value + #define ADC_BUTTON_VALUE(r) int(HAL_ADC_RANGE * (ADC_BUTTONS_VALUE_SCALE) * r / (r + ADC_BUTTONS_R_PULLUP)) - } // ELAPSED(ms, next_lcd_update_ms) + static constexpr uint16_t adc_button_tolerance = HAL_ADC_RANGE * 25 / 1024, + adc_other_button = HAL_ADC_RANGE * 1000 / 1024; + 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) + }; - TERN_(HAS_GRAPHICAL_TFT, tft_idle()); -} - -#if HAS_ADC_BUTTONS - - typedef struct { - uint16_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) int(HAL_ADC_RANGE * (ADC_BUTTONS_VALUE_SCALE) * r / (r + ADC_BUTTONS_R_PULLUP)) - - static constexpr uint16_t adc_button_tolerance = HAL_ADC_RANGE * 25 / 1024, - adc_other_button = HAL_ADC_RANGE * 1000 / 1024; - 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 uint16_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 uint16_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; + uint8_t get_ADC_keyValue() { + if (thermalManager.ADCKey_count >= 16) { + const uint16_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 uint16_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); } - 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_LCD_MENU, AUTO_BED_LEVELING_UBL) - external_encoder(); - #endif - lastEncoderBits = enc; } + return 0; + } - #endif // HAS_ENCODER_WHEEL - } + #endif // HAS_ADC_BUTTONS -#endif // HAS_ENCODER_ACTION + #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_LCD_MENU, AUTO_BED_LEVELING_UBL) + external_encoder(); + #endif + lastEncoderBits = enc; + } + + #endif // HAS_ENCODER_WHEEL + } + + #endif // HAS_ENCODER_ACTION #endif // HAS_WIRED_LCD diff --git a/Marlin/src/lcd/marlinui.h b/Marlin/src/lcd/marlinui.h index 87ecc48366..7531fae674 100644 --- a/Marlin/src/lcd/marlinui.h +++ b/Marlin/src/lcd/marlinui.h @@ -529,10 +529,13 @@ public: static void draw_select_screen_prompt(PGM_P const pref, const char * const string=nullptr, PGM_P const suff=nullptr); - #elif HAS_WIRED_LCD + #else static constexpr bool on_status_screen() { return true; } - FORCE_INLINE static void run_current_screen() { status_screen(); } + + #if HAS_WIRED_LCD + FORCE_INLINE static void run_current_screen() { status_screen(); } + #endif #endif diff --git a/Marlin/src/lcd/menu/menu_filament.cpp b/Marlin/src/lcd/menu/menu_filament.cpp index c6b8568085..d70ed98aa8 100644 --- a/Marlin/src/lcd/menu/menu_filament.cpp +++ b/Marlin/src/lcd/menu/menu_filament.cpp @@ -106,10 +106,11 @@ void _menu_temp_filament_op(const PauseMode mode, const int8_t extruder) { * "Change Filament" submenu */ #if E_STEPPERS > 1 || ENABLED(FILAMENT_LOAD_UNLOAD_GCODES) - bool printingIsPaused(); +#endif - void menu_change_filament() { +void menu_change_filament() { + #if E_STEPPERS > 1 || ENABLED(FILAMENT_LOAD_UNLOAD_GCODES) // Say "filament change" when no print is active editable.int8 = printingIsPaused() ? PAUSE_MODE_PAUSE_PRINT : PAUSE_MODE_CHANGE_FILAMENT; @@ -204,8 +205,16 @@ void _menu_temp_filament_op(const PauseMode mode, const int8_t extruder) { #endif END_MENU(); - } -#endif + + #else + + if (thermalManager.targetHotEnoughToExtrude(active_extruder)) + queue.inject_P(PSTR("M600B0")); + else + _menu_temp_filament_op(PAUSE_MODE_CHANGE_FILAMENT, 0); + + #endif +} static uint8_t hotend_status_extruder = 0; diff --git a/Marlin/src/lcd/menu/menu_main.cpp b/Marlin/src/lcd/menu/menu_main.cpp index 1e79edb864..4e3310f238 100644 --- a/Marlin/src/lcd/menu/menu_main.cpp +++ b/Marlin/src/lcd/menu/menu_main.cpp @@ -77,7 +77,6 @@ void menu_configuration(); #endif #if ENABLED(ADVANCED_PAUSE_FEATURE) - void _menu_temp_filament_op(const PauseMode, const int8_t); void menu_change_filament(); #endif @@ -365,10 +364,11 @@ void menu_main() { #if ENABLED(ADVANCED_PAUSE_FEATURE) #if E_STEPPERS == 1 && DISABLED(FILAMENT_LOAD_UNLOAD_GCODES) - if (thermalManager.targetHotEnoughToExtrude(active_extruder)) - GCODES_ITEM(MSG_FILAMENTCHANGE, PSTR("M600 B0")); - else - SUBMENU(MSG_FILAMENTCHANGE, []{ _menu_temp_filament_op(PAUSE_MODE_CHANGE_FILAMENT, 0); }); + CONFIRM_ITEM(MSG_FILAMENTCHANGE, + MSG_YES, MSG_NO, + menu_change_filament, ui.goto_previous_screen, + GET_TEXT(MSG_FILAMENTCHANGE), (const char *)nullptr, PSTR("?") + ); #else SUBMENU(MSG_FILAMENTCHANGE, menu_change_filament); #endif diff --git a/Marlin/src/pins/lpc1769/pins_BTT_SKR_E3_TURBO.h b/Marlin/src/pins/lpc1769/pins_BTT_SKR_E3_TURBO.h index 1e7b3f02a2..4719dd8111 100644 --- a/Marlin/src/pins/lpc1769/pins_BTT_SKR_E3_TURBO.h +++ b/Marlin/src/pins/lpc1769/pins_BTT_SKR_E3_TURBO.h @@ -201,7 +201,15 @@ #define EXP1_09_PIN P0_16 #define EXP1_10_PIN P2_08 -#if HAS_WIRED_LCD +#if ENABLED(DWIN_CREALITY_LCD) + #error "DWIN_CREALITY_LCD requires a custom cable with TX = P0_15, RX = P0_16, and LCD_SERIAL_PORT 1. Comment out this line to continue." + + #define BEEPER_PIN EXP1_10_PIN + #define BTN_EN1 EXP1_03_PIN + #define BTN_EN2 EXP1_04_PIN + #define BTN_ENC EXP1_06_PIN + +#elif HAS_WIRED_LCD #if ENABLED(CR10_STOCKDISPLAY) diff --git a/buildroot/share/PlatformIO/boards/marlin_blackSTM32F407VET6.json b/buildroot/share/PlatformIO/boards/marlin_blackSTM32F407VET6.json index 1765634086..a3f130c6b1 100644 --- a/buildroot/share/PlatformIO/boards/marlin_blackSTM32F407VET6.json +++ b/buildroot/share/PlatformIO/boards/marlin_blackSTM32F407VET6.json @@ -48,7 +48,7 @@ "upload": { "disable_flushing": false, "maximum_ram_size": 131072, - "maximum_size": 514288, + "maximum_size": 524288, "protocol": "stlink", "protocols": [ "stlink", diff --git a/buildroot/share/PlatformIO/scripts/custom_board.py b/buildroot/share/PlatformIO/scripts/custom_board.py index 5d3ca3c652..e462738190 100644 --- a/buildroot/share/PlatformIO/scripts/custom_board.py +++ b/buildroot/share/PlatformIO/scripts/custom_board.py @@ -1,6 +1,9 @@ # # buildroot/share/PlatformIO/scripts/custom_board.py # +# - For build.address replace VECT_TAB_ADDR to relocate the firmware +# - For build.ldscript use one of the linker scripts in buildroot/share/PlatformIO/ldscripts +# import marlin board = marlin.env.BoardConfig() diff --git a/buildroot/share/PlatformIO/scripts/marlin.py b/buildroot/share/PlatformIO/scripts/marlin.py index 23c1b95742..3949037904 100644 --- a/buildroot/share/PlatformIO/scripts/marlin.py +++ b/buildroot/share/PlatformIO/scripts/marlin.py @@ -67,11 +67,3 @@ def encrypt_mks(source, target, env, new_name): def add_post_action(action): env.AddPostAction(join("$BUILD_DIR", "${PROGNAME}.bin"), action); - -# Apply customizations for a MKS Robin -def prepare_robin(address, ldname, fwname): - def encrypt(source, target, env): - encrypt_mks(source, target, env, fwname) - relocate_firmware(address) - custom_ld_script(ldname) - add_post_action(encrypt); diff --git a/buildroot/share/PlatformIO/scripts/mks_robin.py b/buildroot/share/PlatformIO/scripts/mks_robin.py index 8c5e4ae276..2dea7c615f 100644 --- a/buildroot/share/PlatformIO/scripts/mks_robin.py +++ b/buildroot/share/PlatformIO/scripts/mks_robin.py @@ -1,5 +1,5 @@ # # buildroot/share/PlatformIO/scripts/mks_robin.py # -import marlin -marlin.prepare_robin("0x08007000", "mks_robin.ld", "Robin.bin") +import robin +robin.prepare("0x08007000", "mks_robin.ld", "Robin.bin") diff --git a/buildroot/share/PlatformIO/scripts/mks_robin_e3.py b/buildroot/share/PlatformIO/scripts/mks_robin_e3.py index 7f6f538d6a..6ddeccbf80 100644 --- a/buildroot/share/PlatformIO/scripts/mks_robin_e3.py +++ b/buildroot/share/PlatformIO/scripts/mks_robin_e3.py @@ -1,5 +1,5 @@ # # buildroot/share/PlatformIO/scripts/mks_robin_e3.py # -import marlin -marlin.prepare_robin("0x08005000", "mks_robin_e3.ld", "Robin_e3.bin") +import robin +robin.prepare("0x08005000", "mks_robin_e3.ld", "Robin_e3.bin") diff --git a/buildroot/share/PlatformIO/scripts/mks_robin_e3p.py b/buildroot/share/PlatformIO/scripts/mks_robin_e3p.py index 1f3cacf873..5eeb93c096 100644 --- a/buildroot/share/PlatformIO/scripts/mks_robin_e3p.py +++ b/buildroot/share/PlatformIO/scripts/mks_robin_e3p.py @@ -1,5 +1,5 @@ # # buildroot/share/PlatformIO/scripts/mks_robin_e3p.py # -import marlin -marlin.prepare_robin("0x08007000", "mks_robin_e3p.ld", "Robin_e3p.bin") +import robin +robin.prepare("0x08007000", "mks_robin_e3p.ld", "Robin_e3p.bin") diff --git a/buildroot/share/PlatformIO/scripts/mks_robin_lite.py b/buildroot/share/PlatformIO/scripts/mks_robin_lite.py index b8c039ada8..c2018336fd 100644 --- a/buildroot/share/PlatformIO/scripts/mks_robin_lite.py +++ b/buildroot/share/PlatformIO/scripts/mks_robin_lite.py @@ -1,5 +1,5 @@ # # buildroot/share/PlatformIO/scripts/mks_robin_lite.py # -import marlin -marlin.prepare_robin("0x08005000", "mks_robin_lite.ld", "mksLite.bin") +import robin +robin.prepare("0x08005000", "mks_robin_lite.ld", "mksLite.bin") diff --git a/buildroot/share/PlatformIO/scripts/mks_robin_lite3.py b/buildroot/share/PlatformIO/scripts/mks_robin_lite3.py index bea8b80ace..42c8fb18b6 100644 --- a/buildroot/share/PlatformIO/scripts/mks_robin_lite3.py +++ b/buildroot/share/PlatformIO/scripts/mks_robin_lite3.py @@ -1,5 +1,5 @@ # # buildroot/share/PlatformIO/scripts/mks_robin_lite3.py # -import marlin -marlin.prepare_robin("0x08005000", "mks_robin_lite.ld", "mksLite3.bin") +import robin +robin.prepare("0x08005000", "mks_robin_lite.ld", "mksLite3.bin") diff --git a/buildroot/share/PlatformIO/scripts/mks_robin_mini.py b/buildroot/share/PlatformIO/scripts/mks_robin_mini.py index 3ff9ccf4a6..b0d8388653 100644 --- a/buildroot/share/PlatformIO/scripts/mks_robin_mini.py +++ b/buildroot/share/PlatformIO/scripts/mks_robin_mini.py @@ -1,5 +1,5 @@ # # buildroot/share/PlatformIO/scripts/mks_robin_mini.py # -import marlin -marlin.prepare_robin("0x08007000", "mks_robin_mini.ld", "Robin_mini.bin") +import robin +robin.prepare("0x08007000", "mks_robin_mini.ld", "Robin_mini.bin") diff --git a/buildroot/share/PlatformIO/scripts/mks_robin_nano.py b/buildroot/share/PlatformIO/scripts/mks_robin_nano.py index 319b4d4982..35e99830c4 100644 --- a/buildroot/share/PlatformIO/scripts/mks_robin_nano.py +++ b/buildroot/share/PlatformIO/scripts/mks_robin_nano.py @@ -1,5 +1,5 @@ # # buildroot/share/PlatformIO/scripts/mks_robin_nano.py # -import marlin -marlin.prepare_robin("0x08007000", "mks_robin_nano.ld", "Robin_nano.bin") +import robin +robin.prepare("0x08007000", "mks_robin_nano.ld", "Robin_nano.bin") diff --git a/buildroot/share/PlatformIO/scripts/mks_robin_nano35.py b/buildroot/share/PlatformIO/scripts/mks_robin_nano35.py index 310c3d6606..4a5726ad5b 100644 --- a/buildroot/share/PlatformIO/scripts/mks_robin_nano35.py +++ b/buildroot/share/PlatformIO/scripts/mks_robin_nano35.py @@ -1,5 +1,5 @@ # # buildroot/share/PlatformIO/scripts/mks_robin_nano35.py # -import marlin -marlin.prepare_robin("0x08007000", "mks_robin_nano.ld", "Robin_nano35.bin") +import robin +robin.prepare("0x08007000", "mks_robin_nano.ld", "Robin_nano35.bin") diff --git a/buildroot/share/PlatformIO/scripts/mks_robin_pro.py b/buildroot/share/PlatformIO/scripts/mks_robin_pro.py index c624663a33..60e2482bb0 100644 --- a/buildroot/share/PlatformIO/scripts/mks_robin_pro.py +++ b/buildroot/share/PlatformIO/scripts/mks_robin_pro.py @@ -1,5 +1,5 @@ # # buildroot/share/PlatformIO/scripts/mks_robin_pro.py # -import marlin -marlin.prepare_robin("0x08007000", "mks_robin_pro.ld", "Robin_pro.bin") +import robin +robin.prepare("0x08007000", "mks_robin_pro.ld", "Robin_pro.bin") diff --git a/buildroot/share/PlatformIO/scripts/robin.py b/buildroot/share/PlatformIO/scripts/robin.py new file mode 100644 index 0000000000..50d0d92d2f --- /dev/null +++ b/buildroot/share/PlatformIO/scripts/robin.py @@ -0,0 +1,12 @@ +# +# buildroot/share/PlatformIO/scripts/robin.py +# +import marlin + +# Apply customizations for a MKS Robin +def prepare(address, ldname, fwname): + def encrypt(source, target, env): + marlin.encrypt_mks(source, target, env, fwname) + marlin.relocate_firmware(address) + marlin.custom_ld_script(ldname) + marlin.add_post_action(encrypt); diff --git a/buildroot/share/PlatformIO/scripts/stm32_bootloader.py b/buildroot/share/PlatformIO/scripts/stm32_bootloader.py index eb28b901d2..f3b1b273a2 100644 --- a/buildroot/share/PlatformIO/scripts/stm32_bootloader.py +++ b/buildroot/share/PlatformIO/scripts/stm32_bootloader.py @@ -1,6 +1,13 @@ # # stm32_bootloader.py # +# - If 'build.offset' is provided, either by JSON or by the environment... +# - Set linker flag LD_FLASH_OFFSET and relocate the VTAB based on 'build.offset'. +# - Set linker flag LD_MAX_DATA_SIZE based on 'build.maximum_ram_size'. +# - Define STM32_FLASH_SIZE from 'upload.maximum_size' for use by Flash-based EEPROM emulation. +# +# - For 'board_build.rename' add a post-action to rename the firmware file. +# import os,sys,marlin Import("env") diff --git a/ini/stm32f1.ini b/ini/stm32f1.ini index 3f09a45c7b..0b394d1730 100644 --- a/ini/stm32f1.ini +++ b/ini/stm32f1.ini @@ -41,7 +41,6 @@ board = genericSTM32F103RC monitor_speed = 115200 board_build.core = stm32 board_build.variant = MARLIN_F103Rx -board_build.ldscript = ldscript.ld extra_scripts = ${common_stm32.extra_scripts} pre:buildroot/share/PlatformIO/scripts/generic_create_variant.py buildroot/share/PlatformIO/scripts/stm32_bootloader.py @@ -108,7 +107,6 @@ extends = common_stm32 board = genericSTM32F103ZE board_build.core = stm32 board_build.variant = MARLIN_F103Zx -board_build.ldscript = ldscript.ld board_build.offset = 0x7000 board_build.encrypt = Robin.bin build_flags = ${common_stm32.build_flags} @@ -150,7 +148,6 @@ monitor_speed = 115200 board_build.core = stm32 board_build.variant = MARLIN_F103Rx board_build.offset = 0x7000 -board_build.ldscript = ldscript.ld board_upload.offset_address = 0x08007000 build_unflags = ${common_stm32.build_unflags} -DUSBCON -DUSBD_USE_CDC extra_scripts = ${common.extra_scripts} @@ -175,7 +172,6 @@ monitor_speed = 115200 board_build.core = stm32 board_build.variant = MARLIN_F103Rx board_build.offset = 0x7000 -board_build.ldscript = ldscript.ld board_upload.offset_address = 0x08007000 build_unflags = ${common_stm32.build_unflags} extra_scripts = ${common.extra_scripts} @@ -207,7 +203,6 @@ build_flags = ${common_stm32.build_flags} -DMCU_STM32F103VE -DSS_TIMER= board = genericSTM32F103VE board_build.core = stm32 board_build.variant = MARLIN_F103Vx -board_build.ldscript = ldscript.ld board_build.offset = 0x7000 board_build.encrypt = Robin_mini.bin board_upload.offset_address = 0x08007000 @@ -227,7 +222,6 @@ build_flags = ${common_stm32.build_flags} -DMCU_STM32F103VE -DSS_TIMER= board = genericSTM32F103VE board_build.core = stm32 board_build.variant = MARLIN_F103Vx -board_build.ldscript = ldscript.ld board_build.offset = 0x7000 board_build.encrypt = Robin_nano35.bin board_upload.offset_address = 0x08007000 @@ -248,7 +242,6 @@ extends = common_stm32 board = genericSTM32F103ZE board_build.core = stm32 board_build.variant = MARLIN_F103Zx -board_build.ldscript = ldscript.ld board_build.offset = 0x10000 build_flags = ${common_stm32.build_flags} -DENABLE_HWSERIAL3 -DTIMER_SERIAL=TIM5 build_unflags = ${common_stm32.build_unflags} -DUSBCON -DUSBD_USE_CDC diff --git a/ini/stm32f4.ini b/ini/stm32f4.ini index ee88135eca..e7695dcc7a 100644 --- a/ini/stm32f4.ini +++ b/ini/stm32f4.ini @@ -112,7 +112,6 @@ build_flags = ${common_stm32.build_flags} -DHAL_SD_MODULE_ENABLED -DHAL board = marlin_STM32F407VGT6_CCM board_build.core = stm32 board_build.variant = MARLIN_F4x7Vx -board_build.ldscript = ldscript.ld board_build.encrypt = firmware.srec # Just openblt.py (not stm32_bootloader.py) generates the file board_build.offset = 0x10000 @@ -222,7 +221,6 @@ extends = common_stm32 board = marlin_STM32F407VGT6_CCM board_build.core = stm32 board_build.variant = MARLIN_F4x7Vx -board_build.ldscript = ldscript.ld board_build.offset = 0x8000 board_upload.offset_address = 0x08008000 extra_scripts = ${common.extra_scripts} @@ -344,7 +342,6 @@ upload_protocol = dfu monitor_speed = 500000 board_build.core = stm32 board_build.variant = MARLIN_F446VE -board_build.ldscript = ldscript.ld board_build.offset = 0x0000 extra_scripts = ${common.extra_scripts} pre:buildroot/share/PlatformIO/scripts/generic_create_variant.py @@ -361,7 +358,6 @@ build_flags = ${stm_flash_drive.build_flags} board = genericSTM32F407VET6 board_build.core = stm32 board_build.variant = MARLIN_F4x7Vx -board_build.ldscript = ldscript.ld board_build.offset = 0x0000 board_upload.offset_address = 0x08000000 build_unflags = ${common_stm32.build_unflags} -DUSBCON -DUSBD_USE_CDC @@ -387,7 +383,6 @@ build_flags = ${common_stm32.build_flags} ${stm32f4_I2C1.build_flags} - board = marlin_STM32F407VGT6_CCM board_build.core = stm32 board_build.variant = MARLIN_F4x7Vx -board_build.ldscript = ldscript.ld board_build.rename = Robin_nano_v3.bin board_build.offset = 0xC000 board_upload.offset_address = 0x0800C000