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"> <component name="InspectionProjectProfileManager">
<profile version="1.0"> <profile version="1.0">
<option name="myName" value="Project Default" /> <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"> <inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ourVersions"> <option name="ourVersions">
<value> <value>
@@ -11,5 +14,14 @@
</value> </value>
</option> </option>
</inspection_tool> </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> </profile>
</component> </component>

View File

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

View File

@@ -1,5 +1,5 @@
# Hardware limitations config # 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_VELOCITY_MM_PER_MIN = 1800 # mm per min
STEPPER_MAX_ACCELERATION_MM_PER_S2 = 200 # mm per sec^2 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. """ Check if all coordinates are zero.
:return: boolean value. :return: boolean value.
""" """
return self.x == 0.0 and self.y == 0.0 and self.z == 0.0 and \ return (self.x == 0.0 and self.y == 0.0 and self.z == 0.0
self.e == 0.0 and self.e == 0.0)
def is_in_aabb(self, p1, p2): def is_in_aabb(self, p1, p2):
""" Check coordinates are in aabb(Axis-Aligned Bounding Box). """ Check coordinates are in aabb(Axis-Aligned Bounding Box).
@@ -31,12 +31,12 @@ class Coordinates(object):
:param p2: Second point in Coord object. :param p2: Second point in Coord object.
:return: boolean value. :return: boolean value.
""" """
minx, maxx = sorted((p1.x, p2.x)) min_x, max_x = sorted((p1.x, p2.x))
miny, maxy = sorted((p1.y, p2.y)) min_y, max_y = sorted((p1.y, p2.y))
minz, maxz = sorted((p1.z, p2.z)) min_z, max_z = sorted((p1.z, p2.z))
if self.x < minx or self.y < miny or self.z < minz: if self.x < min_x or self.y < min_y or self.z < min_z:
return False 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 False
return True return True
@@ -76,12 +76,21 @@ class Coordinates(object):
self.z - other.z, self.e - other.e) self.z - other.z, self.e - other.e)
def __mul__(self, v): def __mul__(self, v):
"""
@rtype: Coordinates
"""
return Coordinates(self.x * v, self.y * v, self.z * v, self.e * v) return Coordinates(self.x * v, self.y * v, self.z * v, self.e * v)
def __div__(self, v): def __div__(self, v):
"""
@rtype: Coordinates
"""
return Coordinates(self.x / v, self.y / v, self.z / v, self.e / v) return Coordinates(self.x / v, self.y / v, self.z / v, self.e / v)
def __truediv__(self, v): def __truediv__(self, v):
"""
@rtype: Coordinates
"""
return Coordinates(self.x / v, self.y / v, self.z / v, self.e / v) return Coordinates(self.x / v, self.y / v, self.z / v, self.e / v)
def __eq__(self, other): def __eq__(self, other):

View File

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

View File

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

View File

