0
0
Fork 0
mirror of https://github.com/MarlinFirmware/Marlin.git synced 2025-02-16 22:37:35 +00:00

Add PANELDUE support

This commit is contained in:
Scott Lahteine 2023-02-18 00:51:59 -06:00
parent f6c58c59c3
commit 4bf9ffb71f
12 changed files with 529 additions and 8 deletions

View file

@ -3294,6 +3294,12 @@
//
//#define NEXTION_TFT
//
// PanelDue touch controller by Escher3D
// http://escher3d.com/pages/order/products/product2.php
//
//#define PANELDUE
//
// Third-party or vendor-customized controller interfaces.
// Sources should be installed in 'src/lcd/extui'.

View file

@ -875,6 +875,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
case 407: M407(); break; // M407: Display measured filament diameter
#endif
#if ENABLED(PANELDUE)
case 408: M408(); break; // M408: Report machine state in JSON format
#endif
#if HAS_FILAMENT_SENSOR
case 412: M412(); break; // M412: Enable/Disable filament runout detection
#endif

View file

@ -234,6 +234,7 @@
* M405 - Enable Filament Sensor flow control. "M405 D<delay_cm>". (Requires FILAMENT_WIDTH_SENSOR)
* M406 - Disable Filament Sensor flow control. (Requires FILAMENT_WIDTH_SENSOR)
* M407 - Display measured filament diameter in millimeters. (Requires FILAMENT_WIDTH_SENSOR)
* M408 - Report machine state in JSON format. (Requires PANELDUE)
* M410 - Quickstop. Abort all planned moves.
* M412 - Enable / Disable Filament Runout Detection. (Requires FILAMENT_RUNOUT_SENSOR)
* M413 - Enable / Disable Power-Loss Recovery. (Requires POWER_LOSS_RECOVERY)
@ -1051,6 +1052,10 @@ private:
static void M407();
#endif
#if ENABLED(PANELDUE)
static void M408();
#endif
#if HAS_FILAMENT_SENSOR
static void M412();
static void M412_report(const bool forReplay=true);

View file

