diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index 0e17e74a526e..0b8d454ec2da 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -2044,7 +2044,8 @@ */ //#define FILAMENT_RUNOUT_SENSOR #if ENABLED(FILAMENT_RUNOUT_SENSOR) - #define FIL_RUNOUT_ENABLED_DEFAULT true // Enable the sensor on startup. Override with M412 followed by M500. + #define FIL_RUNOUT_ENABLED_DEFAULT true // Enable sensor monitoring on startup. Override with M412 followed by M500. + #define FIL_RUNOUT_ENABLED { true } // Individual runout sensors default enabled state. Override with M591 followed by M500. #define NUM_RUNOUT_SENSORS 1 // Number of sensors, up to one per extruder. Define a FIL_RUNOUT#_PIN for each. #define FIL_RUNOUT_STATE LOW // Pin state indicating that filament is NOT present. diff --git a/Marlin/src/feature/host_actions.cpp b/Marlin/src/feature/host_actions.cpp index 94bc4db0113c..0b169fec9aaf 100644 --- a/Marlin/src/feature/host_actions.cpp +++ b/Marlin/src/feature/host_actions.cpp @@ -193,7 +193,7 @@ void HostUI::action(FSTR_P const fstr, const bool eol) { #endif #if HAS_FILAMENT_SENSOR if (runout.filament_ran_out) { // Disable a triggered sensor - runout.enabled = false; + runout.monitoring = false; runout.reset(); } #endif diff --git a/Marlin/src/feature/mmu3/mmu3.cpp b/Marlin/src/feature/mmu3/mmu3.cpp index 4c3c5e6fd723..7a9db58b2bfc 100644 --- a/Marlin/src/feature/mmu3/mmu3.cpp +++ b/Marlin/src/feature/mmu3/mmu3.cpp @@ -279,8 +279,8 @@ namespace MMU3 { && xy_are_trusted() && e_active() #if ENABLED(MMU3_SPOOL_JOIN_CONSUMES_ALL_FILAMENT) - && runout.enabled // to prevent M600 to be triggered during M600 AUTO - && !FILAMENT_PRESENT() // so the filament is totally consumed + && runout.monitoring // to prevent M600 triggering during M600 AUTO + && !FILAMENT_PRESENT() // so the filament is totally consumed #endif ) { SERIAL_ECHOLN_P("FINDA filament runout!"); @@ -291,7 +291,7 @@ namespace MMU3 { // disable the filament runout sensor (this is going to be re-enabled after the filament is loaded) runout.reset(); runout.filament_ran_out = false; // trying to disable the purge more / continue message - runout.enabled = false; + runout.monitoring = false; #endif queue.enqueue_now(F("M600A")); // Save print and run M600 A (automatic) command } diff --git a/Marlin/src/feature/mmu3/mmu3_fsensor.cpp b/Marlin/src/feature/mmu3/mmu3_fsensor.cpp index 32d2fac3f60b..63d108256915 100644 --- a/Marlin/src/feature/mmu3/mmu3_fsensor.cpp +++ b/Marlin/src/feature/mmu3/mmu3_fsensor.cpp @@ -36,14 +36,14 @@ namespace MMU3 { #if HAS_FILAMENT_SENSOR FSensorBlockRunout::FSensorBlockRunout() { - runout.enabled = false; // Suppress filament runouts while loading filament. + runout.monitoring = false; // Suppress filament runouts while loading filament. //fsensor.setAutoLoadEnabled(false); //suppress filament autoloads while loading filament. } FSensorBlockRunout::~FSensorBlockRunout() { //fsensor.settings_init(); // restore filament runout state. runout.reset(); - runout.enabled = true; + runout.monitoring = true; //SERIAL_ECHOLNPGM("FSUnBlockRunout"); } diff --git a/Marlin/src/feature/runout.cpp b/Marlin/src/feature/runout.cpp index f0b94d0de856..c840301b4859 100644 --- a/Marlin/src/feature/runout.cpp +++ b/Marlin/src/feature/runout.cpp @@ -32,8 +32,12 @@ FilamentMonitor runout; -bool FilamentMonitorBase::enabled = true, - FilamentMonitorBase::filament_ran_out; // = false +// Default: master monitoring on, all sensors individually enabled, mode RM_NONE. +bool FilamentMonitorBase::monitoring, // initialized by settings + FilamentMonitorBase::enabled[NUM_RUNOUT_SENSORS], // initialized by settings + FilamentMonitorBase::filament_ran_out; // = false + +RunoutMode FilamentMonitorBase::mode[NUM_RUNOUT_SENSORS]; // initialized by settings #if ENABLED(HOST_ACTION_COMMANDS) bool FilamentMonitorBase::host_handling; // = false @@ -46,7 +50,9 @@ bool FilamentMonitorBase::enabled = true, #endif #if HAS_FILAMENT_RUNOUT_DISTANCE - float RunoutResponseDelayed::runout_distance_mm = FILAMENT_RUNOUT_DISTANCE_MM; + // Per-sensor runout distances – initialized by settings.load() from + // FILAMENT_RUNOUT_DISTANCE_MM, then independently adjustable via M591. + float RunoutResponseDelayed::runout_distance_mm[NUM_RUNOUT_SENSORS]; countdown_t RunoutResponseDelayed::mm_countdown; #if ENABLED(FILAMENT_MOTION_SENSOR) uint8_t FilamentSensorEncoder::motion_detected; diff --git a/Marlin/src/feature/runout.h b/Marlin/src/feature/runout.h index c33980d7e627..018a18299bc5 100644 --- a/Marlin/src/feature/runout.h +++ b/Marlin/src/feature/runout.h @@ -82,15 +82,105 @@ extern FilamentMonitor runout; /*******************************************************************************************/ +/** + * RunoutMode describes how a sensor reports filament-out. + * RM_NONE - Use the compile-time FIL_RUNOUT#_STATE default (backward compat) + * RM_OUT_ON_LOW - Sensor output goes LOW when filament is absent + * RM_OUT_ON_HIGH - Sensor output goes HIGH when filament is absent + * RM_MOTION_SENSOR - Quadrature / hall-effect motion sensor (no switch) + */ +enum RunoutMode : uint8_t { + RM_NONE = 0, + RM_OUT_ON_LOW = 1, + RM_OUT_ON_HIGH = 2, + RM_RESERVED3 = 3, + RM_RESERVED4 = 4, + RM_RESERVED5 = 5, + RM_RESERVED6 = 6, + RM_MOTION_SENSOR = 7 +}; + class FilamentMonitorBase { public: - static bool enabled, filament_ran_out; + static bool enabled[NUM_RUNOUT_SENSORS]; // per-sensor enable; preserved across master toggle + static bool monitoring; // master on/off; gates all sensor checks + static bool filament_ran_out; + static RunoutMode mode[NUM_RUNOUT_SENSORS]; + + // Set one sensor's enabled state (does not affect master 'monitoring' flag) + static void set_enabled(const uint8_t e, const bool v) { enabled[e] = v; } + // Set all sensors' enabled state at once (does not affect master 'monitoring' flag) + static void set_enabled(const bool v) { for (uint8_t i = 0; i < NUM_RUNOUT_SENSORS; ++i) enabled[i] = v; } + // True if any sensor is individually enabled + static bool any_enabled() { for (uint8_t i = 0; i < NUM_RUNOUT_SENSORS; ++i) if (enabled[i]) return true; return false; } + // True if sensor 's' is active (master on AND individually enabled) + static bool is_active(const uint8_t s) { return monitoring && enabled[s]; } #if ENABLED(HOST_ACTION_COMMANDS) static bool host_handling; #else static constexpr bool host_handling = false; #endif + + /** + * Return the logical "out" pin state for sensor e. + * + * - RM_OUT_ON_HIGH : filament absent when pin reads HIGH + * - RM_OUT_ON_LOW : filament absent when pin reads LOW + * - RM_NONE : fall back to the compile-time FIL_RUNOUT#_STATE constant + * (backward compatible with configurations that don't use M591) + * - RM_MOTION_SENSOR: returns LOW (motion sensor logic lives in FilamentSensorEncoder) + * - other: falls back to compile-time FIL_RUNOUT#_STATE constant + */ + static uint8_t out_state(const uint8_t e) { + switch (mode[e]) { + case RM_OUT_ON_HIGH: return HIGH; + case RM_OUT_ON_LOW: return LOW; + case RM_MOTION_SENSOR: return LOW; // Motion sensor: active-low; FilamentSensorEncoder handles encoding + case RM_NONE: + default: break; + } + // RM_NONE: fall back to the compile-time per-sensor FIL_RUNOUT#_STATE constant. + static constexpr uint8_t _states[8] = { + FIL_RUNOUT1_STATE, + #if NUM_RUNOUT_SENSORS >= 2 + FIL_RUNOUT2_STATE, + #else + LOW, + #endif + #if NUM_RUNOUT_SENSORS >= 3 + FIL_RUNOUT3_STATE, + #else + LOW, + #endif + #if NUM_RUNOUT_SENSORS >= 4 + FIL_RUNOUT4_STATE, + #else + LOW, + #endif + #if NUM_RUNOUT_SENSORS >= 5 + FIL_RUNOUT5_STATE, + #else + LOW, + #endif + #if NUM_RUNOUT_SENSORS >= 6 + FIL_RUNOUT6_STATE, + #else + LOW, + #endif + #if NUM_RUNOUT_SENSORS >= 7 + FIL_RUNOUT7_STATE, + #else + LOW, + #endif + #if NUM_RUNOUT_SENSORS >= 8 + FIL_RUNOUT8_STATE, + #else + LOW, + #endif + }; + return e < 8 ? _states[e] : LOW; + } }; template @@ -127,15 +217,21 @@ class TFilamentMonitor : public FilamentMonitorBase { #endif #if HAS_FILAMENT_RUNOUT_DISTANCE - static float& runout_distance() { return response.runout_distance_mm; } - static void set_runout_distance(const float mm) { response.runout_distance_mm = mm; } + // Per-sensor runout distance accessors + static float& runout_distance(const uint8_t e=0) { return response.runout_distance_mm[e]; } + static void set_runout_distance(const float mm, const uint8_t e) { response.runout_distance_mm[e] = mm; } + // Single-arg overload for backward compatibility + static void set_runout_distance(const float mm) { + for (uint8_t i = 0; i < NUM_RUNOUT_SENSORS; ++i) response.runout_distance_mm[i] = mm; + } #endif // Handle a block completion. RunoutResponseDelayed uses this to // add up the length of filament moved while the filament is out. // Called from ISR context! static void block_completed(const block_t * const b) { - if (enabled) { + const uint8_t s = TERN0(MULTI_FILAMENT_SENSOR, b->extruder); + if (is_active(s)) { response.block_completed(b); sensor.block_completed(b); } @@ -143,7 +239,8 @@ class TFilamentMonitor : public FilamentMonitorBase { // Give the response a chance to update its counter. static void run() { - if (!enabled || filament_ran_out || !should_monitor_runout()) return; + const uint8_t s = TERN0(MULTI_FILAMENT_SENSOR, motion.extruder); + if (!is_active(s) || filament_ran_out || !should_monitor_runout()) return; TERN_(HAS_FILAMENT_RUNOUT_DISTANCE, cli()); // Prevent RunoutResponseDelayed::block_completed from accumulating here response.run(); sensor.run(); @@ -225,9 +322,11 @@ class FilamentSensorBase { #undef _OR_RUNOUT } - // Return a bitmask of runout flag states (1 bits always indicates runout) + // Return a bitmask of runout flag states (1 bits always indicates runout). + // Uses out_state(e) per sensor so that M591-configured mode is respected, + // while RM_NONE falls back to the compile-time FIL_RUNOUT#_STATE constant. static uint8_t poll_runout_states() { - #define _INVERT_BIT(N) | (FIL_RUNOUT##N##_STATE ? 0 : _BV(N - 1)) + #define _INVERT_BIT(N) | (runout.out_state((N) - 1) ? 0 : _BV((N) - 1)) return poll_runout_pins() ^ uint8_t(0 REPEAT_1(NUM_RUNOUT_SENSORS, _INVERT_BIT)); #undef _INVERT_BIT } @@ -339,8 +438,8 @@ class FilamentSensorBase { #endif // HAS_FILAMENT_SWITCH /** - * This is a simple endstop switch in the path of the filament. - * It can detect filament runout, but not stripouts or jams. + * FilamentSensor aggregates the encoder and switch sub-sensors. + * This is the concrete sensor type used by TFilamentMonitor<>. */ class FilamentSensor : public FilamentSensorBase { private: @@ -374,7 +473,7 @@ class FilamentSensorBase { } countdown_t; // RunoutResponseDelayed triggers a runout event only if the length - // of filament specified by FILAMENT_RUNOUT_DISTANCE_MM has been fed + // of filament specified by runout_distance_mm[e] has been fed // during a runout condition. class RunoutResponseDelayed { private: @@ -384,7 +483,9 @@ class FilamentSensorBase { #endif public: - static float runout_distance_mm; + // Per-sensor runout distance. Initialized from FILAMENT_RUNOUT_DISTANCE_MM; + // individual sensors may be overridden via M591. + static float runout_distance_mm[NUM_RUNOUT_SENSORS]; #if ENABLED(FILAMENT_SWITCH_AND_MOTION) static float motion_distance_mm; @@ -445,11 +546,11 @@ class FilamentSensorBase { } static void filament_present(const uint8_t extruder) { - if (mm_countdown.runout[extruder] < runout_distance_mm || did_pause_print) { + if (mm_countdown.runout[extruder] < runout_distance_mm[extruder] || did_pause_print) { // Reset runout only if it is smaller than runout_distance or printing is paused. // On Bowden systems retract may be larger than runout_distance_mm, so if retract // was added leave it in place, or the following unretract will cause runout event. - mm_countdown.runout[extruder] = runout_distance_mm; + mm_countdown.runout[extruder] = runout_distance_mm[extruder]; mm_countdown.runout_reset.clear(extruder); } else { diff --git a/Marlin/src/gcode/feature/runout/M412.cpp b/Marlin/src/gcode/feature/runout/M412.cpp index c51785401182..73c3d05f1822 100644 --- a/Marlin/src/gcode/feature/runout/M412.cpp +++ b/Marlin/src/gcode/feature/runout/M412.cpp @@ -1,6 +1,6 @@ /** * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * Copyright (c) 2026 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm @@ -20,6 +20,22 @@ * */ +/** + * M412 is retained as a backward-compatibility alias for M591. + * New code should use M591 directly. + * + * M412 S => M591 S (enable/disable for motion.extruder) + * M412 D => M591 D (runout distance for motion.extruder) + * M412 R => M591 R (reset sensor) + * M412 H => M591 H (host handling) + * M412 (no args) => M591 (report active extruder state) + * + * NOTE: M412 L (motion distance for FILAMENT_SWITCH_AND_MOTION) is + * still handled here because M591 uses L as an alias for D, not motion + * distance. That parameter remains M412-only until a separate M591 param + * is decided upon. + */ + #include "../../../inc/MarlinConfig.h" #if HAS_FILAMENT_SENSOR @@ -28,13 +44,15 @@ #include "../../../feature/runout.h" /** - * M412: Enable / Disable filament runout detection + * M412: Enable / Disable filament runout detection (backward-compat shim for M591) * * Parameters * R : Reset the runout sensor - * S : Reset and enable/disable the runout sensor + * S : Enable/disable runout detection for motion.extruder * H : Enable/disable host handling of filament runout + * (Requires HOST_ACTION_COMMANDS) * D : Extra distance to continue after runout is triggered + * (Requires HAS_FILAMENT_RUNOUT_DISTANCE) * * With FILAMENT_SWITCH_AND_MOTION: * L : Missing motion length to consider a jam @@ -42,51 +60,41 @@ void GcodeSuite::M412() { if (parser.seen("RS" TERN_(HAS_FILAMENT_RUNOUT_DISTANCE, "D") - TERN_(HOST_ACTION_COMMANDS, "H") + TERN_(FILAMENT_SWITCH_AND_MOTION, "L") + TERN_(HOST_ACTION_COMMANDS, "H") )) { #if ENABLED(HOST_ACTION_COMMANDS) if (parser.seen('H')) runout.host_handling = parser.value_bool(); #endif + const bool seenR = parser.seen_test('R'), seenS = parser.seen('S'); if (seenR || seenS) runout.reset(); - if (seenS) runout.enabled = parser.value_bool(); + if (seenS) runout.monitoring = parser.value_bool(); + #if HAS_FILAMENT_RUNOUT_DISTANCE - if (parser.seenval('D')) runout.set_runout_distance(parser.value_linear_units()); + if (parser.seenval('D')) + runout.set_runout_distance(parser.value_linear_units(), motion.extruder); #endif #if ENABLED(FILAMENT_SWITCH_AND_MOTION) if (parser.seenval('L')) runout.set_motion_distance(parser.value_linear_units()); #endif } - else { - SERIAL_ECHO_START(); - SERIAL_ECHOPGM("Filament runout ", ON_OFF(runout.enabled)); - #if HAS_FILAMENT_RUNOUT_DISTANCE - SERIAL_ECHOPGM(" ; Distance ", runout.runout_distance(), "mm"); - #endif - #if ENABLED(FILAMENT_SWITCH_AND_MOTION) - SERIAL_ECHOPGM(" ; Motion distance ", runout.motion_distance(), "mm"); - #endif - #if ENABLED(HOST_ACTION_COMMANDS) - SERIAL_ECHOPGM(" ; Host handling ", ON_OFF(runout.host_handling)); - #endif - SERIAL_EOL(); - } + else + M591_report(false); } +/** + * M412_report: Emit M591 lines (one per sensor) for M503 replay. + * Delegates entirely to M591_report so the saved config is always + * in M591 form and can be replayed whether or not M412 remains. + */ void GcodeSuite::M412_report(const bool forReplay/*=true*/) { TERN_(MARLIN_SMALL_BUILD, return); - - report_heading_etc(forReplay, F(STR_FILAMENT_RUNOUT_SENSOR)); - SERIAL_ECHOLNPGM( - " M412 S", runout.enabled - #if HAS_FILAMENT_RUNOUT_DISTANCE - , " D", LINEAR_UNIT(runout.runout_distance()) - #endif - #if ENABLED(FILAMENT_SWITCH_AND_MOTION) - , " L", LINEAR_UNIT(runout.motion_distance()) - #endif - , " ; Sensor ", ON_OFF(runout.enabled) - ); + M591_report(forReplay); + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + report_heading_etc(forReplay, F("Filament Motion Distance")); + SERIAL_ECHOLNPGM(" M412 L", LINEAR_UNIT(runout.motion_distance())); + #endif } #endif // HAS_FILAMENT_SENSOR diff --git a/Marlin/src/gcode/feature/runout/M591.cpp b/Marlin/src/gcode/feature/runout/M591.cpp new file mode 100644 index 000000000000..0545cb50e504 --- /dev/null +++ b/Marlin/src/gcode/feature/runout/M591.cpp @@ -0,0 +1,143 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2026 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 . + * + */ + +#include "../../../inc/MarlinConfig.h" + +#if HAS_FILAMENT_SENSOR + +#include "../../gcode.h" +#include "../../../feature/runout.h" + +/** + * M591: Configure filament runout detection + * + * Parameters + * R : Reset the runout sensor + * S : Enable/disable runout detection for the target extruder + * D : Extra distance (mm) to continue after runout is triggered + * L : Alias for D + * P : Set sensor mode for the target extruder: + * 0 = NONE (disabled) + * 1 = Switch NO (HIGH = filament present) + * 2 = Switch NC (LOW = filament present) + * 7 = Motion sensor + * H : Enable/disable host-action runout handling + * (Requires HOST_ACTION_COMMANDS) + * E : Target extruder (default: motion.extruder) + * (Requires MULTI_FILAMENT_SENSOR) + * + * With no arguments: report current state for the active extruder. + */ +void GcodeSuite::M591() { + + const uint8_t tool = TERN0(MULTI_FILAMENT_SENSOR, parser.ushortval('E', motion.extruder)); + + if (tool >= NUM_RUNOUT_SENSORS) { + SERIAL_ECHO_MSG("?E index out of range (0-", NUM_RUNOUT_SENSORS - 1, ")"); + return; + } + + if (parser.seen("RSLDP" + TERN_(HOST_ACTION_COMMANDS, "H") + TERN_(MULTI_FILAMENT_SENSOR, "E") + )) { + + #if ENABLED(HOST_ACTION_COMMANDS) + if (parser.seen('H')) runout.host_handling = parser.value_bool(); + #endif + + const bool seenR = parser.seen_test('R'), seenS = parser.seen('S'); + if (seenR || seenS) runout.reset(); + if (seenS) { + #if ENABLED(MULTI_FILAMENT_SENSOR) + if (parser.seen('E')) + runout.set_enabled(tool, parser.value_bool()); // per-sensor: M591 E S + else + runout.monitoring = parser.value_bool(); // master: M591 S + #else + runout.monitoring = parser.value_bool(); // single-sensor: S targets master + #endif + } + + #if HAS_FILAMENT_RUNOUT_DISTANCE + if (parser.seenval('D') || parser.seenval('L')) + runout.set_runout_distance(parser.value_linear_units(), tool); + #endif + + if (parser.seenval('P')) { + const RunoutMode tmp_mode = (RunoutMode)parser.value_int(); + switch (tmp_mode) { + case RM_NONE ... RM_OUT_ON_HIGH: + runout.mode[tool] = tmp_mode; + runout.setup(); + break; + #if HAS_FILAMENT_MOTION + case RM_MOTION_SENSOR: + runout.mode[tool] = tmp_mode; + runout.setup(); + break; + #endif + default: break; + } + } + } + else { + SERIAL_ECHO_START(); + SERIAL_ECHOPGM("Runout sensor" + #if ENABLED(MULTI_FILAMENT_SENSOR) + " E", tool + #endif + ); + SERIAL_ECHOPGM(": ", ON_OFF(runout.enabled[tool])); + if (!runout.monitoring) SERIAL_ECHOPGM(" (monitoring OFF)"); + #if HAS_FILAMENT_RUNOUT_DISTANCE + SERIAL_ECHOPGM(" ; D", runout.runout_distance(tool), "mm"); + #endif + SERIAL_ECHOPGM(" ; P", (uint8_t)runout.mode[tool]); + #if ENABLED(HOST_ACTION_COMMANDS) + SERIAL_ECHOPGM(" ; Host ", ON_OFF(runout.host_handling)); + #endif + SERIAL_EOL(); + } +} + +void GcodeSuite::M591_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F(STR_FILAMENT_RUNOUT_SENSOR)); + // Master monitoring flag + SERIAL_ECHOLNPGM(" M591 S", runout.monitoring ? 1 : 0); + // Per-sensor settings + for (uint8_t e = 0; e < NUM_RUNOUT_SENSORS; ++e) { + SERIAL_ECHOPGM(" M591"); + #if ENABLED(MULTI_FILAMENT_SENSOR) + SERIAL_ECHOPGM(" E", e); + #endif + SERIAL_ECHOPGM(" S", runout.enabled[e] ? 1 : 0); + #if HAS_FILAMENT_RUNOUT_DISTANCE + SERIAL_ECHOPGM(" D", LINEAR_UNIT(runout.runout_distance(e))); + #endif + SERIAL_ECHOLNPGM(" P", (uint8_t)runout.mode[e]); + } +} + +#endif // HAS_FILAMENT_SENSOR diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp index 17c6b272a733..512de117a9f4 100644 --- a/Marlin/src/gcode/gcode.cpp +++ b/Marlin/src/gcode/gcode.cpp @@ -880,7 +880,8 @@ void GcodeSuite::process_parsed_command(bool no_ok/*=false*/) { #endif #if HAS_FILAMENT_SENSOR - case 412: M412(); break; // M412: Enable/Disable filament runout detection + case 412: M412(); break; // M412: Backward-compat alias for M591 + case 591: M591(); break; // M591: Configure filament runout detection #endif #if HAS_MULTI_LANGUAGE diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h index c7055d413702..ba1e363a6172 100644 --- a/Marlin/src/gcode/gcode.h +++ b/Marlin/src/gcode/gcode.h @@ -241,6 +241,7 @@ * M407 - Display measured filament diameter in millimeters. (Requires FILAMENT_WIDTH_SENSOR) * M410 - Quickstop. Abort all planned moves. * M412 - Enable/Disable Filament Runout Detection. (Requires FILAMENT_RUNOUT_SENSOR) + * M591 - Configure Filament Runout Detection per extruder. (Requires FILAMENT_RUNOUT_SENSOR) * M413 - Enable/Disable Power-Loss Recovery. (Requires POWER_LOSS_RECOVERY) * M414 - Set language by index. (Requires LCD_LANGUAGE_2...) * M420 - Enable/Disable Leveling (with current values) S1=enable S0=disable (Requires MESH_BED_LEVELING or ABL) @@ -1085,6 +1086,8 @@ class GcodeSuite { #if HAS_FILAMENT_SENSOR static void M412(); static void M412_report(const bool forReplay=true); + static void M591(); + static void M591_report(const bool forReplay=true); #endif #if HAS_MULTI_LANGUAGE diff --git a/Marlin/src/inc/Conditionals-3-etc.h b/Marlin/src/inc/Conditionals-3-etc.h index 8d028e54a2c2..6cc4b07d1edd 100644 --- a/Marlin/src/inc/Conditionals-3-etc.h +++ b/Marlin/src/inc/Conditionals-3-etc.h @@ -127,13 +127,50 @@ #endif /** - * Fill in undefined Filament Sensor options + * Fill in undefined Filament Sensor options. + * Provide backward-compatibility shims for the old FIL_RUNOUT_STATE / + * FIL_RUNOUT_ENABLED_DEFAULT / FILAMENT_MOTION_SENSOR config syntax, + * converting them to the new FIL_RUNOUT_MODE / FIL_RUNOUT_ENABLED array form. */ #if ENABLED(FILAMENT_RUNOUT_SENSOR) + // Backward-compat: derive FIL_RUNOUT_MODE from legacy defines if not yet defined as array + #ifndef FIL_RUNOUT_MODE + #if ENABLED(FILAMENT_MOTION_SENSOR) + #define FIL_RUNOUT_MODE ARRAY_N_1(NUM_RUNOUT_SENSORS, 7) // motion sensor + #elif defined(FIL_RUNOUT_STATE) && FIL_RUNOUT_STATE + #define FIL_RUNOUT_MODE ARRAY_N_1(NUM_RUNOUT_SENSORS, 2) // NC switch (HIGH = runout) + #else + #define FIL_RUNOUT_MODE ARRAY_N_1(NUM_RUNOUT_SENSORS, 1) // NO switch (LOW = runout, default) + #endif + #endif + + // Backward-compat: derive FIL_RUNOUT_ENABLED from legacy FIL_RUNOUT_ENABLED_DEFAULT + #ifndef FIL_RUNOUT_ENABLED + #if defined(FIL_RUNOUT_ENABLED_DEFAULT) && !FIL_RUNOUT_ENABLED_DEFAULT + #define FIL_RUNOUT_ENABLED ARRAY_N_1(NUM_RUNOUT_SENSORS, false) + #else + #define FIL_RUNOUT_ENABLED ARRAY_N_1(NUM_RUNOUT_SENSORS, true) + #endif + #endif + + // FILAMENT_RUNOUT_DISTANCE_MM is always present after this point + #ifndef FILAMENT_RUNOUT_DISTANCE_MM + #define FILAMENT_RUNOUT_DISTANCE_MM 0 + #endif + + // Per-sensor STATE defaults (needed by out_state() RM_NONE fallback in runout.h). + // Use an internal alias so SanityCheck's deprecated-FIL_RUNOUT_STATE check doesn't fire. + #ifndef FIL_RUNOUT_STATE + #define FIL_RUNOUT_STATE LOW + #endif #if NUM_RUNOUT_SENSORS >= 1 #ifndef FIL_RUNOUT1_STATE #define FIL_RUNOUT1_STATE FIL_RUNOUT_STATE #endif + // Provide bare FIL_RUNOUT_STATE for FILAMENT_IS_OUT() macro (no-arg form) + #ifndef FIL_RUNOUT_STATE + #define FIL_RUNOUT_STATE FIL_RUNOUT1_STATE + #endif #ifndef FIL_RUNOUT1_PULLUP #define FIL_RUNOUT1_PULLUP FIL_RUNOUT_PULLUP #endif diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index a4be0a3df269..2cdf9799cfb2 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -534,7 +534,7 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L #elif ALL(FIL_RUNOUT8_PULLUP, FIL_RUNOUT8_PULLDOWN) #error "You can't enable FIL_RUNOUT8_PULLUP and FIL_RUNOUT8_PULLDOWN at the same time." #elif FILAMENT_RUNOUT_DISTANCE_MM < 0 - #error "FILAMENT_RUNOUT_DISTANCE_MM must be greater than or equal to zero." + #error "FILAMENT_RUNOUT_DISTANCE_MM must be >= 0." #elif DISABLED(ADVANCED_PAUSE_FEATURE) && defined(FILAMENT_RUNOUT_SCRIPT) static_assert(nullptr == strstr(FILAMENT_RUNOUT_SCRIPT, "M600"), "FILAMENT_RUNOUT_SCRIPT cannot make use of M600 unless ADVANCED_PAUSE_FEATURE is enabled"); #elif DGUS_LCD_UI_MKS diff --git a/Marlin/src/lcd/dwin/jyersui/dwin.cpp b/Marlin/src/lcd/dwin/jyersui/dwin.cpp index 9768ed7837dc..3c10c4b3b17a 100644 --- a/Marlin/src/lcd/dwin/jyersui/dwin.cpp +++ b/Marlin/src/lcd/dwin/jyersui/dwin.cpp @@ -2952,11 +2952,11 @@ void JyersDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool dra case ADVANCED_FILSENSORENABLED: if (draw) { drawMenuItem(row, ICON_Extruder, GET_TEXT_F(MSG_RUNOUT_SENSOR)); - drawCheckbox(row, runout.enabled); + drawCheckbox(row, runout.monitoring); } else { - FLIP(runout.enabled); - drawCheckbox(row, runout.enabled); + FLIP(runout.monitoring) + drawCheckbox(row, runout.monitoring); } break; @@ -3949,11 +3949,11 @@ void JyersDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool dra case TUNE_FILSENSORENABLED: if (draw) { drawMenuItem(row, ICON_Extruder, GET_TEXT_F(MSG_RUNOUT_SENSOR)); - drawCheckbox(row, runout.enabled); + drawCheckbox(row, runout.monitoring); } else { - FLIP(runout.enabled); - drawCheckbox(row, runout.enabled); + FLIP(runout.monitoring); + drawCheckbox(row, runout.monitoring); } break; #endif diff --git a/Marlin/src/lcd/dwin/proui/dwin.cpp b/Marlin/src/lcd/dwin/proui/dwin.cpp index 47fca72a16eb..9444147ab543 100644 --- a/Marlin/src/lcd/dwin/proui/dwin.cpp +++ b/Marlin/src/lcd/dwin/proui/dwin.cpp @@ -2302,7 +2302,8 @@ void setMoveZ() { hmiValue.axis = Z_AXIS; setPFloatOnClick(Z_MIN_POS, Z_MAX_POS, #if HAS_FILAMENT_SENSOR void setRunoutEnable() { runout.reset(); - toggleCheckboxLine(runout.enabled); + FLIP(runout.monitoring); + showCheckboxLine(runout.monitoring); } #if HAS_FILAMENT_RUNOUT_DISTANCE void applyRunoutDistance() { runout.set_runout_distance(menuData.value / MINUNITMULT); } @@ -3604,7 +3605,7 @@ void drawTuneMenu() { MENU_ITEM(ICON_FilMan, MSG_FILAMENTCHANGE, onDrawMenuItem, changeFilament); #endif #if HAS_FILAMENT_SENSOR - EDIT_ITEM(ICON_Runout, MSG_RUNOUT_SENSOR, onDrawChkbMenu, setRunoutEnable, &runout.enabled); + EDIT_ITEM(ICON_Runout, MSG_RUNOUT_SENSOR, onDrawChkbMenu, setRunoutEnable, &runout.monitoring); #endif #if ENABLED(PROUI_ITEM_PLR) EDIT_ITEM(ICON_Pwrlossr, MSG_OUTAGE_RECOVERY, onDrawChkbMenu, setPwrLossr, &recovery.enabled); @@ -3813,7 +3814,7 @@ void drawFilSetMenu() { if (SET_MENU(filSetMenu, MSG_FILAMENT_SET, items)) { BACK_ITEM(drawAdvancedSettingsMenu); #if HAS_FILAMENT_SENSOR - EDIT_ITEM(ICON_Runout, MSG_RUNOUT_SENSOR, onDrawChkbMenu, setRunoutEnable, &runout.enabled); + EDIT_ITEM(ICON_Runout, MSG_RUNOUT_SENSOR, onDrawChkbMenu, setRunoutEnable, &runout.monitoring); #endif #if HAS_FILAMENT_RUNOUT_DISTANCE EDIT_ITEM(ICON_Runout, MSG_RUNOUT_DISTANCE_MM, onDrawPFloatMenu, setRunoutDistance, &runout.runout_distance()); diff --git a/Marlin/src/lcd/extui/ui_api.cpp b/Marlin/src/lcd/extui/ui_api.cpp index 31bbe800bcf8..5d146c3f0afd 100644 --- a/Marlin/src/lcd/extui/ui_api.cpp +++ b/Marlin/src/lcd/extui/ui_api.cpp @@ -609,8 +609,8 @@ namespace ExtUI { } #if HAS_FILAMENT_SENSOR - bool getFilamentRunoutEnabled() { return runout.enabled; } - void setFilamentRunoutEnabled(const bool value) { runout.enabled = value; } + bool getFilamentRunoutEnabled() { return runout.monitoring; } + void setFilamentRunoutEnabled(const bool value) { runout.monitoring = value; } bool getFilamentRunoutState() { return runout.filament_ran_out; } void setFilamentRunoutState(const bool value) { runout.filament_ran_out = value; } diff --git a/Marlin/src/lcd/menu/menu_configuration.cpp b/Marlin/src/lcd/menu/menu_configuration.cpp index 13a8893cc2cd..f165e7ae03b8 100644 --- a/Marlin/src/lcd/menu/menu_configuration.cpp +++ b/Marlin/src/lcd/menu/menu_configuration.cpp @@ -662,7 +662,7 @@ void menu_configuration() { #endif #if HAS_FILAMENT_SENSOR - EDIT_ITEM(bool, MSG_RUNOUT_SENSOR, &runout.enabled, runout.reset); + EDIT_ITEM(bool, MSG_RUNOUT_SENSOR, &runout.monitoring, runout.reset); #endif #if HAS_FANCHECK diff --git a/Marlin/src/lcd/menu/menu_filament.cpp b/Marlin/src/lcd/menu/menu_filament.cpp index 495b545e210b..3b902e964bb5 100644 --- a/Marlin/src/lcd/menu/menu_filament.cpp +++ b/Marlin/src/lcd/menu/menu_filament.cpp @@ -257,7 +257,7 @@ void menu_pause_option() { #if HAS_FILAMENT_SENSOR if (still_out) - EDIT_ITEM(bool, MSG_RUNOUT_SENSOR, &runout.enabled, runout.reset); + EDIT_ITEM(bool, MSG_RUNOUT_SENSOR, &runout.monitoring, runout.reset); #endif if (!still_out) diff --git a/Marlin/src/module/settings.cpp b/Marlin/src/module/settings.cpp index 261f1aa937f9..b63cb08c84b8 100644 --- a/Marlin/src/module/settings.cpp +++ b/Marlin/src/module/settings.cpp @@ -36,7 +36,7 @@ */ // Change EEPROM version if the structure changes -#define EEPROM_VERSION "V90" +#define EEPROM_VERSION "V91" #define EEPROM_OFFSET 100 // Check the integrity of data offsets. @@ -119,9 +119,6 @@ #if HAS_FILAMENT_SENSOR #include "../feature/runout.h" - #ifndef FIL_RUNOUT_ENABLED_DEFAULT - #define FIL_RUNOUT_ENABLED_DEFAULT true - #endif #endif #if ENABLED(ADVANCE_K_EXTRA) @@ -257,9 +254,15 @@ typedef struct SettingsDataStruct { // // FILAMENT_RUNOUT_SENSOR // - bool runout_sensor_enabled; // M412 S - float runout_distance_mm; // M412 D - float motion_distance_mm; // M412 L + #if HAS_FILAMENT_SENSOR + bool runout_enabled[NUM_RUNOUT_SENSORS]; // M591 En S + bool runout_monitoring; // master runout on/off + float runout_distance_mm[NUM_RUNOUT_SENSORS]; // M591 En L + uint8_t runout_mode[NUM_RUNOUT_SENSORS]; // M591 En P + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + float runout_motion_distance_mm; // M412 L + #endif + #endif // // ENABLE_LEVELING_FADE_HEIGHT @@ -977,29 +980,34 @@ void MarlinSettings::postprocess() { // // Filament Runout Sensor // + #if HAS_FILAMENT_SENSOR { - #if HAS_FILAMENT_SENSOR - const bool &runout_sensor_enabled = runout.enabled; - #else - constexpr int8_t runout_sensor_enabled = -1; + bool runout_monitoring; + bool runout_enabled[NUM_RUNOUT_SENSORS]; + #if HAS_FILAMENT_RUNOUT_DISTANCE + float runout_distance_mm[NUM_RUNOUT_SENSORS]; #endif - _FIELD_TEST(runout_sensor_enabled); - EEPROM_WRITE(runout_sensor_enabled); - + uint8_t runout_mode[NUM_RUNOUT_SENSORS]; + for (uint8_t e = 0; e < NUM_RUNOUT_SENSORS; ++e) { + runout_enabled[e] = runout.enabled[e]; + #if HAS_FILAMENT_RUNOUT_DISTANCE + runout_distance_mm[e] = runout.runout_distance(e); + #endif + runout_mode[e] = (uint8_t)runout.mode[e]; + } + runout_monitoring = runout.monitoring; + _FIELD_TEST(runout_enabled); + EEPROM_WRITE(runout_enabled); + EEPROM_WRITE(runout_monitoring); #if HAS_FILAMENT_RUNOUT_DISTANCE - const float &runout_distance_mm = runout.runout_distance(); - #else - constexpr float runout_distance_mm = 0; + EEPROM_WRITE(runout_distance_mm); #endif - EEPROM_WRITE(runout_distance_mm); - + EEPROM_WRITE(runout_mode); #if ENABLED(FILAMENT_SWITCH_AND_MOTION) - const float &motion_distance_mm = runout.motion_distance(); - #else - constexpr float motion_distance_mm = 0; + EEPROM_WRITE(runout.motion_distance()); #endif - EEPROM_WRITE(motion_distance_mm); } + #endif // // Global Leveling @@ -2048,28 +2056,38 @@ void MarlinSettings::postprocess() { // // Filament Runout Sensor // + #if HAS_FILAMENT_SENSOR { - int8_t runout_sensor_enabled; - _FIELD_TEST(runout_sensor_enabled); - EEPROM_READ(runout_sensor_enabled); - #if HAS_FILAMENT_SENSOR - if (!validating) runout.enabled = runout_sensor_enabled < 0 ? FIL_RUNOUT_ENABLED_DEFAULT : runout_sensor_enabled; - #endif - - TERN_(HAS_FILAMENT_SENSOR, if (runout.enabled) runout.reset()); - - float runout_distance_mm; + bool runout_enabled[NUM_RUNOUT_SENSORS]; + bool runout_monitoring; + float runout_distance_mm[NUM_RUNOUT_SENSORS]; + uint8_t runout_mode[NUM_RUNOUT_SENSORS]; + + _FIELD_TEST(runout_enabled); + EEPROM_READ(runout_enabled); + EEPROM_READ(runout_monitoring); EEPROM_READ(runout_distance_mm); - #if HAS_FILAMENT_RUNOUT_DISTANCE - if (!validating) runout.set_runout_distance(runout_distance_mm); - #endif + EEPROM_READ(runout_mode); - float motion_distance_mm; - EEPROM_READ(motion_distance_mm); #if ENABLED(FILAMENT_SWITCH_AND_MOTION) - if (!validating) runout.set_motion_distance(motion_distance_mm); + float tmp_motion_distance_mm = 0; + EEPROM_READ(tmp_motion_distance_mm); + if (!validating) runout.set_motion_distance(tmp_motion_distance_mm); #endif + + if (!validating) { + for (uint8_t e = 0; e < NUM_RUNOUT_SENSORS; ++e) { + runout.set_enabled(e, runout_enabled[e]); + #if HAS_FILAMENT_RUNOUT_DISTANCE + runout.set_runout_distance(runout_distance_mm[e], e); + #endif + runout.mode[e] = (RunoutMode)runout_mode[e]; + } + runout.monitoring = runout_monitoring; + runout.reset(); + } } + #endif // // Global Leveling @@ -3387,10 +3405,21 @@ void MarlinSettings::reset() { // #if HAS_FILAMENT_SENSOR - runout.enabled = FIL_RUNOUT_ENABLED_DEFAULT; + { + constexpr bool runout_enabled_defaults[] = FIL_RUNOUT_ENABLED; + constexpr uint8_t runout_mode_defaults[] = FIL_RUNOUT_MODE; + static_assert(COUNT(runout_enabled_defaults) == NUM_RUNOUT_SENSORS, "FIL_RUNOUT_ENABLED must have NUM_RUNOUT_SENSORS entries."); + static_assert(COUNT(runout_mode_defaults) == NUM_RUNOUT_SENSORS, "FIL_RUNOUT_MODE must have NUM_RUNOUT_SENSORS entries."); + COPY(runout.enabled, runout_enabled_defaults); + runout.monitoring = true; + for (uint8_t e = 0; e < NUM_RUNOUT_SENSORS; ++e) { + runout.mode[e] = (RunoutMode)runout_mode_defaults[e]; + #if HAS_FILAMENT_RUNOUT_DISTANCE + runout.set_runout_distance(FILAMENT_RUNOUT_DISTANCE_MM, e); + #endif + } runout.reset(); - TERN_(HAS_FILAMENT_RUNOUT_DISTANCE, runout.set_runout_distance(FILAMENT_RUNOUT_DISTANCE_MM)); - TERN_(FILAMENT_SWITCH_AND_MOTION, runout.set_motion_distance(FILAMENT_MOTION_DISTANCE_MM)); + } #endif // @@ -4161,7 +4190,10 @@ void MarlinSettings::reset() { // // Filament Runout Sensor // - TERN_(HAS_FILAMENT_SENSOR, gcode.M412_report(forReplay)); + #if HAS_FILAMENT_SENSOR + gcode.M412_report(forReplay); + gcode.M591_report(forReplay); + #endif #if HAS_ETHERNET CONFIG_ECHO_HEADING("Ethernet");