inline void home_z_safely() { // Disallow Z homing if X or Y are unknown if (!TEST(axis_known_position, X_AXIS) || !TEST(axis_known_position, Y_AXIS)) { LCD_MESSAGEPGM(MSG_ERR_Z_HOMING); SERIAL_ECHO_MSG(MSG_ERR_Z_HOMING); return; } #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("Z_SAFE_HOMING >>>"); #endif sync_plan_position(); /** * Move the Z probe (or just the nozzle) to the safe homing point */ destination[X_AXIS] = Z_SAFE_HOMING_X_POINT; destination[Y_AXIS] = Z_SAFE_HOMING_Y_POINT; destination[Z_AXIS] = current_position[Z_AXIS]; // Z is already at the right height #if HOMING_Z_WITH_PROBE destination[X_AXIS] -= X_PROBE_OFFSET_FROM_EXTRUDER; destination[Y_AXIS] -= Y_PROBE_OFFSET_FROM_EXTRUDER; #endif if (position_is_reachable(destination[X_AXIS], destination[Y_AXIS])) { #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING)) DEBUG_POS("Z_SAFE_HOMING", destination); #endif // This causes the carriage on Dual X to unpark #if ENABLED(DUAL_X_CARRIAGE) active_extruder_parked = false; #endif #if ENABLED(SENSORLESS_HOMING) safe_delay(500); // Short delay needed to settle #endif do_blocking_move_to_xy(destination[X_AXIS], destination[Y_AXIS]); homeaxis(Z_AXIS); } else { LCD_MESSAGEPGM(MSG_ZPROBE_OUT); SERIAL_ECHO_MSG(MSG_ZPROBE_OUT); } #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("<<< Z_SAFE_HOMING"); #endif }
bool PersistentStore::write_data(int &pos, const uint8_t *value, size_t size, uint16_t *crc) { while (size--) { uint8_t v = *value; // Save to either external EEPROM, program flash or Backup SRAM #if ENABLED(SPI_EEPROM) || ENABLED(I2C_EEPROM) // EEPROM has only ~100,000 write cycles, // so only write bytes that have changed! uint8_t * const p = (uint8_t * const)pos; if (v != eeprom_read_byte(p)) { eeprom_write_byte(p, v); if (eeprom_read_byte(p) != v) { SERIAL_ECHO_MSG(MSG_ERR_EEPROM_WRITE); return true; } } #elif DISABLED(EEPROM_EMULATED_WITH_SRAM) eeprom_buffered_write_byte(pos, v); #else *(__IO uint8_t *)(BKPSRAM_BASE + (uint8_t * const)pos) = v; #endif crc16(crc, &v, 1); pos++; value++; }; #if DISABLED(EEPROM_EMULATED_WITH_SRAM) && DISABLED(SPI_EEPROM) && DISABLED(I2C_EEPROM) eeprom_data_written = true; #endif return false; }
void dac_print_values() { if (!dac_present) return; SERIAL_ECHO_MSG("Stepper current values in % (Amps):"); SERIAL_ECHO_START(); SERIAL_ECHOPAIR(" X:", dac_perc(X_AXIS)); SERIAL_ECHOPAIR(" (", dac_amps(X_AXIS)); SERIAL_ECHOPAIR(") Y:", dac_perc(Y_AXIS)); SERIAL_ECHOPAIR(" (", dac_amps(Y_AXIS)); SERIAL_ECHOPAIR(") Z:", dac_perc(Z_AXIS)); SERIAL_ECHOPAIR(" (", dac_amps(Z_AXIS)); SERIAL_ECHOPAIR(") E:", dac_perc(E_AXIS)); SERIAL_ECHOPAIR(" (", dac_amps(E_AXIS)); SERIAL_ECHOLNPGM(")"); }
/** * Perform a tool-change, which may result in moving the * previous tool out of the way and the new tool into place. */ void tool_change(const uint8_t tmp_extruder, const float fr_mm_s/*=0.0*/, bool no_move/*=false*/) { #if ENABLED(MIXING_EXTRUDER) UNUSED(fr_mm_s); UNUSED(no_move); if (tmp_extruder >= MIXING_VIRTUAL_TOOLS) return invalid_extruder_error(tmp_extruder); #if MIXING_VIRTUAL_TOOLS > 1 // T0-Tnnn: Switch virtual tool by changing the index to the mix mixer.T(tmp_extruder); #endif #elif ENABLED(PRUSA_MMU2) UNUSED(fr_mm_s); UNUSED(no_move); mmu2.toolChange(tmp_extruder); #elif EXTRUDERS < 2 UNUSED(fr_mm_s); UNUSED(no_move); if (tmp_extruder) invalid_extruder_error(tmp_extruder); return; #else // EXTRUDERS > 1 planner.synchronize(); #if ENABLED(DUAL_X_CARRIAGE) // Only T0 allowed if the Printer is in DXC_DUPLICATION_MODE or DXC_MIRRORED_MODE if (tmp_extruder != 0 && dxc_is_duplicating()) return invalid_extruder_error(tmp_extruder); #endif #if HAS_LEVELING // Set current position to the physical position const bool leveling_was_active = planner.leveling_active; set_bed_leveling_enabled(false); #endif if (tmp_extruder >= EXTRUDERS) return invalid_extruder_error(tmp_extruder); if (!no_move && !all_axes_homed()) { no_move = true; if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("No move on toolchange"); } #if HAS_LCD_MENU ui.return_to_status(); #endif #if ENABLED(TOOLCHANGE_FILAMENT_SWAP) const bool should_swap = !no_move && toolchange_settings.swap_length; #if ENABLED(PREVENT_COLD_EXTRUSION) const bool too_cold = !DEBUGGING(DRYRUN) && (thermalManager.targetTooColdToExtrude(active_extruder) || thermalManager.targetTooColdToExtrude(tmp_extruder)); #else constexpr bool too_cold = false; #endif if (should_swap) { if (too_cold) { SERIAL_ECHO_MSG(MSG_ERR_HOTEND_TOO_COLD); #if ENABLED(SINGLENOZZLE) active_extruder = tmp_extruder; return; #endif } else { #if ENABLED(ADVANCED_PAUSE_FEATURE) do_pause_e_move(-toolchange_settings.swap_length, MMM_TO_MMS(toolchange_settings.retract_speed)); #else current_position[E_AXIS] -= toolchange_settings.swap_length / planner.e_factor[active_extruder]; planner.buffer_line(current_position, MMM_TO_MMS(toolchange_settings.retract_speed), active_extruder); #endif } } #endif // TOOLCHANGE_FILAMENT_SWAP if (tmp_extruder != active_extruder) { #if SWITCHING_NOZZLE_TWO_SERVOS raise_nozzle(active_extruder); #endif const float old_feedrate_mm_s = fr_mm_s > 0.0 ? fr_mm_s : feedrate_mm_s; feedrate_mm_s = fr_mm_s > 0.0 ? fr_mm_s : XY_PROBE_FEEDRATE_MM_S; #if HAS_SOFTWARE_ENDSTOPS && ENABLED(DUAL_X_CARRIAGE) update_software_endstops(X_AXIS, active_extruder, tmp_extruder); #endif set_destination_from_current(); if (!no_move) { #if DISABLED(SWITCHING_NOZZLE) // Do a small lift to avoid the workpiece in the move back (below) #if ENABLED(TOOLCHANGE_PARK) current_position[X_AXIS] = toolchange_settings.change_point.x; current_position[Y_AXIS] = toolchange_settings.change_point.y; #endif current_position[Z_AXIS] += toolchange_settings.z_raise; #if HAS_SOFTWARE_ENDSTOPS NOMORE(current_position[Z_AXIS], soft_endstop[Z_AXIS].max); #endif planner.buffer_line(current_position, feedrate_mm_s, active_extruder); #endif planner.synchronize(); } #if HAS_HOTEND_OFFSET #if ENABLED(DUAL_X_CARRIAGE) constexpr float xdiff = 0; #else const float xdiff = hotend_offset[X_AXIS][tmp_extruder] - hotend_offset[X_AXIS][active_extruder]; #endif const float ydiff = hotend_offset[Y_AXIS][tmp_extruder] - hotend_offset[Y_AXIS][active_extruder], zdiff = hotend_offset[Z_AXIS][tmp_extruder] - hotend_offset[Z_AXIS][active_extruder]; #else constexpr float xdiff = 0, ydiff = 0, zdiff = 0; #endif #if ENABLED(DUAL_X_CARRIAGE) dualx_tool_change(tmp_extruder, no_move); #elif ENABLED(PARKING_EXTRUDER) // Dual Parking extruder parking_extruder_tool_change(tmp_extruder, no_move); #elif ENABLED(MAGNETIC_PARKING_EXTRUDER) // Magnetic Parking extruder magnetic_parking_extruder_tool_change(tmp_extruder); #elif ENABLED(SWITCHING_TOOLHEAD) // Switching Toolhead switching_toolhead_tool_change(tmp_extruder, fr_mm_s, no_move); #elif ENABLED(SWITCHING_NOZZLE) && !SWITCHING_NOZZLE_TWO_SERVOS // Raise by a configured distance to avoid workpiece, except with // SWITCHING_NOZZLE_TWO_SERVOS, as both nozzles will lift instead. current_position[Z_AXIS] += MAX(-zdiff, 0.0) + toolchange_settings.z_raise; #if HAS_SOFTWARE_ENDSTOPS NOMORE(current_position[Z_AXIS], soft_endstop[Z_AXIS].max); #endif if (!no_move) fast_line_to_current(Z_AXIS); move_nozzle_servo(tmp_extruder); #endif if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("Offset Tool XY by { ", xdiff, ", ", ydiff, ", ", zdiff, " }"); // The newly-selected extruder XY is actually at... current_position[X_AXIS] += xdiff; current_position[Y_AXIS] += ydiff; current_position[Z_AXIS] += zdiff; // Set the new active extruder if not already done in tool specific function above active_extruder = tmp_extruder; // Tell the planner the new "current position" sync_plan_position(); #if ENABLED(DELTA) //LOOP_XYZ(i) update_software_endstops(i); // or modify the constrain function const bool safe_to_move = current_position[Z_AXIS] < delta_clip_start_height - 1; #else constexpr bool safe_to_move = true; #endif // Return to position and lower again if (safe_to_move && !no_move && IsRunning()) { if (DEBUGGING(LEVELING)) DEBUG_POS("Move back", destination); #if ENABLED(SINGLENOZZLE) #if FAN_COUNT > 0 singlenozzle_fan_speed[active_extruder] = thermalManager.fan_speed[0]; thermalManager.fan_speed[0] = singlenozzle_fan_speed[tmp_extruder]; #endif singlenozzle_temp[active_extruder] = thermalManager.temp_hotend[0].target; if (singlenozzle_temp[tmp_extruder] && singlenozzle_temp[tmp_extruder] != singlenozzle_temp[active_extruder]) { thermalManager.setTargetHotend(singlenozzle_temp[tmp_extruder], 0); #if EITHER(ULTRA_LCD, EXTENSIBLE_UI) thermalManager.set_heating_message(0); #endif (void)thermalManager.wait_for_hotend(0, false); // Wait for heating or cooling } active_extruder = tmp_extruder; #endif #if ENABLED(TOOLCHANGE_FILAMENT_SWAP) if (should_swap && !too_cold) { #if ENABLED(ADVANCED_PAUSE_FEATURE) do_pause_e_move(toolchange_settings.swap_length + TOOLCHANGE_FIL_EXTRA_PRIME, toolchange_settings.prime_speed); #else current_position[E_AXIS] += (toolchange_settings.swap_length + TOOLCHANGE_FIL_EXTRA_PRIME) / planner.e_factor[tmp_extruder]; planner.buffer_line(current_position, toolchange_settings.prime_speed, tmp_extruder); #endif planner.synchronize(); #if TOOLCHANGE_FIL_EXTRA_PRIME planner.set_e_position_mm((destination[E_AXIS] = current_position[E_AXIS] = current_position[E_AXIS] - (TOOLCHANGE_FIL_EXTRA_PRIME))); #endif } #endif // Prevent a move outside physical bounds apply_motion_limits(destination); // Move back to the original (or tweaked) position do_blocking_move_to(destination); #if ENABLED(DUAL_X_CARRIAGE) active_extruder_parked = false; #endif feedrate_mm_s = old_feedrate_mm_s; } #if ENABLED(SWITCHING_NOZZLE) else { // Move back down. (Including when the new tool is higher.) do_blocking_move_to_z(destination[Z_AXIS], planner.settings.max_feedrate_mm_s[Z_AXIS]); } #endif #if ENABLED(PRUSA_MMU2) mmu2.toolChange(tmp_extruder); #endif #if SWITCHING_NOZZLE_TWO_SERVOS lower_nozzle(active_extruder); #endif #if ENABLED(TOOLCHANGE_FILAMENT_SWAP) && ADVANCED_PAUSE_RESUME_PRIME != 0 if (should_swap && !too_cold) { const float resume_eaxis = current_position[E_AXIS]; #if ENABLED(ADVANCED_PAUSE_FEATURE) do_pause_e_move(toolchange_settings.swap_length, toolchange_settings.prime_speed); #else current_position[E_AXIS] += (ADVANCED_PAUSE_RESUME_PRIME) / planner.e_factor[active_extruder]; planner.buffer_line(current_position, ADVANCED_PAUSE_PURGE_FEEDRATE, active_extruder); #endif planner.synchronize(); planner.set_e_position_mm((destination[E_AXIS] = current_position[E_AXIS] = resume_eaxis)); } #endif } // (tmp_extruder != active_extruder) planner.synchronize(); #if ENABLED(EXT_SOLENOID) && DISABLED(PARKING_EXTRUDER) disable_all_solenoids(); enable_solenoid_on_active_extruder(); #endif #if ENABLED(MK2_MULTIPLEXER) if (tmp_extruder >= E_STEPPERS) return invalid_extruder_error(tmp_extruder); select_multiplexed_stepper(tmp_extruder); #endif #if DO_SWITCH_EXTRUDER planner.synchronize(); move_extruder_servo(active_extruder); #endif #if HAS_FANMUX fanmux_switch(active_extruder); #endif #if HAS_LEVELING // Restore leveling to re-establish the logical position set_bed_leveling_enabled(leveling_was_active); #endif SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR(MSG_ACTIVE_EXTRUDER, int(active_extruder)); #endif // EXTRUDERS > 1 }
/** * M420: Enable/Disable Bed Leveling and/or set the Z fade height. * * S[bool] Turns leveling on or off * Z[height] Sets the Z fade height (0 or none to disable) * V[bool] Verbose - Print the leveling grid * * With AUTO_BED_LEVELING_UBL only: * * L[index] Load UBL mesh from index (0 is default) * T[map] 0:Human-readable 1:CSV 2:"LCD" 4:Compact * * With mesh-based leveling only: * * C Center mesh on the mean of the lowest and highest * * With MARLIN_DEV_MODE: * S2 Create a simple random mesh and enable */ void GcodeSuite::M420() { const bool seen_S = parser.seen('S'), to_enable = seen_S ? parser.value_bool() : planner.leveling_active; #if ENABLED(MARLIN_DEV_MODE) if (parser.intval('S') == 2) { #if ENABLED(AUTO_BED_LEVELING_BILINEAR) bilinear_start[X_AXIS] = MIN_PROBE_X; bilinear_start[Y_AXIS] = MIN_PROBE_Y; bilinear_grid_spacing[X_AXIS] = (MAX_PROBE_X - (MIN_PROBE_X)) / (GRID_MAX_POINTS_X - 1); bilinear_grid_spacing[Y_AXIS] = (MAX_PROBE_Y - (MIN_PROBE_Y)) / (GRID_MAX_POINTS_Y - 1); #endif for (uint8_t x = 0; x < GRID_MAX_POINTS_X; x++) for (uint8_t y = 0; y < GRID_MAX_POINTS_Y; y++) Z_VALUES(x, y) = 0.001 * random(-200, 200); SERIAL_ECHOPGM("Simulated " STRINGIFY(GRID_MAX_POINTS_X) "x" STRINGIFY(GRID_MAX_POINTS_X) " mesh "); SERIAL_ECHOPAIR(" (", MIN_PROBE_X); SERIAL_CHAR(','); SERIAL_ECHO(MIN_PROBE_Y); SERIAL_ECHOPAIR(")-(", MAX_PROBE_X); SERIAL_CHAR(','); SERIAL_ECHO(MAX_PROBE_Y); SERIAL_ECHOLNPGM(")"); } #endif // If disabling leveling do it right away // (Don't disable for just M420 or M420 V) if (seen_S && !to_enable) set_bed_leveling_enabled(false); const float oldpos[] = { current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] }; #if ENABLED(AUTO_BED_LEVELING_UBL) // L to load a mesh from the EEPROM if (parser.seen('L')) { set_bed_leveling_enabled(false); #if ENABLED(EEPROM_SETTINGS) const int8_t storage_slot = parser.has_value() ? parser.value_int() : ubl.storage_slot; const int16_t a = settings.calc_num_meshes(); if (!a) { SERIAL_ECHOLNPGM("?EEPROM storage not available."); return; } if (!WITHIN(storage_slot, 0, a - 1)) { SERIAL_ECHOLNPGM("?Invalid storage slot."); SERIAL_ECHOLNPAIR("?Use 0 to ", a - 1); return; } settings.load_mesh(storage_slot); ubl.storage_slot = storage_slot; #else SERIAL_ECHOLNPGM("?EEPROM storage not available."); return; #endif } // L or V display the map info if (parser.seen('L') || parser.seen('V')) { ubl.display_map(parser.byteval('T')); SERIAL_ECHOPGM("Mesh is "); if (!ubl.mesh_is_valid()) SERIAL_ECHOPGM("in"); SERIAL_ECHOLNPAIR("valid\nStorage slot: ", ubl.storage_slot); } #endif // AUTO_BED_LEVELING_UBL const bool seenV = parser.seen('V'); #if HAS_MESH if (leveling_is_valid()) { // Subtract the given value or the mean from all mesh values if (parser.seen('C')) { const float cval = parser.value_float(); #if ENABLED(AUTO_BED_LEVELING_UBL) set_bed_leveling_enabled(false); ubl.adjust_mesh_to_mean(true, cval); #else #if ENABLED(M420_C_USE_MEAN) // Get the sum and average of all mesh values float mesh_sum = 0; for (uint8_t x = GRID_MAX_POINTS_X; x--;) for (uint8_t y = GRID_MAX_POINTS_Y; y--;) mesh_sum += Z_VALUES(x, y); const float zmean = mesh_sum / float(GRID_MAX_POINTS); #else // Find the low and high mesh values float lo_val = 100, hi_val = -100; for (uint8_t x = GRID_MAX_POINTS_X; x--;) for (uint8_t y = GRID_MAX_POINTS_Y; y--;) { const float z = Z_VALUES(x, y); NOMORE(lo_val, z); NOLESS(hi_val, z); } // Take the mean of the lowest and highest const float zmean = (lo_val + hi_val) / 2.0 + cval; #endif // If not very close to 0, adjust the mesh if (!NEAR_ZERO(zmean)) { set_bed_leveling_enabled(false); // Subtract the mean from all values for (uint8_t x = GRID_MAX_POINTS_X; x--;) for (uint8_t y = GRID_MAX_POINTS_Y; y--;) Z_VALUES(x, y) -= zmean; #if ENABLED(ABL_BILINEAR_SUBDIVISION) bed_level_virt_interpolate(); #endif } #endif } } else if (to_enable || seenV) { SERIAL_ECHO_MSG("Invalid mesh."); goto EXIT_M420; } #endif // HAS_MESH // V to print the matrix or mesh if (seenV) { #if ABL_PLANAR planner.bed_level_matrix.debug(PSTR("Bed Level Correction Matrix:")); #else if (leveling_is_valid()) { #if ENABLED(AUTO_BED_LEVELING_BILINEAR) print_bilinear_leveling_grid(); #if ENABLED(ABL_BILINEAR_SUBDIVISION) print_bilinear_leveling_grid_virt(); #endif #elif ENABLED(MESH_BED_LEVELING) SERIAL_ECHOLNPGM("Mesh Bed Level data:"); mbl.report_mesh(); #endif } #endif } #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) if (parser.seen('Z')) set_z_fade_height(parser.value_linear_units(), false); #endif // Enable leveling if specified, or if previously active set_bed_leveling_enabled(to_enable); #if HAS_MESH EXIT_M420: #endif // Error if leveling failed to enable or reenable if (to_enable && !planner.leveling_active) SERIAL_ERROR_MSG(MSG_ERR_M420_FAILED); SERIAL_ECHO_START(); SERIAL_ECHOPGM("Bed Leveling "); serialprintln_onoff(planner.leveling_active); #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) SERIAL_ECHO_START(); SERIAL_ECHOPGM("Fade Height "); if (planner.z_fade_height > 0.0) SERIAL_ECHOLN(planner.z_fade_height); else SERIAL_ECHOLNPGM(MSG_OFF); #endif // Report change in position if (memcmp(oldpos, current_position, sizeof(oldpos))) report_current_position(); }