@ -0,0 +1,186 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2022 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "../../inc/MarlinConfig.h"
#if ENABLED(PANELDUE)
#include "../gcode.h"
#include "../../module/planner.h"
#include "../../module/printcounter.h"
#include "../../module/temperature.h"
#include "../../sd/cardreader.h"
#include "../../lcd/marlinui.h"
#if ENABLED(LCD_SET_PROGRESS_MANUALLY)
extern uint8_t progress_bar_percent;
#endif
/**
* M408: Report machine state in JSON format for PanelDue
*
* S<style> Include static values with S1
*
* Since Marlin doesn't use sequence numbers, the R parameter is omitted.
*
*/
inline void json_key(const char * const name) {
SERIAL_ECHOPGM(",\"", name, "\":");
}
inline void json_array_print(const char * const name, const float val[], const int8_t size) {
json_key(name);
SERIAL_CHAR('[');
for (uint8_t i = 0; i < size; i++) {
SERIAL_ECHO(val[i]);
if (i < size - 1) SERIAL_CHAR(',');
}
SERIAL_CHAR(']');
safe_delay(1);
}
void GcodeSuite::M408() {
float tmp[10];
SERIAL_CHAR('{');
// status: SD printing or idle
json_key(PSTR("status"));
SERIAL_ECHO(IS_SD_PRINTING() ? F("\"P\"") : F("\"I\""));
// heaters: current bed, hotend temperatures
tmp[0] = TERN0(HAS_HEATED_BED, thermalManager.degBed());
HOTEND_LOOP() tmp[e + 1] = thermalManager.degHotend(e);
json_array_print(PSTR("heaters"), tmp, HOTENDS + 1);
// active: target bed, hotend temperatures
tmp[0] = TERN0(HAS_HEATED_BED, thermalManager.degTargetBed());
HOTEND_LOOP() tmp[e + 1] = thermalManager.degTargetHotend(e);
json_array_print(PSTR("active"), tmp, HOTENDS + 1);
// standby: in Marlin, same as active
json_array_print(PSTR("standby"), tmp, HOTENDS + 1);
// hstat: in Marlin, heating or off
json_key(PSTR("hstat"));
SERIAL_CHAR('[');
SERIAL_CHAR(TERN0(HAS_HEATED_BED, thermalManager.degTargetBed()) ? '2' : '0');
HOTEND_LOOP() {
SERIAL_CHAR(',');
SERIAL_CHAR(thermalManager.degTargetHotend(e) ? '2' : '0');
}
SERIAL_CHAR(']');
// pos: tool position
tmp[0] = current_position.x; tmp[1] = current_position.y; tmp[2] = current_position.z;
json_array_print(PSTR("pos"), tmp, 3);
// extr: extruder position
for (uint8_t e = 0; e < EXTRUDERS; e++) tmp[e] = current_position.e;
json_array_print(PSTR("extr"), tmp, EXTRUDERS);
// sfactor: feedrate %
json_key(PSTR("sfactor"));
SERIAL_ECHO(feedrate_percentage);
// efactor: flow %
for (uint8_t e = 0; e < EXTRUDERS; e++) tmp[e] = planner.flow_percentage[e];
json_array_print(PSTR("efactor"), tmp, EXTRUDERS);
// tool: selected extruder
json_key(PSTR("tool"));
SERIAL_ECHO(active_extruder);
// probe: the last Z probe reading (just 0 for now)
json_key(PSTR("probe"));
SERIAL_ECHOPGM("\"0\"");
#if FAN_COUNT > 0
// fanPercent: the last Z probe reading
for (uint8_t i = 0; i < FAN_COUNT; i++) tmp[i] = map(thermalManager.fan_speed[i], 0, 255, 0, 100);
json_array_print(PSTR("fanPercent"), tmp, FAN_COUNT);
// fanRPM: print cooling fan faux RPM
json_key(PSTR("fanRPM"));
SERIAL_ECHO(int(thermalManager.fan_speed[0] * 10));
#endif
// homed: axis homed status
json_key(PSTR("homed"));
LOOP_NUM_AXES(i) {
SERIAL_CHAR(i ? ',' : '[');
SERIAL_ECHO(int(TEST(axes_homed, i)));
}
SERIAL_CHAR(']');
#if ENABLED(SDSUPPORT)
// fraction_printed: print progress
json_key(PSTR("fraction_printed"));
SERIAL_ECHO(0.01 * ui.get_progress_percent());
#endif
#if HAS_DISPLAY
// message
if (ui.status_message[0]) {
json_key(PSTR("message"));
SERIAL_ECHOPGM("\"", &ui.status_message);
SERIAL_CHAR('"');
}
#endif
// Extra fields
if (parser.intval('S') == 1) {
// myName
json_key(PSTR("myName"));
SERIAL_ECHOPGM("\"" MACHINE_NAME "\"");
// firmwareName
json_key(PSTR("firmwareName"));
SERIAL_ECHOPGM("\"Marlin\"");
// geometry
json_key(PSTR("geometry"));
SERIAL_ECHOPGM("\""
TERN_(IS_FULL_CARTESIAN, "cartesian")
TERN_(IS_SCARA, "scara")
TERN_(DELTA, "delta")
TERN_(COREXY, "corexy")
TERN_(COREXZ, "corexz")
TERN_(COREYZ, "coreyz")
TERN_(COREYX, "coreyx")
TERN_(COREZX, "corezx")
TERN_(COREZY, "corezy")
"\""
);
// axes
json_key(PSTR("axes"));
SERIAL_CHAR('3');
// volumes: the number of SD card slots available
json_key(PSTR("volumes"));
SERIAL_CHAR(TERN(SDSUPPORT, '1', '0'));
// numTools: extruder count
json_key(PSTR("numTools"));
SERIAL_CHAR('0' + EXTRUDERS);
}
SERIAL_EOL();
}
#endif // PANELDUE

View file

