refactoring

This commit is contained in:
Nikolay Khabarov
2017-06-12 00:52:11 +03:00
parent 0fe98f4cca
commit 98bf7e914e
23 changed files with 448 additions and 405 deletions

11
.idea/codeStyleSettings.xml generated Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value>
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
</value>
</option>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</component>
</project>

View File

@@ -1,6 +1,9 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyChainedComparisonsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoreConstantInTheMiddle" value="true" />
</inspection_tool>
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ourVersions">
<value>
@@ -11,5 +14,14 @@
</value>
</option>
</inspection_tool>
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="pypandoc" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>

View File

@@ -30,11 +30,13 @@ perfect choice for easy development of this project.
Video demo - [YouTube video](https://youtu.be/vcedo59raS4)
# Current command support
G0, G1, G2, G3, G4, G17, G18, G19, G20, G21, G28, G53, G90, G91, G92, M2, M3,
M5, M30
Commands can be easily added, see [gmachine.py](./cnc/gmachine.py) file.
Four axis are supported - X, Y, Z, E
# Current gcode support
Commands G0, G1, G2, G3, G4, G17, G18, G19, G20, G21, G28, G53, G90, G91, G92, M2, M3,
M5, M30 are supported. Commands can be easily added, see
[gmachine.py](./cnc/gmachine.py) file.
Four axis are supported - X, Y, Z, E.
Spindle with rpm control is supported.
Circular interpolation for XY, ZX, YZ planes is supported.
# Config
All configs are stored in [config.py](./cnc/config.py) and contain hardware
@@ -47,8 +49,8 @@ So having Raspberry Pi connected this way, there is no need to configure
pin map for project.
# Hardware
Currently, this project supports Raspberry Pi 1-3. Tested with RPI2 and RPI3.
But there is a way to add new boards. See [hal.py](./cnc/hal.py) file.
Currently, this project supports Raspberry Pi 1-3. Developed and tested with
RPI3. And there is a way to add new boards. See [hal.py](./cnc/hal.py) file.
_Note: Current Raspberry Pi implementation uses the same resources as on board
3.5 mm jack(PWM module), so do not use it. HDMI audio works._
@@ -67,7 +69,7 @@ sudo pip remove pycnc
```
# Performance notice
Pure Python interpreter wouldn't provide great performance for high speed
Pure Python interpreter would not provide great performance for high speed
machines. Overspeeding setting causes motors mispulses and probably lose of
trajectory. According to my tests, Raspberry Pi 2 can handle axises with 400
pulses on mm with top velocity ~800 mm per min. There is always way out! :)

View File

@@ -1,5 +1,5 @@
# Hardware limitations config
STEPPER_PULSE_LINGTH_US = 2
STEPPER_PULSE_LENGTH_US = 2
STEPPER_MAX_VELOCITY_MM_PER_MIN = 1800 # mm per min
STEPPER_MAX_ACCELERATION_MM_PER_S2 = 200 # mm per sec^2

View File

@@ -21,8 +21,8 @@ class Coordinates(object):
""" Check if all coordinates are zero.
:return: boolean value.
"""
return self.x == 0.0 and self.y == 0.0 and self.z == 0.0 and \
self.e == 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).
@@ -31,12 +31,12 @@ class Coordinates(object):
:param p2: Second point in Coord object.
:return: boolean value.
"""
minx, maxx = sorted((p1.x, p2.x))
miny, maxy = sorted((p1.y, p2.y))
minz, maxz = sorted((p1.z, p2.z))
if self.x < minx or self.y < miny or self.z < minz:
min_x, max_x = sorted((p1.x, p2.x))
min_y, max_y = sorted((p1.y, p2.y))
min_z, max_z = sorted((p1.z, p2.z))
if self.x < min_x or self.y < min_y or self.z < min_z:
return False
if self.x > maxx or self.y > maxy or self.z > maxz:
if self.x > max_x or self.y > max_y or self.z > max_z:
return False
return True
@@ -76,12 +76,21 @@ class Coordinates(object):
self.z - other.z, self.e - other.e)
def __mul__(self, v):
"""
@rtype: Coordinates
"""
return Coordinates(self.x * v, self.y * v, self.z * v, self.e * v)
def __div__(self, v):
"""
@rtype: Coordinates
"""
return Coordinates(self.x / v, self.y / v, self.z / v, self.e / v)
def __truediv__(self, v):
"""
@rtype: Coordinates
"""
return Coordinates(self.x / v, self.y / v, self.z / v, self.e / v)
def __eq__(self, other):

View File

@@ -1,11 +1,11 @@
import re
import math
from cnc.coordinates import Coordinates
from cnc.enums import Plane
gpattern = re.compile('([A-Z])([-+]?[0-9.]+)')
cleanpattern = re.compile('\s+|\(.*?\)|;.*') # white spaces and comments start with ';' and in '()'
# extract letter-digit pairs
g_pattern = re.compile('([A-Z])([-+]?[0-9.]+)')
# white spaces and comments start with ';' and in '()'
clean_pattern = re.compile('\s+|\(.*?\)|;.*')
class GCodeException(Exception):
@@ -24,20 +24,20 @@ class GCode(object):
"""
self.params = params
def get(self, argname, default=None, multiply=1.0):
def get(self, arg_name, default=None, multiply=1.0):
""" Get value from gcode line.
:param argname: Value name.
:param arg_name: Value name.
:param default: Default value if value doesn't exist.
:param multiply: if value exist, multiply it by this value.
:return: Value if exists or default otherwise.
"""
if argname not in self.params:
if arg_name not in self.params:
return default
return float(self.params[argname]) * multiply
return float(self.params[arg_name]) * multiply
def coordinates(self, default, multiply):
""" Get X, Y and Z values as Coord object.
:param default: Default values, if any of coords is not specified.
:param default: Default values, if any of coordinates is not specified.
:param multiply: If value exist, multiply it by this value.
:return: Coord object.
"""
@@ -82,16 +82,17 @@ class GCode(object):
:return: gcode objects.
"""
line = line.upper()
line = re.sub(cleanpattern, '', line)
line = re.sub(clean_pattern, '', line)
if len(line) == 0:
return None
if line[0] == '%':
return None
m = gpattern.findall(line)
m = g_pattern.findall(line)
if not m:
raise GCodeException('gcode not found')
if len(''.join(["%s%s" % i for i in m])) != len(line):
raise GCodeException('extra characters in line')
# noinspection PyTypeChecker
params = dict(m)
if len(params) != len(m):
raise GCodeException('duplicated gcode entries')

View File

@@ -1,13 +1,10 @@
from __future__ import division
import time
import logging
import math
import cnc.logging_config as logging_config
from cnc import hal
from cnc.coordinates import Coordinates
from cnc.enums import *
from cnc.config import *
from cnc.pulses import *
from cnc.coordinates import *
class GMachineException(Exception):
@@ -53,6 +50,7 @@ class GMachine(object):
self._absoluteCoordinates = True
self._plane = PLANE_XY
# noinspection PyMethodMayBeStatic
def _spindle(self, spindle_speed):
hal.join()
hal.spindle_control(100.0 * spindle_speed / SPINDLE_MAX_RPM)
@@ -60,7 +58,8 @@ 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, 0.0),
Coordinates(TABLE_SIZE_X_MM, TABLE_SIZE_Y_MM, TABLE_SIZE_Z_MM, 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):
@@ -78,7 +77,8 @@ class GMachine(object):
# save position
self._position = self._position + delta
def __quarter(self, pa, pb):
@staticmethod
def __quarter(pa, pb):
if pa >= 0 and pb >= 0:
return 1
if pa < 0 and pb >= 0:
@@ -88,7 +88,7 @@ class GMachine(object):
if pa >= 0 and pb < 0:
return 4
def __adjust_circle(self, da, db, ra, rb, dir, pa, pb, ma, mb):
def __adjust_circle(self, da, db, ra, rb, direction, pa, pb, ma, mb):
r = math.sqrt(ra * ra + rb * rb)
if r == 0:
raise GMachineException("circle radius is zero")
@@ -108,7 +108,7 @@ class GMachine(object):
q = sq
pq = q
for _ in range(0, 4):
if dir == CW:
if direction == CW:
q -= 1
else:
q += 1
@@ -120,7 +120,7 @@ class GMachine(object):
break
is_raise = False
if (pq == 1 and q == 4) or (pq == 4 and q == 1):
is_raise = (pa + ra + r > ma)
is_raise = (pa + ra + r > ma)
elif (pq == 1 and q == 2) or (pq == 2 and q == 1):
is_raise = (pb + rb + r > mb)
elif (pq == 2 and q == 3) or (pq == 3 and q == 2):
@@ -145,22 +145,25 @@ class GMachine(object):
# get delta vector and put it on circle
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,
self._position.x, self._position.y,
TABLE_SIZE_X_MM, TABLE_SIZE_Y_MM)
circle_end.x, circle_end.y = \
self.__adjust_circle(delta.x, delta.y, radius.x, radius.y,
direction, self._position.x,
self._position.y, TABLE_SIZE_X_MM,
TABLE_SIZE_Y_MM)
circle_end.z = delta.z
elif self._plane == PLANE_YZ:
circle_end.y, circle_end.z = self.__adjust_circle(delta.y, delta.z,
radius.y, radius.z, direction,
self._position.y, self._position.z,
TABLE_SIZE_Y_MM, TABLE_SIZE_Z_MM)
circle_end.y, circle_end.z = \
self.__adjust_circle(delta.y, delta.z, radius.y, radius.z,
direction, self._position.y,
self._position.z, TABLE_SIZE_Y_MM,
TABLE_SIZE_Z_MM)
circle_end.x = delta.x
elif self._plane == PLANE_ZX:
circle_end.z, circle_end.x = self.__adjust_circle(delta.z, delta.x,
radius.z, radius.x, direction,
self._position.z, self._position.x,
TABLE_SIZE_Z_MM, TABLE_SIZE_X_MM)
circle_end.z, circle_end.x = \
self.__adjust_circle(delta.z, delta.x, radius.z, radius.x,
direction, 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,
@@ -168,8 +171,8 @@ class GMachine(object):
1.0 / STEPPER_PULSES_PER_MM_Z,
1.0 / STEPPER_PULSES_PER_MM_E)
logging.info("Moving circularly {} {} {} with radius {}"
" and velocity {}".
format(self._plane, circle_end, direction, radius, velocity))
" and velocity {}".format(self._plane, circle_end,
direction, radius, velocity))
gen = PulseGeneratorCircular(circle_end, radius, self._plane, direction,
velocity)
hal.move(gen)
@@ -227,7 +230,7 @@ class GMachine(object):
else:
delta = gcode.coordinates(Coordinates(0.0, 0.0, 0.0, 0.0),
self._convertCoordinates)
coord = self._position + delta
# coord = self._position + delta
velocity = gcode.get('F', self._velocity)
spindle_rpm = gcode.get('S', self._spindle_rpm)
pause = gcode.get('P', self._pause)
@@ -274,14 +277,14 @@ class GMachine(object):
self._local = self._position - \
gcode.coordinates(Coordinates(0.0, 0.0, 0.0, 0.0),
self._convertCoordinates)
elif c == 'M3': # spinle on
elif c == 'M3': # spindle on
self._spindle(spindle_rpm)
elif c == 'M5': # spindle off
self._spindle(0)
elif c == 'M2' or c == 'M30': # program finish, reset everything.
self.reset()
elif c == 'M111': # enable debug
logging.getLogger().setLevel(logging.DEBUG)
logging_config.debug_enable()
elif c is None: # command not specified(for example, just F was passed)
pass
else:

View File

@@ -1,23 +1,9 @@
import logging
import time
from cnc.hal_raspberry import rpgpio
from cnc.pulses import PulseGeneratorLinear
from cnc.coordinates import Coordinates
from cnc.pulses import *
from cnc.config import *
# Stepper motors channel for RPIO
STEPPER_CHANNEL = 0
# Since there is no way to add pulses and then start cycle in RPIO,
# use this delay to start adding pulses to cycle. It can be easily
# solved by modifying RPIO in a way of adding method to start cycle
# explicitly.
RPIO_START_DELAY_US = 200000
# Since RPIO generate cycles in loop, use this delay to stop RPIO
# It can be removed if RPIO would allow to run single shot cycle.
RPIO_STOP_DELAY_US = 5000000
US_IN_SECONDS = 1000000
gpio = rpgpio.GPIO()
@@ -29,8 +15,9 @@ 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
""" Initialize GPIO pins and machine itself, including calibration if
needed. Do not return till all procedures are completed.
"""
gpio.init(STEPPER_STEP_PIN_X, rpgpio.GPIO.MODE_OUTPUT)
@@ -53,7 +40,7 @@ def init():
gpio.set(STEPPER_DIR_PIN_Z)
pins = STEP_PIN_MASK_X | STEP_PIN_MASK_Y | STEP_PIN_MASK_Z
dma.clear()
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
dma.add_pulse(pins, STEPPER_PULSE_LENGTH_US)
st = time.time()
max_pulses_left = int(1.2 * max(STEPPER_PULSES_PER_MM_X,
STEPPER_PULSES_PER_MM_Y,
@@ -66,15 +53,15 @@ def init():
if (STEP_PIN_MASK_X & pins) != 0 and gpio.read(ENDSTOP_PIN_X) == 0:
pins &= ~STEP_PIN_MASK_X
dma.clear()
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
dma.add_pulse(pins, STEPPER_PULSE_LENGTH_US)
if (STEP_PIN_MASK_Y & pins) != 0 and gpio.read(ENDSTOP_PIN_Y) == 0:
pins &= ~STEP_PIN_MASK_Y
dma.clear()
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
dma.add_pulse(pins, STEPPER_PULSE_LENGTH_US)
if (STEP_PIN_MASK_Z & pins) != 0 and gpio.read(ENDSTOP_PIN_Z) == 0:
pins &= ~STEP_PIN_MASK_Z
dma.clear()
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
dma.add_pulse(pins, STEPPER_PULSE_LENGTH_US)
if pins == 0:
break
dma.run(False)
@@ -122,8 +109,8 @@ def move(generator):
is_ran = False
instant = INSTANT_RUN
st = time.time()
for dir, tx, ty, tz, te in generator:
if dir: # set up directions
for direction, tx, ty, tz, te in generator:
if direction: # set up directions
pins_to_set = 0
pins_to_clear = 0
if tx > 0:
@@ -160,11 +147,11 @@ def move(generator):
pins |= STEP_PIN_MASK_E
if k - prev > 0:
dma.add_delay(k - prev)
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US)
dma.add_pulse(pins, STEPPER_PULSE_LENGTH_US)
# TODO not a precise way! pulses will set in queue, instead of crossing
# if next pulse start during pulse length. Though it almost doesn't
# matter for pulses with 1-2us length.
prev = k + STEPPER_PULSE_LINGTH_US
prev = k + STEPPER_PULSE_LENGTH_US
# instant run handling
if not is_ran and instant:
if k > 500000: # wait at least 500 ms is uploaded

View File

@@ -20,7 +20,7 @@ class GPIO(object):
"""
self._mem = PhysicalMemory(PERI_BASE + GPIO_REGISTER_BASE)
def _pullupdn(self, pin, mode):
def _pull_up_dn(self, pin, mode):
p = self._mem.read_int(GPIO_PULLUPDN_OFFSET)
p &= ~3
if mode == self.MODE_INPUT_PULLUP:
@@ -28,49 +28,49 @@ class GPIO(object):
elif mode == self.MODE_INPUT_PULLDOWN:
p |= 1
self._mem.write_int(GPIO_PULLUPDN_OFFSET, p)
addr = 4 * int(pin / 32) + GPIO_PULLUPDNCLK_OFFSET
self._mem.write_int(addr, 1 << (pin % 32))
address = 4 * int(pin / 32) + GPIO_PULLUPDNCLK_OFFSET
self._mem.write_int(address, 1 << (pin % 32))
p = self._mem.read_int(GPIO_PULLUPDN_OFFSET)
p &= ~3
self._mem.write_int(GPIO_PULLUPDN_OFFSET, p)
self._mem.write_int(addr, 0)
self._mem.write_int(address, 0)
def init(self, pin, mode):
""" Initialize or re-initialize GPIO pin.
:param pin: pin number.
:param mode: one of MODE_* variables in this class.
"""
addr = 4 * int(pin / 10) + GPIO_FSEL_OFFSET
v = self._mem.read_int(addr)
address = 4 * int(pin / 10) + GPIO_FSEL_OFFSET
v = self._mem.read_int(address)
v &= ~(7 << ((pin % 10) * 3)) # input value
if mode == self.MODE_OUTPUT:
v |= (1 << ((pin % 10) * 3)) # output value, base on input
self._mem.write_int(addr, v)
self._mem.write_int(address, v)
else:
self._mem.write_int(addr, v)
self._pullupdn(pin, mode)
self._mem.write_int(address, v)
self._pull_up_dn(pin, mode)
def set(self, pin):
""" Set pin to HIGH state.
:param pin: pin number.
"""
addr = 4 * int(pin / 32) + GPIO_SET_OFFSET
self._mem.write_int(addr, 1 << (pin % 32))
address = 4 * int(pin / 32) + GPIO_SET_OFFSET
self._mem.write_int(address, 1 << (pin % 32))
def clear(self, pin):
""" Set pin to LOW state.
:param pin: pin number.
"""
addr = 4 * int(pin / 32) + GPIO_CLEAR_OFFSET
self._mem.write_int(addr, 1 << (pin % 32))
address = 4 * int(pin / 32) + GPIO_CLEAR_OFFSET
self._mem.write_int(address, 1 << (pin % 32))
def read(self, pin):
""" Read pin current value.
:param pin: pin number.
:return: integer value 0 or 1.
"""
addr = 4 * int(pin / 32) + GPIO_INPUT_OFFSET
v = self._mem.read_int(addr)
address = 4 * int(pin / 32) + GPIO_INPUT_OFFSET
v = self._mem.read_int(address)
v &= 1 << (pin % 32)
if v == 0:
return 0
@@ -102,20 +102,20 @@ class DMAGPIO(DMAProto):
self._clock = PhysicalMemory(PERI_BASE + CM_BASE)
# pre calculated variables for control blocks
self._delay_info = DMA_TI_NO_WIDE_BURSTS | DMA_SRC_IGNORE \
| DMA_TI_PER_MAP(DMA_TI_PER_MAP_PWM) \
| DMA_TI_DEST_DREQ
self._delay_info = (DMA_TI_NO_WIDE_BURSTS | DMA_SRC_IGNORE
| DMA_TI_PER_MAP(DMA_TI_PER_MAP_PWM)
| DMA_TI_DEST_DREQ)
self._delay_destination = PHYSICAL_PWM_BUS + PWM_FIFO
self._delay_stride = 0
self._pulse_info = DMA_TI_NO_WIDE_BURSTS | DMA_TI_TDMODE \
| DMA_TI_WAIT_RESP
self._pulse_info = (DMA_TI_NO_WIDE_BURSTS | DMA_TI_TDMODE
| DMA_TI_WAIT_RESP)
self._pulse_destination = PHYSICAL_GPIO_BUS + GPIO_SET_OFFSET
# YLENGTH is transfers count and XLENGTH size of each transfer
self._pulse_length = DMA_TI_TXFR_LEN_YLENGTH(2) \
| DMA_TI_TXFR_LEN_XLENGTH(4)
self._pulse_stride = DMA_TI_STRIDE_D_STRIDE(12) \
| DMA_TI_STRIDE_S_STRIDE(4)
self._pulse_length = (DMA_TI_TXFR_LEN_YLENGTH(2)
| DMA_TI_TXFR_LEN_XLENGTH(4))
self._pulse_stride = (DMA_TI_STRIDE_D_STRIDE(12)
| DMA_TI_STRIDE_S_STRIDE(4))
def add_pulse(self, pins_mask, length_us):
""" Add single pulse at the current position.
@@ -126,9 +126,9 @@ class DMAGPIO(DMAProto):
:param length_us: length in us.
"""
next_cb = self.__current_address + 3 * self._DMA_CONTROL_BLOCK_SIZE
if next_cb > self._physmem.get_size():
if next_cb > self._phys_memory.get_size():
raise MemoryError("Out of allocated memory.")
next3 = next_cb + self._physmem.get_bus_address()
next3 = next_cb + self._phys_memory.get_bus_address()
next2 = next3 - self._DMA_CONTROL_BLOCK_SIZE
next1 = next2 - self._DMA_CONTROL_BLOCK_SIZE
@@ -137,16 +137,17 @@ class DMAGPIO(DMAProto):
source3 = next3 - 8
data = (
# control block 1 - set
self._pulse_info, source1, self._pulse_destination,
self._pulse_length,
self._pulse_stride, next1, pins_mask, 0,
self._pulse_length, self._pulse_stride, next1, pins_mask, 0,
# control block 2 - delay
self._delay_info, 0, self._delay_destination, length2,
self._delay_stride, next2, 0, 0,
# control block 3 - clear
self._pulse_info, source3, self._pulse_destination,
self._pulse_length,
self._pulse_stride, next3, 0, pins_mask
self._pulse_length, self._pulse_stride, next3, 0, pins_mask
)
self._physmem.write(self.__current_address, "24I", data)
self._phys_memory.write(self.__current_address, "24I", data)
self.__current_address = next_cb
def add_delay(self, delay_us):
@@ -154,16 +155,16 @@ class DMAGPIO(DMAProto):
:param delay_us: delay in us.
"""
next_cb = self.__current_address + self._DMA_CONTROL_BLOCK_SIZE
if next_cb > self._physmem.get_size():
if next_cb > self._phys_memory.get_size():
raise MemoryError("Out of allocated memory.")
next1 = self._physmem.get_bus_address() + next_cb
next1 = self._phys_memory.get_bus_address() + next_cb
source = next1 - 8 # last 8 bytes are padding, use it to store data
length = delay_us << 4 # * 16
data = (
self._delay_info, source, self._delay_destination, length,
self._delay_stride, next1, 0, 0
)
self._physmem.write(self.__current_address, "8I", data)
self._phys_memory.write(self.__current_address, "8I", data)
self.__current_address = next_cb
def add_set_clear(self, pins_to_set, pins_to_clear):
@@ -172,23 +173,23 @@ class DMAGPIO(DMAProto):
: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():
if next_cb > self._phys_memory.get_size():
raise MemoryError("Out of allocated memory.")
next1 = self._physmem.get_bus_address() + next_cb
next1 = self._phys_memory.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._pulse_length, self._pulse_stride, next1,
pins_to_set, pins_to_clear
)
self._physmem.write(self.__current_address, "8I", data)
self._phys_memory.write(self.__current_address, "8I", data)
self.__current_address = next_cb
def finalize_stream(self):
""" Mark last added block as the last one.
"""
self._physmem.write_int(self.__current_address + 20
- self._DMA_CONTROL_BLOCK_SIZE, 0)
self._phys_memory.write_int(self.__current_address + 20
- self._DMA_CONTROL_BLOCK_SIZE, 0)
logging.info("DMA took {}MB of memory".
format(round(self.__current_address / 1024.0 / 1024.0, 2)))
@@ -201,9 +202,10 @@ class DMAGPIO(DMAProto):
self._clock.write_int(CM_PWM_CNTL, CM_PASSWORD | CM_SRC_PLLD) # disable
while (self._clock.read_int(CM_PWM_CNTL) & CM_CNTL_BUSY) != 0:
time.sleep(0.00001) # 10 us, wait until BUSY bit is clear
self._clock.write_int(CM_PWM_DIV, CM_PASSWORD | CM_DIV_VALUE(5)) # 100MHz
self._clock.write_int(CM_PWM_CNTL, CM_PASSWORD | CM_SRC_PLLD |
CM_CNTL_ENABLE)
self._clock.write_int(CM_PWM_DIV,
CM_PASSWORD | CM_DIV_VALUE(5)) # 100MHz
self._clock.write_int(CM_PWM_CNTL,
CM_PASSWORD | CM_SRC_PLLD | CM_CNTL_ENABLE)
self._pwm.write_int(PWM_RNG1, 100)
self._pwm.write_int(PWM_DMAC, PWM_DMAC_ENAB
@@ -220,9 +222,9 @@ class DMAGPIO(DMAProto):
raise RuntimeError("Nothing was added.")
# fix 'next' field in previous control block
if loop:
self._physmem.write_int(self.__current_address + 20
- self._DMA_CONTROL_BLOCK_SIZE,
self._physmem.get_bus_address())
self._phys_memory.write_int(self.__current_address + 20
- self._DMA_CONTROL_BLOCK_SIZE,
self._phys_memory.get_bus_address())
else:
self.finalize_stream()
self.run_stream()
@@ -267,17 +269,17 @@ class DMAPWM(DMAProto):
self.__add_control_block(i * self._DMA_CONTROL_BLOCK_SIZE,
GPIO_CLEAR_OFFSET)
# loop
self._physmem.write_int((self._TOTAL_NUMBER_OF_BLOCKS - 1)
* self._DMA_CONTROL_BLOCK_SIZE + 20,
self._physmem.get_bus_address())
self._phys_memory.write_int((self._TOTAL_NUMBER_OF_BLOCKS - 1)
* self._DMA_CONTROL_BLOCK_SIZE + 20,
self._phys_memory.get_bus_address())
self._gpio = PhysicalMemory(PERI_BASE + GPIO_REGISTER_BASE)
def __add_control_block(self, address, offset):
ba = self._physmem.get_bus_address() + address
ba = self._phys_memory.get_bus_address() + address
data = (
DMA_TI_NO_WIDE_BURSTS | DMA_TI_WAIT_RESP
| DMA_TI_DEST_INC | DMA_TI_SRC_INC, # info
ba + self._DMA_DATA_OFFSET, # source, last 8 bytes are padding, use it to store data
ba + self._DMA_DATA_OFFSET, # source, use padding for storing data
PHYSICAL_GPIO_BUS + offset, # destination
4, # length
0, # stride
@@ -285,7 +287,7 @@ class DMAPWM(DMAProto):
0, # padding, uses as data storage
0 # padding
)
self._physmem.write(address, "8I", data)
self._phys_memory.write(address, "8I", data)
def add_pin(self, pin, duty_cycle):
""" Add pin to PMW with specified duty cycle.
@@ -302,14 +304,14 @@ class DMAPWM(DMAProto):
self._gpio.write_int(GPIO_SET_OFFSET, 1 << pin)
self._clear_pins[pin] = self._DMA_DATA_OFFSET
else:
value = self._physmem.read_int(self._DMA_DATA_OFFSET)
value = self._phys_memory.read_int(self._DMA_DATA_OFFSET)
value |= 1 << pin
self._physmem.write_int(self._DMA_DATA_OFFSET, value)
clear_address = block_number * self._DMA_CONTROL_BLOCK_SIZE \
+ self._DMA_DATA_OFFSET
value = self._physmem.read_int(clear_address)
self._phys_memory.write_int(self._DMA_DATA_OFFSET, value)
clear_address = (block_number * self._DMA_CONTROL_BLOCK_SIZE
+ self._DMA_DATA_OFFSET)
value = self._phys_memory.read_int(clear_address)
value |= 1 << pin
self._physmem.write_int(clear_address, value)
self._phys_memory.write_int(clear_address, value)
self._clear_pins[pin] = clear_address
if not self.is_active():
super(DMAPWM, self)._run_dma()
@@ -321,12 +323,12 @@ class DMAPWM(DMAProto):
assert 0 <= pin < 32
if pin in self._clear_pins.keys():
address = self._clear_pins[pin]
value = self._physmem.read_int(address)
value = self._phys_memory.read_int(address)
value &= ~(1 << pin)
self._physmem.write_int(address, value)
value = self._physmem.read_int(self._DMA_DATA_OFFSET)
self._phys_memory.write_int(address, value)
value = self._phys_memory.read_int(self._DMA_DATA_OFFSET)
value &= ~(1 << pin)
self._physmem.write_int(self._DMA_DATA_OFFSET, value)
self._phys_memory.write_int(self._DMA_DATA_OFFSET, value)
del self._clear_pins[pin]
self._gpio.write_int(GPIO_CLEAR_OFFSET, 1 << pin)
if len(self._clear_pins) == 0 and self.is_active():

View File

@@ -57,32 +57,18 @@ DMA_CS_END = 1 << 1
DMA_CS_ACTIVE = 1 << 0
DMA_TI_PER_MAP_PWM = 5
DMA_TI_PER_MAP_PCM = 2
def DMA_TI_PER_MAP(x):
return x << 16
def DMA_TI_TXFR_LEN_YLENGTH(y):
return (y & 0x3fff) << 16
def DMA_TI_TXFR_LEN_XLENGTH(x):
return x & 0xffff
def DMA_TI_STRIDE_D_STRIDE(x):
return (x & 0xffff) << 16
def DMA_TI_STRIDE_S_STRIDE(x):
return x & 0xffff
def DMA_CS_PRIORITY(x):
return (x & 0xf) << 16
def DMA_CS_PANIC_PRIORITY(x):
return (x & 0xf) << 20
DMA_TI_PER_MAP = (lambda x: x << 16)
DMA_TI_TXFR_LEN_YLENGTH = (lambda y: (y & 0x3fff) << 16)
DMA_TI_TXFR_LEN_XLENGTH = (lambda x: x & 0xffff)
DMA_TI_STRIDE_D_STRIDE = (lambda x: (x & 0xffff) << 16)
DMA_TI_STRIDE_S_STRIDE = (lambda x: x & 0xffff)
DMA_CS_PRIORITY = (lambda x: (x & 0xf) << 16)
DMA_CS_PANIC_PRIORITY = (lambda x: (x & 0xf) << 20)
# hardware PWM controller registers
PWM_BASE = 0x0020C000
PHYSICAL_PWM_BUS = 0x7E000000 + PWM_BASE
PWM_CTL= 0x00
PWM_CTL = 0x00
PWM_DMAC = 0x08
PWM_RNG1 = 0x10
PWM_RNG2 = 0x20
@@ -95,12 +81,8 @@ PWM_CTL_CLRF = 1 << 6
PWM_CTL_USEF1 = 1 << 5
PWM_CTL_USEF2 = 1 << 13
PWM_DMAC_ENAB = 1 << 31
def PWM_DMAC_PANIC(x):
return x << 8
def PWM_DMAC_DREQ(x):
return x
PWM_DMAC_PANIC = (lambda x: x << 8)
PWM_DMAC_DREQ = (lambda x: x)
# clock manager module
CM_BASE = 0x00101000
@@ -113,14 +95,13 @@ CM_CNTL_ENABLE = 1 << 4
CM_CNTL_BUSY = 1 << 7
CM_SRC_OSC = 1 # 19.2 MHz
CM_SRC_PLLC = 5 # 1000 MHz
CM_SRC_PLLD = 6 # 500 MHz
CM_SRC_HDMI = 7 # 216 MHz
def CM_DIV_VALUE(x):
return x << 12
CM_SRC_PLLD = 6 # 500 MHz
CM_SRC_HDMI = 7 # 216 MHz
CM_DIV_VALUE = (lambda x: x << 12)
class PhysicalMemory(object):
# noinspection PyArgumentList,PyArgumentList
def __init__(self, phys_address, size=PAGE_SIZE):
""" Create object which maps physical memory to Python's mmap object.
:param phys_address: based address of physical memory
@@ -128,14 +109,14 @@ class PhysicalMemory(object):
self._size = size
phys_address -= phys_address % PAGE_SIZE
fd = self._open_dev("/dev/mem")
self._rmap = mmap.mmap(fd, size, flags=mmap.MAP_SHARED,
prot=mmap.PROT_READ | mmap.PROT_WRITE,
offset=phys_address)
self._memmap = mmap.mmap(fd, size, flags=mmap.MAP_SHARED,
prot=mmap.PROT_READ | mmap.PROT_WRITE,
offset=phys_address)
self._close_dev(fd)
atexit.register(self.cleanup)
def cleanup(self):
self._rmap.close()
self._memmap.close()
@staticmethod
def _open_dev(name):
@@ -149,13 +130,13 @@ class PhysicalMemory(object):
os.close(fd)
def write_int(self, address, int_value):
ctypes.c_uint32.from_buffer(self._rmap, address).value = int_value
ctypes.c_uint32.from_buffer(self._memmap, address).value = int_value
def write(self, address, fmt, data):
struct.pack_into(fmt, self._rmap, address, *data)
struct.pack_into(fmt, self._memmap, address, *data)
def read_int(self, address):
return ctypes.c_uint32.from_buffer(self._rmap, address).value
return ctypes.c_uint32.from_buffer(self._memmap, address).value
def get_size(self):
return self._size
@@ -177,8 +158,8 @@ class CMAPhysicalMemory(PhysicalMemory):
if self._handle == 0:
raise OSError("No memory to allocate with /dev/vcio")
# lock memory
self._busmem = self._send_data(0x3000d, [self._handle])
if self._busmem == 0:
self._bus_memory = self._send_data(0x3000d, [self._handle])
if self._bus_memory == 0:
# memory should be freed in __del__
raise OSError("Failed to lock memory with /dev/vcio")
# print("allocate {} at {} (bus {})".format(size,
@@ -206,10 +187,10 @@ class CMAPhysicalMemory(PhysicalMemory):
return data[5]
def get_bus_address(self):
return self._busmem
return self._bus_memory
def get_phys_address(self):
return self._busmem & ~0xc0000000
return self._bus_memory & ~0xc0000000
class DMAProto(object):
@@ -219,7 +200,7 @@ class DMAProto(object):
"""
self._DMA_CHANNEL = dma_channel
# allocate buffer for control blocks
self._physmem = CMAPhysicalMemory(memory_size)
self._phys_memory = CMAPhysicalMemory(memory_size)
# prepare dma registers memory map
self._dma = PhysicalMemory(PERI_BASE + DMA_BASE)
@@ -228,7 +209,8 @@ class DMAProto(object):
"""
address = 0x100 * self._DMA_CHANNEL
self._dma.write_int(address + DMA_CS, DMA_CS_END)
self._dma.write_int(address + DMA_CONBLK_AD, self._physmem.get_bus_address())
self._dma.write_int(address + DMA_CONBLK_AD,
self._phys_memory.get_bus_address())
cs = DMA_CS_PRIORITY(7) | DMA_CS_PANIC_PRIORITY(7) | DMA_CS_DISDEBUG
self._dma.write_int(address + DMA_CS, cs)
cs |= DMA_CS_ACTIVE

View File

@@ -1,10 +1,8 @@
from __future__ import division
import logging
import time
from cnc.pulses import PulseGeneratorLinear, PulseGeneratorCircular
from cnc.pulses import *
from cnc.config import *
from cnc.coordinates import Coordinates
""" This is virtual device class which is very useful for debugging.
It checks PulseGenerator with some tests.
@@ -25,6 +23,7 @@ def spindle_control(percent):
logging.info("spindle control: {}%".format(percent))
# noinspection PyUnusedLocal
def move(generator):
""" Move head to specified position.
:param generator: PulseGenerator object.
@@ -35,28 +34,28 @@ def move(generator):
dx, dy, dz, de = 0, 0, 0, 0
mx, my, mz, me = 0, 0, 0, 0
cx, cy, cz, ce = 0, 0, 0, 0
dirx, diry, dirz, dire = 1, 1, 1, 1
direction_x, direction_y, direction_z, dire = 1, 1, 1, 1
st = time.time()
direction_found = False
for dir, tx, ty, tz, te in generator:
if dir:
for direction, tx, ty, tz, te in generator:
if direction:
direction_found = True
dirx, diry, dirz, dire = tx, ty, tz, te
direction_x, direction_y, direction_z, dire = tx, ty, tz, te
if isinstance(generator, PulseGeneratorLinear):
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
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 > mx:
mx = tx
tx = int(round(tx * 1000000))
ix += dirx
ix += direction_x
cx += 1
if lx is not None:
dx = tx - lx
@@ -68,7 +67,7 @@ def move(generator):
if ty > my:
my = ty
ty = int(round(ty * 1000000))
iy += diry
iy += direction_y
cy += 1
if ly is not None:
dy = ty - ly
@@ -80,7 +79,7 @@ def move(generator):
if tz > mz:
mz = tz
tz = int(round(tz * 1000000))
iz += dirz
iz += direction_z
cz += 1
if lz is not None:
dz = tz - lz
@@ -101,7 +100,8 @@ def move(generator):
else:
de = None
# very verbose, uncomment on demand
# logging.debug("Iteration {} is {} {} {} {}".format(max(ix, iy, iz, ie), tx, ty, tz, te))
# 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()
@@ -110,10 +110,11 @@ def move(generator):
assert iy / STEPPER_PULSES_PER_MM_Y == delta.y, "y wrong number of pulses"
assert iz / STEPPER_PULSES_PER_MM_Z == delta.z, "z wrong number of pulses"
assert ie / STEPPER_PULSES_PER_MM_E == delta.e, "e wrong number of pulses"
assert max(mx, my, mz, me) <= generator.total_time_s(), "interpolation time or pulses wrong"
assert max(mx, my, mz, me) <= generator.total_time_s(), \
"interpolation time or pulses wrong"
logging.debug("Moved {}, {}, {}, {} 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")
logging.info("prepared in " + str(round(pt - st, 2)) + "s, estimated "
+ str(round(generator.total_time_s(), 2)) + "s")
def join():

12
cnc/logging_config.py Normal file
View File

@@ -0,0 +1,12 @@
import logging
logging.basicConfig(level=logging.CRITICAL,
format='[%(levelname)s] %(message)s')
def debug_enable():
logging.getLogger().setLevel(logging.DEBUG)
def debug_disable():
logging.getLogger().setLevel(logging.CRITICAL)

View File

@@ -1,19 +1,29 @@
#!/usr/bin/env python
import os
import sys
import readline
import logging
logging.basicConfig(level=logging.CRITICAL,
format='[%(levelname)s] %(message)s')
import atexit
import cnc.logging_config as logging_config
from cnc.gcode import GCode, GCodeException
from cnc.gmachine import GMachine, GMachineException
try: # python3 compatibility
try: # python3 compatibility
type(raw_input)
except NameError:
# noinspection PyShadowingBuiltins
raw_input = input
# configure history file for interactive mode
history_file = os.path.join(os.environ['HOME'], '.pycnc_history')
try:
readline.read_history_file(history_file)
except IOError:
pass
readline.set_history_length(1000)
atexit.register(readline.write_history_file, history_file)
machine = GMachine()
@@ -29,6 +39,7 @@ def do_line(line):
def main():
logging_config.debug_disable()
try:
if len(sys.argv) > 1:
# Read file with gcode
@@ -53,6 +64,6 @@ def main():
print("\r\nExiting...")
machine.release()
if __name__ == "__main__":
main()

View File

@@ -1,10 +1,9 @@
from __future__ import division
import math
import logging
from cnc.config import *
from cnc.coordinates import *
from cnc.enums import *
from cnc.coordinates import Coordinates
SECONDS_IN_MINUTE = 60.0
@@ -33,8 +32,8 @@ class PulseGenerator(object):
"""
def __init__(self, delta):
""" Create object. Do not create directly this object, inherit this class
and implement interpolation function and related methods.
""" Create object. Do not create directly this object, inherit this
class and implement interpolation function and related methods.
All child have to call this method ( super().__init__() ).
:param delta: overall movement delta in mm, uses for debug purpose.
"""
@@ -84,11 +83,11 @@ class PulseGenerator(object):
""" Get iterator.
:return: iterable object.
"""
self._acceleration_time_s, self._linear_time_s, \
max_axis_velocity_mm_per_sec = self._get_movement_parameters()
(self._acceleration_time_s, self._linear_time_s,
max_axis_velocity_mm_per_sec) = self._get_movement_parameters()
# helper variable
self._2Vmax_per_a = 2.0 * max_axis_velocity_mm_per_sec \
/ STEPPER_MAX_ACCELERATION_MM_PER_S2
self._2Vmax_per_a = (2.0 * max_axis_velocity_mm_per_sec
/ STEPPER_MAX_ACCELERATION_MM_PER_S2)
self._iteration_x = 0
self._iteration_y = 0
self._iteration_z = 0
@@ -147,14 +146,13 @@ class PulseGenerator(object):
not be earlier in time then current. If there is no pulses
left StopIteration will be raised.
"""
dir, (tx, ty, tz, te) = self._interpolation_function(self._iteration_x,
self._iteration_y,
self._iteration_z,
self._iteration_e)
direction, (tx, ty, tz, te) = \
self._interpolation_function(self._iteration_x, self._iteration_y,
self._iteration_z, self._iteration_e)
# check if direction update:
if dir != self._iteration_direction:
self._iteration_direction = dir
return (True,) + dir
if direction != self._iteration_direction:
self._iteration_direction = direction
return (True,) + direction
# check condition to stop
if tx is None and ty is None and tz is None and te is None:
raise StopIteration
@@ -215,45 +213,47 @@ class PulseGeneratorLinear(PulseGenerator):
"""
super(PulseGeneratorLinear, self).__init__(delta_mm)
# this class doesn't care about direction
self._distance_mm = abs(delta_mm)
self._distance_mm = abs(delta_mm) # type: Coordinates
# velocity of each axis
distance_total_mm = self._distance_mm.length()
self.max_velocity_mm_per_sec = self._distance_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
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_total_mm:
self.acceleration_time_s = math.sqrt(distance_total_mm /
STEPPER_MAX_ACCELERATION_MM_PER_S2)
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
self.max_velocity_mm_per_sec = (self._distance_mm
/ self.acceleration_time_s)
else:
# calculate linear time
linear_distance_mm = distance_total_mm \
- self.acceleration_time_s ** 2 \
* STEPPER_MAX_ACCELERATION_MM_PER_S2
self.linear_time_s = linear_distance_mm \
/ 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)
self.linear_time_s = (linear_distance_mm
/ 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):
""" Return movement parameters, see super class for details.
"""
return self.acceleration_time_s, \
self.linear_time_s, \
self.max_velocity_mm_per_sec.find_max()
return (self.acceleration_time_s,
self.linear_time_s,
self.max_velocity_mm_per_sec.find_max())
def __linear(self, position_mm, distance_mm, velocity_mm_per_sec):
@staticmethod
def __linear(position_mm, distance_mm, velocity_mm_per_sec):
""" Helper function for linear movement.
"""
# check if need to calculate for this axis
@@ -326,16 +326,18 @@ class PulseGeneratorCircular(PulseGenerator):
eb = sb + delta.x
apm = STEPPER_PULSES_PER_MM_Z
bpm = STEPPER_PULSES_PER_MM_X
else:
raise ValueError("Unknown plane")
# adjust radius to fit into axises step.
radius = round(math.sqrt(sa * sa + sb * sb) * min(apm, bpm)) \
/ min(apm, bpm)
radius = (round(math.sqrt(sa * sa + sb * sb) * min(apm, bpm))
/ min(apm, bpm))
self._radius2 = radius * radius
self._radius_a_pulses = int(radius * apm)
self._radius_b_pulses = int(radius * bpm)
self._start_a_pulses = int(sa * apm)
self._start_b_pulses = int(sb * bpm)
assert round(math.sqrt(ea * ea + eb * eb) * min(apm, bpm)) \
/ min(apm, bpm) == radius, "Wrong end point"
assert (round(math.sqrt(ea * ea + eb * eb) * min(apm, bpm))
/ min(apm, bpm) == radius), "Wrong end point"
# Calculate angles and directions.
start_angle = self.__angle(sa, sb)
@@ -355,7 +357,7 @@ class PulseGeneratorCircular(PulseGenerator):
else:
self._dir_a = 1
elif direction == CCW:
if 0 < start_angle <= math.pi:
if 0.0 < start_angle <= math.pi:
self._dir_b = 1
else:
self._dir_b = -1
@@ -363,8 +365,10 @@ class PulseGeneratorCircular(PulseGenerator):
self._dir_a = -1
else:
self._dir_a = 1
self._side_a = self._start_b_pulses < 0 or (self._start_b_pulses == 0 and self._dir_b < 0)
self._side_b = self._start_a_pulses < 0 or (self._start_a_pulses == 0 and self._dir_a < 0)
self._side_a = (self._start_b_pulses < 0
or (self._start_b_pulses == 0 and self._dir_b < 0))
self._side_b = (self._start_a_pulses < 0
or (self._start_a_pulses == 0 and self._dir_a < 0))
self._start_angle = start_angle
logging.debug("start angle {}, end angle {}, delta {}".format(
start_angle * 180.0 / math.pi,
@@ -381,38 +385,42 @@ class PulseGeneratorCircular(PulseGenerator):
end_angle_m = end_angle
if start_angle >= end_angle:
end_angle_m += 2 * math.pi
rstart = int(start_angle / (math.pi / 2.0))
rend = int(end_angle_m / (math.pi / 2.0))
if rend - rstart >= 4:
quarter_start = int(start_angle / (math.pi / 2.0))
quarter_end = int(end_angle_m / (math.pi / 2.0))
if quarter_end - quarter_start >= 4:
self._iterations_a = 4 * int(radius * apm)
self._iterations_b = 4 * int(radius * apm)
else:
if rstart == rend:
if quarter_start == quarter_end:
self._iterations_a = int(abs(sa - ea) * apm)
self._iterations_b = int(abs(sb - eb) * bpm)
else:
for r in range(rstart, rend + 1):
for r in range(quarter_start, quarter_end + 1):
i = r
if i >= 4:
i -= 4
if r == rstart:
if r == quarter_start:
if i == 0 or i == 2:
self._iterations_a += int(radius * apm) - int(abs(sa) * apm)
self._iterations_a += int(radius * apm) \
- int(abs(sa) * apm)
else:
self._iterations_a += int(abs(sa) * apm)
if i == 1 or i == 3:
self._iterations_b += int(radius * bpm) - int(abs(sb) * bpm)
self._iterations_b += int(radius * bpm) \
- int(abs(sb) * bpm)
else:
self._iterations_b += int(abs(sb) * bpm)
elif r == rend:
elif r == quarter_end:
if i == 0 or i == 2:
self._iterations_a += int(abs(ea) * apm)
else:
self._iterations_a += int(radius * apm) - int(abs(ea) * apm)
self._iterations_a += int(radius * apm) \
- int(abs(ea) * apm)
if i == 1 or i == 3:
self._iterations_b += int(abs(eb) * bpm)
else:
self._iterations_b += int(radius * bpm) - int(abs(eb) * bpm)
self._iterations_b += int(radius * bpm) \
- int(abs(eb) * bpm)
else:
self._iterations_a += int(radius * apm)
self._iterations_b += int(radius * bpm)
@@ -437,20 +445,22 @@ class PulseGeneratorCircular(PulseGenerator):
l = math.sqrt(arc * arc + delta.y * delta.y + e2)
self._velocity_3rd = abs(delta.y) / l * velocity
self._third_dir = math.copysign(1, delta.y)
else:
raise ValueError("Unknown plane")
self._iterations_e = abs(delta.e) * STEPPER_PULSES_PER_MM_E
# Velocity splits with corresponding distance.
cV = arc / l * velocity
self._RdivV = radius / cV
circular_velocity = arc / l * velocity
self._r_div_v = radius / circular_velocity
self._e_velocity = abs(delta.e) / l * velocity
self._e_dir = math.copysign(1, delta.e)
self.max_velocity_mm_per_sec = max(cV, self._velocity_3rd,
self._e_velocity)
self.acceleration_time_s = self.max_velocity_mm_per_sec \
/ STEPPER_MAX_ACCELERATION_MM_PER_S2
self.max_velocity_mm_per_sec = max(circular_velocity,
self._velocity_3rd, self._e_velocity)
self.acceleration_time_s = (self.max_velocity_mm_per_sec
/ STEPPER_MAX_ACCELERATION_MM_PER_S2)
if STEPPER_MAX_ACCELERATION_MM_PER_S2 * self.acceleration_time_s ** 2 \
> l:
self.acceleration_time_s = math.sqrt(l /
STEPPER_MAX_ACCELERATION_MM_PER_S2)
self.acceleration_time_s = \
math.sqrt(l / STEPPER_MAX_ACCELERATION_MM_PER_S2)
self.linear_time_s = 0.0
self.max_velocity_mm_per_sec = l / self.acceleration_time_s
else:
@@ -458,7 +468,8 @@ class PulseGeneratorCircular(PulseGenerator):
* STEPPER_MAX_ACCELERATION_MM_PER_S2
self.linear_time_s = linear_distance_mm / velocity
def __angle(self, a, b):
@staticmethod
def __angle(a, b):
# Calculate angle of entry point (a, b) of circle with center in (0,0)
angle = math.acos(b / math.sqrt(a * a + b * b))
if a < 0:
@@ -468,27 +479,28 @@ class PulseGeneratorCircular(PulseGenerator):
def _get_movement_parameters(self):
""" Return movement parameters, see super class for details.
"""
return self.acceleration_time_s, \
self.linear_time_s, \
self.max_velocity_mm_per_sec
return (self.acceleration_time_s,
self.linear_time_s,
self.max_velocity_mm_per_sec)
def __circularHelper(self, start, i, radius, side, dir):
np = start + dir * i
@staticmethod
def __circular_helper(start, i, radius, side, direction):
np = start + direction * i
if np > radius:
np -= 2 * (np - radius)
dir = -dir
direction = -direction
side = not side
if np < -radius:
np -= 2 * (np + radius)
dir = -dir
direction = -direction
side = not side
if np > radius:
np -= 2 * (np - radius)
dir = -dir
direction = -direction
side = not side
return np, dir, side
return np, direction, side
def __circularFindTime(self, a, b):
def __circular_find_time(self, a, b):
angle = self.__angle(a, b)
if self._direction == CW:
delta_angle = angle - self._start_angle
@@ -496,39 +508,40 @@ class PulseGeneratorCircular(PulseGenerator):
delta_angle = self._start_angle - angle
if delta_angle <= 0:
delta_angle += 2 * math.pi
return self._RdivV * delta_angle
return self._r_div_v * delta_angle
def __circularA(self, i, pulses_per_mm):
def __circular_a(self, i, pulses_per_mm):
if i >= self._iterations_a:
return self._dir_a, None
a, dir, side = self.__circularHelper(self._start_a_pulses, i + 1,
self._radius_a_pulses,
self._side_a, self._dir_a)
a, direction, side = self.__circular_helper(self._start_a_pulses, i + 1,
self._radius_a_pulses,
self._side_a, self._dir_a)
a /= pulses_per_mm
# last item can be slightly more then end angle due to float precision
if i + 1 == self._iterations_a:
return dir, self._RdivV * self._delta_angle
return direction, self._r_div_v * self._delta_angle
b = math.sqrt(self._radius2 - a * a)
if side:
b = -b
return dir, self.__circularFindTime(a, b)
return direction, self.__circular_find_time(a, b)
def __circularB(self, i, pulses_per_mm):
def __circular_b(self, i, pulses_per_mm):
if i >= self._iterations_b:
return self._dir_b, None
b, dir, side = self.__circularHelper(self._start_b_pulses, i + 1,
self._radius_b_pulses,
self._side_b, self._dir_b)
b, direction, side = self.__circular_helper(self._start_b_pulses, i + 1,
self._radius_b_pulses,
self._side_b, self._dir_b)
b /= pulses_per_mm
# last item can be slightly more then end angle due to float precision
if i + 1 == self._iterations_b:
return dir, self._RdivV * self._delta_angle
return direction, self._r_div_v * self._delta_angle
a = math.sqrt(self._radius2 - b * b)
if side:
a = -a
return dir, self.__circularFindTime(a, b)
return direction, self.__circular_find_time(a, b)
def __linear(self, i, total_i, pulses_per_mm, velocity):
@staticmethod
def __linear(i, total_i, pulses_per_mm, velocity):
if i >= total_i:
return None
return i / pulses_per_mm / velocity
@@ -538,22 +551,22 @@ class PulseGeneratorCircular(PulseGenerator):
for details.
"""
if self._plane == PLANE_XY:
dx, tx = self.__circularA(ix, STEPPER_PULSES_PER_MM_X)
dy, ty = self.__circularB(iy, STEPPER_PULSES_PER_MM_Y)
tz = self.__linear(iz, self._iterations_3rd, STEPPER_PULSES_PER_MM_Z,
self._velocity_3rd)
dx, tx = self.__circular_a(ix, STEPPER_PULSES_PER_MM_X)
dy, ty = self.__circular_b(iy, STEPPER_PULSES_PER_MM_Y)
tz = self.__linear(iz, self._iterations_3rd,
STEPPER_PULSES_PER_MM_Z, self._velocity_3rd)
dz = self._third_dir
elif self._plane == PLANE_YZ:
dy, ty = self.__circularA(iy, STEPPER_PULSES_PER_MM_Y)
dz, tz = self.__circularB(iz, STEPPER_PULSES_PER_MM_Z)
tx = self.__linear(ix, self._iterations_3rd, STEPPER_PULSES_PER_MM_X,
self._velocity_3rd)
dy, ty = self.__circular_a(iy, STEPPER_PULSES_PER_MM_Y)
dz, tz = self.__circular_b(iz, STEPPER_PULSES_PER_MM_Z)
tx = self.__linear(ix, self._iterations_3rd,
STEPPER_PULSES_PER_MM_X, self._velocity_3rd)
dx = self._third_dir
elif self._plane == PLANE_ZX:
dz, tz = self.__circularA(iz, STEPPER_PULSES_PER_MM_Z)
dx, tx = self.__circularB(ix, STEPPER_PULSES_PER_MM_X)
ty = self.__linear(iy, self._iterations_3rd, STEPPER_PULSES_PER_MM_Y,
self._velocity_3rd)
else: # self._plane == PLANE_ZX:
dz, tz = self.__circular_a(iz, STEPPER_PULSES_PER_MM_Z)
dx, tx = self.__circular_b(ix, STEPPER_PULSES_PER_MM_X)
ty = self.__linear(iy, self._iterations_3rd,
STEPPER_PULSES_PER_MM_Y, self._velocity_3rd)
dy = self._third_dir
te = self.__linear(ie, self._iterations_e, STEPPER_PULSES_PER_MM_E,
self._e_velocity)

View File

@@ -1,11 +1,15 @@
#!/bin/sh
set -e
PASS=raspberry
ADDR=pi@192.168.0.208
ADDRESS=pi@192.168.0.211
if [ ! -z $1 ]; then
ADDR=pi@$1
if [[ $1 == *"@"* ]]; then
ADDRESS=$1
else
ADDRESS=pi@$1
fi
fi
find . -name "*.py" -o -name "pycnc" | tar -cjf $(dirname "$0")/pycnc.tar.bz2 -T -
sshpass -p${PASS} scp $(dirname "$0")/pycnc.tar.bz2 "${ADDR}:~/pycnc"
sshpass -p${PASS} ssh -t ${ADDR} "(cd ~/pycnc && tar xvf pycnc.tar.bz2) > /dev/null" &> /dev/null
sshpass -p${PASS} ssh -t ${ADDR} "sudo pypy ~/pycnc/pycnc"
find . -name "*.py" -o -name "pycnc" -o -name "*.gcode" | tar -cjf $(dirname "$0")/pycnc.tar.bz2 -T -
sshpass -p${PASS} scp $(dirname "$0")/pycnc.tar.bz2 "${ADDRESS}:~/pycnc"
sshpass -p${PASS} ssh -t ${ADDRESS} "(cd ~/pycnc && tar xvf pycnc.tar.bz2) > /dev/null" &> /dev/null
sshpass -p${PASS} ssh -t ${ADDRESS} "sudo pypy ~/pycnc/pycnc"

View File

@@ -14,14 +14,14 @@ echo '---------------------------Unit tests---------------------------------'
python -m unittest discover "$@" --pattern="test_*.py"
echo '-------------------------Integration tests----------------------------'
app="pycnc"
if ! which $app &> /dev/null; then
if ! which ${app} &> /dev/null; then
echo "WARNING pycnc not found in path. Not installed? Using './pycnc'."
app="./pycnc"
fi
res="$($app tests/rects.gcode 2>&1)"
res="$res$($app tests/circles.gcode 2>&1)"
res="$res$($app tests/test_parser.gcode 2>&1)"
if echo "$res" | grep -q -i error; then
res="$(${app} tests/rects.gcode 2>&1)"
res="${res}$(${app} tests/circles.gcode 2>&1)"
res="${res}$(${app} tests/test_parser.gcode 2>&1)"
if echo "${res}" | grep -q -i error; then
echo "FAILED"
echo "$res"
exit 1

View File

@@ -9,7 +9,7 @@ except(IOError, ImportError):
from setuptools import setup, find_packages
setup(
name="pycnc",
version="0.1.2",
version="0.1.3",
packages=find_packages(),
scripts=['pycnc'],

View File

@@ -1,11 +1,15 @@
#!/bin/sh
set -e
PASS=raspberry
ADDR=pi@192.168.0.208
ADDRESS=pi@192.168.0.211
if [ ! -z $1 ]; then
ADDR=pi@$1
if [[ $1 == *"@"* ]]; then
ADDRESS=$1
else
ADDRESS=pi@$1
fi
fi
find cnc/hal_raspberry -name "rpgpio*.py" -o -name "pycnc" | tar -cjf $(dirname "$0")/../pycnc.tar.bz2 -T -
sshpass -p${PASS} scp $(dirname "$0")/../pycnc.tar.bz2 "${ADDR}:~/pycnc"
sshpass -p${PASS} ssh -t ${ADDR} "(cd ~/pycnc && tar xvf pycnc.tar.bz2) > /dev/null" &> /dev/null
sshpass -p${PASS} ssh -t ${ADDR} "(cd ~/pycnc && sudo pypy -m cnc.hal_raspberry.rpgpio)"
sshpass -p${PASS} scp $(dirname "$0")/../pycnc.tar.bz2 "${ADDRESS}:~/pycnc"
sshpass -p${PASS} ssh -t ${ADDRESS} "(cd ~/pycnc && tar xvf pycnc.tar.bz2) > /dev/null" &> /dev/null
sshpass -p${PASS} ssh -t ${ADDRESS} "(cd ~/pycnc && sudo pypy -m cnc.hal_raspberry.rpgpio)"

View File

@@ -131,6 +131,7 @@ class TestCoordinates(unittest.TestCase):
def test_abs(self):
c = Coordinates(-1, -2.5, -99, -23)
# noinspection PyTypeChecker
r = abs(c)
self.assertEqual(r.x, 1.0)
self.assertEqual(r.y, 2.5)

View File

@@ -1,7 +1,5 @@
import unittest
import math
from cnc.coordinates import *
from cnc.gcode import *
@@ -16,7 +14,7 @@ class TestGCode(unittest.TestCase):
# GCode shouldn't be created with constructor, but since it uses
# internally, let's check it.
self.assertRaises(TypeError, GCode)
gc = GCode({"X": "1", "Y": "-2", "Z":"0", "E": 99, "G": "1"})
gc = GCode({"X": "1", "Y": "-2", "Z": "0", "E": 99, "G": "1"})
self.assertEqual(gc.coordinates(self.default, 1).x, 1.0)
self.assertEqual(gc.coordinates(self.default, 1).y, -2.0)
self.assertEqual(gc.coordinates(self.default, 1).z, 0.0)
@@ -122,11 +120,12 @@ class TestGCode(unittest.TestCase):
gc = GCode.parse_line("X2 Y(inline comment)7")
self.assertEqual(gc.coordinates(self.default, 1).x, 2.0)
self.assertEqual(gc.coordinates(self.default, 1).y, 7.0)
gc = GCode.parse_line("X2 Y(inline comment)3 \t(one more comment) \tz4 ; multi comment test")
gc = GCode.parse_line("X2 Y(inline comment)3 \t(one more comment) "
"\tz4 ; multi comment test")
self.assertEqual(gc.coordinates(self.default, 1).x, 2.0)
self.assertEqual(gc.coordinates(self.default, 1).y, 3.0)
self.assertEqual(gc.coordinates(self.default, 1).z, 4.0)
if __name__ == '__main__':
unittest.main()
unittest.main()

View File

@@ -1,9 +1,8 @@
import unittest
import time
from cnc.coordinates import *
from cnc.gcode import *
from cnc.gmachine import *
from cnc.coordinates import *
class TestGMachine(unittest.TestCase):
@@ -83,7 +82,8 @@ class TestGMachine(unittest.TestCase):
self.assertRaises(GMachineException,
m.do_command, GCode.parse_line("G3I0J0K1"))
self.assertRaises(GMachineException,
m.do_command, GCode.parse_line("G2X99999999Y99999999I1J1"))
m.do_command, GCode.parse_line("G2X99999999Y99999999"
"I1J1"))
self.assertRaises(GMachineException,
m.do_command, GCode.parse_line("G2X2Y2Z99999999I1J1"))
self.assertEqual(m.position(), Coordinates(0, 0, 0, 0))
@@ -194,4 +194,4 @@ class TestGMachine(unittest.TestCase):
if __name__ == '__main__':
unittest.main()
unittest.main()

View File

@@ -24,5 +24,5 @@ g90
g92x100y100z100
m111
g1x98y98z98
(head should be in zero position, and last movent with 500 mm/min velocity)
(head should be in zero position, and last movement with 500 mm/min velocity)
m2

View File

@@ -1,9 +1,8 @@
import unittest
import time
from cnc.coordinates import *
from cnc.pulses import *
from cnc.config import *
from cnc.coordinates import *
from cnc import hal_virtual
@@ -35,8 +34,8 @@ class TestPulses(unittest.TestCase):
0, 0, 0),
self.v)
i = 0
for dir, px, py, pz, pe in g:
if dir:
for direction, px, py, pz, pe in g:
if direction:
continue
i += 1
self.assertEqual(px, 0)
@@ -51,8 +50,8 @@ class TestPulses(unittest.TestCase):
1.0 / STEPPER_PULSES_PER_MM_E),
self.v)
i = 0
for dir, px, py, pz, pe in g:
if dir:
for direction, px, py, pz, pe in g:
if direction:
continue
i += 1
self.assertEqual(px, 0)
@@ -61,20 +60,19 @@ class TestPulses(unittest.TestCase):
self.assertEqual(pe, 0)
self.assertEqual(i, 1)
def __check_circular(self, delta, radius, plane, direction = CW):
def __check_circular(self, delta, radius, plane, direction=CW):
g = PulseGeneratorCircular(delta, radius, plane, direction, self.v)
x, y, z, e = 0, 0, 0, 0
dx, dy, dz, de = None, None, None, None
dir_changed = 0
dir_requested = False
t = -1
for dir, px, py, pz, pe in g:
if dir:
for direction_i, px, py, pz, pe in g:
if direction_i:
dx, dy, dz, de = px, py, pz, pe
dir_requested = True
continue
if dir_requested: # ignore last change
if dir_requested: # ignore last change
dir_requested = False
dir_changed += 1
if px is not None:
@@ -97,53 +95,43 @@ class TestPulses(unittest.TestCase):
def test_single_radius_circles(self):
# Check if PulseGenerator returns correctly single radius movement in
# both direction.
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
Coordinates(1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0),
zero_delta = Coordinates(0, 0, 0, 0)
radius = Coordinates(1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0)
_, pos = self.__check_circular(zero_delta, radius, PLANE_XY, CW)
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
radius = Coordinates(-1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0)
_, pos = self.__check_circular(zero_delta, radius,
PLANE_XY, CW)
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
Coordinates(-1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0),
PLANE_XY, CW)
radius = Coordinates(0, 1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0)
_, pos = self.__check_circular(zero_delta, radius, PLANE_YZ, CW)
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
Coordinates(0, 1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0),
PLANE_YZ, CW)
radius = Coordinates(0, -1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0)
_, pos = self.__check_circular(zero_delta, radius, PLANE_YZ, CW)
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
Coordinates(0, -1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0),
PLANE_YZ, CW)
radius = Coordinates(0, 0, 1.0 / STEPPER_PULSES_PER_MM_Z, 0)
_, pos = self.__check_circular(zero_delta, radius, PLANE_ZX, CW)
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
Coordinates(0, 0, 1.0 / STEPPER_PULSES_PER_MM_Z, 0),
PLANE_ZX, CW)
radius = Coordinates(0, 0, -1.0 / STEPPER_PULSES_PER_MM_Z, 0)
_, pos = self.__check_circular(zero_delta, radius, PLANE_ZX, CW)
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
Coordinates(0, 0, -1.0 / STEPPER_PULSES_PER_MM_Z, 0),
PLANE_ZX, CW)
radius = Coordinates(1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0)
_, pos = self.__check_circular(zero_delta, radius, PLANE_XY, CCW)
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
Coordinates(1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0),
PLANE_XY, CCW)
radius = Coordinates(-1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0)
_, pos = self.__check_circular(zero_delta, radius, PLANE_XY, CCW)
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
Coordinates(-1.0 / STEPPER_PULSES_PER_MM_X, 0, 0, 0),
PLANE_XY, CCW)
radius = Coordinates(0, 1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0)
_, pos = self.__check_circular(zero_delta, radius, PLANE_YZ, CCW)
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
Coordinates(0, 1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0),
PLANE_YZ, CCW)
radius = Coordinates(0, -1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0)
_, pos = self.__check_circular(zero_delta, radius, PLANE_YZ, CCW)
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
Coordinates(0, -1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0),
PLANE_YZ, CCW)
radius = Coordinates(0, 0, 1.0 / STEPPER_PULSES_PER_MM_Z, 0)
_, pos = self.__check_circular(zero_delta, radius, PLANE_ZX, CCW)
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
Coordinates(0, 0, 1.0 / STEPPER_PULSES_PER_MM_Z, 0),
PLANE_ZX, CCW)
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
_, pos = self.__check_circular(Coordinates(0, 0, 0, 0),
Coordinates(0, 0, -1.0 / STEPPER_PULSES_PER_MM_Z, 0),
PLANE_ZX, CCW)
radius = Coordinates(0, 0, -1.0 / STEPPER_PULSES_PER_MM_Z, 0)
_, pos = self.__check_circular(zero_delta, radius, PLANE_ZX, CCW)
self.assertEqual(pos, Coordinates(0, 0, 0, 0))
def test_with_hal_virtual(self):
@@ -158,30 +146,29 @@ class TestPulses(unittest.TestCase):
hal_virtual.move(PulseGeneratorLinear(Coordinates(25.4, 0, 0, 0),
self.v))
hal_virtual.move(PulseGeneratorLinear(Coordinates(TABLE_SIZE_X_MM,
TABLE_SIZE_Y_MM,
TABLE_SIZE_Z_MM,
100.0), self.v))
TABLE_SIZE_Y_MM,
TABLE_SIZE_Z_MM,
100.0), self.v))
hal_virtual.move(PulseGeneratorCircular(Coordinates(0, 20, 0, 0),
Coordinates(-10, 10, 0, 0),
PLANE_XY, CW, self.v))
hal_virtual.move(PulseGeneratorCircular(Coordinates(-4, -4, 0, 0),
Coordinates(-2, -2, 0, 0),
PLANE_XY, CW, self.v))
hal_virtual.move(PulseGeneratorCircular(Coordinates(- 2.0 / STEPPER_PULSES_PER_MM_X,
- 2.0 / STEPPER_PULSES_PER_MM_Y,
0, 0),
Coordinates(- 1.0 / STEPPER_PULSES_PER_MM_X,
- 1.0 / STEPPER_PULSES_PER_MM_Y,
0, 0),
PLANE_XY, CW, self.v))
delta = Coordinates(- 2.0 / STEPPER_PULSES_PER_MM_X,
- 2.0 / STEPPER_PULSES_PER_MM_Y, 0, 0)
radius = Coordinates(- 1.0 / STEPPER_PULSES_PER_MM_X,
- 1.0 / STEPPER_PULSES_PER_MM_Y, 0, 0)
hal_virtual.move(PulseGeneratorCircular(delta, radius, PLANE_XY, CW,
self.v))
def test_twice_faster_linear(self):
# Checks if one axis moves exactly twice faster, pulses are correct.
m = Coordinates(2, 4, 0, 0)
g = PulseGeneratorLinear(m, self.v)
i = 0
for dir, px, py, pz, pe in g:
if dir:
for direction, px, py, pz, pe in g:
if direction:
continue
if i % 2 == 0:
self.assertNotEqual(px, None)
@@ -203,8 +190,8 @@ class TestPulses(unittest.TestCase):
iz = 0
ie = 0
t = -1
for dir, px, py, pz, pe in g:
if dir:
for direction, px, py, pz, pe in g:
if direction:
continue
if px is not None:
ix += 1
@@ -244,8 +231,9 @@ class TestPulses(unittest.TestCase):
g = PulseGeneratorLinear(m, self.v)
i = 0
lx = 0
for dir, px, py, pz, pe in g:
if dir:
lt, at, bt = None, None, None
for direction, px, py, pz, pe in g:
if direction:
continue
if i == 2:
at = px - lx
@@ -254,7 +242,8 @@ class TestPulses(unittest.TestCase):
bt = px - lx
lx = px
i += 1
self.assertEqual(round(60.0 / lt / STEPPER_PULSES_PER_MM_X), round(self.v))
self.assertEqual(round(60.0 / lt / STEPPER_PULSES_PER_MM_X),
round(self.v))
self.assertGreater(at, lt)
self.assertGreater(bt, lt)
@@ -263,8 +252,8 @@ class TestPulses(unittest.TestCase):
m = Coordinates(1, -2, 3, -4)
g = PulseGeneratorLinear(m, self.v)
dir_found = False
for dir, px, py, pz, pe in g:
if dir:
for direction, px, py, pz, pe in g:
if direction:
# should be once
self.assertFalse(dir_found)
dir_found = True
@@ -273,8 +262,8 @@ class TestPulses(unittest.TestCase):
m = Coordinates(-1, 2, -3, 4)
g = PulseGeneratorLinear(m, self.v)
dir_found = False
for dir, px, py, pz, pe in g:
if dir:
for direction, px, py, pz, pe in g:
if direction:
# should be once
self.assertFalse(dir_found)
dir_found = True