浏览代码

Smart-Fill and Mesh-Tilting (both 3-point and grid) working!

Also...   The memory corruption issue may be fixed.   The GCC compiler
was inlining static functions and this caused the G29() stack frame to
become much larger than the AVR could handle.
Roxy-3D 8 年前
父节点
当前提交
d467e97679
共有 6 个文件被更改,包括 552 次插入377 次删除
  1. 51
    73
      Marlin/least_squares_fit.cpp
  2. 57
    0
      Marlin/least_squares_fit.h
  3. 6
    4
      Marlin/ubl.cpp
  4. 66
    30
      Marlin/ubl.h
  5. 371
    270
      Marlin/ubl_G29.cpp
  6. 1
    0
      Marlin/utility.cpp

+ 51
- 73
Marlin/least_squares_fit.cpp 查看文件

@@ -26,7 +26,9 @@
26 26
  * This algorithm is high speed and has a very small code footprint.
27 27
  * Its results are identical to both the Iterative Least-Squares published
28 28
  * earlier by Roxy and the QR_SOLVE solution. If used in place of QR_SOLVE
29
- * it saves roughly 10K of program memory.
29
+ * it saves roughly 10K of program memory.   It also does not require all of 
30
+ * coordinates to be present during the calculations.  Each point can be 
31
+ * probed and then discarded.
30 32
  *
31 33
  */
32 34
 
@@ -34,84 +36,60 @@
34 36
 
35 37
 #if ENABLED(AUTO_BED_LEVELING_UBL)  // Currently only used by UBL, but is applicable to Grid Based (Linear) Bed Leveling
36 38
 
37
-#include "ubl.h"
38
-#include "Marlin.h"
39 39
 #include "macros.h"
40 40
 #include <math.h>
41 41
 
