diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 909949eb91..9aee4a8921 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -146,6 +146,36 @@ #define EXTRUDERS 1 #endif +// Enable this for dual x-carriage printers. +// A dual x-carriage design has the advantage that the inactive extruder can be parked which +// prevents hot-end ooze contaminating the print. It also reduces the weight of each x-carriage +// allowing faster printing speeds. +#define DUAL_X_CARRIAGE +#ifdef DUAL_X_CARRIAGE +// Configuration for second X-carriage +// Note: the first x-carriage is defined as the x-carriage which homes to the minimum endstop; +// the second x-carriage always homes to the maximum endstop. +#define X2_MIN_POS 88 // set minimum to ensure second x-carriage doesn't hit the parked first X-carriage +#define X2_MAX_POS 350.45 // set maximum to the distance between toolheads when both heads are homed +#define X2_HOME_DIR 1 // the second X-carriage always homes to the maximum endstop position +#define X2_HOME_POS X2_MAX_POS // default home position is the maximum carriage position + // However: In this mode the EXTRUDER_OFFSET_X value for the second extruder provides a software + // override for X2_HOME_POS. This also allow recalibration of the distance between the two endstops + // without modifying the firmware (through the "M218 T1 X???" command). + // Remember: you should set the second extruder x-offset to 0 in your slicer. + +// Pins for second x-carriage stepper driver (defined here to avoid further complicating pins.h) +#define X2_ENABLE_PIN 29 +#define X2_STEP_PIN 25 +#define X2_DIR_PIN 23 + +// The following settings control the behaviour of the automatic parking and unparking of inactive extruder +#define TOOLCHANGE_PARK_ZLIFT 0.1 // the distance to raise Z axis when parking an extruder +#define TOOLCHANGE_UNPARK_ZLIFT 1 // the distance to raise Z axis when unparking an extruder +#define TOOLCHANGE_UNPARK_SKIP_TRAVEL_MOVES // disable if slicer natively suports dual x-carriage mode. + // When enabled this avoids unnecessary & inadvertant moves from the last position of old extruder. +#endif // DUAL_X_CARRIAGE + //homing hits the endstop, then retracts by this distance, before it tries to slowly bump again: #define X_HOME_RETRACT_MM 5 #define Y_HOME_RETRACT_MM 5 diff --git a/Marlin/Marlin.h b/Marlin/Marlin.h index c9759eb219..1ae9494e34 100644 --- a/Marlin/Marlin.h +++ b/Marlin/Marlin.h @@ -96,7 +96,11 @@ void process_commands(); void manage_inactivity(); -#if defined(X_ENABLE_PIN) && X_ENABLE_PIN > -1 +#if defined(DUAL_X_CARRIAGE) && defined(X_ENABLE_PIN) && X_ENABLE_PIN > -1 \ + && defined(X2_ENABLE_PIN) && X2_ENABLE_PIN > -1 + #define enable_x() do { WRITE(X_ENABLE_PIN, X_ENABLE_ON); WRITE(X2_ENABLE_PIN, X_ENABLE_ON); } while (0) + #define disable_x() do { WRITE(X_ENABLE_PIN,!X_ENABLE_ON); WRITE(X2_ENABLE_PIN,!X_ENABLE_ON); } while (0) +#elif defined(X_ENABLE_PIN) && X_ENABLE_PIN > -1 #define enable_x() WRITE(X_ENABLE_PIN, X_ENABLE_ON) #define disable_x() WRITE(X_ENABLE_PIN,!X_ENABLE_ON) #else diff --git a/Marlin/Marlin_main.cpp b/Marlin/Marlin_main.cpp index 38da1db08e..c44cf5eca9 100644 --- a/Marlin/Marlin_main.cpp +++ b/Marlin/Marlin_main.cpp @@ -677,7 +677,46 @@ XYZ_CONSTS_FROM_CONFIG(float, max_length, MAX_LENGTH); XYZ_CONSTS_FROM_CONFIG(float, home_retract_mm, HOME_RETRACT_MM); XYZ_CONSTS_FROM_CONFIG(signed char, home_dir, HOME_DIR); +#ifdef DUAL_X_CARRIAGE + #if EXTRUDERS == 1 || defined(COREXY) \ + || !defined(X2_ENABLE_PIN) || !defined(X2_STEP_PIN) || !defined(X2_DIR_PIN) \ + || !defined(X2_HOME_POS) || !defined(X2_MIN_POS) || !defined(X2_MAX_POS) \ + || !defined(X_MAX_PIN) || X_MAX_PIN < 0 + #error "Missing or invalid definitions for DUAL_X_CARRIAGE mode." + #endif + #if X_HOME_DIR != -1 || X2_HOME_DIR != 1 + #error "Please use canonical x-carriage assignment" // the x-carriages are defined by their homing directions + #endif + +static float x_home_pos(int extruder) { + if (extruder == 0) + return base_home_pos(X_AXIS) + add_homeing[X_AXIS]; + else + // In dual carriage mode the extruder offset provides an override of the + // second X-carriage offset when homed - otherwise X2_HOME_POS is used. + // This allow soft recalibration of the second extruder offset position without firmware reflash + // (through the M218 command). + return (extruder_offset[X_AXIS][1] != 0) ? extruder_offset[X_AXIS][1] : X2_HOME_POS; +} + +static int x_home_dir(int extruder) { + return (extruder == 0) ? X_HOME_DIR : X2_HOME_DIR; +} + +static bool active_extruder_parked = false; +static float raised_parked_position[NUM_AXIS]; +static unsigned long delayed_move_time = 0; +#endif + static void axis_is_at_home(int axis) { +#ifdef DUAL_X_CARRIAGE + if (axis == X_AXIS && active_extruder != 0) { + current_position[X_AXIS] = x_home_pos(active_extruder); + min_pos[X_AXIS] = X2_MIN_POS; + max_pos[X_AXIS] = X2_MAX_POS; + return; + } +#endif current_position[axis] = base_home_pos(axis) + add_homeing[axis]; min_pos[axis] = base_min_pos(axis) + add_homeing[axis]; max_pos[axis] = base_max_pos(axis) + add_homeing[axis]; @@ -686,10 +725,16 @@ static void axis_is_at_home(int axis) { static void homeaxis(int axis) { #define HOMEAXIS_DO(LETTER) \ ((LETTER##_MIN_PIN > -1 && LETTER##_HOME_DIR==-1) || (LETTER##_MAX_PIN > -1 && LETTER##_HOME_DIR==1)) + if (axis==X_AXIS ? HOMEAXIS_DO(X) : axis==Y_AXIS ? HOMEAXIS_DO(Y) : axis==Z_AXIS ? HOMEAXIS_DO(Z) : 0) { + int axis_home_dir = home_dir(axis); +#ifdef DUAL_X_CARRIAGE + if (axis == X_AXIS) + axis_home_dir = x_home_dir(active_extruder); +#endif // Engage Servo endstop if enabled #ifdef SERVO_ENDSTOPS @@ -864,8 +909,14 @@ void process_commands() { current_position[X_AXIS] = 0;current_position[Y_AXIS] = 0; + #ifdef DUAL_X_CARRIAGE + int x_axis_home_dir = home_dir(X_AXIS); + #else + int x_axis_home_dir = x_home_dir(active_extruder); + #endif + plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); - destination[X_AXIS] = 1.5 * X_MAX_LENGTH * X_HOME_DIR;destination[Y_AXIS] = 1.5 * Y_MAX_LENGTH * Y_HOME_DIR; + destination[X_AXIS] = 1.5 * max_length(X_AXIS) * x_axis_home_dir;destination[Y_AXIS] = 1.5 * max_length(Y_AXIS) * home_dir(Y_AXIS); feedrate = homing_feedrate[X_AXIS]; if(homing_feedrate[Y_AXIS] raised_parked_position[Z_AXIS]) + raised_parked_position[Z_AXIS] = destination[Z_AXIS]; + delayed_move_time = millis(); + return; + } + delayed_move_time = 0; +#else + // this will cause the unpark code below to execute the specified lift in moving to the initial (travel move) position. + memcpy(current_position, destination, sizeof(current_position)); +#endif + } + // unpark extruder: 1) raise, 2) move into starting XY position, 3) lower + plan_buffer_line(raised_parked_position[X_AXIS], raised_parked_position[Y_AXIS], raised_parked_position[Z_AXIS], current_position[E_AXIS], max_feedrate[Z_AXIS], active_extruder); + plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], raised_parked_position[Z_AXIS], + current_position[E_AXIS], min(max_feedrate[X_AXIS],max_feedrate[Y_AXIS]), active_extruder); + plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], + current_position[E_AXIS], max_feedrate[Z_AXIS], active_extruder); + active_extruder_parked = false; + } +#endif //DUAL_X_CARRIAGE + // Do not use feedmultiply for E or Z only moves if( (current_position[X_AXIS] == destination [X_AXIS]) && (current_position[Y_AXIS] == destination [Y_AXIS])) { plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder); @@ -2254,6 +2378,9 @@ void controllerFan() || !READ(E2_ENABLE_PIN) #endif #if EXTRUDER > 1 + #if defined(X2_ENABLE_PIN) && X2_ENABLE_PIN > -1 + || !READ(X2_ENABLE_PIN) + #endif || !READ(E1_ENABLE_PIN) #endif || !READ(E0_ENABLE_PIN)) //If any of the drivers are enabled... @@ -2320,6 +2447,16 @@ void manage_inactivity() WRITE(E0_ENABLE_PIN,oldstatus); } #endif + #if defined(DUAL_X_CARRIAGE) && defined(TOOLCHANGE_UNPARK_SKIP_TRAVEL_MOVES) + // handle delayed move timeout + if (delayed_move_time != 0 && (millis() - delayed_move_time) > 1000) + { + // travel moves have been received so enact them + delayed_move_time = 0xFFFFFFFFUL; // force moves to be done + memcpy(destination,current_position,sizeof(destination)); + prepare_move(); + } + #endif check_axes_activity(); } diff --git a/Marlin/stepper.cpp b/Marlin/stepper.cpp index a7991501e9..2f1d912600 100644 --- a/Marlin/stepper.cpp +++ b/Marlin/stepper.cpp @@ -348,11 +348,21 @@ ISR(TIMER1_COMPA_vect) // Set the direction bits (X_AXIS=A_AXIS and Y_AXIS=B_AXIS for COREXY) if((out_bits & (1< -1 - bool x_min_endstop=(READ(X_MIN_PIN) != X_ENDSTOPS_INVERTING); - if(x_min_endstop && old_x_min_endstop && (current_block->steps_x > 0)) { - endstops_trigsteps[X_AXIS] = count_position[X_AXIS]; - endstop_x_hit=true; - step_events_completed = current_block->step_event_count; - } - old_x_min_endstop = x_min_endstop; - #endif + #ifdef DUAL_X_CARRIAGE + // with 2 x-carriages, endstops are only checked in the homing direction for the active extruder + if ((active_extruder == 0 && X_HOME_DIR == -1) || (active_extruder != 0 && X2_HOME_DIR == -1)) + #endif + { + #if defined(X_MIN_PIN) && X_MIN_PIN > -1 + bool x_min_endstop=(READ(X_MIN_PIN) != X_ENDSTOPS_INVERTING); + if(x_min_endstop && old_x_min_endstop && (current_block->steps_x > 0)) { + endstops_trigsteps[X_AXIS] = count_position[X_AXIS]; + endstop_x_hit=true; + step_events_completed = current_block->step_event_count; + } + old_x_min_endstop = x_min_endstop; + #endif + } } } else { // +direction CHECK_ENDSTOPS { - #if defined(X_MAX_PIN) && X_MAX_PIN > -1 - bool x_max_endstop=(READ(X_MAX_PIN) != X_ENDSTOPS_INVERTING); - if(x_max_endstop && old_x_max_endstop && (current_block->steps_x > 0)){ - endstops_trigsteps[X_AXIS] = count_position[X_AXIS]; - endstop_x_hit=true; - step_events_completed = current_block->step_event_count; - } - old_x_max_endstop = x_max_endstop; - #endif + #ifdef DUAL_X_CARRIAGE + // with 2 x-carriages, endstops are only checked in the homing direction for the active extruder + if ((active_extruder == 0 && X_HOME_DIR == 1) || (active_extruder != 0 && X2_HOME_DIR == 1)) + #endif + { + #if defined(X_MAX_PIN) && X_MAX_PIN > -1 + bool x_max_endstop=(READ(X_MAX_PIN) != X_ENDSTOPS_INVERTING); + if(x_max_endstop && old_x_max_endstop && (current_block->steps_x > 0)){ + endstops_trigsteps[X_AXIS] = count_position[X_AXIS]; + endstop_x_hit=true; + step_events_completed = current_block->step_event_count; + } + old_x_max_endstop = x_max_endstop; + #endif + } } } @@ -507,10 +529,20 @@ ISR(TIMER1_COMPA_vect) counter_x += current_block->steps_x; if (counter_x > 0) { - WRITE(X_STEP_PIN, !INVERT_X_STEP_PIN); + #ifdef DUAL_X_CARRIAGE + if (active_extruder != 0) + WRITE(X2_STEP_PIN,!INVERT_X_STEP_PIN); + else + #endif + WRITE(X_STEP_PIN, !INVERT_X_STEP_PIN); counter_x -= current_block->step_event_count; count_position[X_AXIS]+=count_direction[X_AXIS]; - WRITE(X_STEP_PIN, INVERT_X_STEP_PIN); + #ifdef DUAL_X_CARRIAGE + if (active_extruder != 0) + WRITE(X2_STEP_PIN,INVERT_X_STEP_PIN); + else + #endif + WRITE(X_STEP_PIN, INVERT_X_STEP_PIN); } counter_y += current_block->steps_y; @@ -685,6 +717,9 @@ void st_init() #if defined(X_DIR_PIN) && X_DIR_PIN > -1 SET_OUTPUT(X_DIR_PIN); #endif + #if defined(X2_DIR_PIN) && X2_DIR_PIN > -1 + SET_OUTPUT(X2_DIR_PIN); + #endif #if defined(Y_DIR_PIN) && Y_DIR_PIN > -1 SET_OUTPUT(Y_DIR_PIN); #endif @@ -711,6 +746,10 @@ void st_init() SET_OUTPUT(X_ENABLE_PIN); if(!X_ENABLE_ON) WRITE(X_ENABLE_PIN,HIGH); #endif + #if defined(X2_ENABLE_PIN) && X2_ENABLE_PIN > -1 + SET_OUTPUT(X2_ENABLE_PIN); + if(!X_ENABLE_ON) WRITE(X2_ENABLE_PIN,HIGH); + #endif #if defined(Y_ENABLE_PIN) && Y_ENABLE_PIN > -1 SET_OUTPUT(Y_ENABLE_PIN); if(!Y_ENABLE_ON) WRITE(Y_ENABLE_PIN,HIGH); @@ -788,6 +827,11 @@ void st_init() WRITE(X_STEP_PIN,INVERT_X_STEP_PIN); disable_x(); #endif + #if defined(X2_STEP_PIN) && (X2_STEP_PIN > -1) + SET_OUTPUT(X2_STEP_PIN); + WRITE(X2_STEP_PIN,INVERT_X_STEP_PIN); + disable_x(); + #endif #if defined(Y_STEP_PIN) && (Y_STEP_PIN > -1) SET_OUTPUT(Y_STEP_PIN); WRITE(Y_STEP_PIN,INVERT_Y_STEP_PIN); diff --git a/README.md b/README.md index f5792ea066..014871739a 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ Features: * Heater power reporting. Useful for PID monitoring. * PID tuning * CoreXY kinematics (www.corexy.com/theory.html) +* Delta kinematics +* Dual X-carriage support for multiple extruder systems * Configurable serial port to support connection of wireless adaptors. * Automatic operation of extruder/cold-end cooling fans based on nozzle temperature * RC Servo Support, specify angle or duration for continuous rotation servos.