diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index fc3220566c..d51004d460 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -2993,9 +2993,21 @@ * Activate to make volumetric extrusion the default method, * with DEFAULT_NOMINAL_FILAMENT_DIA as the default diameter. * - * M200 D0 to disable, M200 Dn to set a new diameter. + * M200 D0 to disable, M200 Dn to set a new diameter (and enable volumetric). + * M200 S0/S1 to disable/enable volumetric extrusion. */ //#define VOLUMETRIC_DEFAULT_ON + + //#define VOLUMETRIC_EXTRUDER_LIMIT + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + /** + * Default volumetric extrusion limit in cubic mm per second (mm^3/sec). + * This factory setting applies to all extruders. + * Use 'M200 [T] L' to override and 'M502' to reset. + * A non-zero value activates Volume-based Extrusion Limiting. + */ + #define DEFAULT_VOLUMETRIC_EXTRUDER_LIMIT 0.00 // (mm^3/sec) + #endif #endif /** diff --git a/Marlin/src/gcode/config/M200-M205.cpp b/Marlin/src/gcode/config/M200-M205.cpp index b717dc3365..d3c355f775 100644 --- a/Marlin/src/gcode/config/M200-M205.cpp +++ b/Marlin/src/gcode/config/M200-M205.cpp @@ -30,21 +30,42 @@ * M200: Set filament diameter and set E axis units to cubic units * * T - Optional extruder number. Current extruder if omitted. - * D - Diameter of the filament. Use "D0" to switch back to linear units on the E axis. + * D - Set filament diameter and enable. D0 disables volumetric. + * S - Turn volumetric ON or OFF. + * L - Volumetric extruder limit (in mm^3/sec). L0 disables the limit. */ void GcodeSuite::M200() { const int8_t target_extruder = get_target_extruder_from_command(); if (target_extruder < 0) return; - if (parser.seen('D')) { - // setting any extruder filament size disables volumetric on the assumption that - // slicers either generate in extruder values as cubic mm or as as filament feeds - // for all extruders + bool vol_enable = parser.volumetric_enabled, + can_enable = true; + + if (parser.seenval('D')) { const float dval = parser.value_linear_units(); - if ( (parser.volumetric_enabled = (dval != 0)) ) + if (dval) { // Set filament size for volumetric calculation planner.set_filament_size(target_extruder, dval); + vol_enable = true; // Dn = enable for compatibility + } + else + can_enable = false; // D0 = disable for compatibility } + + // Enable or disable with S1 / S0 + parser.volumetric_enabled = can_enable && parser.boolval('S', vol_enable); + + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + if (parser.seenval('L')) { + // Set volumetric limit (in mm^3/sec) + const float lval = parser.value_float(); + if (WITHIN(lval, 0, 20)) + planner.set_volumetric_extruder_limit(target_extruder, lval); + else + SERIAL_ECHOLNPGM("?L value out of range (0-20)."); + } + #endif + planner.calculate_volumetric_multipliers(); } diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h index 6fb546822b..ff4f7989db 100644 --- a/Marlin/src/inc/Conditionals_post.h +++ b/Marlin/src/inc/Conditionals_post.h @@ -260,6 +260,13 @@ #define MAX_AUTORETRACT 99 #endif +/** + * Provide a DEFAULT_VOLUMETRIC_EXTRUDER_LIMIT in case NO_VOLUMETRICS is enabled + */ +#ifndef DEFAULT_VOLUMETRIC_EXTRUDER_LIMIT + #define DEFAULT_VOLUMETRIC_EXTRUDER_LIMIT 0.00 +#endif + /** * LCD Contrast for Graphical Displays */ diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index 329f094e47..0d411fb0fd 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -1479,6 +1479,17 @@ static_assert(hbm[Z_AXIS] >= 0, "HOMING_BUMP_MM.Z must be greater than or equal #endif #endif +/** + * Volumetric Extruder Limit + */ +#if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + #if ENABLED(NO_VOLUMETRICS) + #error "VOLUMETRIC_EXTRUDER_LIMIT requires NO_VOLUMETRICS to be disabled." + #elif MIN_STEPS_PER_SEGMENT > 1 + #error "VOLUMETRIC_EXTRUDER_LIMIT is not compatible with MIN_STEPS_PER_SEGMENT greater than 1." + #endif +#endif + /** * ULTIPANEL encoder */ diff --git a/Marlin/src/lcd/language/language_en.h b/Marlin/src/lcd/language/language_en.h index b8013bf16c..bfa1ed52ea 100644 --- a/Marlin/src/lcd/language/language_en.h +++ b/Marlin/src/lcd/language/language_en.h @@ -317,6 +317,8 @@ namespace Language_en { PROGMEM Language_Str MSG_MOTION = _UxGT("Motion"); PROGMEM Language_Str MSG_FILAMENT = _UxGT("Filament"); PROGMEM Language_Str MSG_VOLUMETRIC_ENABLED = _UxGT("E in mm³"); + PROGMEM Language_Str MSG_VOLUMETRIC_LIMIT = _UxGT("E Limit in mm³"); + PROGMEM Language_Str MSG_VOLUMETRIC_LIMIT_E = _UxGT("E Limit *"); PROGMEM Language_Str MSG_FILAMENT_DIAM = _UxGT("Fil. Dia."); PROGMEM Language_Str MSG_FILAMENT_DIAM_E = _UxGT("Fil. Dia. *"); PROGMEM Language_Str MSG_FILAMENT_UNLOAD = _UxGT("Unload mm"); diff --git a/Marlin/src/lcd/menu/menu_advanced.cpp b/Marlin/src/lcd/menu/menu_advanced.cpp index bc74643620..f06024d90e 100644 --- a/Marlin/src/lcd/menu/menu_advanced.cpp +++ b/Marlin/src/lcd/menu/menu_advanced.cpp @@ -117,6 +117,14 @@ void menu_cancelobject(); #if DISABLED(NO_VOLUMETRICS) EDIT_ITEM(bool, MSG_VOLUMETRIC_ENABLED, &parser.volumetric_enabled, planner.calculate_volumetric_multipliers); + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + EDIT_ITEM_FAST(float42_52, MSG_VOLUMETRIC_LIMIT, &planner.volumetric_extruder_limit[active_extruder], 0.0f, 20.0f, planner.calculate_volumetric_extruder_limits); + #if EXTRUDERS > 1 + LOOP_L_N(n, EXTRUDERS) + EDIT_ITEM_FAST_N(float42_52, n, MSG_VOLUMETRIC_LIMIT_E, &planner.volumetric_extruder_limit[n], 0.0f, 20.00f, planner.calculate_volumetric_extruder_limits); + #endif + #endif + if (parser.volumetric_enabled) { EDIT_ITEM_FAST(float43, MSG_FILAMENT_DIAM, &planner.filament_size[active_extruder], 1.5f, 3.25f, planner.calculate_volumetric_multipliers); #if EXTRUDERS > 1 diff --git a/Marlin/src/module/configuration_store.cpp b/Marlin/src/module/configuration_store.cpp index ead9ea060e..ba242a7f02 100644 --- a/Marlin/src/module/configuration_store.cpp +++ b/Marlin/src/module/configuration_store.cpp @@ -37,7 +37,7 @@ */ // Change EEPROM version if the structure changes -#define EEPROM_VERSION "V79" +#define EEPROM_VERSION "V80" #define EEPROM_OFFSET 100 // Check the integrity of data offsets. @@ -320,8 +320,9 @@ typedef struct SettingsDataStruct { // // !NO_VOLUMETRIC // - bool parser_volumetric_enabled; // M200 D parser.volumetric_enabled + bool parser_volumetric_enabled; // M200 S parser.volumetric_enabled float planner_filament_size[EXTRUDERS]; // M200 T D planner.filament_size[] + float planner_volumetric_extruder_limit[EXTRUDERS]; // M200 T L planner.volumetric_extruder_limit[] // // HAS_TRINAMIC_CONFIG @@ -935,12 +936,20 @@ void MarlinSettings::postprocess() { EEPROM_WRITE(parser.volumetric_enabled); EEPROM_WRITE(planner.filament_size); + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + EEPROM_WRITE(planner.volumetric_extruder_limit); + #else + dummyf = DEFAULT_VOLUMETRIC_EXTRUDER_LIMIT; + for (uint8_t q = EXTRUDERS; q--;) EEPROM_WRITE(dummyf); + #endif #else const bool volumetric_enabled = false; - dummyf = DEFAULT_NOMINAL_FILAMENT_DIA; EEPROM_WRITE(volumetric_enabled); + dummyf = DEFAULT_NOMINAL_FILAMENT_DIA; + for (uint8_t q = EXTRUDERS; q--;) EEPROM_WRITE(dummyf); + dummyf = DEFAULT_VOLUMETRIC_EXTRUDER_LIMIT; for (uint8_t q = EXTRUDERS; q--;) EEPROM_WRITE(dummyf); #endif @@ -1787,6 +1796,9 @@ void MarlinSettings::postprocess() { struct { bool volumetric_enabled; float filament_size[EXTRUDERS]; + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + float volumetric_extruder_limit[EXTRUDERS]; + #endif } storage; _FIELD_TEST(parser_volumetric_enabled); @@ -1796,6 +1808,9 @@ void MarlinSettings::postprocess() { if (!validating) { parser.volumetric_enabled = storage.volumetric_enabled; COPY(planner.filament_size, storage.filament_size); + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + COPY(planner.volumetric_extruder_limit, storage.volumetric_extruder_limit); + #endif } #endif } @@ -2598,6 +2613,10 @@ void MarlinSettings::reset() { parser.volumetric_enabled = ENABLED(VOLUMETRIC_DEFAULT_ON); LOOP_L_N(q, COUNT(planner.filament_size)) planner.filament_size[q] = DEFAULT_NOMINAL_FILAMENT_DIA; + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + LOOP_L_N(q, COUNT(planner.volumetric_extruder_limit)) + planner.volumetric_extruder_limit[q] = DEFAULT_VOLUMETRIC_EXTRUDER_LIMIT; + #endif #endif endstops.enable_globally(ENABLED(ENDSTOPS_ALWAYS_ON_DEFAULT)); @@ -2750,7 +2769,7 @@ void MarlinSettings::reset() { SERIAL_EOL(); - #if DISABLED(NO_VOLUMETRICS) + #if EXTRUDERS && DISABLED(NO_VOLUMETRICS) /** * Volumetric extrusion M200 @@ -2765,20 +2784,26 @@ void MarlinSettings::reset() { #if EXTRUDERS == 1 CONFIG_ECHO_START(); - SERIAL_ECHOLNPAIR(" M200 D", LINEAR_UNIT(planner.filament_size[0])); - #elif EXTRUDERS + SERIAL_ECHOLNPAIR(" M200 S", int(parser.volumetric_enabled) + , " D", LINEAR_UNIT(planner.filament_size[0]), + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + , " L", LINEAR_UNIT(planner.volumetric_extruder_limit[0]) + #endif + ); + #else LOOP_L_N(i, EXTRUDERS) { CONFIG_ECHO_START(); - SERIAL_ECHOPGM(" M200"); - if (i) SERIAL_ECHOPAIR_P(SP_T_STR, int(i)); - SERIAL_ECHOLNPAIR(" D", LINEAR_UNIT(planner.filament_size[i])); + SERIAL_ECHOLNPAIR(" M200 T", int(i) + , " D", LINEAR_UNIT(planner.filament_size[i]) + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + , " L", LINEAR_UNIT(planner.volumetric_extruder_limit[i]) + #endif + ); } + CONFIG_ECHO_START(); + SERIAL_ECHOLNPAIR(" M200 S", int(parser.volumetric_enabled)); #endif - - if (!parser.volumetric_enabled) - CONFIG_ECHO_MSG(" M200 D0"); - - #endif // !NO_VOLUMETRICS + #endif // EXTRUDERS && !NO_VOLUMETRICS CONFIG_ECHO_HEADING("Steps per unit:"); report_M92(!forReplay); diff --git a/Marlin/src/module/planner.cpp b/Marlin/src/module/planner.cpp index f04c63060d..cd425ef174 100644 --- a/Marlin/src/module/planner.cpp +++ b/Marlin/src/module/planner.cpp @@ -171,6 +171,11 @@ float Planner::steps_to_mm[XYZE_N]; // (mm) Millimeters per step Planner::volumetric_multiplier[EXTRUDERS]; // Reciprocal of cross-sectional area of filament (in mm^2). Pre-calculated to reduce computation in the planner #endif +#if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + float Planner::volumetric_extruder_limit[EXTRUDERS], // max mm^3/sec the extruder is able to handle + Planner::volumetric_extruder_feedrate_limit[EXTRUDERS]; // pre calculated extruder feedrate limit based on volumetric_extruder_limit; pre-calculated to reduce computation in the planner +#endif + #if HAS_LEVELING bool Planner::leveling_active = false; // Flag that auto bed leveling is enabled #if ABL_PLANAR @@ -1407,10 +1412,28 @@ void Planner::check_axes_activity() { volumetric_multiplier[i] = calculate_volumetric_multiplier(filament_size[i]); refresh_e_factor(i); } + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + calculate_volumetric_extruder_limits(); // update volumetric_extruder_limits as well. + #endif } #endif // !NO_VOLUMETRICS +#if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + + /** + * Convert volumetric based limits into pre calculated extruder feedrate limits. + */ + void Planner::calculate_volumetric_extruder_limit(const uint8_t e) { + const float &lim = volumetric_extruder_limit[e], &siz = filament_size[e]; + volumetric_extruder_feedrate_limit[e] = (lim && siz) ? lim / CIRCLE_AREA(siz * 0.5f) : 0; + } + void Planner::calculate_volumetric_extruder_limits() { + LOOP_L_N(e, EXTRUDERS) calculate_volumetric_extruder_limit(e); + } + +#endif + #if ENABLED(FILAMENT_WIDTH_SENSOR) /** * Convert the ratio value given by the filament width sensor @@ -2077,10 +2100,33 @@ bool Planner::_populate_block(block_t * const block, bool split_move, if (mixer.get_current_vtool() == MIXER_AUTORETRACT_TOOL) current_speed.e *= MIXING_STEPPERS; #endif + const feedRate_t cs = ABS(current_speed.e), max_fr = settings.max_feedrate_mm_s[E_AXIS_N(extruder)] * TERN(HAS_MIXER_SYNC_CHANNEL, MIXING_STEPPERS, 1); - if (cs > max_fr) NOMORE(speed_factor, max_fr / cs); + + if (cs > max_fr) NOMORE(speed_factor, max_fr / cs); //respect max feedrate on any movement (doesn't matter if E axes only or not) + + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + const feedRate_t max_vfr = volumetric_extruder_feedrate_limit[extruder] + * TERN(HAS_MIXER_SYNC_CHANNEL, MIXING_STEPPERS, 1); + + // TODO: Doesn't work properly for joined segments. Set MIN_STEPS_PER_SEGMENT 1 as workaround. + + if (block->steps.a || block->steps.b || block->steps.c) { + + if (max_vfr > 0 && cs > max_vfr) { + NOMORE(speed_factor, max_vfr / cs); // respect volumetric extruder limit (if any) + /* <-- add a slash to enable + SERIAL_ECHOPAIR("volumetric extruder limit enforced: ", (cs * CIRCLE_AREA(filament_size[extruder] * 0.5f))); + SERIAL_ECHOPAIR(" mm^3/s (", cs); + SERIAL_ECHOPAIR(" mm/s) limited to ", (max_vfr * CIRCLE_AREA(filament_size[extruder] * 0.5f))); + SERIAL_ECHOPAIR(" mm^3/s (", max_vfr); + SERIAL_ECHOLNPGM(" mm/s)"); + //*/ + } + } + #endif } #endif diff --git a/Marlin/src/module/planner.h b/Marlin/src/module/planner.h index 228b3c9a1c..4086a8a62a 100644 --- a/Marlin/src/module/planner.h +++ b/Marlin/src/module/planner.h @@ -333,6 +333,11 @@ class Planner { // May be auto-adjusted by a filament width sensor #endif + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + static float volumetric_extruder_limit[EXTRUDERS], // Maximum mm^3/sec the extruder can handle + volumetric_extruder_feedrate_limit[EXTRUDERS]; // Feedrate limit (mm/s) calculated from volume limit + #endif + static planner_settings_t settings; #if ENABLED(LASER_POWER_INLINE) @@ -473,9 +478,6 @@ class Planner { // Manage fans, paste pressure, etc. static void check_axes_activity(); - // Update multipliers based on new diameter measurements - static void calculate_volumetric_multipliers(); - #if ENABLED(FILAMENT_WIDTH_SENSOR) void apply_filament_width_sensor(const int8_t encoded_ratio); @@ -489,8 +491,18 @@ class Planner { #if DISABLED(NO_VOLUMETRICS) + // Update multipliers based on new diameter measurements + static void calculate_volumetric_multipliers(); + + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + // Update pre calculated extruder feedrate limits based on volumetric values + static void calculate_volumetric_extruder_limit(const uint8_t e); + static void calculate_volumetric_extruder_limits(); + #endif + FORCE_INLINE static void set_filament_size(const uint8_t e, const float &v) { filament_size[e] = v; + if (v > 0) volumetric_area_nominal = CIRCLE_AREA(v * 0.5); //TODO: should it be per extruder // make sure all extruders have some sane value for the filament size LOOP_L_N(i, COUNT(filament_size)) if (!filament_size[i]) filament_size[i] = DEFAULT_NOMINAL_FILAMENT_DIA; @@ -498,6 +510,13 @@ class Planner { #endif + #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT) + FORCE_INLINE static void set_volumetric_extruder_limit(const uint8_t e, const float &v) { + volumetric_extruder_limit[e] = v; + calculate_volumetric_extruder_limit(e); + } + #endif + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) /**