@ -41,11 +41,54 @@
*/
void GcodeSuite::M20() {
if (card.flag.mounted) {
SERIAL_ECHOLNPGM(STR_BEGIN_FILE_LIST);
card.ls(TERN0(CUSTOM_FIRMWARE_UPLOAD, parser.boolval('F') << LS_ONLY_BIN)
| TERN0(LONG_FILENAME_HOST_SUPPORT, parser.boolval('L') << LS_LONG_FILENAME)
| TERN0(M20_TIMESTAMP_SUPPORT, parser.boolval('T') << LS_TIMESTAMP));
SERIAL_ECHOLNPGM(STR_END_FILE_LIST);
#if ENABLED(PANELDUE)
const bool json = parser.intval('S') == 2;
if (json) {
// The P parameter gives the path
char *path = parser.stringval('P');
#if DISABLED(GCODE_QUOTED_STRINGS)
// 'S' before 'P' or 'S' is part of the path
if (path < parser.stringval('S')) path = nullptr;
#endif
// Get the CWD as the root for dive
SdFile *listDirPtr;
bool ok = true;
// If any path was specified, dive into it
if (path && path[0]) {
// Ensure the path ends with a slash
const size_t len = strlen(path);
if (len > 1 && path[len - 1] != '/') strcat(path, "/");
// Dive listDirPtr down to the path
ok = (bool)card.diveToFile(false, listDirPtr, path);
}
else
path = (char*)".";
if (ok) {
// Remove the slash at the end
const size_t len = strlen(path);
if (len > 1 && path[len - 1] == '/') path[len - 1] = '\0';
// Print a flat listing of the folder in JSON
SERIAL_ECHOPGM("{\"dir\":\"", path, "\",");
card.lsJSON(0, *listDirPtr); // Don't enter subfolders (but list folders)
SERIAL_ECHOLNPGM("}");
}
}
#else
constexpr bool json = false;
#endif
if (!json) {
SERIAL_ECHOLNPGM(STR_BEGIN_FILE_LIST);
card.ls(TERN0(CUSTOM_FIRMWARE_UPLOAD, parser.boolval('F') << LS_ONLY_BIN)
| TERN0(LONG_FILENAME_HOST_SUPPORT, parser.boolval('L') << LS_LONG_FILENAME)
| TERN0(M20_TIMESTAMP_SUPPORT, parser.boolval('T') << LS_TIMESTAMP));
SERIAL_ECHOLNPGM(STR_END_FILE_LIST);
}
}
else
SERIAL_ECHO_MSG(STR_NO_MEDIA);

View file

@ -549,7 +549,7 @@
#endif
// Extensible UI serial touch screens. (See src/lcd/extui)
#if ANY(HAS_DGUS_LCD, MALYAN_LCD, ANYCUBIC_LCD_I3MEGA, ANYCUBIC_LCD_CHIRON, NEXTION_TFT, TOUCH_UI_FTDI_EVE, DWIN_LCD_PROUI)
#if ANY(HAS_DGUS_LCD, MALYAN_LCD, ANYCUBIC_LCD_I3MEGA, ANYCUBIC_LCD_CHIRON, NEXTION_TFT, TOUCH_UI_FTDI_EVE, DWIN_LCD_PROUI, PANELDUE)
#define IS_EXTUI 1 // Just for sanity check.
#define EXTENSIBLE_UI
#endif
@ -568,7 +568,7 @@
#endif
// Serial Controllers require LCD_SERIAL_PORT
#if ANY(IS_DWIN_MARLINUI, HAS_DWIN_E3V2, HAS_DGUS_LCD, MALYAN_LCD, ANYCUBIC_LCD_I3MEGA, ANYCUBIC_LCD_CHIRON, NEXTION_TFT, SOVOL_SV06_RTS)
#if ANY(IS_DWIN_MARLINUI, HAS_DWIN_E3V2, HAS_DGUS_LCD, MALYAN_LCD, ANYCUBIC_LCD_I3MEGA, ANYCUBIC_LCD_CHIRON, NEXTION_TFT, SOVOL_SV06_RTS, PANELDUE)
#define LCD_IS_SERIAL_HOST 1
#endif

View file

@ -2765,6 +2765,7 @@ static_assert(NUM_SERVOS <= NUM_SERVO_PLUGS, "NUM_SERVOS (or some servo index) i
+ ENABLED(MAKRPANEL) \
+ ENABLED(MALYAN_LCD) \
+ ENABLED(NEXTION_TFT) \
+ ENABLED(PANELDUE) \
+ ENABLED(MKS_LCD12864A) \
+ ENABLED(MKS_LCD12864B) \
+ ENABLED(OLED_PANEL_TINYBOY2) \

View file

@ -0,0 +1,199 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
/**
* lcd/extui/paneldue/paneldue_extui.cpp
*/
#include "../../../inc/MarlinConfigPre.h"
#if ENABLED(PANELDUE)
#include "../ui_api.h"
namespace ExtUI {
void onStartup() {
/* Initialize the display module here. The following
* routines are available for access to the GPIO pins:
*
* SET_OUTPUT(pin)
* SET_INPUT_PULLUP(pin)
* SET_INPUT(pin)
* WRITE(pin,value)
* READ(pin)
*/
}
void onIdle() {}
void onPrinterKilled(FSTR_P const error, FSTR_P const component) {}
void onMediaMounted() {}
void onMediaError() {}
void onMediaRemoved() {}
void onHeatingError(const heater_id_t heater_id) {}
void onMinTempError(const heater_id_t heater_id) {}
void onMaxTempError(const heater_id_t heater_id) {}
void onPlayTone(const uint16_t frequency, const uint16_t duration/*=0*/) {}
void onPrintTimerStarted() {}
void onPrintTimerPaused() {}
void onPrintTimerStopped() {}
#if HAS_FILAMENT_SENSOR
void onFilamentRunout(const extruder_t extruder) {}
#endif
void onUserConfirmRequired(const char * const msg) {}
// For fancy LCDs include an icon ID, message, and translated button title
void onUserConfirmRequired(const int icon, const char * const cstr, FSTR_P const fBtn) {}
void onUserConfirmRequired(const int icon, FSTR_P const fstr, FSTR_P const fBtn) {}
void onStatusChanged(const char * const) {}
#if ENABLED(ADVANCED_PAUSE_FEATURE)
void onPauseMode(
const PauseMessage message,
const PauseMode mode/*=PAUSE_MODE_SAME*/,
const uint8_t extruder/*=active_extruder*/
) {
stdOnPauseMode(message, mode, extruder);
}
#endif
void onHomingStart() {}
void onHomingDone() {}
void onPrintDone() {}
void onFactoryReset() {}
void onStoreSettings(char *buff) {
// Called when saving to EEPROM (i.e. M500). If the ExtUI needs
// permanent data to be stored, it can write up to eeprom_data_size bytes
// into buff.
// Example:
// static_assert(sizeof(myDataStruct) <= eeprom_data_size);
// memcpy(buff, &myDataStruct, sizeof(myDataStruct));
}
void onLoadSettings(const char *buff) {
// Called while loading settings from EEPROM. If the ExtUI
// needs to retrieve data, it should copy up to eeprom_data_size bytes
// from buff
// Example:
// static_assert(sizeof(myDataStruct) <= eeprom_data_size);
// memcpy(&myDataStruct, buff, sizeof(myDataStruct));
}
void onPostprocessSettings() {
// Called after loading or resetting stored settings
}
void onSettingsStored(const bool success) {
// Called after the entire EEPROM has been written,
// whether successful or not.
}
void onSettingsLoaded(const bool success) {
// Called after the entire EEPROM has been read,
// whether successful or not.
}
#if HAS_LEVELING
void onLevelingStart() {}
void onLevelingDone() {}
#if ENABLED(PREHEAT_BEFORE_LEVELING)
celsius_t getLevelingBedTemp() { return LEVELING_BED_TEMP; }
#endif
#endif
#if HAS_MESH
void onMeshUpdate(const int8_t xpos, const int8_t ypos, const_float_t zval) {
// Called when any mesh points are updated
}
void onMeshUpdate(const int8_t xpos, const int8_t ypos, const probe_state_t state) {
// Called to indicate a special condition
}
#endif
#if ENABLED(PREVENT_COLD_EXTRUSION)
void onSetMinExtrusionTemp(const celsius_t) {}
#endif
#if ENABLED(POWER_LOSS_RECOVERY)
void onSetPowerLoss(const bool onoff) {
// Called when power-loss is enabled/disabled
}
void onPowerLoss() {
// Called when power-loss state is detected
}
void onPowerLossResume() {
// Called on resume from power-loss
}
#endif
#if HAS_PID_HEATING
void onPIDTuning(const pidresult_t rst) {
// Called for temperature PID tuning result
switch (rst) {
case PID_STARTED:
case PID_BED_STARTED:
case PID_CHAMBER_STARTED: break;
case PID_BAD_HEATER_ID: break;
case PID_TEMP_TOO_HIGH: break;
case PID_TUNING_TIMEOUT: break;
case PID_DONE: break;
}
}
void onStartM303(const int count, const heater_id_t hid, const celsius_t temp) {
// Called by M303 to update the UI
}
#endif
#if ENABLED(MPC_AUTOTUNE)
void onMPCTuning(const mpcresult_t rst) {
// Called for temperature MPC tuning result
switch (rst) {
case MPC_STARTED: break;
case MPC_TEMP_ERROR: break;
case MPC_INTERRUPTED: break;
case MPC_DONE: break;
}
}
#endif
#if ENABLED(PLATFORM_M997_SUPPORT)
void onFirmwareFlash() {}
#endif
void onSteppersDisabled() {}
void onSteppersEnabled() {}
void onAxisDisabled(const axis_t) {}
void onAxisEnabled(const axis_t) {}
} // ExtUI
#endif // PANELDUE

View file

@ -178,7 +178,7 @@ CardReader::CardReader() {
IF_DISABLED(NO_SD_AUTOSTART, autofile_cancel());
workDirDepth = 0;
ZERO(workDirParents);
//LOOP_L_N(i, MAX_DIR_DEPTH) workDirParents[i] = SdFile();
#if ALL(HAS_MEDIA, HAS_SD_DETECT)
SET_INPUT_PULLUP(SD_DETECT_PIN);
@ -366,6 +366,54 @@ void CardReader::ls(const uint8_t lsflags/*=0*/) {
}
}
#if ENABLED(PANELDUE)
//
// Recursive method to list files in JSON format
// The PanelDue will call this with no depth to get
// just the contents of the current folder. However,
// it's possible to set up code that will get the
// full card listing in a format much more compact
// than the current M20 output for many folders.
//
void CardReader::printListingJSON(const uint8_t depth, SdFile parent) {
dir_t p;
while (parent.readDir(&p, longFilename) > 0) {
if (depth && DIR_IS_SUBDIR(&p)) {
// Get the short name for the item, which we know is a folder
char dosFilename[FILENAME_LENGTH];
createFilename(dosFilename, p);
// Put this folder on the stack and recurse into it.
SdFile child; // child.close() in destructor
if (!child.open(&parent, dosFilename, O_READ)) return;
// Make the folder an object with an array inside
SERIAL_ECHOPGM("{\"", dosFilename, "\":[");
printListingJSON(depth - 1, child);
SERIAL_ECHOPGM("]},");
}
else if (is_visible_entity(p)) {
SERIAL_CHAR('"');
if (flag.filenameIsDir) SERIAL_CHAR('*');
SERIAL_ECHO(createFilename(filename, p));
SERIAL_ECHOPGM("\",");
}
}
}
//
// List all files on the SD card
//
void CardReader::lsJSON(const uint8_t depth, SdFile parent) {
SERIAL_ECHOPGM("\"files\":[");
parent.rewind();
printListingJSON(depth, parent);
SERIAL_ECHOPGM("]");
}
#endif // PANELDUE
#if ENABLED(LONG_FILENAME_HOST_SUPPORT)
//

View file

@ -220,6 +220,10 @@ public:
static void ls(const uint8_t lsflags=0);
#if ENABLED(PANELDUE)
static void lsJSON(const uint8_t depth, SdFile parent);
#endif
#if ENABLED(POWER_LOSS_RECOVERY)
static bool jobRecoverFileExists();
static void openJobRecoveryFile(const bool read);
@ -246,6 +250,8 @@ public:
static int16_t write(void *buf, uint16_t nbyte) { return file.isOpen() ? file.write(buf, nbyte) : -1; }
static void setIndex(const uint32_t index) { file.seekSet((sdpos = index)); }
//static inline int16_t get() { sdpos = file.curPosition(); return (int16_t)file.read(); }
// TODO: rename to diskIODriver()
static DiskIODriver* diskIODriver() { return driver; }
@ -357,6 +363,9 @@ private:
MediaFile parent, const char * const prepend, const uint8_t lsflags
OPTARG(LONG_FILENAME_HOST_SUPPORT, const char * const prependLong=nullptr)
);
#if ENABLED(PANELDUE)
static void printListingJSON(const uint8_t depth, SdFile parent);
#endif
#if ENABLED(SDCARD_SORT_ALPHA)
static void flush_presort();

View file

@ -86,6 +86,25 @@ opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER
opt_disable X_DRIVER_TYPE Y_DRIVER_TYPE Z_DRIVER_TYPE
exec_test $1 $2 "E Axis Only | DOGM MarlinUI" "$3"
#
# Test MegaController with PANELDUE and many features
#
restore_configs
opt_set MOTHERBOARD BOARD_MEGACONTROLLER LCD_LANGUAGE de CONTROLLERFAN_SPEED_IDLE 128 I2C_SLAVE_ADDRESS 63 \
PWM_MOTOR_CURRENT "{ 1300, 1300, 1250 }"
opt_enable EEPROM_SETTINGS EEPROM_CHITCHAT \
PANELDUE SDSUPPORT PCA9632 SOUND_MENU_ITEM \
AUTO_BED_LEVELING_BILINEAR PROBE_MANUALLY G26_MESH_VALIDATION MESH_EDIT_MENU \
LIN_ADVANCE ADVANCE_K_EXTRA \
INCH_MODE_SUPPORT TEMPERATURE_UNITS_SUPPORT EXPERIMENTAL_I2CBUS M100_FREE_MEMORY_WATCHER \
NOZZLE_PARK_FEATURE NOZZLE_CLEAN_FEATURE \
ADVANCED_PAUSE_FEATURE PARK_HEAD_ON_PAUSE ADVANCED_PAUSE_CONTINUOUS_PURGE FILAMENT_LOAD_UNLOAD_GCODES \
PRINTCOUNTER SERVICE_NAME_1 SERVICE_INTERVAL_1 M114_DETAIL \
FAN_SOFT_PWM USE_CONTROLLER_FAN CONTROLLER_FAN_EDITABLE
opt_add M100_FREE_MEMORY_DUMPER
opt_add M100_FREE_MEMORY_CORRUPTOR
exec_test $1 $2 "MEGACONTROLLER | PanelDUE | M100 | PWM_MOTOR_CURRENT | PRINTCOUNTER | Advanced Pause ..." "$3"
#
# Mixing Extruder with 5 steppers, Russian
#

View file

@ -206,6 +206,7 @@ EXTUI_EXAMPLE = build_src_filter=+<src/lcd/extui/exampl
TOUCH_UI_FTDI_EVE = build_src_filter=+<src/lcd/extui/ftdi_eve_touch_ui>
MALYAN_LCD = build_src_filter=+<src/lcd/extui/malyan>
NEXTION_TFT = build_src_filter=+<src/lcd/extui/nextion>
PANELDUE = build_src_filter=+<src/lcd/extui/paneldue> +<src/gcode/lcd/M408.cpp>
USE_UHS2_USB = build_src_filter=+<src/sd/usb_flashdrive/lib-uhs2>
USE_UHS3_USB = build_src_filter=+<src/sd/usb_flashdrive/lib-uhs3>
USB_FLASH_DRIVE_SUPPORT = build_src_filter=+<src/sd/usb_flashdrive/Sd2Card_FlashDrive.cpp>