diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index b1ad8d962906..a48a33727041 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -1960,7 +1960,8 @@ #if ENABLED(MULTI_VOLUME) #define VOLUME_SD_ONBOARD #define VOLUME_USB_FLASH_DRIVE - #define DEFAULT_SHARED_VOLUME SV_USB_FLASH_DRIVE + #define DEFAULT_VOLUME SD_ONBOARD // :[ 'SD_ONBOARD', 'USB_FLASH_DRIVE' ] + #define DEFAULT_SHARED_VOLUME USB_FLASH_DRIVE // :[ 'SD_ONBOARD', 'USB_FLASH_DRIVE' ] #endif #endif // HAS_MEDIA diff --git a/Marlin/src/HAL/STM32/sd/msc_sd.cpp b/Marlin/src/HAL/STM32/sd/msc_sd.cpp index f198b3e49aef..9bb65aab4afd 100644 --- a/Marlin/src/HAL/STM32/sd/msc_sd.cpp +++ b/Marlin/src/HAL/STM32/sd/msc_sd.cpp @@ -48,6 +48,7 @@ class Sd2CardUSBMscHandler : public USBMscHandler { public: DiskIODriver* diskIODriver() { + // TODO: Explore a variable shared volume, or auto share the un-mounted volume(s) #if HAS_MULTI_VOLUME #if SHARED_VOLUME_IS(SD_ONBOARD) return &card.media_driver_sdcard; diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index de6677fc9a26..aa229e80b610 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -1349,8 +1349,11 @@ void setup() { #endif #endif - #if HAS_MEDIA && ANY(SDCARD_EEPROM_EMULATION, POWER_LOSS_RECOVERY) - SETUP_RUN(card.mount()); // Mount media with settings before first_load + #if HAS_MEDIA + SETUP_RUN(card.init()); // Prepare for media usage + #if ANY(SDCARD_EEPROM_EMULATION, POWER_LOSS_RECOVERY) + SETUP_RUN(card.mount()); // Mount media with settings before first_load + #endif #endif // Prepare some LCDs to display early diff --git a/Marlin/src/feature/password/password.cpp b/Marlin/src/feature/password/password.cpp index 1d376cc586cd..d36b3edd098e 100644 --- a/Marlin/src/feature/password/password.cpp +++ b/Marlin/src/feature/password/password.cpp @@ -36,7 +36,7 @@ uint32_t Password::value, Password::value_entry; // // Authenticate user with password. -// Called from Setup, after SD Prinitng Stops/Aborts, and M510 +// Called from Setup, after SD Printing Stops/Aborts, and M510 // void Password::lock_machine() { is_locked = true; diff --git a/Marlin/src/feature/password/password.h b/Marlin/src/feature/password/password.h index 208765b21225..0f8bc28bd859 100644 --- a/Marlin/src/feature/password/password.h +++ b/Marlin/src/feature/password/password.h @@ -37,6 +37,8 @@ class Password { static void access_menu_password(); static void authentication_done(); static void media_gatekeeper(); + static void media_gatekeeper_sd(); + static void media_gatekeeper_usb(); private: static void authenticate_user(const screenFunc_t, const screenFunc_t); diff --git a/Marlin/src/inc/Changes.h b/Marlin/src/inc/Changes.h index abd7db80112d..839804d920b7 100644 --- a/Marlin/src/inc/Changes.h +++ b/Marlin/src/inc/Changes.h @@ -799,3 +799,31 @@ #undef _POWERSTEP01 #undef _TMC26X #undef _TMC26X_STANDALONE + +#if ENABLED(MULTI_VOLUME) + // Change to a generic ID without SV_ prefix + #define SV_SD_ONBOARD 201 + #define SV_USB_FLASH_DRIVE 202 + #if DEFAULT_VOLUME_IS(SV_SD_ONBOARD) || SHARED_VOLUME_IS(SV_SD_ONBOARD) + #error "SV_SD_ONBOARD is now SD_ONBOARD." + #elif DEFAULT_VOLUME_IS(SV_USB_FLASH_DRIVE) || SHARED_VOLUME_IS(SV_USB_FLASH_DRIVE) + #error "SV_USB_FLASH_DRIVE is now USB_FLASH_DRIVE." + #endif + // Skip less clear "bad value" errors in inc/SanityCheck.h + #if DEFAULT_VOLUME_IS(SV_SD_ONBOARD) + #undef DEFAULT_VOLUME + #define DEFAULT_VOLUME SD_ONBOARD + #elif DEFAULT_VOLUME_IS(SV_USB_FLASH_DRIVE) + #undef DEFAULT_VOLUME + #define DEFAULT_VOLUME USB_FLASH_DRIVE + #endif + #if SHARED_VOLUME_IS(SV_SD_ONBOARD) + #undef DEFAULT_SHARED_VOLUME + #define DEFAULT_SHARED_VOLUME SD_ONBOARD + #elif SHARED_VOLUME_IS(SV_USB_FLASH_DRIVE) + #undef DEFAULT_SHARED_VOLUME + #define DEFAULT_SHARED_VOLUME USB_FLASH_DRIVE + #endif + #undef SV_SD_ONBOARD + #undef SV_USB_FLASH_DRIVE +#endif diff --git a/Marlin/src/inc/Conditionals-4-adv.h b/Marlin/src/inc/Conditionals-4-adv.h index a9427c3605f6..629ee9827368 100644 --- a/Marlin/src/inc/Conditionals-4-adv.h +++ b/Marlin/src/inc/Conditionals-4-adv.h @@ -1248,11 +1248,12 @@ #if ENABLED(MULTI_VOLUME) #define HAS_MULTI_VOLUME 1 - #define SV_SD_ONBOARD 101 - #define SV_USB_FLASH_DRIVE 102 - #define _VOLUME_ID(N) _CAT(SV_, N) - #define SHARED_VOLUME_IS(N) (DEFAULT_SHARED_VOLUME == _VOLUME_ID(N)) + #define SD_ONBOARD 101 + #define USB_FLASH_DRIVE 102 + #define DEFAULT_VOLUME_IS(N) (DEFAULT_VOLUME == N) + #define SHARED_VOLUME_IS(N) (DEFAULT_SHARED_VOLUME == N) #else + #define DEFAULT_VOLUME_IS(...) 0 #define SHARED_VOLUME_IS(...) 0 #endif diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index 074cfe0dcbdb..cff241db7251 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -416,10 +416,20 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L /** * SD Card Settings */ -#if ALL(HAS_MEDIA, HAS_SD_DETECT, SD_CONNECTION_TYPICAL, ELB_FULL_GRAPHIC_CONTROLLER, HAS_MARLINUI_MENU) && SD_DETECT_STATE == LOW - #error "SD_DETECT_STATE must be set HIGH for SD on the ELB_FULL_GRAPHIC_CONTROLLER." +#if HAS_MEDIA + #if HAS_MULTI_VOLUME + #if !(DEFAULT_VOLUME_IS(SD_ONBOARD) || DEFAULT_VOLUME_IS(USB_FLASH_DRIVE)) + #error "DEFAULT_VOLUME must be either SD_ONBOARD or USB_FLASH_DRIVE." + #endif + #if !(SHARED_VOLUME_IS(SD_ONBOARD) || SHARED_VOLUME_IS(USB_FLASH_DRIVE)) + #error "DEFAULT_SHARED_VOLUME must be either SD_ONBOARD or USB_FLASH_DRIVE." + #endif + #endif + #if ALL(ELB_FULL_GRAPHIC_CONTROLLER, HAS_MARLINUI_MENU, SD_CONNECTION_TYPICAL, HAS_SD_DETECT) && SD_DETECT_STATE == LOW + #error "SD_DETECT_STATE must be set HIGH for SD on the ELB_FULL_GRAPHIC_CONTROLLER." + #endif + #undef SD_CONNECTION_TYPICAL #endif -#undef SD_CONNECTION_TYPICAL /** * SD File Sorting diff --git a/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp b/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp index 4ac012ba9358..d772447551e7 100644 --- a/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp +++ b/Marlin/src/lcd/TFTGLCD/marlinui_TFTGLCD.cpp @@ -175,9 +175,9 @@ void TFTGLCD::clr_screen() { SPI_SEND_ONE(CLR_SCREEN); WRITE(TFTGLCD_CS, HIGH); #else - Wire.beginTransmission(uint8_t(LCD_I2C_ADDRESS)); //set I2C device address + Wire.beginTransmission(uint8_t(LCD_I2C_ADDRESS)); // Transmit to LCD via I2C Wire.write(CLR_SCREEN); - Wire.endTransmission(); //transmit data + Wire.endTransmission(); // Send the data #endif } @@ -378,10 +378,6 @@ void MarlinUI::clear_for_drawing() { clear_lcd(); } void MarlinUI::_set_contrast() { lcd.setContrast(contrast); } #endif -#if !IS_TFTGLCD_PANEL - void lcd_moveto(const uint8_t col, const uint8_t row) { lcd.setCursor(col, row); } -#endif - static void center_text(FSTR_P const fstart, const uint8_t y) { const uint8_t len = utf8_strlen(fstart); lcd_moveto(len < LCD_WIDTH ? (LCD_WIDTH - len) / 2 : 0, y); diff --git a/Marlin/src/lcd/extui/anycubic_chiron/chiron_tft.cpp b/Marlin/src/lcd/extui/anycubic_chiron/chiron_tft.cpp index e6e8c8815572..b4e197828358 100644 --- a/Marlin/src/lcd/extui/anycubic_chiron/chiron_tft.cpp +++ b/Marlin/src/lcd/extui/anycubic_chiron/chiron_tft.cpp @@ -740,7 +740,7 @@ void ChironTFT::panelAction(uint8_t req) { break; case 26: // A26 Refresh SD - if (card.isMounted())card.release(); + card.release(); card.mount(); safe_delay(500); filenavigator.reset(); diff --git a/Marlin/src/lcd/extui/mks_ui/tft_lvgl_configuration.cpp b/Marlin/src/lcd/extui/mks_ui/tft_lvgl_configuration.cpp index 729d8bc4b278..903afad88492 100644 --- a/Marlin/src/lcd/extui/mks_ui/tft_lvgl_configuration.cpp +++ b/Marlin/src/lcd/extui/mks_ui/tft_lvgl_configuration.cpp @@ -131,32 +131,16 @@ void tft_lvgl_init() { // Init TFT first! SPI_TFT.spiInit(SPI_FULL_SPEED); SPI_TFT.lcdInit(); - hal.watchdog_refresh(); // LVGL init takes time - #if HAS_USB_FLASH_DRIVE - #if HAS_MULTI_VOLUME && !HAS_SD_HOST_DRIVE - if (card.isSDCardInserted()) - card.selectMediaSDCard(); - else - card.selectMediaFlashDrive(); - #endif - // Wait up to two seconds for USB Drive to mount - for (uint16_t usb_flash_loop = 500; --usb_flash_loop;) { - hal.watchdog_refresh(); - card.media_driver_usbFlash.idle(); - delay(4); - if (card.media_driver_usbFlash.isInserted()) break; - } - card.mount(); - #elif HAS_LOGO_IN_FLASH + #if HAS_LOGO_IN_FLASH + // Leave the boot screen visible for a moment delay(1000); - hal.watchdog_refresh(); + hal.watchdog_refresh(); // LVGL init takes time delay(1000); + hal.watchdog_refresh(); // LVGL init takes time #endif - hal.watchdog_refresh(); // LVGL init takes time - #if HAS_MEDIA UpdateAssets(); hal.watchdog_refresh(); // LVGL init takes time diff --git a/Marlin/src/lcd/language/language_en.h b/Marlin/src/lcd/language/language_en.h index 7ea17d5bb3e0..5ed5b2463544 100644 --- a/Marlin/src/lcd/language/language_en.h +++ b/Marlin/src/lcd/language/language_en.h @@ -574,10 +574,8 @@ namespace LanguageNarrow_en { LSTR MSG_CANCEL_OBJECT = _UxGT("Cancel Obj"); LSTR MSG_CANCEL_OBJECT_N = _UxGT("Cancel Obj {"); LSTR MSG_CONTINUE_PRINT_JOB = _UxGT("Continue Job"); - LSTR MSG_MEDIA_MENU = MEDIA_TYPE_EN _UxGT(" Print"); LSTR MSG_TURN_OFF = _UxGT("Turn off now"); LSTR MSG_END_LOOPS = _UxGT("End Loops"); - LSTR MSG_NO_MEDIA = _UxGT("No ") MEDIA_TYPE_EN; LSTR MSG_DWELL = _UxGT("Sleep..."); LSTR MSG_USERWAIT = _UxGT("Click to Resume..."); LSTR MSG_PRINT_PAUSED = _UxGT("Print Paused"); @@ -641,6 +639,10 @@ namespace LanguageNarrow_en { LSTR MSG_RUN_AUTOFILES = _UxGT("Run Autofiles"); LSTR MSG_RUN_AUTOFILES_SD = _UxGT("Run SD Autofiles"); LSTR MSG_RUN_AUTOFILES_USB = _UxGT("Run USB Autofiles"); + LSTR MSG_MEDIA_MENU = MEDIA_TYPE_EN _UxGT(" Print"); + LSTR MSG_MEDIA_MENU_SD = _UxGT("Select from SD"); + LSTR MSG_MEDIA_MENU_USB = _UxGT("Select from USB"); + LSTR MSG_NO_MEDIA = _UxGT("No ") MEDIA_TYPE_EN _UxGT(" Detected"); LSTR MSG_ZPROBE_OUT = _UxGT("Z Probe Past Bed"); LSTR MSG_SKEW_FACTOR = _UxGT("Skew Factor"); @@ -1130,6 +1132,9 @@ namespace LanguageWide_en { LSTR MSG_CANCEL_OBJECT_N = _UxGT("Cancel Object {"); LSTR MSG_CONTINUE_PRINT_JOB = _UxGT("Continue Print Job"); LSTR MSG_MEDIA_MENU = _UxGT("Select from ") MEDIA_TYPE_EN; + LSTR MSG_MEDIA_MENU_SD = _UxGT("Select from SD Card"); + LSTR MSG_MEDIA_MENU_USB = _UxGT("Select from USB Drive"); + LSTR MSG_NO_MEDIA = _UxGT("No ") MEDIA_TYPE_EN _UxGT(" Found"); LSTR MSG_TURN_OFF = _UxGT("Turn off the printer"); LSTR MSG_END_LOOPS = _UxGT("End Repeat Loops"); LSTR MSG_MEDIA_NOT_INSERTED = _UxGT("No media inserted."); // ProUI diff --git a/Marlin/src/lcd/marlinui.cpp b/Marlin/src/lcd/marlinui.cpp index f6b1b60c7dd1..2b730581c86b 100644 --- a/Marlin/src/lcd/marlinui.cpp +++ b/Marlin/src/lcd/marlinui.cpp @@ -1891,40 +1891,60 @@ uint8_t expand_u8str_P(char * const outstr, PGM_P const ptpl, const int8_t ind, #include "extui/ui_api.h" #endif - void MarlinUI::media_changed(const uint8_t old_status, const uint8_t status) { + void MarlinUI::media_changed(const MediaPresence old_status, const MediaPresence status) { TERN_(HAS_DISPLAY_SLEEP, refresh_screen_timeout()); if (old_status == status) { TERN_(EXTENSIBLE_UI, ExtUI::onMediaError()); // Failed to mount/unmount return; } - if (old_status < 2) { // Skip this section on first boot check - if (status) { // Media Mounted + if (old_status > MEDIA_BOOT) { // Skip this section on first boot check + + if (status > old_status) { // Media Mounted + #if ENABLED(EXTENSIBLE_UI) + ExtUI::onMediaMounted(); + #elif ENABLED(BROWSE_MEDIA_ON_INSERT) + clear_menu_history(); quick_feedback(); goto_screen(MEDIA_MENU_GATEWAY); + #else + if (card.isSDCardSelected()) LCD_MESSAGE(MSG_MEDIA_INSERTED_SD); else if (card.isFlashDriveSelected()) LCD_MESSAGE(MSG_MEDIA_INSERTED_USB); else LCD_MESSAGE(MSG_MEDIA_INSERTED); + #endif } else { // Media Removed + #if ENABLED(EXTENSIBLE_UI) + ExtUI::onMediaRemoved(); - #elif HAS_SD_DETECT // Q: Does "Media Removed" need to be shown for manual release too? - LCD_MESSAGE(MSG_MEDIA_REMOVED); - #if HAS_MARLINUI_MENU - if (ENABLED(HAS_WIRED_LCD) || !defer_return_to_status) return_to_status(); - #endif + + #elif HAS_SD_DETECT || HAS_USB_FLASH_DRIVE // Q: Does "Media Removed" need to be shown for manual release too? + + if ((old_status ^ status) & INSERT_SD) + LCD_MESSAGE(MSG_MEDIA_REMOVED_SD); + else if ((old_status ^ status) & INSERT_USB) + LCD_MESSAGE(MSG_MEDIA_REMOVED_USB); + else + LCD_MESSAGE(MSG_MEDIA_REMOVED); + + if (ENABLED(HAS_WIRED_LCD) || !defer_return_to_status) + return_to_status(); + #elif HAS_WIRED_LCD + return_to_status(); + #endif } } diff --git a/Marlin/src/lcd/marlinui.h b/Marlin/src/lcd/marlinui.h index 9f2ac5a69ae9..a610796ebcbe 100644 --- a/Marlin/src/lcd/marlinui.h +++ b/Marlin/src/lcd/marlinui.h @@ -255,8 +255,10 @@ class MarlinUI { #endif #if HAS_MEDIA - #define MEDIA_MENU_GATEWAY TERN(PASSWORD_ON_SD_PRINT_MENU, password.media_gatekeeper, menu_media) - static void media_changed(const uint8_t old_stat, const uint8_t stat); + #define MEDIA_MENU_GATEWAY TERN(PASSWORD_ON_SD_PRINT_MENU, password.media_gatekeeper, menu_file_selector) + #define MEDIA_MENU_GATEWAY_SD TERN(PASSWORD_ON_SD_PRINT_MENU, password.media_gatekeeper_sd, menu_file_selector_sd) + #define MEDIA_MENU_GATEWAY_USB TERN(PASSWORD_ON_SD_PRINT_MENU, password.media_gatekeeper_usb, menu_file_selector_usb) + static void media_changed(const MediaPresence old_stat, const MediaPresence stat); #endif #if HAS_LCD_BRIGHTNESS @@ -864,7 +866,7 @@ class MarlinUI { TERN_(REVERSE_SELECT_DIRECTION, encoderDirection = -(ENCODERBASE)); } - #else + #else // !HAS_ENCODER_ACTION static void update_buttons() {} static bool hw_button_pressed() { return false; } diff --git a/Marlin/src/lcd/menu/menu.h b/Marlin/src/lcd/menu/menu.h index b7861655e279..47f5a312ebd8 100644 --- a/Marlin/src/lcd/menu/menu.h +++ b/Marlin/src/lcd/menu/menu.h @@ -212,7 +212,8 @@ void menu_move(); #if HAS_MEDIA void menu_file_selector(); - void menu_media(); + void menu_file_selector_sd(); + void menu_file_selector_usb(); #endif //////////////////////////////////////////// diff --git a/Marlin/src/lcd/menu/menu_item.h b/Marlin/src/lcd/menu/menu_item.h index af4558cefcbb..d79956aea7ac 100644 --- a/Marlin/src/lcd/menu/menu_item.h +++ b/Marlin/src/lcd/menu/menu_item.h @@ -226,6 +226,9 @@ class MenuItem_bool : public MenuEditItemBase { * should be done before the menu loop (START_MENU / START_SCREEN). */ +// CAUTION! When using menu items in a lambda or sub-function always use: +#define INJECT_MENU_ITEMS(FN) { FN; if (ui.screen_changed) return; } + /** * SCREEN_OR_MENU_LOOP generates header code for a screen or menu * diff --git a/Marlin/src/lcd/menu/menu_main.cpp b/Marlin/src/lcd/menu/menu_main.cpp index 7e2d458e41ef..04bbc41074e1 100644 --- a/Marlin/src/lcd/menu/menu_main.cpp +++ b/Marlin/src/lcd/menu/menu_main.cpp @@ -237,8 +237,8 @@ void menu_configuration(); void menu_main() { const bool busy = printingIsActive(); #if HAS_MEDIA - const bool card_detected = card.isMounted(), - card_open = card_detected && card.isFileOpen(); + const bool card_is_mounted = card.isMounted(), + card_open = card_is_mounted && card.isFileOpen(); #endif START_MENU(); @@ -248,45 +248,123 @@ void menu_main() { #define MEDIA_MENU_AT_TOP #endif - auto media_menus = [&]{ + // Show "Attach" for drives that don't auto-detect media (yet) + //#define ATTACH_WITHOUT_INSERT_SD + #define ATTACH_WITHOUT_INSERT_USB + + // Show all "inserted" drives and mount as-needed + #define SHOW_UNMOUNTED_DRIVES + + /** + * Previously: + * - The "selected" media is mounted? + * - [Run Auto Files] + * - HAS_SD_DETECT: + * - [Change Media] = M21 / M21S + * - HAS_MULTI_VOLUME? + * - [Attach USB Drive] = M21U + * - ELSE: + * - [Release Media] = M22 + * - [Select from Media] (or Password Gateway) > + * + * - The "selected" media is not mounted? + * - HAS_SD_DETECT? + * - [No Media] (does nothing) + * - HAS_MULTI_VOLUME? + * - [Attach SD Card] = M21S + * - [Attach USB Drive] = M21U + * - ELSE: + * - [Attach Media] = M21 + * + * Updated: + * - Something is mounted? + * - [Run SD/USB Autofiles] + * - [Release SD/USB] = M22 + * - [Select from SD/USB] (or Password Gateway) > + * + * - Something is inserted and SHOW_UNMOUNTED_DRIVES? + * - [Select from SD/USB] (or Password Gateway) > + * + * - The "selected" Card is NOT DETECTED? + * - Trust all media detect methods? + * - [No Media] (does nothing) + * - HAS_MULTI_VOLUME? + * - [Attach SD Card] = M21S + * - [Attach USB Drive] = M21U + * - ELSE: + * - [Attach SD Card/USB Drive] = M21 + * + * Ideal: + * - Password Gateway? + * - Use gateway passthroughs for all SD/USB Drive menu items... + * - [Run SD Autofiles] + * - [Run USB Autofiles] + * - [Select from SD Card] (or Password Gateway) > + * - [Select from USB Drive] (or Password Gateway) > + * - [Eject SD Card/USB Drive] + */ + auto media_menu_items = [&]{ #if HAS_MEDIA - if (card_detected) { - if (!card_open) { - #if ENABLED(MENU_ADDAUTOSTART) - ACTION_ITEM(MSG_RUN_AUTOFILES, card.autofile_begin); // Run Auto Files - #endif + if (card_open) return; + + if (card_is_mounted) { + #if ENABLED(MENU_ADDAUTOSTART) + // [Run AutoFiles] for mounted drive(s) + if (card.isSDCardMounted()) + ACTION_ITEM(MSG_RUN_AUTOFILES_SD, card.autofile_begin); + if (card.isFlashDriveMounted()) + ACTION_ITEM(MSG_RUN_AUTOFILES_USB, card.autofile_begin); + #endif - #if HAS_SD_DETECT - GCODES_ITEM(MSG_CHANGE_MEDIA, F("M21" TERN_(HAS_MULTI_VOLUME, "S"))); // M21 Change Media - #if HAS_MULTI_VOLUME - GCODES_ITEM(MSG_ATTACH_USB, F("M21U")); // M21 Attach USB Media - #endif - #else // - or - - ACTION_ITEM(MSG_RELEASE_MEDIA, []{ // M22 Release Media - queue.inject(F("M22")); - #if ENABLED(TFT_COLOR_UI) - // Menu display issue on item removal with multi language selection menu - if (encoderTopLine > 0) encoderTopLine--; - ui.refresh(); - #endif - }); - #endif - SUBMENU(MSG_MEDIA_MENU, MEDIA_MENU_GATEWAY); // Media Menu (or Password First) - } + #if ENABLED(TFT_COLOR_UI) + // Menu display issue on item removal with multi language selection menu + #define M22_ITEM(T) do{ \ + ACTION_ITEM(T, []{ \ + queue.inject(F("M22")); encoderTopLine -= (encoderTopLine > 0); ui.refresh(); \ + }); \ + }while(0) + #else + #define M22_ITEM(T) GCODES_ITEM(T, F("M22")) + #endif + + // [Release Media] for mounted drive(s) + if (card.isSDCardMounted()) + M22_ITEM(MSG_RELEASE_SD); + if (card.isFlashDriveMounted()) + M22_ITEM(MSG_RELEASE_USB); + + // [Select from SD/USB] (or Password First) + if (card.isSDCardMounted()) + SUBMENU(MSG_MEDIA_MENU_SD, MEDIA_MENU_GATEWAY); + else if (TERN0(SHOW_UNMOUNTED_DRIVES, card.isSDCardInserted())) + SUBMENU(MSG_MEDIA_MENU_SD, MEDIA_MENU_GATEWAY_SD); + if (card.isFlashDriveMounted()) + SUBMENU(MSG_MEDIA_MENU_USB, MEDIA_MENU_GATEWAY); + else if (TERN0(SHOW_UNMOUNTED_DRIVES, card.isFlashDriveInserted())) + SUBMENU(MSG_MEDIA_MENU_USB, MEDIA_MENU_GATEWAY_USB); } else { - #if HAS_SD_DETECT - ACTION_ITEM(MSG_NO_MEDIA, nullptr); // "No Media" - #else - #if HAS_MULTI_VOLUME - GCODES_ITEM(MSG_ATTACH_SD, F("M21S")); // M21S Attach SD Card - GCODES_ITEM(MSG_ATTACH_USB, F("M21U")); // M21U Attach USB Media + // NOTE: If the SD Card has no SD_DETECT it will always appear to be "inserted" + const bool att_sd = ENABLED(ATTACH_WITHOUT_INSERT_SD) || card.isSDCardInserted(), + att_usb = ENABLED(ATTACH_WITHOUT_INSERT_USB) || card.isFlashDriveInserted(); + if (!att_sd && !att_usb) { + ACTION_ITEM(MSG_NO_MEDIA, nullptr); // [No Media] + } + else { + #if ALL(HAS_MULTI_VOLUME, SHOW_UNMOUNTED_DRIVES) + // [Select from SD/USB] (or Password First) + if (TERN0(SHOW_UNMOUNTED_DRIVES, card.isSDCardInserted())) + SUBMENU(MSG_MEDIA_MENU_SD, MEDIA_MENU_GATEWAY_SD); + if (TERN0(SHOW_UNMOUNTED_DRIVES, card.isFlashDriveInserted())) + SUBMENU(MSG_MEDIA_MENU_USB, MEDIA_MENU_GATEWAY_USB); #else - GCODES_ITEM(MSG_ATTACH_MEDIA, F("M21")); // M21 Attach Media + #define M21(T) F("M21" TERN_(HAS_MULTI_VOLUME, T)) + if (att_sd) GCODES_ITEM(MSG_ATTACH_SD, M21("S")); // M21 S - [Attach SD Card] + if (att_usb) GCODES_ITEM(MSG_ATTACH_USB, M21("U")); // M21 U - [Attach USB Drive] #endif - #endif + } } - #endif + #endif // HAS_MEDIA }; if (busy) { @@ -317,7 +395,9 @@ void menu_main() { else { // SD Card / Flash Drive - TERN_(MEDIA_MENU_AT_TOP, media_menus()); + #if ENABLED(MEDIA_MENU_AT_TOP) + INJECT_MENU_ITEMS(media_menu_items()); + #endif if (TERN0(MACHINE_CAN_PAUSE, printingIsPaused())) ACTION_ITEM(MSG_RESUME_PRINT, ui.resume_print); @@ -407,7 +487,7 @@ void menu_main() { // SD Card / Flash Drive #if DISABLED(MEDIA_MENU_AT_TOP) - if (!busy) media_menus(); + if (!busy) INJECT_MENU_ITEMS(media_menu_items()); #endif #if HAS_SERVICE_INTERVALS diff --git a/Marlin/src/lcd/menu/menu_media.cpp b/Marlin/src/lcd/menu/menu_media.cpp index 8747685fe579..f4c409c4a36b 100644 --- a/Marlin/src/lcd/menu/menu_media.cpp +++ b/Marlin/src/lcd/menu/menu_media.cpp @@ -101,29 +101,27 @@ class MenuItem_sdfolder : public MenuItem_sdbase { } }; -#if HAS_MULTI_VOLUME - void menu_media_select() { - START_MENU(); - BACK_ITEM_F(TERN1(BROWSE_MEDIA_ON_INSERT, screen_history_depth) ? GET_TEXT_F(MSG_MAIN_MENU) : GET_TEXT_F(MSG_BACK)); - #if HAS_SDCARD - ACTION_ITEM(MSG_SD_CARD, []{ card.selectMediaSDCard(); card.mount(); ui.goto_screen(menu_file_selector); }); - #endif - #if HAS_USB_FLASH_DRIVE - ACTION_ITEM(MSG_USB_DISK, []{ card.selectMediaFlashDrive(); card.mount(); ui.goto_screen(menu_file_selector); }); - #endif - END_MENU(); +// Shortcut menu items to go directly to inserted — not necessarily mounted — drives +void menu_file_selector_sd() { + if (!card.isSDCardSelected()) { + card.release(); + card.selectMediaSDCard(); } -#endif + if (!card.isSDCardMounted()) card.mount(); + ui.goto_screen(menu_file_selector); +} -/** - * "Select From Media" menu item. Depending on single or multiple drives: - * - menu_file_selector - List files on the current media - * - menu_media_select - Select one of the attached drives, then go to the file list - */ -void menu_media() { - ui.goto_screen(TERN(HAS_MULTI_VOLUME, menu_media_select, menu_file_selector)); +// Shortcut menu items to go directly to inserted — not necessarily mounted — drives +void menu_file_selector_usb() { + if (!card.isFlashDriveSelected()) { + card.release(); + card.selectMediaFlashDrive(); + } + if (!card.isFlashDriveMounted()) card.mount(); + ui.goto_screen(menu_file_selector); } +// Shortcut menu items to go directly to inserted — not necessarily mounted — drives void menu_file_selector() { ui.encoder_direction_menus(); @@ -135,11 +133,9 @@ void menu_file_selector() { #endif START_MENU(); - #if HAS_MULTI_VOLUME - ACTION_ITEM(MSG_BACK, []{ ui.goto_screen(menu_media_select); }); - #else - BACK_ITEM_F(TERN1(BROWSE_MEDIA_ON_INSERT, screen_history_depth) ? GET_TEXT_F(MSG_MAIN_MENU) : GET_TEXT_F(MSG_BACK)); - #endif + + BACK_ITEM_F(TERN1(BROWSE_MEDIA_ON_INSERT, screen_history_depth) ? GET_TEXT_F(MSG_MAIN_MENU) : GET_TEXT_F(MSG_BACK)); + if (card.flag.workDirIsRoot) { #if !HAS_SD_DETECT ACTION_ITEM(MSG_REFRESH, []{ encoderTopLine = 0; card.mount(); }); diff --git a/Marlin/src/lcd/menu/menu_password.cpp b/Marlin/src/lcd/menu/menu_password.cpp index 33d4231cd543..5e3db846ac63 100644 --- a/Marlin/src/lcd/menu/menu_password.cpp +++ b/Marlin/src/lcd/menu/menu_password.cpp @@ -140,8 +140,18 @@ void Password::access_menu_password() { #if ENABLED(PASSWORD_ON_SD_PRINT_MENU) void Password::media_gatekeeper() { - authenticate_user(menu_media, menu_main); + authenticate_user(menu_file_selector, menu_main); } + #if HAS_SDCARD + void Password::media_gatekeeper_sd() { + authenticate_user(menu_file_selector_sd, menu_main); + } + #endif + #if HAS_USB_FLASH_DRIVE + void Password::media_gatekeeper_usb() { + authenticate_user(menu_file_selector_usb, menu_main); + } + #endif #endif void Password::start_over() { diff --git a/Marlin/src/lcd/tft/ui_color_ui.cpp b/Marlin/src/lcd/tft/ui_color_ui.cpp index 8186650070b2..6c90cb6c3627 100644 --- a/Marlin/src/lcd/tft/ui_color_ui.cpp +++ b/Marlin/src/lcd/tft/ui_color_ui.cpp @@ -323,7 +323,7 @@ void MarlinUI::draw_status_screen() { if (cm && pa) add_control(SDCARD_ICON_X, SDCARD_ICON_Y, STOP, imgCancel, true, COLOR_CONTROL_CANCEL); else - add_control(SDCARD_ICON_X, SDCARD_ICON_Y, menu_media, imgSD, cm && !pa, COLOR_CONTROL_ENABLED, COLOR_CONTROL_DISABLED); + add_control(SDCARD_ICON_X, SDCARD_ICON_Y, menu_file_selector, imgSD, cm && !pa, COLOR_CONTROL_ENABLED, COLOR_CONTROL_DISABLED); #endif #endif diff --git a/Marlin/src/sd/cardreader.cpp b/Marlin/src/sd/cardreader.cpp index 21bc6e780a06..b7d6f70f6501 100644 --- a/Marlin/src/sd/cardreader.cpp +++ b/Marlin/src/sd/cardreader.cpp @@ -28,10 +28,6 @@ #if HAS_MEDIA -#if HAS_MULTI_VOLUME && !SHARED_VOLUME_IS(SD_ONBOARD) && !SHARED_VOLUME_IS(USB_FLASH_DRIVE) - #error "DEFAULT_SHARED_VOLUME must be either SV_SD_ONBOARD or SV_USB_FLASH_DRIVE." -#endif - //#define DEBUG_CARDREADER #include "cardreader.h" @@ -144,7 +140,14 @@ int16_t CardReader::nrItems = -1; DiskIODriver_USBFlash CardReader::media_driver_usbFlash; #endif -DiskIODriver* CardReader::driver = nullptr; +DiskIODriver* CardReader::driver = ( + #if HAS_USB_FLASH_DRIVE && !DEFAULT_VOLUME_IS(SD_ONBOARD) + &CardReader::media_driver_usbFlash + #else + &CardReader::media_driver_sdcard + #endif +); + MarlinVolume CardReader::volume; MediaFile CardReader::myfile; @@ -157,12 +160,6 @@ MediaFile CardReader::myfile; uint32_t CardReader::filesize, CardReader::sdpos; CardReader::CardReader() { - #if HAS_USB_FLASH_DRIVE && !SHARED_VOLUME_IS(SD_ONBOARD) - selectMediaFlashDrive(); - #else - selectMediaSDCard(); - #endif - #if ENABLED(SDCARD_SORT_ALPHA) sort_count = 0; #if ENABLED(SDSORT_GCODE) @@ -483,11 +480,15 @@ void CardReader::mount() { nrItems = -1; if (root.isOpen()) root.close(); - if (!driver->init(SD_SPI_SPEED, SD_SS_PIN) + const bool driver_init = ( + driver->init(SD_SPI_SPEED, SD_SS_PIN) #if PIN_EXISTS(LCD_SDSS) && (LCD_SDSS_PIN != SD_SS_PIN) - && !driver->init(SD_SPI_SPEED, LCD_SDSS_PIN) + || driver->init(SD_SPI_SPEED, LCD_SDSS_PIN) #endif - ) SERIAL_ECHO_MSG(STR_SD_INIT_FAIL); + ); + + if (!driver_init) + SERIAL_ECHO_MSG(STR_SD_INIT_FAIL); else if (!volume.init(driver)) SERIAL_WARN_MSG(STR_SD_VOL_INIT_FAIL); else if (!root.openRoot(&volume)) @@ -519,69 +520,150 @@ void CardReader::mount() { #include "../module/stepper.h" #endif +// Provide a little time for drives to prepare +void CardReader::init() { + #if HAS_USB_FLASH_DRIVE + for (uint8_t i = 10; --i;) { + media_driver_usbFlash.idle(); + hal.watchdog_refresh(); + if (media_driver_usbFlash.isInserted()) break; + delay(20); + } + #endif +} + /** - * Handle SD card events + * Handle media insertion and removal events + * based on SD Card detect and/or driver.isInserted() + * + * MULTI_VOLUME: + * - Track insert/remove for both media drives. + * - If the MOUNTED media is removed call release(). + * - If media is INSERTED when NO MEDIA is mounted, select and mount it. */ void CardReader::manage_media() { - #if HAS_USB_FLASH_DRIVE // Wrap for optimal non-virtual? - driver->idle(); // Handle device tasks (e.g., USB Drive insert / remove) + /** + * Handle device tasks (e.g., USB Drive insert / remove) + * - USB Flash Drive needs to run even when not selected. + * - SD Card currently has no background tasks. + */ + //driver->idle(); + #if HAS_USB_FLASH_DRIVE + //if (!isFlashDriveSelected()) + media_driver_usbFlash.idle(); #endif - static uint8_t prev_stat = 2; // At boot we don't know if media is present or not - uint8_t stat = uint8_t(isInserted()); + // Prevent re-entry during Marlin::idle + #if HAS_MULTI_VOLUME + static bool no_reenter = false; + if (no_reenter) return; + #endif + + static MediaPresence prev_stat = MEDIA_BOOT; // At boot we don't know if media is present or not + + // Live status is based on available media flags + MediaPresence stat = MediaPresence( + #if HAS_MULTI_VOLUME + (isSDCardInserted() ? INSERT_SD : 0) // Without SD Detect it's always "inserted" + | (isFlashDriveInserted() ? INSERT_USB : 0) + #else + isInserted() ? INSERT_MEDIA : 0 // Without SD Detect it's always "inserted" + #endif + ); + if (stat == prev_stat) return; // Already checked and still no change? DEBUG_SECTION(cmm, "CardReader::manage_media()", true); DEBUG_ECHOLNPGM("Media present: ", prev_stat, " -> ", stat); - if (!ui.detected()) { - DEBUG_ECHOLNPGM("SD: No UI Detected."); - return; - } - - flag.workDirIsRoot = true; // Return to root on mount/release/init + // Without a UI there's no auto-mount or release + if (!ui.detected()) { DEBUG_ECHOLNPGM("SD: No UI Detected."); return; } - const uint8_t old_stat = prev_stat; + const MediaPresence old_stat = prev_stat, + old_real = old_stat == MEDIA_BOOT ? INSERT_NONE : old_stat; prev_stat = stat; // Change now to prevent re-entry in safe_delay - if (stat) { // Media Inserted - safe_delay(500); // Some boards need a delay to get settled + #if HAS_MULTI_VOLUME + const int8_t vdiff = (old_real ^ stat), vadd = vdiff & stat; + #endif + const bool did_insert = TERN(HAS_MULTI_VOLUME, vadd, stat) != INSERT_NONE; + + if (did_insert) { // Media Inserted - // Try to mount the media (only later with SD_IGNORE_AT_STARTUP) - if (TERN1(SD_IGNORE_AT_STARTUP, old_stat != 2)) mount(); - if (!isMounted()) stat = 0; // Not mounted? + TERN_(HAS_MULTI_VOLUME, ui.refresh()); // Refresh for insert events without messages + + // Some media is already mounted? Nothing to do. + if (TERN0(HAS_MULTI_VOLUME, isMounted())) return; + + // Prevent re-entry during the following phases + TERN_(HAS_MULTI_VOLUME, no_reenter = true); + + // Try to mount the media (but not at boot if SD_IGNORE_AT_STARTUP) + if (TERN1(SD_IGNORE_AT_STARTUP, old_stat > MEDIA_BOOT)) { + // If both SD/FD mount simultaneously prefer the default + #if HAS_MULTI_VOLUME + #if HAS_USB_FLASH_DRIVE && !DEFAULT_VOLUME_IS(SD_ONBOARD) + if (vadd & INSERT_USB) selectMediaFlashDrive(); + else if (vadd & INSERT_SD) selectMediaSDCard(); + #else + if (vadd & INSERT_SD) selectMediaSDCard(); + else if (vadd & INSERT_USB) selectMediaFlashDrive(); + #endif + #endif + safe_delay(500); // Time for inserted media to settle. May re-enter for multiple media? + mount(); + } + + // If the selected media isn't mounted throw an alert in ui.media_changed + if (!isMounted()) stat = old_real; TERN_(RESET_STEPPERS_ON_MEDIA_INSERT, reset_stepper_drivers()); // Workaround for Cheetah bug + + // Re-enable media detection logic + TERN_(HAS_MULTI_VOLUME, no_reenter = false); + } + else if ( + // Media was removed from the device slot + #if HAS_MULTI_VOLUME + (isSDCardSelected() && (vdiff & INSERT_SD)) + || (isFlashDriveSelected() && (vdiff & INSERT_USB)) + #else + stat // == INSERT_MEDIA + #endif + ) { + flag.workDirIsRoot = true; // Return to root on release + release(); + //TERN_(HAS_MULTI_VOLUME, prev_stat = INSERT_NONE); // HACK to try mounting any remaining media } else { - TERN_(HAS_SD_DETECT, release()); // Card is released + #if HAS_MULTI_VOLUME + stat = old_real; // Ignore un-mounted media being ejected + ui.refresh(); // Refresh for menus that show inserted unmounted media + #endif } - ui.media_changed(old_stat, stat); // Update the UI or flag an error + ui.media_changed(old_stat, stat); // Update the UI or flag an error - if (!stat) return; // Exit if no media is present - - bool do_auto = true; UNUSED(do_auto); + if (stat == INSERT_NONE) return; // Exit if no media is present // First mount on boot? Load emulated EEPROM and look for PLR file. - if (old_stat == 2) { + if (old_stat <= MEDIA_BOOT) { DEBUG_ECHOLNPGM("First mount."); // Load settings the first time media is inserted (not just during init) TERN_(SDCARD_EEPROM_EMULATION, settings.first_load()); - // Check for PLR file. Skip One-Click and auto#.g if found - TERN_(POWER_LOSS_RECOVERY, if (recovery.check()) do_auto = false); + // Check for PLR file. If found skip other procedures! + if (TERN0(POWER_LOSS_RECOVERY, recovery.check())) return; } - // Find the newest file and prompt to print it. - TERN_(ONE_CLICK_PRINT, if (do_auto && one_click_check()) do_auto = false); + // Find the newest file and prompt to print it. Skip other procedures! + if (TERN0(ONE_CLICK_PRINT, one_click_check())) return; - // Also for the first mount run auto#.g for machine init. - // (Skip if PLR or One-Click Print was invoked.) - if (old_stat == 2) { + // On first mount at boot run auto#.g for machine init. + if (old_stat <= MEDIA_BOOT) { // Look for auto0.g on the next idle() - IF_DISABLED(NO_SD_AUTOSTART, if (do_auto) autofile_begin()); + IF_DISABLED(NO_SD_AUTOSTART, autofile_begin()); } } @@ -590,6 +672,8 @@ void CardReader::manage_media() { * Used by M22, "Release Media", manage_media. */ void CardReader::release() { + if (!flag.mounted) return; + // Card removed while printing? Abort! if (isStillPrinting()) abortFilePrintSoon(); diff --git a/Marlin/src/sd/cardreader.h b/Marlin/src/sd/cardreader.h index 1a8843da5870..6c1f05b8aff7 100644 --- a/Marlin/src/sd/cardreader.h +++ b/Marlin/src/sd/cardreader.h @@ -78,6 +78,14 @@ typedef struct { ; } card_flags_t; +enum MediaPresence : int8_t { + MEDIA_BOOT = -1, + INSERT_NONE = 0x00, + INSERT_MEDIA = 0x01, + INSERT_SD = TERN(HAS_MULTI_VOLUME, 0x02, 0x00), + INSERT_USB = TERN(HAS_MULTI_VOLUME, 0x04, 0x00) +}; + enum ListingFlags : uint8_t { LS_LONG_FILENAME, LS_ONLY_BIN, LS_TIMESTAMP }; enum SortFlag : int8_t { AS_REV = -1, AS_OFF, AS_FWD, AS_ALSO_REV }; @@ -102,6 +110,9 @@ class CardReader { CardReader(); + // Init at startup before mounting media + static void init(); + /** * Media Selection - Only one drive may be active at a time, * so switching drives (currently) returns to the root folder.