diff --git a/Marlin/least_squares_fit.cpp b/Marlin/least_squares_fit.cpp index 1ae9d8513..ee9d7cc9e 100644 --- a/Marlin/least_squares_fit.cpp +++ b/Marlin/least_squares_fit.cpp @@ -26,7 +26,9 @@ * This algorithm is high speed and has a very small code footprint. * Its results are identical to both the Iterative Least-Squares published * earlier by Roxy and the QR_SOLVE solution. If used in place of QR_SOLVE - * it saves roughly 10K of program memory. + * it saves roughly 10K of program memory. It also does not require all of + * coordinates to be present during the calculations. Each point can be + * probed and then discarded. * */ @@ -34,84 +36,60 @@ #if ENABLED(AUTO_BED_LEVELING_UBL) // Currently only used by UBL, but is applicable to Grid Based (Linear) Bed Leveling -#include "ubl.h" -#include "Marlin.h" #include "macros.h" #include -double linear_fit_average(double m[], const int); -//double linear_fit_average_squared(double m[], const int); -//double linear_fit_average_mixed_terms(double m1[], double m2[], const int); -double linear_fit_average_product(double matrix1[], double matrix2[], const int n); -void linear_fit_subtract_mean(double matrix[], double bar, const int n); -double linear_fit_max_abs(double m[], const int); - -linear_fit linear_fit_results; - -linear_fit* lsf_linear_fit(double x[], double y[], double z[], const int n) { - double xbar, ybar, zbar, - x2bar, y2bar, - xybar, xzbar, yzbar, - D; - - linear_fit_results.A = 0.0; - linear_fit_results.B = 0.0; - linear_fit_results.D = 0.0; - - xbar = linear_fit_average(x, n); - ybar = linear_fit_average(y, n); - zbar = linear_fit_average(z, n); - - linear_fit_subtract_mean(x, xbar, n); - linear_fit_subtract_mean(y, ybar, n); - linear_fit_subtract_mean(z, zbar, n); - - x2bar = linear_fit_average_product(x, x, n); - y2bar = linear_fit_average_product(y, y, n); - xybar = linear_fit_average_product(x, y, n); - xzbar = linear_fit_average_product(x, z, n); - yzbar = linear_fit_average_product(y, z, n); - - D = x2bar * y2bar - xybar * xybar; - for (int i = 0; i < n; i++) { - if (fabs(D) <= 1e-15 * (linear_fit_max_abs(x, n) + linear_fit_max_abs(y, n))) { - printf("error: x,y points are collinear at index:%d\n", i); - return NULL; +#include "least_squares_fit.h" + +void incremental_LSF_reset(struct linear_fit_data *lsf) { + lsf->n = 0; + lsf->A = 0.0; // probably a memset() can be done to zero + lsf->B = 0.0; // this whole structure + lsf->D = 0.0; + lsf->xbar = lsf->ybar = lsf->zbar = 0.0; + lsf->x2bar = lsf->y2bar = lsf->z2bar = 0.0; + lsf->xybar = lsf->xzbar = lsf->yzbar = 0.0; + lsf->max_absx = lsf->max_absy = 0.0; } - } - - linear_fit_results.A = -(xzbar * y2bar - yzbar * xybar) / D; - linear_fit_results.B = -(yzbar * x2bar - xzbar * xybar) / D; - // linear_fit_results.D = -(zbar - linear_fit_results->A * xbar - linear_fit_results->B * ybar); - linear_fit_results.D = -(zbar + linear_fit_results.A * xbar + linear_fit_results.B * ybar); - return &linear_fit_results; -} - -double linear_fit_average(double *matrix, const int n) { - double sum = 0.0; - for (int i = 0; i < n; i++) - sum += matrix[i]; - return sum / (double)n; -} - -double linear_fit_average_product(double *matrix1, double *matrix2, const int n) { - double sum = 0.0; - for (int i = 0; i < n; i++) - sum += matrix1[i] * matrix2[i]; - return sum / (double)n; -} - -void linear_fit_subtract_mean(double *matrix, double bar, const int n) { - for (int i = 0; i < n; i++) - matrix[i] -= bar; -} +void incremental_LSF(struct linear_fit_data *lsf, float x, float y, float z) { + lsf->xbar += x; + lsf->ybar += y; + lsf->zbar += z; + lsf->x2bar += x*x; + lsf->y2bar += y*y; + lsf->z2bar += z*z; + lsf->xybar += x*y; + lsf->xzbar += x*z; + lsf->yzbar += y*z; + lsf->max_absx = (fabs(x) > lsf->max_absx) ? fabs(x) : lsf->max_absx; + lsf->max_absy = (fabs(y) > lsf->max_absy) ? fabs(y) : lsf->max_absy; + lsf->n++; + return; + } -double linear_fit_max_abs(double *matrix, const int n) { - double max_abs = 0.0; - for (int i = 0; i < n; i++) - NOLESS(max_abs, fabs(matrix[i])); - return max_abs; +int finish_incremental_LSF(struct linear_fit_data *lsf) { + float DD, N; + + N = (float) lsf->n; + lsf->xbar /= N; + lsf->ybar /= N; + lsf->zbar /= N; + lsf->x2bar = lsf->x2bar/N - lsf->xbar*lsf->xbar; + lsf->y2bar = lsf->y2bar/N - lsf->ybar*lsf->ybar; + lsf->z2bar = lsf->z2bar/N - lsf->zbar*lsf->zbar; + lsf->xybar = lsf->xybar/N - lsf->xbar*lsf->ybar; + lsf->yzbar = lsf->yzbar/N - lsf->ybar*lsf->zbar; + lsf->xzbar = lsf->xzbar/N - lsf->xbar*lsf->zbar; + + DD = lsf->x2bar*lsf->y2bar - lsf->xybar*lsf->xybar; + if (fabs(DD) <= 1e-10*(lsf->max_absx+lsf->max_absy)) + return -1; + + lsf->A = (lsf->yzbar*lsf->xybar - lsf->xzbar*lsf->y2bar) / DD; + lsf->B = (lsf->xzbar*lsf->xybar - lsf->yzbar*lsf->x2bar) / DD; + lsf->D = -(lsf->zbar + lsf->A*lsf->xbar + lsf->B*lsf->ybar); + return 0; } #endif diff --git a/Marlin/least_squares_fit.h b/Marlin/least_squares_fit.h new file mode 100644 index 000000000..41a5741cf --- /dev/null +++ b/Marlin/least_squares_fit.h @@ -0,0 +1,57 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/** + * Incremental Least Squares Best Fit By Roxy and Ed Williams + * + * This algorithm is high speed and has a very small code footprint. + * Its results are identical to both the Iterative Least-Squares published + * earlier by Roxy and the QR_SOLVE solution. If used in place of QR_SOLVE + * it saves roughly 10K of program memory. And even better... the data + * fed into the algorithm does not need to all be present at the same time. + * A point can be probed and its values fed into the algorithm and then discarded. + * + */ + +#include "MarlinConfig.h" + +#if ENABLED(AUTO_BED_LEVELING_UBL) // Currently only used by UBL, but is applicable to Grid Based (Linear) Bed Leveling + +#include "Marlin.h" +#include "macros.h" +#include + +struct linear_fit_data { + int n; + float xbar, ybar, zbar; + float x2bar, y2bar, z2bar; + float xybar, xzbar, yzbar; + float max_absx, max_absy; + float A, B, D; +}; + +void incremental_LSF_reset(struct linear_fit_data *); +void incremental_LSF(struct linear_fit_data *, float x, float y, float z); +int finish_incremental_LSF(struct linear_fit_data *); + +#endif + diff --git a/Marlin/ubl.cpp b/Marlin/ubl.cpp index 9eb469fdd..8d8676bf7 100755 --- a/Marlin/ubl.cpp +++ b/Marlin/ubl.cpp @@ -27,6 +27,7 @@ #include "ubl.h" #include "hex_print_routines.h" + #include "temperature.h" /** * These support functions allow the use of large bit arrays of flags that take very @@ -38,6 +39,8 @@ void bit_set(uint16_t bits[16], uint8_t x, uint8_t y) { SBI(bits[y], x); } bool is_bit_set(uint16_t bits[16], uint8_t x, uint8_t y) { return TEST(bits[y], x); } + int ubl_cnt=0; + static void serial_echo_xy(const uint16_t x, const uint16_t y) { SERIAL_CHAR('('); SERIAL_ECHO(x); @@ -50,9 +53,6 @@ static void serial_echo_12x_spaces() { for (uint8_t i = GRID_MAX_POINTS_X - 1; --i;) { SERIAL_ECHOPGM(" "); - #if TX_BUFFER_SIZE > 0 - MYSERIAL.flushTX(); - #endif safe_delay(10); } } @@ -70,11 +70,13 @@ bool unified_bed_leveling::g26_debug_flag = false, unified_bed_leveling::has_control_of_lcd_panel = false; - int8_t unified_bed_leveling::eeprom_start = -1; + int16_t unified_bed_leveling::eeprom_start = -1; // Please stop changing this to 8 bits in size + // It needs to hold values bigger than this. volatile int unified_bed_leveling::encoder_diff; unified_bed_leveling::unified_bed_leveling() { + ubl_cnt++; // Debug counter to insure we only have one UBL object present in memory. reset(); } diff --git a/Marlin/ubl.h b/Marlin/ubl.h index 9ddf67b97..b02411c2c 100755 --- a/Marlin/ubl.h +++ b/Marlin/ubl.h @@ -26,11 +26,10 @@ #include "MarlinConfig.h" #if ENABLED(AUTO_BED_LEVELING_UBL) - #include "Marlin.h" + #include "planner.h" #include "math.h" #include "vector_3.h" - #include "planner.h" #define UBL_VERSION "1.00" #define UBL_OK false @@ -49,10 +48,8 @@ void debug_current_and_destination(const char * const title); void ubl_line_to_destination(const float&, uint8_t); void manually_probe_remaining_mesh(const float&, const float&, const float&, const float&, const bool); - vector_3 tilt_mesh_based_on_3pts(const float&, const float&, const float&); float measure_business_card_thickness(const float&); mesh_index_pair find_closest_mesh_point_of_type(const MeshPointType, const float&, const float&, const bool, unsigned int[16], bool); - void find_mean_mesh_height(); void shift_mesh_height(); bool g29_parameter_parsing(); void g29_what_command(); @@ -67,10 +64,8 @@ void gcode_G26(); void gcode_G28(); void gcode_G29(); - extern char conv[9]; - void save_ubl_active_state_and_disable(); - void restore_ubl_active_state_and_leave(); + extern int ubl_cnt; /////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -79,7 +74,6 @@ void lcd_quick_feedback(); #endif - enum MBLStatus { MBL_STATUS_NONE = 0, MBL_STATUS_HAS_MESH_BIT = 0, MBL_STATUS_ACTIVE_BIT = 1 }; #define MESH_X_DIST (float(UBL_MESH_MAX_X - (UBL_MESH_MIN_X)) / float(GRID_MAX_POINTS_X - 1)) #define MESH_Y_DIST (float(UBL_MESH_MAX_Y - (UBL_MESH_MIN_Y)) / float(GRID_MAX_POINTS_Y - 1)) @@ -97,6 +91,43 @@ public: +// +// Please do not put STATIC qualifiers in front of ANYTHING in this file. You WILL cause problems by doing that. +// The GCC optimizer inlines static functions and this DRAMATICALLY increases the size of the stack frame of +// functions that call STATIC functions. +// + void find_mean_mesh_height(); + void shift_mesh_height(); + void probe_entire_mesh(const float &lx, const float &ly, const bool do_ubl_mesh_map, const bool stow_probe, bool do_furthest); + void tilt_mesh_based_on_3pts(const float &z1, const float &z2, const float &z3); + void tilt_mesh_based_on_probed_grid(const bool do_ubl_mesh_map); + void manually_probe_remaining_mesh(const float &lx, const float &ly, const float &z_clearance, const float &card_thickness, const bool do_ubl_mesh_map); + void save_ubl_active_state_and_disable(); + void restore_ubl_active_state_and_leave(); + void g29_what_command(); +// +// Please do not put STATIC qualifiers in front of ANYTHING in this file. You WILL cause problems by doing that. +// The GCC optimizer inlines static functions and this DRAMATICALLY increases the size of the stack frame of +// functions that call STATIC functions. +// + void g29_eeprom_dump() ; + void g29_compare_current_mesh_to_stored_mesh(); + void fine_tune_mesh(const float &lx, const float &ly, const bool do_ubl_mesh_map); + void smart_fill_mesh(); + void display_map(const int); + void reset(); +// +// Please do not put STATIC qualifiers in front of ANYTHING in this file. You WILL cause problems by doing that. +// The GCC optimizer inlines static functions and this DRAMATICALLY increases the size of the stack frame of +// functions that call STATIC functions. +// + void invalidate(); + void store_state(); + void load_state(); + void store_mesh(const int16_t); + void load_mesh(const int16_t); + bool sanity_check(); + static ubl_state state; static float z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; @@ -125,32 +156,27 @@ static bool g26_debug_flag, has_control_of_lcd_panel; - static int8_t eeprom_start; + static int16_t eeprom_start; // Please do no change this to 8 bits in size + // It needs to hold values bigger than this. static volatile int encoder_diff; // Volatile because it's changed at interrupt time. unified_bed_leveling(); - static void display_map(const int); - - static void reset(); - static void invalidate(); - - static void store_mesh(const int16_t); - static void load_mesh(const int16_t); - - static bool sanity_check(); - - static FORCE_INLINE void set_z(const int8_t px, const int8_t py, const float &z) { z_values[px][py] = z; } - - static int8_t get_cell_index_x(const float &x) { +// +// Please do not put STATIC qualifiers in front of ANYTHING in this file. You WILL cause problems by doing that. +// The GCC optimizer inlines static functions and this DRAMATICALLY increases the size of the stack frame of +// functions that call STATIC functions. +// + FORCE_INLINE void set_z(const int8_t px, const int8_t py, const float &z) { z_values[px][py] = z; } + int8_t get_cell_index_x(const float &x) { const int8_t cx = (x - (UBL_MESH_MIN_X)) * (1.0 / (MESH_X_DIST)); return constrain(cx, 0, (GRID_MAX_POINTS_X) - 1); // -1 is appropriate if we want all movement to the X_MAX } // position. But with this defined this way, it is possible // to extrapolate off of this point even further out. Probably // that is OK because something else should be keeping that from // happening and should not be worried about at this level. - static int8_t get_cell_index_y(const float &y) { + int8_t get_cell_index_y(const float &y) { const int8_t cy = (y - (UBL_MESH_MIN_Y)) * (1.0 / (MESH_Y_DIST)); return constrain(cy, 0, (GRID_MAX_POINTS_Y) - 1); // -1 is appropriate if we want all movement to the Y_MAX } // position. But with this defined this way, it is possible @@ -158,12 +184,17 @@ // that is OK because something else should be keeping that from // happening and should not be worried about at this level. - static int8_t find_closest_x_index(const float &x) { +// +// Please do not put STATIC qualifiers in front of ANYTHING in this file. You WILL cause problems by doing that. +// The GCC optimizer inlines static functions and this DRAMATICALLY increases the size of the stack frame of +// functions that call STATIC functions. +// + int8_t find_closest_x_index(const float &x) { const int8_t px = (x - (UBL_MESH_MIN_X) + (MESH_X_DIST) * 0.5) * (1.0 / (MESH_X_DIST)); return WITHIN(px, 0, GRID_MAX_POINTS_X - 1) ? px : -1; } - static int8_t find_closest_y_index(const float &y) { + int8_t find_closest_y_index(const float &y) { const int8_t py = (y - (UBL_MESH_MIN_Y) + (MESH_Y_DIST) * 0.5) * (1.0 / (MESH_Y_DIST)); return WITHIN(py, 0, GRID_MAX_POINTS_Y - 1) ? py : -1; } @@ -183,15 +214,20 @@ * It is fairly expensive with its 4 floating point additions and 2 floating point * multiplications. */ - static FORCE_INLINE float calc_z0(const float &a0, const float &a1, const float &z1, const float &a2, const float &z2) { + FORCE_INLINE float calc_z0(const float &a0, const float &a1, const float &z1, const float &a2, const float &z2) { return z1 + (z2 - z1) * (a0 - a1) / (a2 - a1); } +// +// Please do not put STATIC qualifiers in front of ANYTHING in this file. You WILL cause problems by doing that. +// The GCC optimizer inlines static functions and this DRAMATICALLY increases the size of the stack frame of +// functions that call STATIC functions. +// /** * z_correction_for_x_on_horizontal_mesh_line is an optimization for * the rare occasion when a point lies exactly on a Mesh line (denoted by index yi). */ - static inline float z_correction_for_x_on_horizontal_mesh_line(const float &lx0, const int x1_i, const int yi) { + inline float z_correction_for_x_on_horizontal_mesh_line(const float &lx0, const int x1_i, const int yi) { if (!WITHIN(x1_i, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(yi, 0, GRID_MAX_POINTS_Y - 1)) { SERIAL_ECHOPAIR("? in z_correction_for_x_on_horizontal_mesh_line(lx0=", lx0); SERIAL_ECHOPAIR(",x1_i=", x1_i); @@ -210,7 +246,7 @@ // // See comments above for z_correction_for_x_on_horizontal_mesh_line // - static inline float z_correction_for_y_on_vertical_mesh_line(const float &ly0, const int xi, const int y1_i) { + inline float z_correction_for_y_on_vertical_mesh_line(const float &ly0, const int xi, const int y1_i) { if (!WITHIN(xi, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(y1_i, 0, GRID_MAX_POINTS_Y - 1)) { SERIAL_ECHOPAIR("? in get_z_correction_along_vertical_mesh_line_at_specific_x(ly0=", ly0); SERIAL_ECHOPAIR(", x1_i=", xi); @@ -232,7 +268,7 @@ * Z-Height at both ends. Then it does a linear interpolation of these heights based * on the Y position within the cell. */ - static float get_z_correction(const float &lx0, const float &ly0) { + float get_z_correction(const float &lx0, const float &ly0) { const int8_t cx = get_cell_index_x(RAW_X_POSITION(lx0)), cy = get_cell_index_y(RAW_Y_POSITION(ly0)); @@ -308,7 +344,7 @@ */ #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) - static FORCE_INLINE float fade_scaling_factor_for_z(const float &lz) { + FORCE_INLINE float fade_scaling_factor_for_z(const float &lz) { if (planner.z_fade_height == 0.0) return 1.0; static float fade_scaling_factor = 1.0; diff --git a/Marlin/ubl_G29.cpp b/Marlin/ubl_G29.cpp index e6ff144b8..a7071ae6d 100755 --- a/Marlin/ubl_G29.cpp +++ b/Marlin/ubl_G29.cpp @@ -33,12 +33,12 @@ #include "ultralcd.h" #include + #include "least_squares_fit.h" void lcd_return_to_status(); bool lcd_clicked(); void lcd_implementation_clear(); void lcd_mesh_edit_setup(float initial); - void tilt_mesh_based_on_probed_grid(const bool); float lcd_mesh_edit(); void lcd_z_offset_edit_setup(float); float lcd_z_offset_edit(); @@ -50,15 +50,10 @@ extern bool code_has_value(); extern float probe_pt(float x, float y, bool, int); extern bool set_probe_deployed(bool); + void smart_fill_mesh(); bool ProbeStay = true; - constexpr float ubl_3_point_1_X = UBL_PROBE_PT_1_X, - ubl_3_point_1_Y = UBL_PROBE_PT_1_Y, - ubl_3_point_2_X = UBL_PROBE_PT_2_X, - ubl_3_point_2_Y = UBL_PROBE_PT_2_Y, - ubl_3_point_3_X = UBL_PROBE_PT_3_X, - ubl_3_point_3_Y = UBL_PROBE_PT_3_Y; #define SIZE_OF_LITTLE_RAISE 0 #define BIG_RAISE_NOT_NEEDED 0 @@ -133,10 +128,6 @@ * be specified and is suitable to Cut & Paste into Excel to allow graphing of the user's * mesh. * - * N No Home G29 normally insists that a G28 has been performed. You can over rule this with an - * N option. In general, you should not do this. This can only be done safely with - * commands that do not move the nozzle. - * * The P or Phase commands are used for the bulk of the work to setup a Mesh. In general, your Mesh will * start off being initialized with a G29 P0 or a G29 P1. Further refinement of the Mesh happens with * each additional Phase that processes it. @@ -195,12 +186,21 @@ * Phase 2 allows the O (Map) parameter to be specified. This helps the user see the progression * of the Mesh being built. * - * P3 Phase 3 Fill the unpopulated regions of the Mesh with a fixed value. The C parameter is - * used to specify the 'constant' value to fill all invalid areas of the Mesh. If no C parameter - * is specified, a value of 0.0 is assumed. The R parameter can be given to specify the number - * of points to set. If the R parameter is specified the current nozzle position is used to - * find the closest points to alter unless the X and Y parameter are used to specify the fill - * location. + * P3 Phase 3 Fill the unpopulated regions of the Mesh with a fixed value. There are two different paths the + * user can go down. If the user specifies the value using the C parameter, the closest invalid + * mesh points to the nozzle will be filled. The user can specify a repeat count using the R + * parameter with the C version of the command. + * + * A second version of the fill command is available if no C constant is specified. Not + * specifying a C constant will invoke the 'Smart Fill' algorithm. The G29 P3 command will search + * from the edges of the mesh inward looking for invalid mesh points. It will look at the next + * several mesh points to determine if the print bed is sloped up or down. If the bed is sloped + * upward from the invalid mesh point, it will be replaced with the value of the nearest mesh point. + * If the bed is sloped downward from the invalid mesh point, it will be replaced with a value that + * puts all three points in a line. The second version of the G29 P3 command is a quick, easy and + * usually safe way to populate the unprobed regions of your mesh so you can continue to the G26 + * Mesh Validation Pattern phase. Please note that you are populating your mesh with unverified + * numbers. You should use some scrutiny and caution. * * P4 Phase 4 Fine tune the Mesh. The Delta Mesh Compensation System assume the existance of * an LCD Panel. It is possible to fine tune the mesh without the use of an LCD Panel. @@ -243,6 +243,9 @@ * command is not anticipated to be of much value to the typical user. It is intended * for developers to help them verify correct operation of the Unified Bed Leveling System. * + * R # Repeat Repeat this command the specified number of times. If no number is specified the + * command will be repeated GRID_MAX_POINTS_X * GRID_MAX_POINTS_Y times. + * * S Store Store the current Mesh in the Activated area of the EEPROM. It will also store the * current state of the Unified Bed Leveling system in the EEPROM. * @@ -301,19 +304,20 @@ * we now have the functionality and features of all three systems combined. */ + #define USE_NOZZLE_AS_REFERENCE 0 + #define USE_PROBE_AS_REFERENCE 1 + // The simple parameter flags and values are 'static' so parameter parsing can be in a support routine. static int g29_verbose_level, phase_value = -1, repetition_cnt, storage_slot = 0, map_type, grid_size; static bool repeat_flag, c_flag, x_flag, y_flag; static float x_pos, y_pos, measured_z, card_thickness = 0.0, ubl_constant = 0.0; - #if ENABLED(ULTRA_LCD) extern void lcd_setstatus(const char* message, const bool persist); extern void lcd_setstatuspgm(const char* message, const uint8_t level); - #endif - void gcode_G29() { - SERIAL_PROTOCOLLNPAIR("ubl.eeprom_start=", ubl.eeprom_start); + void __attribute__((optimize("O0"))) gcode_G29() { + if (ubl.eeprom_start < 0) { SERIAL_PROTOCOLLNPGM("?You need to enable your EEPROM and initialize it"); SERIAL_PROTOCOLLNPGM("with M502, M500, M501 in that order.\n"); @@ -332,7 +336,7 @@ repetition_cnt = code_has_value() ? code_value_int() : 1; while (repetition_cnt--) { if (cnt > 20) { cnt = 0; idle(); } - const mesh_index_pair location = find_closest_mesh_point_of_type(REAL, x_pos, y_pos, 0, NULL, false); // The '0' says we want to use the nozzle's position + const mesh_index_pair location = find_closest_mesh_point_of_type(REAL, x_pos, y_pos, USE_NOZZLE_AS_REFERENCE, NULL, false); if (location.x_index < 0) { SERIAL_PROTOCOLLNPGM("Entire Mesh invalidated.\n"); break; // No more invalid Mesh Points to populate @@ -376,11 +380,13 @@ } if (code_seen('J')) { - if (!WITHIN(grid_size, 2, 5)) { - SERIAL_PROTOCOLLNPGM("ERROR - grid size must be between 2 and 5"); + if (!WITHIN(grid_size, 2, 9)) { + SERIAL_PROTOCOLLNPGM("ERROR - grid size must be between 2 and 9"); return; } - tilt_mesh_based_on_probed_grid(code_seen('O') || code_seen('M')); + ubl.save_ubl_active_state_and_disable(); + ubl.tilt_mesh_based_on_probed_grid(code_seen('O') || code_seen('M')); + ubl.restore_ubl_active_state_and_leave(); } if (code_seen('P')) { @@ -412,7 +418,7 @@ SERIAL_PROTOCOL(y_pos); SERIAL_PROTOCOLLNPGM(")\n"); } - probe_entire_mesh(x_pos + X_PROBE_OFFSET_FROM_EXTRUDER, y_pos + Y_PROBE_OFFSET_FROM_EXTRUDER, + ubl.probe_entire_mesh(x_pos + X_PROBE_OFFSET_FROM_EXTRUDER, y_pos + Y_PROBE_OFFSET_FROM_EXTRUDER, code_seen('O') || code_seen('M'), code_seen('E'), code_seen('U')); break; @@ -454,17 +460,22 @@ case 3: { // - // Populate invalid Mesh areas with a constant + // Populate invalid Mesh areas. Two choices are available to the user. The user can + // specify the constant to be used with a C # paramter. Or the user can allow the G29 P3 command to + // apply a 'reasonable' constant to the invalid mesh point. Some caution and scrutiny should be used + // on either of these paths! // - const float height = code_seen('C') ? ubl_constant : 0.0; - // If no repetition is specified, do the whole Mesh - if (!repeat_flag) repetition_cnt = 9999; + if (c_flag) { while (repetition_cnt--) { - const mesh_index_pair location = find_closest_mesh_point_of_type(INVALID, x_pos, y_pos, 0, NULL, false); // The '0' says we want to use the nozzle's position + const mesh_index_pair location = find_closest_mesh_point_of_type(INVALID, x_pos, y_pos, USE_NOZZLE_AS_REFERENCE, NULL, false); if (location.x_index < 0) break; // No more invalid Mesh Points to populate - ubl.z_values[location.x_index][location.y_index] = height; + ubl.z_values[location.x_index][location.y_index] = ubl_constant; + } + break; + } else // The user wants to do a 'Smart' fill where we use the surrounding known + smart_fill_mesh(); // values to provide a good guess of what the unprobed mesh point should be + break; } - } break; case 4: // @@ -473,10 +484,10 @@ fine_tune_mesh(x_pos, y_pos, code_seen('O') || code_seen('M')); break; case 5: - find_mean_mesh_height(); + ubl.find_mean_mesh_height(); break; case 6: - shift_mesh_height(); + ubl.shift_mesh_height(); break; case 10: @@ -517,26 +528,22 @@ } if (code_seen('T')) { - const float lx1 = LOGICAL_X_POSITION(ubl_3_point_1_X), - lx2 = LOGICAL_X_POSITION(ubl_3_point_2_X), - lx3 = LOGICAL_X_POSITION(ubl_3_point_3_X), - ly1 = LOGICAL_Y_POSITION(ubl_3_point_1_Y), - ly2 = LOGICAL_Y_POSITION(ubl_3_point_2_Y), - ly3 = LOGICAL_Y_POSITION(ubl_3_point_3_Y); - float z1 = probe_pt(lx1, ly1, false /*Stow Flag*/, g29_verbose_level), - z2 = probe_pt(lx2, ly2, false /*Stow Flag*/, g29_verbose_level), - z3 = probe_pt(lx3, ly3, true /*Stow Flag*/, g29_verbose_level); + float z1 = probe_pt( LOGICAL_X_POSITION(UBL_PROBE_PT_1_X), LOGICAL_Y_POSITION(UBL_PROBE_PT_1_Y), false, g29_verbose_level), + z2 = probe_pt( LOGICAL_X_POSITION(UBL_PROBE_PT_2_X), LOGICAL_Y_POSITION(UBL_PROBE_PT_2_Y), false, g29_verbose_level), + z3 = probe_pt( LOGICAL_X_POSITION(UBL_PROBE_PT_3_X), LOGICAL_Y_POSITION(UBL_PROBE_PT_3_Y), true, g29_verbose_level); // We need to adjust z1, z2, z3 by the Mesh Height at these points. Just because they are non-zero doesn't mean // the Mesh is tilted! (We need to compensate each probe point by what the Mesh says that location's height is) - z1 -= ubl.get_z_correction(lx1, ly1); - z2 -= ubl.get_z_correction(lx2, ly2); - z3 -= ubl.get_z_correction(lx3, ly3); + ubl.save_ubl_active_state_and_disable(); + z1 -= ubl.get_z_correction(LOGICAL_X_POSITION(UBL_PROBE_PT_1_X), LOGICAL_Y_POSITION(UBL_PROBE_PT_1_Y)) /* + zprobe_zoffset */ ; + z2 -= ubl.get_z_correction(LOGICAL_X_POSITION(UBL_PROBE_PT_2_X), LOGICAL_Y_POSITION(UBL_PROBE_PT_2_Y)) /* + zprobe_zoffset */ ; + z3 -= ubl.get_z_correction(LOGICAL_X_POSITION(UBL_PROBE_PT_3_X), LOGICAL_Y_POSITION(UBL_PROBE_PT_3_Y)) /* + zprobe_zoffset */ ; do_blocking_move_to_xy((X_MAX_POS - (X_MIN_POS)) / 2.0, (Y_MAX_POS - (Y_MIN_POS)) / 2.0); - tilt_mesh_based_on_3pts(z1, z2, z3); + ubl.tilt_mesh_based_on_3pts(z1, z2, z3); + ubl.restore_ubl_active_state_and_leave(); } // @@ -618,7 +625,7 @@ if (code_has_value()) ubl.state.z_offset = code_value_float(); // do the simple case. Just lock in the specified value else { - save_ubl_active_state_and_disable(); + ubl.save_ubl_active_state_and_disable(); //measured_z = probe_pt(x_pos + X_PROBE_OFFSET_FROM_EXTRUDER, y_pos + Y_PROBE_OFFSET_FROM_EXTRUDER, ProbeDeployAndStow, g29_verbose_level); ubl.has_control_of_lcd_panel = true; // Grab the LCD Hardware @@ -653,7 +660,7 @@ SERIAL_PROTOCOLLNPGM("\nZ-Offset Adjustment Stopped."); do_blocking_move_to_z(Z_CLEARANCE_DEPLOY_PROBE); LCD_MESSAGEPGM("Z-Offset Stopped"); - restore_ubl_active_state_and_leave(); + ubl.restore_ubl_active_state_and_leave(); goto LEAVE; } } @@ -663,7 +670,7 @@ ubl.state.z_offset = measured_z; lcd_implementation_clear(); - restore_ubl_active_state_and_leave(); + ubl.restore_ubl_active_state_and_leave(); } } @@ -676,7 +683,7 @@ ubl.has_control_of_lcd_panel = false; } - void find_mean_mesh_height() { + void unified_bed_leveling::find_mean_mesh_height() { uint8_t x, y; int n; float sum, sum_of_diff_squared, sigma, difference, mean; @@ -719,7 +726,7 @@ ubl.z_values[x][y] -= mean + ubl_constant; } - void shift_mesh_height() { + void unified_bed_leveling::shift_mesh_height() { for (uint8_t x = 0; x < GRID_MAX_POINTS_X; x++) for (uint8_t y = 0; y < GRID_MAX_POINTS_Y; y++) if (!isnan(ubl.z_values[x][y])) @@ -730,11 +737,11 @@ * Probe all invalidated locations of the mesh that can be reached by the probe. * This attempts to fill in locations closest to the nozzle's start location first. */ - void probe_entire_mesh(const float &lx, const float &ly, const bool do_ubl_mesh_map, const bool stow_probe, bool do_furthest) { + void unified_bed_leveling::probe_entire_mesh(const float &lx, const float &ly, const bool do_ubl_mesh_map, const bool stow_probe, bool do_furthest) { mesh_index_pair location; ubl.has_control_of_lcd_panel = true; - save_ubl_active_state_and_disable(); // we don't do bed level correction because we want the raw data when we probe + ubl.save_ubl_active_state_and_disable(); // we don't do bed level correction because we want the raw data when we probe DEPLOY_PROBE(); do { @@ -744,12 +751,12 @@ STOW_PROBE(); while (ubl_lcd_clicked()) idle(); ubl.has_control_of_lcd_panel = false; - restore_ubl_active_state_and_leave(); + ubl.restore_ubl_active_state_and_leave(); safe_delay(50); // Debounce the Encoder wheel return; } - location = find_closest_mesh_point_of_type(INVALID, lx, ly, 1, NULL, do_furthest); // the '1' says we want the location to be relative to the probe + location = find_closest_mesh_point_of_type(INVALID, lx, ly, USE_PROBE_AS_REFERENCE, NULL, do_furthest); if (location.x_index >= 0 && location.y_index >= 0) { const float rawx = pgm_read_float(&(ubl.mesh_index_to_xpos[location.x_index])), @@ -773,7 +780,7 @@ LEAVE: STOW_PROBE(); - restore_ubl_active_state_and_leave(); + ubl.restore_ubl_active_state_and_leave(); do_blocking_move_to_xy( constrain(lx - (X_PROBE_OFFSET_FROM_EXTRUDER), X_MIN_POS, X_MAX_POS), @@ -781,69 +788,113 @@ ); } - vector_3 tilt_mesh_based_on_3pts(const float &z1, const float &z2, const float &z3) { - float c, d, t; + void unified_bed_leveling::tilt_mesh_based_on_3pts(const float &z1, const float &z2, const float &z3) { + float d, t, inv_z; int i, j; - vector_3 v1 = vector_3( (ubl_3_point_1_X - ubl_3_point_2_X), - (ubl_3_point_1_Y - ubl_3_point_2_Y), + matrix_3x3 rotation; + vector_3 v1 = vector_3( (UBL_PROBE_PT_1_X - UBL_PROBE_PT_2_X), + (UBL_PROBE_PT_1_Y - UBL_PROBE_PT_2_Y), (z1 - z2) ), - v2 = vector_3( (ubl_3_point_3_X - ubl_3_point_2_X), - (ubl_3_point_3_Y - ubl_3_point_2_Y), + v2 = vector_3( (UBL_PROBE_PT_3_X - UBL_PROBE_PT_2_X), + (UBL_PROBE_PT_3_Y - UBL_PROBE_PT_2_Y), (z3 - z2) ), normal = vector_3::cross(v1, v2); - // printf("[%f,%f,%f] ", normal.x, normal.y, normal.z); + normal = normal.get_normal(); /** - * This code does two things. This vector is normal to the tilted plane. + * This vector is normal to the tilted plane. * However, we don't know its direction. We need it to point up. So if * Z is negative, we need to invert the sign of all components of the vector - * We also need Z to be unity because we are going to be treating this triangle - * as the sin() and cos() of the bed's tilt */ - const float inv_z = 1.0 / normal.z; - normal.x *= inv_z; - normal.y *= inv_z; - normal.z = 1.0; + if ( normal.z < 0.0 ) { + normal.x = -normal.x; + normal.y = -normal.y; + normal.z = -normal.z; + } + + rotation = matrix_3x3::create_look_at( vector_3( normal.x, normal.y, 1)); + + if (g29_verbose_level>2) { + SERIAL_ECHOPGM("bed plane normal = ["); + SERIAL_PROTOCOL_F( normal.x, 7); + SERIAL_ECHOPGM(","); + SERIAL_PROTOCOL_F( normal.y, 7); + SERIAL_ECHOPGM(","); + SERIAL_PROTOCOL_F( normal.z, 7); + SERIAL_ECHOPGM("]\n"); + rotation.debug("rotation matrix:"); + } // // All of 3 of these points should give us the same d constant // - t = normal.x * ubl_3_point_1_X + normal.y * ubl_3_point_1_Y; + + t = normal.x * UBL_PROBE_PT_1_X + normal.y * UBL_PROBE_PT_1_Y; d = t + normal.z * z1; - c = d - t; + + if (g29_verbose_level>2) { + SERIAL_ECHOPGM("D constant: "); + SERIAL_PROTOCOL_F( d, 7); + SERIAL_ECHOPGM(" \n"); + } + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { SERIAL_ECHOPGM("d from 1st point: "); SERIAL_ECHO_F(d, 6); - SERIAL_ECHOPGM(" c: "); - SERIAL_ECHO_F(c, 6); SERIAL_EOL; - t = normal.x * ubl_3_point_2_X + normal.y * ubl_3_point_2_Y; + t = normal.x * UBL_PROBE_PT_2_X + normal.y * UBL_PROBE_PT_2_Y; d = t + normal.z * z2; - c = d - t; SERIAL_ECHOPGM("d from 2nd point: "); SERIAL_ECHO_F(d, 6); - SERIAL_ECHOPGM(" c: "); - SERIAL_ECHO_F(c, 6); SERIAL_EOL; - t = normal.x * ubl_3_point_3_X + normal.y * ubl_3_point_3_Y; + t = normal.x * UBL_PROBE_PT_3_X + normal.y * UBL_PROBE_PT_3_Y; d = t + normal.z * z3; - c = d - t; SERIAL_ECHOPGM("d from 3rd point: "); SERIAL_ECHO_F(d, 6); - SERIAL_ECHOPGM(" c: "); - SERIAL_ECHO_F(c, 6); SERIAL_EOL; + } + #endif for (i = 0; i < GRID_MAX_POINTS_X; i++) { for (j = 0; j < GRID_MAX_POINTS_Y; j++) { - c = -((normal.x * (UBL_MESH_MIN_X + i * (MESH_X_DIST)) + normal.y * (UBL_MESH_MIN_Y + j * (MESH_Y_DIST))) - d); - ubl.z_values[i][j] += c; + float x_tmp, y_tmp, z_tmp; + x_tmp = pgm_read_float(ubl.mesh_index_to_xpos[i]); + y_tmp = pgm_read_float(ubl.mesh_index_to_ypos[j]); + z_tmp = ubl.z_values[i][j]; + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPGM("before rotation = ["); + SERIAL_PROTOCOL_F( x_tmp, 7); + SERIAL_ECHOPGM(","); + SERIAL_PROTOCOL_F( y_tmp, 7); + SERIAL_ECHOPGM(","); + SERIAL_PROTOCOL_F( z_tmp, 7); + SERIAL_ECHOPGM("] ---> "); + safe_delay(20); } + #endif + apply_rotation_xyz(rotation, x_tmp, y_tmp, z_tmp); + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPGM("after rotation = ["); + SERIAL_PROTOCOL_F( x_tmp, 7); + SERIAL_ECHOPGM(","); + SERIAL_PROTOCOL_F( y_tmp, 7); + SERIAL_ECHOPGM(","); + SERIAL_PROTOCOL_F( z_tmp, 7); + SERIAL_ECHOPGM("]\n"); + safe_delay(55); } - return normal; + #endif + ubl.z_values[i][j] += z_tmp - d; + } + } + return; } float use_encoder_wheel_to_measure_point() { @@ -862,7 +913,7 @@ float measure_business_card_thickness(const float &in_height) { ubl.has_control_of_lcd_panel = true; - save_ubl_active_state_and_disable(); // we don't do bed level correction because we want the raw data when we probe + ubl.save_ubl_active_state_and_disable(); // we don't do bed level correction because we want the raw data when we probe SERIAL_PROTOCOLLNPGM("Place Shim Under Nozzle and Perform Measurement."); do_blocking_move_to_z(in_height); @@ -882,21 +933,21 @@ SERIAL_PROTOCOL_F(abs(z1 - z2), 6); SERIAL_PROTOCOLLNPGM("mm thick."); } - restore_ubl_active_state_and_leave(); + ubl.restore_ubl_active_state_and_leave(); return abs(z1 - z2); } void manually_probe_remaining_mesh(const float &lx, const float &ly, const float &z_clearance, const float &card_thickness, const bool do_ubl_mesh_map) { ubl.has_control_of_lcd_panel = true; - save_ubl_active_state_and_disable(); // we don't do bed level correction because we want the raw data when we probe + ubl.save_ubl_active_state_and_disable(); // we don't do bed level correction because we want the raw data when we probe do_blocking_move_to_z(z_clearance); do_blocking_move_to_xy(lx, ly); float last_x = -9999.99, last_y = -9999.99; mesh_index_pair location; do { - location = find_closest_mesh_point_of_type(INVALID, lx, ly, 0, NULL, false); // The '0' says we want to use the nozzle's position + location = find_closest_mesh_point_of_type(INVALID, lx, ly, USE_NOZZLE_AS_REFERENCE, NULL, false); // It doesn't matter if the probe can't reach the NAN location. This is a manual probe. if (location.x_index < 0 && location.y_index < 0) continue; @@ -949,7 +1000,7 @@ while (ubl_lcd_clicked()) idle(); ubl.has_control_of_lcd_panel = false; KEEPALIVE_STATE(IN_HANDLER); - restore_ubl_active_state_and_leave(); + ubl.restore_ubl_active_state_and_leave(); return; } } @@ -965,7 +1016,7 @@ if (do_ubl_mesh_map) ubl.display_map(map_type); LEAVE: - restore_ubl_active_state_and_leave(); + ubl.restore_ubl_active_state_and_leave(); KEEPALIVE_STATE(IN_HANDLER); do_blocking_move_to_z(Z_CLEARANCE_DEPLOY_PROBE); do_blocking_move_to_xy(lx, ly); @@ -975,6 +1026,8 @@ bool err_flag = false; LCD_MESSAGEPGM("Doing G29 UBL!"); + ubl_constant = 0.0; + repetition_cnt = 0; lcd_quick_feedback(); x_flag = code_seen('X') && code_has_value(); @@ -983,7 +1036,6 @@ y_flag = code_seen('Y') && code_has_value(); y_pos = y_flag ? code_value_float() : current_position[Y_AXIS]; - repetition_cnt = 0; repeat_flag = code_seen('R'); if (repeat_flag) { repetition_cnt = code_has_value() ? code_value_int() : (GRID_MAX_POINTS_X) * (GRID_MAX_POINTS_Y); @@ -999,7 +1051,7 @@ err_flag = true; } - if (code_seen('G')) { + if (code_seen('J')) { grid_size = code_has_value() ? code_value_int() : 3; if (!WITHIN(grid_size, 2, 5)) { SERIAL_PROTOCOLLNPGM("Invalid grid probe points specified.\n"); @@ -1015,27 +1067,11 @@ if (!WITHIN(RAW_X_POSITION(x_pos), X_MIN_POS, X_MAX_POS)) { SERIAL_PROTOCOLLNPGM("Invalid X location specified.\n"); err_flag = true; - SERIAL_PROTOCOLPAIR("\nx_flag = ", x_flag); // These print blocks are only useful because sometimes the - SERIAL_PROTOCOLPAIR("\nx_pos = ", x_pos ); // data corruption causes x_pos and y_pos to be crazy. This gets deleted soon. - SERIAL_PROTOCOLPAIR("\ncurrent[] = ", current_position[X_AXIS]); - SERIAL_PROTOCOLPAIR("\nX_MIN_POS = ", X_MIN_POS); - SERIAL_PROTOCOLPAIR("\nX_MAX_POS = ", X_MAX_POS); - SERIAL_PROTOCOLPAIR("\nRAW_X_POSITION() = ", RAW_X_POSITION(x_pos)); - SERIAL_PROTOCOLPAIR("\nwithin() = ", WITHIN(RAW_X_POSITION(x_pos), X_MIN_POS, X_MAX_POS)); - SERIAL_PROTOCOL("\n"); } if (!WITHIN(RAW_Y_POSITION(y_pos), Y_MIN_POS, Y_MAX_POS)) { SERIAL_PROTOCOLLNPGM("Invalid Y location specified.\n"); err_flag = true; - SERIAL_PROTOCOLPAIR("\ny_flag = ", y_flag); // These print blocks are only useful because sometimes the - SERIAL_PROTOCOLPAIR("\ny_pos = ", y_pos ); // data corruption causes x_pos and y_pos to be crazy. This gets deleted soon. - SERIAL_PROTOCOLPAIR("\ncurrent[] = ", current_position[Y_AXIS]); - SERIAL_PROTOCOLPAIR("\nY_MIN_POS = ", Y_MIN_POS); - SERIAL_PROTOCOLPAIR("\nY_MAX_POS = ", Y_MAX_POS); - SERIAL_PROTOCOLPAIR("\nRAW_Y_POSITION() = ", RAW_Y_POSITION(y_pos)); - SERIAL_PROTOCOLPAIR("\nwithin() = ", WITHIN(RAW_Y_POSITION(y_pos), Y_MIN_POS, Y_MAX_POS)); - SERIAL_PROTOCOL("\n"); } if (err_flag) return UBL_ERR; @@ -1045,8 +1081,9 @@ SERIAL_PROTOCOLLNPGM("Unified Bed Leveling System activated.\n"); } - c_flag = code_seen('C') && code_has_value(); - ubl_constant = c_flag ? code_value_float() : 0.0; + c_flag = code_seen('C'); + if (c_flag) + ubl_constant = code_value_float(); if (code_seen('D')) { // Disable the Unified Bed Leveling System ubl.state.active = 0; @@ -1109,7 +1146,7 @@ static int ubl_state_at_invocation = 0, ubl_state_recursion_chk = 0; - void save_ubl_active_state_and_disable() { + void unified_bed_leveling::save_ubl_active_state_and_disable() { ubl_state_recursion_chk++; if (ubl_state_recursion_chk != 1) { SERIAL_ECHOLNPGM("save_ubl_active_state_and_disabled() called multiple times in a row."); @@ -1121,7 +1158,7 @@ ubl.state.active = 0; } - void restore_ubl_active_state_and_leave() { + void unified_bed_leveling::restore_ubl_active_state_and_leave() { if (--ubl_state_recursion_chk) { SERIAL_ECHOLNPGM("restore_ubl_active_state_and_leave() called too many times."); LCD_MESSAGEPGM("restore_UBL_active() error"); @@ -1156,14 +1193,21 @@ SERIAL_EOL; safe_delay(50); + SERIAL_PROTOCOLLNPAIR("UBL object count: ", ubl_cnt); + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) SERIAL_PROTOCOLLNPAIR("planner.z_fade_height : ", planner.z_fade_height); #endif + SERIAL_PROTOCOLPGM("zprobe_zoffset: "); + SERIAL_PROTOCOL_F(zprobe_zoffset, 7); + SERIAL_EOL; SERIAL_PROTOCOLPGM("z_offset: "); - SERIAL_PROTOCOL_F(ubl.state.z_offset, 6); + SERIAL_PROTOCOL_F(ubl.state.z_offset, 7); SERIAL_EOL; - safe_delay(50); + safe_delay(25); + + SERIAL_PROTOCOLLNPAIR("ubl.eeprom_start=0x", hex_word(ubl.eeprom_start)); SERIAL_PROTOCOLPGM("X-Axis Mesh Points at: "); for (uint8_t i = 0; i < GRID_MAX_POINTS_X; i++) { @@ -1300,8 +1344,8 @@ current_y = current_position[Y_AXIS]; // Get our reference position. Either the nozzle or probe location. - const float px = lx - (probe_as_reference ? X_PROBE_OFFSET_FROM_EXTRUDER : 0), - py = ly - (probe_as_reference ? Y_PROBE_OFFSET_FROM_EXTRUDER : 0); + const float px = lx - (probe_as_reference==USE_PROBE_AS_REFERENCE ? X_PROBE_OFFSET_FROM_EXTRUDER : 0), + py = ly - (probe_as_reference==USE_PROBE_AS_REFERENCE ? Y_PROBE_OFFSET_FROM_EXTRUDER : 0); for (uint8_t i = 0; i < GRID_MAX_POINTS_X; i++) { for (uint8_t j = 0; j < GRID_MAX_POINTS_Y; j++) { @@ -1319,7 +1363,7 @@ // If using the probe as the reference there are some unreachable locations. // Prune them from the list and ignore them till the next Phase (manual nozzle probing). - if (probe_as_reference && + if (probe_as_reference==USE_PROBE_AS_REFERENCE && (!WITHIN(rawx, MIN_PROBE_X, MAX_PROBE_X) || !WITHIN(rawy, MIN_PROBE_Y, MAX_PROBE_Y)) ) continue; @@ -1363,7 +1407,7 @@ uint16_t not_done[16]; int32_t round_off; - save_ubl_active_state_and_disable(); + ubl.save_ubl_active_state_and_disable(); memset(not_done, 0xFF, sizeof(not_done)); LCD_MESSAGEPGM("Fine Tuning Mesh"); @@ -1371,7 +1415,7 @@ do_blocking_move_to_z(Z_CLEARANCE_DEPLOY_PROBE); do_blocking_move_to_xy(lx, ly); do { - location = find_closest_mesh_point_of_type(SET_IN_BITMAP, lx, ly, 0, not_done, false); // The '0' says we want to use the nozzle's position + location = find_closest_mesh_point_of_type(SET_IN_BITMAP, lx, ly, USE_NOZZLE_AS_REFERENCE, not_done, false); // It doesn't matter if the probe can not reach this // location. This is a manual edit of the Mesh Point. if (location.x_index < 0 && location.y_index < 0) continue; // abort if we can't find any more points. @@ -1446,7 +1490,7 @@ KEEPALIVE_STATE(IN_HANDLER); if (do_ubl_mesh_map) ubl.display_map(map_type); - restore_ubl_active_state_and_leave(); + ubl.restore_ubl_active_state_and_leave(); do_blocking_move_to_z(Z_CLEARANCE_DEPLOY_PROBE); do_blocking_move_to_xy(lx, ly); @@ -1455,172 +1499,229 @@ SERIAL_ECHOLNPGM("Done Editing Mesh"); } - void tilt_mesh_based_on_probed_grid(const bool do_ubl_mesh_map) { - int8_t grid_G_index_to_xpos[grid_size], // UBL MESH X index to be probed - grid_G_index_to_ypos[grid_size], // UBL MESH Y index to be probed - i, j ,k, xCount, yCount, xi, yi; // counter variables - float z_values_G[grid_size][grid_size]; - - linear_fit *results; - - for (yi = 0; yi < grid_size; yi++) - for (xi = 0; xi < grid_size; xi++) - z_values_G[xi][yi] = NAN; - - uint8_t x_min = GRID_MAX_POINTS_X - 1, - x_max = 0, - y_min = GRID_MAX_POINTS_Y - 1, - y_max = 0; - - //find min & max probeable points in the mesh - for (xCount = 0; xCount < GRID_MAX_POINTS_X; xCount++) { - for (yCount = 0; yCount < GRID_MAX_POINTS_Y; yCount++) { - if (WITHIN(pgm_read_float(&(ubl.mesh_index_to_xpos[xCount])), MIN_PROBE_X, MAX_PROBE_X) && - WITHIN(pgm_read_float(&(ubl.mesh_index_to_ypos[yCount])), MIN_PROBE_Y, MAX_PROBE_Y)) { - NOMORE(x_min, xCount); - NOLESS(x_max, xCount); - NOMORE(y_min, yCount); - NOLESS(y_max, yCount); + // + // The routine provides the 'Smart Fill' capability. It scans from the + // outward edges of the mesh towards the center. If it finds an invalid + // location, it uses the next two points (assumming they are valid) to + // calculate a 'reasonable' value for the unprobed mesh point. + // + void smart_fill_mesh() { + float f, diff; + for (uint8_t x = 0; x < GRID_MAX_POINTS_X; x++) { // Bottom of the mesh looking up + for (uint8_t y = 0; y < GRID_MAX_POINTS_Y-2; y++) { + if (isnan(ubl.z_values[x][y])) { + if (isnan(ubl.z_values[x][y+1])) // we only deal with the first NAN next to a block of + continue; // good numbers. we want 2 good numbers to extrapolate off of. + if (isnan(ubl.z_values[x][y+2])) + continue; + if (ubl.z_values[x][y+1] < ubl.z_values[x][y+2]) // The bed is angled down near this edge. So to be safe, we + ubl.z_values[x][y] = ubl.z_values[x][y+1]; // use the closest value, which is probably a little too high + else { + diff = ubl.z_values[x][y+1] - ubl.z_values[x][y+2]; // The bed is angled up near this edge. So we will use the closest + ubl.z_values[x][y] = ubl.z_values[x][y+1] + diff; // height and add in the difference between that and the next point } + break; } } - - if (x_max - x_min + 1 < grid_size || y_max - y_min + 1 < grid_size) { - SERIAL_ECHOPAIR("ERROR - probeable UBL MESH smaller than grid - X points: ", x_max - x_min + 1); - SERIAL_ECHOPAIR(" Y points: ", y_max - y_min + 1); - SERIAL_ECHOLNPAIR(" grid: ", grid_size); - return; - } - - // populate X matrix - for (xi = 0; xi < grid_size; xi++) { - grid_G_index_to_xpos[xi] = x_min + xi * (x_max - x_min) / (grid_size - 1); - if (xi > 0 && grid_G_index_to_xpos[xi - 1] == grid_G_index_to_xpos[xi]) { - grid_G_index_to_xpos[xi] = grid_G_index_to_xpos[xi - 1] + 1; + } + for (uint8_t x = 0; x < GRID_MAX_POINTS_X; x++) { // Top of the mesh looking down + for (uint8_t y=GRID_MAX_POINTS_Y-1; y>=1; y--) { + if (isnan(ubl.z_values[x][y])) { + if (isnan(ubl.z_values[x][y-1])) // we only deal with the first NAN next to a block of + continue; // good numbers. we want 2 good numbers to extrapolate off of. + if (isnan(ubl.z_values[x][y-2])) + continue; + if (ubl.z_values[x][y-1] < ubl.z_values[x][y-2]) // The bed is angled down near this edge. So to be safe, we + ubl.z_values[x][y] = ubl.z_values[x][y-1]; // use the closest value, which is probably a little too high + else { + diff = ubl.z_values[x][y-1] - ubl.z_values[x][y-2]; // The bed is angled up near this edge. So we will use the closest + ubl.z_values[x][y] = ubl.z_values[x][y-1] + diff; // height and add in the difference between that and the next point + } + break; } } - - // populate Y matrix - for (yi = 0; yi < grid_size; yi++) { - grid_G_index_to_ypos[yi] = y_min + yi * (y_max - y_min) / (grid_size - 1); - if (yi > 0 && grid_G_index_to_ypos[yi - 1] == grid_G_index_to_ypos[yi]) { - grid_G_index_to_ypos[yi] = grid_G_index_to_ypos[yi - 1] + 1; + } + for (uint8_t y = 0; y < GRID_MAX_POINTS_Y; y++) { + for (uint8_t x = 0; x < GRID_MAX_POINTS_X-2; x++) { // Left side of the mesh looking right + if (isnan(ubl.z_values[x][y])) { + if (isnan(ubl.z_values[x+1][y])) // we only deal with the first NAN next to a block of + continue; // good numbers. we want 2 good numbers to extrapolate off of. + if (isnan(ubl.z_values[x+2][y])) + continue; + if (ubl.z_values[x+1][y] < ubl.z_values[x+2][y]) // The bed is angled down near this edge. So to be safe, we + ubl.z_values[x][y] = ubl.z_values[x][y+1]; // use the closest value, which is probably a little too high + else { + diff = ubl.z_values[x+1][y] - ubl.z_values[x+2][y]; // The bed is angled up near this edge. So we will use the closest + ubl.z_values[x][y] = ubl.z_values[x+1][y] + diff; // height and add in the difference between that and the next point + } + break; } } + } + for (uint8_t y=0; y < GRID_MAX_POINTS_Y; y++) { + for (uint8_t x=GRID_MAX_POINTS_X-1; x>=1; x--) { // Right side of the mesh looking left + if (isnan(ubl.z_values[x][y])) { + if (isnan(ubl.z_values[x-1][y])) // we only deal with the first NAN next to a block of + continue; // good numbers. we want 2 good numbers to extrapolate off of. + if (isnan(ubl.z_values[x-2][y])) + continue; + if (ubl.z_values[x-1][y] < ubl.z_values[x-2][y]) // The bed is angled down near this edge. So to be safe, we + ubl.z_values[x][y] = ubl.z_values[x-1][y]; // use the closest value, which is probably a little too high + else { + diff = ubl.z_values[x-1][y] - ubl.z_values[x-2][y]; // The bed is angled up near this edge. So we will use the closest + ubl.z_values[x][y] = ubl.z_values[x-1][y] + diff; // height and add in the difference between that and the next point + } + break; + } + } + } + } - ubl.has_control_of_lcd_panel = true; - save_ubl_active_state_and_disable(); // we don't do bed level correction because we want the raw data when we probe - - DEPLOY_PROBE(); - - // this is a copy of the G29 AUTO_BED_LEVELING_BILINEAR method/code - #undef PROBE_Y_FIRST - #if ENABLED(PROBE_Y_FIRST) - #define PR_OUTER_VAR xCount - #define PR_OUTER_NUM grid_size - #define PR_INNER_VAR yCount - #define PR_INNER_NUM grid_size - #else - #define PR_OUTER_VAR yCount - #define PR_OUTER_NUM grid_size - #define PR_INNER_VAR xCount - #define PR_INNER_NUM grid_size - #endif - - bool zig = PR_OUTER_NUM & 1; // Always end at RIGHT and BACK_PROBE_BED_POSITION - - // Outer loop is Y with PROBE_Y_FIRST disabled - for (PR_OUTER_VAR = 0; PR_OUTER_VAR < PR_OUTER_NUM; PR_OUTER_VAR++) { - - int8_t inStart, inStop, inInc; - - SERIAL_ECHOPAIR("\nPR_OUTER_VAR: ", PR_OUTER_VAR); - if (zig) { // away from origin - inStart = 0; - inStop = PR_INNER_NUM; - inInc = 1; - } - else { // towards origin - inStart = PR_INNER_NUM - 1; - inStop = -1; - inInc = -1; + void unified_bed_leveling::tilt_mesh_based_on_probed_grid(const bool do_ubl_mesh_map) { + int8_t i, j ,k, xCount, yCount, xi, yi; // counter variables + int8_t ix, iy, zig_zag=0, status; + + float dx, dy, x, y, measured_z, inv_z; + struct linear_fit_data lsf_results; + matrix_3x3 rotation; + vector_3 normal; + + int16_t x_min = max((MIN_PROBE_X),(UBL_MESH_MIN_X)), + x_max = min((MAX_PROBE_X),(UBL_MESH_MAX_X)), + y_min = max((MIN_PROBE_Y),(UBL_MESH_MIN_Y)), + y_max = min((MAX_PROBE_Y),(UBL_MESH_MAX_Y)); + + dx = ((float)(x_max-x_min)) / (grid_size-1.0); + dy = ((float)(y_max-y_min)) / (grid_size-1.0); + + incremental_LSF_reset(&lsf_results); + for(ix=0; ix>>---> "); + SERIAL_PROTOCOL_F( measured_z, 7); + SERIAL_ECHOPGM("\n"); } + #endif + incremental_LSF(&lsf_results, x, y, measured_z); + } - zig ^= true; // zag - - // Inner loop is Y with PROBE_Y_FIRST enabled - for (PR_INNER_VAR = inStart; PR_INNER_VAR != inStop; PR_INNER_VAR += inInc) { - //SERIAL_ECHOPAIR("\nPR_INNER_VAR: ", PR_INNER_VAR); - - //SERIAL_ECHOPAIR("\nCheckpoint: ", 1); - - // end of G29 AUTO_BED_LEVELING_BILINEAR method/code - if (ubl_lcd_clicked()) { - //SERIAL_ECHOPAIR("\nCheckpoint: ", 2); - SERIAL_ECHOLNPGM("\nGrid only partially populated.\n"); - lcd_quick_feedback(); - STOW_PROBE(); - //SERIAL_ECHOPAIR("\nCheckpoint: ", 3); - while (ubl_lcd_clicked()) idle(); - //SERIAL_ECHOPAIR("\nCheckpoint: ", 4); - ubl.has_control_of_lcd_panel = false; - restore_ubl_active_state_and_leave(); - safe_delay(50); // Debounce the Encoder wheel - return; - } - //SERIAL_ECHOPAIR("\nCheckpoint: ", 5); + zig_zag = !zig_zag; + } - const float probeX = pgm_read_float(&(ubl.mesh_index_to_xpos[grid_G_index_to_xpos[xCount]])), //where we want the probe to be - probeY = pgm_read_float(&(ubl.mesh_index_to_ypos[grid_G_index_to_ypos[yCount]])); - //SERIAL_ECHOPAIR("\nCheckpoint: ", 6); + status = finish_incremental_LSF(&lsf_results); + if (g29_verbose_level>3) { + SERIAL_ECHOPGM("LSF Results A="); + SERIAL_PROTOCOL_F( lsf_results.A, 7); + SERIAL_ECHOPGM(" B="); + SERIAL_PROTOCOL_F( lsf_results.B, 7); + SERIAL_ECHOPGM(" D="); + SERIAL_PROTOCOL_F( lsf_results.D, 7); + SERIAL_CHAR('\n'); + } - const float measured_z = probe_pt(LOGICAL_X_POSITION(probeX), LOGICAL_Y_POSITION(probeY), code_seen('E'), - (code_seen('V') && code_has_value()) ? code_value_int() : 0); // takes into account the offsets + normal = vector_3( lsf_results.A, lsf_results.B, 1.0000); + normal = normal.get_normal(); + + if (g29_verbose_level>2) { + SERIAL_ECHOPGM("bed plane normal = ["); + SERIAL_PROTOCOL_F( normal.x, 7); + SERIAL_ECHOPGM(","); + SERIAL_PROTOCOL_F( normal.y, 7); + SERIAL_ECHOPGM(","); + SERIAL_PROTOCOL_F( normal.z, 7); + SERIAL_ECHOPGM("]\n"); + } - //SERIAL_ECHOPAIR("\nmeasured_z: ", measured_z); + rotation = matrix_3x3::create_look_at( vector_3( lsf_results.A, lsf_results.B, 1)); - z_values_G[xCount][yCount] = measured_z; - //SERIAL_ECHOLNPGM("\nFine Tuning of Mesh Stopped."); + for (i = 0; i < GRID_MAX_POINTS_X; i++) { + for (j = 0; j < GRID_MAX_POINTS_Y; j++) { + float x_tmp, y_tmp, z_tmp; + x_tmp = pgm_read_float(&(ubl.mesh_index_to_xpos[i])); + y_tmp = pgm_read_float(&(ubl.mesh_index_to_ypos[j])); + z_tmp = ubl.z_values[i][j]; + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPGM("before rotation = ["); + SERIAL_PROTOCOL_F( x_tmp, 7); + SERIAL_ECHOPGM(","); + SERIAL_PROTOCOL_F( y_tmp, 7); + SERIAL_ECHOPGM(","); + SERIAL_PROTOCOL_F( z_tmp, 7); + SERIAL_ECHOPGM("] ---> "); + safe_delay(20); } + #endif + apply_rotation_xyz(rotation, x_tmp, y_tmp, z_tmp); + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPGM("after rotation = ["); + SERIAL_PROTOCOL_F( x_tmp, 7); + SERIAL_ECHOPGM(","); + SERIAL_PROTOCOL_F( y_tmp, 7); + SERIAL_ECHOPGM(","); + SERIAL_PROTOCOL_F( z_tmp, 7); + SERIAL_ECHOPGM("]\n"); + safe_delay(55); } - //SERIAL_ECHOLNPGM("\nDone probing...\n"); - - STOW_PROBE(); - restore_ubl_active_state_and_leave(); - - // ?? ubl.has_control_of_lcd_panel = true; - //do_blocking_move_to_xy(pgm_read_float(&(ubl.mesh_index_to_xpos[grid_G_index_to_xpos[0]])),pgm_read_float(&(ubl.mesh_index_to_ypos[grid_G_index_to_ypos[0]]))); - - // least squares code - double xxx5[] = { 0,50,100,150,200, 20,70,120,165,195, 0,50,100,150,200, 0,55,100,150,200, 0,65,100,150,205 }, - yyy5[] = { 0, 1, 2, 3, 4, 50, 51, 52, 53, 54, 100, 101,102,103,104, 150,151,152,153,154, 200,201,202,203,204 }, - zzz5[] = { 0.01,.002,-.01,-.02,0, 0.01,.002,-.01,-.02,0, 0.01,.002,-.01,-.02,0, 0.01,.002,-.01,-.02,0, 0.01,.002,-.01,-.012,0.01}, - xxx0[] = { 0.0, 0.0, 1.0 }, // Expect [0,0,0.1,0] - yyy0[] = { 0.0, 1.0, 0.0 }, - zzz0[] = { 0.1, 0.1, 0.1 }, - xxx[] = { 0.0, 0.0, 1.0, 1.0 }, // Expect [0.1,0,0.05,0] - yyy[] = { 0.0, 1.0, 0.0, 1.0 }, - zzz[] = { 0.05, 0.05, 0.15, 0.15 }; - - results = lsf_linear_fit(xxx5, yyy5, zzz5, COUNT(xxx5)); - SERIAL_ECHOPAIR("\nxxx5->A =", results->A); - SERIAL_ECHOPAIR("\nxxx5->B =", results->B); - SERIAL_ECHOPAIR("\nxxx5->D =", results->D); - SERIAL_EOL; - results = lsf_linear_fit(xxx0, yyy0, zzz0, COUNT(xxx0)); - SERIAL_ECHOPAIR("\nxxx0->A =", results->A); - SERIAL_ECHOPAIR("\nxxx0->B =", results->B); - SERIAL_ECHOPAIR("\nxxx0->D =", results->D); - SERIAL_EOL; + #endif - results = lsf_linear_fit(xxx, yyy, zzz, COUNT(xxx)); - SERIAL_ECHOPAIR("\nxxx->A =", results->A); - SERIAL_ECHOPAIR("\nxxx->B =", results->B); - SERIAL_ECHOPAIR("\nxxx->D =", results->D); - SERIAL_EOL; + ubl.z_values[i][j] += z_tmp - lsf_results.D; + } + } - } // end of tilt_mesh_based_on_probed_grid() + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + rotation.debug("rotation matrix:"); + SERIAL_ECHOPGM("LSF Results A="); + SERIAL_PROTOCOL_F( lsf_results.A, 7); + SERIAL_ECHOPGM(" B="); + SERIAL_PROTOCOL_F( lsf_results.B, 7); + SERIAL_ECHOPGM(" D="); + SERIAL_PROTOCOL_F( lsf_results.D, 7); + SERIAL_CHAR('\n'); + safe_delay(55); + + SERIAL_ECHOPGM("bed plane normal = ["); + SERIAL_PROTOCOL_F( normal.x, 7); + SERIAL_ECHOPGM(","); + SERIAL_PROTOCOL_F( normal.y, 7); + SERIAL_ECHOPGM(","); + SERIAL_PROTOCOL_F( normal.z, 7); + SERIAL_ECHOPGM("]\n"); + SERIAL_CHAR('\n'); + } + #endif + return; + } #endif // AUTO_BED_LEVELING_UBL diff --git a/Marlin/utility.cpp b/Marlin/utility.cpp index 50dcbca80..11e499f16 100644 --- a/Marlin/utility.cpp +++ b/Marlin/utility.cpp @@ -31,6 +31,7 @@ void safe_delay(millis_t ms) { thermalManager.manage_heater(); } delay(ms); + thermalManager.manage_heater(); // This keeps us safe if too many small safe_delay() calls are made } #if ENABLED(ULTRA_LCD)