From 790c666ecbe0775019ee9deef51c27db33409ab9 Mon Sep 17 00:00:00 2001 From: chamnit Date: Wed, 31 May 2017 21:37:21 -0600 Subject: [PATCH] New nonlinear spindle speed PWM output model and solution. Updated scripts. [new] A nonlinear spindle speed/PWM output option via a piecewise linear fit model. Enabled through config.h and solved by a Python script in /doc/script [new] fit_nonlinear_spindle.py. A solver script that can be run on http://repl.it for free. No Python install necessary. All instructions are available in the script file comments. [new] stream.py has been updated to include status reports feedback at 1 second interval. [fix] stream.py bug fix with verbose mode disabled. --- doc/log/commit_log_v1.1.txt | 11 + doc/script/fit_nonlinear_spindle.py | 363 ++++++++++++++++++++++++++++ doc/script/stream.py | 45 +++- grbl/config.h | 25 ++ grbl/grbl.h | 2 +- grbl/spindle_control.c | 95 ++++++-- 6 files changed, 503 insertions(+), 38 deletions(-) create mode 100644 doc/script/fit_nonlinear_spindle.py diff --git a/doc/log/commit_log_v1.1.txt b/doc/log/commit_log_v1.1.txt index bae65ace..2686f9b4 100644 --- a/doc/log/commit_log_v1.1.txt +++ b/doc/log/commit_log_v1.1.txt @@ -1,3 +1,14 @@ +---------------- +Date: 2017-03-24 +Author: Sonny Jeon +Subject: Added an error code for laser mode when VARIABLE_SPINDLE is disabled. + +- When trying to enable laser mode with $32=1 and VARIABLE_SPINDLE is +disabled, the error code shown was improperly stating it was a homing +failure. Added an new error code specifically for the laser mode being +disabled without VARIABLE_SPINDLE. + + ---------------- Date: 2017-03-19 Author: Sonny Jeon diff --git a/doc/script/fit_nonlinear_spindle.py b/doc/script/fit_nonlinear_spindle.py new file mode 100644 index 00000000..549a18c1 --- /dev/null +++ b/doc/script/fit_nonlinear_spindle.py @@ -0,0 +1,363 @@ +""" +--------------------- +The MIT License (MIT) + +Copyright (c) 2017 Sungeun K. Jeon for Gnea Research LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +--------------------- +""" + + +""" +This Python script produces a continuous piece-wise line fit of actual spindle speed over +programmed speed/PWM, which must be measured and provided by the user. A plot of the data +and line fit will be auto-generated and saved in the working directory as 'line_fit.png'. + +REQUIREMENTS: + - Python 2.7 or 3.x with SciPy, NumPy, and Matplotlib Python Libraries + + - For the most people, the easiest way to run this script is on the free cloud service + https://repl.it/site/languages/python3. No account necessary. Unlimited runs. To use, + go to the website and start the Python REPL. Copy and paste this script into the + browser editor. Click the 'Add New File' icon on the upper left side. This is very + important. It places the REPL in multiple file mode and will enable viewing the plot. + Click the 'Run' icon. The solution will be presented in the console on the right side, + and the data plot will appear as a tab called 'line_fit.png'. You can edit the script + directly in the browser and re-run the script as many times as you need. A free + account is only necessary if you want to save files on their servers. + + - For offline Python installs, most Mac and Linux computers have Python pre-installed + with the required libraries. If not, a quick google search will show you how to + install them. For Windows, Python installations are bit more difficult. Anaconda and + Pyzo seem to work well. + +USAGE: + - First, make sure you are using the stock build of Grbl for the 328p processor. Most + importantly, the SPINDLE_PWM_MAX_VALUE and SPINDLE_PWM_MIN_VALUE should be unaltered + from defaults, otherwise change them back to 255.0 and 1.0 respectively for this test. + + - Next, program the max and min rpm Grbl settings to '$30=255' and '$31=1'. This sets + the internal PWM values equal to 'S' spindle speed for the standard Grbl build. + + - Check if your spindle does not turn on at very low voltages by setting 'S' spindle + speed to 'S1'. If it does not turn on or turns at a non-useful rpm, increase 'S' by + one until it does. Write down this 'S' value for later. You'll start the rpm data + collection from this point onward and will need to update the SPINDLE_PWM_MIN_VALUE + in cpu_map.h afterwards. + + - Collect actual spindle speed with a tachometer or similar means over a range of 'S' + and PWM values. Start by setting the spindle 'S' speed to the minimum useful 'S' from + the last step and measure and record actual spindle rpm. Next, increase 'S' spindle + speed over equally sized intervals and repeat the measurement. Increments of 20 rpm + should be more than enough, but decrease increment size, if highly nonlinear. Complete + the data collection the 'S' spindle speed equal to '$30' max rpm, or at the max useful + rpm, and record the actual rpm output. Make sure to collect rpm data all the way + throughout useful output rpm. The actual operating range within this model may be set + later within Grbl with the '$30' and '$31' settings. + + - In some cases, spindle PWM output can have discontinuities or not have a useful rpm + in certain ranges. For example, a known controller board has the spindle rpm drop + completely at voltages above ~4.5V. If you have discontinuities like this at the low + or high range of rpm, simply trim them from the data set. Don't include them. For + Grbl to compensate, you'll need to alter the SPINDLE_PWM_MIN_VALUE and/or + SPINDLE_PWM_MAX_VALUE in cpu_map.h to where your data set ends. This script will + indicate if you need to do that in the solution output. + + - Keep in mind that spindles without control electronics can slow down drastically when + cutting and under load. How much it slows down is dependent on a lot of factors, such + as feed rate, chip load, cutter diameter, flutes, cutter type, lubricant/coolant, + material being cut, etc. Even spindles with controllers can still slow down if the + load is higher than the max current the controller can provide. It's recommended to + frequently re-check and measure actual spindle speed during a job. You can always use + spindle speed overrides to tweak it temporarily to the desired speed. + + - Edit this script and enter the measured rpm values and their corresponding 'S' spindle + speed values in the data arrays below. Set the number of piecewise lines you would + like to use, from one to four lines. For most cases, four lines is perfectly fine. + In certain scenarios (laser engraving), this may significantly degrade performance and + should be reduced if possible. + + - Run the Python script. Visually assess the line fit from the plot. It will not likely + to what you want on the first go. Dial things in by altering the line fit junction + points 'PWM_pointX' in this script to move where the piecewise line junctions are + located along the plot x-axis. It may be desired to tweak the junction points so the + model solution is more accurate in the region that the spindle typically running. + Re-run the script and tweak the junction points until you are satified with the model. + + - Record the solution and enter the RPM_POINT and RPM_LINE values into config.h. Set the + number of piecewise lines used in this model in config.h. Also set the '$30' and '$31' + max and min rpm values to the solution values or in a range between them in Grbl '$' + settings. And finally, alter the SPINDLE_PWM_MIN_VALUE in cpu_map.h, if your spindle + needs to be above a certain voltage to produce a useful low rpm. + + - Once the solution is entered. Recompile and flash Grbl. This solution model is only + valid for this particular set of data. If the machine is altered, you will need to + perform this experiment again and regenerate a new model here. + +OUTPUT: + The solver produces a set of values that define the piecewise fit and can be used by + Grbl to quickly and efficiently compute spindle PWM output voltage for a desired RPM. + + The first two are the RPM_MAX ($30) and RPM_MIN ($31) Grbl settings. These must be + programmed into Grbl manually or setup in defaults.h for new systems. Altering these + values within Grbl after a piece-wise linear model is installed will not change alter + model. It will only alter the range of spindle speed rpm values Grbl output. + + For example, if the solver produces an RPM_MAX of 9000 and Grbl is programmed with + $30=8000, S9000 may be programmed, but Grbl will only produce the output voltage to run + at 8000 rpm. In other words, Grbl will only output voltages the range between + max(RPM_MIN,$31) and min(RPM_MAX,$30). + + The remaining values define the slopes and offsets of the line segments and the junction + points between line segments, like so for n_pieces=3: + + PWM_output = RPM_LINE_A1 * rpm - RPM_LINE_B1 [ RPM_MIN < rpm < RPM_POINT12 ] + PWM_output = RPM_LINE_A2 * rpm - RPM_LINE_B2 [ RPM_POINT12 < rpm < RPM_POINT23 ] + PWM_output = RPM_LINE_A3 * rpm - RPM_LINE_B3 [ RPM_POINT23 < rpm < RPM_MAX ] + + NOTE: The script solves in terms of PWM but the final equations and values are expressed + in terms of rpm in the form 'PWM = a*rpm - b'. + +""" + +from scipy import optimize +import numpy as np + +# ---------------------------------------------------------------------------------------- +# Configure spindle PWM line fit solver + +n_pieces = 4 # Number of line segments used for data fit. Only 1 to 4 line segments supported. + +# Programmed 'S' spindle speed values. Must start with minimum useful PWM or 'S' programmed +# value and end with the maximum useful PWM or 'S' programmed value. Order of the array must +# be synced with the RPM_measured array below. +# NOTE: ** DO NOT USE DATA FROM AN EXISTING PIECEWISE LINE FIT. USE DEFAULT GRBL MODEL ONLY. ** +PWM_set = np.array([2,18,36,55,73,91,109,127,146,164,182,200,218,237,254], dtype=float) + +# Actual RPM measured at the spindle. Must be in the ascending value and equal in length +# as the PWM_set array. Must include the min and max measured rpm output in the first and +# last array entries, respectively. +RPM_measured = np.array([213.,5420,7145,8282,9165,9765,10100,10500,10700,10900,11100,11250,11400,11550,11650], dtype=float) + +# Configure line fit points by 'S' programmed rpm or PWM value. Values must be between +# PWM_max and PWM_min. Typically, alter these values to space the points evenly between +# max and min PWM range. However, they may be tweaked to maximize accuracy in the places +# you normally operate for highly nonlinear curves. Plot to visually assess how well the +# solution fits the data. +PWM_point1 = 20.0 # (S) Point between segments 0 and 1. Used when n_pieces >= 2. +PWM_point2 = 80.0 # (S) Point between segments 1 and 2. Used when n_pieces >= 3. +PWM_point3 = 150.0 # (S) Point between segments 2 and 3. Used when n_pieces = 4. + +# ---------------------------------------------------------------------------------------- + +# Advanced settings + +# The optimizer requires an initial guess of the solution. Change value if solution fails. +slope_i = 100.0; # > 0.0 + +PWM_max = max(PWM_set) # Maximum PWM set in measured range +PWM_min = min(PWM_set) # Minimum PWM set in measured range +plot_figure = True # Set to False, if matplotlib is not available. + +# ---------------------------------------------------------------------------------------- +# DO NOT ALTER ANYTHING BELOW. + +def piecewise_linear_1(x,b,k1): + return np.piecewise(x, [(x>=PWM_min)&(x<=PWM_max)], [lambda x:k1*(x-PWM_min)+b]) + +def piecewise_linear_2(x,b,k1,k2): + c = [b, + b+k1*(PWM_point1-PWM_min)] + funcs = [lambda x:k1*(x-PWM_min)+c[0], + lambda x:k2*(x-PWM_point1)+c[1]] + conds = [(x=PWM_min), + (x<=PWM_max)&(x>=PWM_point1)] + return np.piecewise(x, conds, funcs) + +def piecewise_linear_3(x,b,k1,k2,k3): + c = [b, + b+k1*(PWM_point1-PWM_min), + b+k1*(PWM_point1-PWM_min)+k2*(PWM_point2-PWM_point1)] + funcs = [lambda x:k1*(x-PWM_min)+c[0], + lambda x:k2*(x-PWM_point1)+c[1], + lambda x:k3*(x-PWM_point2)+c[2]] + conds = [(x=PWM_min), + (x=PWM_point1), + (x<=PWM_max)&(x>=PWM_point2)] + return np.piecewise(x, conds, funcs) + +def piecewise_linear_4(x,b,k1,k2,k3,k4): + c = [b, + b+k1*(PWM_point1-PWM_min), + b+k1*(PWM_point1-PWM_min)+k2*(PWM_point2-PWM_point1), + b+k1*(PWM_point1-PWM_min)+k2*(PWM_point2-PWM_point1)+k3*(PWM_point3-PWM_point2)] + funcs = [lambda x:k1*(x-PWM_min)+c[0], + lambda x:k2*(x-PWM_point1)+c[1], + lambda x:k3*(x-PWM_point2)+c[2], + lambda x:k4*(x-PWM_point3)+c[3]] + conds = [(x=PWM_min), + (x=PWM_point1), + (x=PWM_point2), + (x<=PWM_max)&(x>=PWM_point3)] + return np.piecewise(x, conds, funcs) + +# ---------------------------------------------------------------------------------------- + +print("\nCONFIG:") +print(" N_pieces: %i" % n_pieces) +print(" PWM_min: %.1f" % PWM_min) +print(" PWM_max: %.1f" % PWM_max) +if n_pieces > 1: + print(" PWM_point1: %.1f" % PWM_point1) +if n_pieces > 2: + print(" PWM_point2: %.1f" % PWM_point2) +if n_pieces > 3: + print(" PWM_point3: %.1f" % PWM_point3) +print(" N_data: %i" % len(RPM_measured)) +print(" PWM_set: ", PWM_set) +print(" RPM_measured: ", RPM_measured) + +if n_pieces == 1: + piece_func = piecewise_linear_1 + p_initial = [RPM_measured[0],slope_i] + + p , e = optimize.curve_fit(piece_func, PWM_set, RPM_measured, p0=p_initial) + a = [p[1]] + b = [ p[0]-p[1]*PWM_min] + rpm = [ p[0], + p[0]+p[1]*(PWM_point1-PWM_min)] + +elif n_pieces == 2: + piece_func = piecewise_linear_2 + p_initial = [RPM_measured[0],slope_i,slope_i] + + p , e = optimize.curve_fit(piece_func, PWM_set, RPM_measured, p0=p_initial) + a = [p[1],p[2]] + b = [ p[0]-p[1]*PWM_min, + p[0]+p[1]*(PWM_point1-PWM_min)-p[2]*PWM_point1] + rpm = [ p[0], + p[0]+p[1]*(PWM_point1-PWM_min), + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_max-PWM_point1)] + +elif n_pieces == 3: + piece_func = piecewise_linear_3 + p_initial = [RPM_measured[0],slope_i,slope_i,slope_i] + + p , e = optimize.curve_fit(piece_func, PWM_set, RPM_measured, p0=p_initial) + a = [p[1],p[2],p[3]] + b = [ p[0]-p[1]*PWM_min, + p[0]+p[1]*(PWM_point1-PWM_min)-p[2]*PWM_point1, + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)-p[3]*PWM_point2] + rpm = [ p[0], + p[0]+p[1]*(PWM_point1-PWM_min), + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1), + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)+p[3]*(PWM_max-PWM_point2) ] + +elif n_pieces == 4: + piece_func = piecewise_linear_4 + p_initial = [RPM_measured[0],slope_i,slope_i,slope_i,slope_i] + + p , e = optimize.curve_fit(piece_func, PWM_set, RPM_measured, p0=p_initial) + a = [p[1],p[2],p[3],p[4]] + b = [ p[0]-p[1]*PWM_min, + p[0]+p[1]*(PWM_point1-PWM_min)-p[2]*PWM_point1, + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)-p[3]*PWM_point2, + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)+p[3]*(PWM_point3-PWM_point2)-p[4]*PWM_point3 ] + rpm = [ p[0], + p[0]+p[1]*(PWM_point1-PWM_min), + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1), + p[0]+p[1]*(PWM_point1-PWM_min)+p[2]*(PWM_point2-PWM_point1)+p[3]*(PWM_point3-PWM_point2), + 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) ] + +else : + print("ERROR: Unsupported number of pieces. Check and alter n_pieces") + quit() + +print("\nSOLUTION:\n\n[Update these #define values and uncomment]\n[ENABLE_PIECEWISE_LINEAR_SPINDLE in config.h.]") +print("#define N_PIECES %.0f" % n_pieces) +print("#define RPM_MAX %.1f" % rpm[-1]) +print("#define RPM_MIN %.1f" % rpm[0]) + +if n_pieces > 1: + print("#define RPM_POINT12 %.1f" % rpm[1]) +if n_pieces > 2: + print("#define RPM_POINT23 %.1f" %rpm[2]) +if n_pieces > 3: + print("#define RPM_POINT34 %.1f" %rpm[3]) + +print("#define RPM_LINE_A1 %.6e" % (1./a[0])) +print("#define RPM_LINE_B1 %.6e" % (b[0]/a[0])) +if n_pieces > 1: + print("#define RPM_LINE_A2 %.6e" % (1./a[1])) + print("#define RPM_LINE_B2 %.6e" % (b[1]/a[1])) +if n_pieces > 2: + print("#define RPM_LINE_A3 %.6e" % (1./a[2])) + print("#define RPM_LINE_B3 %.6e" % (b[2]/a[2])) +if n_pieces > 3: + print("#define RPM_LINE_A4 %.6e" % (1./a[3])) + print("#define RPM_LINE_B4 %.6e" % (b[3]/a[3])) + +print("\n[To operate over full model range, manually write these]") +print("['$' settings or alter values in defaults.h. Grbl will]") +print("[operate between min($30,RPM_MAX) and max($31,RPM_MIN)]") +print("$30=%.1f (rpm max)" % rpm[-1]) +print("$31=%.1f (rpm min)" % rpm[0]) + +if (PWM_min > 1)|(PWM_max<255): + print("\n[Update the following #define values in cpu_map.h]") + if (PWM_min >1) : + print("#define SPINDLE_PWM_MIN_VALUE %.0f" % PWM_min) + if PWM_max <255: + print("#define SPINDLE_PWM_MAX_VALUE %.0f" % PWM_max) +else: + print("\n[No cpu_map.h changes required.]") +print("\n") + +test_val = (1./a[0])*rpm[0] - (b[0]/a[0]) +if test_val < 0.0 : + print("ERROR: Solution is negative at RPM_MIN. Adjust junction points or increase n_pieces.\n") + +if plot_figure: + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + + fig = plt.figure() + ax = fig.add_subplot(111) + xd = np.linspace(PWM_min, PWM_max, 10000) + ax.plot(PWM_set, RPM_measured, "o") + ax.plot(xd, piece_func(xd, *p),'g') + plt.xlabel("Programmed PWM") + plt.ylabel("Measured RPM") + + # Check solution by plotting in terms of rpm. +# x = np.linspace(rpm[0], rpm[1], 10000) +# ax.plot((1./a[0])*x-(b[0]/a[0]),x,'r:') +# if n_pieces > 1: +# x = np.linspace(rpm[1], rpm[2], 10000) +# ax.plot((1./a[1])*x-(b[1]/a[1]),x,'r:') +# if n_pieces > 2: +# x = np.linspace(rpm[2], rpm[3], 10000) +# ax.plot((1./a[2])*x-(b[2]/a[2]),x,'r:') +# if n_pieces > 3: +# x = np.linspace(rpm[3], rpm[-1], 10000) +# ax.plot((1./a[3])*x-(b[3]/a[3]),x,'r:') + + fig.savefig("line_fit.png") diff --git a/doc/script/stream.py b/doc/script/stream.py index 96e38d6e..989a6e69 100755 --- a/doc/script/stream.py +++ b/doc/script/stream.py @@ -11,6 +11,8 @@ buffer layer to prevent buffer starvation. CHANGELOG: +- 20170531: Status report feedback at 1.0 second intervals. + Configurable baudrate and report intervals. Bug fixes. - 20161212: Added push message feedback for simple streaming - 20140714: Updated baud rate to 115200. Added a settings write mode via simple streaming method. MIT-licensed. @@ -21,7 +23,7 @@ --------------------- The MIT License (MIT) -Copyright (c) 2012-2016 Sungeun K. Jeon +Copyright (c) 2012-2017 Sungeun K. Jeon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -48,9 +50,14 @@ import time import sys import argparse -# import threading +import threading RX_BUFFER_SIZE = 128 +BAUD_RATE = 115200 +ENABLE_STATUS_REPORTS = True +REPORT_INTERVAL = 1.0 # seconds + +is_run = True # Controls query timer # Define command line argument interface parser = argparse.ArgumentParser(description='Stream g-code file to grbl. (pySerial and argparse libraries required)') @@ -66,13 +73,17 @@ # Periodic timer to query for status reports # TODO: Need to track down why this doesn't restart consistently before a release. -# def periodic(): -# s.write('?') -# t = threading.Timer(0.1, periodic) # In seconds -# t.start() +def send_status_query(): + s.write('?') + +def periodic_timer() : + while is_run: + send_status_query() + time.sleep(REPORT_INTERVAL) + # Initialize -s = serial.Serial(args.device_file,115200) +s = serial.Serial(args.device_file,BAUD_RATE) f = args.gcode_file verbose = True if args.quiet : verbose = False @@ -86,6 +97,13 @@ # Wait for grbl to initialize and flush startup text in serial input time.sleep(2) s.flushInput() +start_time = time.time(); + +# Start status report periodic timer +if ENABLE_STATUS_REPORTS : + timerThread = threading.Thread(target=periodic_timer) + timerThread.daemon = True + timerThread.start() # Stream g-code to grbl l_count = 0 @@ -114,11 +132,10 @@ # responses, such that we never overflow Grbl's serial read buffer. g_count = 0 c_line = [] - # periodic() # Start status report periodic timer for line in f: l_count += 1 # Iterate line counter - # l_block = re.sub('\s|\(.*?\)','',line).upper() # Strip comments/spaces/new line and capitalize - l_block = line.strip() + l_block = re.sub('\s|\(.*?\)','',line).upper() # Strip comments/spaces/new line and capitalize + # l_block = line.strip() c_line.append(len(l_block)+1) # Track number of characters in grbl serial read buffer grbl_out = '' while sum(c_line) >= RX_BUFFER_SIZE-1 | s.inWaiting() : @@ -130,12 +147,14 @@ g_count += 1 # Iterate g-code counter grbl_out += str(g_count); # Add line finished indicator del c_line[0] # Delete the block character count corresponding to the last 'ok' - if verbose: print "SND: " + str(l_count) + " : " + l_block, s.write(l_block + '\n') # Send g-code block to grbl - if verbose : print "BUF:",str(sum(c_line)),"REC:",grbl_out + if verbose: print "BUF: " + str(sum(c_line)) + " SND: " + str(l_count) + " [" + l_block + "] REC: " + grbl_out # Wait for user input after streaming is completed -print "G-code streaming finished!\n" +print "G-code streaming finished!" +end_time = time.time(); +is_run = False; +print " Time elapsed: ",end_time-start_time,"\n" print "WARNING: Wait until grbl completes buffered g-code blocks before exiting." raw_input(" Press to exit and disable grbl.") diff --git a/grbl/config.h b/grbl/config.h index 063e6767..28dafa11 100644 --- a/grbl/config.h +++ b/grbl/config.h @@ -586,6 +586,31 @@ // to ensure the laser doesn't inadvertently remain powered while at a stop and cause a fire. #define DISABLE_LASER_DURING_HOLD // Default enabled. Comment to disable. +// Enables a piecewise linear model of the spindle PWM/speed output. Requires a solution by the +// 'fit_nonlinear_spindle.py' script in the /doc/script folder of the repo. See file comments +// on how to gather spindle data and run the script to generate a solution. +// #define ENABLE_PIECEWISE_LINEAR_SPINDLE // Default disabled. Uncomment to enable. + +// N_PIECES, RPM_MAX, RPM_MIN, RPM_POINTxx, and RPM_LINE_XX constants are all set and given by +// the 'fit_nonlinear_spindle.py' script solution. Used only when ENABLE_PIECEWISE_LINEAR_SPINDLE +// is enabled. Make sure the constant values are exactly the same as the script solution. +// NOTE: When N_PIECES < 4, unused RPM_LINE and RPM_POINT defines are not required and omitted. +#define N_PIECES 4 // Integer (1-4). Number of piecewise lines used in script solution. +#define RPM_MAX 11686.4 // Max RPM of model. $30 > RPM_MAX will be limited to RPM_MAX. +#define RPM_MIN 202.5 // Min RPM of model. $31 < RPM_MIN will be limited to RPM_MIN. +#define RPM_POINT12 6145.4 // Used N_PIECES >=2. Junction point between lines 1 and 2. +#define RPM_POINT23 9627.8 // Used N_PIECES >=3. Junction point between lines 2 and 3. +#define RPM_POINT34 10813.9 // Used N_PIECES = 4. Junction point between lines 3 and 4. +#define RPM_LINE_A1 3.197101e-03 // Used N_PIECES >=1. A and B constants of line 1. +#define RPM_LINE_B1 -3.526076e-1 +#define RPM_LINE_A2 1.722950e-2 // Used N_PIECES >=2. A and B constants of line 2. +#define RPM_LINE_B2 8.588176e+01 +#define RPM_LINE_A3 5.901518e-02 // Used N_PIECES >=3. A and B constants of line 3. +#define RPM_LINE_B3 4.881851e+02 +#define RPM_LINE_A4 1.203413e-01 // Used N_PIECES = 4. A and B constants of line 4. +#define RPM_LINE_B4 1.151360e+03 + + /* --------------------------------------------------------------------------------------- OEM Single File Configuration Option diff --git a/grbl/grbl.h b/grbl/grbl.h index 14eb778b..9652cc53 100644 --- a/grbl/grbl.h +++ b/grbl/grbl.h @@ -23,7 +23,7 @@ // Grbl versioning system #define GRBL_VERSION "1.1f" -#define GRBL_VERSION_BUILD "20170324" +#define GRBL_VERSION_BUILD "20170531" // Define standard libraries used by Grbl. #include diff --git a/grbl/spindle_control.c b/grbl/spindle_control.c index a66a8688..61cb90b8 100644 --- a/grbl/spindle_control.c +++ b/grbl/spindle_control.c @@ -2,7 +2,7 @@ spindle_control.c - spindle control methods Part of Grbl - Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC + Copyright (c) 2012-2017 Sungeun K. Jeon for Gnea Research LLC Copyright (c) 2009-2011 Simen Svale Skogsrud Grbl is free software: you can redistribute it and/or modify @@ -137,32 +137,79 @@ void spindle_stop() } - // Called by spindle_set_state() and step segment generator. Keep routine small and efficient. - uint8_t spindle_compute_pwm_value(float rpm) // 328p PWM register is 8-bit. - { - uint8_t pwm_value; - rpm *= (0.010*sys.spindle_speed_ovr); // Scale by spindle speed override value. - // Calculate PWM register value based on rpm max/min settings and programmed rpm. - if ((settings.rpm_min >= settings.rpm_max) || (rpm >= settings.rpm_max)) { - // No PWM range possible. Set simple on/off spindle control pin state. - sys.spindle_speed = settings.rpm_max; - pwm_value = SPINDLE_PWM_MAX_VALUE; - } else if (rpm <= settings.rpm_min) { - if (rpm == 0.0) { // S0 disables spindle - sys.spindle_speed = 0.0; - pwm_value = SPINDLE_PWM_OFF_VALUE; - } else { // Set minimum PWM output - sys.spindle_speed = settings.rpm_min; - pwm_value = SPINDLE_PWM_MIN_VALUE; + #ifdef ENABLE_PIECEWISE_LINEAR_SPINDLE + + // Called by spindle_set_state() and step segment generator. Keep routine small and efficient. + uint8_t spindle_compute_pwm_value(float rpm) // 328p PWM register is 8-bit. + { + uint8_t pwm_value; + rpm *= (0.010*sys.spindle_speed_ovr); // Scale by spindle speed override value. + // Calculate PWM register value based on rpm max/min settings and programmed rpm. + if ((settings.rpm_min >= settings.rpm_max) || (rpm >= RPM_MAX)) { + rpm = RPM_MAX; + pwm_value = SPINDLE_PWM_MAX_VALUE; + } else if (rpm <= RPM_MIN) { + if (rpm == 0.0) { // S0 disables spindle + pwm_value = SPINDLE_PWM_OFF_VALUE; + } else { + rpm = RPM_MIN; + pwm_value = SPINDLE_PWM_MIN_VALUE; + } + } else { + // Compute intermediate PWM value with linear spindle speed model via piecewise linear fit model. + #if (N_PIECES > 3) + if (rpm > RPM_POINT34) { + pwm_value = floor(RPM_LINE_A4*rpm - RPM_LINE_B4); + } else + #endif + #if (N_PIECES > 2) + if (rpm > RPM_POINT23) { + pwm_value = floor(RPM_LINE_A3*rpm - RPM_LINE_B3); + } else + #endif + #if (N_PIECES > 1) + if (rpm > RPM_POINT12) { + pwm_value = floor(RPM_LINE_A2*rpm - RPM_LINE_B2); + } else + #endif + { + pwm_value = floor(RPM_LINE_A1*rpm - RPM_LINE_B1); + } } - } else { - // Compute intermediate PWM value with linear spindle speed model. - // NOTE: A nonlinear model could be installed here, if required, but keep it VERY light-weight. sys.spindle_speed = rpm; - pwm_value = floor((rpm-settings.rpm_min)*pwm_gradient) + SPINDLE_PWM_MIN_VALUE; + return(pwm_value); } - return(pwm_value); - } + + #else + + // Called by spindle_set_state() and step segment generator. Keep routine small and efficient. + uint8_t spindle_compute_pwm_value(float rpm) // 328p PWM register is 8-bit. + { + uint8_t pwm_value; + rpm *= (0.010*sys.spindle_speed_ovr); // Scale by spindle speed override value. + // Calculate PWM register value based on rpm max/min settings and programmed rpm. + if ((settings.rpm_min >= settings.rpm_max) || (rpm >= settings.rpm_max)) { + // No PWM range possible. Set simple on/off spindle control pin state. + sys.spindle_speed = settings.rpm_max; + pwm_value = SPINDLE_PWM_MAX_VALUE; + } else if (rpm <= settings.rpm_min) { + if (rpm == 0.0) { // S0 disables spindle + sys.spindle_speed = 0.0; + pwm_value = SPINDLE_PWM_OFF_VALUE; + } else { // Set minimum PWM output + sys.spindle_speed = settings.rpm_min; + pwm_value = SPINDLE_PWM_MIN_VALUE; + } + } else { + // Compute intermediate PWM value with linear spindle speed model. + // NOTE: A nonlinear model could be installed here, if required, but keep it VERY light-weight. + sys.spindle_speed = rpm; + pwm_value = floor((rpm-settings.rpm_min)*pwm_gradient) + SPINDLE_PWM_MIN_VALUE; + } + return(pwm_value); + } + + #endif #endif