Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
24 changes: 15 additions & 9 deletions Marlin/src/feature/runout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,29 @@

#include "runout.h"

FilamentRunoutSensor runout;
FilamentMonitor runout;

bool FilamentSensorBase::enabled = true,
FilamentSensorBase::filament_ran_out; // = false
bool FilamentMonitorBase::enabled = true,
FilamentMonitorBase::filament_ran_out; // = false

void FilamentSensorTypeBase::filament_present(const uint8_t extruder) {
runout.filament_present(extruder);
/**
* Called by FilamentSensorSwitch::run when filament is detected.
* Called by FilamentSensorEncoder::block_completed when motion is detected.
*/
void FilamentSensorBase::filament_present(const uint8_t extruder) {
runout.filament_present(extruder); // calls response.filament_present(extruder)
}

uint8_t FilamentSensorTypeEncoder::motion_detected,
FilamentSensorTypeEncoder::old_state; // = 0
#if ENABLED(FILAMENT_MOTION_SENSOR)
uint8_t FilamentSensorEncoder::motion_detected,
FilamentSensorEncoder::old_state; // = 0
#endif

#if FILAMENT_RUNOUT_DISTANCE_MM > 0
float RunoutResponseDelayed::runout_distance_mm = FILAMENT_RUNOUT_DISTANCE_MM;
int32_t RunoutResponseDelayed::steps_since_detection[EXTRUDERS];
volatile float RunoutResponseDelayed::runout_mm_countdown[EXTRUDERS];
#else
uint8_t RunoutResponseDebounced::runout_count; // = 0
int8_t RunoutResponseDebounced::runout_count; // = 0
#endif

#endif // FILAMENT_RUNOUT_SENSOR
203 changes: 116 additions & 87 deletions Marlin/src/feature/runout.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@

//#define FILAMENT_RUNOUT_SENSOR_DEBUG

class FilamentSensorBase {
class FilamentMonitorBase {
public:
static bool enabled;

Expand All @@ -47,38 +47,52 @@ class FilamentSensorBase {
};

template<class RESPONSE_T, class SENSOR_T>
class TFilamentSensor : public FilamentSensorBase {
class TFilamentMonitor : public FilamentMonitorBase {
private:
typedef RESPONSE_T response_t;
typedef SENSOR_T sensor_t;
static response_t response;
static sensor_t sensor;

public:
static void setup() {
static inline void setup() {
sensor.setup();
reset();
}

static inline void reset() {
filament_ran_out = false;
response.reset();
}

// The sensor calls this method when filament is present
// Call this method when filament is present,
// so the response can reset its counter.
static inline void filament_present(const uint8_t extruder) {
response.filament_present(extruder);
}

static inline void block_complete(const block_t *b) {
response.block_complete(b);
sensor.block_complete(b);
// Handle a block completion. RunoutResponseDelayed uses this to
// add up the length of filament moved while the filament is out.
static inline void block_completed(const block_t* const b) {
if (enabled) {
response.block_completed(b);
sensor.block_completed(b);
}
}

static void run() {
// Give the response a chance to update its counter.
static inline void run() {
if (enabled && !filament_ran_out && (IS_SD_PRINTING() || print_job_timer.isRunning())) {
#if FILAMENT_RUNOUT_DISTANCE_MM > 0
cli(); // Prevent RunoutResponseDelayed::block_completed from accumulating here
#endif
response.run();
sensor.run();
if (response.has_runout()) {
const bool ran_out = response.has_run_out();
#if FILAMENT_RUNOUT_DISTANCE_MM > 0
sei();
#endif
if (ran_out) {
filament_ran_out = true;
#if ENABLED(EXTENSIBLE_UI)
UI::onFilamentRunout();
Expand All @@ -92,7 +106,7 @@ class TFilamentSensor : public FilamentSensorBase {

/*************************** FILAMENT PRESENCE SENSORS ***************************/

class FilamentSensorTypeBase {
class FilamentSensorBase {
protected:
static void filament_present(const uint8_t extruder);

Expand Down Expand Up @@ -153,13 +167,52 @@ class FilamentSensorTypeBase {
}
};

#if ENABLED(FILAMENT_MOTION_SENSOR)

/**
* This sensor is a simple endstop
* switch in the path of the filament. It detects
* filament runout, but not stripouts or jams.
* This sensor uses a magnetic encoder disc and a Hall effect
* sensor (or a slotted disc and optical sensor). The state
* will toggle between 0 and 1 on filament movement. It can detect
* filament runout and stripouts or jams.
*/
class FilamentSensorEncoder : public FilamentSensorBase {
private:
static uint8_t motion_detected;

static inline void poll_motion_sensor() {
static uint8_t old_state;
const uint8_t new_state = poll_runout_pins(),
change = old_state ^ new_state;
old_state = new_state;

#ifdef FILAMENT_RUNOUT_SENSOR_DEBUG
if (change) SERIAL_PROTOCOLLNPAIR("Motion detected: ", int(change));
#endif

motion_detected |= change;
}

public:
static inline void block_completed(const block_t* const b) {
// If the sensor wheel has moved since the last call to
// this method reset the runout counter for the extruder.
if (TEST(motion_detected, b->extruder))
filament_present(b->extruder);

// Clear motion triggers for next block
motion_detected = 0;
}

static inline void run() { poll_motion_sensor(); }
};

#else

class FilamentSensorTypeSwitch : public FilamentSensorTypeBase {
/**
* This is a simple endstop switch in the path of the filament.
* It can detect filament runout, but not stripouts or jams.
*/
class FilamentSensorSwitch : public FilamentSensorBase {
private:
static bool poll_runout_pin(const uint8_t extruder) {
const uint8_t runout_bits = poll_runout_pins();
Expand All @@ -180,128 +233,104 @@ class FilamentSensorTypeSwitch : public FilamentSensorTypeBase {
}

public:
static inline void block_complete(const block_t *b) {}
static inline void block_completed(const block_t* const b) {}

static inline void run() {
if (!poll_runout_pin(active_extruder))
filament_present(active_extruder);
}
};

// This filament sensor uses a magnetic encoder disc and a hall
// effect sensor (or a slitted disc and an optical sensor). The state
// will toggle between 0 and 1 with filament movement. It can detect
// filament runout and stripouts or jams.

class FilamentSensorTypeEncoder : public FilamentSensorTypeBase {
private:
static uint8_t motion_detected, old_state;

static void poll_motion_sensor() {
const uint8_t new_state = poll_runout_pins(),
change = old_state ^ new_state;
old_state = new_state;

const bool out = poll_runout_pin(active_extruder);
if (!out) filament_present(active_extruder);
#ifdef FILAMENT_RUNOUT_SENSOR_DEBUG
if (change) SERIAL_PROTOCOLLNPAIR("motion detected: ", change);
static bool was_out = false;
if (out != was_out) {
was_out = out;
SERIAL_PROTOCOL("Filament ");
serialprintPGM(out ? PSTR("OUT\n") : PSTR("IN\n"));
}
#endif

motion_detected |= change;
}
};

public:
static void block_complete(const block_t *b) {
// If the just-executed block caused the sensor wheel
// to turn, reset the runout counter for that extruder.
if (TEST(motion_detected, b->extruder))
filament_present(b->extruder);

// Clear motion triggers for next block
motion_detected = 0;
}

static inline void run() { poll_motion_sensor(); }
};
#endif // !FILAMENT_MOTION_SENSOR

/********************************* RESPONSE TYPE *********************************/

#if FILAMENT_RUNOUT_DISTANCE_MM > 0

// The RunoutResponseDelayed will trigger an runout event only after
// RUNOUT_DISTANCE_MM of filament have been fed after a runout condition.
// RunoutResponseDelayed triggers a runout event only if the length
// of filament specified by FILAMENT_RUNOUT_DISTANCE_MM has been fed
// during a runout condition.
class RunoutResponseDelayed {
private:
static int32_t steps_since_detection[EXTRUDERS];

static float get_mm_since_runout(const uint8_t extruder) {
return (steps_since_detection[extruder] / planner.settings.axis_steps_per_mm[E_AXIS_N(extruder)]);
}
static volatile float runout_mm_countdown[EXTRUDERS];

public:
static float runout_distance_mm;

static inline bool has_runout() {
return get_mm_since_runout(active_extruder) > runout_distance_mm;
}

static inline void filament_present(const uint8_t extruder) {
steps_since_detection[extruder] = 0;
static void reset() {
LOOP_L_N(i, EXTRUDERS) filament_present(i);
}

static inline void run() {
#ifdef FILAMENT_RUNOUT_SENSOR_DEBUG
static uint16_t r = 0;
if ((r++ % 24000) == 0) {
SERIAL_PROTOCOLPGM("mm since filament detection: ");
LOOP_L_N(i, NUM_RUNOUT_SENSORS) {
if (i > 0) SERIAL_PROTOCOLPGM(", ");
SERIAL_PROTOCOL(get_mm_since_runout(i));
static millis_t t = 0;
const millis_t ms = millis();
if (ELAPSED(ms, t)) {
t = millis() + 1000UL;
LOOP_L_N(i, EXTRUDERS) {
serialprintPGM(i ? PSTR(", ") : PSTR("Remaining mm: "));
SERIAL_PROTOCOL(runout_mm_countdown[i]);
}
SERIAL_EOL();
}
#endif
}

static void reset() {
LOOP_L_N(i, NUM_RUNOUT_SENSORS) steps_since_detection[i] = 0;
static inline bool has_run_out() {
return runout_mm_countdown[active_extruder] < 0;
}

static inline void filament_present(const uint8_t extruder) {
runout_mm_countdown[extruder] = runout_distance_mm;
}

static inline void block_complete(const block_t *b) {
steps_since_detection[b->extruder] += TEST(b->direction_bits, E_AXIS) ? -b->steps[E_AXIS] : b->steps[E_AXIS];
static inline void block_completed(const block_t* const b) {
const uint8_t e = b->extruder;
const int32_t steps = b->steps[E_AXIS];
runout_mm_countdown[e] -= (TEST(b->direction_bits, E_AXIS) ? -steps : steps) * planner.steps_to_mm[E_AXIS_N(e)];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thinkyhead: I had originally used steps to avoid a multiplication in the callback from the ISR, but if you think doing the conversion to mm here is acceptable, it does make the code more readable. Bravo!

@thinkyhead thinkyhead Nov 14, 2018

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does happen far less often than run(), but since it happens in the stepper ISR it's an open question. At least it now has the benefit of using multiplication rather than division.

@ghost ghost Nov 14, 2018

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just countdown steps from the pre-calculated initial value? I.e. you could avoid the multiplication in block_complete if you count down steps instead of mm. The initial amount of steps ( for each extruder ) would be the initial_mm / planner.steps_to_mm[E_AXIS_N( for each extruder )]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thinkyhead already mentioned that run() gets called more often than block_complete(), so I think your idea would be less efficient, not to mention requiring a division instead of a multiplication. I doubt it makes enough of a difference either way, however.

@ghost ghost Nov 14, 2018

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, ok. so disregard please. Wishful thinking on my part. Unless you trade storage: 32 bits per extruder array of precalculated steps values on setup to re-init the countdown. Then no division in run and no multiplication in ISR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. You could precalculate the values as 32-bits per extruder, but storage is very much at a permium on this AVR microcontrollers, so I don't think that would fly :)

}
};

#else // !FILAMENT_RUNOUT_DISTANCE_MM

// The RunoutResponseDebounced will trigger an runout event after
// a runout condition is detected FIL_RUNOUT_THRESHOLD times in a row.
// RunoutResponseDebounced triggers a runout event after a runout
// condition has been detected runout_threshold times in a row.

class RunoutResponseDebounced {
private:
static constexpr uint8_t FIL_RUNOUT_THRESHOLD = 5;
static uint8_t runout_count;
static constexpr int8_t runout_threshold = 5;
static int8_t runout_count;
public:
static inline bool has_runout() { return runout_count > FIL_RUNOUT_THRESHOLD; }
static inline void block_complete(const block_t *b) {}
static inline void filament_present(const uint8_t extruder) { runout_count = 0; UNUSED(extruder); }
static inline void run() { runout_count++; }
static inline void reset() { runout_count = 0; }
static inline void reset() { runout_count = runout_threshold; }
static inline void run() { runout_count--; }
static inline bool has_run_out() { return runout_count < 0; }
static inline void block_completed(const block_t* const b) {}
static inline void filament_present(const uint8_t extruder) { runout_count = runout_threshold; UNUSED(extruder); }
};

#endif // !FILAMENT_RUNOUT_DISTANCE_MM

/********************************* TEMPLATE SPECIALIZATION *********************************/

typedef TFilamentSensor<
typedef TFilamentMonitor<
#if FILAMENT_RUNOUT_DISTANCE_MM > 0
#if ENABLED(FILAMENT_MOTION_SENSOR)
RunoutResponseDelayed, FilamentSensorTypeEncoder
RunoutResponseDelayed, FilamentSensorEncoder
#else
RunoutResponseDelayed, FilamentSensorTypeSwitch
RunoutResponseDelayed, FilamentSensorSwitch
#endif
#else
RunoutResponseDebounced, FilamentSensorTypeSwitch
RunoutResponseDebounced, FilamentSensorSwitch
#endif
> FilamentRunoutSensor;
> FilamentMonitor;

extern FilamentRunoutSensor runout;
extern FilamentMonitor runout;
2 changes: 1 addition & 1 deletion Marlin/src/module/stepper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1492,7 +1492,7 @@ uint32_t Stepper::stepper_block_phase_isr() {
// If current block is finished, reset pointer
if (step_events_completed >= step_event_count) {
#if FILAMENT_RUNOUT_DISTANCE_MM > 0
runout.block_complete(current_block);
runout.block_completed(current_block);
#endif
axis_did_move = 0;
current_block = NULL;
Expand Down