Case-insensitive DOS-style path to a file or folder
*
* Example:
* M33 miscel~1/armchair/armcha~1.gco
*
* Output:
* /Miscellaneous/Armchair/Armchair.gcode
*/
inline void gcode_M33() {
card.printLongPath(current_command_args);
}
#endif
/**
* M928: Start SD Write
*/
inline void gcode_M928() {
card.openLogFile(current_command_args);
}
#endif // SDSUPPORT
/**
* M42: Change pin status via GCode
*/
inline void gcode_M42() {
if (code_seen('S')) {
int pin_status = code_value_short(),
pin_number = LED_PIN;
if (code_seen('P') && pin_status >= 0 && pin_status <= 255)
pin_number = code_value_short();
for (int8_t i = 0; i < (int8_t)(sizeof(sensitive_pins) / sizeof(*sensitive_pins)); i++) {
if (sensitive_pins[i] == pin_number) {
pin_number = -1;
break;
}
}
#if HAS_FAN
if (pin_number == FAN_PIN) fanSpeed = pin_status;
#endif
if (pin_number > -1) {
pinMode(pin_number, OUTPUT);
digitalWrite(pin_number, pin_status);
analogWrite(pin_number, pin_status);
}
} // code_seen('S')
}
#if defined(ENABLE_AUTO_BED_LEVELING) && defined(Z_PROBE_REPEATABILITY_TEST)
// This is redundant since the SanityCheck.h already checks for a valid Z_PROBE_PIN, but here for clarity.
#ifdef Z_PROBE_ENDSTOP
#if !HAS_Z_PROBE
#error You must define Z_PROBE_PIN to enable Z-Probe repeatability calculation.
#endif
#elif !HAS_Z_MIN
#error You must define Z_MIN_PIN to enable Z-Probe repeatability calculation.
#endif
/**
* M48: Z-Probe repeatability measurement function.
*
* Usage:
* M48
* P = Number of sampled points (4-50, default 10)
* X = Sample X position
* Y = Sample Y position
* V = Verbose level (0-4, default=1)
* E = Engage probe for each reading
* L = Number of legs of movement before probe
*
* This function assumes the bed has been homed. Specifically, that a G28 command
* as been issued prior to invoking the M48 Z-Probe repeatability measurement function.
* Any information generated by a prior G29 Bed leveling command will be lost and need to be
* regenerated.
*/
inline void gcode_M48() {
double sum = 0.0, mean = 0.0, sigma = 0.0, sample_set[50];
uint8_t verbose_level = 1, n_samples = 10, n_legs = 0;
if (code_seen('V') || code_seen('v')) {
verbose_level = code_value_short();
if (verbose_level < 0 || verbose_level > 4 ) {
SERIAL_PROTOCOLPGM("?Verbose Level not plausible (0-4).\n");
return;
}
}
if (verbose_level > 0)
SERIAL_PROTOCOLPGM("M48 Z-Probe Repeatability test\n");
if (code_seen('P') || code_seen('p')) {
n_samples = code_value_short();
if (n_samples < 4 || n_samples > 50) {
SERIAL_PROTOCOLPGM("?Sample size not plausible (4-50).\n");
return;
}
}
double X_current = st_get_position_mm(X_AXIS),
Y_current = st_get_position_mm(Y_AXIS),
Z_current = st_get_position_mm(Z_AXIS),
E_current = st_get_position_mm(E_AXIS),
X_probe_location = X_current, Y_probe_location = Y_current,
Z_start_location = Z_current + Z_RAISE_BEFORE_PROBING;
bool deploy_probe_for_each_reading = code_seen('E') || code_seen('e');
if (code_seen('X') || code_seen('x')) {
X_probe_location = code_value() - X_PROBE_OFFSET_FROM_EXTRUDER;
if (X_probe_location < X_MIN_POS || X_probe_location > X_MAX_POS) {
out_of_range_error(PSTR("X"));
return;
}
}
if (code_seen('Y') || code_seen('y')) {
Y_probe_location = code_value() - Y_PROBE_OFFSET_FROM_EXTRUDER;
if (Y_probe_location < Y_MIN_POS || Y_probe_location > Y_MAX_POS) {
out_of_range_error(PSTR("Y"));
return;
}
}
if (code_seen('L') || code_seen('l')) {
n_legs = code_value_short();
if (n_legs == 1) n_legs = 2;
if (n_legs < 0 || n_legs > 15) {
SERIAL_PROTOCOLPGM("?Number of legs in movement not plausible (0-15).\n");
return;
}
}
//
// Do all the preliminary setup work. First raise the probe.
//
st_synchronize();
plan_bed_level_matrix.set_to_identity();
plan_buffer_line(X_current, Y_current, Z_start_location, E_current, homing_feedrate[Z_AXIS] / 60, active_extruder);
st_synchronize();
//
// Now get everything to the specified probe point So we can safely do a probe to
// get us close to the bed. If the Z-Axis is far from the bed, we don't want to
// use that as a starting point for each probe.
//
if (verbose_level > 2)
SERIAL_PROTOCOLPGM("Positioning the probe...\n");
plan_buffer_line( X_probe_location, Y_probe_location, Z_start_location,
E_current,
homing_feedrate[X_AXIS]/60,
active_extruder);
st_synchronize();
current_position[X_AXIS] = X_current = st_get_position_mm(X_AXIS);
current_position[Y_AXIS] = Y_current = st_get_position_mm(Y_AXIS);
current_position[Z_AXIS] = Z_current = st_get_position_mm(Z_AXIS);
current_position[E_AXIS] = E_current = st_get_position_mm(E_AXIS);
//
// OK, do the initial probe to get us close to the bed.
// Then retrace the right amount and use that in subsequent probes
//
deploy_z_probe();
setup_for_endstop_move();
run_z_probe();
current_position[Z_AXIS] = Z_current = st_get_position_mm(Z_AXIS);
Z_start_location = st_get_position_mm(Z_AXIS) + Z_RAISE_BEFORE_PROBING;
plan_buffer_line( X_probe_location, Y_probe_location, Z_start_location,
E_current,
homing_feedrate[X_AXIS]/60,
active_extruder);
st_synchronize();
current_position[Z_AXIS] = Z_current = st_get_position_mm(Z_AXIS);
if (deploy_probe_for_each_reading) stow_z_probe();
for (uint8_t n=0; n < n_samples; n++) {
// Make sure we are at the probe location
do_blocking_move_to(X_probe_location, Y_probe_location, Z_start_location); // this also updates current_position
if (n_legs) {
millis_t ms = millis();
double radius = ms % (X_MAX_LENGTH / 4), // limit how far out to go
theta = RADIANS(ms % 360L);
float dir = (ms & 0x0001) ? 1 : -1; // clockwise or counter clockwise
//SERIAL_ECHOPAIR("starting radius: ",radius);
//SERIAL_ECHOPAIR(" theta: ",theta);
//SERIAL_ECHOPAIR(" direction: ",dir);
//SERIAL_EOL;
for (uint8_t l = 0; l < n_legs - 1; l++) {
ms = millis();
theta += RADIANS(dir * (ms % 20L));
radius += (ms % 10L) - 5L;
if (radius < 0.0) radius = -radius;
X_current = X_probe_location + cos(theta) * radius;
X_current = constrain(X_current, X_MIN_POS, X_MAX_POS);
Y_current = Y_probe_location + sin(theta) * radius;
Y_current = constrain(Y_current, Y_MIN_POS, Y_MAX_POS);
if (verbose_level > 3) {
SERIAL_ECHOPAIR("x: ", X_current);
SERIAL_ECHOPAIR("y: ", Y_current);
SERIAL_EOL;
}
do_blocking_move_to(X_current, Y_current, Z_current); // this also updates current_position
} // n_legs loop
// Go back to the probe location
do_blocking_move_to(X_probe_location, Y_probe_location, Z_start_location); // this also updates current_position
} // n_legs
if (deploy_probe_for_each_reading) {
deploy_z_probe();
delay(1000);
}
setup_for_endstop_move();
run_z_probe();
sample_set[n] = current_position[Z_AXIS];
//
// Get the current mean for the data points we have so far
//
sum = 0.0;
for (uint8_t j = 0; j <= n; j++) sum += sample_set[j];
mean = sum / (n + 1);
//
// Now, use that mean to calculate the standard deviation for the
// data points we have so far
//
sum = 0.0;
for (uint8_t j = 0; j <= n; j++) {
float ss = sample_set[j] - mean;
sum += ss * ss;
}
sigma = sqrt(sum / (n + 1));
if (verbose_level > 1) {
SERIAL_PROTOCOL(n+1);
SERIAL_PROTOCOLPGM(" of ");
SERIAL_PROTOCOL((int)n_samples);
SERIAL_PROTOCOLPGM(" z: ");
SERIAL_PROTOCOL_F(current_position[Z_AXIS], 6);
if (verbose_level > 2) {
SERIAL_PROTOCOLPGM(" mean: ");
SERIAL_PROTOCOL_F(mean,6);
SERIAL_PROTOCOLPGM(" sigma: ");
SERIAL_PROTOCOL_F(sigma,6);
}
}
if (verbose_level > 0) SERIAL_EOL;
plan_buffer_line(X_probe_location, Y_probe_location, Z_start_location, current_position[E_AXIS], homing_feedrate[Z_AXIS]/60, active_extruder);
st_synchronize();
// Stow between
if (deploy_probe_for_each_reading) {
stow_z_probe();
delay(1000);
}
}
// Stow after
if (!deploy_probe_for_each_reading) {
stow_z_probe();
delay(1000);
}
clean_up_after_endstop_move();
if (verbose_level > 0) {
SERIAL_PROTOCOLPGM("Mean: ");
SERIAL_PROTOCOL_F(mean, 6);
SERIAL_EOL;
}
SERIAL_PROTOCOLPGM("Standard Deviation: ");
SERIAL_PROTOCOL_F(sigma, 6);
SERIAL_EOL; SERIAL_EOL;
}
#endif // ENABLE_AUTO_BED_LEVELING && Z_PROBE_REPEATABILITY_TEST
/**
* M104: Set hot end temperature
*/
inline void gcode_M104() {
if (setTargetedHotend(104)) return;
if (marlin_debug_flags & DEBUG_DRYRUN) return;
if (code_seen('S')) {
float temp = code_value();
setTargetHotend(temp, target_extruder);
#ifdef DUAL_X_CARRIAGE
if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && target_extruder == 0)
setTargetHotend1(temp == 0.0 ? 0.0 : temp + duplicate_extruder_temp_offset);
#endif
}
}
/**
* M105: Read hot end and bed temperature
*/
inline void gcode_M105() {
if (setTargetedHotend(105)) return;
#if HAS_TEMP_0 || HAS_TEMP_BED || defined(HEATER_0_USES_MAX6675)
SERIAL_PROTOCOLPGM(MSG_OK);
#if HAS_TEMP_0 || defined(HEATER_0_USES_MAX6675)
SERIAL_PROTOCOLPGM(" T:");
SERIAL_PROTOCOL_F(degHotend(target_extruder), 1);
SERIAL_PROTOCOLPGM(" /");
SERIAL_PROTOCOL_F(degTargetHotend(target_extruder), 1);
#endif
#if HAS_TEMP_BED
SERIAL_PROTOCOLPGM(" B:");
SERIAL_PROTOCOL_F(degBed(), 1);
SERIAL_PROTOCOLPGM(" /");
SERIAL_PROTOCOL_F(degTargetBed(), 1);
#endif
for (int8_t e = 0; e < EXTRUDERS; ++e) {
SERIAL_PROTOCOLPGM(" T");
SERIAL_PROTOCOL(e);
SERIAL_PROTOCOLCHAR(':');
SERIAL_PROTOCOL_F(degHotend(e), 1);
SERIAL_PROTOCOLPGM(" /");
SERIAL_PROTOCOL_F(degTargetHotend(e), 1);
}
#else // !HAS_TEMP_0 && !HAS_TEMP_BED
SERIAL_ERROR_START;
SERIAL_ERRORLNPGM(MSG_ERR_NO_THERMISTORS);
#endif
SERIAL_PROTOCOLPGM(" @:");
#ifdef EXTRUDER_WATTS
SERIAL_PROTOCOL((EXTRUDER_WATTS * getHeaterPower(target_extruder))/127);
SERIAL_PROTOCOLCHAR('W');
#else
SERIAL_PROTOCOL(getHeaterPower(target_extruder));
#endif
SERIAL_PROTOCOLPGM(" B@:");
#ifdef BED_WATTS
SERIAL_PROTOCOL((BED_WATTS * getHeaterPower(-1))/127);
SERIAL_PROTOCOLCHAR('W');
#else
SERIAL_PROTOCOL(getHeaterPower(-1));
#endif
#ifdef SHOW_TEMP_ADC_VALUES
#if HAS_TEMP_BED
SERIAL_PROTOCOLPGM(" ADC B:");
SERIAL_PROTOCOL_F(degBed(),1);
SERIAL_PROTOCOLPGM("C->");
SERIAL_PROTOCOL_F(rawBedTemp()/OVERSAMPLENR,0);
#endif
for (int8_t cur_extruder = 0; cur_extruder < EXTRUDERS; ++cur_extruder) {
SERIAL_PROTOCOLPGM(" T");
SERIAL_PROTOCOL(cur_extruder);
SERIAL_PROTOCOLCHAR(':');
SERIAL_PROTOCOL_F(degHotend(cur_extruder),1);
SERIAL_PROTOCOLPGM("C->");
SERIAL_PROTOCOL_F(rawHotendTemp(cur_extruder)/OVERSAMPLENR,0);
}
#endif
SERIAL_EOL;
}
#if HAS_FAN
/**
* M106: Set Fan Speed
*/
inline void gcode_M106() { fanSpeed = code_seen('S') ? constrain(code_value_short(), 0, 255) : 255; }
/**
* M107: Fan Off
*/
inline void gcode_M107() { fanSpeed = 0; }
#endif // HAS_FAN
/**
* M109: Wait for extruder(s) to reach temperature
*/
inline void gcode_M109() {
if (setTargetedHotend(109)) return;
if (marlin_debug_flags & DEBUG_DRYRUN) return;
LCD_MESSAGEPGM(MSG_HEATING);
no_wait_for_cooling = code_seen('S');
if (no_wait_for_cooling || code_seen('R')) {
float temp = code_value();
setTargetHotend(temp, target_extruder);
#ifdef DUAL_X_CARRIAGE
if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && target_extruder == 0)
setTargetHotend1(temp == 0.0 ? 0.0 : temp + duplicate_extruder_temp_offset);
#endif
}
#ifdef AUTOTEMP
autotemp_enabled = code_seen('F');
if (autotemp_enabled) autotemp_factor = code_value();
if (code_seen('S')) autotemp_min = code_value();
if (code_seen('B')) autotemp_max = code_value();
#endif
millis_t temp_ms = millis();
/* See if we are heating up or cooling down */
target_direction = isHeatingHotend(target_extruder); // true if heating, false if cooling
cancel_heatup = false;
#ifdef TEMP_RESIDENCY_TIME
long residency_start_ms = -1;
/* continue to loop until we have reached the target temp
_and_ until TEMP_RESIDENCY_TIME hasn't passed since we reached it */
while((!cancel_heatup)&&((residency_start_ms == -1) ||
(residency_start_ms >= 0 && (((unsigned int) (millis() - residency_start_ms)) < (TEMP_RESIDENCY_TIME * 1000UL)))) )
#else
while ( target_direction ? (isHeatingHotend(target_extruder)) : (isCoolingHotend(target_extruder)&&(no_wait_for_cooling==false)) )
#endif //TEMP_RESIDENCY_TIME
{ // while loop
if (millis() > temp_ms + 1000UL) { //Print temp & remaining time every 1s while waiting
SERIAL_PROTOCOLPGM("T:");
SERIAL_PROTOCOL_F(degHotend(target_extruder),1);
SERIAL_PROTOCOLPGM(" E:");
SERIAL_PROTOCOL((int)target_extruder);
#ifdef TEMP_RESIDENCY_TIME
SERIAL_PROTOCOLPGM(" W:");
if (residency_start_ms > -1) {
temp_ms = ((TEMP_RESIDENCY_TIME * 1000UL) - (millis() - residency_start_ms)) / 1000UL;
SERIAL_PROTOCOLLN(temp_ms);
}
else {
SERIAL_PROTOCOLLNPGM("?");
}
#else
SERIAL_EOL;
#endif
temp_ms = millis();
}
idle();
#ifdef TEMP_RESIDENCY_TIME
// start/restart the TEMP_RESIDENCY_TIME timer whenever we reach target temp for the first time
// or when current temp falls outside the hysteresis after target temp was reached
if ((residency_start_ms == -1 && target_direction && (degHotend(target_extruder) >= (degTargetHotend(target_extruder)-TEMP_WINDOW))) ||
(residency_start_ms == -1 && !target_direction && (degHotend(target_extruder) <= (degTargetHotend(target_extruder)+TEMP_WINDOW))) ||
(residency_start_ms > -1 && labs(degHotend(target_extruder) - degTargetHotend(target_extruder)) > TEMP_HYSTERESIS) )
{
residency_start_ms = millis();
}
#endif //TEMP_RESIDENCY_TIME
}
LCD_MESSAGEPGM(MSG_HEATING_COMPLETE);
refresh_cmd_timeout();
print_job_start_ms = previous_cmd_ms;
}
#if HAS_TEMP_BED
/**
* M190: Sxxx Wait for bed current temp to reach target temp. Waits only when heating
* Rxxx Wait for bed current temp to reach target temp. Waits when heating and cooling
*/
inline void gcode_M190() {
if (marlin_debug_flags & DEBUG_DRYRUN) return;
LCD_MESSAGEPGM(MSG_BED_HEATING);
no_wait_for_cooling = code_seen('S');
if (no_wait_for_cooling || code_seen('R'))
setTargetBed(code_value());
millis_t temp_ms = millis();
cancel_heatup = false;
target_direction = isHeatingBed(); // true if heating, false if cooling
while ((target_direction && !cancel_heatup) ? isHeatingBed() : isCoolingBed() && !no_wait_for_cooling) {
millis_t ms = millis();
if (ms > temp_ms + 1000UL) { //Print Temp Reading every 1 second while heating up.
temp_ms = ms;
float tt = degHotend(active_extruder);
SERIAL_PROTOCOLPGM("T:");
SERIAL_PROTOCOL(tt);
SERIAL_PROTOCOLPGM(" E:");
SERIAL_PROTOCOL((int)active_extruder);
SERIAL_PROTOCOLPGM(" B:");
SERIAL_PROTOCOL_F(degBed(), 1);
SERIAL_EOL;
}
idle();
}
LCD_MESSAGEPGM(MSG_BED_DONE);
refresh_cmd_timeout();
}
#endif // HAS_TEMP_BED
/**
* M111: Set the debug level
*/
inline void gcode_M111() {
marlin_debug_flags = code_seen('S') ? code_value_short() : DEBUG_INFO|DEBUG_COMMUNICATION;
if (marlin_debug_flags & DEBUG_ECHO) {
SERIAL_ECHO_START;
SERIAL_ECHOLNPGM(MSG_DEBUG_ECHO);
}
// FOR MOMENT NOT ACTIVE
//if (marlin_debug_flags & DEBUG_INFO) SERIAL_ECHOLNPGM(MSG_DEBUG_INFO);
//if (marlin_debug_flags & DEBUG_ERRORS) SERIAL_ECHOLNPGM(MSG_DEBUG_ERRORS);
if (marlin_debug_flags & DEBUG_DRYRUN) {
SERIAL_ECHO_START;
SERIAL_ECHOLNPGM(MSG_DEBUG_DRYRUN);
disable_all_heaters();
}
}
/**
* M112: Emergency Stop
*/
inline void gcode_M112() { kill(PSTR(MSG_KILLED)); }
#ifdef BARICUDA
#if HAS_HEATER_1
/**
* M126: Heater 1 valve open
*/
inline void gcode_M126() { ValvePressure = code_seen('S') ? constrain(code_value(), 0, 255) : 255; }
/**
* M127: Heater 1 valve close
*/
inline void gcode_M127() { ValvePressure = 0; }
#endif
#if HAS_HEATER_2
/**
* M128: Heater 2 valve open
*/
inline void gcode_M128() { EtoPPressure = code_seen('S') ? constrain(code_value(), 0, 255) : 255; }
/**
* M129: Heater 2 valve close
*/
inline void gcode_M129() { EtoPPressure = 0; }
#endif
#endif //BARICUDA
/**
* M140: Set bed temperature
*/
inline void gcode_M140() {
if (marlin_debug_flags & DEBUG_DRYRUN) return;
if (code_seen('S')) setTargetBed(code_value());
}
#ifdef ULTIPANEL
/**
* M145: Set the heatup state for a material in the LCD menu
* S (0=PLA, 1=ABS)
* H
* B
* F
*/
inline void gcode_M145() {
uint8_t material = code_seen('S') ? code_value_short() : 0;
if (material < 0 || material > 1) {
SERIAL_ERROR_START;
SERIAL_ERRORLNPGM(MSG_ERR_MATERIAL_INDEX);
}
else {
int v;
switch (material) {
case 0:
if (code_seen('H')) {
v = code_value_short();
plaPreheatHotendTemp = constrain(v, EXTRUDE_MINTEMP, HEATER_0_MAXTEMP - 15);
}
if (code_seen('F')) {
v = code_value_short();
plaPreheatFanSpeed = constrain(v, 0, 255);
}
#if TEMP_SENSOR_BED != 0
if (code_seen('B')) {
v = code_value_short();
plaPreheatHPBTemp = constrain(v, BED_MINTEMP, BED_MAXTEMP - 15);
}
#endif
break;
case 1:
if (code_seen('H')) {
v = code_value_short();
absPreheatHotendTemp = constrain(v, EXTRUDE_MINTEMP, HEATER_0_MAXTEMP - 15);
}
if (code_seen('F')) {
v = code_value_short();
absPreheatFanSpeed = constrain(v, 0, 255);
}
#if TEMP_SENSOR_BED != 0
if (code_seen('B')) {
v = code_value_short();
absPreheatHPBTemp = constrain(v, BED_MINTEMP, BED_MAXTEMP - 15);
}
#endif
break;
}
}
}
#endif
#if HAS_POWER_SWITCH
/**
* M80: Turn on Power Supply
*/
inline void gcode_M80() {
OUT_WRITE(PS_ON_PIN, PS_ON_AWAKE); //GND
// If you have a switch on suicide pin, this is useful
// if you want to start another print with suicide feature after
// a print without suicide...
#if HAS_SUICIDE
OUT_WRITE(SUICIDE_PIN, HIGH);
#endif
#ifdef ULTIPANEL
powersupply = true;
LCD_MESSAGEPGM(WELCOME_MSG);
lcd_update();
#endif
}
#endif // HAS_POWER_SWITCH
/**
* M81: Turn off Power, including Power Supply, if there is one.
*
* This code should ALWAYS be available for EMERGENCY SHUTDOWN!
*/
inline void gcode_M81() {
disable_all_heaters();
st_synchronize();
disable_e0();
disable_e1();
disable_e2();
disable_e3();
finishAndDisableSteppers();
fanSpeed = 0;
delay(1000); // Wait 1 second before switching off
#if HAS_SUICIDE
st_synchronize();
suicide();
#elif HAS_POWER_SWITCH
OUT_WRITE(PS_ON_PIN, PS_ON_ASLEEP);
#endif
#ifdef ULTIPANEL
#if HAS_POWER_SWITCH
powersupply = false;
#endif
LCD_MESSAGEPGM(MACHINE_NAME " " MSG_OFF ".");
lcd_update();
#endif
}
/**
* M82: Set E codes absolute (default)
*/
inline void gcode_M82() { axis_relative_modes[E_AXIS] = false; }
/**
* M83: Set E codes relative while in Absolute Coordinates (G90) mode
*/
inline void gcode_M83() { axis_relative_modes[E_AXIS] = true; }
/**
* M18, M84: Disable all stepper motors
*/
inline void gcode_M18_M84() {
if (code_seen('S')) {
stepper_inactive_time = code_value() * 1000;
}
else {
bool all_axis = !((code_seen(axis_codes[X_AXIS])) || (code_seen(axis_codes[Y_AXIS])) || (code_seen(axis_codes[Z_AXIS]))|| (code_seen(axis_codes[E_AXIS])));
if (all_axis) {
st_synchronize();
disable_e0();
disable_e1();
disable_e2();
disable_e3();
finishAndDisableSteppers();
}
else {
st_synchronize();
if (code_seen('X')) disable_x();
if (code_seen('Y')) disable_y();
if (code_seen('Z')) disable_z();
#if ((E0_ENABLE_PIN != X_ENABLE_PIN) && (E1_ENABLE_PIN != Y_ENABLE_PIN)) // Only enable on boards that have seperate ENABLE_PINS
if (code_seen('E')) {
disable_e0();
disable_e1();
disable_e2();
disable_e3();
}
#endif
}
}
}
/**
* M85: Set inactivity shutdown timer with parameter S. To disable set zero (default)
*/
inline void gcode_M85() {
if (code_seen('S')) max_inactive_time = code_value() * 1000;
}
/**
* M92: Set axis steps-per-unit for one or more axes, X, Y, Z, and E.
* (Follows the same syntax as G92)
*/
inline void gcode_M92() {
for(int8_t i=0; i < NUM_AXIS; i++) {
if (code_seen(axis_codes[i])) {
if (i == E_AXIS) {
float value = code_value();
if (value < 20.0) {
float factor = axis_steps_per_unit[i] / value; // increase e constants if M92 E14 is given for netfab.
max_e_jerk *= factor;
max_feedrate[i] *= factor;
axis_steps_per_sqr_second[i] *= factor;
}
axis_steps_per_unit[i] = value;
}
else {
axis_steps_per_unit[i] = code_value();
}
}
}
}
/**
* M114: Output current position to serial port
*/
inline void gcode_M114() {
SERIAL_PROTOCOLPGM("X:");
SERIAL_PROTOCOL(current_position[X_AXIS]);
SERIAL_PROTOCOLPGM(" Y:");
SERIAL_PROTOCOL(current_position[Y_AXIS]);
SERIAL_PROTOCOLPGM(" Z:");
SERIAL_PROTOCOL(current_position[Z_AXIS]);
SERIAL_PROTOCOLPGM(" E:");
SERIAL_PROTOCOL(current_position[E_AXIS]);
SERIAL_PROTOCOLPGM(MSG_COUNT_X);
SERIAL_PROTOCOL(st_get_position_mm(X_AXIS));
SERIAL_PROTOCOLPGM(" Y:");
SERIAL_PROTOCOL(st_get_position_mm(Y_AXIS));
SERIAL_PROTOCOLPGM(" Z:");
SERIAL_PROTOCOL(st_get_position_mm(Z_AXIS));
SERIAL_EOL;
#ifdef SCARA
SERIAL_PROTOCOLPGM("SCARA Theta:");
SERIAL_PROTOCOL(delta[X_AXIS]);
SERIAL_PROTOCOLPGM(" Psi+Theta:");
SERIAL_PROTOCOL(delta[Y_AXIS]);
SERIAL_EOL;
SERIAL_PROTOCOLPGM("SCARA Cal - Theta:");
SERIAL_PROTOCOL(delta[X_AXIS]+home_offset[X_AXIS]);
SERIAL_PROTOCOLPGM(" Psi+Theta (90):");
SERIAL_PROTOCOL(delta[Y_AXIS]-delta[X_AXIS]-90+home_offset[Y_AXIS]);
SERIAL_EOL;
SERIAL_PROTOCOLPGM("SCARA step Cal - Theta:");
SERIAL_PROTOCOL(delta[X_AXIS]/90*axis_steps_per_unit[X_AXIS]);
SERIAL_PROTOCOLPGM(" Psi+Theta:");
SERIAL_PROTOCOL((delta[Y_AXIS]-delta[X_AXIS])/90*axis_steps_per_unit[Y_AXIS]);
SERIAL_EOL; SERIAL_EOL;
#endif
}
/**
* M115: Capabilities string
*/
inline void gcode_M115() {
SERIAL_PROTOCOLPGM(MSG_M115_REPORT);
}
/**
* M117: Set LCD Status Message
*/
inline void gcode_M117() {
lcd_setstatus(current_command_args);
}
/**
* M119: Output endstop states to serial output
*/
inline void gcode_M119() {
SERIAL_PROTOCOLLN(MSG_M119_REPORT);
#if HAS_X_MIN
SERIAL_PROTOCOLPGM(MSG_X_MIN);
SERIAL_PROTOCOLLN(((READ(X_MIN_PIN)^X_MIN_ENDSTOP_INVERTING)?MSG_ENDSTOP_HIT:MSG_ENDSTOP_OPEN));
#endif
#if HAS_X_MAX
SERIAL_PROTOCOLPGM(MSG_X_MAX);
SERIAL_PROTOCOLLN(((READ(X_MAX_PIN)^X_MAX_ENDSTOP_INVERTING)?MSG_ENDSTOP_HIT:MSG_ENDSTOP_OPEN));
#endif
#if HAS_Y_MIN
SERIAL_PROTOCOLPGM(MSG_Y_MIN);
SERIAL_PROTOCOLLN(((READ(Y_MIN_PIN)^Y_MIN_ENDSTOP_INVERTING)?MSG_ENDSTOP_HIT:MSG_ENDSTOP_OPEN));
#endif
#if HAS_Y_MAX
SERIAL_PROTOCOLPGM(MSG_Y_MAX);
SERIAL_PROTOCOLLN(((READ(Y_MAX_PIN)^Y_MAX_ENDSTOP_INVERTING)?MSG_ENDSTOP_HIT:MSG_ENDSTOP_OPEN));
#endif
#if HAS_Z_MIN
SERIAL_PROTOCOLPGM(MSG_Z_MIN);
SERIAL_PROTOCOLLN(((READ(Z_MIN_PIN)^Z_MIN_ENDSTOP_INVERTING)?MSG_ENDSTOP_HIT:MSG_ENDSTOP_OPEN));
#endif
#if HAS_Z_MAX
SERIAL_PROTOCOLPGM(MSG_Z_MAX);
SERIAL_PROTOCOLLN(((READ(Z_MAX_PIN)^Z_MAX_ENDSTOP_INVERTING)?MSG_ENDSTOP_HIT:MSG_ENDSTOP_OPEN));
#endif
#if HAS_Z2_MAX
SERIAL_PROTOCOLPGM(MSG_Z2_MAX);
SERIAL_PROTOCOLLN(((READ(Z2_MAX_PIN)^Z2_MAX_ENDSTOP_INVERTING)?MSG_ENDSTOP_HIT:MSG_ENDSTOP_OPEN));
#endif
#if HAS_Z_PROBE
SERIAL_PROTOCOLPGM(MSG_Z_PROBE);
SERIAL_PROTOCOLLN(((READ(Z_PROBE_PIN)^Z_PROBE_ENDSTOP_INVERTING)?MSG_ENDSTOP_HIT:MSG_ENDSTOP_OPEN));
#endif
}
/**
* M120: Enable endstops
*/
inline void gcode_M120() { enable_endstops(true); }
/**
* M121: Disable endstops
*/
inline void gcode_M121() { enable_endstops(false); }
#ifdef BLINKM
/**
* M150: Set Status LED Color - Use R-U-B for R-G-B
*/
inline void gcode_M150() {
SendColors(
code_seen('R') ? (byte)code_value_short() : 0,
code_seen('U') ? (byte)code_value_short() : 0,
code_seen('B') ? (byte)code_value_short() : 0
);
}
#endif // BLINKM
/**
* M200: Set filament diameter and set E axis units to cubic millimeters
*
* T - Optional extruder number. Current extruder if omitted.
* D - Diameter of the filament. Use "D0" to set units back to millimeters.
*/
inline void gcode_M200() {
if (setTargetedHotend(200)) return;
if (code_seen('D')) {
float diameter = code_value();
// 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
volumetric_enabled = (diameter != 0.0);
if (volumetric_enabled) {
filament_size[target_extruder] = diameter;
// make sure all extruders have some sane value for the filament size
for (int i=0; i 1
if (code_seen('W')) retract_length_swap = code_value();
#endif
}
/**
* M208: Set firmware un-retraction values
*
* S[+mm] retract_recover_length (in addition to M207 S*)
* W[+mm] retract_recover_length_swap (multi-extruder)
* F[mm/min] retract_recover_feedrate
*/
inline void gcode_M208() {
if (code_seen('S')) retract_recover_length = code_value();
if (code_seen('F')) retract_recover_feedrate = code_value() / 60;
#if EXTRUDERS > 1
if (code_seen('W')) retract_recover_length_swap = code_value();
#endif
}
/**
* M209: Enable automatic retract (M209 S1)
* detect if the slicer did not support G10/11: every normal extrude-only move will be classified as retract depending on the direction.
*/
inline void gcode_M209() {
if (code_seen('S')) {
int t = code_value_short();
switch(t) {
case 0:
autoretract_enabled = false;
break;
case 1:
autoretract_enabled = true;
break;
default:
unknown_command_error();
return;
}
for (int i=0; i 1
/**
* M218 - set hotend offset (in mm), T X Y
*/
inline void gcode_M218() {
if (setTargetedHotend(218)) return;
if (code_seen('X')) extruder_offset[X_AXIS][target_extruder] = code_value();
if (code_seen('Y')) extruder_offset[Y_AXIS][target_extruder] = code_value();
#ifdef DUAL_X_CARRIAGE
if (code_seen('Z')) extruder_offset[Z_AXIS][target_extruder] = code_value();
#endif
SERIAL_ECHO_START;
SERIAL_ECHOPGM(MSG_HOTEND_OFFSET);
for (int e = 0; e < EXTRUDERS; e++) {
SERIAL_CHAR(' ');
SERIAL_ECHO(extruder_offset[X_AXIS][e]);
SERIAL_CHAR(',');
SERIAL_ECHO(extruder_offset[Y_AXIS][e]);
#ifdef DUAL_X_CARRIAGE
SERIAL_CHAR(',');
SERIAL_ECHO(extruder_offset[Z_AXIS][e]);
#endif
}
SERIAL_EOL;
}
#endif // EXTRUDERS > 1
/**
* M220: Set speed percentage factor, aka "Feed Rate" (M220 S95)
*/
inline void gcode_M220() {
if (code_seen('S')) feedrate_multiplier = code_value();
}
/**
* M221: Set extrusion percentage (M221 T0 S95)
*/
inline void gcode_M221() {
if (code_seen('S')) {
int sval = code_value();
if (code_seen('T')) {
if (setTargetedHotend(221)) return;
extruder_multiplier[target_extruder] = sval;
}
else {
extruder_multiplier[active_extruder] = sval;
}
}
}
/**
* M226: Wait until the specified pin reaches the state required (M226 P S)
*/
inline void gcode_M226() {
if (code_seen('P')) {
int pin_number = code_value();
int pin_state = code_seen('S') ? code_value() : -1; // required pin state - default is inverted
if (pin_state >= -1 && pin_state <= 1) {
for (int8_t i = 0; i < (int8_t)(sizeof(sensitive_pins)/sizeof(*sensitive_pins)); i++) {
if (sensitive_pins[i] == pin_number) {
pin_number = -1;
break;
}
}
if (pin_number > -1) {
int target = LOW;
st_synchronize();
pinMode(pin_number, INPUT);
switch(pin_state){
case 1:
target = HIGH;
break;
case 0:
target = LOW;
break;
case -1:
target = !digitalRead(pin_number);
break;
}
while (digitalRead(pin_number) != target) idle();
} // pin_number > -1
} // pin_state -1 0 1
} // code_seen('P')
}
#if NUM_SERVOS > 0
/**
* M280: Get or set servo position. P S
*/
inline void gcode_M280() {
int servo_index = code_seen('P') ? code_value_short() : -1;
int servo_position = 0;
if (code_seen('S')) {
servo_position = code_value_short();
if (servo_index >= 0 && servo_index < NUM_SERVOS) {
Servo *srv = &servo[servo_index];
#if SERVO_LEVELING
srv->attach(0);
#endif
srv->write(servo_position);
#if SERVO_LEVELING
delay(PROBE_SERVO_DEACTIVATION_DELAY);
srv->detach();
#endif
}
else {
SERIAL_ECHO_START;
SERIAL_ECHO("Servo ");
SERIAL_ECHO(servo_index);
SERIAL_ECHOLN(" out of range");
}
}
else if (servo_index >= 0) {
SERIAL_PROTOCOL(MSG_OK);
SERIAL_PROTOCOL(" Servo ");
SERIAL_PROTOCOL(servo_index);
SERIAL_PROTOCOL(": ");
SERIAL_PROTOCOL(servo[servo_index].read());
SERIAL_EOL;
}
}
#endif // NUM_SERVOS > 0
#if HAS_BUZZER
/**
* M300: Play beep sound S P
*/
inline void gcode_M300() {
uint16_t beepS = code_seen('S') ? code_value_short() : 110;
uint32_t beepP = code_seen('P') ? code_value_long() : 1000;
if (beepP > 5000) beepP = 5000; // limit to 5 seconds
buzz(beepP, beepS);
}
#endif // HAS_BUZZER
#ifdef PIDTEMP
/**
* M301: Set PID parameters P I D (and optionally C)
*/
inline void gcode_M301() {
// multi-extruder PID patch: M301 updates or prints a single extruder's PID values
// default behaviour (omitting E parameter) is to update for extruder 0 only
int e = code_seen('E') ? code_value() : 0; // extruder being updated
if (e < EXTRUDERS) { // catch bad input value
if (code_seen('P')) PID_PARAM(Kp, e) = code_value();
if (code_seen('I')) PID_PARAM(Ki, e) = scalePID_i(code_value());
if (code_seen('D')) PID_PARAM(Kd, e) = scalePID_d(code_value());
#ifdef PID_ADD_EXTRUSION_RATE
if (code_seen('C')) PID_PARAM(Kc, e) = code_value();
#endif
updatePID();
SERIAL_PROTOCOL(MSG_OK);
#ifdef PID_PARAMS_PER_EXTRUDER
SERIAL_PROTOCOL(" e:"); // specify extruder in serial output
SERIAL_PROTOCOL(e);
#endif // PID_PARAMS_PER_EXTRUDER
SERIAL_PROTOCOL(" p:");
SERIAL_PROTOCOL(PID_PARAM(Kp, e));
SERIAL_PROTOCOL(" i:");
SERIAL_PROTOCOL(unscalePID_i(PID_PARAM(Ki, e)));
SERIAL_PROTOCOL(" d:");
SERIAL_PROTOCOL(unscalePID_d(PID_PARAM(Kd, e)));
#ifdef PID_ADD_EXTRUSION_RATE
SERIAL_PROTOCOL(" c:");
//Kc does not have scaling applied above, or in resetting defaults
SERIAL_PROTOCOL(PID_PARAM(Kc, e));
#endif
SERIAL_EOL;
}
else {
SERIAL_ECHO_START;
SERIAL_ECHOLN(MSG_INVALID_EXTRUDER);
}
}
#endif // PIDTEMP
#ifdef PIDTEMPBED
inline void gcode_M304() {
if (code_seen('P')) bedKp = code_value();
if (code_seen('I')) bedKi = scalePID_i(code_value());
if (code_seen('D')) bedKd = scalePID_d(code_value());
updatePID();
SERIAL_PROTOCOL(MSG_OK);
SERIAL_PROTOCOL(" p:");
SERIAL_PROTOCOL(bedKp);
SERIAL_PROTOCOL(" i:");
SERIAL_PROTOCOL(unscalePID_i(bedKi));
SERIAL_PROTOCOL(" d:");
SERIAL_PROTOCOL(unscalePID_d(bedKd));
SERIAL_EOL;
}
#endif // PIDTEMPBED
#if defined(CHDK) || HAS_PHOTOGRAPH
/**
* M240: Trigger a camera by emulating a Canon RC-1
* See http://www.doc-diy.net/photo/rc-1_hacked/
*/
inline void gcode_M240() {
#ifdef CHDK
OUT_WRITE(CHDK, HIGH);
chdkHigh = millis();
chdkActive = true;
#elif HAS_PHOTOGRAPH
const uint8_t NUM_PULSES = 16;
const float PULSE_LENGTH = 0.01524;
for (int i = 0; i < NUM_PULSES; i++) {
WRITE(PHOTOGRAPH_PIN, HIGH);
_delay_ms(PULSE_LENGTH);
WRITE(PHOTOGRAPH_PIN, LOW);
_delay_ms(PULSE_LENGTH);
}
delay(7.33);
for (int i = 0; i < NUM_PULSES; i++) {
WRITE(PHOTOGRAPH_PIN, HIGH);
_delay_ms(PULSE_LENGTH);
WRITE(PHOTOGRAPH_PIN, LOW);
_delay_ms(PULSE_LENGTH);
}
#endif // !CHDK && HAS_PHOTOGRAPH
}
#endif // CHDK || PHOTOGRAPH_PIN
#ifdef HAS_LCD_CONTRAST
/**
* M250: Read and optionally set the LCD contrast
*/
inline void gcode_M250() {
if (code_seen('C')) lcd_setcontrast(code_value_short() & 0x3F);
SERIAL_PROTOCOLPGM("lcd contrast value: ");
SERIAL_PROTOCOL(lcd_contrast);
SERIAL_EOL;
}
#endif // HAS_LCD_CONTRAST
#ifdef PREVENT_DANGEROUS_EXTRUDE
void set_extrude_min_temp(float temp) { extrude_min_temp = temp; }
/**
* M302: Allow cold extrudes, or set the minimum extrude S.
*/
inline void gcode_M302() {
set_extrude_min_temp(code_seen('S') ? code_value() : 0);
}
#endif // PREVENT_DANGEROUS_EXTRUDE
/**
* M303: PID relay autotune
* S sets the target temperature. (default target temperature = 150C)
* E (-1 for the bed)
* C
*/
inline void gcode_M303() {
int e = code_seen('E') ? code_value_short() : 0;
int c = code_seen('C') ? code_value_short() : 5;
float temp = code_seen('S') ? code_value() : (e < 0 ? 70.0 : 150.0);
PID_autotune(temp, e, c);
}
#ifdef SCARA
bool SCARA_move_to_cal(uint8_t delta_x, uint8_t delta_y) {
//SoftEndsEnabled = false; // Ignore soft endstops during calibration
//SERIAL_ECHOLN(" Soft endstops disabled ");
if (IsRunning()) {
//gcode_get_destination(); // For X Y Z E F
delta[X_AXIS] = delta_x;
delta[Y_AXIS] = delta_y;
calculate_SCARA_forward_Transform(delta);
destination[X_AXIS] = delta[X_AXIS]/axis_scaling[X_AXIS];
destination[Y_AXIS] = delta[Y_AXIS]/axis_scaling[Y_AXIS];
prepare_move();
//ok_to_send();
return true;
}
return false;
}
/**
* M360: SCARA calibration: Move to cal-position ThetaA (0 deg calibration)
*/
inline bool gcode_M360() {
SERIAL_ECHOLN(" Cal: Theta 0 ");
return SCARA_move_to_cal(0, 120);
}
/**
* M361: SCARA calibration: Move to cal-position ThetaB (90 deg calibration - steps per degree)
*/
inline bool gcode_M361() {
SERIAL_ECHOLN(" Cal: Theta 90 ");
return SCARA_move_to_cal(90, 130);
}
/**
* M362: SCARA calibration: Move to cal-position PsiA (0 deg calibration)
*/
inline bool gcode_M362() {
SERIAL_ECHOLN(" Cal: Psi 0 ");
return SCARA_move_to_cal(60, 180);
}
/**
* M363: SCARA calibration: Move to cal-position PsiB (90 deg calibration - steps per degree)
*/
inline bool gcode_M363() {
SERIAL_ECHOLN(" Cal: Psi 90 ");
return SCARA_move_to_cal(50, 90);
}
/**
* M364: SCARA calibration: Move to cal-position PSIC (90 deg to Theta calibration position)
*/
inline bool gcode_M364() {
SERIAL_ECHOLN(" Cal: Theta-Psi 90 ");
return SCARA_move_to_cal(45, 135);
}
/**
* M365: SCARA calibration: Scaling factor, X, Y, Z axis
*/
inline void gcode_M365() {
for (int8_t i = X_AXIS; i <= Z_AXIS; i++) {
if (code_seen(axis_codes[i])) {
axis_scaling[i] = code_value();
}
}
}
#endif // SCARA
#ifdef EXT_SOLENOID
void enable_solenoid(uint8_t num) {
switch(num) {
case 0:
OUT_WRITE(SOL0_PIN, HIGH);
break;
#if HAS_SOLENOID_1
case 1:
OUT_WRITE(SOL1_PIN, HIGH);
break;
#endif
#if HAS_SOLENOID_2
case 2:
OUT_WRITE(SOL2_PIN, HIGH);
break;
#endif
#if HAS_SOLENOID_3
case 3:
OUT_WRITE(SOL3_PIN, HIGH);
break;
#endif
default:
SERIAL_ECHO_START;
SERIAL_ECHOLNPGM(MSG_INVALID_SOLENOID);
break;
}
}
void enable_solenoid_on_active_extruder() { enable_solenoid(active_extruder); }
void disable_all_solenoids() {
OUT_WRITE(SOL0_PIN, LOW);
OUT_WRITE(SOL1_PIN, LOW);
OUT_WRITE(SOL2_PIN, LOW);
OUT_WRITE(SOL3_PIN, LOW);
}
/**
* M380: Enable solenoid on the active extruder
*/
inline void gcode_M380() { enable_solenoid_on_active_extruder(); }
/**
* M381: Disable all solenoids
*/
inline void gcode_M381() { disable_all_solenoids(); }
#endif // EXT_SOLENOID
/**
* M400: Finish all moves
*/
inline void gcode_M400() { st_synchronize(); }
#if defined(ENABLE_AUTO_BED_LEVELING) && !defined(Z_PROBE_SLED) && (defined(SERVO_ENDSTOPS) || defined(Z_PROBE_ALLEN_KEY))
#ifdef SERVO_ENDSTOPS
void raise_z_for_servo() {
float zpos = current_position[Z_AXIS], z_dest = Z_RAISE_BEFORE_HOMING;
z_dest += axis_known_position[Z_AXIS] ? zprobe_zoffset : zpos;
if (zpos < z_dest)
do_blocking_move_to(current_position[X_AXIS], current_position[Y_AXIS], z_dest); // also updates current_position
}
#endif
/**
* M401: Engage Z Servo endstop if available
*/
inline void gcode_M401() {
#ifdef SERVO_ENDSTOPS
raise_z_for_servo();
#endif
deploy_z_probe();
}
/**
* M402: Retract Z Servo endstop if enabled
*/
inline void gcode_M402() {
#ifdef SERVO_ENDSTOPS
raise_z_for_servo();
#endif
stow_z_probe(false);
}
#endif // ENABLE_AUTO_BED_LEVELING && (SERVO_ENDSTOPS || Z_PROBE_ALLEN_KEY) && !Z_PROBE_SLED
#ifdef FILAMENT_SENSOR
/**
* M404: Display or set the nominal filament width (3mm, 1.75mm ) W<3.0>
*/
inline void gcode_M404() {
#if HAS_FILWIDTH
if (code_seen('W')) {
filament_width_nominal = code_value();
}
else {
SERIAL_PROTOCOLPGM("Filament dia (nominal mm):");
SERIAL_PROTOCOLLN(filament_width_nominal);
}
#endif
}
/**
* M405: Turn on filament sensor for control
*/
inline void gcode_M405() {
if (code_seen('D')) meas_delay_cm = code_value();
if (meas_delay_cm > MAX_MEASUREMENT_DELAY) meas_delay_cm = MAX_MEASUREMENT_DELAY;
if (delay_index2 == -1) { //initialize the ring buffer if it has not been done since startup
int temp_ratio = widthFil_to_size_ratio();
for (delay_index1 = 0; delay_index1 < MAX_MEASUREMENT_DELAY + 1; ++delay_index1)
measurement_delay[delay_index1] = temp_ratio - 100; //subtract 100 to scale within a signed byte
delay_index1 = delay_index2 = 0;
}
filament_sensor = true;
//SERIAL_PROTOCOLPGM("Filament dia (measured mm):");
//SERIAL_PROTOCOL(filament_width_meas);
//SERIAL_PROTOCOLPGM("Extrusion ratio(%):");
//SERIAL_PROTOCOL(extruder_multiplier[active_extruder]);
}
/**
* M406: Turn off filament sensor for control
*/
inline void gcode_M406() { filament_sensor = false; }
/**
* M407: Get measured filament diameter on serial output
*/
inline void gcode_M407() {
SERIAL_PROTOCOLPGM("Filament dia (measured mm):");
SERIAL_PROTOCOLLN(filament_width_meas);
}
#endif // FILAMENT_SENSOR
/**
* M410: Quickstop - Abort all planned moves
*
* This will stop the carriages mid-move, so most likely they
* will be out of sync with the stepper position after this.
*/
inline void gcode_M410() { quickStop(); }
#ifdef MESH_BED_LEVELING
/**
* M420: Enable/Disable Mesh Bed Leveling
*/
inline void gcode_M420() { if (code_seen('S') && code_has_value()) mbl.active = !!code_value_short(); }
/**
* M421: Set a single Mesh Bed Leveling Z coordinate
*/
inline void gcode_M421() {
float x, y, z;
bool err = false, hasX, hasY, hasZ;
if ((hasX = code_seen('X'))) x = code_value();
if ((hasY = code_seen('Y'))) y = code_value();
if ((hasZ = code_seen('Z'))) z = code_value();
if (!hasX || !hasY || !hasZ) {
SERIAL_ERROR_START;
SERIAL_ERRORLNPGM(MSG_ERR_M421_REQUIRES_XYZ);
err = true;
}
if (x >= MESH_NUM_X_POINTS || y >= MESH_NUM_Y_POINTS) {
SERIAL_ERROR_START;
SERIAL_ERRORLNPGM(MSG_ERR_MESH_INDEX_OOB);
err = true;
}
if (!err) mbl.set_z(mbl.select_x_index(x), mbl.select_y_index(y), z);
}
#endif
/**
* M428: Set home_offset based on the distance between the
* current_position and the nearest "reference point."
* If an axis is past center its endstop position
* is the reference-point. Otherwise it uses 0. This allows
* the Z offset to be set near the bed when using a max endstop.
*
* M428 can't be used more than 2cm away from 0 or an endstop.
*
* Use M206 to set these values directly.
*/
inline void gcode_M428() {
bool err = false;
float new_offs[3], new_pos[3];
memcpy(new_pos, current_position, sizeof(new_pos));
memcpy(new_offs, home_offset, sizeof(new_offs));
for (int8_t i = X_AXIS; i <= Z_AXIS; i++) {
if (axis_known_position[i]) {
float base = (new_pos[i] > (min_pos[i] + max_pos[i]) / 2) ? base_home_pos(i) : 0,
diff = new_pos[i] - base;
if (diff > -20 && diff < 20) {
new_offs[i] -= diff;
new_pos[i] = base;
}
else {
SERIAL_ERROR_START;
SERIAL_ERRORLNPGM(MSG_ERR_M428_TOO_FAR);
LCD_ALERTMESSAGEPGM("Err: Too far!");
#if HAS_BUZZER
enqueuecommands_P(PSTR("M300 S40 P200"));
#endif
err = true;
break;
}
}
}
if (!err) {
memcpy(current_position, new_pos, sizeof(new_pos));
memcpy(home_offset, new_offs, sizeof(new_offs));
sync_plan_position();
LCD_ALERTMESSAGEPGM("Offset applied.");
#if HAS_BUZZER
enqueuecommands_P(PSTR("M300 S659 P200\nM300 S698 P200"));
#endif
}
}
/**
* M500: Store settings in EEPROM
*/
inline void gcode_M500() {
Config_StoreSettings();
}
/**
* M501: Read settings from EEPROM
*/
inline void gcode_M501() {
Config_RetrieveSettings();
}
/**
* M502: Revert to default settings
*/
inline void gcode_M502() {
Config_ResetDefault();
}
/**
* M503: print settings currently in memory
*/
inline void gcode_M503() {
Config_PrintSettings(code_seen('S') && code_value() == 0);
}
#ifdef ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED
/**
* M540: Set whether SD card print should abort on endstop hit (M540 S<0|1>)
*/
inline void gcode_M540() {
if (code_seen('S')) abort_on_endstop_hit = (code_value() > 0);
}
#endif // ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED
#ifdef CUSTOM_M_CODE_SET_Z_PROBE_OFFSET
inline void gcode_SET_Z_PROBE_OFFSET() {
float value;
if (code_seen('Z')) {
value = code_value();
if (Z_PROBE_OFFSET_RANGE_MIN <= value && value <= Z_PROBE_OFFSET_RANGE_MAX) {
zprobe_zoffset = value;
SERIAL_ECHO_START;
SERIAL_ECHOLNPGM(MSG_ZPROBE_ZOFFSET " " MSG_OK);
SERIAL_EOL;
}
else {
SERIAL_ECHO_START;
SERIAL_ECHOPGM(MSG_ZPROBE_ZOFFSET);
SERIAL_ECHOPGM(MSG_Z_MIN);
SERIAL_ECHO(Z_PROBE_OFFSET_RANGE_MIN);
SERIAL_ECHOPGM(MSG_Z_MAX);
SERIAL_ECHO(Z_PROBE_OFFSET_RANGE_MAX);
SERIAL_EOL;
}
}
else {
SERIAL_ECHO_START;
SERIAL_ECHOPGM(MSG_ZPROBE_ZOFFSET " : ");
SERIAL_ECHO(zprobe_zoffset);
SERIAL_EOL;
}
}
#endif // CUSTOM_M_CODE_SET_Z_PROBE_OFFSET
#ifdef FILAMENTCHANGEENABLE
/**
* M600: Pause for filament change
*
* E[distance] - Retract the filament this far (negative value)
* Z[distance] - Move the Z axis by this distance
* X[position] - Move to this X position, with Y
* Y[position] - Move to this Y position, with X
* L[distance] - Retract distance for removal (manual reload)
*
* Default values are used for omitted arguments.
*
*/
inline void gcode_M600() {
if (degHotend(active_extruder) < extrude_min_temp) {
SERIAL_ERROR_START;
SERIAL_ERRORLNPGM(MSG_TOO_COLD_FOR_M600);
return;
}
float lastpos[NUM_AXIS], fr60 = feedrate / 60;
for (int i=0; i= next_tick) {
lcd_quick_feedback();
next_tick = ms + 2500; // feedback every 2.5s while waiting
}
manage_heater();
manage_inactivity(true);
lcd_update();
#else
current_position[E_AXIS] += AUTO_FILAMENT_CHANGE_LENGTH;
destination[E_AXIS] = current_position[E_AXIS];
line_to_destination(AUTO_FILAMENT_CHANGE_FEEDRATE);
st_synchronize();
#endif
} // while(!lcd_clicked)
lcd_quick_feedback(); // click sound feedback
#ifdef AUTO_FILAMENT_CHANGE
current_position[E_AXIS] = 0;
st_synchronize();
#endif
//return to normal
if (code_seen('L')) destination[E_AXIS] -= code_value();
#ifdef FILAMENTCHANGE_FINALRETRACT
else destination[E_AXIS] -= FILAMENTCHANGE_FINALRETRACT;
#endif
current_position[E_AXIS] = destination[E_AXIS]; //the long retract of L is compensated by manual filament feeding
plan_set_e_position(current_position[E_AXIS]);
RUNPLAN; //should do nothing
lcd_reset_alert_level();
#ifdef DELTA
// Move XYZ to starting position, then E
calculate_delta(lastpos);
plan_buffer_line(delta[X_AXIS], delta[Y_AXIS], delta[Z_AXIS], destination[E_AXIS], fr60, active_extruder);
plan_buffer_line(delta[X_AXIS], delta[Y_AXIS], delta[Z_AXIS], lastpos[E_AXIS], fr60, active_extruder);
#else
// Move XY to starting position, then Z, then E
destination[X_AXIS] = lastpos[X_AXIS];
destination[Y_AXIS] = lastpos[Y_AXIS];
line_to_destination();
destination[Z_AXIS] = lastpos[Z_AXIS];
line_to_destination();
destination[E_AXIS] = lastpos[E_AXIS];
line_to_destination();
#endif
#ifdef FILAMENT_RUNOUT_SENSOR
filrunoutEnqueued = false;
#endif
}
#endif // FILAMENTCHANGEENABLE
#ifdef DUAL_X_CARRIAGE
/**
* M605: Set dual x-carriage movement mode
*
* M605 S0: Full control mode. The slicer has full control over x-carriage movement
* M605 S1: Auto-park mode. The inactive head will auto park/unpark without slicer involvement
* M605 S2 [Xnnn] [Rmmm]: Duplication mode. The second extruder will duplicate the first with nnn
* millimeters x-offset and an optional differential hotend temperature of
* mmm degrees. E.g., with "M605 S2 X100 R2" the second extruder will duplicate
* the first with a spacing of 100mm in the x direction and 2 degrees hotter.
*
* Note: the X axis should be homed after changing dual x-carriage mode.
*/
inline void gcode_M605() {
st_synchronize();
if (code_seen('S')) dual_x_carriage_mode = code_value();
switch(dual_x_carriage_mode) {
case DXC_DUPLICATION_MODE:
if (code_seen('X')) duplicate_extruder_x_offset = max(code_value(), X2_MIN_POS - x_home_pos(0));
if (code_seen('R')) duplicate_extruder_temp_offset = code_value();
SERIAL_ECHO_START;
SERIAL_ECHOPGM(MSG_HOTEND_OFFSET);
SERIAL_CHAR(' ');
SERIAL_ECHO(extruder_offset[X_AXIS][0]);
SERIAL_CHAR(',');
SERIAL_ECHO(extruder_offset[Y_AXIS][0]);
SERIAL_CHAR(' ');
SERIAL_ECHO(duplicate_extruder_x_offset);
SERIAL_CHAR(',');
SERIAL_ECHOLN(extruder_offset[Y_AXIS][1]);
break;
case DXC_FULL_CONTROL_MODE:
case DXC_AUTO_PARK_MODE:
break;
default:
dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE;
break;
}
active_extruder_parked = false;
extruder_duplication_enabled = false;
delayed_move_time = 0;
}
#endif // DUAL_X_CARRIAGE
/**
* M907: Set digital trimpot motor current using axis codes X, Y, Z, E, B, S
*/
inline void gcode_M907() {
#if HAS_DIGIPOTSS
for (int i=0;i S)
*/
inline void gcode_M908() {
digitalPotWrite(
code_seen('P') ? code_value() : 0,
code_seen('S') ? code_value() : 0
);
}
#endif // HAS_DIGIPOTSS
#if HAS_MICROSTEPS
// M350 Set microstepping mode. Warning: Steps per unit remains unchanged. S code sets stepping mode for all drivers.
inline void gcode_M350() {
if(code_seen('S')) for(int i=0;i<=4;i++) microstep_mode(i,code_value());
for(int i=0;i= EXTRUDERS) {
SERIAL_ECHO_START;
SERIAL_CHAR('T');
SERIAL_ECHO(tmp_extruder);
SERIAL_ECHOLN(MSG_INVALID_EXTRUDER);
}
else {
target_extruder = tmp_extruder;
#if EXTRUDERS > 1
bool make_move = false;
#endif
if (code_seen('F')) {
#if EXTRUDERS > 1
make_move = true;
#endif
float next_feedrate = code_value();
if (next_feedrate > 0.0) feedrate = next_feedrate;
}
#if EXTRUDERS > 1
if (tmp_extruder != active_extruder) {
// Save current position to return to after applying extruder offset
set_destination_to_current();
#ifdef DUAL_X_CARRIAGE
if (dual_x_carriage_mode == DXC_AUTO_PARK_MODE && IsRunning() &&
(delayed_move_time != 0 || current_position[X_AXIS] != x_home_pos(active_extruder))) {
// Park old head: 1) raise 2) move to park position 3) lower
plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT,
current_position[E_AXIS], max_feedrate[Z_AXIS], active_extruder);
plan_buffer_line(x_home_pos(active_extruder), current_position[Y_AXIS], current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT,
current_position[E_AXIS], max_feedrate[X_AXIS], active_extruder);
plan_buffer_line(x_home_pos(active_extruder), current_position[Y_AXIS], current_position[Z_AXIS],
current_position[E_AXIS], max_feedrate[Z_AXIS], active_extruder);
st_synchronize();
}
// apply Y & Z extruder offset (x offset is already used in determining home pos)
current_position[Y_AXIS] = current_position[Y_AXIS] -
extruder_offset[Y_AXIS][active_extruder] +
extruder_offset[Y_AXIS][tmp_extruder];
current_position[Z_AXIS] = current_position[Z_AXIS] -
extruder_offset[Z_AXIS][active_extruder] +
extruder_offset[Z_AXIS][tmp_extruder];
active_extruder = tmp_extruder;
// This function resets the max/min values - the current position may be overwritten below.
axis_is_at_home(X_AXIS);
if (dual_x_carriage_mode == DXC_FULL_CONTROL_MODE) {
current_position[X_AXIS] = inactive_extruder_x_pos;
inactive_extruder_x_pos = destination[X_AXIS];
}
else if (dual_x_carriage_mode == DXC_DUPLICATION_MODE) {
active_extruder_parked = (active_extruder == 0); // this triggers the second extruder to move into the duplication position
if (active_extruder == 0 || active_extruder_parked)
current_position[X_AXIS] = inactive_extruder_x_pos;
else
current_position[X_AXIS] = destination[X_AXIS] + duplicate_extruder_x_offset;
inactive_extruder_x_pos = destination[X_AXIS];
extruder_duplication_enabled = false;
}
else {
// record raised toolhead position for use by unpark
memcpy(raised_parked_position, current_position, sizeof(raised_parked_position));
raised_parked_position[Z_AXIS] += TOOLCHANGE_UNPARK_ZLIFT;
active_extruder_parked = true;
delayed_move_time = 0;
}
#else // !DUAL_X_CARRIAGE
// Offset extruder (only by XY)
for (int i=X_AXIS; i<=Y_AXIS; i++)
current_position[i] += extruder_offset[i][tmp_extruder] - extruder_offset[i][active_extruder];
// Set the new active extruder and position
active_extruder = tmp_extruder;
#endif // !DUAL_X_CARRIAGE
#ifdef DELTA
sync_plan_position_delta();
#else
sync_plan_position();
#endif
// Move to the old position if 'F' was in the parameters
if (make_move && IsRunning()) prepare_move();
}
#ifdef EXT_SOLENOID
st_synchronize();
disable_all_solenoids();
enable_solenoid_on_active_extruder();
#endif // EXT_SOLENOID
#endif // EXTRUDERS > 1
SERIAL_ECHO_START;
SERIAL_ECHO(MSG_ACTIVE_EXTRUDER);
SERIAL_PROTOCOLLN((int)active_extruder);
}
}
/**
* Process a single command and dispatch it to its handler
* This is called from the main loop()
*/
void process_next_command() {
current_command = command_queue[cmd_queue_index_r];
if ((marlin_debug_flags & DEBUG_ECHO)) {
SERIAL_ECHO_START;
SERIAL_ECHOLN(current_command);
}
// Sanitize the current command:
// - Skip leading spaces
// - Bypass N[0-9][0-9]*[ ]*
// - Overwrite * with nul to mark the end
while (*current_command == ' ') ++current_command;
if (*current_command == 'N' && ((current_command[1] >= '0' && current_command[1] <= '9') || current_command[1] == '-')) {
current_command += 2; // skip N[-0-9]
while (*current_command >= '0' && *current_command <= '9') ++current_command; // skip [0-9]*
while (*current_command == ' ') ++current_command; // skip [ ]*
}
char *starpos = strchr(current_command, '*'); // * should always be the last parameter
if (starpos) while (*starpos == ' ' || *starpos == '*') *starpos-- = '\0'; // nullify '*' and ' '
// Get the command code, which must be G, M, or T
char command_code = *current_command;
// The code must have a numeric value
bool code_is_good = (current_command[1] >= '0' && current_command[1] <= '9');
int codenum; // define ahead of goto
// Bail early if there's no code
if (!code_is_good) goto ExitUnknownCommand;
// Args pointer optimizes code_seen, especially those taking XYZEF
// This wastes a little cpu on commands that expect no arguments.
current_command_args = current_command;
while (*current_command_args && *current_command_args != ' ') ++current_command_args;
while (*current_command_args == ' ') ++current_command_args;
// Interpret the code int
seen_pointer = current_command;
codenum = code_value_short();
// Handle a known G, M, or T
switch(command_code) {
case 'G': switch (codenum) {
// G0, G1
case 0:
case 1:
gcode_G0_G1();
break;
// G2, G3
#ifndef SCARA
case 2: // G2 - CW ARC
case 3: // G3 - CCW ARC
gcode_G2_G3(codenum == 2);
break;
#endif
// G4 Dwell
case 4:
gcode_G4();
break;
#ifdef FWRETRACT
case 10: // G10: retract
case 11: // G11: retract_recover
gcode_G10_G11(codenum == 10);
break;
#endif //FWRETRACT
case 28: // G28: Home all axes, one at a time
gcode_G28();
break;
#if defined(ENABLE_AUTO_BED_LEVELING) || defined(MESH_BED_LEVELING)
case 29: // G29 Detailed Z-Probe, probes the bed at 3 or more points.
gcode_G29();
break;
#endif
#ifdef ENABLE_AUTO_BED_LEVELING
#ifndef Z_PROBE_SLED
case 30: // G30 Single Z Probe
gcode_G30();
break;
#else // Z_PROBE_SLED
case 31: // G31: dock the sled
case 32: // G32: undock the sled
dock_sled(codenum == 31);
break;
#endif // Z_PROBE_SLED
#endif // ENABLE_AUTO_BED_LEVELING
case 90: // G90
relative_mode = false;
break;
case 91: // G91
relative_mode = true;
break;
case 92: // G92
gcode_G92();
break;
}
break;
case 'M': switch (codenum) {
#ifdef ULTIPANEL
case 0: // M0 - Unconditional stop - Wait for user button press on LCD
case 1: // M1 - Conditional stop - Wait for user button press on LCD
gcode_M0_M1();
break;
#endif // ULTIPANEL
case 17:
gcode_M17();
break;
#ifdef SDSUPPORT
case 20: // M20 - list SD card
gcode_M20(); break;
case 21: // M21 - init SD card
gcode_M21(); break;
case 22: //M22 - release SD card
gcode_M22(); break;
case 23: //M23 - Select file
gcode_M23(); break;
case 24: //M24 - Start SD print
gcode_M24(); break;
case 25: //M25 - Pause SD print
gcode_M25(); break;
case 26: //M26 - Set SD index
gcode_M26(); break;
case 27: //M27 - Get SD status
gcode_M27(); break;
case 28: //M28 - Start SD write
gcode_M28(); break;
case 29: //M29 - Stop SD write
gcode_M29(); break;
case 30: //M30 Delete File
gcode_M30(); break;
case 32: //M32 - Select file and start SD print
gcode_M32(); break;
#ifdef LONG_FILENAME_HOST_SUPPORT
case 33: //M33 - Get the long full path to a file or folder
gcode_M33(); break;
#endif // LONG_FILENAME_HOST_SUPPORT
case 928: //M928 - Start SD write
gcode_M928(); break;
#endif //SDSUPPORT
case 31: //M31 take time since the start of the SD print or an M109 command
gcode_M31();
break;
case 42: //M42 -Change pin status via gcode
gcode_M42();
break;
#if defined(ENABLE_AUTO_BED_LEVELING) && defined(Z_PROBE_REPEATABILITY_TEST)
case 48: // M48 Z-Probe repeatability
gcode_M48();
break;
#endif // ENABLE_AUTO_BED_LEVELING && Z_PROBE_REPEATABILITY_TEST
case 104: // M104
gcode_M104();
break;
case 111: // M111: Set debug level
gcode_M111();
break;
case 112: // M112: Emergency Stop
gcode_M112();
break;
case 140: // M140: Set bed temp
gcode_M140();
break;
case 105: // M105: Read current temperature
gcode_M105();
return; // "ok" already printed
case 109: // M109: Wait for temperature
gcode_M109();
break;
#if HAS_TEMP_BED
case 190: // M190: Wait for bed heater to reach target
gcode_M190();
break;
#endif // HAS_TEMP_BED
#if HAS_FAN
case 106: // M106: Fan On
gcode_M106();
break;
case 107: // M107: Fan Off
gcode_M107();
break;
#endif // HAS_FAN
#ifdef BARICUDA
// PWM for HEATER_1_PIN
#if HAS_HEATER_1
case 126: // M126: valve open
gcode_M126();
break;
case 127: // M127: valve closed
gcode_M127();
break;
#endif // HAS_HEATER_1
// PWM for HEATER_2_PIN
#if HAS_HEATER_2
case 128: // M128: valve open
gcode_M128();
break;
case 129: // M129: valve closed
gcode_M129();
break;
#endif // HAS_HEATER_2
#endif // BARICUDA
#if HAS_POWER_SWITCH
case 80: // M80: Turn on Power Supply
gcode_M80();
break;
#endif // HAS_POWER_SWITCH
case 81: // M81: Turn off Power, including Power Supply, if possible
gcode_M81();
break;
case 82:
gcode_M82();
break;
case 83:
gcode_M83();
break;
case 18: // (for compatibility)
case 84: // M84
gcode_M18_M84();
break;
case 85: // M85
gcode_M85();
break;
case 92: // M92: Set the steps-per-unit for one or more axes
gcode_M92();
break;
case 115: // M115: Report capabilities
gcode_M115();
break;
case 117: // M117: Set LCD message text, if possible
gcode_M117();
break;
case 114: // M114: Report current position
gcode_M114();
break;
case 120: // M120: Enable endstops
gcode_M120();
break;
case 121: // M121: Disable endstops
gcode_M121();
break;
case 119: // M119: Report endstop states
gcode_M119();
break;
#ifdef ULTIPANEL
case 145: // M145: Set material heatup parameters
gcode_M145();
break;
#endif
#ifdef BLINKM
case 150: // M150
gcode_M150();
break;
#endif //BLINKM
case 200: // M200 D set filament diameter and set E axis units to cubic millimeters (use S0 to set back to millimeters).
gcode_M200();
break;
case 201: // M201
gcode_M201();
break;
#if 0 // Not used for Sprinter/grbl gen6
case 202: // M202
gcode_M202();
break;
#endif
case 203: // M203 max feedrate mm/sec
gcode_M203();
break;
case 204: // M204 acclereration S normal moves T filmanent only moves
gcode_M204();
break;
case 205: //M205 advanced settings: minimum travel speed S=while printing T=travel only, B=minimum segment time X= maximum xy jerk, Z=maximum Z jerk
gcode_M205();
break;
case 206: // M206 additional homing offset
gcode_M206();
break;
#ifdef DELTA
case 665: // M665 set delta configurations L R S
gcode_M665();
break;
#endif
#if defined(DELTA) || defined(Z_DUAL_ENDSTOPS)
case 666: // M666 set delta / dual endstop adjustment
gcode_M666();
break;
#endif
#ifdef FWRETRACT
case 207: //M207 - set retract length S[positive mm] F[feedrate mm/min] Z[additional zlift/hop]
gcode_M207();
break;
case 208: // M208 - set retract recover length S[positive mm surplus to the M207 S*] F[feedrate mm/min]
gcode_M208();
break;
case 209: // M209 - S<1=true/0=false> enable automatic retract detect if the slicer did not support G10/11: every normal extrude-only move will be classified as retract depending on the direction.
gcode_M209();
break;
#endif // FWRETRACT
#if EXTRUDERS > 1
case 218: // M218 - set hotend offset (in mm), T X Y
gcode_M218();
break;
#endif
case 220: // M220 S- set speed factor override percentage
gcode_M220();
break;
case 221: // M221 S- set extrude factor override percentage
gcode_M221();
break;
case 226: // M226 P S- Wait until the specified pin reaches the state required
gcode_M226();
break;
#if NUM_SERVOS > 0
case 280: // M280 - set servo position absolute. P: servo index, S: angle or microseconds
gcode_M280();
break;
#endif // NUM_SERVOS > 0
#if HAS_BUZZER
case 300: // M300 - Play beep tone
gcode_M300();
break;
#endif // HAS_BUZZER
#ifdef PIDTEMP
case 301: // M301
gcode_M301();
break;
#endif // PIDTEMP
#ifdef PIDTEMPBED
case 304: // M304
gcode_M304();
break;
#endif // PIDTEMPBED
#if defined(CHDK) || HAS_PHOTOGRAPH
case 240: // M240 Triggers a camera by emulating a Canon RC-1 : http://www.doc-diy.net/photo/rc-1_hacked/
gcode_M240();
break;
#endif // CHDK || PHOTOGRAPH_PIN
#ifdef HAS_LCD_CONTRAST
case 250: // M250 Set LCD contrast value: C (value 0..63)
gcode_M250();
break;
#endif // HAS_LCD_CONTRAST
#ifdef PREVENT_DANGEROUS_EXTRUDE
case 302: // allow cold extrudes, or set the minimum extrude temperature
gcode_M302();
break;
#endif // PREVENT_DANGEROUS_EXTRUDE
case 303: // M303 PID autotune
gcode_M303();
break;
#ifdef SCARA
case 360: // M360 SCARA Theta pos1
if (gcode_M360()) return;
break;
case 361: // M361 SCARA Theta pos2
if (gcode_M361()) return;
break;
case 362: // M362 SCARA Psi pos1
if (gcode_M362()) return;
break;
case 363: // M363 SCARA Psi pos2
if (gcode_M363()) return;
break;
case 364: // M364 SCARA Psi pos3 (90 deg to Theta)
if (gcode_M364()) return;
break;
case 365: // M365 Set SCARA scaling for X Y Z
gcode_M365();
break;
#endif // SCARA
case 400: // M400 finish all moves
gcode_M400();
break;
#if defined(ENABLE_AUTO_BED_LEVELING) && (defined(SERVO_ENDSTOPS) || defined(Z_PROBE_ALLEN_KEY)) && !defined(Z_PROBE_SLED)
case 401:
gcode_M401();
break;
case 402:
gcode_M402();
break;
#endif // ENABLE_AUTO_BED_LEVELING && (SERVO_ENDSTOPS || Z_PROBE_ALLEN_KEY) && !Z_PROBE_SLED
#ifdef FILAMENT_SENSOR
case 404: //M404 Enter the nominal filament width (3mm, 1.75mm ) N<3.0> or display nominal filament width
gcode_M404();
break;
case 405: //M405 Turn on filament sensor for control
gcode_M405();
break;
case 406: //M406 Turn off filament sensor for control
gcode_M406();
break;
case 407: //M407 Display measured filament diameter
gcode_M407();
break;
#endif // FILAMENT_SENSOR
case 410: // M410 quickstop - Abort all the planned moves.
gcode_M410();
break;
#ifdef MESH_BED_LEVELING
case 420: // M420 Enable/Disable Mesh Bed Leveling
gcode_M420();
break;
case 421: // M421 Set a Mesh Bed Leveling Z coordinate
gcode_M421();
break;
#endif
case 428: // M428 Apply current_position to home_offset
gcode_M428();
break;
case 500: // M500 Store settings in EEPROM
gcode_M500();
break;
case 501: // M501 Read settings from EEPROM
gcode_M501();
break;
case 502: // M502 Revert to default settings
gcode_M502();
break;
case 503: // M503 print settings currently in memory
gcode_M503();
break;
#ifdef ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED
case 540:
gcode_M540();
break;
#endif
#ifdef CUSTOM_M_CODE_SET_Z_PROBE_OFFSET
case CUSTOM_M_CODE_SET_Z_PROBE_OFFSET:
gcode_SET_Z_PROBE_OFFSET();
break;
#endif // CUSTOM_M_CODE_SET_Z_PROBE_OFFSET
#ifdef FILAMENTCHANGEENABLE
case 600: //Pause for filament change X[pos] Y[pos] Z[relative lift] E[initial retract] L[later retract distance for removal]
gcode_M600();
break;
#endif // FILAMENTCHANGEENABLE
#ifdef DUAL_X_CARRIAGE
case 605:
gcode_M605();
break;
#endif // DUAL_X_CARRIAGE
case 907: // M907 Set digital trimpot motor current using axis codes.
gcode_M907();
break;
#if HAS_DIGIPOTSS
case 908: // M908 Control digital trimpot directly.
gcode_M908();
break;
#endif // HAS_DIGIPOTSS
#if HAS_MICROSTEPS
case 350: // M350 Set microstepping mode. Warning: Steps per unit remains unchanged. S code sets stepping mode for all drivers.
gcode_M350();
break;
case 351: // M351 Toggle MS1 MS2 pins directly, S# determines MS1 or MS2, X# sets the pin high/low.
gcode_M351();
break;
#endif // HAS_MICROSTEPS
case 999: // M999: Restart after being Stopped
gcode_M999();
break;
}
break;
case 'T':
gcode_T(codenum);
break;
default: code_is_good = false;
}
ExitUnknownCommand:
// Still unknown command? Throw an error
if (!code_is_good) unknown_command_error();
ok_to_send();
}
void FlushSerialRequestResend() {
//char command_queue[cmd_queue_index_r][100]="Resend:";
MYSERIAL.flush();
SERIAL_PROTOCOLPGM(MSG_RESEND);
SERIAL_PROTOCOLLN(gcode_LastN + 1);
ok_to_send();
}
void ok_to_send() {
refresh_cmd_timeout();
#ifdef SDSUPPORT
if (fromsd[cmd_queue_index_r]) return;
#endif
SERIAL_PROTOCOLPGM(MSG_OK);
#ifdef ADVANCED_OK
SERIAL_PROTOCOLPGM(" N"); SERIAL_PROTOCOL(gcode_LastN);
SERIAL_PROTOCOLPGM(" P"); SERIAL_PROTOCOL(int(BLOCK_BUFFER_SIZE - movesplanned() - 1));
SERIAL_PROTOCOLPGM(" B"); SERIAL_PROTOCOL(BUFSIZE - commands_in_queue);
#endif
SERIAL_EOL;
}
void clamp_to_software_endstops(float target[3]) {
if (min_software_endstops) {
NOLESS(target[X_AXIS], min_pos[X_AXIS]);
NOLESS(target[Y_AXIS], min_pos[Y_AXIS]);
float negative_z_offset = 0;
#ifdef ENABLE_AUTO_BED_LEVELING
if (zprobe_zoffset < 0) negative_z_offset += zprobe_zoffset;
if (home_offset[Z_AXIS] < 0) negative_z_offset += home_offset[Z_AXIS];
#endif
NOLESS(target[Z_AXIS], min_pos[Z_AXIS] + negative_z_offset);
}
if (max_software_endstops) {
NOMORE(target[X_AXIS], max_pos[X_AXIS]);
NOMORE(target[Y_AXIS], max_pos[Y_AXIS]);
NOMORE(target[Z_AXIS], max_pos[Z_AXIS]);
}
}
#ifdef DELTA
void recalc_delta_settings(float radius, float diagonal_rod) {
delta_tower1_x = -SIN_60 * radius; // front left tower
delta_tower1_y = -COS_60 * radius;
delta_tower2_x = SIN_60 * radius; // front right tower
delta_tower2_y = -COS_60 * radius;
delta_tower3_x = 0.0; // back middle tower
delta_tower3_y = radius;
delta_diagonal_rod_2 = sq(diagonal_rod);
}
void calculate_delta(float cartesian[3]) {
delta[X_AXIS] = sqrt(delta_diagonal_rod_2
- sq(delta_tower1_x-cartesian[X_AXIS])
- sq(delta_tower1_y-cartesian[Y_AXIS])
) + cartesian[Z_AXIS];
delta[Y_AXIS] = sqrt(delta_diagonal_rod_2
- sq(delta_tower2_x-cartesian[X_AXIS])
- sq(delta_tower2_y-cartesian[Y_AXIS])
) + cartesian[Z_AXIS];
delta[Z_AXIS] = sqrt(delta_diagonal_rod_2
- sq(delta_tower3_x-cartesian[X_AXIS])
- sq(delta_tower3_y-cartesian[Y_AXIS])
) + cartesian[Z_AXIS];
/*
SERIAL_ECHOPGM("cartesian x="); SERIAL_ECHO(cartesian[X_AXIS]);
SERIAL_ECHOPGM(" y="); SERIAL_ECHO(cartesian[Y_AXIS]);
SERIAL_ECHOPGM(" z="); SERIAL_ECHOLN(cartesian[Z_AXIS]);
SERIAL_ECHOPGM("delta x="); SERIAL_ECHO(delta[X_AXIS]);
SERIAL_ECHOPGM(" y="); SERIAL_ECHO(delta[Y_AXIS]);
SERIAL_ECHOPGM(" z="); SERIAL_ECHOLN(delta[Z_AXIS]);
*/
}
#ifdef ENABLE_AUTO_BED_LEVELING
// Adjust print surface height by linear interpolation over the bed_level array.
void adjust_delta(float cartesian[3]) {
if (delta_grid_spacing[0] == 0 || delta_grid_spacing[1] == 0) return; // G29 not done!
int half = (AUTO_BED_LEVELING_GRID_POINTS - 1) / 2;
float h1 = 0.001 - half, h2 = half - 0.001,
grid_x = max(h1, min(h2, cartesian[X_AXIS] / delta_grid_spacing[0])),
grid_y = max(h1, min(h2, cartesian[Y_AXIS] / delta_grid_spacing[1]));
int floor_x = floor(grid_x), floor_y = floor(grid_y);
float ratio_x = grid_x - floor_x, ratio_y = grid_y - floor_y,
z1 = bed_level[floor_x + half][floor_y + half],
z2 = bed_level[floor_x + half][floor_y + half + 1],
z3 = bed_level[floor_x + half + 1][floor_y + half],
z4 = bed_level[floor_x + half + 1][floor_y + half + 1],
left = (1 - ratio_y) * z1 + ratio_y * z2,
right = (1 - ratio_y) * z3 + ratio_y * z4,
offset = (1 - ratio_x) * left + ratio_x * right;
delta[X_AXIS] += offset;
delta[Y_AXIS] += offset;
delta[Z_AXIS] += offset;
/*
SERIAL_ECHOPGM("grid_x="); SERIAL_ECHO(grid_x);
SERIAL_ECHOPGM(" grid_y="); SERIAL_ECHO(grid_y);
SERIAL_ECHOPGM(" floor_x="); SERIAL_ECHO(floor_x);
SERIAL_ECHOPGM(" floor_y="); SERIAL_ECHO(floor_y);
SERIAL_ECHOPGM(" ratio_x="); SERIAL_ECHO(ratio_x);
SERIAL_ECHOPGM(" ratio_y="); SERIAL_ECHO(ratio_y);
SERIAL_ECHOPGM(" z1="); SERIAL_ECHO(z1);
SERIAL_ECHOPGM(" z2="); SERIAL_ECHO(z2);
SERIAL_ECHOPGM(" z3="); SERIAL_ECHO(z3);
SERIAL_ECHOPGM(" z4="); SERIAL_ECHO(z4);
SERIAL_ECHOPGM(" left="); SERIAL_ECHO(left);
SERIAL_ECHOPGM(" right="); SERIAL_ECHO(right);
SERIAL_ECHOPGM(" offset="); SERIAL_ECHOLN(offset);
*/
}
#endif // ENABLE_AUTO_BED_LEVELING
#endif // DELTA
#ifdef MESH_BED_LEVELING
// This function is used to split lines on mesh borders so each segment is only part of one mesh area
void mesh_plan_buffer_line(float x, float y, float z, const float e, float feed_rate, const uint8_t &extruder, uint8_t x_splits=0xff, uint8_t y_splits=0xff)
{
if (!mbl.active) {
plan_buffer_line(x, y, z, e, feed_rate, extruder);
set_current_to_destination();
return;
}
int pix = mbl.select_x_index(current_position[X_AXIS]);
int piy = mbl.select_y_index(current_position[Y_AXIS]);
int ix = mbl.select_x_index(x);
int iy = mbl.select_y_index(y);
pix = min(pix, MESH_NUM_X_POINTS - 2);
piy = min(piy, MESH_NUM_Y_POINTS - 2);
ix = min(ix, MESH_NUM_X_POINTS - 2);
iy = min(iy, MESH_NUM_Y_POINTS - 2);
if (pix == ix && piy == iy) {
// Start and end on same mesh square
plan_buffer_line(x, y, z, e, feed_rate, extruder);
set_current_to_destination();
return;
}
float nx, ny, ne, normalized_dist;
if (ix > pix && (x_splits) & BIT(ix)) {
nx = mbl.get_x(ix);
normalized_dist = (nx - current_position[X_AXIS])/(x - current_position[X_AXIS]);
ny = current_position[Y_AXIS] + (y - current_position[Y_AXIS]) * normalized_dist;
ne = current_position[E_AXIS] + (e - current_position[E_AXIS]) * normalized_dist;
x_splits ^= BIT(ix);
} else if (ix < pix && (x_splits) & BIT(pix)) {
nx = mbl.get_x(pix);
normalized_dist = (nx - current_position[X_AXIS])/(x - current_position[X_AXIS]);
ny = current_position[Y_AXIS] + (y - current_position[Y_AXIS]) * normalized_dist;
ne = current_position[E_AXIS] + (e - current_position[E_AXIS]) * normalized_dist;
x_splits ^= BIT(pix);
} else if (iy > piy && (y_splits) & BIT(iy)) {
ny = mbl.get_y(iy);
normalized_dist = (ny - current_position[Y_AXIS])/(y - current_position[Y_AXIS]);
nx = current_position[X_AXIS] + (x - current_position[X_AXIS]) * normalized_dist;
ne = current_position[E_AXIS] + (e - current_position[E_AXIS]) * normalized_dist;
y_splits ^= BIT(iy);
} else if (iy < piy && (y_splits) & BIT(piy)) {
ny = mbl.get_y(piy);
normalized_dist = (ny - current_position[Y_AXIS])/(y - current_position[Y_AXIS]);
nx = current_position[X_AXIS] + (x - current_position[X_AXIS]) * normalized_dist;
ne = current_position[E_AXIS] + (e - current_position[E_AXIS]) * normalized_dist;
y_splits ^= BIT(piy);
} else {
// Already split on a border
plan_buffer_line(x, y, z, e, feed_rate, extruder);
set_current_to_destination();
return;
}
// Do the split and look for more borders
destination[X_AXIS] = nx;
destination[Y_AXIS] = ny;
destination[E_AXIS] = ne;
mesh_plan_buffer_line(nx, ny, z, ne, feed_rate, extruder, x_splits, y_splits);
destination[X_AXIS] = x;
destination[Y_AXIS] = y;
destination[E_AXIS] = e;
mesh_plan_buffer_line(x, y, z, e, feed_rate, extruder, x_splits, y_splits);
}
#endif // MESH_BED_LEVELING
#ifdef PREVENT_DANGEROUS_EXTRUDE
inline void prevent_dangerous_extrude(float &curr_e, float &dest_e) {
if (marlin_debug_flags & DEBUG_DRYRUN) return;
float de = dest_e - curr_e;
if (de) {
if (degHotend(active_extruder) < extrude_min_temp) {
curr_e = dest_e; // Behave as if the move really took place, but ignore E part
SERIAL_ECHO_START;
SERIAL_ECHOLNPGM(MSG_ERR_COLD_EXTRUDE_STOP);
}
#ifdef PREVENT_LENGTHY_EXTRUDE
if (labs(de) > EXTRUDE_MAXLENGTH) {
curr_e = dest_e; // Behave as if the move really took place, but ignore E part
SERIAL_ECHO_START;
SERIAL_ECHOLNPGM(MSG_ERR_LONG_EXTRUDE_STOP);
}
#endif
}
}
#endif // PREVENT_DANGEROUS_EXTRUDE
#if defined(DELTA) || defined(SCARA)
inline bool prepare_move_delta() {
float difference[NUM_AXIS];
for (int8_t i=0; i < NUM_AXIS; i++) difference[i] = destination[i] - current_position[i];
float cartesian_mm = sqrt(sq(difference[X_AXIS]) + sq(difference[Y_AXIS]) + sq(difference[Z_AXIS]));
if (cartesian_mm < 0.000001) cartesian_mm = abs(difference[E_AXIS]);
if (cartesian_mm < 0.000001) return false;
float seconds = 6000 * cartesian_mm / feedrate / feedrate_multiplier;
int steps = max(1, int(delta_segments_per_second * seconds));
// SERIAL_ECHOPGM("mm="); SERIAL_ECHO(cartesian_mm);
// SERIAL_ECHOPGM(" seconds="); SERIAL_ECHO(seconds);
// SERIAL_ECHOPGM(" steps="); SERIAL_ECHOLN(steps);
for (int s = 1; s <= steps; s++) {
float fraction = float(s) / float(steps);
for (int8_t i = 0; i < NUM_AXIS; i++)
destination[i] = current_position[i] + difference[i] * fraction;
calculate_delta(destination);
#ifdef ENABLE_AUTO_BED_LEVELING
adjust_delta(destination);
#endif
//SERIAL_ECHOPGM("destination[X_AXIS]="); SERIAL_ECHOLN(destination[X_AXIS]);
//SERIAL_ECHOPGM("destination[Y_AXIS]="); SERIAL_ECHOLN(destination[Y_AXIS]);
//SERIAL_ECHOPGM("destination[Z_AXIS]="); SERIAL_ECHOLN(destination[Z_AXIS]);
//SERIAL_ECHOPGM("delta[X_AXIS]="); SERIAL_ECHOLN(delta[X_AXIS]);
//SERIAL_ECHOPGM("delta[Y_AXIS]="); SERIAL_ECHOLN(delta[Y_AXIS]);
//SERIAL_ECHOPGM("delta[Z_AXIS]="); SERIAL_ECHOLN(delta[Z_AXIS]);
plan_buffer_line(delta[X_AXIS], delta[Y_AXIS], delta[Z_AXIS], destination[E_AXIS], feedrate/60*feedrate_multiplier/100.0, active_extruder);
}
return true;
}
#endif // DELTA || SCARA
#ifdef SCARA
inline bool prepare_move_scara() { return prepare_move_delta(); }
#endif
#ifdef DUAL_X_CARRIAGE
inline bool prepare_move_dual_x_carriage() {
if (active_extruder_parked) {
if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && active_extruder == 0) {
// move duplicate extruder into correct duplication position.
plan_set_position(inactive_extruder_x_pos, current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]);
plan_buffer_line(current_position[X_AXIS] + duplicate_extruder_x_offset,
current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], max_feedrate[X_AXIS], 1);
sync_plan_position();
st_synchronize();
extruder_duplication_enabled = true;
active_extruder_parked = false;
}
else if (dual_x_carriage_mode == DXC_AUTO_PARK_MODE) { // handle unparking of head
if (current_position[E_AXIS] == destination[E_AXIS]) {
// This is a travel move (with no extrusion)
// Skip it, but keep track of the current position
// (so it can be used as the start of the next non-travel move)
if (delayed_move_time != 0xFFFFFFFFUL) {
set_current_to_destination();
NOLESS(raised_parked_position[Z_AXIS], destination[Z_AXIS]);
delayed_move_time = millis();
return false;
}
}
delayed_move_time = 0;
// 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;
}
}
return true;
}
#endif // DUAL_X_CARRIAGE
#if !defined(DELTA) && !defined(SCARA)
inline bool prepare_move_cartesian() {
// Do not use feedrate_multiplier for E or Z only moves
if (current_position[X_AXIS] == destination[X_AXIS] && current_position[Y_AXIS] == destination[Y_AXIS]) {
line_to_destination();
}
else {
#ifdef MESH_BED_LEVELING
mesh_plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], (feedrate/60)*(feedrate_multiplier/100.0), active_extruder);
return false;
#else
line_to_destination(feedrate * feedrate_multiplier / 100.0);
#endif
}
return true;
}
#endif // !DELTA && !SCARA
/**
* Prepare a single move and get ready for the next one
*
* (This may call plan_buffer_line several times to put
* smaller moves into the planner for DELTA or SCARA.)
*/
void prepare_move() {
clamp_to_software_endstops(destination);
refresh_cmd_timeout();
#ifdef PREVENT_DANGEROUS_EXTRUDE
prevent_dangerous_extrude(current_position[E_AXIS], destination[E_AXIS]);
#endif
#ifdef SCARA
if (!prepare_move_scara()) return;
#elif defined(DELTA)
if (!prepare_move_delta()) return;
#endif
#ifdef DUAL_X_CARRIAGE
if (!prepare_move_dual_x_carriage()) return;
#endif
#if !defined(DELTA) && !defined(SCARA)
if (!prepare_move_cartesian()) return;
#endif
set_current_to_destination();
}
#if HAS_CONTROLLERFAN
void controllerFan() {
static millis_t lastMotor = 0; // Last time a motor was turned on
static millis_t lastMotorCheck = 0; // Last time the state was checked
millis_t ms = millis();
if (ms >= lastMotorCheck + 2500) { // Not a time critical function, so we only check every 2500ms
lastMotorCheck = ms;
if (X_ENABLE_READ == X_ENABLE_ON || Y_ENABLE_READ == Y_ENABLE_ON || Z_ENABLE_READ == Z_ENABLE_ON || soft_pwm_bed > 0
|| E0_ENABLE_READ == E_ENABLE_ON // If any of the drivers are enabled...
#if EXTRUDERS > 1
|| E1_ENABLE_READ == E_ENABLE_ON
#if HAS_X2_ENABLE
|| X2_ENABLE_READ == X_ENABLE_ON
#endif
#if EXTRUDERS > 2
|| E2_ENABLE_READ == E_ENABLE_ON
#if EXTRUDERS > 3
|| E3_ENABLE_READ == E_ENABLE_ON
#endif
#endif
#endif
) {
lastMotor = ms; //... set time to NOW so the fan will turn on
}
uint8_t speed = (lastMotor == 0 || ms >= lastMotor + (CONTROLLERFAN_SECS * 1000UL)) ? 0 : CONTROLLERFAN_SPEED;
// allows digital or PWM fan output to be used (see M42 handling)
digitalWrite(CONTROLLERFAN_PIN, speed);
analogWrite(CONTROLLERFAN_PIN, speed);
}
}
#endif // HAS_CONTROLLERFAN
#ifdef SCARA
void calculate_SCARA_forward_Transform(float f_scara[3]) {
// Perform forward kinematics, and place results in delta[3]
// The maths and first version has been done by QHARLEY . Integrated into masterbranch 06/2014 and slightly restructured by Joachim Cerny in June 2014
float x_sin, x_cos, y_sin, y_cos;
//SERIAL_ECHOPGM("f_delta x="); SERIAL_ECHO(f_scara[X_AXIS]);
//SERIAL_ECHOPGM(" y="); SERIAL_ECHO(f_scara[Y_AXIS]);
x_sin = sin(f_scara[X_AXIS]/SCARA_RAD2DEG) * Linkage_1;
x_cos = cos(f_scara[X_AXIS]/SCARA_RAD2DEG) * Linkage_1;
y_sin = sin(f_scara[Y_AXIS]/SCARA_RAD2DEG) * Linkage_2;
y_cos = cos(f_scara[Y_AXIS]/SCARA_RAD2DEG) * Linkage_2;
//SERIAL_ECHOPGM(" x_sin="); SERIAL_ECHO(x_sin);
//SERIAL_ECHOPGM(" x_cos="); SERIAL_ECHO(x_cos);
//SERIAL_ECHOPGM(" y_sin="); SERIAL_ECHO(y_sin);
//SERIAL_ECHOPGM(" y_cos="); SERIAL_ECHOLN(y_cos);
delta[X_AXIS] = x_cos + y_cos + SCARA_offset_x; //theta
delta[Y_AXIS] = x_sin + y_sin + SCARA_offset_y; //theta+phi
//SERIAL_ECHOPGM(" delta[X_AXIS]="); SERIAL_ECHO(delta[X_AXIS]);
//SERIAL_ECHOPGM(" delta[Y_AXIS]="); SERIAL_ECHOLN(delta[Y_AXIS]);
}
void calculate_delta(float cartesian[3]){
//reverse kinematics.
// Perform reversed kinematics, and place results in delta[3]
// The maths and first version has been done by QHARLEY . Integrated into masterbranch 06/2014 and slightly restructured by Joachim Cerny in June 2014
float SCARA_pos[2];
static float SCARA_C2, SCARA_S2, SCARA_K1, SCARA_K2, SCARA_theta, SCARA_psi;
SCARA_pos[X_AXIS] = cartesian[X_AXIS] * axis_scaling[X_AXIS] - SCARA_offset_x; //Translate SCARA to standard X Y
SCARA_pos[Y_AXIS] = cartesian[Y_AXIS] * axis_scaling[Y_AXIS] - SCARA_offset_y; // With scaling factor.
#if (Linkage_1 == Linkage_2)
SCARA_C2 = ( ( sq(SCARA_pos[X_AXIS]) + sq(SCARA_pos[Y_AXIS]) ) / (2 * (float)L1_2) ) - 1;
#else
SCARA_C2 = ( sq(SCARA_pos[X_AXIS]) + sq(SCARA_pos[Y_AXIS]) - (float)L1_2 - (float)L2_2 ) / 45000;
#endif
SCARA_S2 = sqrt( 1 - sq(SCARA_C2) );
SCARA_K1 = Linkage_1 + Linkage_2 * SCARA_C2;
SCARA_K2 = Linkage_2 * SCARA_S2;
SCARA_theta = ( atan2(SCARA_pos[X_AXIS],SCARA_pos[Y_AXIS])-atan2(SCARA_K1, SCARA_K2) ) * -1;
SCARA_psi = atan2(SCARA_S2,SCARA_C2);
delta[X_AXIS] = SCARA_theta * SCARA_RAD2DEG; // Multiply by 180/Pi - theta is support arm angle
delta[Y_AXIS] = (SCARA_theta + SCARA_psi) * SCARA_RAD2DEG; // - equal to sub arm angle (inverted motor)
delta[Z_AXIS] = cartesian[Z_AXIS];
/*
SERIAL_ECHOPGM("cartesian x="); SERIAL_ECHO(cartesian[X_AXIS]);
SERIAL_ECHOPGM(" y="); SERIAL_ECHO(cartesian[Y_AXIS]);
SERIAL_ECHOPGM(" z="); SERIAL_ECHOLN(cartesian[Z_AXIS]);
SERIAL_ECHOPGM("scara x="); SERIAL_ECHO(SCARA_pos[X_AXIS]);
SERIAL_ECHOPGM(" y="); SERIAL_ECHOLN(SCARA_pos[Y_AXIS]);
SERIAL_ECHOPGM("delta x="); SERIAL_ECHO(delta[X_AXIS]);
SERIAL_ECHOPGM(" y="); SERIAL_ECHO(delta[Y_AXIS]);
SERIAL_ECHOPGM(" z="); SERIAL_ECHOLN(delta[Z_AXIS]);
SERIAL_ECHOPGM("C2="); SERIAL_ECHO(SCARA_C2);
SERIAL_ECHOPGM(" S2="); SERIAL_ECHO(SCARA_S2);
SERIAL_ECHOPGM(" Theta="); SERIAL_ECHO(SCARA_theta);
SERIAL_ECHOPGM(" Psi="); SERIAL_ECHOLN(SCARA_psi);
SERIAL_EOL;
*/
}
#endif // SCARA
#ifdef TEMP_STAT_LEDS
static bool red_led = false;
static millis_t next_status_led_update_ms = 0;
void handle_status_leds(void) {
float max_temp = 0.0;
if (millis() > next_status_led_update_ms) {
next_status_led_update_ms += 500; // Update every 0.5s
for (int8_t cur_extruder = 0; cur_extruder < EXTRUDERS; ++cur_extruder)
max_temp = max(max(max_temp, degHotend(cur_extruder)), degTargetHotend(cur_extruder));
#if HAS_TEMP_BED
max_temp = max(max(max_temp, degTargetBed()), degBed());
#endif
bool new_led = (max_temp > 55.0) ? true : (max_temp < 54.0) ? false : red_led;
if (new_led != red_led) {
red_led = new_led;
digitalWrite(STAT_LED_RED, new_led ? HIGH : LOW);
digitalWrite(STAT_LED_BLUE, new_led ? LOW : HIGH);
}
}
}
#endif
void enable_all_steppers() {
enable_x();
enable_y();
enable_z();
enable_e0();
enable_e1();
enable_e2();
enable_e3();
}
void disable_all_steppers() {
disable_x();
disable_y();
disable_z();
disable_e0();
disable_e1();
disable_e2();
disable_e3();
}
/**
* Standard idle routine keeps the machine alive
*/
void idle() {
manage_heater();
manage_inactivity();
lcd_update();
}
/**
* Manage several activities:
* - Check for Filament Runout
* - Keep the command buffer full
* - Check for maximum inactive time between commands
* - Check for maximum inactive time between stepper commands
* - Check if pin CHDK needs to go LOW
* - Check for KILL button held down
* - Check for HOME button held down
* - Check if cooling fan needs to be switched on
* - Check if an idle but hot extruder needs filament extruded (EXTRUDER_RUNOUT_PREVENT)
*/
void manage_inactivity(bool ignore_stepper_queue/*=false*/) {
#if HAS_FILRUNOUT
if (IS_SD_PRINTING && !(READ(FILRUNOUT_PIN) ^ FIL_RUNOUT_INVERTING))
filrunout();
#endif
if (commands_in_queue < BUFSIZE - 1) get_command();
millis_t ms = millis();
if (max_inactive_time && ms > previous_cmd_ms + max_inactive_time) kill(PSTR(MSG_KILLED));
if (stepper_inactive_time && ms > previous_cmd_ms + stepper_inactive_time
&& !ignore_stepper_queue && !blocks_queued()) {
#if DISABLE_X == true
disable_x();
#endif
#if DISABLE_Y == true
disable_y();
#endif
#if DISABLE_Z == true
disable_z();
#endif
#if DISABLE_E == true
disable_e0();
disable_e1();
disable_e2();
disable_e3();
#endif
}
#ifdef CHDK // Check if pin should be set to LOW after M240 set it to HIGH
if (chdkActive && ms > chdkHigh + CHDK_DELAY) {
chdkActive = false;
WRITE(CHDK, LOW);
}
#endif
#if HAS_KILL
// Check if the kill button was pressed and wait just in case it was an accidental
// key kill key press
// -------------------------------------------------------------------------------
static int killCount = 0; // make the inactivity button a bit less responsive
const int KILL_DELAY = 750;
if (!READ(KILL_PIN))
killCount++;
else if (killCount > 0)
killCount--;
// Exceeded threshold and we can confirm that it was not accidental
// KILL the machine
// ----------------------------------------------------------------
if (killCount >= KILL_DELAY) kill(PSTR(MSG_KILLED));
#endif
#if HAS_HOME
// Check to see if we have to home, use poor man's debouncer
// ---------------------------------------------------------
static int homeDebounceCount = 0; // poor man's debouncing count
const int HOME_DEBOUNCE_DELAY = 750;
if (!READ(HOME_PIN)) {
if (!homeDebounceCount) {
enqueuecommands_P(PSTR("G28"));
LCD_MESSAGEPGM(MSG_AUTO_HOME);
}
if (homeDebounceCount < HOME_DEBOUNCE_DELAY)
homeDebounceCount++;
else
homeDebounceCount = 0;
}
#endif
#if HAS_CONTROLLERFAN
controllerFan(); // Check if fan should be turned on to cool stepper drivers down
#endif
#ifdef EXTRUDER_RUNOUT_PREVENT
if (ms > previous_cmd_ms + EXTRUDER_RUNOUT_SECONDS * 1000)
if (degHotend(active_extruder) > EXTRUDER_RUNOUT_MINTEMP) {
bool oldstatus;
switch(active_extruder) {
case 0:
oldstatus = E0_ENABLE_READ;
enable_e0();
break;
#if EXTRUDERS > 1
case 1:
oldstatus = E1_ENABLE_READ;
enable_e1();
break;
#if EXTRUDERS > 2
case 2:
oldstatus = E2_ENABLE_READ;
enable_e2();
break;
#if EXTRUDERS > 3
case 3:
oldstatus = E3_ENABLE_READ;
enable_e3();
break;
#endif
#endif
#endif
}
float oldepos = current_position[E_AXIS], oldedes = destination[E_AXIS];
plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS],
destination[E_AXIS] + EXTRUDER_RUNOUT_EXTRUDE * EXTRUDER_RUNOUT_ESTEPS / axis_steps_per_unit[E_AXIS],
EXTRUDER_RUNOUT_SPEED / 60. * EXTRUDER_RUNOUT_ESTEPS / axis_steps_per_unit[E_AXIS], active_extruder);
current_position[E_AXIS] = oldepos;
destination[E_AXIS] = oldedes;
plan_set_e_position(oldepos);
previous_cmd_ms = ms; // refresh_cmd_timeout()
st_synchronize();
switch(active_extruder) {
case 0:
E0_ENABLE_WRITE(oldstatus);
break;
#if EXTRUDERS > 1
case 1:
E1_ENABLE_WRITE(oldstatus);
break;
#if EXTRUDERS > 2
case 2:
E2_ENABLE_WRITE(oldstatus);
break;
#if EXTRUDERS > 3
case 3:
E3_ENABLE_WRITE(oldstatus);
break;
#endif
#endif
#endif
}
}
#endif
#ifdef DUAL_X_CARRIAGE
// handle delayed move timeout
if (delayed_move_time && ms > delayed_move_time + 1000 && IsRunning()) {
// travel moves have been received so enact them
delayed_move_time = 0xFFFFFFFFUL; // force moves to be done
set_destination_to_current();
prepare_move();
}
#endif
#ifdef TEMP_STAT_LEDS
handle_status_leds();
#endif
check_axes_activity();
}
void kill(const char *lcd_msg) {
#ifdef ULTRA_LCD
lcd_setalertstatuspgm(lcd_msg);
#endif
cli(); // Stop interrupts
disable_all_heaters();
disable_all_steppers();
#if HAS_POWER_SWITCH
pinMode(PS_ON_PIN, INPUT);
#endif
SERIAL_ERROR_START;
SERIAL_ERRORLNPGM(MSG_ERR_KILLED);
// FMC small patch to update the LCD before ending
sei(); // enable interrupts
for (int i = 5; i--; lcd_update()) delay(200); // Wait a short time
cli(); // disable interrupts
suicide();
while(1) { /* Intentionally left empty */ } // Wait for reset
}
#ifdef FILAMENT_RUNOUT_SENSOR
void filrunout() {
if (!filrunoutEnqueued) {
filrunoutEnqueued = true;
enqueuecommands_P(PSTR(FILAMENT_RUNOUT_SCRIPT));
st_synchronize();
}
}
#endif // FILAMENT_RUNOUT_SENSOR
#ifdef FAST_PWM_FAN
void setPwmFrequency(uint8_t pin, int val) {
val &= 0x07;
switch (digitalPinToTimer(pin)) {
#if defined(TCCR0A)
case TIMER0A:
case TIMER0B:
// TCCR0B &= ~(_BV(CS00) | _BV(CS01) | _BV(CS02));
// TCCR0B |= val;
break;
#endif
#if defined(TCCR1A)
case TIMER1A:
case TIMER1B:
// TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));
// TCCR1B |= val;
break;
#endif
#if defined(TCCR2)
case TIMER2:
case TIMER2:
TCCR2 &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));
TCCR2 |= val;
break;
#endif
#if defined(TCCR2A)
case TIMER2A:
case TIMER2B:
TCCR2B &= ~(_BV(CS20) | _BV(CS21) | _BV(CS22));
TCCR2B |= val;
break;
#endif
#if defined(TCCR3A)
case TIMER3A:
case TIMER3B:
case TIMER3C:
TCCR3B &= ~(_BV(CS30) | _BV(CS31) | _BV(CS32));
TCCR3B |= val;
break;
#endif
#if defined(TCCR4A)
case TIMER4A:
case TIMER4B:
case TIMER4C:
TCCR4B &= ~(_BV(CS40) | _BV(CS41) | _BV(CS42));
TCCR4B |= val;
break;
#endif
#if defined(TCCR5A)
case TIMER5A:
case TIMER5B:
case TIMER5C:
TCCR5B &= ~(_BV(CS50) | _BV(CS51) | _BV(CS52));
TCCR5B |= val;
break;
#endif
}
}
#endif // FAST_PWM_FAN
void Stop() {
disable_all_heaters();
if (IsRunning()) {
Running = false;
Stopped_gcode_LastN = gcode_LastN; // Save last g_code for restart
SERIAL_ERROR_START;
SERIAL_ERRORLNPGM(MSG_ERR_STOPPED);
LCD_MESSAGEPGM(MSG_STOPPED);
}
}
/**
* Set target_extruder from the T parameter or the active_extruder
*
* Returns TRUE if the target is invalid
*/
bool setTargetedHotend(int code) {
target_extruder = active_extruder;
if (code_seen('T')) {
target_extruder = code_value_short();
if (target_extruder >= EXTRUDERS) {
SERIAL_ECHO_START;
SERIAL_CHAR('M');
SERIAL_ECHO(code);
SERIAL_ECHOPGM(" " MSG_INVALID_EXTRUDER " ");
SERIAL_ECHOLN(target_extruder);
return true;
}
}
return false;
}
float calculate_volumetric_multiplier(float diameter) {
if (!volumetric_enabled || diameter == 0) return 1.0;
float d2 = diameter * 0.5;
return 1.0 / (M_PI * d2 * d2);
}
void calculate_volumetric_multipliers() {
for (int i=0; i