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.
This commit is contained in:
@@ -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
|
||||
|
||||
363
doc/script/fit_nonlinear_spindle.py
Normal file
363
doc/script/fit_nonlinear_spindle.py
Normal file
@@ -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_point1)&(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_point1)&(x>=PWM_min),
|
||||
(x<PWM_point2)&(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_point1)&(x>=PWM_min),
|
||||
(x<PWM_point2)&(x>=PWM_point1),
|
||||
(x<PWM_point3)&(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")
|
||||
@@ -11,6 +11,8 @@ response from the computer. This effectively adds another
|
||||
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 @@ TODO:
|
||||
---------------------
|
||||
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 re
|
||||
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 @@ args = parser.parse_args()
|
||||
|
||||
# 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 @@ s.write("\r\n\r\n")
|
||||
# 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 @@ else:
|
||||
# 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 @@ else:
|
||||
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 <Enter> to exit and disable grbl.")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 <avr/io.h>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user