/** * Marlin 3D Printer Firmware * Copyright (c) 2020 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 . * */ #pragma once /** * feature/runout.h - Runout sensor support */ #include "../sd/cardreader.h" #include "../module/printcounter.h" #include "../module/planner.h" #include "../module/stepper.h" // for block_t #include "../gcode/queue.h" #include "../inc/MarlinConfig.h" #if ENABLED(EXTENSIBLE_UI) #include "../lcd/extui/ui_api.h" #endif #if ENABLED(ADVANCED_PAUSE_FEATURE) #include "pause.h" #endif //#define FILAMENT_RUNOUT_SENSOR_DEBUG #ifndef FILAMENT_RUNOUT_THRESHOLD #define FILAMENT_RUNOUT_THRESHOLD 5 #endif void event_filament_runout(); class FilamentMonitorBase { public: static bool enabled, filament_ran_out; #if ENABLED(HOST_ACTION_COMMANDS) static bool host_handling; #else static constexpr bool host_handling = false; #endif }; template 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 inline void setup() { sensor.setup(); reset(); } static inline void reset() { filament_ran_out = false; response.reset(); } // 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); } #ifdef FILAMENT_RUNOUT_DISTANCE_MM static inline float& runout_distance() { return response.runout_distance_mm; } static inline void set_runout_distance(const float &mm) { response.runout_distance_mm = mm; } #endif // 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); } } // Give the response a chance to update its counter. static inline void run() { if ( enabled && !filament_ran_out && (printingIsActive() || TERN0(ADVANCED_PAUSE_FEATURE, did_pause_print)) ) { #ifdef FILAMENT_RUNOUT_DISTANCE_MM cli(); // Prevent RunoutResponseDelayed::block_completed from accumulating here #endif response.run(); sensor.run(); const bool ran_out = response.has_run_out(); #ifdef FILAMENT_RUNOUT_DISTANCE_MM sei(); #endif if (ran_out) { filament_ran_out = true; event_filament_runout(); planner.synchronize(); } } } }; /*************************** FILAMENT PRESENCE SENSORS ***************************/ class FilamentSensorBase { protected: static void filament_present(const uint8_t extruder); public: static inline void setup() { #if ENABLED(FIL_RUNOUT_PULLUP) #define INIT_RUNOUT_PIN(P) SET_INPUT_PULLUP(P) #elif ENABLED(FIL_RUNOUT_PULLDOWN) #define INIT_RUNOUT_PIN(P) SET_INPUT_PULLDOWN(P) #else #define INIT_RUNOUT_PIN(P) SET_INPUT(P) #endif #define _INIT_RUNOUT(N) INIT_RUNOUT_PIN(FIL_RUNOUT##N##_PIN); REPEAT_S(1, INCREMENT(NUM_RUNOUT_SENSORS), _INIT_RUNOUT) #undef _INIT_RUNOUT } // Return a bitmask of runout pin states static inline uint8_t poll_runout_pins() { #define _OR_RUNOUT(N) | (READ(FIL_RUNOUT##N##_PIN) ? _BV((N) - 1) : 0) return (0 REPEAT_S(1, INCREMENT(NUM_RUNOUT_SENSORS), _OR_RUNOUT)); #undef _OR_RUNOUT } // Return a bitmask of runout flag states (1 bits always indicates runout) static inline uint8_t poll_runout_states() { return poll_runout_pins() ^ uint8_t(TERN0(FIL_RUNOUT_INVERTING, _BV(NUM_RUNOUT_SENSORS) - 1)); } #undef INIT_RUNOUT_PIN }; #if ENABLED(FILAMENT_MOTION_SENSOR) /** * 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_ECHOPGM("Motion detected:"); LOOP_L_N(e, NUM_RUNOUT_SENSORS) if (TEST(change, e)) SERIAL_CHAR(' ', '0' + e); SERIAL_EOL(); } #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 /** * 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 inline bool poll_runout_state(const uint8_t extruder) { const uint8_t runout_states = poll_runout_states(); #if NUM_RUNOUT_SENSORS == 1 UNUSED(extruder); #else if ( !TERN0(DUAL_X_CARRIAGE, dxc_is_duplicating()) && !TERN0(MULTI_NOZZLE_DUPLICATION, extruder_duplication_enabled) ) return TEST(runout_states, extruder); // A specific extruder ran out #endif return !!runout_states; // Any extruder ran out } public: static inline void block_completed(const block_t* const) {} static inline void run() { const bool out = poll_runout_state(active_extruder); if (!out) filament_present(active_extruder); #ifdef FILAMENT_RUNOUT_SENSOR_DEBUG static bool was_out = false; if (out != was_out) { was_out = out; SERIAL_ECHOPGM("Filament "); serialprintPGM(out ? PSTR("OUT\n") : PSTR("IN\n")); } #endif } }; #endif // !FILAMENT_MOTION_SENSOR /********************************* RESPONSE TYPE *********************************/ #ifdef FILAMENT_RUNOUT_DISTANCE_MM // 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 volatile float runout_mm_countdown[EXTRUDERS]; public: static float runout_distance_mm; static inline void reset() { LOOP_L_N(i, EXTRUDERS) filament_present(i); } static inline void run() { #ifdef FILAMENT_RUNOUT_SENSOR_DEBUG 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_ECHO(runout_mm_countdown[i]); } SERIAL_EOL(); } #endif } 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_completed(const block_t* const b) { if (b->steps.x || b->steps.y || b->steps.z || TERN0(ADVANCED_PAUSE_FEATURE, did_pause_print) // Allow pause purge move to re-trigger runout state ) { // Only trigger on extrusion with XYZ movement to allow filament change and retract/recover. const uint8_t e = b->extruder; const int32_t steps = b->steps.e; runout_mm_countdown[e] -= (TEST(b->direction_bits, E_AXIS) ? -steps : steps) * planner.steps_to_mm[E_AXIS_N(e)]; } } }; #else // !FILAMENT_RUNOUT_DISTANCE_MM // RunoutResponseDebounced triggers a runout event after a runout // condition has been detected runout_threshold times in a row. class RunoutResponseDebounced { private: static constexpr int8_t runout_threshold = FILAMENT_RUNOUT_THRESHOLD; static int8_t runout_count; public: static inline void reset() { runout_count = runout_threshold; } static inline void run() { if (runout_count >= 0) runout_count--; } static inline bool has_run_out() { return runout_count < 0; } static inline void block_completed(const block_t* const) { } static inline void filament_present(const uint8_t) { runout_count = runout_threshold; } }; #endif // !FILAMENT_RUNOUT_DISTANCE_MM /********************************* TEMPLATE SPECIALIZATION *********************************/ typedef TFilamentMonitor< #ifdef FILAMENT_RUNOUT_DISTANCE_MM RunoutResponseDelayed, TERN(FILAMENT_MOTION_SENSOR, FilamentSensorEncoder, FilamentSensorSwitch) #else RunoutResponseDebounced, FilamentSensorSwitch #endif > FilamentMonitor; extern FilamentMonitor runout;