Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions Marlin/src/feature/runout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@

FilamentMonitor runout;

bool FilamentMonitorBase::enabled = true,
FilamentMonitorBase::filament_ran_out; // = false
// enabled[] is initialized by settings.load() / settings defaults.
// Default: all sensors enabled, mode RM_NONE (use compile-time FIL_RUNOUT#_STATE).
bool 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
Expand All @@ -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;
Expand Down
114 changes: 101 additions & 13 deletions Marlin/src/feature/runout.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,94 @@ 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];
static bool filament_ran_out;
static RunoutMode mode[NUM_RUNOUT_SENSORS];
Comment thread
thinkyhead marked this conversation as resolved.

#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 / other: treated like RM_OUT_ON_LOW (motion sensor
* logic lives in FilamentSensorEncoder, not here)
*/
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_NONE:
default: break;
}
Comment thread
thinkyhead marked this conversation as resolved.
// RM_NONE: look up the compile-time per-sensor 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<class RESPONSE_T, class SENSOR_T>
Expand Down Expand Up @@ -127,23 +206,28 @@ 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) {
if (enabled[b->extruder]) {
response.block_completed(b);
sensor.block_completed(b);
}
Comment thread
thinkyhead marked this conversation as resolved.
}

// Give the response a chance to update its counter.
static void run() {
if (!enabled || filament_ran_out || !should_monitor_runout()) return;
if (!enabled[motion.extruder] || filament_ran_out || !should_monitor_runout()) return;
TERN_(HAS_FILAMENT_RUNOUT_DISTANCE, cli()); // Prevent RunoutResponseDelayed::block_completed from accumulating here
response.run();
sensor.run();
Comment thread
thinkyhead marked this conversation as resolved.
Expand Down Expand Up @@ -225,9 +309,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
Comment on lines +325 to 331
}
Expand Down Expand Up @@ -339,8 +425,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:
Expand Down Expand Up @@ -374,7 +460,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:
Expand All @@ -384,7 +470,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;
Expand Down Expand Up @@ -445,11 +533,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 {
Expand Down
68 changes: 36 additions & 32 deletions Marlin/src/gcode/feature/runout/M412.cpp
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -20,6 +20,22 @@
*
*/

/**
* M412 is retained as a backward-compatibility alias for M591.
* New code should use M591 directly.
*
* M412 S<bool> => M591 S<bool> (enable/disable for motion.extruder)
* M412 D<mm> => M591 D<mm> (runout distance for motion.extruder)
* M412 R => M591 R (reset sensor)
* M412 H<bool> => M591 H<bool> (host handling)
* M412 (no args) => M591 (report active extruder state)
*
* NOTE: M412 L<mm> (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
Expand All @@ -28,65 +44,53 @@
#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<bool> : Reset and enable/disable the runout sensor
* S<bool> : Enable/disable runout detection for motion.extruder
* H<bool> : Enable/disable host handling of filament runout
* (Requires HOST_ACTION_COMMANDS)
* D<linear> : Extra distance to continue after runout is triggered
* (Requires HAS_FILAMENT_RUNOUT_DISTANCE)
*
* With FILAMENT_SWITCH_AND_MOTION:
* L<linear> : Missing motion length to consider a jam
*/
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.enabled[motion.extruder] = 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
Comment thread
thinkyhead marked this conversation as resolved.
#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);
Comment thread
thinkyhead marked this conversation as resolved.
}

/**
* 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);
}
Comment thread
thinkyhead marked this conversation as resolved.

#endif // HAS_FILAMENT_SENSOR
Loading
Loading