add direction control in pulsegenerator

This commit is contained in:
Nikolay Khabarov
2017-05-28 02:25:19 +03:00
parent a87e2a379b
commit ff93f526a4
5 changed files with 145 additions and 52 deletions
+23 -20
View File
@@ -31,7 +31,7 @@ STEP_PIN_MASK_E = 1 << STEPPER_STEP_PIN_E
def init(): def init():
""" Initialize GPIO pins and machine itself, including callibration if """ Initialize GPIO pins and machine itself, including callibration if
needed. Do not return till all procedure is completed. needed. Do not return till all procedures are completed.
""" """
gpio.init(STEPPER_STEP_PIN_X, rpgpio.GPIO.MODE_OUTPUT) 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_Y, rpgpio.GPIO.MODE_OUTPUT)
@@ -120,30 +120,33 @@ def move_linear(delta, velocity):
while dma.is_active(): while dma.is_active():
time.sleep(0.001) time.sleep(0.001)
# set direction pins
if delta.x > 0.0:
gpio.clear(STEPPER_DIR_PIN_X)
else:
gpio.set(STEPPER_DIR_PIN_X)
if delta.y > 0.0:
gpio.clear(STEPPER_DIR_PIN_Y)
else:
gpio.set(STEPPER_DIR_PIN_Y)
if delta.z > 0.0:
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 # prepare and run dma
dma.clear() dma.clear()
prev = 0 prev = 0
is_ran = False is_ran = False
st = time.time() st = time.time()
for tx, ty, tz, te in generator: for dir, tx, ty, tz, te in generator:
if dir: # set up directions
pins_to_set = 0
pins_to_clear = 0
if tx > 0:
pins_to_clear |= 1 << STEPPER_DIR_PIN_X
elif tx < 0:
pins_to_set |= 1 << STEPPER_DIR_PIN_X
if ty > 0:
pins_to_clear |= 1 << STEPPER_DIR_PIN_Y
elif ty < 0:
pins_to_set |= 1 << STEPPER_DIR_PIN_Y
if tz > 0:
pins_to_clear |= 1 << STEPPER_DIR_PIN_Z
elif tz < 0:
pins_to_set |= 1 << STEPPER_DIR_PIN_Z
if te > 0:
pins_to_clear |= 1 << STEPPER_DIR_PIN_E
elif te < 0:
pins_to_set |= 1 << STEPPER_DIR_PIN_E
dma.add_set_clear(pins_to_set, pins_to_clear)
continue
pins = 0 pins = 0
k = int(round(min(x for x in (tx, ty, tz, te) 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)) * US_IN_SECONDS))
+18
View File
@@ -166,6 +166,24 @@ class DMAGPIO(DMAProto):
self._physmem.write(self.__current_address, "8I", data) self._physmem.write(self.__current_address, "8I", data)
self.__current_address = next_cb self.__current_address = next_cb
def add_set_clear(self, pins_to_set, pins_to_clear):
""" Change state of gpio pins.
:param pins_to_set: bitwise mask which pins should be set.
:param pins_to_clear: bitwise mask which pins should be clear.
"""
next_cb = self.__current_address + self._DMA_CONTROL_BLOCK_SIZE
if next_cb > self._physmem.get_size():
raise MemoryError("Out of allocated memory.")
next1 = self._physmem.get_bus_address() + next_cb
source = next1 - 8 # last 8 bytes are padding, use it to store data
data = (
self._pulse_info, source, self._pulse_destination,
self._pulse_length,
self._pulse_stride, next1, pins_to_set, pins_to_clear
)
self._physmem.write(self.__current_address, "8I", data)
self.__current_address = next_cb
def finalize_stream(self): def finalize_stream(self):
""" Mark last added block as the last one. """ Mark last added block as the last one.
""" """
+14 -1
View File
@@ -37,7 +37,19 @@ def move_linear(delta, velocity):
dx, dy, dz, de = 0, 0, 0, 0 dx, dy, dz, de = 0, 0, 0, 0
mx, my, mz, me = 0, 0, 0, 0 mx, my, mz, me = 0, 0, 0, 0
st = time.time() st = time.time()
for tx, ty, tz, te in generator: direction_found = False
for dir, tx, ty, tz, te in generator:
if dir:
direction_found = True
assert (tx < 0 and delta.x < 0) or (tx > 0 and delta.x > 0) \
or delta.x == 0
assert (ty < 0 and delta.y < 0) or (ty > 0 and delta.y > 0) \
or delta.y == 0
assert (tz < 0 and delta.z < 0) or (tz > 0 and delta.z > 0) \
or delta.z == 0
assert (te < 0 and delta.e < 0) or (te > 0 and delta.e > 0) \
or delta.e == 0
continue
if tx is not None: if tx is not None:
if tx > mx: if tx > mx:
mx = tx mx = tx
@@ -87,6 +99,7 @@ def move_linear(delta, velocity):
f = list(x for x in (tx, ty, tz, te) if x is not None) 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" assert f.count(f[0]) == len(f), "fast forwarded pulse detected"
pt = time.time() pt = time.time()
assert direction_found, "direction not found"
assert ix / STEPPER_PULSES_PER_MM_X == abs(delta.x), "x wrong number of pulses" 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 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 iz / STEPPER_PULSES_PER_MM_Z == abs(delta.z), "z wrong number of pulses"
+42 -17
View File
@@ -42,6 +42,7 @@ class PulseGenerator(object):
self._iteration_y = 0 self._iteration_y = 0
self._iteration_z = 0 self._iteration_z = 0
self._iteration_e = 0 self._iteration_e = 0
self._iteration_direction = None
self._acceleration_time_s = 0.0 self._acceleration_time_s = 0.0
self._linear_time_s = 0.0 self._linear_time_s = 0.0
self._2Vmax_per_a = 0.0 self._2Vmax_per_a = 0.0
@@ -54,10 +55,11 @@ class PulseGenerator(object):
during movement during movement
linear_time_s: time for uniform movement, it is total movement linear_time_s: time for uniform movement, it is total movement
time minus acceleration and braking time time minus acceleration and braking time
max_axis_velocity_mm_per_sec: maximum velocity of any of axis during max_axis_velocity_mm_per_sec: maximum velocity of any of axis
movement. Even if whole movement is during movement. Even if whole
accelerated, this value should be movement is accelerated, this
calculated as top velocity. value should be calculated as top
velocity.
""" """
raise NotImplemented raise NotImplemented
@@ -67,10 +69,13 @@ class PulseGenerator(object):
must be expressed in terms of position, i.e. t = S / V for linear, must be expressed in terms of position, i.e. t = S / V for linear,
where S - distance would be increment on motor minimum step. where S - distance would be increment on motor minimum step.
:param ix: number of pulse for X axis. :param ix: number of pulse for X axis.
:param iy: number of pulse for X axis. :param iy: number of pulse for Y axis.
:param iz: number of pulse for X axis. :param iz: number of pulse for Z axis.
:param ie: number of pulse for X axis. :param ie: number of pulse for E axis.
:return: time for each axis or None if movement for axis is finished. :return: Two tuples. First is tuple is directions for each axis,
positive means forward, negative means reverse. Second is
tuple of times for each axis in us or None if movement for
axis is finished.
""" """
raise NotImplemented raise NotImplemented
@@ -87,6 +92,7 @@ class PulseGenerator(object):
self._iteration_y = 0 self._iteration_y = 0
self._iteration_z = 0 self._iteration_z = 0
self._iteration_e = 0 self._iteration_e = 0
self._iteration_direction = None
logging.debug(', '.join("%s: %s" % i for i in vars(self).items())) logging.debug(', '.join("%s: %s" % i for i in vars(self).items()))
return self return self
@@ -116,7 +122,8 @@ class PulseGenerator(object):
# V on start braking is Vlinear = Taccel * a = Tbreaking * a # V on start braking is Vlinear = Taccel * a = Tbreaking * a
# Vmax * Tpseudo = Tbreaking * a * t - a * t^2 / 2 # Vmax * Tpseudo = Tbreaking * a * t - a * t^2 / 2
return 2.0 * self._acceleration_time_s + self._linear_time_s \ return 2.0 * self._acceleration_time_s + self._linear_time_s \
- math.sqrt(self._acceleration_time_s ** 2 - self._2Vmax_per_a * bt) - math.sqrt(self._acceleration_time_s ** 2
- self._2Vmax_per_a * bt)
def __next__(self): def __next__(self):
# for python3 # for python3
@@ -124,14 +131,26 @@ class PulseGenerator(object):
def next(self): def next(self):
""" Iterate pulses. """ Iterate pulses.
:return: Tuple of three values for each axis which represent time for :return: Tuple of five values:
the next pulse. If there is no pulses left None will be - first is boolean value, if it is True, motors direction
returned. should be changed and next pulse should performed in
this direction.
- values for all machine axises. For direction update,
positive values means forward movement, negative value
means reverse movement. For normal pulse, values are
represent time for the next pulse in microseconds.
This iteration strictly guarantees that next pulses time will
not be earlier in time then current. If there is no pulses
left StopIteration will be raised.
""" """
tx, ty, tz, te = self._interpolation_function(self._iteration_x, dir, (tx, ty, tz, te) = self._interpolation_function(self._iteration_x,
self._iteration_y, self._iteration_y,
self._iteration_z, self._iteration_z,
self._iteration_e) self._iteration_e)
# check if direction update:
if dir != self._iteration_direction:
self._iteration_direction = dir
return (True,) + dir
# check condition to stop # check condition to stop
if tx is None and ty is None and tz is None and te is None: if tx is None and ty is None and tz is None and te is None:
raise StopIteration raise StopIteration
@@ -165,7 +184,7 @@ class PulseGenerator(object):
te = am te = am
self._iteration_e += 1 self._iteration_e += 1
return tx, ty, tz, te return False, tx, ty, tz, te
def total_time_s(self): def total_time_s(self):
""" Get total time for movement. """ Get total time for movement.
@@ -198,8 +217,10 @@ class PulseGeneratorLinear(PulseGenerator):
self.acceleration_time_s = math.sqrt(distance_total_mm / self.acceleration_time_s = math.sqrt(distance_total_mm /
STEPPER_MAX_ACCELERATION_MM_PER_S2) STEPPER_MAX_ACCELERATION_MM_PER_S2)
self.linear_time_s = 0.0 self.linear_time_s = 0.0
# V = a * t -> V = 2 * S / t, take half of total distance for acceleration and braking # V = a * t -> V = 2 * S / t, take half of total distance for
self.max_velocity_mm_per_sec = self._distance_mm / self.acceleration_time_s # acceleration and braking
self.max_velocity_mm_per_sec = self._distance_mm \
/ self.acceleration_time_s
else: else:
# calculate linear time # calculate linear time
linear_distance_mm = distance_total_mm \ linear_distance_mm = distance_total_mm \
@@ -207,6 +228,10 @@ class PulseGeneratorLinear(PulseGenerator):
* STEPPER_MAX_ACCELERATION_MM_PER_S2 * STEPPER_MAX_ACCELERATION_MM_PER_S2
self.linear_time_s = linear_distance_mm \ self.linear_time_s = linear_distance_mm \
/ self.max_velocity_mm_per_sec.length() / self.max_velocity_mm_per_sec.length()
self._direction = math.copysign(1, delta_mm.x), \
math.copysign(1, delta_mm.y), \
math.copysign(1, delta_mm.z), \
math.copysign(1, delta_mm.e) \
def _get_movement_parameters(self): def _get_movement_parameters(self):
""" Return movement parameters, see super class for details. """ Return movement parameters, see super class for details.
@@ -236,4 +261,4 @@ class PulseGeneratorLinear(PulseGenerator):
self.max_velocity_mm_per_sec.z) self.max_velocity_mm_per_sec.z)
t_e = self.__linear(ie / STEPPER_PULSES_PER_MM_E, self._distance_mm.e, t_e = self.__linear(ie / STEPPER_PULSES_PER_MM_E, self._distance_mm.e,
self.max_velocity_mm_per_sec.e) self.max_velocity_mm_per_sec.e)
return t_x, t_y, t_z, t_e return self._direction, (t_x, t_y, t_z, t_e)
+39 -5
View File
@@ -26,7 +26,9 @@ class TestPulses(unittest.TestCase):
0, 0, 0), 0, 0, 0),
self.v) self.v)
i = 0 i = 0
for px, py, pz, pe in g: for dir, px, py, pz, pe in g:
if dir:
continue
i += 1 i += 1
self.assertEqual(px, 0) self.assertEqual(px, 0)
self.assertEqual(py, None) self.assertEqual(py, None)
@@ -40,7 +42,9 @@ class TestPulses(unittest.TestCase):
1.0 / STEPPER_PULSES_PER_MM_E), 1.0 / STEPPER_PULSES_PER_MM_E),
self.v) self.v)
i = 0 i = 0
for px, py, pz, pe in g: for dir, px, py, pz, pe in g:
if dir:
continue
i += 1 i += 1
self.assertEqual(px, 0) self.assertEqual(px, 0)
self.assertEqual(py, 0) self.assertEqual(py, 0)
@@ -65,7 +69,9 @@ class TestPulses(unittest.TestCase):
m = Coordinates(2, 4, 0, 0) m = Coordinates(2, 4, 0, 0)
g = PulseGeneratorLinear(m, self.v) g = PulseGeneratorLinear(m, self.v)
i = 0 i = 0
for px, py, pz, pe in g: for dir, px, py, pz, pe in g:
if dir:
continue
if i % 2 == 0: if i % 2 == 0:
self.assertNotEqual(px, None) self.assertNotEqual(px, None)
else: else:
@@ -86,7 +92,9 @@ class TestPulses(unittest.TestCase):
iz = 0 iz = 0
ie = 0 ie = 0
t = -1 t = -1
for px, py, pz, pe in g: for dir, px, py, pz, pe in g:
if dir:
continue
if px is not None: if px is not None:
ix += 1 ix += 1
if py is not None: if py is not None:
@@ -112,7 +120,9 @@ class TestPulses(unittest.TestCase):
g = PulseGeneratorLinear(m, self.v) g = PulseGeneratorLinear(m, self.v)
i = 0 i = 0
lx = 0 lx = 0
for px, py, pz, pe in g: for dir, px, py, pz, pe in g:
if dir:
continue
if i == 2: if i == 2:
at = px - lx at = px - lx
if i == TABLE_SIZE_X_MM * STEPPER_PULSES_PER_MM_X / 2: if i == TABLE_SIZE_X_MM * STEPPER_PULSES_PER_MM_X / 2:
@@ -124,5 +134,29 @@ class TestPulses(unittest.TestCase):
self.assertGreater(at, lt) self.assertGreater(at, lt)
self.assertGreater(bt, lt) self.assertGreater(bt, lt)
def test_directions(self):
# Check if directions are set up correctly.
m = Coordinates(1, -2, 3, -4)
g = PulseGeneratorLinear(m, self.v)
dir_found = False
for dir, px, py, pz, pe in g:
if dir:
# should be once
self.assertFalse(dir_found)
dir_found = True
# check dirs
self.assertTrue(px > 0 and py < 0 and pz > 0 and pe < 0)
m = Coordinates(-1, 2, -3, 4)
g = PulseGeneratorLinear(m, self.v)
dir_found = False
for dir, px, py, pz, pe in g:
if dir:
# should be once
self.assertFalse(dir_found)
dir_found = True
# check dirs
self.assertTrue(px < 0 and py > 0 and pz < 0 and pe > 0)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()