From 06fe85e9ee1339b142a9e788923ac7cdaf06d571 Mon Sep 17 00:00:00 2001 From: michalprusa Date: Fri, 7 Jul 2017 01:58:02 +0200 Subject: [PATCH] Linear Advance --- Firmware/Configuration_adv.h | 53 ++++-- Firmware/Marlin_main.cpp | 56 ++++++ Firmware/planner.cpp | 69 ++++++- Firmware/planner.h | 9 + Firmware/stepper.cpp | 169 ++++++++++++++++-- Firmware/stepper.h | 10 ++ .../variants/1_75mm_MK3-EINY04-E3Dv6full.h | 4 +- 7 files changed, 334 insertions(+), 36 deletions(-) diff --git a/Firmware/Configuration_adv.h b/Firmware/Configuration_adv.h index 54e74ba0..0888e9f4 100644 --- a/Firmware/Configuration_adv.h +++ b/Firmware/Configuration_adv.h @@ -265,24 +265,45 @@ #endif #endif -// extruder advance constant (s2/mm3) -// -// advance (steps) = STEPS_PER_CUBIC_MM_E * EXTRUDER_ADVANCE_K * cubic mm per second ^ 2 -// -// Hooke's law says: force = k * distance -// Bernoulli's principle says: v ^ 2 / 2 + g . h + pressure / density = constant -// so: v ^ 2 is proportional to number of steps we advance the extruder -//#define ADVANCE +/** + * Implementation of linear pressure control + * + * Assumption: advance = k * (delta velocity) + * K=0 means advance disabled. + * See Marlin documentation for calibration instructions. + */ +#define LIN_ADVANCE -#ifdef ADVANCE - #define EXTRUDER_ADVANCE_K .006 +#ifdef LIN_ADVANCE + #define LIN_ADVANCE_K 0 //Try around 45 for PLA, around 25 for ABS. - #define D_FILAMENT 1.75 - #define STEPS_MM_E 174.6 - #define EXTRUSION_AREA (0.25 * D_FILAMENT * D_FILAMENT * 3.14159) - #define STEPS_PER_CUBIC_MM_E (axis_steps_per_unit[E_AXIS]/ EXTRUSION_AREA) - -#endif // ADVANCE + /** + * Some Slicers produce Gcode with randomly jumping extrusion widths occasionally. + * For example within a 0.4mm perimeter it may produce a single segment of 0.05mm width. + * While this is harmless for normal printing (the fluid nature of the filament will + * close this very, very tiny gap), it throws off the LIN_ADVANCE pressure adaption. + * + * For this case LIN_ADVANCE_E_D_RATIO can be used to set the extrusion:distance ratio + * to a fixed value. Note that using a fixed ratio will lead to wrong nozzle pressures + * if the slicer is using variable widths or layer heights within one print! + * + * This option sets the default E:D ratio at startup. Use `M900` to override this value. + * + * Example: `M900 W0.4 H0.2 D1.75`, where: + * - W is the extrusion width in mm + * - H is the layer height in mm + * - D is the filament diameter in mm + * + * Example: `M900 R0.0458` to set the ratio directly. + * + * Set to 0 to auto-detect the ratio based on given Gcode G1 print moves. + * + * Slic3r (including Prusa Slic3r) produces Gcode compatible with the automatic mode. + * Cura (as of this writing) may produce Gcode incompatible with the automatic mode. + */ +#define LIN_ADVANCE_E_D_RATIO 0 // The calculated ratio (or 0) according to the formula W * H / ((D / 2) ^ 2 * PI) + // Example: 0.4 * 0.2 / ((1.75 / 2) ^ 2 * PI) = 0.033260135 +#endif // Arc interpretation settings: #define MM_PER_ARC_SEGMENT 1 diff --git a/Firmware/Marlin_main.cpp b/Firmware/Marlin_main.cpp index 960661fe..2781927f 100644 --- a/Firmware/Marlin_main.cpp +++ b/Firmware/Marlin_main.cpp @@ -203,6 +203,7 @@ // M540 - Use S[0|1] to enable or disable the stop SD card print on endstop hit (requires ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED) // M600 - Pause for filament change X[pos] Y[pos] Z[relative lift] E[initial retract] L[later retract distance for removal] // M605 - Set dual x-carriage movement mode: S [ X R ] +// M900 - Set LIN_ADVANCE options, if enabled. See Configuration_adv.h for details. // M907 - Set digital trimpot motor current using axis codes. // M908 - Control digital trimpot directly. // M350 - Set microstepping mode. @@ -1655,6 +1656,15 @@ static inline long code_value_long() { return strtol(strchr_pointer+1, NUL static inline int16_t code_value_short() { return int16_t(strtol(strchr_pointer+1, NULL, 10)); }; static inline uint8_t code_value_uint8() { return uint8_t(strtol(strchr_pointer+1, NULL, 10)); }; +static inline float code_value_float() { + char* e = strchr(strchr_pointer, 'E'); + if (!e) return strtod(strchr_pointer + 1, NULL); + *e = 0; + float ret = strtod(strchr_pointer + 1, NULL); + *e = 'E'; + return ret; +} + #define DEFINE_PGM_READ_ANY(type, reader) \ static inline type pgm_read_any(const type *p) \ { return pgm_read_##reader##_near(p); } @@ -1839,6 +1849,40 @@ static float probe_pt(float x, float y, float z_before) { } #endif // #ifdef ENABLE_AUTO_BED_LEVELING + +#ifdef LIN_ADVANCE + /** + * M900: Set and/or Get advance K factor and WH/D ratio + * + * K Set advance K factor + * R Set ratio directly (overrides WH/D) + * W H D Set ratio from WH/D + */ +inline void gcode_M900() { + st_synchronize(); + + const float newK = code_seen('K') ? code_value_float() : -1; + if (newK >= 0) extruder_advance_k = newK; + + float newR = code_seen('R') ? code_value_float() : -1; + if (newR < 0) { + const float newD = code_seen('D') ? code_value_float() : -1, + newW = code_seen('W') ? code_value_float() : -1, + newH = code_seen('H') ? code_value_float() : -1; + if (newD >= 0 && newW >= 0 && newH >= 0) + newR = newD ? (newW * newH) / (sq(newD * 0.5) * M_PI) : 0; + } + if (newR >= 0) advance_ed_ratio = newR; + + SERIAL_ECHO_START; + SERIAL_ECHOPGM("Advance K="); + SERIAL_ECHOLN(extruder_advance_k); + SERIAL_ECHOPGM(" E/D="); + const float ratio = advance_ed_ratio; + if (ratio) SERIAL_ECHOLN(ratio); else SERIAL_ECHOLNPGM("Auto"); + } +#endif // LIN_ADVANCE + /* void homeaxis(int axis) { #define HOMEAXIS_DO(LETTER) \ @@ -5488,6 +5532,12 @@ case 404: //M404 Enter the nominal filament width (3mm, 1.75mm ) N<3.0> or disp if(lcd_commands_type == 0) lcd_commands_type = LCD_COMMAND_LONG_PAUSE_RESUME; } break; + +#ifdef LIN_ADVANCE + case 900: // M900: Set LIN_ADVANCE options. + gcode_M900(); + break; +#endif case 907: // M907 Set digital trimpot motor current using axis codes. { @@ -5714,6 +5764,12 @@ case 404: //M404 Enter the nominal filament width (3mm, 1.75mm ) N<3.0> or disp } snmm_filaments_used |= (1 << tmp_extruder); //for stop print #ifdef SNMM + + #ifdef LIN_ADVANCE + if (snmm_extruder != tmp_extruder) + clear_current_adv_vars(); //Check if the selected extruder is not the active one and reset LIN_ADVANCE variables if so. + #endif + snmm_extruder = tmp_extruder; st_synchronize(); diff --git a/Firmware/planner.cpp b/Firmware/planner.cpp index e3d777a4..f7e61802 100644 --- a/Firmware/planner.cpp +++ b/Firmware/planner.cpp @@ -126,6 +126,12 @@ float extrude_min_temp=EXTRUDE_MINTEMP; static char meas_sample; //temporary variable to hold filament measurement sample #endif +#ifdef LIN_ADVANCE + float extruder_advance_k = LIN_ADVANCE_K, + advance_ed_ratio = LIN_ADVANCE_E_D_RATIO, + position_float[NUM_AXIS] = { 0 }; +#endif + // Returns the index of the next block in the ring buffer // NOTE: Removed modulo (%) operator, which uses an expensive divide and multiplication. static inline int8_t next_block_index(int8_t block_index) { @@ -411,6 +417,9 @@ void plan_init() { block_buffer_head = 0; block_buffer_tail = 0; memset(position, 0, sizeof(position)); // clear position +#ifdef LIN_ADVANCE + memset(position_float, 0, sizeof(position)); // clear position +#endif previous_speed[0] = 0.0; previous_speed[1] = 0.0; previous_speed[2] = 0.0; @@ -676,12 +685,22 @@ void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate target[Z_AXIS] = lround(z*axis_steps_per_unit[Z_AXIS]); #endif // ENABLE_MESH_BED_LEVELING target[E_AXIS] = lround(e*axis_steps_per_unit[E_AXIS]); + +#ifdef LIN_ADVANCE + const float mm_D_float = sqrt(sq(x - position_float[X_AXIS]) + sq(y - position_float[Y_AXIS])); + float de_float = e - position_float[E_AXIS]; +#endif + #ifdef PREVENT_DANGEROUS_EXTRUDE if(target[E_AXIS]!=position[E_AXIS]) { if(degHotend(active_extruder)axis_steps_per_unit[E_AXIS]*EXTRUDE_MAXLENGTH) { position[E_AXIS]=target[E_AXIS]; //behave as if the move really took place, but ignore E part +#ifdef LIN_ADVANCE + position_float[E_AXIS] = e; + de_float = 0; +#endif SERIAL_ECHO_START; SERIAL_ECHOLNRPGM(MSG_ERR_LONG_EXTRUDE_STOP); } @@ -1112,6 +1135,37 @@ Having the real displacement of the head, we can calculate the total movement le previous_nominal_speed = block->nominal_speed; previous_safe_speed = safe_speed; +#ifdef LIN_ADVANCE + + // + // Use LIN_ADVANCE for blocks if all these are true: + // + // esteps : We have E steps todo (a printing move) + // + // block->steps[X_AXIS] || block->steps[Y_AXIS] : We have a movement in XY direction (i.e., not retract / prime). + // + // extruder_advance_k : There is an advance factor set. + // + // block->steps[E_AXIS] != block->step_event_count : A problem occurs if the move before a retract is too small. + // In that case, the retract and move will be executed together. + // This leads to too many advance steps due to a huge e_acceleration. + // The math is good, but we must avoid retract moves with advance! + // de_float > 0.0 : Extruder is running forward (e.g., for "Wipe while retracting" (Slic3r) or "Combing" (Cura) moves) + // + block->use_advance_lead = block->steps_e + && (block->steps_x || block->steps_y) + && extruder_advance_k + && (uint32_t)block->steps_e != block->step_event_count + && de_float > 0.0; + if (block->use_advance_lead) + block->abs_adv_steps_multiplier8 = lround( + extruder_advance_k + * ((advance_ed_ratio < 0.000001) ? de_float / mm_D_float : advance_ed_ratio) // Use the fixed ratio, if set + * (block->nominal_speed / (float)block->nominal_rate) + * axis_steps_per_unit[E_AXIS] * 256.0 + ); +#endif + // Precalculate the division, so when all the trapezoids in the planner queue get recalculated, the division is not repeated. block->speed_factor = block->nominal_rate / block->nominal_speed; calculate_trapezoid_for_block(block, block->entry_speed, safe_speed); @@ -1122,6 +1176,13 @@ Having the real displacement of the head, we can calculate the total movement le // Update position memcpy(position, target, sizeof(target)); // position[] = target[] +#ifdef LIN_ADVANCE + position_float[X_AXIS] = x; + position_float[Y_AXIS] = y; + position_float[Z_AXIS] = z; + position_float[E_AXIS] = e; +#endif + // Recalculate the trapezoids to maximize speed at the segment transitions while respecting // the machine limits (maximum acceleration and maximum jerk). // This runs asynchronously with the stepper interrupt controller, which may @@ -1178,7 +1239,13 @@ void plan_set_position(float x, float y, float z, const float &e) #else position[Z_AXIS] = lround(z*axis_steps_per_unit[Z_AXIS]); #endif // ENABLE_MESH_BED_LEVELING - position[E_AXIS] = lround(e*axis_steps_per_unit[E_AXIS]); + position[E_AXIS] = lround(e*axis_steps_per_unit[E_AXIS]); +#ifdef LIN_ADVANCE + position_float[X_AXIS] = x; + position_float[Y_AXIS] = y; + position_float[Z_AXIS] = z; + position_float[E_AXIS] = e; +#endif st_set_position(position[X_AXIS], position[Y_AXIS], position[Z_AXIS], position[E_AXIS]); previous_nominal_speed = 0.0; // Resets planner junction speeds. Assumes start from rest. previous_speed[0] = 0.0; diff --git a/Firmware/planner.h b/Firmware/planner.h index c47cecbb..96ab83bc 100644 --- a/Firmware/planner.h +++ b/Firmware/planner.h @@ -88,8 +88,17 @@ typedef struct { // Pre-calculated division for the calculate_trapezoid_for_block() routine to run faster. float speed_factor; + +#ifdef LIN_ADVANCE + bool use_advance_lead; + unsigned long abs_adv_steps_multiplier8; // Factorised by 2^8 to avoid float +#endif } block_t; +#ifdef LIN_ADVANCE + extern float extruder_advance_k, advance_ed_ratio; +#endif + #ifdef ENABLE_AUTO_BED_LEVELING // this holds the required transform to compensate for bed level extern matrix_3x3 plan_bed_level_matrix; diff --git a/Firmware/stepper.cpp b/Firmware/stepper.cpp index 6d58d7b8..5f131c50 100644 --- a/Firmware/stepper.cpp +++ b/Firmware/stepper.cpp @@ -98,6 +98,26 @@ int8_t SilentMode; volatile long count_position[NUM_AXIS] = { 0, 0, 0, 0}; volatile signed char count_direction[NUM_AXIS] = { 1, 1, 1, 1}; +#ifdef LIN_ADVANCE + + uint16_t ADV_NEVER = 65535; + + static uint16_t nextMainISR = 0; + static uint16_t nextAdvanceISR = ADV_NEVER; + static uint16_t eISR_Rate = ADV_NEVER; + + static volatile int e_steps; //Extrusion steps to be executed by the stepper + static int final_estep_rate; //Speed of extruder at cruising speed + static int current_estep_rate; //The current speed of the extruder + static int current_adv_steps; //The current pretension of filament expressed in steps + + #define ADV_RATE(T, L) (e_steps ? (T) * (L) / abs(e_steps) : ADV_NEVER) + #define _NEXT_ISR(T) nextMainISR = T + +#else + #define _NEXT_ISR(T) OCR1A = T +#endif + //=========================================================================== //=============================functions ============================ //=========================================================================== @@ -319,24 +339,28 @@ FORCE_INLINE void trapezoid_generator_reset() { step_loops_nominal = step_loops; acc_step_rate = current_block->initial_rate; acceleration_time = calc_timer(acc_step_rate); - OCR1A = acceleration_time; + _NEXT_ISR(acceleration_time); -// SERIAL_ECHO_START; -// SERIAL_ECHOPGM("advance :"); -// SERIAL_ECHO(current_block->advance/256.0); -// SERIAL_ECHOPGM("advance rate :"); -// SERIAL_ECHO(current_block->advance_rate/256.0); -// SERIAL_ECHOPGM("initial advance :"); -// SERIAL_ECHO(current_block->initial_advance/256.0); -// SERIAL_ECHOPGM("final advance :"); -// SERIAL_ECHOLN(current_block->final_advance/256.0); + #ifdef LIN_ADVANCE + if (current_block->use_advance_lead) { + current_estep_rate = ((unsigned long)acc_step_rate * current_block->abs_adv_steps_multiplier8) >> 17; + final_estep_rate = (current_block->nominal_rate * current_block->abs_adv_steps_multiplier8) >> 17; + } + #endif } // "The Stepper Driver Interrupt" - This timer interrupt is the workhorse. // It pops blocks from the block_buffer and executes them by pulsing the stepper pins appropriately. -ISR(TIMER1_COMPA_vect) -{ +ISR(TIMER1_COMPA_vect) { + #ifdef LIN_ADVANCE + advance_isr_scheduler(); + #else + isr(); + #endif +} + +void isr() { //if (UVLO) uvlo(); // If there is no current block, attempt to pop one from the buffer if (current_block == NULL) { @@ -355,13 +379,13 @@ ISR(TIMER1_COMPA_vect) #ifdef Z_LATE_ENABLE if(current_block->steps_z > 0) { enable_z(); - OCR1A = 2000; //1ms wait + _NEXT_ISR(2000); //1ms wait return; } #endif } else { - OCR1A=2000; // 1kHz. + _NEXT_ISR(2000); // 1kHz. } } @@ -582,6 +606,15 @@ ISR(TIMER1_COMPA_vect) MSerial.checkRx(); // Check for serial chars. #endif +#ifdef LIN_ADVANCE + counter_e += current_block->steps_e; + if (counter_e > 0) { + counter_e -= current_block->step_event_count; + count_position[E_AXIS] += count_direction[E_AXIS]; + ((out_bits&(1<steps_x; if (counter_x > 0) { WRITE(X_STEP_PIN, !INVERT_X_STEP_PIN); @@ -636,6 +669,7 @@ ISR(TIMER1_COMPA_vect) #endif } +#ifndef LIN_ADVANCE counter_e += current_block->steps_e; if (counter_e > 0) { WRITE_E_STEP(!INVERT_E_STEP_PIN); @@ -643,9 +677,21 @@ ISR(TIMER1_COMPA_vect) count_position[E_AXIS]+=count_direction[E_AXIS]; WRITE_E_STEP(INVERT_E_STEP_PIN); } +#endif + step_events_completed += 1; if(step_events_completed >= current_block->step_event_count) break; } +#ifdef LIN_ADVANCE + if (current_block->use_advance_lead) { + const int delta_adv_steps = current_estep_rate - current_adv_steps; + current_adv_steps += delta_adv_steps; + e_steps += delta_adv_steps; + } + // If we have esteps to execute, fire the next advance_isr "now" + if (e_steps) nextAdvanceISR = 0; +#endif + // Calculare new timer value unsigned short timer; unsigned short step_rate; @@ -660,8 +706,15 @@ ISR(TIMER1_COMPA_vect) // step_rate to timer interval timer = calc_timer(acc_step_rate); - OCR1A = timer; + _NEXT_ISR(timer); acceleration_time += timer; + +#ifdef LIN_ADVANCE + if (current_block->use_advance_lead) { + current_estep_rate = ((uint32_t)acc_step_rate * current_block->abs_adv_steps_multiplier8) >> 17; + } + eISR_Rate = ADV_RATE(timer, step_loops); +#endif } else if (step_events_completed > (unsigned long int)current_block->decelerate_after) { MultiU24X24toH16(step_rate, deceleration_time, current_block->acceleration_rate); @@ -679,11 +732,25 @@ ISR(TIMER1_COMPA_vect) // step_rate to timer interval timer = calc_timer(step_rate); - OCR1A = timer; + _NEXT_ISR(timer); deceleration_time += timer; + +#ifdef LIN_ADVANCE + if (current_block->use_advance_lead) { + current_estep_rate = ((uint32_t)step_rate * current_block->abs_adv_steps_multiplier8) >> 17; + } + eISR_Rate = ADV_RATE(timer, step_loops); +#endif } else { - OCR1A = OCR1A_nominal; +#ifdef LIN_ADVANCE + if (current_block->use_advance_lead) + current_estep_rate = final_estep_rate; + + eISR_Rate = ADV_RATE(OCR1A_nominal, step_loops_nominal); +#endif + + _NEXT_ISR(OCR1A_nominal); // ensure we're running at the correct step rate, even if we just came off an acceleration step_loops = step_loops_nominal; } @@ -697,6 +764,69 @@ ISR(TIMER1_COMPA_vect) check_fans(); } +#ifdef LIN_ADVANCE + + // Timer interrupt for E. e_steps is set in the main routine. + +void advance_isr() { + + nextAdvanceISR = eISR_Rate; + + if (e_steps) { + bool dir = +#ifdef SNMM + ((e_steps < 0) == (snmm_extruder & 1)) +#else + (e_steps < 0) +#endif + ? INVERT_E0_DIR : !INVERT_E0_DIR; //If we have SNMM, reverse every second extruder. + WRITE(E0_DIR_PIN, dir); + + for (uint8_t i = step_loops; e_steps && i--;) { + WRITE(E0_STEP_PIN, !INVERT_E_STEP_PIN); + e_steps < 0 ? ++e_steps : --e_steps; + WRITE(E0_STEP_PIN, INVERT_E_STEP_PIN); + } + } +} + +void advance_isr_scheduler() { + // Run main stepping ISR if flagged + if (!nextMainISR) isr(); + + // Run Advance stepping ISR if flagged + if (!nextAdvanceISR) advance_isr(); + + // Is the next advance ISR scheduled before the next main ISR? + if (nextAdvanceISR <= nextMainISR) { + // Set up the next interrupt + OCR1A = nextAdvanceISR; + // New interval for the next main ISR + if (nextMainISR) nextMainISR -= nextAdvanceISR; + // Will call Stepper::advance_isr on the next interrupt + nextAdvanceISR = 0; + } + else { + // The next main ISR comes first + OCR1A = nextMainISR; + // New interval for the next advance ISR, if any + if (nextAdvanceISR && nextAdvanceISR != ADV_NEVER) + nextAdvanceISR -= nextMainISR; + // Will call Stepper::isr on the next interrupt + nextMainISR = 0; + } + + // Don't run the ISR faster than possible + if (OCR1A < TCNT1 + 16) OCR1A = TCNT1 + 16; +} + +void clear_current_adv_vars() { + e_steps = 0; //Should be already 0 at an filament change event, but just to be sure.. + current_adv_steps = 0; +} + +#endif // LIN_ADVANCE + void st_init() { #ifdef HAVE_TMC2130_DRIVERS @@ -897,6 +1027,11 @@ void st_init() TCNT1 = 0; ENABLE_STEPPER_DRIVER_INTERRUPT(); +#ifdef LIN_ADVANCE + e_steps = 0; + current_adv_steps = 0; +#endif + enable_endstops(true); // Start with endstops active. After homing they can be disabled sei(); } diff --git a/Firmware/stepper.h b/Firmware/stepper.h index 70d1bce5..29af8e52 100644 --- a/Firmware/stepper.h +++ b/Firmware/stepper.h @@ -44,6 +44,16 @@ extern bool abort_on_endstop_hit; // Initialize and start the stepper motor subsystem void st_init(); +// Interrupt Service Routines + +void isr(); + +#ifdef LIN_ADVANCE + void advance_isr(); + void advance_isr_scheduler(); + void clear_current_adv_vars(); //Used to reset the built up pretension and remaining esteps on filament change. +#endif + // Block until all buffered steps are executed void st_synchronize(); diff --git a/Firmware/variants/1_75mm_MK3-EINY04-E3Dv6full.h b/Firmware/variants/1_75mm_MK3-EINY04-E3Dv6full.h index b9cae5ec..09cec37a 100644 --- a/Firmware/variants/1_75mm_MK3-EINY04-E3Dv6full.h +++ b/Firmware/variants/1_75mm_MK3-EINY04-E3Dv6full.h @@ -134,8 +134,8 @@ const bool Z_MIN_ENDSTOP_INVERTING = false; // set to true to invert the logic o #define TMC2130_SG_DELAY 10 // stallguard delay (temporary solution) //new settings is possible for vsense = 1 -#define TMC2130_CURRENTS_H {15, 15, 20, 30} // default holding currents for all axes -#define TMC2130_CURRENTS_R {15, 15, 30, 30} // default running currents for all axes +#define TMC2130_CURRENTS_H {15, 15, 20, 28} // default holding currents for all axes +#define TMC2130_CURRENTS_R {15, 15, 40, 28} // default running currents for all axes //#define TMC2130_DEBUG //#define TMC2130_DEBUG_WR