@@ -1,23 +1,9 @@
import logging
import time import time
from cnc.hal_raspberry import rpgpio from cnc.hal_raspberry import rpgpio
from cnc.pulses import *
from cnc.pulses import PulseGeneratorLinear
from cnc.coordinates import Coordinates
from cnc.config 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 US_IN_SECONDS = 1000000
gpio = rpgpio.GPIO() 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_Z = 1 << STEPPER_STEP_PIN_Z
STEP_PIN_MASK_E = 1 << STEPPER_STEP_PIN_E 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 calibration if
needed. Do not return till all procedures are 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)
@@ -53,7 +40,7 @@ def init():
gpio.set(STEPPER_DIR_PIN_Z) gpio.set(STEPPER_DIR_PIN_Z)
pins = STEP_PIN_MASK_X | STEP_PIN_MASK_Y | STEP_PIN_MASK_Z pins = STEP_PIN_MASK_X | STEP_PIN_MASK_Y | STEP_PIN_MASK_Z
dma.clear() dma.clear()
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US) dma.add_pulse(pins, STEPPER_PULSE_LENGTH_US)
st = time.time() st = time.time()
max_pulses_left = int(1.2 * max(STEPPER_PULSES_PER_MM_X, max_pulses_left = int(1.2 * max(STEPPER_PULSES_PER_MM_X,
STEPPER_PULSES_PER_MM_Y, 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: if (STEP_PIN_MASK_X & pins) != 0 and gpio.read(ENDSTOP_PIN_X) == 0:
pins &= ~STEP_PIN_MASK_X pins &= ~STEP_PIN_MASK_X
dma.clear() 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: if (STEP_PIN_MASK_Y & pins) != 0 and gpio.read(ENDSTOP_PIN_Y) == 0:
pins &= ~STEP_PIN_MASK_Y pins &= ~STEP_PIN_MASK_Y
dma.clear() 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: if (STEP_PIN_MASK_Z & pins) != 0 and gpio.read(ENDSTOP_PIN_Z) == 0:
pins &= ~STEP_PIN_MASK_Z pins &= ~STEP_PIN_MASK_Z
dma.clear() dma.clear()
dma.add_pulse(pins, STEPPER_PULSE_LINGTH_US) dma.add_pulse(pins, STEPPER_PULSE_LENGTH_US)
if pins == 0: if pins == 0:
break break
dma.run(False) dma.run(False)
@@ -122,8 +109,8 @@ def move(generator):
is_ran = False is_ran = False
instant = INSTANT_RUN instant = INSTANT_RUN
st = time.time() st = time.time()
for dir, tx, ty, tz, te in generator: for direction, tx, ty, tz, te in generator:
if dir: # set up directions if direction: # set up directions
pins_to_set = 0 pins_to_set = 0
pins_to_clear = 0 pins_to_clear = 0
if tx > 0: if tx > 0:
@@ -160,11 +147,11 @@ def move(generator):
pins |= STEP_PIN_MASK_E pins |= STEP_PIN_MASK_E
if k - prev > 0: if k - prev > 0:
dma.add_delay(k - prev) 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 # 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 # if next pulse start during pulse length. Though it almost doesn't
# matter for pulses with 1-2us length. # matter for pulses with 1-2us length.
prev = k + STEPPER_PULSE_LINGTH_US prev = k + STEPPER_PULSE_LENGTH_US
# instant run handling # instant run handling
if not is_ran and instant: if not is_ran and instant:
if k > 500000: # wait at least 500 ms is uploaded 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) 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 = self._mem.read_int(GPIO_PULLUPDN_OFFSET)
p &= ~3 p &= ~3
if mode == self.MODE_INPUT_PULLUP: if mode == self.MODE_INPUT_PULLUP:
@@ -28,49 +28,49 @@ class GPIO(object):
elif mode == self.MODE_INPUT_PULLDOWN: elif mode == self.MODE_INPUT_PULLDOWN:
p |= 1 p |= 1
self._mem.write_int(GPIO_PULLUPDN_OFFSET, p) self._mem.write_int(GPIO_PULLUPDN_OFFSET, p)
addr = 4 * int(pin / 32) + GPIO_PULLUPDNCLK_OFFSET address = 4 * int(pin / 32) + GPIO_PULLUPDNCLK_OFFSET
self._mem.write_int(addr, 1 << (pin % 32)) self._mem.write_int(address, 1 << (pin % 32))
p = self._mem.read_int(GPIO_PULLUPDN_OFFSET) p = self._mem.read_int(GPIO_PULLUPDN_OFFSET)
p &= ~3 p &= ~3
self._mem.write_int(GPIO_PULLUPDN_OFFSET, p) 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): def init(self, pin, mode):
""" Initialize or re-initialize GPIO pin. """ Initialize or re-initialize GPIO pin.
:param pin: pin number. :param pin: pin number.
:param mode: one of MODE_* variables in this class. :param mode: one of MODE_* variables in this class.
""" """
addr = 4 * int(pin / 10) + GPIO_FSEL_OFFSET address = 4 * int(pin / 10) + GPIO_FSEL_OFFSET
v = self._mem.read_int(addr) v = self._mem.read_int(address)
v &= ~(7 << ((pin % 10) * 3)) # input value v &= ~(7 << ((pin % 10) * 3)) # input value
if mode == self.MODE_OUTPUT: if mode == self.MODE_OUTPUT:
v |= (1 << ((pin % 10) * 3)) # output value, base on input v |= (1 << ((pin % 10) * 3)) # output value, base on input
self._mem.write_int(addr, v) self._mem.write_int(address, v)
else: else:
self._mem.write_int(addr, v) self._mem.write_int(address, v)
self._pullupdn(pin, mode) self._pull_up_dn(pin, mode)
def set(self, pin): def set(self, pin):
""" Set pin to HIGH state. """ Set pin to HIGH state.
:param pin: pin number. :param pin: pin number.
""" """
addr = 4 * int(pin / 32) + GPIO_SET_OFFSET address = 4 * int(pin / 32) + GPIO_SET_OFFSET
self._mem.write_int(addr, 1 << (pin % 32)) self._mem.write_int(address, 1 << (pin % 32))
def clear(self, pin): def clear(self, pin):
""" Set pin to LOW state. """ Set pin to LOW state.
:param pin: pin number. :param pin: pin number.
""" """
addr = 4 * int(pin / 32) + GPIO_CLEAR_OFFSET address = 4 * int(pin / 32) + GPIO_CLEAR_OFFSET
self._mem.write_int(addr, 1 << (pin % 32)) self._mem.write_int(address, 1 << (pin % 32))
def read(self, pin): def read(self, pin):
""" Read pin current value. """ Read pin current value.
:param pin: pin number. :param pin: pin number.
:return: integer value 0 or 1. :return: integer value 0 or 1.
""" """
addr = 4 * int(pin / 32) + GPIO_INPUT_OFFSET address = 4 * int(pin / 32) + GPIO_INPUT_OFFSET
v = self._mem.read_int(addr) v = self._mem.read_int(address)
v &= 1 << (pin % 32) v &= 1 << (pin % 32)
if v == 0: if v == 0:
return 0 return 0
@@ -102,20 +102,20 @@ class DMAGPIO(DMAProto):
self._clock = PhysicalMemory(PERI_BASE + CM_BASE) self._clock = PhysicalMemory(PERI_BASE + CM_BASE)
# pre calculated variables for control blocks # pre calculated variables for control blocks
self._delay_info = DMA_TI_NO_WIDE_BURSTS | DMA_SRC_IGNORE \ self._delay_info = (DMA_TI_NO_WIDE_BURSTS | DMA_SRC_IGNORE
| DMA_TI_PER_MAP(DMA_TI_PER_MAP_PWM) \ | DMA_TI_PER_MAP(DMA_TI_PER_MAP_PWM)
| DMA_TI_DEST_DREQ | DMA_TI_DEST_DREQ)
self._delay_destination = PHYSICAL_PWM_BUS + PWM_FIFO self._delay_destination = PHYSICAL_PWM_BUS + PWM_FIFO
self._delay_stride = 0 self._delay_stride = 0
self._pulse_info = DMA_TI_NO_WIDE_BURSTS | DMA_TI_TDMODE \ self._pulse_info = (DMA_TI_NO_WIDE_BURSTS | DMA_TI_TDMODE
| DMA_TI_WAIT_RESP | DMA_TI_WAIT_RESP)
self._pulse_destination = PHYSICAL_GPIO_BUS + GPIO_SET_OFFSET self._pulse_destination = PHYSICAL_GPIO_BUS + GPIO_SET_OFFSET
# YLENGTH is transfers count and XLENGTH size of each transfer # YLENGTH is transfers count and XLENGTH size of each transfer
self._pulse_length = DMA_TI_TXFR_LEN_YLENGTH(2) \ self._pulse_length = (DMA_TI_TXFR_LEN_YLENGTH(2)
| DMA_TI_TXFR_LEN_XLENGTH(4) | DMA_TI_TXFR_LEN_XLENGTH(4))
self._pulse_stride = DMA_TI_STRIDE_D_STRIDE(12) \ self._pulse_stride = (DMA_TI_STRIDE_D_STRIDE(12)
| DMA_TI_STRIDE_S_STRIDE(4) | DMA_TI_STRIDE_S_STRIDE(4))
def add_pulse(self, pins_mask, length_us): def add_pulse(self, pins_mask, length_us):
""" Add single pulse at the current position. """ Add single pulse at the current position.
@@ -126,9 +126,9 @@ class DMAGPIO(DMAProto):
:param length_us: length in us. :param length_us: length in us.
""" """
next_cb = self.__current_address + 3 * self._DMA_CONTROL_BLOCK_SIZE 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.") 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 next2 = next3 - self._DMA_CONTROL_BLOCK_SIZE
next1 = next2 - self._DMA_CONTROL_BLOCK_SIZE next1 = next2 - self._DMA_CONTROL_BLOCK_SIZE
@@ -137,16 +137,17 @@ class DMAGPIO(DMAProto):
source3 = next3 - 8 source3 = next3 - 8
data = ( data = (
# control block 1 - set
self._pulse_info, source1, self._pulse_destination, self._pulse_info, source1, self._pulse_destination,
self._pulse_length, self._pulse_length, self._pulse_stride, next1, pins_mask, 0,
self._pulse_stride, next1, pins_mask, 0, # control block 2 - delay
self._delay_info, 0, self._delay_destination, length2, self._delay_info, 0, self._delay_destination, length2,
self._delay_stride, next2, 0, 0, self._delay_stride, next2, 0, 0,
# control block 3 - clear
self._pulse_info, source3, self._pulse_destination, self._pulse_info, source3, self._pulse_destination,
self._pulse_length, self._pulse_length, self._pulse_stride, next3, 0, pins_mask
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 self.__current_address = next_cb
def add_delay(self, delay_us): def add_delay(self, delay_us):
@@ -154,16 +155,16 @@ class DMAGPIO(DMAProto):
:param delay_us: delay in us. :param delay_us: delay in us.
""" """
next_cb = self.__current_address + self._DMA_CONTROL_BLOCK_SIZE 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.") 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 source = next1 - 8 # last 8 bytes are padding, use it to store data
length = delay_us << 4 # * 16 length = delay_us << 4 # * 16
data = ( data = (
self._delay_info, source, self._delay_destination, length, self._delay_info, source, self._delay_destination, length,
self._delay_stride, next1, 0, 0 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 self.__current_address = next_cb
def add_set_clear(self, pins_to_set, pins_to_clear): 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. :param pins_to_clear: bitwise mask which pins should be clear.
""" """
next_cb = self.__current_address + self._DMA_CONTROL_BLOCK_SIZE 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.") 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 source = next1 - 8 # last 8 bytes are padding, use it to store data
data = ( data = (
self._pulse_info, source, self._pulse_destination, self._pulse_info, source, self._pulse_destination,
self._pulse_length, self._pulse_length, self._pulse_stride, next1,
self._pulse_stride, next1, pins_to_set, pins_to_clear 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 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.
""" """
self._physmem.write_int(self.__current_address + 20 self._phys_memory.write_int(self.__current_address + 20
- self._DMA_CONTROL_BLOCK_SIZE, 0) - self._DMA_CONTROL_BLOCK_SIZE, 0)
logging.info("DMA took {}MB of memory". logging.info("DMA took {}MB of memory".
format(round(self.__current_address / 1024.0 / 1024.0, 2))) 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 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: while (self._clock.read_int(CM_PWM_CNTL) & CM_CNTL_BUSY) != 0:
time.sleep(0.00001) # 10 us, wait until BUSY bit is clear 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_DIV,
self._clock.write_int(CM_PWM_CNTL, CM_PASSWORD | CM_SRC_PLLD | CM_PASSWORD | CM_DIV_VALUE(5)) # 100MHz
CM_CNTL_ENABLE) 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_RNG1, 100)
self._pwm.write_int(PWM_DMAC, PWM_DMAC_ENAB self._pwm.write_int(PWM_DMAC, PWM_DMAC_ENAB
@@ -220,9 +222,9 @@ class DMAGPIO(DMAProto):
raise RuntimeError("Nothing was added.") raise RuntimeError("Nothing was added.")
# fix 'next' field in previous control block # fix 'next' field in previous control block
if loop: if loop:
self._physmem.write_int(self.__current_address + 20 self._phys_memory.write_int(self.__current_address + 20
- self._DMA_CONTROL_BLOCK_SIZE, - self._DMA_CONTROL_BLOCK_SIZE,
self._physmem.get_bus_address()) self._phys_memory.get_bus_address())
else: else:
self.finalize_stream() self.finalize_stream()
self.run_stream() self.run_stream()
@@ -267,17 +269,17 @@ class DMAPWM(DMAProto):
self.__add_control_block(i * self._DMA_CONTROL_BLOCK_SIZE, self.__add_control_block(i * self._DMA_CONTROL_BLOCK_SIZE,
GPIO_CLEAR_OFFSET) GPIO_CLEAR_OFFSET)
# loop # loop
self._physmem.write_int((self._TOTAL_NUMBER_OF_BLOCKS - 1) self._phys_memory.write_int((self._TOTAL_NUMBER_OF_BLOCKS - 1)
* self._DMA_CONTROL_BLOCK_SIZE + 20, * self._DMA_CONTROL_BLOCK_SIZE + 20,
self._physmem.get_bus_address()) self._phys_memory.get_bus_address())
self._gpio = PhysicalMemory(PERI_BASE + GPIO_REGISTER_BASE) self._gpio = PhysicalMemory(PERI_BASE + GPIO_REGISTER_BASE)
def __add_control_block(self, address, offset): def __add_control_block(self, address, offset):
ba = self._physmem.get_bus_address() + address ba = self._phys_memory.get_bus_address() + address
data = ( data = (
DMA_TI_NO_WIDE_BURSTS | DMA_TI_WAIT_RESP DMA_TI_NO_WIDE_BURSTS | DMA_TI_WAIT_RESP
| DMA_TI_DEST_INC | DMA_TI_SRC_INC, # info | 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 PHYSICAL_GPIO_BUS + offset, # destination
4, # length 4, # length
0, # stride 0, # stride
@@ -285,7 +287,7 @@ class DMAPWM(DMAProto):
0, # padding, uses as data storage 0, # padding, uses as data storage
0 # padding 0 # padding
) )
self._physmem.write(address, "8I", data) self._phys_memory.write(address, "8I", data)
def add_pin(self, pin, duty_cycle): def add_pin(self, pin, duty_cycle):
""" Add pin to PMW with specified 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._gpio.write_int(GPIO_SET_OFFSET, 1 << pin)
self._clear_pins[pin] = self._DMA_DATA_OFFSET self._clear_pins[pin] = self._DMA_DATA_OFFSET
else: else:
value = self._physmem.read_int(self._DMA_DATA_OFFSET) value = self._phys_memory.read_int(self._DMA_DATA_OFFSET)
value |= 1 << pin value |= 1 << pin
self._physmem.write_int(self._DMA_DATA_OFFSET, value) self._phys_memory.write_int(self._DMA_DATA_OFFSET, value)
clear_address = block_number * self._DMA_CONTROL_BLOCK_SIZE \ clear_address = (block_number * self._DMA_CONTROL_BLOCK_SIZE
+ self._DMA_DATA_OFFSET + self._DMA_DATA_OFFSET)
value = self._physmem.read_int(clear_address) value = self._phys_memory.read_int(clear_address)
value |= 1 << pin value |= 1 << pin
self._physmem.write_int(clear_address, value) self._phys_memory.write_int(clear_address, value)
self._clear_pins[pin] = clear_address self._clear_pins[pin] = clear_address
if not self.is_active(): if not self.is_active():
super(DMAPWM, self)._run_dma() super(DMAPWM, self)._run_dma()
@@ -321,12 +323,12 @@ class DMAPWM(DMAProto):
assert 0 <= pin < 32 assert 0 <= pin < 32
if pin in self._clear_pins.keys(): if pin in self._clear_pins.keys():
address = self._clear_pins[pin] address = self._clear_pins[pin]
value = self._physmem.read_int(address) value = self._phys_memory.read_int(address)
value &= ~(1 << pin) value &= ~(1 << pin)
self._physmem.write_int(address, value) self._phys_memory.write_int(address, value)
value = self._physmem.read_int(self._DMA_DATA_OFFSET) value = self._phys_memory.read_int(self._DMA_DATA_OFFSET)
value &= ~(1 << pin) 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] del self._clear_pins[pin]
self._gpio.write_int(GPIO_CLEAR_OFFSET, 1 << pin) self._gpio.write_int(GPIO_CLEAR_OFFSET, 1 << pin)
if len(self._clear_pins) == 0 and self.is_active(): 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_CS_ACTIVE = 1 << 0
DMA_TI_PER_MAP_PWM = 5 DMA_TI_PER_MAP_PWM = 5
DMA_TI_PER_MAP_PCM = 2 DMA_TI_PER_MAP_PCM = 2
DMA_TI_PER_MAP = (lambda x: x << 16)
def DMA_TI_PER_MAP(x): DMA_TI_TXFR_LEN_YLENGTH = (lambda y: (y & 0x3fff) << 16)
return x << 16 DMA_TI_TXFR_LEN_XLENGTH = (lambda x: x & 0xffff)
DMA_TI_STRIDE_D_STRIDE = (lambda x: (x & 0xffff) << 16)
def DMA_TI_TXFR_LEN_YLENGTH(y): DMA_TI_STRIDE_S_STRIDE = (lambda x: x & 0xffff)
return (y & 0x3fff) << 16 DMA_CS_PRIORITY = (lambda x: (x & 0xf) << 16)
DMA_CS_PANIC_PRIORITY = (lambda x: (x & 0xf) << 20)
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
# hardware PWM controller registers # hardware PWM controller registers
PWM_BASE = 0x0020C000 PWM_BASE = 0x0020C000
PHYSICAL_PWM_BUS = 0x7E000000 + PWM_BASE PHYSICAL_PWM_BUS = 0x7E000000 + PWM_BASE
PWM_CTL= 0x00 PWM_CTL = 0x00
PWM_DMAC = 0x08 PWM_DMAC = 0x08
PWM_RNG1 = 0x10 PWM_RNG1 = 0x10
PWM_RNG2 = 0x20 PWM_RNG2 = 0x20
@@ -95,12 +81,8 @@ PWM_CTL_CLRF = 1 << 6
PWM_CTL_USEF1 = 1 << 5 PWM_CTL_USEF1 = 1 << 5
PWM_CTL_USEF2 = 1 << 13 PWM_CTL_USEF2 = 1 << 13
PWM_DMAC_ENAB = 1 << 31 PWM_DMAC_ENAB = 1 << 31
PWM_DMAC_PANIC = (lambda x: x << 8)
def PWM_DMAC_PANIC(x): PWM_DMAC_DREQ = (lambda x: x)
return x << 8
def PWM_DMAC_DREQ(x):
return x
# clock manager module # clock manager module
CM_BASE = 0x00101000 CM_BASE = 0x00101000
@@ -113,14 +95,13 @@ CM_CNTL_ENABLE = 1 << 4
CM_CNTL_BUSY = 1 << 7 CM_CNTL_BUSY = 1 << 7
CM_SRC_OSC = 1 # 19.2 MHz CM_SRC_OSC = 1 # 19.2 MHz
CM_SRC_PLLC = 5 # 1000 MHz CM_SRC_PLLC = 5 # 1000 MHz
CM_SRC_PLLD = 6 # 500 MHz CM_SRC_PLLD = 6 # 500 MHz
CM_SRC_HDMI = 7 # 216 MHz CM_SRC_HDMI = 7 # 216 MHz
CM_DIV_VALUE = (lambda x: x << 12)
def CM_DIV_VALUE(x):
return x << 12
class PhysicalMemory(object): class PhysicalMemory(object):
# noinspection PyArgumentList,PyArgumentList
def __init__(self, phys_address, size=PAGE_SIZE): def __init__(self, phys_address, size=PAGE_SIZE):
""" Create object which maps physical memory to Python's mmap object. """ Create object which maps physical memory to Python's mmap object.
:param phys_address: based address of physical memory :param phys_address: based address of physical memory
@@ -128,14 +109,14 @@ class PhysicalMemory(object):
self._size = size self._size = size
phys_address -= phys_address % PAGE_SIZE phys_address -= phys_address % PAGE_SIZE
fd = self._open_dev("/dev/mem") fd = self._open_dev("/dev/mem")
self._rmap = mmap.mmap(fd, size, flags=mmap.MAP_SHARED, self._memmap = mmap.mmap(fd, size, flags=mmap.MAP_SHARED,
prot=mmap.PROT_READ | mmap.PROT_WRITE, prot=mmap.PROT_READ | mmap.PROT_WRITE,
offset=phys_address) offset=phys_address)
self._close_dev(fd) self._close_dev(fd)
atexit.register(self.cleanup) atexit.register(self.cleanup)
def cleanup(self): def cleanup(self):
self._rmap.close() self._memmap.close()
@staticmethod @staticmethod
def _open_dev(name): def _open_dev(name):
@@ -149,13 +130,13 @@ class PhysicalMemory(object):
os.close(fd) os.close(fd)
def write_int(self, address, int_value): 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): 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): 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): def get_size(self):
return self._size return self._size
@@ -177,8 +158,8 @@ class CMAPhysicalMemory(PhysicalMemory):
if self._handle == 0: if self._handle == 0:
raise OSError("No memory to allocate with /dev/vcio") raise OSError("No memory to allocate with /dev/vcio")
# lock memory # lock memory
self._busmem = self._send_data(0x3000d, [self._handle]) self._bus_memory = self._send_data(0x3000d, [self._handle])
if self._busmem == 0: if self._bus_memory == 0:
# memory should be freed in __del__ # memory should be freed in __del__
raise OSError("Failed to lock memory with /dev/vcio") raise OSError("Failed to lock memory with /dev/vcio")
# print("allocate {} at {} (bus {})".format(size, # print("allocate {} at {} (bus {})".format(size,
@@ -206,10 +187,10 @@ class CMAPhysicalMemory(PhysicalMemory):
return data[5] return data[5]
def get_bus_address(self): def get_bus_address(self):
return self._busmem return self._bus_memory
def get_phys_address(self): def get_phys_address(self):
return self._busmem & ~0xc0000000 return self._bus_memory & ~0xc0000000
class DMAProto(object): class DMAProto(object):
@@ -219,7 +200,7 @@ class DMAProto(object):
""" """
self._DMA_CHANNEL = dma_channel self._DMA_CHANNEL = dma_channel
# allocate buffer for control blocks # allocate buffer for control blocks
self._physmem = CMAPhysicalMemory(memory_size) self._phys_memory = CMAPhysicalMemory(memory_size)
# prepare dma registers memory map # prepare dma registers memory map
self._dma = PhysicalMemory(PERI_BASE + DMA_BASE) self._dma = PhysicalMemory(PERI_BASE + DMA_BASE)
@@ -228,7 +209,8 @@ class DMAProto(object):
""" """
address = 0x100 * self._DMA_CHANNEL address = 0x100 * self._DMA_CHANNEL
self._dma.write_int(address + DMA_CS, DMA_CS_END) 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 cs = DMA_CS_PRIORITY(7) | DMA_CS_PANIC_PRIORITY(7) | DMA_CS_DISDEBUG
self._dma.write_int(address + DMA_CS, cs) self._dma.write_int(address + DMA_CS, cs)
cs |= DMA_CS_ACTIVE cs |= DMA_CS_ACTIVE

View File

@@ -1,10 +1,8 @@
from __future__ import division from __future__ import division
import logging
import time import time
from cnc.pulses import PulseGeneratorLinear, PulseGeneratorCircular from cnc.pulses import *
from cnc.config import * from cnc.config import *
from cnc.coordinates import Coordinates
""" This is virtual device class which is very useful for debugging. """ This is virtual device class which is very useful for debugging.
It checks PulseGenerator with some tests. It checks PulseGenerator with some tests.
@@ -25,6 +23,7 @@ def spindle_control(percent):
logging.info("spindle control: {}%".format(percent)) logging.info("spindle control: {}%".format(percent))
# noinspection PyUnusedLocal
def move(generator): def move(generator):
""" Move head to specified position. """ Move head to specified position.
:param generator: PulseGenerator object. :param generator: PulseGenerator object.
@@ -35,28 +34,28 @@ def move(generator):
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
cx, cy, cz, ce = 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() st = time.time()
direction_found = False direction_found = False
for dir, tx, ty, tz, te in generator: for direction, tx, ty, tz, te in generator:
if dir: if direction:
direction_found = True 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): if isinstance(generator, PulseGeneratorLinear):
assert (tx < 0 and delta.x < 0) or (tx > 0 and delta.x > 0) \ assert ((tx < 0 and delta.x < 0) or (tx > 0 and delta.x > 0)
or delta.x == 0 or delta.x == 0)
assert (ty < 0 and delta.y < 0) or (ty > 0 and delta.y > 0) \ assert ((ty < 0 and delta.y < 0) or (ty > 0 and delta.y > 0)
or delta.y == 0 or delta.y == 0)
assert (tz < 0 and delta.z < 0) or (tz > 0 and delta.z > 0) \ assert ((tz < 0 and delta.z < 0) or (tz > 0 and delta.z > 0)
or delta.z == 0 or delta.z == 0)
assert (te < 0 and delta.e < 0) or (te > 0 and delta.e > 0) \ assert ((te < 0 and delta.e < 0) or (te > 0 and delta.e > 0)
or delta.e == 0 or delta.e == 0)
continue continue
if tx is not None: if tx is not None:
if tx > mx: if tx > mx:
mx = tx mx = tx
tx = int(round(tx * 1000000)) tx = int(round(tx * 1000000))
ix += dirx ix += direction_x
cx += 1 cx += 1
if lx is not None: if lx is not None:
dx = tx - lx dx = tx - lx
@@ -68,7 +67,7 @@ def move(generator):
if ty > my: if ty > my:
my = ty my = ty
ty = int(round(ty * 1000000)) ty = int(round(ty * 1000000))
iy += diry iy += direction_y
cy += 1 cy += 1
if ly is not None: if ly is not None:
dy = ty - ly dy = ty - ly
@@ -80,7 +79,7 @@ def move(generator):
if tz > mz: if tz > mz:
mz = tz mz = tz
tz = int(round(tz * 1000000)) tz = int(round(tz * 1000000))
iz += dirz iz += direction_z
cz += 1 cz += 1
if lz is not None: if lz is not None:
dz = tz - lz dz = tz - lz
@@ -101,7 +100,8 @@ def move(generator):
else: else:
de = None de = None
# very verbose, uncomment on demand # 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) 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()
@@ -110,10 +110,11 @@ def move(generator):
assert iy / STEPPER_PULSES_PER_MM_Y == delta.y, "y wrong number of pulses" 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 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 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.debug("Moved {}, {}, {}, {} iterations".format(ix, iy, iz, ie))
logging.info("prepared in " + str(round(pt - st, 2)) \ logging.info("prepared in " + str(round(pt - st, 2)) + "s, estimated "
+ "s, estimated " + str(round(generator.total_time_s(), 2)) + "s") + str(round(generator.total_time_s(), 2)) + "s")
def join(): 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 #!/usr/bin/env python
import os
import sys import sys
import readline import readline
import logging import atexit
logging.basicConfig(level=logging.CRITICAL,
format='[%(levelname)s] %(message)s')
import cnc.logging_config as logging_config
from cnc.gcode import GCode, GCodeException from cnc.gcode import GCode, GCodeException
from cnc.gmachine import GMachine, GMachineException from cnc.gmachine import GMachine, GMachineException
try: # python3 compatibility try: # python3 compatibility
type(raw_input) type(raw_input)
except NameError: except NameError:
# noinspection PyShadowingBuiltins
raw_input = input 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() machine = GMachine()
@@ -29,6 +39,7 @@ def do_line(line):
def main(): def main():
logging_config.debug_disable()
try: try:
if len(sys.argv) > 1: if len(sys.argv) > 1:
# Read file with gcode # Read file with gcode
@@ -53,6 +64,6 @@ def main():
print("\r\nExiting...") print("\r\nExiting...")
machine.release() machine.release()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

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

View File

@@ -1,11 +1,15 @@
#!/bin/sh #!/bin/sh
set -e set -e
PASS=raspberry PASS=raspberry
ADDR=pi@192.168.0.208 ADDRESS=pi@192.168.0.211
if [ ! -z $1 ]; then if [ ! -z $1 ]; then
ADDR=pi@$1 if [[ $1 == *"@"* ]]; then
ADDRESS=$1
else
ADDRESS=pi@$1
fi
fi fi
find . -name "*.py" -o -name "pycnc" | tar -cjf $(dirname "$0")/pycnc.tar.bz2 -T - 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 "${ADDR}:~/pycnc" sshpass -p${PASS} scp $(dirname "$0")/pycnc.tar.bz2 "${ADDRESS}:~/pycnc"
sshpass -p${PASS} ssh -t ${ADDR} "(cd ~/pycnc && tar xvf pycnc.tar.bz2) > /dev/null" &> /dev/null sshpass -p${PASS} ssh -t ${ADDRESS} "(cd ~/pycnc && tar xvf pycnc.tar.bz2) > /dev/null" &> /dev/null
sshpass -p${PASS} ssh -t ${ADDR} "sudo pypy ~/pycnc/pycnc" 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" python -m unittest discover "$@" --pattern="test_*.py"
echo '-------------------------Integration tests----------------------------' echo '-------------------------Integration tests----------------------------'
app="pycnc" 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'." echo "WARNING pycnc not found in path. Not installed? Using './pycnc'."
app="./pycnc" app="./pycnc"
fi fi
res="$($app tests/rects.gcode 2>&1)" res="$(${app} tests/rects.gcode 2>&1)"
res="$res$($app tests/circles.gcode 2>&1)" res="${res}$(${app} tests/circles.gcode 2>&1)"
res="$res$($app tests/test_parser.gcode 2>&1)" res="${res}$(${app} tests/test_parser.gcode 2>&1)"
if echo "$res" | grep -q -i error; then if echo "${res}" | grep -q -i error; then
echo "FAILED" echo "FAILED"
echo "$res" echo "$res"
exit 1 exit 1

View File

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

View File

@@ -1,11 +1,15 @@
#!/bin/sh #!/bin/sh
set -e set -e
PASS=raspberry PASS=raspberry
ADDR=pi@192.168.0.208 ADDRESS=pi@192.168.0.211
if [ ! -z $1 ]; then if [ ! -z $1 ]; then
ADDR=pi@$1 if [[ $1 == *"@"* ]]; then
ADDRESS=$1
else
ADDRESS=pi@$1
fi
fi fi
find cnc/hal_raspberry -name "rpgpio*.py" -o -name "pycnc" | tar -cjf $(dirname "$0")/../pycnc.tar.bz2 -T - 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} scp $(dirname "$0")/../pycnc.tar.bz2 "${ADDRESS}:~/pycnc"
sshpass -p${PASS} ssh -t ${ADDR} "(cd ~/pycnc && tar xvf pycnc.tar.bz2) > /dev/null" &> /dev/null sshpass -p${PASS} ssh -t ${ADDRESS} "(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} 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): def test_abs(self):
c = Coordinates(-1, -2.5, -99, -23) c = Coordinates(-1, -2.5, -99, -23)
# noinspection PyTypeChecker
r = abs(c) r = abs(c)
self.assertEqual(r.x, 1.0) self.assertEqual(r.x, 1.0)
self.assertEqual(r.y, 2.5) self.assertEqual(r.y, 2.5)

View File

@@ -1,7 +1,5 @@
import unittest import unittest
import math
from cnc.coordinates import *
from cnc.gcode import * from cnc.gcode import *
@@ -16,7 +14,7 @@ class TestGCode(unittest.TestCase):
# GCode shouldn't be created with constructor, but since it uses # GCode shouldn't be created with constructor, but since it uses
# internally, let's check it. # internally, let's check it.
self.assertRaises(TypeError, GCode) 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).x, 1.0)
self.assertEqual(gc.coordinates(self.default, 1).y, -2.0) self.assertEqual(gc.coordinates(self.default, 1).y, -2.0)
self.assertEqual(gc.coordinates(self.default, 1).z, 0.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") 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).x, 2.0)
self.assertEqual(gc.coordinates(self.default, 1).y, 7.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).x, 2.0)
self.assertEqual(gc.coordinates(self.default, 1).y, 3.0) self.assertEqual(gc.coordinates(self.default, 1).y, 3.0)
self.assertEqual(gc.coordinates(self.default, 1).z, 4.0) self.assertEqual(gc.coordinates(self.default, 1).z, 4.0)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

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

View File

@@ -24,5 +24,5 @@ g90
g92x100y100z100 g92x100y100z100
m111 m111
g1x98y98z98 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 m2

View File

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