mirror of
https://github.com/sinseman44/PyCNC.git
synced 2026-01-11 02:30:05 +00:00
refactoring
This commit is contained in:
11
.idea/codeStyleSettings.xml
generated
Normal file
11
.idea/codeStyleSettings.xml
generated
Normal 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>
|
||||||
12
.idea/inspectionProfiles/Project_Default.xml
generated
12
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -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>
|
||||||
18
README.md
18
README.md
@@ -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! :)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
23
cnc/gcode.py
23
cnc/gcode.py
@@ -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')
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
12
cnc/logging_config.py
Normal 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)
|
||||||
21
cnc/main.py
21
cnc/main.py
@@ -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()
|
||||||
|
|
||||||
|
|||||||
205
cnc/pulses.py
205
cnc/pulses.py
@@ -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)
|
||||||
|
|||||||
16
deploy.sh
16
deploy.sh
@@ -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"
|
||||||
|
|||||||
10
runtests.sh
10
runtests.sh
@@ -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
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -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'],
|
||||||
|
|
||||||
|
|||||||
@@ -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)"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user