/** * Print calibration results for plotting or manual frame adjustment. */ void Bed_level::print_2d_array(const uint8_t sx, const uint8_t sy, const uint8_t precision, float (*fn)(const uint8_t, const uint8_t)) { #if DISABLED(SCAD_MESH_OUTPUT) SERIAL_STR(ECHO); for (uint8_t x = 0; x < sx; x++) { for (uint8_t i = 0; i < precision + 2 + (x < 10 ? 1 : 0); i++) SERIAL_CHR(' '); SERIAL_VAL((int)x); } SERIAL_EOL(); #endif #if ENABLED(SCAD_MESH_OUTPUT) SERIAL_EM("measured_z = ["); // open 2D array #endif for (uint8_t y = 0; y < sy; y++) { #if ENABLED(SCAD_MESH_OUTPUT) SERIAL_MSG(" ["); // open sub-array #else SERIAL_STR(ECHO); if (y < 10) SERIAL_CHR(' '); SERIAL_VAL((int)y); #endif for (uint8_t x = 0; x < sx; x++) { SERIAL_CHR(' '); const float offset = fn(x, y); if (!isnan(offset)) { if (offset >= 0) SERIAL_CHR('+'); SERIAL_VAL(offset, precision); } else { #if ENABLED(SCAD_MESH_OUTPUT) for (uint8_t i = 3; i < precision + 3; i++) SERIAL_CHR(' '); SERIAL_MSG("NAN"); #else for (uint8_t i = 0; i < precision + 3; i++) SERIAL_CHR(i ? '=' : ' '); #endif } #if ENABLED(SCAD_MESH_OUTPUT) if (x < sx - 1) SERIAL_CHR(','); #endif } #if ENABLED(SCAD_MESH_OUTPUT) SERIAL_CHR(' '); SERIAL_CHR(']'); // close sub-array if (y < sy - 1) SERIAL_CHR(','); #endif SERIAL_EOL(); } #if ENABLED(SCAD_MESH_OUTPUT) SERIAL_MSG("\n];"); // close 2D array #endif SERIAL_EOL(); }
void PrintJobRecovery::debug(PGM_P const prefix) { serialprintPGM(prefix); SERIAL_ECHOLNPAIR(" Job Recovery Info...\nvalid_head:", int(info.valid_head), " valid_foot:", int(info.valid_foot)); if (info.valid_head) { if (info.valid_head == info.valid_foot) { SERIAL_ECHOPGM("current_position: "); LOOP_XYZE(i) { SERIAL_ECHO(info.current_position[i]); if (i < E_AXIS) SERIAL_CHAR(','); } SERIAL_EOL(); SERIAL_ECHOLNPAIR("feedrate: ", info.feedrate); #if HOTENDS > 1 SERIAL_ECHOLNPAIR("active_hotend: ", int(info.active_hotend)); #endif SERIAL_ECHOPGM("target_temperature: "); HOTEND_LOOP() { SERIAL_ECHO(info.target_temperature[e]); if (e < HOTENDS - 1) SERIAL_CHAR(','); } SERIAL_EOL(); #if HAS_HEATED_BED SERIAL_ECHOLNPAIR("target_temperature_bed: ", info.target_temperature_bed); #endif #if FAN_COUNT SERIAL_ECHOPGM("fan_speed: "); FANS_LOOP(i) { SERIAL_ECHO(int(info.fan_speed[i])); if (i < FAN_COUNT - 1) SERIAL_CHAR(','); } SERIAL_EOL(); #endif #if HAS_LEVELING SERIAL_ECHOLNPAIR("leveling: ", int(info.leveling), "\n fade: ", int(info.fade)); #endif #if ENABLED(FWRETRACT) SERIAL_ECHOPGM("retract: "); for (int8_t e = 0; e < EXTRUDERS; e++) { SERIAL_ECHO(info.retract[e]); if (e < EXTRUDERS - 1) SERIAL_CHAR(','); } SERIAL_EOL(); SERIAL_ECHOLNPAIR("retract_hop: ", info.retract_hop); #endif SERIAL_ECHOLNPAIR("cmd_queue_index_r: ", int(info.cmd_queue_index_r)); SERIAL_ECHOLNPAIR("commands_in_queue: ", int(info.commands_in_queue)); for (uint8_t i = 0; i < info.commands_in_queue; i++) SERIAL_ECHOLNPAIR("> ", info.command_queue[i]); SERIAL_ECHOLNPAIR("sd_filename: ", info.sd_filename); SERIAL_ECHOLNPAIR("sdpos: ", info.sdpos); SERIAL_ECHOLNPAIR("print_job_elapsed: ", info.print_job_elapsed); } else
void debug_current_and_destination(PGM_P title) { // if the title message starts with a '!' it is so important, we are going to // ignore the status of the g26_debug_flag if (*title != '!' && !g26_debug_flag) return; const float de = destination[E_AXIS] - current_position[E_AXIS]; if (de == 0.0) return; // Printing moves only const float dx = destination[X_AXIS] - current_position[X_AXIS], dy = destination[Y_AXIS] - current_position[Y_AXIS], xy_dist = HYPOT(dx, dy); if (xy_dist == 0.0) return; const float fpmm = de / xy_dist; SERIAL_ECHOPAIR_F(" fpmm=", fpmm, 6); SERIAL_ECHOPAIR_F(" current=( ", current_position[X_AXIS], 6); SERIAL_ECHOPAIR_F(", ", current_position[Y_AXIS], 6); SERIAL_ECHOPAIR_F(", ", current_position[Z_AXIS], 6); SERIAL_ECHOPAIR_F(", ", current_position[E_AXIS], 6); SERIAL_ECHOPGM(" ) destination=( "); debug_echo_axis(X_AXIS); SERIAL_ECHOPGM(", "); debug_echo_axis(Y_AXIS); SERIAL_ECHOPGM(", "); debug_echo_axis(Z_AXIS); SERIAL_ECHOPGM(", "); debug_echo_axis(E_AXIS); SERIAL_ECHOPGM(" ) "); serialprintPGM(title); SERIAL_EOL(); }
/** * M900: Get or Set Linear Advance K-factor * * K<factor> Set advance K factor */ void GcodeSuite::M900() { #if EXTRUDERS < 2 constexpr uint8_t tmp_extruder = 0; #else const uint8_t tmp_extruder = parser.seenval('T') ? parser.value_int() : active_extruder; if (tmp_extruder >= EXTRUDERS) { SERIAL_ECHOLNPGM("?T value out of range."); return; } #endif if (parser.seenval('K')) { const float newK = parser.floatval('K'); if (WITHIN(newK, 0, 10)) { planner.synchronize(); planner.extruder_advance_K[tmp_extruder] = newK; } else SERIAL_ECHOLNPGM("?K value out of range (0-10)."); } else { SERIAL_ECHO_START(); #if EXTRUDERS < 2 SERIAL_ECHOLNPAIR("Advance K=", planner.extruder_advance_K[0]); #else SERIAL_ECHOPGM("Advance K"); LOOP_L_N(i, EXTRUDERS) { SERIAL_CHAR(' '); SERIAL_ECHO(int(i)); SERIAL_CHAR('='); SERIAL_ECHO(planner.extruder_advance_K[i]); } SERIAL_EOL(); #endif }
/** * M100 D * Dump the free memory block from __brkval to the stack pointer. * malloc() eats memory from the start of the block and the stack grows * up from the bottom of the block. Solid test bytes indicate nothing has * used that memory yet. There should not be anything but test bytes within * the block. If so, it may indicate memory corruption due to a bad pointer. * Unexpected bytes are flagged in the right column. */ void dump_free_memory(const char *ptr, const char *sp) { // // Start and end the dump on a nice 16 byte boundary // (even though the values are not 16-byte aligned). // ptr = (char *)((uint16_t)ptr & 0xFFF0); // Align to 16-byte boundary sp = (char *)((uint16_t)sp | 0x000F); // Align sp to the 15th byte (at or above sp) // Dump command main loop while (ptr < sp) { print_hex_word((uint16_t)ptr); // Print the address SERIAL_CHAR(':'); for (uint8_t i = 0; i < 16; i++) { // and 16 data bytes if (i == 8) SERIAL_CHAR('-'); print_hex_byte(ptr[i]); SERIAL_CHAR(' '); } safe_delay(25); SERIAL_CHAR('|'); // Point out non test bytes for (uint8_t i = 0; i < 16; i++) { char ccc = (char)ptr[i]; // cast to char before automatically casting to char on assignment, in case the compiler is broken if (&ptr[i] >= (const char*)command_queue && &ptr[i] < (const char*)(command_queue + sizeof(command_queue))) { // Print out ASCII in the command buffer area if (!WITHIN(ccc, ' ', 0x7E)) ccc = ' '; } else { // If not in the command buffer area, flag bytes that don't match the test byte ccc = (ccc == TEST_BYTE) ? ' ' : '?'; } SERIAL_CHAR(ccc); } SERIAL_EOL(); ptr += 16; safe_delay(25); idle(); } }
bool set_bltouch_deployed(const bool deploy) { if (deploy && TEST_BLTOUCH()) { // If BL-Touch says it's triggered bltouch_command(BLTOUCH_RESET); // try to reset it. bltouch_command(BLTOUCH_DEPLOY); // Also needs to deploy and stow to bltouch_command(BLTOUCH_STOW); // clear the triggered condition. safe_delay(1500); // Wait for internal self-test to complete. // (Measured completion time was 0.65 seconds // after reset, deploy, and stow sequence) if (TEST_BLTOUCH()) { // If it still claims to be triggered... SERIAL_ERROR_START(); SERIAL_ERRORLNPGM(MSG_STOP_BLTOUCH); stop(); // punt! return true; } } bltouch_command(deploy ? BLTOUCH_DEPLOY : BLTOUCH_STOW); #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING)) { SERIAL_ECHOPAIR("set_bltouch_deployed(", deploy); SERIAL_CHAR(')'); SERIAL_EOL(); } #endif return false; }
int check_for_free_memory_corruption(const char * const title) { SERIAL_ECHO(title); char *ptr = END_OF_HEAP(), *sp = top_of_stack(); int n = sp - ptr; SERIAL_ECHOPAIR("\nfmc() n=", n); SERIAL_ECHOPAIR("\n&__brkval: ", hex_address(&__brkval)); SERIAL_ECHOPAIR("=", hex_address(__brkval)); SERIAL_ECHOPAIR("\n__bss_end: ", hex_address(&__bss_end)); SERIAL_ECHOPAIR(" sp=", hex_address(sp)); if (sp < ptr) { SERIAL_ECHOPGM(" sp < Heap "); // SET_INPUT_PULLUP(63); // if the developer has a switch wired up to their controller board // safe_delay(5); // this code can be enabled to pause the display as soon as the // while ( READ(63)) // malfunction is detected. It is currently defaulting to a switch // idle(); // being on pin-63 which is unassigend and available on most controller // safe_delay(20); // boards. // while ( !READ(63)) // idle(); safe_delay(20); #ifdef M100_FREE_MEMORY_DUMPER M100_dump_routine(" Memory corruption detected with sp<Heap\n", (char*)0x1B80, (char*)0x21FF); #endif } // Scan through the range looking for the biggest block of 0xE5's we can find int block_cnt = 0; for (int i = 0; i < n; i++) { if (ptr[i] == TEST_BYTE) { int16_t j = count_test_bytes(ptr + i); if (j > 8) { // SERIAL_ECHOPAIR("Found ", j); // SERIAL_ECHOLNPAIR(" bytes free at ", hex_address(ptr + i)); i += j; block_cnt++; SERIAL_ECHOPAIR(" (", block_cnt); SERIAL_ECHOPAIR(") found=", j); SERIAL_ECHOPGM(" "); } } } SERIAL_ECHOPAIR(" block_found=", block_cnt); if (block_cnt != 1 || __brkval != 0x0000) SERIAL_ECHOLNPGM("\nMemory Corruption detected in free memory area."); if (block_cnt == 0) // Make sure the special case of no free blocks shows up as an block_cnt = -1; // error to the calling code! SERIAL_ECHOPGM(" return="); if (block_cnt == 1) { SERIAL_CHAR('0'); // if the block_cnt is 1, nothing has broken up the free memory SERIAL_EOL(); // area and it is appropriate to say 'no corruption'. return 0; } SERIAL_ECHOLNPGM("true"); return block_cnt; }
void print_xyz(PGM_P const prefix, PGM_P const suffix, const float x, const float y, const float z) { serialprintPGM(prefix); SERIAL_CHAR('('); SERIAL_ECHO(x); SERIAL_ECHOPAIR(", ", y); SERIAL_ECHOPAIR(", ", z); SERIAL_CHAR(')'); if (suffix) serialprintPGM(suffix); else SERIAL_EOL(); }
void report_xyze(const float pos[XYZE], const uint8_t n = 4, const uint8_t precision = 3) { char str[12]; for (uint8_t i = 0; i < n; i++) { SERIAL_CHAR(' '); SERIAL_CHAR(axis_codes[i]); SERIAL_CHAR(':'); SERIAL_PROTOCOL(dtostrf(pos[i], 8, precision, str)); } SERIAL_EOL(); }
void automatic_current_control(TMC2130Stepper &st, String axisID) { // Check otpw even if we don't use automatic control. Allows for flag inspection. const bool is_otpw = st.checkOT(); // Report if a warning was triggered static bool previous_otpw = false; if (is_otpw && !previous_otpw) { char timestamp[10]; duration_t elapsed = print_job_timer.duration(); const bool has_days = (elapsed.value > 60*60*24L); (void)elapsed.toDigital(timestamp, has_days); SERIAL_ECHO(timestamp); SERIAL_ECHOPGM(": "); SERIAL_ECHO(axisID); SERIAL_ECHOLNPGM(" driver overtemperature warning!"); } previous_otpw = is_otpw; #if ENABLED(AUTOMATIC_CURRENT_CONTROL) && CURRENT_STEP > 0 // Return if user has not enabled current control start with M906 S1. if (!auto_current_control) return; /** * Decrease current if is_otpw is true. * Bail out if driver is disabled. * Increase current if OTPW has not been triggered yet. */ uint16_t current = st.getCurrent(); if (is_otpw) { st.setCurrent(current - CURRENT_STEP, R_SENSE, HOLD_MULTIPLIER); #if ENABLED(REPORT_CURRENT_CHANGE) SERIAL_ECHO(axisID); SERIAL_ECHOPAIR(" current decreased to ", st.getCurrent()); #endif } else if (!st.isEnabled()) return; else if (!is_otpw && !st.getOTPW()) { current += CURRENT_STEP; if (current <= AUTO_ADJUST_MAX) { st.setCurrent(current, R_SENSE, HOLD_MULTIPLIER); #if ENABLED(REPORT_CURRENT_CHANGE) SERIAL_ECHO(axisID); SERIAL_ECHOPAIR(" current increased to ", st.getCurrent()); #endif } } SERIAL_EOL(); #endif }
/** * M100 C<num> * Corrupt <num> locations in the free memory pool and report the corrupt addresses. * This is useful to check the correctness of the M100 D and the M100 F commands. */ void corrupt_free_memory(char *ptr, const uint16_t size) { ptr += 8; const uint16_t near_top = top_of_stack() - ptr - 250, // -250 to avoid interrupt activity that's altered the stack. j = near_top / (size + 1); SERIAL_ECHOLNPGM("Corrupting free memory block.\n"); for (uint16_t i = 1; i <= size; i++) { char * const addr = ptr + i * j; *addr = i; SERIAL_ECHOPAIR("\nCorrupting address: ", hex_address(addr)); } SERIAL_EOL(); }
/** * Raise Z to a minimum height to make room for a probe to move */ inline void do_probe_raise(const float z_raise) { #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING)) { SERIAL_ECHOPAIR("do_probe_raise(", z_raise); SERIAL_CHAR(')'); SERIAL_EOL(); } #endif float z_dest = z_raise; if (zprobe_zoffset < 0) z_dest -= zprobe_zoffset; if (z_dest > current_position[Z_AXIS]) do_blocking_move_to_z(z_dest); }
/** * Method to dock/undock a sled designed by Charles Bell. * * stow[in] If false, move to MAX_X and engage the solenoid * If true, move to MAX_X and release the solenoid */ static void dock_sled(bool stow) { #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING)) { SERIAL_ECHOPAIR("dock_sled(", stow); SERIAL_CHAR(')'); SERIAL_EOL(); } #endif // Dock sled a bit closer to ensure proper capturing do_blocking_move_to_x(X_MAX_POS + SLED_DOCKING_OFFSET - ((stow) ? 1 : 0)); #if HAS_SOLENOID_1 && DISABLED(EXT_SOLENOID) WRITE(SOL1_PIN, !stow); // switch solenoid #endif }
/** * Extrapolate a single point from its neighbors */ static void extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir) { #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING)) { SERIAL_ECHOPGM("Extrapolate ["); if (x < 10) SERIAL_CHAR(' '); SERIAL_ECHO((int)x); SERIAL_CHAR(xdir ? (xdir > 0 ? '+' : '-') : ' '); SERIAL_CHAR(' '); if (y < 10) SERIAL_CHAR(' '); SERIAL_ECHO((int)y); SERIAL_CHAR(ydir ? (ydir > 0 ? '+' : '-') : ' '); SERIAL_CHAR(']'); } #endif if (!isnan(z_values[x][y])) { #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM(" (done)"); #endif return; // Don't overwrite good values. } SERIAL_EOL(); // Get X neighbors, Y neighbors, and XY neighbors const uint8_t x1 = x + xdir, y1 = y + ydir, x2 = x1 + xdir, y2 = y1 + ydir; float a1 = z_values[x1][y ], a2 = z_values[x2][y ], b1 = z_values[x ][y1], b2 = z_values[x ][y2], c1 = z_values[x1][y1], c2 = z_values[x2][y2]; // Treat far unprobed points as zero, near as equal to far if (isnan(a2)) a2 = 0.0; if (isnan(a1)) a1 = a2; if (isnan(b2)) b2 = 0.0; if (isnan(b1)) b1 = b2; if (isnan(c2)) c2 = 0.0; if (isnan(c1)) c1 = c2; const float a = 2 * a1 - a2, b = 2 * b1 - b2, c = 2 * c1 - c2; // Take the average instead of the median z_values[x][y] = (a + b + c) / 3.0; // Median is robust (ignores outliers). // z_values[x][y] = (a < b) ? ((b < c) ? b : (c < a) ? a : c) // : ((c < b) ? b : (a < c) ? a : c); }
/** * M100 I * Init memory for the M100 tests. (Automatically applied on the first M100.) */ void init_free_memory(char *ptr, int16_t size) { SERIAL_ECHOLNPGM("Initializing free memory block.\n\n"); size -= 250; // -250 to avoid interrupt activity that's altered the stack. if (size < 0) { SERIAL_ECHOLNPGM("Unable to initialize.\n"); return; } ptr += 8; // move a few bytes away from the heap just because we don't want // to be altering memory that close to it. memset(ptr, TEST_BYTE, size); SERIAL_ECHO(size); SERIAL_ECHOLNPGM(" bytes of memory initialized.\n"); for (int16_t i = 0; i < size; i++) { if (ptr[i] != TEST_BYTE) { SERIAL_ECHOPAIR("? address : ", hex_address(ptr + i)); SERIAL_ECHOLNPAIR("=", hex_byte(ptr[i])); SERIAL_EOL(); } } }
// Populate all fields by parsing a single line of GCode // 58 bytes of SRAM are used to speed up seen/value void GCodeParser::parse(char *p) { reset(); // No codes to report // Skip spaces while (*p == ' ') ++p; // Skip N[-0-9] if included in the command line if (*p == 'N' && NUMERIC_SIGNED(p[1])) { #if ENABLED(FASTER_GCODE_PARSER) //set('N', p + 1); // (optional) Set the 'N' parameter value #endif p += 2; // skip N[-0-9] while (NUMERIC(*p)) ++p; // skip [0-9]* while (*p == ' ') ++p; // skip [ ]* } // *p now points to the current command, which should be G, M, or T command_ptr = p; // Get the command letter, which must be G, M, or T const char letter = *p++; // Nullify asterisk and trailing whitespace char *starpos = strchr(p, '*'); if (starpos) { --starpos; // * while (*starpos == ' ') --starpos; // spaces... starpos[1] = '\0'; } // Bail if the letter is not G, M, or T switch (letter) { case 'G': case 'M': case 'T': break; default: return; } // Skip spaces to get the numeric part while (*p == ' ') p++; // Bail if there's no command code number if (!NUMERIC(*p)) return; // Save the command letter at this point // A '?' signifies an unknown command command_letter = letter; // Get the code number - integer digits only codenum = 0; do { codenum *= 10, codenum += *p++ - '0'; } while (NUMERIC(*p)); // Allow for decimal point in command #if USE_GCODE_SUBCODES if (*p == '.') { p++; while (NUMERIC(*p)) subcode *= 10, subcode += *p++ - '0'; } #endif // Skip all spaces to get to the first argument, or nul while (*p == ' ') p++; // The command parameters (if any) start here, for sure! #if DISABLED(FASTER_GCODE_PARSER) command_args = p; // Scan for parameters in seen() #endif // Only use string_arg for these M codes if (letter == 'M') switch (codenum) { case 23: case 28: case 30: case 117: case 118: case 928: string_arg = p; return; default: break; } #if ENABLED(DEBUG_GCODE_PARSER) const bool debug = codenum == 800; #endif /** * Find all parameters, set flags and pointers for fast parsing * * Most codes ignore 'string_arg', but those that want a string will get the right pointer. * The following loop assigns the first "parameter" having no numeric value to 'string_arg'. * This allows M0/M1 with expire time to work: "M0 S5 You Win!" */ string_arg = NULL; while (char code = *p++) { // Get the next parameter. A NUL ends the loop // Special handling for M32 [P] !/path/to/file.g# // The path must be the last parameter if (code == '!' && letter == 'M' && codenum == 32) { string_arg = p; // Name starts after '!' char * const lb = strchr(p, '#'); // Already seen '#' as SD char (to pause buffering) if (lb) *lb = '\0'; // Safe to mark the end of the filename return; } // Arguments MUST be uppercase for fast GCode parsing #if ENABLED(FASTER_GCODE_PARSER) #define PARAM_TEST WITHIN(code, 'A', 'Z') #else #define PARAM_TEST true #endif if (PARAM_TEST) { while (*p == ' ') p++; // Skip spaces between parameters & values const bool has_num = DECIMAL_SIGNED(*p); // The parameter has a number [-+0-9.] #if ENABLED(DEBUG_GCODE_PARSER) if (debug) { SERIAL_ECHOPAIR("Got letter ", code); // DEBUG SERIAL_ECHOPAIR(" at index ", (int)(p - command_ptr - 1)); // DEBUG if (has_num) SERIAL_ECHOPGM(" (has_num)"); } #endif if (!has_num && !string_arg) { // No value? First time, keep as string_arg string_arg = p - 1; #if ENABLED(DEBUG_GCODE_PARSER) if (debug) SERIAL_ECHOPAIR(" string_arg: ", hex_address((void*)string_arg)); // DEBUG #endif } #if ENABLED(DEBUG_GCODE_PARSER) if (debug) SERIAL_EOL(); #endif #if ENABLED(FASTER_GCODE_PARSER) set(code, has_num ? p : NULL // Set parameter exists and pointer (NULL for no number) #if ENABLED(DEBUG_GCODE_PARSER) , debug #endif ); #endif } else if (!string_arg) { // Not A-Z? First time, keep as the string_arg string_arg = p - 1; #if ENABLED(DEBUG_GCODE_PARSER) if (debug) SERIAL_ECHOPAIR(" string_arg: ", hex_address((void*)string_arg)); // DEBUG #endif } if (!WITHIN(*p, 'A', 'Z')) { while (*p && NUMERIC(*p)) p++; // Skip over the value section of a parameter while (*p == ' ') p++; // Skip over all spaces } } }
/** * G26: Mesh Validation Pattern generation. * * Used to interactively edit the mesh by placing the * nozzle in a problem area and doing a G29 P4 R command. * * Parameters: * * B Bed Temperature * C Continue from the Closest mesh point * D Disable leveling before starting * F Filament diameter * H Hotend Temperature * K Keep heaters on when completed * L Layer Height * O Ooze extrusion length * P Prime length * Q Retraction multiplier * R Repetitions (number of grid points) * S Nozzle Size (diameter) in mm * T Tool index to change to, if included * U Random deviation (50 if no value given) * X X position * Y Y position */ void GcodeSuite::G26() { SERIAL_ECHOLNPGM("G26 starting..."); // Don't allow Mesh Validation without homing first, // or if the parameter parsing did not go OK, abort if (axis_unhomed_error()) return; // Change the tool first, if specified if (parser.seenval('T')) tool_change(parser.value_int()); g26_extrusion_multiplier = EXTRUSION_MULTIPLIER; g26_retraction_multiplier = RETRACTION_MULTIPLIER; g26_layer_height = MESH_TEST_LAYER_HEIGHT; g26_prime_length = PRIME_LENGTH; g26_bed_temp = MESH_TEST_BED_TEMP; g26_hotend_temp = MESH_TEST_HOTEND_TEMP; g26_prime_flag = 0; float g26_nozzle = MESH_TEST_NOZZLE_SIZE, g26_filament_diameter = DEFAULT_NOMINAL_FILAMENT_DIA, g26_ooze_amount = parser.linearval('O', OOZE_AMOUNT); bool g26_continue_with_closest = parser.boolval('C'), g26_keep_heaters_on = parser.boolval('K'); if (parser.seenval('B')) { g26_bed_temp = parser.value_celsius(); if (g26_bed_temp && !WITHIN(g26_bed_temp, 40, 140)) { SERIAL_ECHOLNPGM("?Specified bed temperature not plausible (40-140C)."); return; } } if (parser.seenval('L')) { g26_layer_height = parser.value_linear_units(); if (!WITHIN(g26_layer_height, 0.0, 2.0)) { SERIAL_ECHOLNPGM("?Specified layer height not plausible."); return; } } if (parser.seen('Q')) { if (parser.has_value()) { g26_retraction_multiplier = parser.value_float(); if (!WITHIN(g26_retraction_multiplier, 0.05, 15.0)) { SERIAL_ECHOLNPGM("?Specified Retraction Multiplier not plausible."); return; } } else { SERIAL_ECHOLNPGM("?Retraction Multiplier must be specified."); return; } } if (parser.seenval('S')) { g26_nozzle = parser.value_float(); if (!WITHIN(g26_nozzle, 0.1, 1.0)) { SERIAL_ECHOLNPGM("?Specified nozzle size not plausible."); return; } } if (parser.seen('P')) { if (!parser.has_value()) { #if HAS_LCD_MENU g26_prime_flag = -1; #else SERIAL_ECHOLNPGM("?Prime length must be specified when not using an LCD."); return; #endif } else { g26_prime_flag++; g26_prime_length = parser.value_linear_units(); if (!WITHIN(g26_prime_length, 0.0, 25.0)) { SERIAL_ECHOLNPGM("?Specified prime length not plausible."); return; } } } if (parser.seenval('F')) { g26_filament_diameter = parser.value_linear_units(); if (!WITHIN(g26_filament_diameter, 1.0, 4.0)) { SERIAL_ECHOLNPGM("?Specified filament size not plausible."); return; } } g26_extrusion_multiplier *= sq(1.75) / sq(g26_filament_diameter); // If we aren't using 1.75mm filament, we need to // scale up or down the length needed to get the // same volume of filament g26_extrusion_multiplier *= g26_filament_diameter * sq(g26_nozzle) / sq(0.3); // Scale up by nozzle size if (parser.seenval('H')) { g26_hotend_temp = parser.value_celsius(); if (!WITHIN(g26_hotend_temp, 165, 280)) { SERIAL_ECHOLNPGM("?Specified nozzle temperature not plausible."); return; } } if (parser.seen('U')) { randomSeed(millis()); // This setting will persist for the next G26 random_deviation = parser.has_value() ? parser.value_float() : 50.0; } int16_t g26_repeats; #if HAS_LCD_MENU g26_repeats = parser.intval('R', GRID_MAX_POINTS + 1); #else if (!parser.seen('R')) { SERIAL_ECHOLNPGM("?(R)epeat must be specified when not using an LCD."); return; } else g26_repeats = parser.has_value() ? parser.value_int() : GRID_MAX_POINTS + 1; #endif if (g26_repeats < 1) { SERIAL_ECHOLNPGM("?(R)epeat value not plausible; must be at least 1."); return; } g26_x_pos = parser.seenval('X') ? RAW_X_POSITION(parser.value_linear_units()) : current_position[X_AXIS]; g26_y_pos = parser.seenval('Y') ? RAW_Y_POSITION(parser.value_linear_units()) : current_position[Y_AXIS]; if (!position_is_reachable(g26_x_pos, g26_y_pos)) { SERIAL_ECHOLNPGM("?Specified X,Y coordinate out of bounds."); return; } /** * Wait until all parameters are verified before altering the state! */ set_bed_leveling_enabled(!parser.seen('D')); if (current_position[Z_AXIS] < Z_CLEARANCE_BETWEEN_PROBES) { do_blocking_move_to_z(Z_CLEARANCE_BETWEEN_PROBES); set_current_from_destination(); } if (turn_on_heaters() != G26_OK) goto LEAVE; current_position[E_AXIS] = 0.0; sync_plan_position_e(); if (g26_prime_flag && prime_nozzle() != G26_OK) goto LEAVE; /** * Bed is preheated * * Nozzle is at temperature * * Filament is primed! * * It's "Show Time" !!! */ ZERO(circle_flags); ZERO(horizontal_mesh_line_flags); ZERO(vertical_mesh_line_flags); // Move nozzle to the specified height for the first layer set_destination_from_current(); destination[Z_AXIS] = g26_layer_height; move_to(destination, 0.0); move_to(destination, g26_ooze_amount); #if HAS_LCD_MENU ui.capture(); #endif //debug_current_and_destination(PSTR("Starting G26 Mesh Validation Pattern.")); #if DISABLED(ARC_SUPPORT) /** * Pre-generate radius offset values at 30 degree intervals to reduce CPU load. */ #define A_INT 30 #define _ANGS (360 / A_INT) #define A_CNT (_ANGS / 2) #define _IND(A) ((A + _ANGS * 8) % _ANGS) #define _COS(A) (trig_table[_IND(A) % A_CNT] * (_IND(A) >= A_CNT ? -1 : 1)) #define _SIN(A) (-_COS((A + A_CNT / 2) % _ANGS)) #if A_CNT & 1 #error "A_CNT must be a positive value. Please change A_INT." #endif float trig_table[A_CNT]; for (uint8_t i = 0; i < A_CNT; i++) trig_table[i] = INTERSECTION_CIRCLE_RADIUS * cos(RADIANS(i * A_INT)); #endif // !ARC_SUPPORT mesh_index_pair location; do { location = g26_continue_with_closest ? find_closest_circle_to_print(current_position[X_AXIS], current_position[Y_AXIS]) : find_closest_circle_to_print(g26_x_pos, g26_y_pos); // Find the closest Mesh Intersection to where we are now. if (location.x_index >= 0 && location.y_index >= 0) { const float circle_x = _GET_MESH_X(location.x_index), circle_y = _GET_MESH_Y(location.y_index); // If this mesh location is outside the printable_radius, skip it. if (!position_is_reachable(circle_x, circle_y)) continue; // Determine where to start and end the circle, // which is always drawn counter-clockwise. const uint8_t xi = location.x_index, yi = location.y_index; const bool f = yi == 0, r = xi >= GRID_MAX_POINTS_X - 1, b = yi >= GRID_MAX_POINTS_Y - 1; #if ENABLED(ARC_SUPPORT) #define ARC_LENGTH(quarters) (INTERSECTION_CIRCLE_RADIUS * M_PI * (quarters) / 2) float sx = circle_x + INTERSECTION_CIRCLE_RADIUS, // default to full circle ex = circle_x + INTERSECTION_CIRCLE_RADIUS, sy = circle_y, ey = circle_y, arc_length = ARC_LENGTH(4); // Figure out where to start and end the arc - we always print counterclockwise if (xi == 0) { // left edge sx = f ? circle_x + INTERSECTION_CIRCLE_RADIUS : circle_x; ex = b ? circle_x + INTERSECTION_CIRCLE_RADIUS : circle_x; sy = f ? circle_y : circle_y - (INTERSECTION_CIRCLE_RADIUS); ey = b ? circle_y : circle_y + INTERSECTION_CIRCLE_RADIUS; arc_length = (f || b) ? ARC_LENGTH(1) : ARC_LENGTH(2); } else if (r) { // right edge sx = b ? circle_x - (INTERSECTION_CIRCLE_RADIUS) : circle_x; ex = f ? circle_x - (INTERSECTION_CIRCLE_RADIUS) : circle_x; sy = b ? circle_y : circle_y + INTERSECTION_CIRCLE_RADIUS; ey = f ? circle_y : circle_y - (INTERSECTION_CIRCLE_RADIUS); arc_length = (f || b) ? ARC_LENGTH(1) : ARC_LENGTH(2); } else if (f) { sx = circle_x + INTERSECTION_CIRCLE_RADIUS; ex = circle_x - (INTERSECTION_CIRCLE_RADIUS); sy = ey = circle_y; arc_length = ARC_LENGTH(2); } else if (b) { sx = circle_x - (INTERSECTION_CIRCLE_RADIUS); ex = circle_x + INTERSECTION_CIRCLE_RADIUS; sy = ey = circle_y; arc_length = ARC_LENGTH(2); } const float arc_offset[2] = { circle_x - sx, circle_y - sy }; const float dx_s = current_position[X_AXIS] - sx, // find our distance from the start of the actual circle dy_s = current_position[Y_AXIS] - sy, dist_start = HYPOT2(dx_s, dy_s); const float endpoint[XYZE] = { ex, ey, g26_layer_height, current_position[E_AXIS] + (arc_length * g26_e_axis_feedrate * g26_extrusion_multiplier) }; if (dist_start > 2.0) { retract_filament(destination); //todo: parameterize the bump height with a define move_to(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] + 0.500, 0.0); // Z bump to minimize scraping move_to(sx, sy, g26_layer_height + 0.500, 0.0); // Get to the starting point with no extrusion while bumped } move_to(sx, sy, g26_layer_height, 0.0); // Get to the starting point with no extrusion / un-Z bump recover_filament(destination); const float save_feedrate = feedrate_mm_s; feedrate_mm_s = PLANNER_XY_FEEDRATE() / 10.0; if (g26_debug_flag) { SERIAL_ECHOPAIR(" plan_arc(ex=", endpoint[X_AXIS]); SERIAL_ECHOPAIR(", ey=", endpoint[Y_AXIS]); SERIAL_ECHOPAIR(", ez=", endpoint[Z_AXIS]); SERIAL_ECHOPAIR(", len=", arc_length); SERIAL_ECHOPAIR(") -> (ex=", current_position[X_AXIS]); SERIAL_ECHOPAIR(", ey=", current_position[Y_AXIS]); SERIAL_ECHOPAIR(", ez=", current_position[Z_AXIS]); SERIAL_CHAR(')'); SERIAL_EOL(); } plan_arc(endpoint, arc_offset, false); // Draw a counter-clockwise arc feedrate_mm_s = save_feedrate; set_destination_from_current(); #if HAS_LCD_MENU if (user_canceled()) goto LEAVE; // Check if the user wants to stop the Mesh Validation #endif #else // !ARC_SUPPORT int8_t start_ind = -2, end_ind = 9; // Assume a full circle (from 5:00 to 5:00) if (xi == 0) { // Left edge? Just right half. start_ind = f ? 0 : -3; // 03:00 to 12:00 for front-left end_ind = b ? 0 : 2; // 06:00 to 03:00 for back-left } else if (r) { // Right edge? Just left half. start_ind = b ? 6 : 3; // 12:00 to 09:00 for front-right end_ind = f ? 5 : 8; // 09:00 to 06:00 for back-right } else if (f) { // Front edge? Just back half. start_ind = 0; // 03:00 end_ind = 5; // 09:00 } else if (b) { // Back edge? Just front half. start_ind = 6; // 09:00 end_ind = 11; // 03:00 } for (int8_t ind = start_ind; ind <= end_ind; ind++) { #if HAS_LCD_MENU if (user_canceled()) goto LEAVE; // Check if the user wants to stop the Mesh Validation #endif float rx = circle_x + _COS(ind), // For speed, these are now a lookup table entry ry = circle_y + _SIN(ind), xe = circle_x + _COS(ind + 1), ye = circle_y + _SIN(ind + 1); #if IS_KINEMATIC // Check to make sure this segment is entirely on the bed, skip if not. if (!position_is_reachable(rx, ry) || !position_is_reachable(xe, ye)) continue; #else // not, we need to skip rx = constrain(rx, X_MIN_POS + 1, X_MAX_POS - 1); // This keeps us from bumping the endstops ry = constrain(ry, Y_MIN_POS + 1, Y_MAX_POS - 1); xe = constrain(xe, X_MIN_POS + 1, X_MAX_POS - 1); ye = constrain(ye, Y_MIN_POS + 1, Y_MAX_POS - 1); #endif print_line_from_here_to_there(rx, ry, g26_layer_height, xe, ye, g26_layer_height); SERIAL_FLUSH(); // Prevent host M105 buffer overrun. } #endif // !ARC_SUPPORT if (look_for_lines_to_connect()) goto LEAVE; } SERIAL_FLUSH(); // Prevent host M105 buffer overrun. } while (--g26_repeats && location.x_index >= 0 && location.y_index >= 0); LEAVE: ui.set_status_P(PSTR("Leaving G26"), -1); retract_filament(destination); destination[Z_AXIS] = Z_CLEARANCE_BETWEEN_PROBES; //debug_current_and_destination(PSTR("ready to do Z-Raise.")); move_to(destination, 0); // Raise the nozzle //debug_current_and_destination(PSTR("done doing Z-Raise.")); destination[X_AXIS] = g26_x_pos; // Move back to the starting position destination[Y_AXIS] = g26_y_pos; //destination[Z_AXIS] = Z_CLEARANCE_BETWEEN_PROBES; // Keep the nozzle where it is move_to(destination, 0); // Move back to the starting position //debug_current_and_destination(PSTR("done doing X/Y move.")); #if HAS_LCD_MENU ui.release(); // Give back control of the LCD #endif if (!g26_keep_heaters_on) { #if HAS_HEATED_BED thermalManager.setTargetBed(0); #endif thermalManager.setTargetHotend(active_extruder, 0); } }
bool unified_bed_leveling::look_for_lines_to_connect() { float sx, sy, ex, ey; for (uint8_t i = 0; i < GRID_MAX_POINTS_X; i++) { for (uint8_t j = 0; j < GRID_MAX_POINTS_Y; j++) { #if ENABLED(NEWPANEL) if (user_canceled()) return true; // Check if the user wants to stop the Mesh Validation #endif if (i < GRID_MAX_POINTS_X) { // We can't connect to anything to the right than GRID_MAX_POINTS_X. // This is already a half circle because we are at the edge of the bed. if (is_bit_set(circle_flags, i, j) && is_bit_set(circle_flags, i + 1, j)) { // check if we can do a line to the left if (!is_bit_set(horizontal_mesh_line_flags, i, j)) { // // We found two circles that need a horizontal line to connect them // Print it! // sx = mesh_index_to_xpos( i ) + (SIZE_OF_INTERSECTION_CIRCLES - (SIZE_OF_CROSSHAIRS)); // right edge ex = mesh_index_to_xpos(i + 1) - (SIZE_OF_INTERSECTION_CIRCLES - (SIZE_OF_CROSSHAIRS)); // left edge sx = constrain(sx, X_MIN_POS + 1, X_MAX_POS - 1); sy = ey = constrain(mesh_index_to_ypos(j), Y_MIN_POS + 1, Y_MAX_POS - 1); ex = constrain(ex, X_MIN_POS + 1, X_MAX_POS - 1); if (position_is_reachable_raw_xy(sx, sy) && position_is_reachable_raw_xy(ex, ey)) { if (g26_debug_flag) { SERIAL_ECHOPAIR(" Connecting with horizontal line (sx=", sx); SERIAL_ECHOPAIR(", sy=", sy); SERIAL_ECHOPAIR(") -> (ex=", ex); SERIAL_ECHOPAIR(", ey=", ey); SERIAL_CHAR(')'); SERIAL_EOL(); //debug_current_and_destination(PSTR("Connecting horizontal line.")); } print_line_from_here_to_there(LOGICAL_X_POSITION(sx), LOGICAL_Y_POSITION(sy), g26_layer_height, LOGICAL_X_POSITION(ex), LOGICAL_Y_POSITION(ey), g26_layer_height); } bit_set(horizontal_mesh_line_flags, i, j); // Mark it as done so we don't do it again, even if we skipped it } } if (j < GRID_MAX_POINTS_Y) { // We can't connect to anything further back than GRID_MAX_POINTS_Y. // This is already a half circle because we are at the edge of the bed. if (is_bit_set(circle_flags, i, j) && is_bit_set(circle_flags, i, j + 1)) { // check if we can do a line straight down if (!is_bit_set( vertical_mesh_line_flags, i, j)) { // // We found two circles that need a vertical line to connect them // Print it! // sy = mesh_index_to_ypos( j ) + (SIZE_OF_INTERSECTION_CIRCLES - (SIZE_OF_CROSSHAIRS)); // top edge ey = mesh_index_to_ypos(j + 1) - (SIZE_OF_INTERSECTION_CIRCLES - (SIZE_OF_CROSSHAIRS)); // bottom edge sx = ex = constrain(mesh_index_to_xpos(i), X_MIN_POS + 1, X_MAX_POS - 1); sy = constrain(sy, Y_MIN_POS + 1, Y_MAX_POS - 1); ey = constrain(ey, Y_MIN_POS + 1, Y_MAX_POS - 1); if (position_is_reachable_raw_xy(sx, sy) && position_is_reachable_raw_xy(ex, ey)) { if (g26_debug_flag) { SERIAL_ECHOPAIR(" Connecting with vertical line (sx=", sx); SERIAL_ECHOPAIR(", sy=", sy); SERIAL_ECHOPAIR(") -> (ex=", ex); SERIAL_ECHOPAIR(", ey=", ey); SERIAL_CHAR(')'); SERIAL_EOL(); debug_current_and_destination(PSTR("Connecting vertical line.")); } print_line_from_here_to_there(LOGICAL_X_POSITION(sx), LOGICAL_Y_POSITION(sy), g26_layer_height, LOGICAL_X_POSITION(ex), LOGICAL_Y_POSITION(ey), g26_layer_height); } bit_set(vertical_mesh_line_flags, i, j); // Mark it as done so we don't do it again, even if skipped } } } } } } return false; }
/** * G26: Mesh Validation Pattern generation. * * Used to interactively edit UBL's Mesh by placing the * nozzle in a problem area and doing a G29 P4 R command. */ void unified_bed_leveling::G26() { SERIAL_ECHOLNPGM("G26 command started. Waiting for heater(s)."); float tmp, start_angle, end_angle; int i, xi, yi; mesh_index_pair location; // Don't allow Mesh Validation without homing first, // or if the parameter parsing did not go OK, abort if (axis_unhomed_error() || parse_G26_parameters()) return; if (current_position[Z_AXIS] < Z_CLEARANCE_BETWEEN_PROBES) { do_blocking_move_to_z(Z_CLEARANCE_BETWEEN_PROBES); stepper.synchronize(); set_current_to_destination(); } if (turn_on_heaters()) goto LEAVE; current_position[E_AXIS] = 0.0; sync_plan_position_e(); if (g26_prime_flag && prime_nozzle()) goto LEAVE; /** * Bed is preheated * * Nozzle is at temperature * * Filament is primed! * * It's "Show Time" !!! */ ZERO(circle_flags); ZERO(horizontal_mesh_line_flags); ZERO(vertical_mesh_line_flags); // Move nozzle to the specified height for the first layer set_destination_to_current(); destination[Z_AXIS] = g26_layer_height; move_to(destination, 0.0); move_to(destination, g26_ooze_amount); has_control_of_lcd_panel = true; //debug_current_and_destination(PSTR("Starting G26 Mesh Validation Pattern.")); /** * Declare and generate a sin() & cos() table to be used during the circle drawing. This will lighten * the CPU load and make the arc drawing faster and more smooth */ float sin_table[360 / 30 + 1], cos_table[360 / 30 + 1]; for (i = 0; i <= 360 / 30; i++) { cos_table[i] = SIZE_OF_INTERSECTION_CIRCLES * cos(RADIANS(valid_trig_angle(i * 30.0))); sin_table[i] = SIZE_OF_INTERSECTION_CIRCLES * sin(RADIANS(valid_trig_angle(i * 30.0))); } do { location = g26_continue_with_closest ? find_closest_circle_to_print(current_position[X_AXIS], current_position[Y_AXIS]) : find_closest_circle_to_print(g26_x_pos, g26_y_pos); // Find the closest Mesh Intersection to where we are now. if (location.x_index >= 0 && location.y_index >= 0) { const float circle_x = mesh_index_to_xpos(location.x_index), circle_y = mesh_index_to_ypos(location.y_index); // If this mesh location is outside the printable_radius, skip it. if (!position_is_reachable_raw_xy(circle_x, circle_y)) continue; xi = location.x_index; // Just to shrink the next few lines and make them easier to understand yi = location.y_index; if (g26_debug_flag) { SERIAL_ECHOPAIR(" Doing circle at: (xi=", xi); SERIAL_ECHOPAIR(", yi=", yi); SERIAL_CHAR(')'); SERIAL_EOL(); } start_angle = 0.0; // assume it is going to be a full circle end_angle = 360.0; if (xi == 0) { // Check for bottom edge start_angle = -90.0; end_angle = 90.0; if (yi == 0) // it is an edge, check for the two left corners start_angle = 0.0; else if (yi == GRID_MAX_POINTS_Y - 1) end_angle = 0.0; } else if (xi == GRID_MAX_POINTS_X - 1) { // Check for top edge start_angle = 90.0; end_angle = 270.0; if (yi == 0) // it is an edge, check for the two right corners end_angle = 180.0; else if (yi == GRID_MAX_POINTS_Y - 1) start_angle = 180.0; } else if (yi == 0) { start_angle = 0.0; // only do the top side of the cirlce end_angle = 180.0; } else if (yi == GRID_MAX_POINTS_Y - 1) { start_angle = 180.0; // only do the bottom side of the cirlce end_angle = 360.0; } for (tmp = start_angle; tmp < end_angle - 0.1; tmp += 30.0) { #if ENABLED(NEWPANEL) if (user_canceled()) goto LEAVE; // Check if the user wants to stop the Mesh Validation #endif int tmp_div_30 = tmp / 30.0; if (tmp_div_30 < 0) tmp_div_30 += 360 / 30; if (tmp_div_30 > 11) tmp_div_30 -= 360 / 30; float x = circle_x + cos_table[tmp_div_30], // for speed, these are now a lookup table entry y = circle_y + sin_table[tmp_div_30], xe = circle_x + cos_table[tmp_div_30 + 1], ye = circle_y + sin_table[tmp_div_30 + 1]; #if IS_KINEMATIC // Check to make sure this segment is entirely on the bed, skip if not. if (!position_is_reachable_raw_xy(x, y) || !position_is_reachable_raw_xy(xe, ye)) continue; #else // not, we need to skip x = constrain(x, X_MIN_POS + 1, X_MAX_POS - 1); // This keeps us from bumping the endstops y = constrain(y, Y_MIN_POS + 1, Y_MAX_POS - 1); xe = constrain(xe, X_MIN_POS + 1, X_MAX_POS - 1); ye = constrain(ye, Y_MIN_POS + 1, Y_MAX_POS - 1); #endif //if (g26_debug_flag) { // char ccc, *cptr, seg_msg[50], seg_num[10]; // strcpy(seg_msg, " segment: "); // strcpy(seg_num, " \n"); // cptr = (char*) "01234567890ABCDEF????????"; // ccc = cptr[tmp_div_30]; // seg_num[1] = ccc; // strcat(seg_msg, seg_num); // debug_current_and_destination(seg_msg); //} print_line_from_here_to_there(LOGICAL_X_POSITION(x), LOGICAL_Y_POSITION(y), g26_layer_height, LOGICAL_X_POSITION(xe), LOGICAL_Y_POSITION(ye), g26_layer_height); } if (look_for_lines_to_connect()) goto LEAVE; } } while (--g26_repeats && location.x_index >= 0 && location.y_index >= 0); LEAVE: lcd_setstatusPGM(PSTR("Leaving G26"), -1); retract_filament(destination); destination[Z_AXIS] = Z_CLEARANCE_BETWEEN_PROBES; //debug_current_and_destination(PSTR("ready to do Z-Raise.")); move_to(destination, 0); // Raise the nozzle //debug_current_and_destination(PSTR("done doing Z-Raise.")); destination[X_AXIS] = g26_x_pos; // Move back to the starting position destination[Y_AXIS] = g26_y_pos; //destination[Z_AXIS] = Z_CLEARANCE_BETWEEN_PROBES; // Keep the nozzle where it is move_to(destination, 0); // Move back to the starting position //debug_current_and_destination(PSTR("done doing X/Y move.")); has_control_of_lcd_panel = false; // Give back control of the LCD Panel! if (!g26_keep_heaters_on) { #if HAS_TEMP_BED thermalManager.setTargetBed(0); #endif thermalManager.setTargetHotend(0, 0); } }
/** * - Move to the given XY * - Deploy the probe, if not already deployed * - Probe the bed, get the Z position * - Depending on the 'stow' flag * - Stow the probe, or * - Raise to the BETWEEN height * - Return the probed Z position */ float probe_pt(const float &lx, const float &ly, const bool stow, const uint8_t verbose_level, const bool printable/*=true*/) { #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING)) { SERIAL_ECHOPAIR(">>> probe_pt(", lx); SERIAL_ECHOPAIR(", ", ly); SERIAL_ECHOPAIR(", ", stow ? "" : "no "); SERIAL_ECHOLNPGM("stow)"); DEBUG_POS("", current_position); } #endif const float nx = lx - (X_PROBE_OFFSET_FROM_EXTRUDER), ny = ly - (Y_PROBE_OFFSET_FROM_EXTRUDER); if (printable ? !position_is_reachable_xy(nx, ny) : !position_is_reachable_by_probe_xy(lx, ly) ) return NAN; const float old_feedrate_mm_s = feedrate_mm_s; #if ENABLED(DELTA) if (current_position[Z_AXIS] > delta_clip_start_height) do_blocking_move_to_z(delta_clip_start_height); #endif #if HAS_SOFTWARE_ENDSTOPS // Store the status of the soft endstops and disable if we're probing a non-printable location static bool enable_soft_endstops = soft_endstops_enabled; if (!printable) soft_endstops_enabled = false; #endif feedrate_mm_s = XY_PROBE_FEEDRATE_MM_S; // Move the probe to the given XY do_blocking_move_to_xy(nx, ny); float measured_z = NAN; if (!DEPLOY_PROBE()) { measured_z = run_z_probe(printable); if (!stow) do_blocking_move_to_z(current_position[Z_AXIS] + Z_CLEARANCE_BETWEEN_PROBES, MMM_TO_MMS(Z_PROBE_SPEED_FAST)); else if (STOW_PROBE()) measured_z = NAN; } #if HAS_SOFTWARE_ENDSTOPS // Restore the soft endstop status soft_endstops_enabled = enable_soft_endstops; #endif if (verbose_level > 2) { SERIAL_PROTOCOLPGM("Bed X: "); SERIAL_PROTOCOL_F(lx, 3); SERIAL_PROTOCOLPGM(" Y: "); SERIAL_PROTOCOL_F(ly, 3); SERIAL_PROTOCOLPGM(" Z: "); SERIAL_PROTOCOL_F(measured_z, 3); SERIAL_EOL(); } #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("<<< probe_pt"); #endif feedrate_mm_s = old_feedrate_mm_s; if (isnan(measured_z)) { LCD_MESSAGEPGM(MSG_ERR_PROBING_FAILED); SERIAL_ERROR_START(); SERIAL_ERRORLNPGM(MSG_ERR_PROBING_FAILED); } return measured_z; }
void unified_bed_leveling::line_to_destination_cartesian(const float &feed_rate, const uint8_t extruder) { /** * Much of the nozzle movement will be within the same cell. So we will do as little computation * as possible to determine if this is the case. If this move is within the same cell, we will * just do the required Z-Height correction, call the Planner's buffer_line() routine, and leave */ #if ENABLED(SKEW_CORRECTION) // For skew correction just adjust the destination point and we're done float start[XYZE] = { current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS] }, end[XYZE] = { destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS] }; planner.skew(start[X_AXIS], start[Y_AXIS], start[Z_AXIS]); planner.skew(end[X_AXIS], end[Y_AXIS], end[Z_AXIS]); #else const float (&start)[XYZE] = current_position, (&end)[XYZE] = destination; #endif const int cell_start_xi = get_cell_index_x(start[X_AXIS]), cell_start_yi = get_cell_index_y(start[Y_AXIS]), cell_dest_xi = get_cell_index_x(end[X_AXIS]), cell_dest_yi = get_cell_index_y(end[Y_AXIS]); if (g26_debug_flag) { SERIAL_ECHOPAIR(" ubl.line_to_destination_cartesian(xe=", destination[X_AXIS]); SERIAL_ECHOPAIR(", ye=", destination[Y_AXIS]); SERIAL_ECHOPAIR(", ze=", destination[Z_AXIS]); SERIAL_ECHOPAIR(", ee=", destination[E_AXIS]); SERIAL_CHAR(')'); SERIAL_EOL(); debug_current_and_destination(PSTR("Start of ubl.line_to_destination_cartesian()")); } if (cell_start_xi == cell_dest_xi && cell_start_yi == cell_dest_yi) { // if the whole move is within the same cell, /** * we don't need to break up the move * * If we are moving off the print bed, we are going to allow the move at this level. * But we detect it and isolate it. For now, we just pass along the request. */ if (!WITHIN(cell_dest_xi, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(cell_dest_yi, 0, GRID_MAX_POINTS_Y - 1)) { // Note: There is no Z Correction in this case. We are off the grid and don't know what // a reasonable correction would be. planner.buffer_segment(end[X_AXIS], end[Y_AXIS], end[Z_AXIS], end[E_AXIS], feed_rate, extruder); set_current_from_destination(); if (g26_debug_flag) debug_current_and_destination(PSTR("out of bounds in ubl.line_to_destination_cartesian()")); return; } FINAL_MOVE: /** * Optimize some floating point operations here. We could call float get_z_correction(float x0, float y0) to * generate the correction for us. But we can lighten the load on the CPU by doing a modified version of the function. * We are going to only calculate the amount we are from the first mesh line towards the second mesh line once. * We will use this fraction in both of the original two Z Height calculations for the bi-linear interpolation. And, * instead of doing a generic divide of the distance, we know the distance is MESH_X_DIST so we can use the preprocessor * to create a 1-over number for us. That will allow us to do a floating point multiply instead of a floating point divide. */ const float xratio = (end[X_AXIS] - mesh_index_to_xpos(cell_dest_xi)) * (1.0 / (MESH_X_DIST)); float z1 = z_values[cell_dest_xi ][cell_dest_yi ] + xratio * (z_values[cell_dest_xi + 1][cell_dest_yi ] - z_values[cell_dest_xi][cell_dest_yi ]), z2 = z_values[cell_dest_xi ][cell_dest_yi + 1] + xratio * (z_values[cell_dest_xi + 1][cell_dest_yi + 1] - z_values[cell_dest_xi][cell_dest_yi + 1]); if (cell_dest_xi >= GRID_MAX_POINTS_X - 1) z1 = z2 = 0.0; // we are done with the fractional X distance into the cell. Now with the two Z-Heights we have calculated, we // are going to apply the Y-Distance into the cell to interpolate the final Z correction. const float yratio = (end[Y_AXIS] - mesh_index_to_ypos(cell_dest_yi)) * (1.0 / (MESH_Y_DIST)); float z0 = cell_dest_yi < GRID_MAX_POINTS_Y - 1 ? (z1 + (z2 - z1) * yratio) * planner.fade_scaling_factor_for_z(end[Z_AXIS]) : 0.0; /** * If part of the Mesh is undefined, it will show up as NAN * in z_values[][] and propagate through the * calculations. If our correction is NAN, we throw it out * because part of the Mesh is undefined and we don't have the * information we need to complete the height correction. */ if (isnan(z0)) z0 = 0.0; planner.buffer_segment(end[X_AXIS], end[Y_AXIS], end[Z_AXIS] + z0, end[E_AXIS], feed_rate, extruder); if (g26_debug_flag) debug_current_and_destination(PSTR("FINAL_MOVE in ubl.line_to_destination_cartesian()")); set_current_from_destination(); return; } /** * If we get here, we are processing a move that crosses at least one Mesh Line. We will check * for the simple case of just crossing X or just crossing Y Mesh Lines after we get all the details * of the move figured out. We can process the easy case of just crossing an X or Y Mesh Line with less * computation and in fact most lines are of this nature. We will check for that in the following * blocks of code: */ const float dx = end[X_AXIS] - start[X_AXIS], dy = end[Y_AXIS] - start[Y_AXIS]; const int left_flag = dx < 0.0 ? 1 : 0, down_flag = dy < 0.0 ? 1 : 0; const float adx = left_flag ? -dx : dx, ady = down_flag ? -dy : dy; const int dxi = cell_start_xi == cell_dest_xi ? 0 : left_flag ? -1 : 1, dyi = cell_start_yi == cell_dest_yi ? 0 : down_flag ? -1 : 1; /** * Compute the scaling factor for the extruder for each partial move. * We need to watch out for zero length moves because it will cause us to * have an infinate scaling factor. We are stuck doing a floating point * divide to get our scaling factor, but after that, we just multiply by this * number. We also pick our scaling factor based on whether the X or Y * component is larger. We use the biggest of the two to preserve precision. */ const bool use_x_dist = adx > ady; float on_axis_distance = use_x_dist ? dx : dy, e_position = end[E_AXIS] - start[E_AXIS], z_position = end[Z_AXIS] - start[Z_AXIS]; const float e_normalized_dist = e_position / on_axis_distance, z_normalized_dist = z_position / on_axis_distance; int current_xi = cell_start_xi, current_yi = cell_start_yi; const float m = dy / dx, c = start[Y_AXIS] - m * start[X_AXIS]; const bool inf_normalized_flag = (isinf(e_normalized_dist) != 0), inf_m_flag = (isinf(m) != 0); /** * This block handles vertical lines. These are lines that stay within the same * X Cell column. They do not need to be perfectly vertical. They just can * not cross into another X Cell column. */ if (dxi == 0) { // Check for a vertical line current_yi += down_flag; // Line is heading down, we just want to go to the bottom while (current_yi != cell_dest_yi + down_flag) { current_yi += dyi; const float next_mesh_line_y = mesh_index_to_ypos(current_yi); /** * if the slope of the line is infinite, we won't do the calculations * else, we know the next X is the same so we can recover and continue! * Calculate X at the next Y mesh line */ const float rx = inf_m_flag ? start[X_AXIS] : (next_mesh_line_y - c) / m; float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, current_xi, current_yi) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); /** * If part of the Mesh is undefined, it will show up as NAN * in z_values[][] and propagate through the * calculations. If our correction is NAN, we throw it out * because part of the Mesh is undefined and we don't have the * information we need to complete the height correction. */ if (isnan(z0)) z0 = 0.0; const float ry = mesh_index_to_ypos(current_yi); /** * Without this check, it is possible for the algorithm to generate a zero length move in the case * where the line is heading down and it is starting right on a Mesh Line boundary. For how often that * happens, it might be best to remove the check and always 'schedule' the move because * the planner.buffer_segment() routine will filter it if that happens. */ if (ry != start[Y_AXIS]) { if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? rx - start[X_AXIS] : ry - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } planner.buffer_segment(rx, ry, z_position + z0, e_position, feed_rate, extruder); } //else printf("FIRST MOVE PRUNED "); } if (g26_debug_flag) debug_current_and_destination(PSTR("vertical move done in ubl.line_to_destination_cartesian()")); // // Check if we are at the final destination. Usually, we won't be, but if it is on a Y Mesh Line, we are done. // if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS]) goto FINAL_MOVE; set_current_from_destination(); return; } /** * * This block handles horizontal lines. These are lines that stay within the same * Y Cell row. They do not need to be perfectly horizontal. They just can * not cross into another Y Cell row. * */ if (dyi == 0) { // Check for a horizontal line current_xi += left_flag; // Line is heading left, we just want to go to the left // edge of this cell for the first move. while (current_xi != cell_dest_xi + left_flag) { current_xi += dxi; const float next_mesh_line_x = mesh_index_to_xpos(current_xi), ry = m * next_mesh_line_x + c; // Calculate Y at the next X mesh line float z0 = z_correction_for_y_on_vertical_mesh_line(ry, current_xi, current_yi) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); /** * If part of the Mesh is undefined, it will show up as NAN * in z_values[][] and propagate through the * calculations. If our correction is NAN, we throw it out * because part of the Mesh is undefined and we don't have the * information we need to complete the height correction. */ if (isnan(z0)) z0 = 0.0; const float rx = mesh_index_to_xpos(current_xi); /** * Without this check, it is possible for the algorithm to generate a zero length move in the case * where the line is heading left and it is starting right on a Mesh Line boundary. For how often * that happens, it might be best to remove the check and always 'schedule' the move because * the planner.buffer_segment() routine will filter it if that happens. */ if (rx != start[X_AXIS]) { if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? rx - start[X_AXIS] : ry - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; // is based on X or Y because this is a horizontal move z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } planner.buffer_segment(rx, ry, z_position + z0, e_position, feed_rate, extruder); } //else printf("FIRST MOVE PRUNED "); } if (g26_debug_flag) debug_current_and_destination(PSTR("horizontal move done in ubl.line_to_destination_cartesian()")); if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS]) goto FINAL_MOVE; set_current_from_destination(); return; } /** * * This block handles the generic case of a line crossing both X and Y Mesh lines. * */ int xi_cnt = cell_start_xi - cell_dest_xi, yi_cnt = cell_start_yi - cell_dest_yi; if (xi_cnt < 0) xi_cnt = -xi_cnt; if (yi_cnt < 0) yi_cnt = -yi_cnt; current_xi += left_flag; current_yi += down_flag; while (xi_cnt > 0 || yi_cnt > 0) { const float next_mesh_line_x = mesh_index_to_xpos(current_xi + dxi), next_mesh_line_y = mesh_index_to_ypos(current_yi + dyi), ry = m * next_mesh_line_x + c, // Calculate Y at the next X mesh line rx = (next_mesh_line_y - c) / m; // Calculate X at the next Y mesh line // (No need to worry about m being zero. // If that was the case, it was already detected // as a vertical line move above.) if (left_flag == (rx > next_mesh_line_x)) { // Check if we hit the Y line first // Yes! Crossing a Y Mesh Line next float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, current_xi - left_flag, current_yi + dyi) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); /** * If part of the Mesh is undefined, it will show up as NAN * in z_values[][] and propagate through the * calculations. If our correction is NAN, we throw it out * because part of the Mesh is undefined and we don't have the * information we need to complete the height correction. */ if (isnan(z0)) z0 = 0.0; if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? rx - start[X_AXIS] : next_mesh_line_y - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } planner.buffer_segment(rx, next_mesh_line_y, z_position + z0, e_position, feed_rate, extruder); current_yi += dyi; yi_cnt--; } else { // Yes! Crossing a X Mesh Line next float z0 = z_correction_for_y_on_vertical_mesh_line(ry, current_xi + dxi, current_yi - down_flag) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); /** * If part of the Mesh is undefined, it will show up as NAN * in z_values[][] and propagate through the * calculations. If our correction is NAN, we throw it out * because part of the Mesh is undefined and we don't have the * information we need to complete the height correction. */ if (isnan(z0)) z0 = 0.0; if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? next_mesh_line_x - start[X_AXIS] : ry - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } planner.buffer_segment(next_mesh_line_x, ry, z_position + z0, e_position, feed_rate, extruder); current_xi += dxi; xi_cnt--; } if (xi_cnt < 0 || yi_cnt < 0) break; // we've gone too far, so exit the loop and move on to FINAL_MOVE } if (g26_debug_flag) debug_current_and_destination(PSTR("generic move done in ubl.line_to_destination_cartesian()")); if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS]) goto FINAL_MOVE; set_current_from_destination(); }
void unified_bed_leveling::line_to_destination_cartesian(const float &feed_rate, const uint8_t extruder) { /** * Much of the nozzle movement will be within the same cell. So we will do as little computation * as possible to determine if this is the case. If this move is within the same cell, we will * just do the required Z-Height correction, call the Planner's buffer_line() routine, and leave */ #if ENABLED(SKEW_CORRECTION) // For skew correction just adjust the destination point and we're done float start[XYZE] = { current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS] }, end[XYZE] = { destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS] }; planner.skew(start[X_AXIS], start[Y_AXIS], start[Z_AXIS]); planner.skew(end[X_AXIS], end[Y_AXIS], end[Z_AXIS]); #else const float (&start)[XYZE] = current_position, (&end)[XYZE] = destination; #endif const int cell_start_xi = get_cell_index_x(start[X_AXIS]), cell_start_yi = get_cell_index_y(start[Y_AXIS]), cell_dest_xi = get_cell_index_x(end[X_AXIS]), cell_dest_yi = get_cell_index_y(end[Y_AXIS]); if (g26_debug_flag) { SERIAL_ECHOPAIR(" ubl.line_to_destination_cartesian(xe=", destination[X_AXIS]); SERIAL_ECHOPAIR(", ye=", destination[Y_AXIS]); SERIAL_ECHOPAIR(", ze=", destination[Z_AXIS]); SERIAL_ECHOPAIR(", ee=", destination[E_AXIS]); SERIAL_CHAR(')'); SERIAL_EOL(); debug_current_and_destination(PSTR("Start of ubl.line_to_destination_cartesian()")); } // A move within the same cell needs no splitting if (cell_start_xi == cell_dest_xi && cell_start_yi == cell_dest_yi) { // For a move off the bed, use a constant Z raise if (!WITHIN(cell_dest_xi, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(cell_dest_yi, 0, GRID_MAX_POINTS_Y - 1)) { // Note: There is no Z Correction in this case. We are off the grid and don't know what // a reasonable correction would be. If the user has specified a UBL_Z_RAISE_WHEN_OFF_MESH // value, that will be used instead of a calculated (Bi-Linear interpolation) correction. const float z_raise = 0.0 #ifdef UBL_Z_RAISE_WHEN_OFF_MESH + UBL_Z_RAISE_WHEN_OFF_MESH #endif ; planner.buffer_segment(end[X_AXIS], end[Y_AXIS], end[Z_AXIS] + z_raise, end[E_AXIS], feed_rate, extruder); set_current_from_destination(); if (g26_debug_flag) debug_current_and_destination(PSTR("out of bounds in ubl.line_to_destination_cartesian()")); return; } FINAL_MOVE: // The distance is always MESH_X_DIST so multiply by the constant reciprocal. const float xratio = (end[X_AXIS] - mesh_index_to_xpos(cell_dest_xi)) * (1.0f / (MESH_X_DIST)); float z1 = z_values[cell_dest_xi ][cell_dest_yi ] + xratio * (z_values[cell_dest_xi + 1][cell_dest_yi ] - z_values[cell_dest_xi][cell_dest_yi ]), z2 = z_values[cell_dest_xi ][cell_dest_yi + 1] + xratio * (z_values[cell_dest_xi + 1][cell_dest_yi + 1] - z_values[cell_dest_xi][cell_dest_yi + 1]); if (cell_dest_xi >= GRID_MAX_POINTS_X - 1) z1 = z2 = 0.0; // X cell-fraction done. Interpolate the two Z offsets with the Y fraction for the final Z offset. const float yratio = (end[Y_AXIS] - mesh_index_to_ypos(cell_dest_yi)) * (1.0f / (MESH_Y_DIST)), z0 = cell_dest_yi < GRID_MAX_POINTS_Y - 1 ? (z1 + (z2 - z1) * yratio) * planner.fade_scaling_factor_for_z(end[Z_AXIS]) : 0.0; // Undefined parts of the Mesh in z_values[][] are NAN. // Replace NAN corrections with 0.0 to prevent NAN propagation. planner.buffer_segment(end[X_AXIS], end[Y_AXIS], end[Z_AXIS] + (isnan(z0) ? 0.0 : z0), end[E_AXIS], feed_rate, extruder); if (g26_debug_flag) debug_current_and_destination(PSTR("FINAL_MOVE in ubl.line_to_destination_cartesian()")); set_current_from_destination(); return; } /** * Past this point the move is known to cross one or more mesh lines. Check for the most common * case - crossing only one X or Y line - after details are worked out to reduce computation. */ const float dx = end[X_AXIS] - start[X_AXIS], dy = end[Y_AXIS] - start[Y_AXIS]; const int left_flag = dx < 0.0 ? 1 : 0, down_flag = dy < 0.0 ? 1 : 0; const float adx = left_flag ? -dx : dx, ady = down_flag ? -dy : dy; const int dxi = cell_start_xi == cell_dest_xi ? 0 : left_flag ? -1 : 1, dyi = cell_start_yi == cell_dest_yi ? 0 : down_flag ? -1 : 1; /** * Compute the extruder scaling factor for each partial move, checking for * zero-length moves that would result in an infinite scaling factor. * A float divide is required for this, but then it just multiplies. * Also select a scaling factor based on the larger of the X and Y * components. The larger of the two is used to preserve precision. */ const bool use_x_dist = adx > ady; float on_axis_distance = use_x_dist ? dx : dy, e_position = end[E_AXIS] - start[E_AXIS], z_position = end[Z_AXIS] - start[Z_AXIS]; const float e_normalized_dist = e_position / on_axis_distance, z_normalized_dist = z_position / on_axis_distance; int current_xi = cell_start_xi, current_yi = cell_start_yi; const float m = dy / dx, c = start[Y_AXIS] - m * start[X_AXIS]; const bool inf_normalized_flag = (isinf(e_normalized_dist) != 0), inf_m_flag = (isinf(m) != 0); /** * Handle vertical lines that stay within one column. * These need not be perfectly vertical. */ if (dxi == 0) { // Vertical line? current_yi += down_flag; // Line going down? Just go to the bottom. while (current_yi != cell_dest_yi + down_flag) { current_yi += dyi; const float next_mesh_line_y = mesh_index_to_ypos(current_yi); /** * Skip the calculations for an infinite slope. * For others the next X is the same so this can continue. * Calculate X at the next Y mesh line. */ const float rx = inf_m_flag ? start[X_AXIS] : (next_mesh_line_y - c) / m; float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, current_xi, current_yi) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); // Undefined parts of the Mesh in z_values[][] are NAN. // Replace NAN corrections with 0.0 to prevent NAN propagation. if (isnan(z0)) z0 = 0.0; const float ry = mesh_index_to_ypos(current_yi); /** * Without this check, it's possible to generate a zero length move, as in the case where * the line is heading down, starting exactly on a mesh line boundary. Since this is rare * it might be fine to remove this check and let planner.buffer_segment() filter it out. */ if (ry != start[Y_AXIS]) { if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? rx - start[X_AXIS] : ry - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } planner.buffer_segment(rx, ry, z_position + z0, e_position, feed_rate, extruder); } //else printf("FIRST MOVE PRUNED "); } if (g26_debug_flag) debug_current_and_destination(PSTR("vertical move done in ubl.line_to_destination_cartesian()")); // At the final destination? Usually not, but when on a Y Mesh Line it's completed. if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS]) goto FINAL_MOVE; set_current_from_destination(); return; } /** * Handle horizontal lines that stay within one row. * These need not be perfectly horizontal. */ if (dyi == 0) { // Horizontal line? current_xi += left_flag; // Heading left? Just go to the left edge of the cell for the first move. while (current_xi != cell_dest_xi + left_flag) { current_xi += dxi; const float next_mesh_line_x = mesh_index_to_xpos(current_xi), ry = m * next_mesh_line_x + c; // Calculate Y at the next X mesh line float z0 = z_correction_for_y_on_vertical_mesh_line(ry, current_xi, current_yi) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); // Undefined parts of the Mesh in z_values[][] are NAN. // Replace NAN corrections with 0.0 to prevent NAN propagation. if (isnan(z0)) z0 = 0.0; const float rx = mesh_index_to_xpos(current_xi); /** * Without this check, it's possible to generate a zero length move, as in the case where * the line is heading left, starting exactly on a mesh line boundary. Since this is rare * it might be fine to remove this check and let planner.buffer_segment() filter it out. */ if (rx != start[X_AXIS]) { if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? rx - start[X_AXIS] : ry - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; // is based on X or Y because this is a horizontal move z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } if (!planner.buffer_segment(rx, ry, z_position + z0, e_position, feed_rate, extruder)) break; } //else printf("FIRST MOVE PRUNED "); } if (g26_debug_flag) debug_current_and_destination(PSTR("horizontal move done in ubl.line_to_destination_cartesian()")); if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS]) goto FINAL_MOVE; set_current_from_destination(); return; } /** * * Handle the generic case of a line crossing both X and Y Mesh lines. * */ int xi_cnt = cell_start_xi - cell_dest_xi, yi_cnt = cell_start_yi - cell_dest_yi; if (xi_cnt < 0) xi_cnt = -xi_cnt; if (yi_cnt < 0) yi_cnt = -yi_cnt; current_xi += left_flag; current_yi += down_flag; while (xi_cnt || yi_cnt) { const float next_mesh_line_x = mesh_index_to_xpos(current_xi + dxi), next_mesh_line_y = mesh_index_to_ypos(current_yi + dyi), ry = m * next_mesh_line_x + c, // Calculate Y at the next X mesh line rx = (next_mesh_line_y - c) / m; // Calculate X at the next Y mesh line // (No need to worry about m being zero. // If that was the case, it was already detected // as a vertical line move above.) if (left_flag == (rx > next_mesh_line_x)) { // Check if we hit the Y line first // Yes! Crossing a Y Mesh Line next float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, current_xi - left_flag, current_yi + dyi) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); // Undefined parts of the Mesh in z_values[][] are NAN. // Replace NAN corrections with 0.0 to prevent NAN propagation. if (isnan(z0)) z0 = 0.0; if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? rx - start[X_AXIS] : next_mesh_line_y - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } if (!planner.buffer_segment(rx, next_mesh_line_y, z_position + z0, e_position, feed_rate, extruder)) break; current_yi += dyi; yi_cnt--; } else { // Yes! Crossing a X Mesh Line next float z0 = z_correction_for_y_on_vertical_mesh_line(ry, current_xi + dxi, current_yi - down_flag) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); // Undefined parts of the Mesh in z_values[][] are NAN. // Replace NAN corrections with 0.0 to prevent NAN propagation. if (isnan(z0)) z0 = 0.0; if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? next_mesh_line_x - start[X_AXIS] : ry - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } if (!planner.buffer_segment(next_mesh_line_x, ry, z_position + z0, e_position, feed_rate, extruder)) break; current_xi += dxi; xi_cnt--; } if (xi_cnt < 0 || yi_cnt < 0) break; // Too far! Exit the loop and go to FINAL_MOVE } if (g26_debug_flag) debug_current_and_destination(PSTR("generic move done in ubl.line_to_destination_cartesian()")); if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS]) goto FINAL_MOVE; set_current_from_destination(); }
void tmc_say_stealth_status(TMC &st) { st.printLabel(); SERIAL_ECHOPGM(" driver mode:\t"); serialprintPGM(st.get_stealthChop_status() ? PSTR("stealthChop") : PSTR("spreadCycle")); SERIAL_EOL(); }
void unified_bed_leveling::display_map(const int map_type) { constexpr uint8_t spaces = 8 * (GRID_MAX_POINTS_X - 2); SERIAL_PROTOCOLPGM("\nBed Topography Report"); if (map_type == 0) { SERIAL_PROTOCOLPGM(":\n\n"); serial_echo_xy(0, GRID_MAX_POINTS_Y - 1); SERIAL_ECHO_SP(spaces + 3); serial_echo_xy(GRID_MAX_POINTS_X - 1, GRID_MAX_POINTS_Y - 1); SERIAL_EOL(); serial_echo_xy(MESH_MIN_X, MESH_MAX_Y); SERIAL_ECHO_SP(spaces); serial_echo_xy(MESH_MAX_X, MESH_MAX_Y); SERIAL_EOL(); } else { SERIAL_PROTOCOLPGM(" for "); serialprintPGM(map_type == 1 ? PSTR("CSV:\n\n") : PSTR("LCD:\n\n")); } const float current_xi = get_cell_index_x(current_position[X_AXIS] + (MESH_X_DIST) / 2.0), current_yi = get_cell_index_y(current_position[Y_AXIS] + (MESH_Y_DIST) / 2.0); for (int8_t j = GRID_MAX_POINTS_Y - 1; j >= 0; j--) { for (uint8_t i = 0; i < GRID_MAX_POINTS_X; i++) { const bool is_current = i == current_xi && j == current_yi; // is the nozzle here? then mark the number if (map_type == 0) SERIAL_CHAR(is_current ? '[' : ' '); const float f = z_values[i][j]; if (isnan(f)) { serialprintPGM(map_type == 0 ? PSTR(" . ") : PSTR("NAN")); } else if (map_type <= 1) { // if we don't do this, the columns won't line up nicely if (map_type == 0 && f >= 0.0) SERIAL_CHAR(' '); SERIAL_PROTOCOL_F(f, 3); } idle(); if (map_type == 1 && i < GRID_MAX_POINTS_X - 1) SERIAL_CHAR(','); #if TX_BUFFER_SIZE > 0 MYSERIAL.flushTX(); #endif safe_delay(15); if (map_type == 0) { SERIAL_CHAR(is_current ? ']' : ' '); SERIAL_CHAR(' '); } } SERIAL_EOL(); if (j && map_type == 0) { // we want the (0,0) up tight against the block of numbers SERIAL_CHAR(' '); SERIAL_EOL(); } } if (map_type == 0) { serial_echo_xy(MESH_MIN_X, MESH_MIN_Y); SERIAL_ECHO_SP(spaces + 4); serial_echo_xy(MESH_MAX_X, MESH_MIN_Y); SERIAL_EOL(); serial_echo_xy(0, 0); SERIAL_ECHO_SP(spaces + 5); serial_echo_xy(GRID_MAX_POINTS_X - 1, 0); SERIAL_EOL(); } }
void GCodeParser::unknown_command_error() { SERIAL_ECHO_START(); SERIAL_ECHOPAIR(MSG_UNKNOWN_COMMAND, command_ptr); SERIAL_CHAR('"'); SERIAL_EOL(); }
inline bool look_for_lines_to_connect() { float sx, sy, ex, ey; for (uint8_t i = 0; i < GRID_MAX_POINTS_X; i++) { for (uint8_t j = 0; j < GRID_MAX_POINTS_Y; j++) { #if HAS_LCD_MENU if (user_canceled()) return true; // Check if the user wants to stop the Mesh Validation #endif if (i < GRID_MAX_POINTS_X) { // We can't connect to anything to the right than GRID_MAX_POINTS_X. // This is already a half circle because we are at the edge of the bed. if (is_bitmap_set(circle_flags, i, j) && is_bitmap_set(circle_flags, i + 1, j)) { // check if we can do a line to the left if (!is_bitmap_set(horizontal_mesh_line_flags, i, j)) { // // We found two circles that need a horizontal line to connect them // Print it! // sx = _GET_MESH_X( i ) + (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // right edge ex = _GET_MESH_X(i + 1) - (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // left edge sx = constrain(sx, X_MIN_POS + 1, X_MAX_POS - 1); sy = ey = constrain(_GET_MESH_Y(j), Y_MIN_POS + 1, Y_MAX_POS - 1); ex = constrain(ex, X_MIN_POS + 1, X_MAX_POS - 1); if (position_is_reachable(sx, sy) && position_is_reachable(ex, ey)) { if (g26_debug_flag) { SERIAL_ECHOPAIR(" Connecting with horizontal line (sx=", sx); SERIAL_ECHOPAIR(", sy=", sy); SERIAL_ECHOPAIR(") -> (ex=", ex); SERIAL_ECHOPAIR(", ey=", ey); SERIAL_CHAR(')'); SERIAL_EOL(); //debug_current_and_destination(PSTR("Connecting horizontal line.")); } print_line_from_here_to_there(sx, sy, g26_layer_height, ex, ey, g26_layer_height); } bitmap_set(horizontal_mesh_line_flags, i, j); // Mark it as done so we don't do it again, even if we skipped it } } if (j < GRID_MAX_POINTS_Y) { // We can't connect to anything further back than GRID_MAX_POINTS_Y. // This is already a half circle because we are at the edge of the bed. if (is_bitmap_set(circle_flags, i, j) && is_bitmap_set(circle_flags, i, j + 1)) { // check if we can do a line straight down if (!is_bitmap_set( vertical_mesh_line_flags, i, j)) { // // We found two circles that need a vertical line to connect them // Print it! // sy = _GET_MESH_Y( j ) + (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // top edge ey = _GET_MESH_Y(j + 1) - (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // bottom edge sx = ex = constrain(_GET_MESH_X(i), X_MIN_POS + 1, X_MAX_POS - 1); sy = constrain(sy, Y_MIN_POS + 1, Y_MAX_POS - 1); ey = constrain(ey, Y_MIN_POS + 1, Y_MAX_POS - 1); if (position_is_reachable(sx, sy) && position_is_reachable(ex, ey)) { if (g26_debug_flag) { SERIAL_ECHOPAIR(" Connecting with vertical line (sx=", sx); SERIAL_ECHOPAIR(", sy=", sy); SERIAL_ECHOPAIR(") -> (ex=", ex); SERIAL_ECHOPAIR(", ey=", ey); SERIAL_CHAR(')'); SERIAL_EOL(); #if ENABLED(AUTO_BED_LEVELING_UBL) debug_current_and_destination(PSTR("Connecting vertical line.")); #endif } print_line_from_here_to_there(sx, sy, g26_layer_height, ex, ey, g26_layer_height); } bitmap_set(vertical_mesh_line_flags, i, j); // Mark it as done so we don't do it again, even if skipped } } } } } } return false; }
void serialprintln_onoff(const bool onoff) { serialprint_onoff(onoff); SERIAL_EOL(); }
void log_machine_info() { SERIAL_ECHOLNPGM("Machine Type: " #if ENABLED(DELTA) "Delta" #elif IS_SCARA "SCARA" #elif IS_CORE "Core" #else "Cartesian" #endif ); SERIAL_ECHOLNPGM("Probe: " #if ENABLED(PROBE_MANUALLY) "PROBE_MANUALLY" #elif ENABLED(FIX_MOUNTED_PROBE) "FIX_MOUNTED_PROBE" #elif ENABLED(BLTOUCH) "BLTOUCH" #elif HAS_Z_SERVO_PROBE "SERVO PROBE" #elif ENABLED(Z_PROBE_SLED) "Z_PROBE_SLED" #elif ENABLED(Z_PROBE_ALLEN_KEY) "Z_PROBE_ALLEN_KEY" #else "NONE" #endif ); #if HAS_BED_PROBE SERIAL_ECHOPAIR( "Probe Offset X:" STRINGIFY(X_PROBE_OFFSET_FROM_EXTRUDER) " Y:" STRINGIFY(Y_PROBE_OFFSET_FROM_EXTRUDER) " Z:", zprobe_zoffset ); if ((X_PROBE_OFFSET_FROM_EXTRUDER) > 0) SERIAL_ECHOPGM(" (Right"); else if ((X_PROBE_OFFSET_FROM_EXTRUDER) < 0) SERIAL_ECHOPGM(" (Left"); else if ((Y_PROBE_OFFSET_FROM_EXTRUDER) != 0) SERIAL_ECHOPGM(" (Middle"); else SERIAL_ECHOPGM(" (Aligned With"); if ((Y_PROBE_OFFSET_FROM_EXTRUDER) > 0) { #if IS_SCARA SERIAL_ECHOPGM("-Distal"); #else SERIAL_ECHOPGM("-Back"); #endif } else if ((Y_PROBE_OFFSET_FROM_EXTRUDER) < 0) { #if IS_SCARA SERIAL_ECHOPGM("-Proximal"); #else SERIAL_ECHOPGM("-Front"); #endif } else if ((X_PROBE_OFFSET_FROM_EXTRUDER) != 0) SERIAL_ECHOPGM("-Center"); if (zprobe_zoffset < 0) SERIAL_ECHOPGM(" & Below"); else if (zprobe_zoffset > 0) SERIAL_ECHOPGM(" & Above"); else SERIAL_ECHOPGM(" & Same Z as"); SERIAL_ECHOLNPGM(" Nozzle)"); #endif #if HAS_ABL_OR_UBL SERIAL_ECHOLNPGM("Auto Bed Leveling: " #if ENABLED(AUTO_BED_LEVELING_LINEAR) "LINEAR" #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) "BILINEAR" #elif ENABLED(AUTO_BED_LEVELING_3POINT) "3POINT" #elif ENABLED(AUTO_BED_LEVELING_UBL) "UBL" #endif ); if (planner.leveling_active) { SERIAL_ECHOLNPGM(" (enabled)"); #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) if (planner.z_fade_height) SERIAL_ECHOLNPAIR("Z Fade: ", planner.z_fade_height); #endif #if ABL_PLANAR const float diff[XYZ] = { planner.get_axis_position_mm(X_AXIS) - current_position[X_AXIS], planner.get_axis_position_mm(Y_AXIS) - current_position[Y_AXIS], planner.get_axis_position_mm(Z_AXIS) - current_position[Z_AXIS] }; SERIAL_ECHOPGM("ABL Adjustment X"); if (diff[X_AXIS] > 0) SERIAL_CHAR('+'); SERIAL_ECHO(diff[X_AXIS]); SERIAL_ECHOPGM(" Y"); if (diff[Y_AXIS] > 0) SERIAL_CHAR('+'); SERIAL_ECHO(diff[Y_AXIS]); SERIAL_ECHOPGM(" Z"); if (diff[Z_AXIS] > 0) SERIAL_CHAR('+'); SERIAL_ECHO(diff[Z_AXIS]); #else #if ENABLED(AUTO_BED_LEVELING_UBL) SERIAL_ECHOPGM("UBL Adjustment Z"); const float rz = ubl.get_z_correction(current_position[X_AXIS], current_position[Y_AXIS]); #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) SERIAL_ECHOPGM("ABL Adjustment Z"); const float rz = bilinear_z_offset(current_position); #endif SERIAL_ECHO(ftostr43sign(rz, '+')); #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) if (planner.z_fade_height) { SERIAL_ECHOPAIR(" (", ftostr43sign(rz * planner.fade_scaling_factor_for_z(current_position[Z_AXIS]), '+')); SERIAL_CHAR(')'); } #endif #endif } else SERIAL_ECHOLNPGM(" (disabled)"); SERIAL_EOL(); #elif ENABLED(MESH_BED_LEVELING) SERIAL_ECHOPGM("Mesh Bed Leveling"); if (planner.leveling_active) { SERIAL_ECHOLNPGM(" (enabled)"); SERIAL_ECHOPAIR("MBL Adjustment Z", ftostr43sign(mbl.get_z(current_position[X_AXIS], current_position[Y_AXIS] #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) , 1.0 #endif ), '+')); #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) if (planner.z_fade_height) { SERIAL_ECHOPAIR(" (", ftostr43sign( mbl.get_z(current_position[X_AXIS], current_position[Y_AXIS], planner.fade_scaling_factor_for_z(current_position[Z_AXIS])), '+' )); SERIAL_CHAR(')'); } #endif } else SERIAL_ECHOPGM(" (disabled)"); SERIAL_EOL(); #endif // MESH_BED_LEVELING }
/** * M48: Z probe repeatability measurement function. * * Usage: * M48 <P#> <X#> <Y#> <V#> <E> <L#> <S> * 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 Z probe for each reading * L = Number of legs of movement before probe * S = Schizoid (Or Star if you prefer) * * This function requires the machine to be homed before invocation. */ void GcodeSuite::M48() { if (axis_unhomed_error()) return; const int8_t verbose_level = parser.byteval('V', 1); if (!WITHIN(verbose_level, 0, 4)) { SERIAL_ECHOLNPGM("?(V)erbose level is implausible (0-4)."); return; } if (verbose_level > 0) SERIAL_ECHOLNPGM("M48 Z-Probe Repeatability Test"); const int8_t n_samples = parser.byteval('P', 10); if (!WITHIN(n_samples, 4, 50)) { SERIAL_ECHOLNPGM("?Sample size not plausible (4-50)."); return; } const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE; float X_current = current_position[X_AXIS], Y_current = current_position[Y_AXIS]; const float X_probe_location = parser.linearval('X', X_current + X_PROBE_OFFSET_FROM_EXTRUDER), Y_probe_location = parser.linearval('Y', Y_current + Y_PROBE_OFFSET_FROM_EXTRUDER); if (!position_is_reachable_by_probe(X_probe_location, Y_probe_location)) { SERIAL_ECHOLNPGM("? (X,Y) out of bounds."); return; } bool seen_L = parser.seen('L'); uint8_t n_legs = seen_L ? parser.value_byte() : 0; if (n_legs > 15) { SERIAL_ECHOLNPGM("?Number of legs in movement not plausible (0-15)."); return; } if (n_legs == 1) n_legs = 2; const bool schizoid_flag = parser.boolval('S'); if (schizoid_flag && !seen_L) n_legs = 7; /** * 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_ECHOLNPGM("Positioning the probe..."); // Disable bed level correction in M48 because we want the raw data when we probe #if HAS_LEVELING const bool was_enabled = planner.leveling_active; set_bed_leveling_enabled(false); #endif setup_for_endstop_or_probe_move(); float mean = 0.0, sigma = 0.0, min = 99999.9, max = -99999.9, sample_set[n_samples]; // Move to the first point, deploy, and probe const float t = probe_pt(X_probe_location, Y_probe_location, raise_after, verbose_level); bool probing_good = !isnan(t); if (probing_good) { randomSeed(millis()); for (uint8_t n = 0; n < n_samples; n++) { if (n_legs) { const int dir = (random(0, 10) > 5.0) ? -1 : 1; // clockwise or counter clockwise float angle = random(0, 360); const float radius = random( #if ENABLED(DELTA) (int) (0.1250000000 * (DELTA_PRINTABLE_RADIUS)), (int) (0.3333333333 * (DELTA_PRINTABLE_RADIUS)) #else (int) 5.0, (int) (0.125 * MIN(X_BED_SIZE, Y_BED_SIZE)) #endif ); if (verbose_level > 3) { SERIAL_ECHOPAIR("Starting radius: ", radius); SERIAL_ECHOPAIR(" angle: ", angle); SERIAL_ECHOPGM(" Direction: "); if (dir > 0) SERIAL_ECHOPGM("Counter-"); SERIAL_ECHOLNPGM("Clockwise"); } for (uint8_t l = 0; l < n_legs - 1; l++) { float delta_angle; if (schizoid_flag) // The points of a 5 point star are 72 degrees apart. We need to // skip a point and go to the next one on the star. delta_angle = dir * 2.0 * 72.0; else // If we do this line, we are just trying to move further // around the circle. delta_angle = dir * (float) random(25, 45); angle += delta_angle; while (angle > 360.0) // We probably do not need to keep the angle between 0 and 2*PI, but the angle -= 360.0; // Arduino documentation says the trig functions should not be given values while (angle < 0.0) // outside of this range. It looks like they behave correctly with angle += 360.0; // numbers outside of the range, but just to be safe we clamp them. X_current = X_probe_location - (X_PROBE_OFFSET_FROM_EXTRUDER) + cos(RADIANS(angle)) * radius; Y_current = Y_probe_location - (Y_PROBE_OFFSET_FROM_EXTRUDER) + sin(RADIANS(angle)) * radius; #if DISABLED(DELTA) X_current = constrain(X_current, X_MIN_POS, X_MAX_POS); Y_current = constrain(Y_current, Y_MIN_POS, Y_MAX_POS); #else // If we have gone out too far, we can do a simple fix and scale the numbers // back in closer to the origin. while (!position_is_reachable_by_probe(X_current, Y_current)) { X_current *= 0.8; Y_current *= 0.8; if (verbose_level > 3) { SERIAL_ECHOPAIR("Pulling point towards center:", X_current); SERIAL_ECHOLNPAIR(", ", Y_current); } } #endif if (verbose_level > 3) { SERIAL_ECHOPGM("Going to:"); SERIAL_ECHOPAIR(" X", X_current); SERIAL_ECHOPAIR(" Y", Y_current); SERIAL_ECHOLNPAIR(" Z", current_position[Z_AXIS]); } do_blocking_move_to_xy(X_current, Y_current); } // n_legs loop } // n_legs // Probe a single point sample_set[n] = probe_pt(X_probe_location, Y_probe_location, raise_after, 0); // Break the loop if the probe fails probing_good = !isnan(sample_set[n]); if (!probing_good) break; /** * Get the current mean for the data points we have so far */ float sum = 0.0; for (uint8_t j = 0; j <= n; j++) sum += sample_set[j]; mean = sum / (n + 1); NOMORE(min, sample_set[n]); NOLESS(max, sample_set[n]); /** * 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++) sum += sq(sample_set[j] - mean); sigma = SQRT(sum / (n + 1)); if (verbose_level > 0) { if (verbose_level > 1) { SERIAL_ECHO(n + 1); SERIAL_ECHOPAIR(" of ", (int)n_samples); SERIAL_ECHOPAIR_F(": z: ", sample_set[n], 3); if (verbose_level > 2) { SERIAL_ECHOPAIR_F(" mean: ", mean, 4); SERIAL_ECHOPAIR_F(" sigma: ", sigma, 6); SERIAL_ECHOPAIR_F(" min: ", min, 3); SERIAL_ECHOPAIR_F(" max: ", max, 3); SERIAL_ECHOPAIR_F(" range: ", max-min, 3); } SERIAL_EOL(); } } } // n_samples loop } STOW_PROBE(); if (probing_good) { SERIAL_ECHOLNPGM("Finished!"); if (verbose_level > 0) { SERIAL_ECHOPAIR_F("Mean: ", mean, 6); SERIAL_ECHOPAIR_F(" Min: ", min, 3); SERIAL_ECHOPAIR_F(" Max: ", max, 3); SERIAL_ECHOLNPAIR_F(" Range: ", max-min, 3); } SERIAL_ECHOLNPAIR_F("Standard Deviation: ", sigma, 6); SERIAL_EOL(); } clean_up_after_endstop_or_probe_move(); // Re-enable bed level correction if it had been on #if HAS_LEVELING set_bed_leveling_enabled(was_enabled); #endif report_current_position(); }
void scara_report_positions() { SERIAL_ECHOLNPAIR("SCARA Theta:", planner.get_axis_position_degrees(A_AXIS), " Psi+Theta:", planner.get_axis_position_degrees(B_AXIS)); SERIAL_EOL(); }