add E axis

This commit is contained in:
Nikolay Khabarov
2017-05-27 18:47:01 +03:00
parent 665ba2ad1e
commit a87e2a379b
11 changed files with 238 additions and 147 deletions
+3
View File
@@ -6,6 +6,7 @@ STEPPER_MAX_ACCELERATION_MM_PER_S2 = 200 # mm per sec^2
STEPPER_PULSES_PER_MM_X = 400
STEPPER_PULSES_PER_MM_Y = 400
STEPPER_PULSES_PER_MM_Z = 400
STEPPER_PULSES_PER_MM_E = 80
TABLE_SIZE_X_MM = 200
TABLE_SIZE_Y_MM = 300
@@ -17,10 +18,12 @@ SPINDLE_MAX_RPM = 10000
STEPPER_STEP_PIN_X = 16
STEPPER_STEP_PIN_Y = 20
STEPPER_STEP_PIN_Z = 21
STEPPER_STEP_PIN_E = 25
STEPPER_DIR_PIN_X = 13
STEPPER_DIR_PIN_Y = 19
STEPPER_DIR_PIN_Z = 26
STEPPER_DIR_PIN_E = 8
SPINDLE_PWM_PIN = 7
+24 -15
View File
@@ -6,7 +6,7 @@ class Coordinates(object):
""" This object represent machine coordinates.
Machine supports 3 axis, so there are X, Y and Z.
"""
def __init__(self, x, y, z):
def __init__(self, x, y, z, e):
""" Create object.
:param x: x coordinated.
:param y: y coordinated.
@@ -15,16 +15,18 @@ class Coordinates(object):
self.x = round(x, 10)
self.y = round(y, 10)
self.z = round(z, 10)
self.e = round(e, 10)
def is_zero(self):
""" Check if all coordinates are zero.
:return: boolean value.
"""
return self.x == 0.0 and self.y == 0.0 and self.z == 0.0
return self.x == 0.0 and self.y == 0.0 and self.z == 0.0 and \
self.e == 0.0
def is_in_aabb(self, p1, p2):
""" Check coordinates are in aabb(Axis-Aligned Bounding Box).
aabb is specified with two points.
aabb is specified with two points. E is ignored.
:param p1: First point in Coord object.
:param p2: Second point in Coord object.
:return: boolean value.
@@ -42,46 +44,53 @@ class Coordinates(object):
""" Calculate the length of vector.
:return: Vector length.
"""
return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z
+ self.e * self.e)
def round(self, base_x, base_y, base_z):
def round(self, base_x, base_y, base_z, base_e):
""" Round values to specified base, ie 0.49 with base 0.25 will be 0.5.
:param base_x: Base for x axis.
:param base_y: Base for y axis.
:param base_z: Base for z axis.
:param base_e: Base for e axis.
:return: New rounded object.
"""
return Coordinates(round(self.x / base_x) * base_x,
round(self.y / base_y) * base_y,
round(self.z / base_z) * base_z)
round(self.z / base_z) * base_z,
round(self.e / base_e) * base_e)
def find_max(self):
""" Find a maximum value of all values.
:return: maximum value.
"""
return max(self.x, self.y, self.z)
return max(self.x, self.y, self.z, self.e)
# build in function implementation
def __add__(self, other):
return Coordinates(self.x + other.x, self.y + other.y, self.z + other.z)
return Coordinates(self.x + other.x, self.y + other.y,
self.z + other.z, self.e + other.e)
def __sub__(self, other):
return Coordinates(self.x - other.x, self.y - other.y, self.z - other.z)
return Coordinates(self.x - other.x, self.y - other.y,
self.z - other.z, self.e - other.e)
def __mul__(self, v):
return Coordinates(self.x * v, self.y * v, self.z * v)
return Coordinates(self.x * v, self.y * v, self.z * v, self.e * v)
def __div__(self, v):
return Coordinates(self.x / v, self.y / v, self.z / v)
return Coordinates(self.x / v, self.y / v, self.z / v, self.e / v)
def __truediv__(self, v):
return Coordinates(self.x / v, self.y / v, self.z / v)
return Coordinates(self.x / v, self.y / v, self.z / v, self.e / v)
def __eq__(self, other):
return self.x == other.x and self.y == other.y and self.z == other.z
return self.x == other.x and self.y == other.y and self.z == other.z \
and self.e == other.e
def __str__(self):
return '(' + str(self.x) + ', ' + str(self.y) + ', ' + str(self.z) + ')'
return '(' + str(self.x) + ', ' + str(self.y) + ', ' + str(self.z) \
+ ', ' + str(self.e) + ')'
def __abs__(self):
return Coordinates(abs(self.x), abs(self.y), abs(self.z))
return Coordinates(abs(self.x), abs(self.y), abs(self.z), abs(self.e))
+5 -3
View File
@@ -44,13 +44,15 @@ class GCode(object):
x = self.get('X', default.x, multiply)
y = self.get('Y', default.y, multiply)
z = self.get('Z', default.z, multiply)
return Coordinates(x, y, z)
e = self.get('E', default.e, multiply)
return Coordinates(x, y, z, e)
def has_coordinates(self):
""" Check if at least one of the coordinates is present.
:return: Boolean value.
"""
return 'X' in self.params or 'Y' in self.params or 'Z' in self.params
return 'X' in self.params or 'Y' in self.params or 'Z' in self.params \
or 'E' in self.params
def radius(self, default, multiply):
""" Get radius for circular interpolation(I, J, K or R).
@@ -61,7 +63,7 @@ class GCode(object):
i = self.get('I', default.x, multiply)
j = self.get('J', default.y, multiply)
k = self.get('K', default.z, multiply)
return Coordinates(i, j, k)
return Coordinates(i, j, k, 0)
def command(self):
""" Get value from gcode line.
+20 -16
View File
@@ -22,7 +22,7 @@ class GMachine(object):
def __init__(self):
""" Initialization.
"""
self._position = Coordinates(0.0, 0.0, 0.0)
self._position = Coordinates(0.0, 0.0, 0.0, 0.0)
# init variables
self._velocity = 0
self._spindle_rpm = 0
@@ -48,11 +48,11 @@ class GMachine(object):
self._velocity = 1000
self._spindle_rpm = 1000
self._pause = 0
self._local = Coordinates(0.0, 0.0, 0.0)
self._local = Coordinates(0.0, 0.0, 0.0, 0.0)
self._convertCoordinates = 1.0
self._absoluteCoordinates = True
self._plane = PLANE_XY
self._radius = Coordinates(0.0, 0.0, 0.0)
self._radius = Coordinates(0.0, 0.0, 0.0, 0.0)
def _spindle(self, spindle_speed):
hal.join()
@@ -60,14 +60,15 @@ class GMachine(object):
def __check_delta(self, delta):
pos = self._position + delta
if not pos.is_in_aabb(Coordinates(0.0, 0.0, 0.0),
Coordinates(TABLE_SIZE_X_MM, TABLE_SIZE_Y_MM, TABLE_SIZE_Z_MM)):
if not pos.is_in_aabb(Coordinates(0.0, 0.0, 0.0, 0.0),
Coordinates(TABLE_SIZE_X_MM, TABLE_SIZE_Y_MM, TABLE_SIZE_Z_MM, 0)):
raise GMachineException("out of effective area")
def _move_linear(self, delta, velocity):
delta = delta.round(1.0 / STEPPER_PULSES_PER_MM_X,
1.0 / STEPPER_PULSES_PER_MM_Y,
1.0 / STEPPER_PULSES_PER_MM_Z)
1.0 / STEPPER_PULSES_PER_MM_Z,
1.0 / STEPPER_PULSES_PER_MM_E)
if delta.is_zero():
return
self.__check_delta(delta)
@@ -130,10 +131,11 @@ class GMachine(object):
def _circular(self, delta, radius, velocity, direction):
delta = delta.round(1.0 / STEPPER_PULSES_PER_MM_X,
1.0 / STEPPER_PULSES_PER_MM_Y,
1.0 / STEPPER_PULSES_PER_MM_Z)
1.0 / STEPPER_PULSES_PER_MM_Z,
1.0 / STEPPER_PULSES_PER_MM_E)
self.__check_delta(delta)
# get delta vector and put it on circle
circle_end = Coordinates(0,0,0)
circle_end = Coordinates(0, 0, 0, 0)
if self._plane == PLANE_XY:
circle_end.x, circle_end.y = self.__adjust_circle(delta.x, delta.y,
radius.x, radius.y, direction,
@@ -152,9 +154,11 @@ class GMachine(object):
self._position.z, self._position.x,
TABLE_SIZE_Z_MM, TABLE_SIZE_X_MM)
circle_end.y = delta.y
circle_end.e = delta.e
circle_end = circle_end.round(1.0 / STEPPER_PULSES_PER_MM_X,
1.0 / STEPPER_PULSES_PER_MM_Y,
1.0 / STEPPER_PULSES_PER_MM_Z)
1.0 / STEPPER_PULSES_PER_MM_Z,
1.0 / STEPPER_PULSES_PER_MM_E)
hal.move_circular(circle_end, radius, self._plane, velocity, direction)
# if finish coords is not on circle, move some distance linearly
linear_delta = delta - circle_end
@@ -168,9 +172,9 @@ class GMachine(object):
def home(self):
""" Move head to park position
"""
d = Coordinates(0, 0, -self._position.z)
d = Coordinates(0, 0, -self._position.z, 0)
self._move_linear(d, STEPPER_MAX_VELOCITY_MM_PER_MIN)
d = Coordinates(-self._position.x, -self._position.y, 0)
d = Coordinates(-self._position.x, -self._position.y, 0, 0)
self._move_linear(d, STEPPER_MAX_VELOCITY_MM_PER_MIN)
def position(self):
@@ -207,7 +211,8 @@ class GMachine(object):
coord = coord + self._local
delta = coord - self._position
else:
delta = gcode.coordinates(Coordinates(0.0, 0.0, 0.0), self._convertCoordinates)
delta = gcode.coordinates(Coordinates(0.0, 0.0, 0.0, 0.0),
self._convertCoordinates)
coord = self._position + delta
velocity = gcode.get('F', self._velocity)
spindle_rpm = gcode.get('S', self._spindle_rpm)
@@ -245,14 +250,14 @@ class GMachine(object):
elif c == 'G28': # home
self.home()
elif c == 'G53': # switch to machine coords
self._local = Coordinates(0.0, 0.0, 0.0)
self._local = Coordinates(0.0, 0.0, 0.0, 0.0)
elif c == 'G90': # switch to absolute coords
self._absoluteCoordinates = True
elif c == 'G91': # switch to relative coords
self._absoluteCoordinates = False
elif c == 'G92': # switch to local coords
self._local = self._position - \
gcode.coordinates(Coordinates(0.0, 0.0, 0.0),
gcode.coordinates(Coordinates(0.0, 0.0, 0.0, 0.0),
self._convertCoordinates)
elif c == 'M3': # spinle on
self._spindle(spindle_rpm)
@@ -271,5 +276,4 @@ class GMachine(object):
self._spindle_rpm = spindle_rpm
self._pause = pause
self._radius = radius
logging.debug("position {}, {}, {}".format(
self._position.x, self._position.y, self._position.z))
logging.debug("position {}".format(self._position))
+11 -2
View File
@@ -27,6 +27,7 @@ pwm = rpgpio.DMAPWM()
STEP_PIN_MASK_X = 1 << STEPPER_STEP_PIN_X
STEP_PIN_MASK_Y = 1 << STEPPER_STEP_PIN_Y
STEP_PIN_MASK_Z = 1 << STEPPER_STEP_PIN_Z
STEP_PIN_MASK_E = 1 << STEPPER_STEP_PIN_E
def init():
""" Initialize GPIO pins and machine itself, including callibration if
@@ -35,9 +36,11 @@ def init():
gpio.init(STEPPER_STEP_PIN_X, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(STEPPER_STEP_PIN_Y, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(STEPPER_STEP_PIN_Z, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(STEPPER_STEP_PIN_E, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(STEPPER_DIR_PIN_X, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(STEPPER_DIR_PIN_Y, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(STEPPER_DIR_PIN_Z, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(STEPPER_DIR_PIN_E, rpgpio.GPIO.MODE_OUTPUT)
gpio.init(ENDSTOP_PIN_X, rpgpio.GPIO.MODE_INPUT_PULLUP)
gpio.init(ENDSTOP_PIN_X, rpgpio.GPIO.MODE_INPUT_PULLUP)
gpio.init(ENDSTOP_PIN_X, rpgpio.GPIO.MODE_INPUT_PULLUP)
@@ -130,15 +133,19 @@ def move_linear(delta, velocity):
gpio.clear(STEPPER_DIR_PIN_Z)
else:
gpio.set(STEPPER_DIR_PIN_Z)
if delta.e > 0.0:
gpio.clear(STEPPER_DIR_PIN_E)
else:
gpio.set(STEPPER_DIR_PIN_E)
# prepare and run dma
dma.clear()
prev = 0
is_ran = False
st = time.time()
for tx, ty, tz in generator:
for tx, ty, tz, te in generator:
pins = 0
k = int(round(min(x for x in (tx, ty, tz) if x is not None)
k = int(round(min(x for x in (tx, ty, tz, te) if x is not None)
* US_IN_SECONDS))
if tx is not None:
pins |= STEP_PIN_MASK_X
@@ -146,6 +153,8 @@ def move_linear(delta, velocity):
pins |= STEP_PIN_MASK_Y
if tz is not None:
pins |= STEP_PIN_MASK_Z
if te is not None:
pins |= STEP_PIN_MASK_E
if k - prev > 0:
dma.add_delay(k - prev)
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
+21 -9
View File
@@ -31,13 +31,13 @@ def move_linear(delta, velocity):
:param velocity: velocity in mm per min
"""
logging.info("move {} with velocity {}".format(delta, velocity))
ix = iy = iz = 0
ix = iy = iz = ie = 0
generator = PulseGeneratorLinear(delta, velocity)
lx, ly, lz = None, None, None
dx, dy, dz = 0, 0, 0
mx, my, mz = 0, 0, 0
lx, ly, lz, le = None, None, None, None
dx, dy, dz, de = 0, 0, 0, 0
mx, my, mz, me = 0, 0, 0, 0
st = time.time()
for tx, ty, tz in generator:
for tx, ty, tz, te in generator:
if tx is not None:
if tx > mx:
mx = tx
@@ -71,16 +71,28 @@ def move_linear(delta, velocity):
lz = tz
else:
dz = None
if te is not None:
if te > me:
me = te
te = int(round(te * 1000000))
ie += 1
if le is not None:
de = te - le
assert de > 0, "negative or zero time delta detected for e"
le = te
else:
de = None
# very verbose, uncomment on demand
# logging.debug("Iteration {} is {} {} {}".format(max(ix, iy, iz), tx, ty, tz))
f = list(x for x in (tx, ty, tz) if x is not None)
# logging.debug("Iteration {} is {} {} {} {}".format(max(ix, iy, iz, ie), tx, ty, tz, te))
f = list(x for x in (tx, ty, tz, te) if x is not None)
assert f.count(f[0]) == len(f), "fast forwarded pulse detected"
pt = time.time()
assert ix / STEPPER_PULSES_PER_MM_X == abs(delta.x), "x wrong number of pulses"
assert iy / STEPPER_PULSES_PER_MM_Y == abs(delta.y), "y wrong number of pulses"
assert iz / STEPPER_PULSES_PER_MM_Z == abs(delta.z), "z wrong number of pulses"
assert max(mx, my, mz) <= generator.total_time_s(), "interpolation time or pulses wrong"
logging.debug("Did {}, {}, {} iterations".format(ix, iy, iz))
assert ie / STEPPER_PULSES_PER_MM_E == abs(delta.e), "e wrong number of pulses"
assert max(mx, my, mz, me) <= generator.total_time_s(), "interpolation time or pulses wrong"
logging.debug("Did {}, {}, {}, {} iterations".format(ix, iy, iz, ie))
logging.info("prepared in " + str(round(pt - st, 2)) \
+ "s, estimated " + str(round(generator.total_time_s(), 2)) + "s")
+29 -15
View File
@@ -41,6 +41,7 @@ class PulseGenerator(object):
self._iteration_x = 0
self._iteration_y = 0
self._iteration_z = 0
self._iteration_e = 0
self._acceleration_time_s = 0.0
self._linear_time_s = 0.0
self._2Vmax_per_a = 0.0
@@ -60,12 +61,15 @@ class PulseGenerator(object):
"""
raise NotImplemented
def _interpolation_function(self, pulse_number):
def _interpolation_function(self, ix, iy, iz, ie):
""" Get function for interpolation path. This function should returned
values as it is uniform movement. There is only one trick, function
must be expressed in terms of position, i.e. t = S / V for linear,
where S - distance would be increment on motor minimum step.
:param pulse_number: number of pulse.
:param ix: number of pulse for X axis.
:param iy: number of pulse for X axis.
:param iz: number of pulse for X axis.
:param ie: number of pulse for X axis.
:return: time for each axis or None if movement for axis is finished.
"""
raise NotImplemented
@@ -82,6 +86,7 @@ class PulseGenerator(object):
self._iteration_x = 0
self._iteration_y = 0
self._iteration_z = 0
self._iteration_e = 0
logging.debug(', '.join("%s: %s" % i for i in vars(self).items()))
return self
@@ -123,15 +128,16 @@ class PulseGenerator(object):
the next pulse. If there is no pulses left None will be
returned.
"""
tx, ty, tz = self._interpolation_function(self._iteration_x,
self._iteration_y,
self._iteration_z)
tx, ty, tz, te = self._interpolation_function(self._iteration_x,
self._iteration_y,
self._iteration_z,
self._iteration_e)
# check condition to stop
if tx is None and ty is None and tz is None:
if tx is None and ty is None and tz is None and te is None:
raise StopIteration
# convert to real time
m = min(x for x in (tx, ty, tz) if x is not None)
m = min(x for x in (tx, ty, tz, te) if x is not None)
am = self._to_accelerated_time(m)
# sort pulses in time
if tx is not None:
@@ -152,8 +158,14 @@ class PulseGenerator(object):
else:
tz = am
self._iteration_z += 1
if te is not None:
if te > m:
te = None
else:
te = am
self._iteration_e += 1
return tx, ty, tz
return tx, ty, tz, te
def total_time_s(self):
""" Get total time for movement.
@@ -173,24 +185,24 @@ class PulseGeneratorLinear(PulseGenerator):
# this class doesn't care about direction
self._distance_mm = abs(delta_mm)
# velocity of each axis
distance_xyz_mm = self._distance_mm.length()
distance_total_mm = self._distance_mm.length()
self.max_velocity_mm_per_sec = self._distance_mm * (
velocity_mm_per_min / SECONDS_IN_MINUTE / distance_xyz_mm)
velocity_mm_per_min / SECONDS_IN_MINUTE / distance_total_mm)
# acceleration time
self.acceleration_time_s = self.max_velocity_mm_per_sec.find_max() \
/ STEPPER_MAX_ACCELERATION_MM_PER_S2
# check if there is enough space to accelerate and brake, adjust time
# S = a * t^2 / 2
if STEPPER_MAX_ACCELERATION_MM_PER_S2 * self.acceleration_time_s ** 2 \
> distance_xyz_mm:
self.acceleration_time_s = math.sqrt(distance_xyz_mm /
> distance_total_mm:
self.acceleration_time_s = math.sqrt(distance_total_mm /
STEPPER_MAX_ACCELERATION_MM_PER_S2)
self.linear_time_s = 0.0
# V = a * t -> V = 2 * S / t, take half of total distance for acceleration and braking
self.max_velocity_mm_per_sec = self._distance_mm / self.acceleration_time_s
else:
# calculate linear time
linear_distance_mm = distance_xyz_mm\
linear_distance_mm = distance_total_mm\
- self.acceleration_time_s ** 2 \
* STEPPER_MAX_ACCELERATION_MM_PER_S2
self.linear_time_s = linear_distance_mm \
@@ -212,7 +224,7 @@ class PulseGeneratorLinear(PulseGenerator):
# Linear movement, S = V * t -> t = S / V
return position_mm / velocity_mm_per_sec
def _interpolation_function(self, ix, iy, iz):
def _interpolation_function(self, ix, iy, iz, ie):
""" Calculate interpolation values for linear movement, see super class
for details.
"""
@@ -222,4 +234,6 @@ class PulseGeneratorLinear(PulseGenerator):
self.max_velocity_mm_per_sec.y)
t_z = self.__linear(iz / STEPPER_PULSES_PER_MM_Z, self._distance_mm.z,
self.max_velocity_mm_per_sec.z)
return t_x, t_y, t_z
t_e = self.__linear(ie / STEPPER_PULSES_PER_MM_E, self._distance_mm.e,
self.max_velocity_mm_per_sec.e)
return t_x, t_y, t_z, t_e