42
-double linear_fit_average(double m[], const int);
43
-//double linear_fit_average_squared(double m[], const int);
44
-//double linear_fit_average_mixed_terms(double m1[], double m2[], const int);
45
-double linear_fit_average_product(double matrix1[], double matrix2[], const int n);
46
-void   linear_fit_subtract_mean(double matrix[], double bar, const int n);
47
-double linear_fit_max_abs(double m[], const int);
48
-
49
-linear_fit linear_fit_results;
50
-
51
-linear_fit* lsf_linear_fit(double x[], double y[], double z[], const int n) {
52
-  double xbar, ybar, zbar,
53
-         x2bar, y2bar,
54
-         xybar, xzbar, yzbar,
55
-         D;
56
-
57
-  linear_fit_results.A = 0.0;
58
-  linear_fit_results.B = 0.0;
59
-  linear_fit_results.D = 0.0;
60
-
61
-  xbar = linear_fit_average(x, n);
62
-  ybar = linear_fit_average(y, n);
63
-  zbar = linear_fit_average(z, n);
64
-
65
-  linear_fit_subtract_mean(x, xbar, n);
66
-  linear_fit_subtract_mean(y, ybar, n);
67
-  linear_fit_subtract_mean(z, zbar, n);
68
-
69
-  x2bar = linear_fit_average_product(x, x, n);
70
-  y2bar = linear_fit_average_product(y, y, n);
71
-  xybar = linear_fit_average_product(x, y, n);
72
-  xzbar = linear_fit_average_product(x, z, n);
73
-  yzbar = linear_fit_average_product(y, z, n);
74
-
75
-  D = x2bar * y2bar - xybar * xybar;
76
-  for (int i = 0; i < n; i++) {
77
-    if (fabs(D) <= 1e-15 * (linear_fit_max_abs(x, n) + linear_fit_max_abs(y, n))) {
78
-      printf("error: x,y points are collinear at index:%d\n", i);
79
-      return NULL;
42
+#include "least_squares_fit.h"
43
+
44
+void incremental_LSF_reset(struct linear_fit_data *lsf) {
45
+	lsf->n = 0;
46
+	lsf->A = 0.0;					// probably a memset() can be done to zero 
47
+	lsf->B = 0.0;                                   // this whole structure
48
+	lsf->D = 0.0;
49
+	lsf->xbar = lsf->ybar = lsf->zbar = 0.0;
50
+	lsf->x2bar = lsf->y2bar = lsf->z2bar = 0.0;
51
+	lsf->xybar = lsf->xzbar = lsf->yzbar = 0.0;
52
+	lsf->max_absx = lsf->max_absy = 0.0;
80 53
     }
81
-  }
82
-
83
-  linear_fit_results.A = -(xzbar * y2bar - yzbar * xybar) / D;
84
-  linear_fit_results.B = -(yzbar * x2bar - xzbar * xybar) / D;
85
-  // linear_fit_results.D = -(zbar - linear_fit_results->A * xbar - linear_fit_results->B * ybar);
86
-  linear_fit_results.D = -(zbar + linear_fit_results.A * xbar + linear_fit_results.B * ybar);
87 54
 
88
-  return &linear_fit_results;
89
-}
90
-
91
-double linear_fit_average(double *matrix, const int n) {
92
-  double sum = 0.0;
93
-  for (int i = 0; i < n; i++)
94
-    sum += matrix[i];
95
-  return sum / (double)n;
96
-}
97
-
98
-double linear_fit_average_product(double *matrix1, double *matrix2, const int n) {
99
-  double sum = 0.0;
100
-  for (int i = 0; i < n; i++)
101
-    sum += matrix1[i] * matrix2[i];
102
-  return sum / (double)n;
103
-}
104
-
105
-void linear_fit_subtract_mean(double *matrix, double bar, const int n) {
106
-  for (int i = 0; i < n; i++)
107
-    matrix[i] -= bar;
108
-}
55
+void incremental_LSF(struct linear_fit_data *lsf, float x, float y, float z) {
56
+	lsf->xbar += x;
57
+	lsf->ybar += y;
58
+	lsf->zbar += z;
59
+	lsf->x2bar += x*x;
60
+	lsf->y2bar += y*y;
61
+	lsf->z2bar += z*z;
62
+	lsf->xybar += x*y;
63
+	lsf->xzbar += x*z;
64
+	lsf->yzbar += y*z;
65
+	lsf->max_absx = (fabs(x) > lsf->max_absx) ? fabs(x) : lsf->max_absx;
66
+	lsf->max_absy = (fabs(y) > lsf->max_absy) ? fabs(y) : lsf->max_absy;
67
+	lsf->n++;
68
+	return;
69
+  }
109 70
 
110
-double linear_fit_max_abs(double *matrix, const int n) {
111
-  double max_abs = 0.0;
112
-  for (int i = 0; i < n; i++)
113
-    NOLESS(max_abs, fabs(matrix[i]));
114
-  return max_abs;
71
+int finish_incremental_LSF(struct linear_fit_data *lsf) {
72
+	float DD, N;
73
+
74
+	N = (float) lsf->n;
75
+	lsf->xbar /= N;
76
+	lsf->ybar /= N;
77
+	lsf->zbar /= N;
78
+	lsf->x2bar = lsf->x2bar/N - lsf->xbar*lsf->xbar;
79
+	lsf->y2bar = lsf->y2bar/N - lsf->ybar*lsf->ybar;
80
+	lsf->z2bar = lsf->z2bar/N - lsf->zbar*lsf->zbar;
81
+	lsf->xybar = lsf->xybar/N - lsf->xbar*lsf->ybar;
82
+	lsf->yzbar = lsf->yzbar/N - lsf->ybar*lsf->zbar;
83
+	lsf->xzbar = lsf->xzbar/N - lsf->xbar*lsf->zbar;
84
+
85
+	DD = lsf->x2bar*lsf->y2bar - lsf->xybar*lsf->xybar;
86
+	if (fabs(DD) <= 1e-10*(lsf->max_absx+lsf->max_absy)) 
87
+	  return -1;
88
+	
89
+	lsf->A = (lsf->yzbar*lsf->xybar - lsf->xzbar*lsf->y2bar) / DD;
90
+	lsf->B = (lsf->xzbar*lsf->xybar - lsf->yzbar*lsf->x2bar) / DD;
91
+	lsf->D = -(lsf->zbar + lsf->A*lsf->xbar + lsf->B*lsf->ybar);
92
+	return 0;
115 93
 }
116 94
 #endif
117 95
 

+ 57
- 0
Marlin/least_squares_fit.h 查看文件

@@ -0,0 +1,57 @@
1
+/**
2
+ * Marlin 3D Printer Firmware
3
+ * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4
+ *
5
+ * Based on Sprinter and grbl.
6
+ * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
+ *
21
+ */
22
+
23
+/**
24
+ * Incremental Least Squares Best Fit  By Roxy and Ed Williams
25
+ *
26
+ * This algorithm is high speed and has a very small code footprint.
27
+ * Its results are identical to both the Iterative Least-Squares published
28
+ * earlier by Roxy and the QR_SOLVE solution. If used in place of QR_SOLVE
29
+ * it saves roughly 10K of program memory.   And even better...  the data
30
+ * fed into the algorithm does not need to all be present at the same time.  
31
+ * A point can be probed and its values fed into the algorithm and then discarded.
32
+ *
33
+ */
34
+
35
+#include "MarlinConfig.h"
36
+
37
+#if ENABLED(AUTO_BED_LEVELING_UBL)  // Currently only used by UBL, but is applicable to Grid Based (Linear) Bed Leveling
38
+
39
+#include "Marlin.h"
40
+#include "macros.h"
41
+#include <math.h>
42
+
43
+struct linear_fit_data {
44
+  int n;
45
+  float xbar, ybar, zbar;
46
+  float x2bar, y2bar, z2bar;
47
+  float xybar, xzbar, yzbar;
48
+  float max_absx, max_absy;
49
+  float A, B, D;
50
+};
51
+
52
+void incremental_LSF_reset(struct linear_fit_data *); 
53
+void incremental_LSF(struct linear_fit_data *, float x, float y, float z);
54
+int finish_incremental_LSF(struct linear_fit_data *);
55
+
56
+#endif
57
+

+ 6
- 4
Marlin/ubl.cpp 查看文件

@@ -27,6 +27,7 @@
27 27
 
28 28
   #include "ubl.h"
29 29
   #include "hex_print_routines.h"
30
+  #include "temperature.h"
30 31
 
31 32
   /**
32 33
    * These support functions allow the use of large bit arrays of flags that take very
@@ -38,6 +39,8 @@
38 39
   void bit_set(uint16_t bits[16], uint8_t x, uint8_t y) { SBI(bits[y], x); }
39 40
   bool is_bit_set(uint16_t bits[16], uint8_t x, uint8_t y) { return TEST(bits[y], x); }
40 41
 
42
+  int ubl_cnt=0;
43
+
41 44
   static void serial_echo_xy(const uint16_t x, const uint16_t y) {
42 45
     SERIAL_CHAR('(');
43 46
     SERIAL_ECHO(x);
@@ -50,9 +53,6 @@
50 53
   static void serial_echo_12x_spaces() {
51 54
     for (uint8_t i = GRID_MAX_POINTS_X - 1; --i;) {
52 55
       SERIAL_ECHOPGM("            ");
53
-      #if TX_BUFFER_SIZE > 0
54
-        MYSERIAL.flushTX();
55
-      #endif
56 56
       safe_delay(10);
57 57
     }
58 58
   }
@@ -70,11 +70,13 @@
70 70
   bool unified_bed_leveling::g26_debug_flag = false,
71 71
        unified_bed_leveling::has_control_of_lcd_panel = false;
72 72
 
73
-  int8_t unified_bed_leveling::eeprom_start = -1;
73
+  int16_t unified_bed_leveling::eeprom_start = -1;  // Please stop changing this to 8 bits in size
74
+                                                    // It needs to hold values bigger than this.
74 75
 
75 76
   volatile int unified_bed_leveling::encoder_diff;
76 77
 
77 78
   unified_bed_leveling::unified_bed_leveling() {
79
+    ubl_cnt++;  // Debug counter to insure we only have one UBL object present in memory.
78 80
     reset();
79 81
   }
80 82
 

+ 66
- 30
Marlin/ubl.h 查看文件

@@ -26,11 +26,10 @@
26 26
 #include "MarlinConfig.h"
27 27
 
28 28
 #if ENABLED(AUTO_BED_LEVELING_UBL)
29
-
30 29
   #include "Marlin.h"
30
+  #include "planner.h"
31 31
   #include "math.h"
32 32
   #include "vector_3.h"
33
-  #include "planner.h"
34 33
 
35 34
   #define UBL_VERSION "1.00"
36 35
   #define UBL_OK false
@@ -49,10 +48,8 @@
49 48
   void debug_current_and_destination(const char * const title);
50 49
   void ubl_line_to_destination(const float&, uint8_t);
51 50
   void manually_probe_remaining_mesh(const float&, const float&, const float&, const float&, const bool);
52
-  vector_3 tilt_mesh_based_on_3pts(const float&, const float&, const float&);
53 51
   float measure_business_card_thickness(const float&);
54 52
   mesh_index_pair find_closest_mesh_point_of_type(const MeshPointType, const float&, const float&, const bool, unsigned int[16], bool);
55
-  void find_mean_mesh_height();
56 53
   void shift_mesh_height();
57 54
   bool g29_parameter_parsing();
58 55
   void g29_what_command();
@@ -67,10 +64,8 @@
67 64
   void gcode_G26();
68 65
   void gcode_G28();
69 66
   void gcode_G29();
70
-  extern char conv[9];
71 67
 
72
-  void save_ubl_active_state_and_disable();
73
-  void restore_ubl_active_state_and_leave();
68
+  extern int ubl_cnt;
74 69
 
75 70
   ///////////////////////////////////////////////////////////////////////////////////////////////////////
76 71
 
@@ -79,7 +74,6 @@
79 74
     void lcd_quick_feedback();
80 75
   #endif
81 76
 
82
-  enum MBLStatus { MBL_STATUS_NONE = 0, MBL_STATUS_HAS_MESH_BIT = 0, MBL_STATUS_ACTIVE_BIT = 1 };
83 77
 
84 78
   #define MESH_X_DIST (float(UBL_MESH_MAX_X - (UBL_MESH_MIN_X)) / float(GRID_MAX_POINTS_X - 1))
85 79
   #define MESH_Y_DIST (float(UBL_MESH_MAX_Y - (UBL_MESH_MIN_Y)) / float(GRID_MAX_POINTS_Y - 1))
@@ -97,6 +91,43 @@
97 91
 
98 92
     public:
99 93
 
94
+//
95
+// Please do not put STATIC qualifiers in front of ANYTHING in this file.   You WILL cause problems by doing that.
96
+// The GCC optimizer inlines static functions and this DRAMATICALLY increases the size of the stack frame of 
97
+// functions that call STATIC functions.
98
+//
99
+      void find_mean_mesh_height();
100
+      void shift_mesh_height();
101
+      void probe_entire_mesh(const float &lx, const float &ly, const bool do_ubl_mesh_map, const bool stow_probe, bool do_furthest);
102
+      void tilt_mesh_based_on_3pts(const float &z1, const float &z2, const float &z3);
103
+      void tilt_mesh_based_on_probed_grid(const bool do_ubl_mesh_map);
104
+      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);
105
+      void save_ubl_active_state_and_disable();
106
+      void restore_ubl_active_state_and_leave();
107
+      void g29_what_command(); 
108
+//
109
+// Please do not put STATIC qualifiers in front of ANYTHING in this file.   You WILL cause problems by doing that.
110
+// The GCC optimizer inlines static functions and this DRAMATICALLY increases the size of the stack frame of 
111
+// functions that call STATIC functions.
112
+//
113
+      void g29_eeprom_dump() ;
114
+      void g29_compare_current_mesh_to_stored_mesh();
115
+      void fine_tune_mesh(const float &lx, const float &ly, const bool do_ubl_mesh_map);
116
+      void smart_fill_mesh();
117
+      void display_map(const int);
118
+      void reset();
119
+//
120
+// Please do not put STATIC qualifiers in front of ANYTHING in this file.   You WILL cause problems by doing that.
121
+// The GCC optimizer inlines static functions and this DRAMATICALLY increases the size of the stack frame of 
122
+// functions that call STATIC functions.
123
+//
124
+      void invalidate();
125
+      void store_state();
126
+      void load_state();
127
+      void store_mesh(const int16_t);
128
+      void load_mesh(const int16_t);
129
+      bool sanity_check();
130
+
100 131
       static ubl_state state;
101 132
 
102 133
       static float z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y];
@@ -125,32 +156,27 @@
125 156
 
126 157
       static bool g26_debug_flag, has_control_of_lcd_panel;
127 158
 
128
-      static int8_t eeprom_start;
159
+      static int16_t eeprom_start;    // Please do no change this to 8 bits in size
160
+                                      // It needs to hold values bigger than this.
129 161
 
130 162
       static volatile int encoder_diff; // Volatile because it's changed at interrupt time.
131 163
 
132 164
       unified_bed_leveling();
133 165
 
134
-      static void display_map(const int);
135
-
136
-      static void reset();
137
-      static void invalidate();
138
-
139
-      static void store_mesh(const int16_t);
140
-      static void load_mesh(const int16_t);
141
-
142
-      static bool sanity_check();
143
-
144
-      static FORCE_INLINE void set_z(const int8_t px, const int8_t py, const float &z) { z_values[px][py] = z; }
145
-
146
-      static int8_t get_cell_index_x(const float &x) {
166
+//
167
+// Please do not put STATIC qualifiers in front of ANYTHING in this file.   You WILL cause problems by doing that.
168
+// The GCC optimizer inlines static functions and this DRAMATICALLY increases the size of the stack frame of 
169
+// functions that call STATIC functions.
170
+//
171
+      FORCE_INLINE void set_z(const int8_t px, const int8_t py, const float &z) { z_values[px][py] = z; }
172
+        int8_t get_cell_index_x(const float &x) {
147 173
         const int8_t cx = (x - (UBL_MESH_MIN_X)) * (1.0 / (MESH_X_DIST));
148 174
         return constrain(cx, 0, (GRID_MAX_POINTS_X) - 1);   // -1 is appropriate if we want all movement to the X_MAX
149 175
       }                                                     // position. But with this defined this way, it is possible
150 176
                                                             // to extrapolate off of this point even further out. Probably
151 177
                                                             // that is OK because something else should be keeping that from
152 178
                                                             // happening and should not be worried about at this level.
153
-      static int8_t get_cell_index_y(const float &y) {
179
+      int8_t get_cell_index_y(const float &y) {
154 180
         const int8_t cy = (y - (UBL_MESH_MIN_Y)) * (1.0 / (MESH_Y_DIST));
155 181
         return constrain(cy, 0, (GRID_MAX_POINTS_Y) - 1);   // -1 is appropriate if we want all movement to the Y_MAX
156 182
       }                                                     // position. But with this defined this way, it is possible
@@ -158,12 +184,17 @@
158 184
                                                             // that is OK because something else should be keeping that from
159 185
                                                             // happening and should not be worried about at this level.
160 186
 
161
-      static int8_t find_closest_x_index(const float &x) {
187
+//
188
+// Please do not put STATIC qualifiers in front of ANYTHING in this file.   You WILL cause problems by doing that.
189
+// The GCC optimizer inlines static functions and this DRAMATICALLY increases the size of the stack frame of 
190
+// functions that call STATIC functions.
191
+//
192
+      int8_t find_closest_x_index(const float &x) {
162 193
         const int8_t px = (x - (UBL_MESH_MIN_X) + (MESH_X_DIST) * 0.5) * (1.0 / (MESH_X_DIST));
163 194
         return WITHIN(px, 0, GRID_MAX_POINTS_X - 1) ? px : -1;
164 195
       }
165 196
 
166
-      static int8_t find_closest_y_index(const float &y) {
197
+      int8_t find_closest_y_index(const float &y) {
167 198
         const int8_t py = (y - (UBL_MESH_MIN_Y) + (MESH_Y_DIST) * 0.5) * (1.0 / (MESH_Y_DIST));
168 199
         return WITHIN(py, 0, GRID_MAX_POINTS_Y - 1) ? py : -1;
169 200
       }
@@ -183,15 +214,20 @@
183 214
        *  It is fairly expensive with its 4 floating point additions and 2 floating point
184 215
        *  multiplications.
185 216
        */
186
-      static FORCE_INLINE float calc_z0(const float &a0, const float &a1, const float &z1, const float &a2, const float &z2) {
217
+      FORCE_INLINE float calc_z0(const float &a0, const float &a1, const float &z1, const float &a2, const float &z2) {
187 218
         return z1 + (z2 - z1) * (a0 - a1) / (a2 - a1);
188 219
       }
220
+//
221
+// Please do not put STATIC qualifiers in front of ANYTHING in this file.   You WILL cause problems by doing that.
222
+// The GCC optimizer inlines static functions and this DRAMATICALLY increases the size of the stack frame of 
223
+// functions that call STATIC functions.
224
+//
189 225
 
190 226
       /**
191 227
        * z_correction_for_x_on_horizontal_mesh_line is an optimization for
192 228
        * the rare occasion when a point lies exactly on a Mesh line (denoted by index yi).
193 229
        */
194
-      static inline float z_correction_for_x_on_horizontal_mesh_line(const float &lx0, const int x1_i, const int yi) {
230
+      inline float z_correction_for_x_on_horizontal_mesh_line(const float &lx0, const int x1_i, const int yi) {
195 231
         if (!WITHIN(x1_i, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(yi, 0, GRID_MAX_POINTS_Y - 1)) {
196 232
           SERIAL_ECHOPAIR("? in z_correction_for_x_on_horizontal_mesh_line(lx0=", lx0);
197 233
           SERIAL_ECHOPAIR(",x1_i=", x1_i);
@@ -210,7 +246,7 @@
210 246
       //
211 247
       // See comments above for z_correction_for_x_on_horizontal_mesh_line
212 248
       //
213
-      static inline float z_correction_for_y_on_vertical_mesh_line(const float &ly0, const int xi, const int y1_i) {
249
+      inline float z_correction_for_y_on_vertical_mesh_line(const float &ly0, const int xi, const int y1_i) {
214 250
         if (!WITHIN(xi, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(y1_i, 0, GRID_MAX_POINTS_Y - 1)) {
215 251
           SERIAL_ECHOPAIR("? in get_z_correction_along_vertical_mesh_line_at_specific_x(ly0=", ly0);
216 252
           SERIAL_ECHOPAIR(", x1_i=", xi);
@@ -232,7 +268,7 @@
232 268
        * Z-Height at both ends. Then it does a linear interpolation of these heights based
233 269
        * on the Y position within the cell.
234 270
        */
235
-      static float get_z_correction(const float &lx0, const float &ly0) {
271
+      float get_z_correction(const float &lx0, const float &ly0) {
236 272
         const int8_t cx = get_cell_index_x(RAW_X_POSITION(lx0)),
237 273
                      cy = get_cell_index_y(RAW_Y_POSITION(ly0));
238 274
 
@@ -308,7 +344,7 @@
308 344
        */
309 345
       #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
310 346
 
311
-        static FORCE_INLINE float fade_scaling_factor_for_z(const float &lz) {
347
+        FORCE_INLINE float fade_scaling_factor_for_z(const float &lz) {
312 348
           if (planner.z_fade_height == 0.0) return 1.0;
313 349
 
314 350
           static float fade_scaling_factor = 1.0;

+ 371
- 270
Marlin/ubl_G29.cpp
文件差异内容过多而无法显示
查看文件


+ 1
- 0
Marlin/utility.cpp 查看文件

@@ -31,6 +31,7 @@ void safe_delay(millis_t ms) {
31 31
     thermalManager.manage_heater();
32 32
   }
33 33
   delay(ms);
34
+  thermalManager.manage_heater();	// This keeps us safe if too many small safe_delay() calls are made
34 35
 }
35 36
 
36 37
 #if ENABLED(ULTRA_LCD)

正在加载...
取消
保存