diff --git a/Marlin/src/lcd/marlinui.cpp b/Marlin/src/lcd/marlinui.cpp index 9019cdbf17..a9d4241cba 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" +#if EITHER(HAS_LCD_MENU, EXTENSIBLE_UI) + bool MarlinUI::lcd_clicked; #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 HAS_WIRED_LCD -#if ENABLED(AUTO_BED_LEVELING_UBL) - #include "../feature/bedlevel/bedlevel.h" -#endif + #if HAS_MARLINUI_U8GLIB + #include "dogm/marlinui_DOGM.h" + #endif -#if HAS_TRINAMIC_CONFIG - #include "../feature/tmc_util.h" -#endif + #include "lcdprint.h" -#if HAS_ADC_BUTTONS - #include "../module/thermistor/thermistors.h" -#endif + #include "../sd/cardreader.h" -#if HAS_POWER_MONITOR - #include "../feature/power_monitor.h" -#endif + #include "../module/temperature.h" + #include "../module/planner.h" + #include "../module/motion.h" -#if HAS_ENCODER_ACTION - volatile uint8_t MarlinUI::buttons; - #if HAS_SLOW_BUTTONS - volatile uint8_t MarlinUI::slow_buttons; + #if HAS_LCD_MENU + #include "../module/settings.h" #endif - #if HAS_TOUCH_BUTTONS - #include "touch/touch_buttons.h" - bool MarlinUI::on_edit_screen = false; + + #if ENABLED(AUTO_BED_LEVELING_UBL) + #include "../feature/bedlevel/bedlevel.h" #endif -#endif -#if SCREENS_CAN_TIME_OUT - bool MarlinUI::defer_return_to_status; - millis_t MarlinUI::return_to_status_ms = 0; -#endif + #if HAS_TRINAMIC_CONFIG + #include "../feature/tmc_util.h" + #endif -uint8_t MarlinUI::lcd_status_update_delay = 1; // First update one loop delayed + #if HAS_ADC_BUTTONS + #include "../module/thermistor/thermistors.h" + #endif -#if BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT) - millis_t MarlinUI::next_filament_display; // = 0 -#endif + #if HAS_POWER_MONITOR + #include "../feature/power_monitor.h" + #endif -millis_t MarlinUI::next_button_update_ms; // = 0 + #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 HAS_MARLINUI_U8GLIB - bool MarlinUI::drawing_screen, MarlinUI::first_page; // = false -#endif + #if SCREENS_CAN_TIME_OUT + bool MarlinUI::defer_return_to_status; + millis_t MarlinUI::return_to_status_ms = 0; + #endif -// Encoder Handling -#if HAS_ENCODER_ACTION - uint32_t MarlinUI::encoderPosition; - volatile int8_t encoderDiff; // Updated in update_buttons, added to encoderPosition every LCD update -#endif + uint8_t MarlinUI::lcd_status_update_delay = 1; // First update one loop delayed -#if ENABLED(SDSUPPORT) + #if BOTH(FILAMENT_LCD_DISPLAY, SDSUPPORT) + millis_t MarlinUI::next_filament_display; // = 0 + #endif - #include "../sd/cardreader.h" + millis_t MarlinUI::next_button_update_ms; // = 0 - #if MARLINUI_SCROLL_NAME - uint8_t MarlinUI::filename_scroll_pos, MarlinUI::filename_scroll_max; + #if HAS_MARLINUI_U8GLIB + bool MarlinUI::drawing_screen, MarlinUI::first_page; // = false #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 + // 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 EITHER(HAS_LCD_MENU, EXTENSIBLE_UI) - bool MarlinUI::lcd_clicked; -#endif + #if ENABLED(SDSUPPORT) -#if HAS_LCD_MENU - #include "menu/menu.h" + #include "../sd/cardreader.h" - screenFunc_t MarlinUI::currentScreen; // Initialized in CTOR - bool MarlinUI::screen_changed; + #if MARLINUI_SCROLL_NAME + uint8_t MarlinUI::filename_scroll_pos, MarlinUI::filename_scroll_max; + #endif - #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; + 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(REVERSE_MENU_DIRECTION, REVERSE_SELECT_DIRECTION) - int8_t MarlinUI::encoderDirection = ENCODERBASE; #endif - #if HAS_TOUCH_BUTTONS - uint8_t MarlinUI::touch_buttons; - uint8_t MarlinUI::repeat_delay; - #endif + #if HAS_LCD_MENU + #include "menu/menu.h" - #if EITHER(AUTO_BED_LEVELING_UBL, G26_MESH_VALIDATION) + screenFunc_t MarlinUI::currentScreen; // Initialized in CTOR + bool MarlinUI::screen_changed; - bool MarlinUI::external_control; // = false + #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 - void MarlinUI::wait_for_release() { - while (button_pressed()) safe_delay(50); - safe_delay(50); - } + #if EITHER(REVERSE_MENU_DIRECTION, REVERSE_SELECT_DIRECTION) + int8_t MarlinUI::encoderDirection = ENCODERBASE; + #endif - #endif + #if HAS_TOUCH_BUTTONS + uint8_t MarlinUI::touch_buttons; + uint8_t MarlinUI::repeat_delay; + #endif - #if !HAS_GRAPHICAL_TFT + #if EITHER(AUTO_BED_LEVELING_UBL, G26_MESH_VALIDATION) - 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; + bool MarlinUI::external_control; // = false - auto _newline = [&col, &row]{ - col = 0; row++; // Move col to string len (plus space) - SETCURSOR(0, row); // Simulate carriage return - }; + void MarlinUI::wait_for_release() { + while (button_pressed()) safe_delay(50); + safe_delay(50); + } + + #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 !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 } - if (eol) break; // all done! - wrd = nullptr; // set up for next word + else c++; // count word characters } - 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(); + 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); + 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); } - if (suff) wrap_string_P(col, row, suff); - } - #endif // !HAS_GRAPHICAL_TFT + #endif // !HAS_GRAPHICAL_TFT -#endif // HAS_LCD_MENU + #endif // HAS_LCD_MENU -void MarlinUI::init() { + void MarlinUI::init() { - init_lcd(); + 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); + #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 - #endif - #if HAS_SHIFT_ENCODER + #if HAS_SHIFT_ENCODER - #if ENABLED(SR_LCD_2W_NL) // Non latching 2 wire shift register + #if ENABLED(SR_LCD_2W_NL) // Non latching 2 wire shift register - SET_OUTPUT(SR_DATA_PIN); - SET_OUTPUT(SR_CLK_PIN); + SET_OUTPUT(SR_DATA_PIN); + SET_OUTPUT(SR_CLK_PIN); - #elif PIN_EXISTS(SHIFT_CLK) + #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); + 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 - #endif // HAS_SHIFT_ENCODER + #endif // HAS_SHIFT_ENCODER - #if BOTH(HAS_ENCODER_ACTION, HAS_SLOW_BUTTONS) - slow_buttons = 0; - #endif + #if BOTH(HAS_ENCODER_ACTION, HAS_SLOW_BUTTONS) + slow_buttons = 0; + #endif - update_buttons(); + update_buttons(); - TERN_(HAS_ENCODER_ACTION, encoderDiff = 0); -} + 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; + 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; } - return blink != 0; -} -//////////////////////////////////////////// -///////////// Keypad Handling ////////////// -//////////////////////////////////////////// + //////////////////////////////////////////// + ///////////// Keypad Handling ////////////// + //////////////////////////////////////////// -#if IS_RRW_KEYPAD && HAS_ENCODER_ACTION + #if IS_RRW_KEYPAD && HAS_ENCODER_ACTION - volatile uint8_t MarlinUI::keypad_buttons; + volatile uint8_t MarlinUI::keypad_buttons; - #if HAS_LCD_MENU && !HAS_ADC_BUTTONS + #if HAS_LCD_MENU && !HAS_ADC_BUTTONS - void lcd_move_x(); - void lcd_move_y(); - void lcd_move_z(); + 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; + 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 + #endif - bool MarlinUI::handle_keypad() { + bool MarlinUI::handle_keypad() { - #if HAS_ADC_BUTTONS + #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 - { + #define ADC_MIN_KEY_DELAY 100 + if (keypad_buttons) { + #if HAS_ENCODER_ACTION + refresh(LCDVIEW_REDRAW_NOW); #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; + 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 - } - #endif - next_button_update_ms = millis() + ADC_MIN_KEY_DELAY; - return true; - } - - #else // !HAS_ADC_BUTTONS + { + #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; + } - static uint8_t keypad_debounce = 0; + #else // !HAS_ADC_BUTTONS - 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; + static uint8_t keypad_debounce = 0; - const bool homed = all_axes_homed(); + 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; - #if HAS_LCD_MENU + const bool homed = all_axes_homed(); - if (RRK(EN_KEYPAD_MIDDLE)) goto_screen(menu_move); + #if HAS_LCD_MENU - #if NONE(DELTA, Z_HOME_TO_MAX) - if (RRK(EN_KEYPAD_F2)) _reprapworld_keypad_move(Z_AXIS, 1); - #endif + if (RRK(EN_KEYPAD_MIDDLE)) goto_screen(menu_move); - if (homed) { - #if EITHER(DELTA, Z_HOME_TO_MAX) - if (RRK(EN_KEYPAD_F2)) _reprapworld_keypad_move(Z_AXIS, 1); + #if NONE(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) { + #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); + } - if (!homed && RRK(EN_KEYPAD_F1)) queue.inject_P(G28_STR); - return true; - } + #endif // HAS_LCD_MENU - #endif // !HAS_ADC_BUTTONS + if (!homed && RRK(EN_KEYPAD_F1)) queue.inject_P(G28_STR); + return true; + } - return false; - } + #endif // !HAS_ADC_BUTTONS -#endif // IS_RRW_KEYPAD && HAS_ENCODER_ACTION + return false; + } -/** - * Status Screen - * - * This is very display-dependent, so the lcd implementation draws this. - */ + #endif // IS_RRW_KEYPAD && HAS_ENCODER_ACTION -#if BASIC_PROGRESS_BAR - millis_t MarlinUI::progress_bar_ms; // = 0 - #if PROGRESS_MSG_EXPIRE > 0 - millis_t MarlinUI::expire_status_ms; // = 0 + /** + * 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 -#endif -void MarlinUI::status_screen() { + void MarlinUI::status_screen() { - TERN_(HAS_LCD_MENU, ENCODER_RATE_MULTIPLY(false)); + TERN_(HAS_LCD_MENU, ENCODER_RATE_MULTIPLY(false)); - #if BASIC_PROGRESS_BAR + #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. - // + // + // HD44780 implements the following message blinking and + // message expiration because Status Line and Progress Bar + // share the same line on the display. + // - #if DISABLED(PROGRESS_MSG_ONCE) || (PROGRESS_MSG_EXPIRE > 0) - #define GOT_MS - const millis_t ms = millis(); - #endif + #if DISABLED(PROGRESS_MSG_ONCE) || (PROGRESS_MSG_EXPIRE > 0) + #define GOT_MS + const millis_t ms = millis(); + #endif - // If the message will blink rather than expire... - #if DISABLED(PROGRESS_MSG_ONCE) - if (ELAPSED(ms, progress_bar_ms + PROGRESS_BAR_MSG_TIME + PROGRESS_BAR_BAR_TIME)) - progress_bar_ms = ms; - #endif + // If the message will blink rather than expire... + #if DISABLED(PROGRESS_MSG_ONCE) + if (ELAPSED(ms, progress_bar_ms + PROGRESS_BAR_MSG_TIME + PROGRESS_BAR_BAR_TIME)) + progress_bar_ms = ms; + #endif - #if PROGRESS_MSG_EXPIRE > 0 + #if PROGRESS_MSG_EXPIRE > 0 - // Handle message expire - if (expire_status_ms) { + // Handle message expire + if (expire_status_ms) { - // Expire the message if a job is active and the bar has ticks - if (get_progress_percent() > 2 && !print_job_timer.isPaused()) { - if (ELAPSED(ms, expire_status_ms)) { - status_message[0] = '\0'; - expire_status_ms = 0; + // 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; } } - 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 // PROGRESS_MSG_EXPIRE - #endif // BASIC_PROGRESS_BAR + #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; - } + #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 + #endif - #if ENABLED(ULTIPANEL_FEEDMULTIPLY) + #if ENABLED(ULTIPANEL_FEEDMULTIPLY) - const int16_t old_frm = feedrate_percentage; - int16_t new_frm = old_frm + int16_t(encoderPosition); + 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(); + // 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 - if (ELAPSED(ms, next_beep)) { - buzz(FEEDRATE_CHANGE_BEEP_DURATION, FEEDRATE_CHANGE_BEEP_FREQUENCY); - next_beep = ms + 500UL; - } - #endif - } + } - #endif // ULTIPANEL_FEEDMULTIPLY + #endif // ULTIPANEL_FEEDMULTIPLY - draw_status_screen(); -} + 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()); + 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(); + // 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 - #endif - - draw_kill_screen(); -} -void MarlinUI::quick_feedback(const bool clear_buttons/*=true*/) { + draw_kill_screen(); + } - TERN_(HAS_LCD_MENU, refresh()); + void MarlinUI::quick_feedback(const bool clear_buttons/*=true*/) { - #if HAS_ENCODER_ACTION - if (clear_buttons) buttons = 0; - next_button_update_ms = millis() + 500; - #else - UNUSED(clear_buttons); - #endif + TERN_(HAS_LCD_MENU, refresh()); - #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); + #if HAS_ENCODER_ACTION + if (clear_buttons) buttons = 0; + next_button_update_ms = millis() + 500; + #else + UNUSED(clear_buttons); #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 + #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 + } - // Add a manual move to the queue? - if (axis != NO_AXIS_ENUM && ELAPSED(millis(), start_time) && !planner.is_full()) { + //////////////////////////////////////////// + /////////////// Manual Move //////////////// + //////////////////////////////////////////// - const feedRate_t fr_mm_s = (axis <= LOGICAL_AXES) ? manual_feedrate_mm_s[axis] : XY_PROBE_FEEDRATE_MM_S; + #if HAS_LCD_MENU - #if IS_KINEMATIC + ManualMove MarlinUI::manual_move{}; - #if HAS_MULTI_EXTRUDER - REMEMBER(ae, active_extruder); - #if MULTI_E_MANUAL - if (axis == E_AXIS) active_extruder = e_index; + 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 - #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; - } + // 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; + // 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; + // 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 + #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 - ); + // 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); + //SERIAL_ECHOLNPAIR("Add planner.move with Axis ", AS_CHAR(axis_codes[axis]), " at FR ", fr_mm_s); - axis = NO_AXIS_ENUM; + axis = NO_AXIS_ENUM; - #endif + #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."); - } + // + // 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) + #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. + 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 -#endif // HAS_LCD_MENU + #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. - */ + /** + * 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; + 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) -} + inline bool can_encode() { + return !BUTTON_PRESSED(ENC_EN); // Update encoder only when ENC_EN is not LOW (pressed) + } -void MarlinUI::update() { + void MarlinUI::update() { - static uint16_t max_display_update_time = 0; - millis_t ms = millis(); + static uint16_t max_display_update_time = 0; + millis_t ms = millis(); - #ifdef LED_BACKLIGHT_TIMEOUT - leds.update_timeout(powersupply_on); - #endif + #ifdef LED_BACKLIGHT_TIMEOUT + leds.update_timeout(powersupply_on); + #endif - #if HAS_LCD_MENU + #if HAS_LCD_MENU - // Handle any queued Move Axis motion - manual_move.task(); + // 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(); + // 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 + // 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 - }; + 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(); + #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(); } - 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 + // 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 + 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; } - else - wait_for_unclick = false; - } - if (LCD_BACK_CLICKED()) { - quick_feedback(); - goto_previous_screen(); - } + if (LCD_BACK_CLICKED()) { + quick_feedback(); + goto_previous_screen(); + } - #endif // HAS_LCD_MENU + #endif // HAS_LCD_MENU - if (ELAPSED(ms, next_lcd_update_ms) || TERN0(HAS_MARLINUI_U8GLIB, drawing_screen)) { + if (ELAPSED(ms, next_lcd_update_ms) || TERN0(HAS_MARLINUI_U8GLIB, drawing_screen)) { - next_lcd_update_ms = ms + LCD_UPDATE_INTERVAL; + next_lcd_update_ms = ms + LCD_UPDATE_INTERVAL; - #if HAS_TOUCH_BUTTONS + #if HAS_TOUCH_BUTTONS - if (on_status_screen()) next_lcd_update_ms += (LCD_UPDATE_INTERVAL) * 2; + if (on_status_screen()) next_lcd_update_ms += (LCD_UPDATE_INTERVAL) * 2; - TERN_(HAS_ENCODER_ACTION, touch_buttons = touch.read_buttons()); + TERN_(HAS_ENCODER_ACTION, touch_buttons = touch.read_buttons()); - #endif + #endif - TERN_(LCD_HAS_STATUS_INDICATORS, update_indicators()); + TERN_(LCD_HAS_STATUS_INDICATORS, update_indicators()); - #if HAS_ENCODER_ACTION + #if HAS_ENCODER_ACTION - TERN_(HAS_SLOW_BUTTONS, slow_buttons = read_slow_buttons()); // Buttons that take too long to read in interrupt context + 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); + if (TERN0(IS_RRW_KEYPAD, handle_keypad())) + reset_status_timeout(ms); - uint8_t abs_diff = ABS(encoderDiff); + 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; + #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 + // 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)) { + 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) + #if BOTH(HAS_LCD_MENU, ENCODER_RATE_MULTIPLIER) - int32_t encoderMultiplier = 1; + int32_t encoderMultiplier = 1; - if (encoderRateMultiplierEnabled) { - const float encoderMovementSteps = float(abs_diff) / epps; + 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 (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; + 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 - } + // 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 + lastEncoderMovementMillis = ms; + } // encoderRateMultiplierEnabled - #else - - constexpr int32_t encoderMultiplier = 1; + #else - #endif // ENCODER_RATE_MULTIPLIER + constexpr int32_t encoderMultiplier = 1; - if (can_encode()) encoderPosition += (encoderDiff * encoderMultiplier) / epps; + #endif // ENCODER_RATE_MULTIPLIER - encoderDiff = 0; - } + if (can_encode()) encoderPosition += (encoderDiff * encoderMultiplier) / epps; - reset_status_timeout(ms); + encoderDiff = 0; + } - refresh(LCDVIEW_REDRAW_NOW); + reset_status_timeout(ms); - #ifdef LED_BACKLIGHT_TIMEOUT - if (!powersupply_on) leds.reset_timeout(ms); - #endif - } + refresh(LCDVIEW_REDRAW_NOW); - #endif + #ifdef LED_BACKLIGHT_TIMEOUT + if (!powersupply_on) leds.reset_timeout(ms); + #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); - } + #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; + // 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); - 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: + #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); - case LCDVIEW_REDRAW_NOW: // set above, or by a handler through LCDVIEW_CALL_REDRAW_NEXT - case LCDVIEW_NONE: - break; - } // switch + reset_status_timeout(ms); + } + #endif - TERN_(HAS_ADC_BUTTONS, keypad_buttons = 0); + // 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 HAS_MARLINUI_U8GLIB + 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; + } + } - #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(); - run_current_screen(); + #endif - #endif + TERN_(HAS_LCD_MENU, lcd_clicked = false); - 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); + } - // Keeping track of the longest time for an individual LCD update. - // Used to do screen throttling when the planner starts to fill up. - if (on_status_screen()) - NOLESS(max_display_update_time, millis() - ms); - } + #if SCREENS_CAN_TIME_OUT + // Return to Status Screen after a timeout + if (on_status_screen() || defer_return_to_status) + reset_status_timeout(ms); + else if (ELAPSED(ms, return_to_status_ms)) + return_to_status(); + #endif - #if 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_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 - // Change state of drawing flag between screen updates - if (!drawing_screen) switch (lcdDrawUpdate) { - case LCDVIEW_CLEAR_CALL_REDRAW: - clear_lcd(); break; - case LCDVIEW_REDRAW_NOW: - refresh(LCDVIEW_NONE); - case LCDVIEW_NONE: - case LCDVIEW_CALL_REDRAW_NEXT: - case LCDVIEW_CALL_NO_REDRAW: - default: break; - } // switch + } // ELAPSED(ms, next_lcd_update_ms) - } // ELAPSED(ms, next_lcd_update_ms) + TERN_(HAS_GRAPHICAL_TFT, tft_idle()); + } - TERN_(HAS_GRAPHICAL_TFT, tft_idle()); -} + #if HAS_ADC_BUTTONS -#if HAS_ADC_BUTTONS + typedef struct { + uint16_t ADCKeyValueMin, ADCKeyValueMax; + uint8_t ADCKeyNo; + } _stADCKeypadTable_; - 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 - #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) + }; - // 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); - } + 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; } - return 0; - } -#endif // HAS_ADC_BUTTONS + #endif // HAS_ADC_BUTTONS -#if 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)) { + /** + * 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 HAS_DIGITAL_BUTTONS - #if ANY_BUTTON(EN1, EN2, ENC, BACK) + #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; + 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 + #else - constexpr uint8_t newbutton = 0; + constexpr uint8_t newbutton = 0; - #endif + #endif - // - // Directional buttons - // - #if ANY_BUTTON(UP, DWN, LFT, RT) + // + // Directional buttons + // + #if ANY_BUTTON(UP, DWN, LFT, RT) - const int8_t pulses = epps * encoderDirection; + 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; - } + 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 + #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 - ); + 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 + #elif HAS_ADC_BUTTONS - buttons = 0; + buttons = 0; - #endif + #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_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 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 + #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 + } // next_button_update_ms - #if HAS_ENCODER_WHEEL - static uint8_t lastEncoderBits; + #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; } + // 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; + 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; } - #if BOTH(HAS_LCD_MENU, AUTO_BED_LEVELING_UBL) - external_encoder(); - #endif - lastEncoderBits = enc; - } - #endif // HAS_ENCODER_WHEEL - } + #endif // HAS_ENCODER_WHEEL + } -#endif // HAS_ENCODER_ACTION + #endif // HAS_ENCODER_ACTION #endif // HAS_WIRED_LCD