LAT Hologramm-Software 2.0
Loading...
Searching...
No Matches
fit_nonlinear_spindle.py
Go to the documentation of this file.
1"""
2---------------------
3The MIT License (MIT)
4
5Copyright (c) 2017-2018 Sungeun K. Jeon for Gnea Research LLC
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in
15all copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23THE SOFTWARE.
24---------------------
25"""
26
27
28"""
29This Python script produces a continuous piece-wise line fit of actual spindle speed over
30programmed speed/PWM, which must be measured and provided by the user. A plot of the data
31and line fit will be auto-generated and saved in the working directory as 'line_fit.png'.
32
33REQUIREMENTS:
34 - Python 2.7 or 3.x with SciPy, NumPy, and Matplotlib Python Libraries
35
36 - For the most people, the easiest way to run this script is on the free cloud service
37 https://repl.it/site/languages/python3. No account necessary. Unlimited runs.
38
39 - Last checked on 4/5/2018. This site has been regularly evolving and becoming more
40 powerful. Things may not work exactly as shown. Please report any issues.
41
42 - To use, go to the website and start the Python REPL. Copy and paste this script into
43 the main.py file in the browser editor. Click 'Run' and a solution should appear in
44 the text output in the REPL console window. You can edit the script directly in the
45 browser and re-run the script as many times as you need. A free account is only
46 necessary if you want to save files on their servers.
47
48 - This script will also automatically generate a png image with the plot of the data
49 with the piece-wise linear fit over it, but this will not show up by default on this
50 website. To enable this, just click the 'Add File' icon and create a dummy file.
51 Name it anything, like dummy.py. Leave this file blank. Doing this places the REPL
52 in multiple file mode and will enable viewing the plot. Click the 'Run' icon. The
53 solution will be presented again in the console, and the data plot will appear in
54 the file list called 'line_fit.png'. Click the file to view the plot.
55
56 - For offline Python installs, most Mac and Linux computers have Python pre-installed
57 with the required libraries. If not, a quick google search will show you how to
58 install them. For Windows, Python installations are bit more difficult. Anaconda and
59 Pyzo seem to work well.
60
61USAGE:
62 - First, make sure you are using the stock build of Grbl for the 328p processor. Most
63 importantly, the SPINDLE_PWM_MAX_VALUE and SPINDLE_PWM_MIN_VALUE should be unaltered
64 from defaults, otherwise change them back to 255.0 and 1.0 respectively for this test.
65
66 - Next, program the max and min rpm Grbl settings to '$30=255' and '$31=1'. This sets
67 the internal PWM values equal to 'S' spindle speed for the standard Grbl build.
68
69 - Check if your spindle does not turn on at very low voltages by setting 'S' spindle
70 speed to 'S1'. If it does not turn on or turns at a non-useful rpm, increase 'S' by
71 one until it does. Write down this 'S' value for later. You'll start the rpm data
72 collection from this point onward and will need to update the SPINDLE_PWM_MIN_VALUE
73 in cpu_map.h afterwards.
74
75 - Collect actual spindle speed with a tachometer or similar means over a range of 'S'
76 and PWM values. Start by setting the spindle 'S' speed to the minimum useful 'S' from
77 the last step and measure and record actual spindle rpm. Next, increase 'S' spindle
78 speed over equally sized intervals and repeat the measurement. Increments of 20 rpm
79 should be more than enough, but decrease increment size, if highly nonlinear. Complete
80 the data collection the 'S' spindle speed equal to '$30' max rpm, or at the max useful
81 rpm, and record the actual rpm output. Make sure to collect rpm data all the way
82 throughout useful output rpm. The actual operating range within this model may be set
83 later within Grbl with the '$30' and '$31' settings.
84
85 - In some cases, spindle PWM output can have discontinuities or not have a useful rpm
86 in certain ranges. For example, a known controller board has the spindle rpm drop
87 completely at voltages above ~4.5V. If you have discontinuities like this at the low
88 or high range of rpm, simply trim them from the data set. Don't include them. For
89 Grbl to compensate, you'll need to alter the SPINDLE_PWM_MIN_VALUE and/or
90 SPINDLE_PWM_MAX_VALUE in cpu_map.h to where your data set ends. This script will
91 indicate if you need to do that in the solution output.
92
93 - Keep in mind that spindles without control electronics can slow down drastically when
94 cutting and under load. How much it slows down is dependent on a lot of factors, such
95 as feed rate, chip load, cutter diameter, flutes, cutter type, lubricant/coolant,
96 material being cut, etc. Even spindles with controllers can still slow down if the
97 load is higher than the max current the controller can provide. It's recommended to
98 frequently re-check and measure actual spindle speed during a job. You can always use
99 spindle speed overrides to tweak it temporarily to the desired speed.
100
101 - Edit this script and enter the measured rpm values and their corresponding 'S' spindle
102 speed values in the data arrays below. Set the number of piecewise lines you would
103 like to use, from one to four lines. For most cases, four lines is perfectly fine.
104 In certain scenarios (laser engraving), this may significantly degrade performance and
105 should be reduced if possible.
106
107 - Run the Python script. Visually assess the line fit from the plot. It will not likely
108 to what you want on the first go. Dial things in by altering the line fit junction
109 points 'PWM_pointX' in this script to move where the piecewise line junctions are
110 located along the plot x-axis. It may be desired to tweak the junction points so the
111 model solution is more accurate in the region that the spindle typically running.
112 Re-run the script and tweak the junction points until you are satified with the model.
113
114 - Record the solution and enter the RPM_POINT and RPM_LINE values into config.h. Set the
115 number of piecewise lines used in this model in config.h. Also set the '$30' and '$31'
116 max and min rpm values to the solution values or in a range between them in Grbl '$'
117 settings. And finally, alter the SPINDLE_PWM_MIN_VALUE in cpu_map.h, if your spindle
118 needs to be above a certain voltage to produce a useful low rpm.
119
120 - Once the solution is entered. Recompile and flash Grbl. This solution model is only
121 valid for this particular set of data. If the machine is altered, you will need to
122 perform this experiment again and regenerate a new model here.
123
124OUTPUT:
125 The solver produces a set of values that define the piecewise fit and can be used by
126 Grbl to quickly and efficiently compute spindle PWM output voltage for a desired RPM.
127
128 The first two are the RPM_MAX ($30) and RPM_MIN ($31) Grbl settings. These must be
129 programmed into Grbl manually or setup in defaults.h for new systems. Altering these
130 values within Grbl after a piece-wise linear model is installed will not change alter
131 model. It will only alter the range of spindle speed rpm values Grbl output.
132
133 For example, if the solver produces an RPM_MAX of 9000 and Grbl is programmed with
134 $30=8000, S9000 may be programmed, but Grbl will only produce the output voltage to run
135 at 8000 rpm. In other words, Grbl will only output voltages the range between
136 max(RPM_MIN,$31) and min(RPM_MAX,$30).
137
138 The remaining values define the slopes and offsets of the line segments and the junction
139 points between line segments, like so for n_pieces=3:
140
141 PWM_output = RPM_LINE_A1 * rpm - RPM_LINE_B1 [ RPM_MIN < rpm < RPM_POINT12 ]
142 PWM_output = RPM_LINE_A2 * rpm - RPM_LINE_B2 [ RPM_POINT12 < rpm < RPM_POINT23 ]
143 PWM_output = RPM_LINE_A3 * rpm - RPM_LINE_B3 [ RPM_POINT23 < rpm < RPM_MAX ]
144
145 NOTE: The script solves in terms of PWM but the final equations and values are expressed
146 in terms of rpm in the form 'PWM = a*rpm - b'.
147
148"""
149
150from scipy import optimize
151import numpy as np
152
153# ----------------------------------------------------------------------------------------
154# Configure spindle PWM line fit solver
155
156n_pieces = 4 # Number of line segments used for data fit. Only 1 to 4 line segments supported.
157
158# Programmed 'S' spindle speed values. Must start with minimum useful PWM or 'S' programmed
159# value and end with the maximum useful PWM or 'S' programmed value. Order of the array must
160# be synced with the RPM_measured array below.
161# NOTE: ** DO NOT USE DATA FROM AN EXISTING PIECEWISE LINE FIT. USE DEFAULT GRBL MODEL ONLY. **
162PWM_set = np.array([2,18,36,55,73,91,109,127,146,164,182,200,218,237,254], dtype=float)
163
164# Actual RPM measured at the spindle. Must be in the ascending value and equal in length
165# as the PWM_set array. Must include the min and max measured rpm output in the first and
166# last array entries, respectively.
167RPM_measured = np.array([213.,5420,7145,8282,9165,9765,10100,10500,10700,10900,11100,11250,11400,11550,11650], dtype=float)
168
169# Configure line fit points by 'S' programmed rpm or PWM value. Values must be between
170# PWM_max and PWM_min. Typically, alter these values to space the points evenly between
171# max and min PWM range. However, they may be tweaked to maximize accuracy in the places
172# you normally operate for highly nonlinear curves. Plot to visually assess how well the
173# solution fits the data.
174PWM_point1 = 20.0 # (S) Point between segments 0 and 1. Used when n_pieces >= 2.
175PWM_point2 = 80.0 # (S) Point between segments 1 and 2. Used when n_pieces >= 3.
176PWM_point3 = 150.0 # (S) Point between segments 2 and 3. Used when n_pieces = 4.
177
178# ----------------------------------------------------------------------------------------
179
180# Advanced settings
181
182# The optimizer requires an initial guess of the solution. Change value if solution fails.
183slope_i = 100.0; # > 0.0
184
185PWM_max = max(PWM_set) # Maximum PWM set in measured range
186PWM_min = min(PWM_set) # Minimum PWM set in measured range
187plot_figure = True # Set to False, if matplotlib is not available.
188
189# ----------------------------------------------------------------------------------------
190# DO NOT ALTER ANYTHING BELOW.
191
193 return np.piecewise(x, [(x>=PWM_min)&(x<=PWM_max)], [lambda x:k1*(x-PWM_min)+b])
194
195def piecewise_linear_2(x,b,k1,k2):
196 c = [b,
197 b+k1*(PWM_point1-PWM_min)]
198 funcs = [lambda x:k1*(x-PWM_min)+c[0],
199 lambda x:k2*(x-PWM_point1)+c[1]]
200 conds = [(x<PWM_point1)&(x>=PWM_min),
201 (x<=PWM_max)&(x>=PWM_point1)]
202 return np.piecewise(x, conds, funcs)
203
204def piecewise_linear_3(x,b,k1,k2,k3):
205 c = [b,
206 b+k1*(PWM_point1-PWM_min),
207 b+k1*(PWM_point1-PWM_min)+k2*(PWM_point2-PWM_point1)]
208 funcs = [lambda x:k1*(x-PWM_min)+c[0],
209 lambda x:k2*(x-PWM_point1)+c[1],
210 lambda x:k3*(x-PWM_point2)+c[2]]
211 conds = [(x<PWM_point1)&(x>=PWM_min),
212 (x<PWM_point2)&(x>=PWM_point1),
213 (x<=PWM_max)&(x>=PWM_point2)]
214 return np.piecewise(x, conds, funcs)
215
216def piecewise_linear_4(x,b,k1,k2,k3,k4):
217 c = [b,
218 b+k1*(PWM_point1-PWM_min),
219 b+k1*(PWM_point1-PWM_min)+k2*(PWM_point2-PWM_point1),
220 b+k1*(PWM_point1-PWM_min)+k2*(PWM_point2-PWM_point1)+k3*(PWM_point3-PWM_point2)]
221 funcs = [lambda x:k1*(x-PWM_min)+c[0],
222 lambda x:k2*(x-PWM_point1)+c[1],
223 lambda x:k3*(x-PWM_point2)+c[2],
224 lambda x:k4*(x-PWM_point3)+c[3]]
225 conds = [(x<PWM_point1)&(x>=PWM_min),
226 (x<PWM_point2)&(x>=PWM_point1),
227 (x<PWM_point3)&(x>=PWM_point2),
228 (x<=PWM_max)&(x>=PWM_point3)]
229 return np.piecewise(x, conds, funcs)
230
231# ----------------------------------------------------------------------------------------
232
233print("\nCONFIG:")
234print(" N_pieces: %i" % n_pieces)
235print(" PWM_min: %.1f" % PWM_min)
236print(" PWM_max: %.1f" % PWM_max)
237if n_pieces > 1:
238 print(" PWM_point1: %.1f" % PWM_point1)
239if n_pieces > 2:
240 print(" PWM_point2: %.1f" % PWM_point2)
241if n_pieces > 3:
242 print(" PWM_point3: %.1f" % PWM_point3)
243print(" N_data: %i" % len(RPM_measured))
244print(" PWM_set: ", PWM_set)
245print(" RPM_measured: ", RPM_measured)
246
247if n_pieces == 1:
248 piece_func = piecewise_linear_1
249 p_initial = [RPM_measured[0],slope_i]
250
251 p , e = optimize.curve_fit(piece_func, PWM_set, RPM_measured, p0=p_initial)
252 a = [p[1]]
253 b = [ p[0]-p[1]*PWM_min]
254 rpm = [ p[0],
255 p[0]+p[1]*(PWM_point1-PWM_min)]
256
257elif n_pieces == 2:
258 piece_func = piecewise_linear_2
259 p_initial = [RPM_measured[0],slope_i,slope_i]
260
261 p , e = optimize.curve_fit(piece_func, PWM_set, RPM_measured, p0=p_initial)
262 a = [p[1],p[2]]
263 b = [ p[0]-p[1]*PWM_min,
264 p[0]+p[1]*(PWM_point1-PWM_min)-p[2]*PWM_point1]
265 rpm = [ p[0],
266 p[0]+p[1]*(PWM_point1-PWM_min),
267 p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_max-PWM_point1)]
268
269elif n_pieces == 3:
270 piece_func = piecewise_linear_3
271 p_initial = [RPM_measured[0],slope_i,slope_i,slope_i]
272
273 p , e = optimize.curve_fit(piece_func, PWM_set, RPM_measured, p0=p_initial)
274 a = [p[1],p[2],p[3]]
275 b = [ p[0]-p[1]*PWM_min,
276 p[0]+p[1]*(PWM_point1-PWM_min)-p[2]*PWM_point1,
277 p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)-p[3]*PWM_point2]
278 rpm = [ p[0],
279 p[0]+p[1]*(PWM_point1-PWM_min),
280 p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1),
281 p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)+p[3]*(PWM_max-PWM_point2) ]
282
283elif n_pieces == 4:
284 piece_func = piecewise_linear_4
285 p_initial = [RPM_measured[0],slope_i,slope_i,slope_i,slope_i]
286
287 p , e = optimize.curve_fit(piece_func, PWM_set, RPM_measured, p0=p_initial)
288 a = [p[1],p[2],p[3],p[4]]
289 b = [ p[0]-p[1]*PWM_min,
290 p[0]+p[1]*(PWM_point1-PWM_min)-p[2]*PWM_point1,
291 p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)-p[3]*PWM_point2,
292 p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)+p[3]*(PWM_point3-PWM_point2)-p[4]*PWM_point3 ]
293 rpm = [ p[0],
294 p[0]+p[1]*(PWM_point1-PWM_min),
295 p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1),
296 p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)+p[3]*(PWM_point3-PWM_point2),
297 p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)+p[3]*(PWM_point3-PWM_point2)+p[4]*(PWM_max-PWM_point3) ]
298
299else :
300 print("ERROR: Unsupported number of pieces. Check and alter n_pieces")
301 quit()
302
303print("\nSOLUTION:\n\n[Update these #define values and uncomment]\n[ENABLE_PIECEWISE_LINEAR_SPINDLE in config.h.]")
304print("#define N_PIECES %.0f" % n_pieces)
305print("#define RPM_MAX %.1f" % rpm[-1])
306print("#define RPM_MIN %.1f" % rpm[0])
307
308if n_pieces > 1:
309 print("#define RPM_POINT12 %.1f" % rpm[1])
310if n_pieces > 2:
311 print("#define RPM_POINT23 %.1f" %rpm[2])
312if n_pieces > 3:
313 print("#define RPM_POINT34 %.1f" %rpm[3])
314
315print("#define RPM_LINE_A1 %.6e" % (1./a[0]))
316print("#define RPM_LINE_B1 %.6e" % (b[0]/a[0]))
317if n_pieces > 1:
318 print("#define RPM_LINE_A2 %.6e" % (1./a[1]))
319 print("#define RPM_LINE_B2 %.6e" % (b[1]/a[1]))
320if n_pieces > 2:
321 print("#define RPM_LINE_A3 %.6e" % (1./a[2]))
322 print("#define RPM_LINE_B3 %.6e" % (b[2]/a[2]))
323if n_pieces > 3:
324 print("#define RPM_LINE_A4 %.6e" % (1./a[3]))
325 print("#define RPM_LINE_B4 %.6e" % (b[3]/a[3]))
326
327print("\n[To operate over full model range, manually write these]")
328print("['$' settings or alter values in defaults.h. Grbl will]")
329print("[operate between min($30,RPM_MAX) and max($31,RPM_MIN)]")
330print("$30=%.1f (rpm max)" % rpm[-1])
331print("$31=%.1f (rpm min)" % rpm[0])
332
333if (PWM_min > 1)|(PWM_max<255):
334 print("\n[Update the following #define values in cpu_map.h]")
335 if (PWM_min >1) :
336 print("#define SPINDLE_PWM_MIN_VALUE %.0f" % PWM_min)
337 if PWM_max <255:
338 print("#define SPINDLE_PWM_MAX_VALUE %.0f" % PWM_max)
339else:
340 print("\n[No cpu_map.h changes required.]")
341print("\n")
342
343test_val = (1./a[0])*rpm[0] - (b[0]/a[0])
344if test_val < 0.0 :
345 print("ERROR: Solution is negative at RPM_MIN. Adjust junction points or increase n_pieces.\n")
346
347if plot_figure:
348 import matplotlib
349 matplotlib.use("Agg")
350 import matplotlib.pyplot as plt
351
352 fig = plt.figure()
353 ax = fig.add_subplot(111)
354 xd = np.linspace(PWM_min, PWM_max, 10000)
355 ax.plot(PWM_set, RPM_measured, "o")
356 ax.plot(xd, piece_func(xd, *p),'g')
357 plt.xlabel("Programmed PWM")
358 plt.ylabel("Measured RPM")
359
360 # Check solution by plotting in terms of rpm.
361# x = np.linspace(rpm[0], rpm[1], 10000)
362# ax.plot((1./a[0])*x-(b[0]/a[0]),x,'r:')
363# if n_pieces > 1:
364# x = np.linspace(rpm[1], rpm[2], 10000)
365# ax.plot((1./a[1])*x-(b[1]/a[1]),x,'r:')
366# if n_pieces > 2:
367# x = np.linspace(rpm[2], rpm[3], 10000)
368# ax.plot((1./a[2])*x-(b[2]/a[2]),x,'r:')
369# if n_pieces > 3:
370# x = np.linspace(rpm[3], rpm[-1], 10000)
371# ax.plot((1./a[3])*x-(b[3]/a[3]),x,'r:')
372
373 fig.savefig("line_fit.png")
def piecewise_linear_3(x, b, k1, k2, k3)
def piecewise_linear_4(x, b, k1, k2, k3, k4)
def piecewise_linear_2(x, b, k1, k2)
#define min(a, b)
Definition: nuts_bolts.h:57
#define max(a, b)
Definition: nuts_bolts.h:56