Compare commits
231 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bee179eab4 | ||
|
|
039bb9f10f | ||
|
|
b0b4bc8958 | ||
|
|
a6553538e6 | ||
|
|
019666a6f6 | ||
|
|
7579b9671b | ||
|
|
99f3c99238 | ||
|
|
aba04bf3bd | ||
|
|
f5c67baac2 | ||
|
|
973ef97143 | ||
|
|
310cdf88cc | ||
|
|
5c05a24947 | ||
|
|
c93dad8eba | ||
|
|
fb3065cfa7 | ||
|
|
e3f9ff6701 | ||
|
|
c95cc3fb66 | ||
|
|
1dda4628a0 | ||
|
|
36612fd544 | ||
|
|
ef8c464d97 | ||
|
|
66eefa1da8 | ||
|
|
e78377eebd | ||
|
|
451f7d5672 | ||
|
|
ef820d98f6 | ||
|
|
9dfe612516 | ||
|
|
830cfc5414 | ||
|
|
45afa04578 | ||
|
|
9bc4239e9c | ||
|
|
3a1cdc7d70 | ||
|
|
3a75748762 | ||
|
|
e336c24665 | ||
|
|
849f4ed25f | ||
|
|
57d342b455 | ||
|
|
5208fc38ed | ||
|
|
b549c3927e | ||
|
|
e53a589ac3 | ||
|
|
4d48c111d8 | ||
|
|
6c1e1dcc8d | ||
|
|
ddcf9a7ff7 | ||
|
|
05ec7ca7ff | ||
|
|
ef0c80af51 | ||
|
|
d997821e58 | ||
|
|
385e6b67d9 | ||
|
|
04602b2470 | ||
|
|
923954772f | ||
|
|
5a16863465 | ||
|
|
08a5f8a5ff | ||
|
|
43ac56766e | ||
|
|
afc9bcf27b | ||
|
|
9140f36d99 | ||
|
|
fa07be9346 | ||
|
|
7290ed5f73 | ||
|
|
fc60bda4d1 | ||
|
|
82a65e9f4a | ||
|
|
b139a8561f | ||
|
|
8518da9824 | ||
|
|
656cb2c417 | ||
|
|
239c1ad5c9 | ||
|
|
7733e1d832 | ||
|
|
99b4122901 | ||
|
|
28d70eaf0c | ||
|
|
5d635c5252 | ||
|
|
22d7e48aa2 | ||
|
|
a6b0649cb1 | ||
|
|
58dd6d9106 | ||
|
|
d139389267 | ||
|
|
e99c0f53f8 | ||
|
|
02ae2ab984 | ||
|
|
97f7735c6a | ||
|
|
69486e45c1 | ||
|
|
5e8aaed41f | ||
|
|
c128a9dfd5 | ||
|
|
6dc623b35d | ||
|
|
8fc3487a8a | ||
|
|
82db072151 | ||
|
|
41f73d0c8c | ||
|
|
08a1183a01 | ||
|
|
f77e1b67f6 | ||
|
|
11c7c110a1 | ||
|
|
5abea041b4 | ||
|
|
268d1cb27c | ||
|
|
561c84dd93 | ||
|
|
955d940b60 | ||
|
|
6ea36de9f2 | ||
|
|
e0cedfb853 | ||
|
|
0a5b07f9da | ||
|
|
08874b9c91 | ||
|
|
8121a4a29f | ||
|
|
d1c209c689 | ||
|
|
f4bfce260a | ||
|
|
7e3adde542 | ||
|
|
33bdc2fc32 | ||
|
|
0b76864453 | ||
|
|
56bfb3280a | ||
|
|
3ddbd34a7c | ||
|
|
67f9c4948d | ||
|
|
7d3600f918 | ||
|
|
21df21b7af | ||
|
|
b7b216af7f | ||
|
|
077a56c2ca | ||
|
|
a67306c76b | ||
|
|
6eefbe5e30 | ||
|
|
650d55d7b2 | ||
|
|
03bcae8a98 | ||
|
|
1a67902858 | ||
|
|
01bb4b291e | ||
|
|
e38c7df064 | ||
|
|
3001a089c0 | ||
|
|
39d62556b1 | ||
|
|
434341d074 | ||
|
|
ce9db609ad | ||
|
|
6c252d30f5 | ||
|
|
978777f09f | ||
|
|
5db84779c6 | ||
|
|
1e3a03fbee | ||
|
|
01a89b951a | ||
|
|
d166d1f692 | ||
|
|
9399911490 | ||
|
|
d3665699f1 | ||
|
|
81013ba5c8 | ||
|
|
f0a754e496 | ||
|
|
51e1085dbc | ||
|
|
47bb8b7cc2 | ||
|
|
33893ece1d | ||
|
|
1b3ef8a8fb | ||
|
|
09eec3710d | ||
|
|
6fa95e12ea | ||
|
|
7a11b78fd4 | ||
|
|
3d26bf6635 | ||
|
|
08444a8b89 | ||
|
|
84c623e705 | ||
|
|
72074078f9 | ||
|
|
1d11c4e74d | ||
|
|
f8bb383e9a | ||
|
|
8e5d228555 | ||
|
|
6770aa96c9 | ||
|
|
896c31fd05 | ||
|
|
054cbbe094 | ||
|
|
0d8ddcadbb | ||
|
|
d3eda337a9 | ||
|
|
138f3c2646 | ||
|
|
f1222565b8 | ||
|
|
5caff594c5 | ||
|
|
5a68c636da | ||
|
|
02b141ac43 | ||
|
|
47e458210e | ||
|
|
3833669c3a | ||
|
|
df6528715e | ||
|
|
265b9097d5 | ||
|
|
a6025686b6 | ||
|
|
ed80b92b59 | ||
|
|
45e65580f7 | ||
|
|
f6f174ab36 | ||
|
|
0cf06ee69a | ||
|
|
ed9dee4602 | ||
|
|
f183871e28 | ||
|
|
d891baa860 | ||
|
|
f6cd51bfb7 | ||
|
|
83e9e92b9a | ||
|
|
9e4eb050f9 | ||
|
|
5a86391f78 | ||
|
|
29c83cec22 | ||
|
|
db927bd822 | ||
|
|
cb969527bc | ||
|
|
3ab9a8d26c | ||
|
|
5db4886c9c | ||
|
|
b22a81cd34 | ||
|
|
9fc5506c83 | ||
|
|
bba22ab7f0 | ||
|
|
a0b4cdb5c4 | ||
|
|
e4129a7e53 | ||
|
|
00ea3934ee | ||
|
|
cf4c31cb88 | ||
|
|
f10bd5726d | ||
|
|
7db6fa7bfc | ||
|
|
b05eb1e8e3 | ||
|
|
1bdebeaebf | ||
|
|
479772ca00 | ||
|
|
1d276d160f | ||
|
|
9313e58123 | ||
|
|
d778ae1846 | ||
|
|
522093ef00 | ||
|
|
d303e556ad | ||
|
|
1d21bf66c6 | ||
|
|
1b07505973 | ||
|
|
3c5649219f | ||
|
|
68d03e4a3e | ||
|
|
e6e0a21b06 | ||
|
|
0e0780a460 | ||
|
|
9a4425c85a | ||
|
|
57c27f75ae | ||
|
|
8d62318c5f | ||
|
|
add528532e | ||
|
|
8944e2104d | ||
|
|
319221ee23 | ||
|
|
4a5801bb2e | ||
|
|
f8acf0f54f | ||
|
|
bc5d900e61 | ||
|
|
efb4a5daa1 | ||
|
|
e0c947e188 | ||
|
|
3ffab763c0 | ||
|
|
ef09ac5a7f | ||
|
|
f6d4284d5c | ||
|
|
8d9ca6f2dd | ||
|
|
1d6af72de5 | ||
|
|
2a8dd5c51f | ||
|
|
c78f66b8e8 | ||
|
|
b340fdcc4a | ||
|
|
7785d3a87d | ||
|
|
31db4cc772 | ||
|
|
7932de11a7 | ||
|
|
bc9cbc8133 | ||
|
|
b5a41d0dd1 | ||
|
|
64a091fb98 | ||
|
|
80f23441dd | ||
|
|
3a2d16abb3 | ||
|
|
c70cc8fadb | ||
|
|
10e11950ae | ||
|
|
37788c1e55 | ||
|
|
6d6638826c | ||
|
|
6930a7de8d | ||
|
|
6bbb84326d | ||
|
|
8c2fa2e2d6 | ||
|
|
38643f52c9 | ||
|
|
eecf3b6ea8 | ||
|
|
fc1d690d75 | ||
|
|
d10380e73f | ||
|
|
aaeda540b6 | ||
|
|
fda988889b | ||
|
|
b58a897b70 | ||
|
|
84d8cf9b7e | ||
|
|
8463a83324 |
13
.travis.yml
Normal file
13
.travis.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# This is a travis-ci.org continuous integration configuration file.
|
||||||
|
language: c
|
||||||
|
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- gcc-avr
|
||||||
|
- avr-libc
|
||||||
|
- wget
|
||||||
|
|
||||||
|
install: ./scripts/travis-install.sh
|
||||||
|
|
||||||
|
script: ./scripts/travis-build.sh
|
||||||
4
Makefile
4
Makefile
@@ -80,12 +80,12 @@ $(OUT)%.o.ctr: $(OUT)%.o
|
|||||||
$(OUT)compile_time_request.o: $(patsubst %.c, $(OUT)src/%.o.ctr,$(src-y)) ./scripts/buildcommands.py
|
$(OUT)compile_time_request.o: $(patsubst %.c, $(OUT)src/%.o.ctr,$(src-y)) ./scripts/buildcommands.py
|
||||||
@echo " Building $@"
|
@echo " Building $@"
|
||||||
$(Q)cat $(patsubst %.c, $(OUT)src/%.o.ctr,$(src-y)) > $(OUT)klipper.compile_time_request
|
$(Q)cat $(patsubst %.c, $(OUT)src/%.o.ctr,$(src-y)) > $(OUT)klipper.compile_time_request
|
||||||
$(Q)$(PYTHON) ./scripts/buildcommands.py -d $(OUT)klipper.dict $(OUT)klipper.compile_time_request $(OUT)compile_time_request.c
|
$(Q)$(PYTHON) ./scripts/buildcommands.py -d $(OUT)klipper.dict -t "$(CC);$(AS);$(LD);$(OBJCOPY);$(OBJDUMP);$(STRIP)" $(OUT)klipper.compile_time_request $(OUT)compile_time_request.c
|
||||||
$(Q)$(CC) $(CFLAGS) -c $(OUT)compile_time_request.c -o $@
|
$(Q)$(CC) $(CFLAGS) -c $(OUT)compile_time_request.c -o $@
|
||||||
|
|
||||||
$(OUT)klipper.elf: $(patsubst %.c, $(OUT)src/%.o,$(src-y)) $(OUT)compile_time_request.o
|
$(OUT)klipper.elf: $(patsubst %.c, $(OUT)src/%.o,$(src-y)) $(OUT)compile_time_request.o
|
||||||
@echo " Linking $@"
|
@echo " Linking $@"
|
||||||
$(Q)$(CC) $(CFLAGS_klipper.elf) $^ -o $@
|
$(Q)$(CC) $^ $(CFLAGS_klipper.elf) -o $@
|
||||||
|
|
||||||
################ Kconfig rules
|
################ Kconfig rules
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ nozzle_diameter: 0.500
|
|||||||
filament_diameter: 3.500
|
filament_diameter: 3.500
|
||||||
heater_pin: ar4
|
heater_pin: ar4
|
||||||
sensor_type: EPCOS 100K B57560G104F
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
sensor_pin: analog1
|
sensor_pin: analog7
|
||||||
control: pid
|
control: pid
|
||||||
pid_Kp: 22.2
|
pid_Kp: 22.2
|
||||||
pid_Ki: 1.08
|
pid_Ki: 1.08
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ homing_speed: 50
|
|||||||
step_pin: ar46
|
step_pin: ar46
|
||||||
dir_pin: ar48
|
dir_pin: ar48
|
||||||
enable_pin: !ar62
|
enable_pin: !ar62
|
||||||
step_distance: .01
|
step_distance: .0025
|
||||||
endstop_pin: ^ar18
|
endstop_pin: ^ar18
|
||||||
position_endstop: 0.5
|
position_endstop: 0.5
|
||||||
position_max: 200
|
position_max: 200
|
||||||
|
|||||||
@@ -16,12 +16,22 @@ dir_pin: ar55
|
|||||||
enable_pin: !ar38
|
enable_pin: !ar38
|
||||||
step_distance: .01
|
step_distance: .01
|
||||||
endstop_pin: ^ar2
|
endstop_pin: ^ar2
|
||||||
|
homing_speed: 50
|
||||||
position_endstop: 297.05
|
position_endstop: 297.05
|
||||||
|
# Distance (in mm) between the nozzle and the bed when the nozzle is
|
||||||
|
# in the center of the build area and the endstop triggers. This
|
||||||
|
# parameter must be provided for stepper_a; for stepper_b and
|
||||||
|
# stepper_c this parameter defaults to the value specified for
|
||||||
|
# stepper_a.
|
||||||
|
arm_length: 333.0
|
||||||
|
# Length (in mm) of the diagonal rod that connects this tower to the
|
||||||
|
# print head. This parameter must be provided for stepper_a; for
|
||||||
|
# stepper_b and stepper_c this parameter defaults to the value
|
||||||
|
# specified for stepper_a.
|
||||||
#angle:
|
#angle:
|
||||||
# This option specifies the angle (in degrees) that the tower is
|
# This option specifies the angle (in degrees) that the tower is
|
||||||
# at. The default is 210 for stepper_a, 330 for stepper_b, and 90
|
# at. The default is 210 for stepper_a, 330 for stepper_b, and 90
|
||||||
# for stepper_c.
|
# for stepper_c.
|
||||||
homing_speed: 50
|
|
||||||
|
|
||||||
# The stepper_b section describes the stepper controlling the front
|
# The stepper_b section describes the stepper controlling the front
|
||||||
# right tower (at 330 degrees).
|
# right tower (at 330 degrees).
|
||||||
@@ -31,7 +41,6 @@ dir_pin: ar61
|
|||||||
enable_pin: !ar56
|
enable_pin: !ar56
|
||||||
step_distance: .01
|
step_distance: .01
|
||||||
endstop_pin: ^ar15
|
endstop_pin: ^ar15
|
||||||
position_endstop: 297.05
|
|
||||||
|
|
||||||
# The stepper_c section describes the stepper controlling the rear
|
# The stepper_c section describes the stepper controlling the rear
|
||||||
# tower (at 90 degrees).
|
# tower (at 90 degrees).
|
||||||
@@ -41,7 +50,6 @@ dir_pin: ar48
|
|||||||
enable_pin: !ar62
|
enable_pin: !ar62
|
||||||
step_distance: .01
|
step_distance: .01
|
||||||
endstop_pin: ^ar19
|
endstop_pin: ^ar19
|
||||||
position_endstop: 297.05
|
|
||||||
|
|
||||||
[extruder]
|
[extruder]
|
||||||
step_pin: ar26
|
step_pin: ar26
|
||||||
@@ -91,11 +99,33 @@ max_z_velocity: 150
|
|||||||
# maximum speed of up/down moves (which require a higher step rate
|
# maximum speed of up/down moves (which require a higher step rate
|
||||||
# than other moves on a delta printer). The default is to use
|
# than other moves on a delta printer). The default is to use
|
||||||
# max_velocity for max_z_velocity.
|
# max_velocity for max_z_velocity.
|
||||||
delta_arm_length: 333.0
|
#minimum_z_position: 0
|
||||||
# Length (in mm) of the diagonal rods that connect the linear axes
|
# The minimum Z position that the user may command the head to move
|
||||||
# to the print head. This parameter must be provided.
|
# to. The default is 0.
|
||||||
delta_radius: 174.75
|
delta_radius: 174.75
|
||||||
# Radius (in mm) of the horizontal circle formed by the three linear
|
# Radius (in mm) of the horizontal circle formed by the three linear
|
||||||
# axis towers. This parameter may also be calculated as:
|
# axis towers. This parameter may also be calculated as:
|
||||||
# delta_radius = smooth_rod_offset - effector_offset - carriage_offset
|
# delta_radius = smooth_rod_offset - effector_offset - carriage_offset
|
||||||
# This parameter must be provided.
|
# This parameter must be provided.
|
||||||
|
|
||||||
|
# The delta_calibrate section enables a DELTA_CALIBRATE extended
|
||||||
|
# g-code command that can calibrate the tower endstop positions and
|
||||||
|
# angles.
|
||||||
|
[delta_calibrate]
|
||||||
|
radius: 50
|
||||||
|
# Radius (in mm) of the area that may be probed. This is typically
|
||||||
|
# the size of the printer bed. This parameter must be provided.
|
||||||
|
#speed: 50
|
||||||
|
# The speed (in mm/s) of non-probing moves during the
|
||||||
|
# calibration. The default is 50.
|
||||||
|
#horizontal_move_z: 5
|
||||||
|
# The height (in mm) that the head should be commanded to move to
|
||||||
|
# just prior to starting a probe operation. The default is 5.
|
||||||
|
#manual_probe:
|
||||||
|
# If true, then DELTA_CALIBRATE will perform manual probing. If
|
||||||
|
# false, then a PROBE command will be run at each probe
|
||||||
|
# point. Manual probing is accomplished by manually jogging the Z
|
||||||
|
# position of the print head at each probe point and then issuing a
|
||||||
|
# NEXT extended g-code command to record the position at that
|
||||||
|
# point. The default is false if a [probe] config section is present
|
||||||
|
# and true otherwise.
|
||||||
|
|||||||
@@ -2,6 +2,67 @@
|
|||||||
# additional devices that may be configured on a printer. The snippets
|
# additional devices that may be configured on a printer. The snippets
|
||||||
# in this file may be copied into the main printer.cfg file. See the
|
# in this file may be copied into the main printer.cfg file. See the
|
||||||
# "example.cfg" file for description of common config parameters.
|
# "example.cfg" file for description of common config parameters.
|
||||||
|
#
|
||||||
|
# Note, where an extra config section creates additional pins, the
|
||||||
|
# section defining the pins must be listed in the config file before
|
||||||
|
# any sections using those pins.
|
||||||
|
|
||||||
|
|
||||||
|
# Z height probe. One may define this section to enable Z height
|
||||||
|
# probing hardware. When this section is enabled, PROBE and
|
||||||
|
# QUERY_PROBE extended g-code commands become available. The probe
|
||||||
|
# section also creates a virtual probe:z_virtual_endstop pin. One may
|
||||||
|
# set the stepper_z endstop_pin to this virtual pin on cartesian style
|
||||||
|
# printers that use the probe in place of a z endstop.
|
||||||
|
#[probe]
|
||||||
|
#pin: ar15
|
||||||
|
# Probe detection pin. This parameter must be provided.
|
||||||
|
#z_offset:
|
||||||
|
# The distance (in mm) between the bed and the nozzle when the probe
|
||||||
|
# triggers. This parameter must be provided.
|
||||||
|
#speed: 5.0
|
||||||
|
# Speed (in mm/s) of the Z axis when probing. The default is 5mm/s.
|
||||||
|
#activate_gcode:
|
||||||
|
# A list of G-Code commands (one per line) to execute prior to each
|
||||||
|
# probe attempt. This may be useful if the probe needs to be
|
||||||
|
# activated in some way. The default is to not run any special
|
||||||
|
# G-Code commands on activation.
|
||||||
|
#deactivate_gcode:
|
||||||
|
# A list of G-Code commands (one per line) to execute after each
|
||||||
|
# probe attempt completes. The default is to not run any special
|
||||||
|
# G-Code commands on deactivation.
|
||||||
|
|
||||||
|
|
||||||
|
# Bed tilt compensation. One may define a [bed_tilt] config section to
|
||||||
|
# enable move transformations that account for a tilted bed.
|
||||||
|
#[bed_tilt]
|
||||||
|
#x_adjust: 0
|
||||||
|
# The amount to add to each move's Z height for each mm on the X
|
||||||
|
# axis. The default is 0.
|
||||||
|
#y_adjust: 0
|
||||||
|
# The amount to add to each move's Z height for each mm on the Y
|
||||||
|
# axis. The default is 0.
|
||||||
|
# The remaining parameters control a BED_TILT_CALIBRATE extended
|
||||||
|
# g-code command that may be used to calibrate appropriate x and y
|
||||||
|
# adjustment parameters.
|
||||||
|
#points:
|
||||||
|
# A newline separated list of X,Y points that should be probed
|
||||||
|
# during a BED_TILT_CALIBRATE command. The default is to not enable
|
||||||
|
# the command.
|
||||||
|
#speed: 50
|
||||||
|
# The speed (in mm/s) of non-probing moves during the
|
||||||
|
# calibration. The default is 50.
|
||||||
|
#horizontal_move_z: 5
|
||||||
|
# The height (in mm) that the head should be commanded to move to
|
||||||
|
# just prior to starting a probe operation. The default is 5.
|
||||||
|
#manual_probe:
|
||||||
|
# If true, then BED_TILT_CALIBRATE will perform manual probing. If
|
||||||
|
# false, then a PROBE command will be run at each probe
|
||||||
|
# point. Manual probing is accomplished by manually jogging the Z
|
||||||
|
# position of the print head at each probe point and then issuing a
|
||||||
|
# NEXT extended g-code command to record the position at that
|
||||||
|
# point. The default is false if a [probe] config section is present
|
||||||
|
# and true otherwise.
|
||||||
|
|
||||||
|
|
||||||
# In a multi-extruder printer add an additional extruder section for
|
# In a multi-extruder printer add an additional extruder section for
|
||||||
@@ -13,6 +74,13 @@
|
|||||||
#step_pin: ar36
|
#step_pin: ar36
|
||||||
#dir_pin: ar34
|
#dir_pin: ar34
|
||||||
#...
|
#...
|
||||||
|
#shared_heater:
|
||||||
|
# If this extruder uses the same heater already defined for another
|
||||||
|
# extruder then place the name of that extruder here. For example,
|
||||||
|
# should extruder3 and extruder4 share a heater then the extruder3
|
||||||
|
# config section should define the heater and the extruder4 section
|
||||||
|
# should specify "shared_heater: extruder3". The default is to not
|
||||||
|
# reuse an existing heater.
|
||||||
#deactivate_gcode:
|
#deactivate_gcode:
|
||||||
# A list of G-Code commands (one per line) to execute on a G-Code
|
# A list of G-Code commands (one per line) to execute on a G-Code
|
||||||
# tool change command (eg, "T1") that deactivates this extruder and
|
# tool change command (eg, "T1") that deactivates this extruder and
|
||||||
@@ -27,6 +95,102 @@
|
|||||||
# activation.
|
# activation.
|
||||||
|
|
||||||
|
|
||||||
|
# Support for cartesian printers with dual carriages on a single
|
||||||
|
# axis. The active carriage is set via the SET_DUAL_CARRIAGE extended
|
||||||
|
# g-code command. The "SET_DUAL_CARRIAGE CARRIAGE=1" command will
|
||||||
|
# activate the carriage defined in this section (CARRIAGE=0 will
|
||||||
|
# return activation to the primary carriage). Dual carriage support is
|
||||||
|
# typically combined with extra extruders - use the SET_DUAL_CARRIAGE
|
||||||
|
# command in the activate_gcode / deactivate_gcode section of the
|
||||||
|
# appropriate extruder. Be sure to also use that mechanism to park the
|
||||||
|
# carriages during deactivation.
|
||||||
|
#[dual_carriage]
|
||||||
|
#axis:
|
||||||
|
# The axis this extra carriage is on (either x or y). This parameter
|
||||||
|
# must be provided.
|
||||||
|
#step_pin:
|
||||||
|
#dir_pin:
|
||||||
|
#enable_pin:
|
||||||
|
#step_distance:
|
||||||
|
#endstop_pin:
|
||||||
|
#position_endstop:
|
||||||
|
#position_min:
|
||||||
|
#position_max:
|
||||||
|
# See the example.cfg for the definition of the above parameters.
|
||||||
|
|
||||||
|
|
||||||
|
# Heater and temperature sensor verification. Heater verification is
|
||||||
|
# automatically enabled for each heater that is configured on the
|
||||||
|
# printer. Use verify_heater sections to change the default settings.
|
||||||
|
#[verify_heater heater_config_name]
|
||||||
|
#heating_gain: 2
|
||||||
|
# The minimum temperature (in Celsius) that the heater must increase
|
||||||
|
# by when approaching a new target temperature. The default is 2.
|
||||||
|
#check_gain_time:
|
||||||
|
# The amount of time (in seconds) that the heating_gain must be met
|
||||||
|
# in before an error is raised. The default is 20 seconds for
|
||||||
|
# extruders and 60 seconds for heater_bed.
|
||||||
|
#hysteresis: 5
|
||||||
|
# The difference between the target temperature and the current
|
||||||
|
# temperature for the heater to be considered within range of the
|
||||||
|
# target temperature. The default is 5.
|
||||||
|
#max_error: 120
|
||||||
|
# The maximum temperature difference a heater that falls outside the
|
||||||
|
# target temperature range may accumulate before an error is
|
||||||
|
# raised. For example, if the target temperature is 200, the
|
||||||
|
# hysteresis is 5, the max_error is 120, and the temperature is
|
||||||
|
# reported at 185 degrees for 12 seconds then an error would be
|
||||||
|
# raised (or 24 seconds at 190, or 120 seconds at 194, etc.). The
|
||||||
|
# default is 120.
|
||||||
|
|
||||||
|
|
||||||
|
# Multi-stepper axes. On a cartesian style printer, the stepper
|
||||||
|
# controlling a given axis may have additional config blocks defining
|
||||||
|
# steppers that should be stepped in concert with the primary
|
||||||
|
# stepper. One may define any number of sections with a numeric suffix
|
||||||
|
# starting at 1 (for example, "stepper_z1", "stepper_z2", etc.).
|
||||||
|
#[stepper_z1]
|
||||||
|
#step_pin: ar36
|
||||||
|
#dir_pin: ar34
|
||||||
|
#enable_pin: !ar30
|
||||||
|
#step_distance: .005
|
||||||
|
# See the example.cfg for the definition of the above parameters.
|
||||||
|
#endstop_pin: ^ar19
|
||||||
|
# If an endstop_pin is defined for the additional stepper then the
|
||||||
|
# stepper will home until the endstop is triggered. Otherwise, the
|
||||||
|
# endstop will home until the endstop on the primary stepper for the
|
||||||
|
# axis is triggered.
|
||||||
|
|
||||||
|
|
||||||
|
# Stepper phase adjusted endstops. The following additional parameters
|
||||||
|
# may be added to a stepper axis definition to improve the accuracy of
|
||||||
|
# endstop switches.
|
||||||
|
#[stepper_z]
|
||||||
|
#homing_stepper_phases:
|
||||||
|
# One may set this to the number of phases of the stepper motor
|
||||||
|
# driver (which is the number of micro-steps multiplied by
|
||||||
|
# four). This parameter must be provided if using stepper phase
|
||||||
|
# adjustments.
|
||||||
|
#homing_endstop_accuracy: 0.200
|
||||||
|
# Sets the expected accuracy (in mm) of the endstop. This represents
|
||||||
|
# the maximum error distance the endstop may trigger (eg, if an
|
||||||
|
# endstop may occasionally trigger 100um early or up to 100um late
|
||||||
|
# then set this to 0.200 for 200um). The default is
|
||||||
|
# homing_stepper_phases*step_distance.
|
||||||
|
#homing_endstop_phase:
|
||||||
|
# This specifies the phase of the stepper motor driver to expect
|
||||||
|
# when hitting the endstop. Only set this value if one is sure the
|
||||||
|
# stepper motor driver is reset every time the mcu is reset. If this
|
||||||
|
# is not set, then the stepper phase will be detected on the first
|
||||||
|
# home and that phase will be used on all subsequent homes.
|
||||||
|
#homing_endstop_align_zero: False
|
||||||
|
# If true then the code will arrange for the zero position on the
|
||||||
|
# axis to occur at a full step on the stepper motor. (If used on the
|
||||||
|
# Z axis and the print layer height is a multiple of a full step
|
||||||
|
# distance then every layer will occur on a full step.) The default
|
||||||
|
# is False.
|
||||||
|
|
||||||
|
|
||||||
# Heater cooling fans (one may define any number of sections with a
|
# Heater cooling fans (one may define any number of sections with a
|
||||||
# "heater_fan" prefix). A "heater fan" is a fan that will be enabled
|
# "heater_fan" prefix). A "heater fan" is a fan that will be enabled
|
||||||
# whenever its associated heater is active. In the event of an MCU
|
# whenever its associated heater is active. In the event of an MCU
|
||||||
@@ -79,7 +243,8 @@
|
|||||||
|
|
||||||
# Statically configured digital output pins (one may define any number
|
# Statically configured digital output pins (one may define any number
|
||||||
# of sections with a "static_digital_output" prefix). Pins configured
|
# of sections with a "static_digital_output" prefix). Pins configured
|
||||||
# here will be setup as a GPIO output during MCU configuration.
|
# here will be setup as a GPIO output during MCU configuration. They
|
||||||
|
# can not be changed at run-time.
|
||||||
#[static_digital_output my_output_pins]
|
#[static_digital_output my_output_pins]
|
||||||
#pins:
|
#pins:
|
||||||
# A comma separated list of pins to be set as GPIO output pins. The
|
# A comma separated list of pins to be set as GPIO output pins. The
|
||||||
@@ -87,35 +252,60 @@
|
|||||||
# with "!". This parameter must be provided.
|
# with "!". This parameter must be provided.
|
||||||
|
|
||||||
|
|
||||||
# Statically configured PWM output pins (one may define any number of
|
# Run-time configurable output pins (one may define any number of
|
||||||
# sections with a "static_pwm_output" prefix). Pins configured here
|
# sections with an "output_pin" prefix). Pins configured here will be
|
||||||
# will be setup as PWM outputs during MCU configuration.
|
# setup as output pins and one may modify them at run-time using the
|
||||||
#[static_pwm_output my_output_pwm]
|
# "SET_PIN PIN=my_pin VALUE=.1" extended g-code command.
|
||||||
|
#[output_pin my_pin]
|
||||||
#pin:
|
#pin:
|
||||||
# The pin to configure as PWM output. This parameter must be
|
# The pin to configure as an output. This parameter must be
|
||||||
# provided.
|
# provided.
|
||||||
|
#pwm: False
|
||||||
|
# Set if the output pin should be capable of
|
||||||
|
# pulse-width-modulation. If this is true, the value fields should
|
||||||
|
# be between 0 and 1; if it is false the value fields should be
|
||||||
|
# either 0 or 1. The default is False.
|
||||||
|
#static_value:
|
||||||
|
# If this is set, then the pin is assigned to this value at startup
|
||||||
|
# and the pin can not be changed during runtime. A static pin uses
|
||||||
|
# slightly less ram in the micro-controller. The default is to use
|
||||||
|
# runtime configuration of pins.
|
||||||
#value:
|
#value:
|
||||||
# The value to statically set the PWM output to. This is typically
|
# The value to initially set the pin to during MCU
|
||||||
# set to a number between 0.0 and 1.0 with 1.0 being full on and 0.0
|
# configuration. The default is 0 (for low voltage).
|
||||||
# being full off. However, the range may be changed with the 'scale'
|
#shutdown_value:
|
||||||
# parameter (see below). This parameter must be provided.
|
# The value to set the pin to on an MCU shutdown event. The default
|
||||||
#hard_pwm:
|
# is 0 (for low voltage).
|
||||||
# Set this value to force hardware PWM instead of software PWM. Set
|
|
||||||
# to 1 to force a hardware PWM at the fastest rate; set to a higher
|
|
||||||
# number to force hardware PWM with the given cycle time in clock
|
|
||||||
# ticks. The default is to use software PWM.
|
|
||||||
#cycle_time: 0.100
|
#cycle_time: 0.100
|
||||||
# The amount of time (in seconds) per PWM cycle when using software
|
# The amount of time (in seconds) per PWM cycle. It is recommended
|
||||||
# based PWM. The default is 0.100 seconds.
|
# this be 10 milliseconds or greater when using software based
|
||||||
|
# PWM. The default is 0.100 seconds for pwm pins.
|
||||||
|
#hardware_pwm: False
|
||||||
|
# Enable this to use hardware PWM instead of software PWM. The
|
||||||
|
# default is False.
|
||||||
#scale:
|
#scale:
|
||||||
# This parameter can be used to alter how the 'value' parameter is
|
# This parameter can be used to alter how the 'value' and
|
||||||
# interpreted. If provided, then the 'value' parameter should be
|
# 'shutdown_value' parameters are interpreted for pwm pins. If
|
||||||
# between 0.0 and 'scale'. This may be useful when configuring a PWM
|
# provided, then the 'value' parameter should be between 0.0 and
|
||||||
# pin that controls a stepper voltage reference. The 'scale' can be
|
# 'scale'. This may be useful when configuring a PWM pin that
|
||||||
# set to the equivalent stepper amperage if the PWM were fully
|
# controls a stepper voltage reference. The 'scale' can be set to
|
||||||
# enabled, and then the 'value' parameter can be specified using the
|
# the equivalent stepper amperage if the PWM were fully enabled, and
|
||||||
# desired amperage for the stepper. The default is to not scale the
|
# then the 'value' parameter can be specified using the desired
|
||||||
# 'value' parameter.
|
# amperage for the stepper. The default is to not scale the 'value'
|
||||||
|
# parameter.
|
||||||
|
|
||||||
|
|
||||||
|
# Multiple pin outputs (one may define any number of sections with a
|
||||||
|
# "multi_pin" prefix). A multi_pin output creates an internal pin
|
||||||
|
# alias that can modify multiple output pins each time the alias pin
|
||||||
|
# is set. For example, one could define a "[multi_pin my_fan]" object
|
||||||
|
# containing two pins and then set "pin=multi_pin:my_fan" in the
|
||||||
|
# "[fan]" section - on each fan change both output pins would be
|
||||||
|
# updated. These aliases may not be used with stepper motor pins.
|
||||||
|
#[multi_pin my_multi_pin]
|
||||||
|
#pins:
|
||||||
|
# A comma separated list of pins associated with this alias. This
|
||||||
|
# parameter must be provided.
|
||||||
|
|
||||||
|
|
||||||
# Statically configured AD5206 digipots connected via SPI bus (one may
|
# Statically configured AD5206 digipots connected via SPI bus (one may
|
||||||
@@ -148,6 +338,63 @@
|
|||||||
# default is to not scale the 'channel_x' parameters.
|
# default is to not scale the 'channel_x' parameters.
|
||||||
|
|
||||||
|
|
||||||
|
# Homing override. One may use this mechanism to run a series of
|
||||||
|
# g-code commands in place of a G28 found in the normal g-code input.
|
||||||
|
# This may be useful on printers that require a specific procedure to
|
||||||
|
# home the machine.
|
||||||
|
#[homing_override]
|
||||||
|
#gcode:
|
||||||
|
# A list of G-Code commands (one per line) to execute in place of
|
||||||
|
# all G28 commands found in the normal g-code input. If a G28 is
|
||||||
|
# contained in this list of commands then it will invoke the normal
|
||||||
|
# homing procedure for the printer. The commands listed here must
|
||||||
|
# home all axes. This parameter must be provided.
|
||||||
|
#set_position_x:
|
||||||
|
#set_position_y:
|
||||||
|
#set_position_z:
|
||||||
|
# If specified, the printer will assume the axis is at the specified
|
||||||
|
# position prior to running the above g-code commands. Setting this
|
||||||
|
# disables homing checks for that axis. This may be useful if the
|
||||||
|
# head must move prior to invoking the normal G28 mechanism for an
|
||||||
|
# axis. The default is to not force a position for an axis.
|
||||||
|
|
||||||
|
|
||||||
|
# A virtual sdcard may be useful if the host machine is not fast
|
||||||
|
# enough to run OctoPrint well. It allows the Klipper host software to
|
||||||
|
# directly print gcode files stored in a directory on the host using
|
||||||
|
# standard sdcard G-Code commands (eg, M24).
|
||||||
|
#[virtual_sdcard]
|
||||||
|
#path: ~/.octoprint/uploads/
|
||||||
|
# The path of the local directory on the host machine to look for
|
||||||
|
# g-code files. This is a read-only directory (sdcard file writes
|
||||||
|
# are not supported). One may point this to OctoPrint's upload
|
||||||
|
# directory (generally ~/.octoprint/uploads/ ). This parameter must
|
||||||
|
# be provided.
|
||||||
|
|
||||||
|
|
||||||
|
# Support for a display attached to the micro-controller.
|
||||||
|
#[display]
|
||||||
|
#lcd_type:
|
||||||
|
# The type of LCD chip in use. This may be either "hd44780" (which
|
||||||
|
# is used in "RepRapDiscount 2004 Smart Controller" type displays)
|
||||||
|
# or "st7920" (which is used in "RepRapDiscount 12864 Full Graphic
|
||||||
|
# Smart Controller" type displays). This parameter must be
|
||||||
|
# provided.
|
||||||
|
#rs_pin:
|
||||||
|
#e_pin:
|
||||||
|
#d4_pin:
|
||||||
|
#d5_pin:
|
||||||
|
#d6_pin:
|
||||||
|
#d7_pin:
|
||||||
|
# The pins connected to an hd44780 type lcd. These parameters must
|
||||||
|
# be provided when using an hd44780 display.
|
||||||
|
#cs_pin:
|
||||||
|
#sclk_pin:
|
||||||
|
#sid_pin:
|
||||||
|
# The pins connected to an st7920 type lcd. These parameters must
|
||||||
|
# be provided when using an st7920 display.
|
||||||
|
|
||||||
|
|
||||||
# Replicape support - see the generic-replicape.cfg file for further
|
# Replicape support - see the generic-replicape.cfg file for further
|
||||||
# details.
|
# details.
|
||||||
#[replicape]
|
#[replicape]
|
||||||
|
|||||||
87
config/example-multi-mcu.cfg
Normal file
87
config/example-multi-mcu.cfg
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# This file contains an example configuration with three
|
||||||
|
# micro-controllers simultaneously controlling a single printer.
|
||||||
|
|
||||||
|
# See both the example.cfg and example-extras.cfg file for a
|
||||||
|
# description of available parameters.
|
||||||
|
|
||||||
|
|
||||||
|
# The main micro-controller is used as the timing source for all the
|
||||||
|
# micro-controllers on the printer. Typically, both the X and Y axes
|
||||||
|
# are connected to the main micro-controller.
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyACM0
|
||||||
|
pin_map: arduino
|
||||||
|
|
||||||
|
# The "zboard" micro-controller will be used to control the Z axis.
|
||||||
|
[mcu zboard]
|
||||||
|
serial: /dev/ttyACM1
|
||||||
|
pin_map: arduino
|
||||||
|
|
||||||
|
# The "auxboard" micro-controller will be used to control the heaters.
|
||||||
|
[mcu auxboard]
|
||||||
|
serial: /dev/ttyACM2
|
||||||
|
pin_map: arduino
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: ar54
|
||||||
|
dir_pin: ar55
|
||||||
|
enable_pin: !ar38
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^ar3
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: ar60
|
||||||
|
dir_pin: !ar61
|
||||||
|
enable_pin: !ar56
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^ar14
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: zboard:ar46
|
||||||
|
dir_pin: zboard:ar48
|
||||||
|
enable_pin: !zboard:ar62
|
||||||
|
step_distance: .0025
|
||||||
|
endstop_pin: ^zboard:ar18
|
||||||
|
position_endstop: 0.5
|
||||||
|
position_max: 200
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: auxboard:ar26
|
||||||
|
dir_pin: auxboard:ar28
|
||||||
|
enable_pin: !auxboard:ar24
|
||||||
|
step_distance: .002
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: auxboard:ar10
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: auxboard:analog13
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.2
|
||||||
|
pid_Ki: 1.08
|
||||||
|
pid_Kd: 114
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: auxboard:ar8
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: auxboard:analog14
|
||||||
|
control: watermark
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: auxboard:ar9
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 5
|
||||||
|
max_z_accel: 100
|
||||||
@@ -57,26 +57,6 @@ position_max: 200
|
|||||||
# direction (away from zero); if false, home towards zero. The
|
# direction (away from zero); if false, home towards zero. The
|
||||||
# default is true if position_endstop is near position_max and false
|
# default is true if position_endstop is near position_max and false
|
||||||
# if near position_min.
|
# if near position_min.
|
||||||
#homing_stepper_phases: 0
|
|
||||||
# One may optionally set this to the number of phases of the stepper
|
|
||||||
# motor driver (which is the number of micro-steps multiplied by
|
|
||||||
# four). When set, the phase of the stepper driver will be used
|
|
||||||
# during homing to improve the accuracy of the endstop switch.
|
|
||||||
#homing_endstop_accuracy: 0.200
|
|
||||||
# Sets the expected accuracy (in mm) of the endstop. This represents
|
|
||||||
# the maximum error distance the endstop may trigger (eg, if an
|
|
||||||
# endstop may occasionally trigger 100um early or up to 100um late
|
|
||||||
# then set this to 0.200 for 200um). This setting is used with
|
|
||||||
# homing_stepper_phases and is only useful if that parameter is also
|
|
||||||
# configured.
|
|
||||||
#homing_endstop_phase: 0
|
|
||||||
# This specifies the phase of the stepper motor driver to expect
|
|
||||||
# when hitting the endstop. This setting is only meaningful if
|
|
||||||
# homing_stepper_phases is also set. Only set this value if one is
|
|
||||||
# sure the stepper motor driver is reset every time the mcu is
|
|
||||||
# reset. If this is not set, but homing_stepper_phases is set, then
|
|
||||||
# the stepper phase will be detected on the first home and that
|
|
||||||
# phase will be used on all subsequent homes.
|
|
||||||
|
|
||||||
# The stepper_y section is used to describe the stepper controlling
|
# The stepper_y section is used to describe the stepper controlling
|
||||||
# the Y axis in a cartesian robot. It has the same settings as the
|
# the Y axis in a cartesian robot. It has the same settings as the
|
||||||
@@ -197,15 +177,24 @@ pid_Kd: 114
|
|||||||
#pid_integral_max:
|
#pid_integral_max:
|
||||||
# The maximum "windup" the integral term may accumulate. The default
|
# The maximum "windup" the integral term may accumulate. The default
|
||||||
# is to use the same value as max_power.
|
# is to use the same value as max_power.
|
||||||
|
#pwm_cycle_time: 0.100
|
||||||
|
# Time in seconds for each software PWM cycle of the heater. It is
|
||||||
|
# not recommended to set this unless there is an electrical
|
||||||
|
# requirement to switch the heater faster than 10 times a second.
|
||||||
|
# The default is 0.100 seconds.
|
||||||
#min_extrude_temp: 170
|
#min_extrude_temp: 170
|
||||||
# The minimum temperature (in Celsius) at which extruder move
|
# The minimum temperature (in Celsius) at which extruder move
|
||||||
# commands may be issued. The default is 170 Celsius.
|
# commands may be issued. The default is 170 Celsius.
|
||||||
min_temp: 0
|
min_temp: 0
|
||||||
# Minimum temperature in Celsius (mcu will shutdown if not
|
|
||||||
# met). This parameter must be provided.
|
|
||||||
max_temp: 210
|
max_temp: 210
|
||||||
# Maximum temperature (mcu will shutdown if temperature is above
|
# The maximum range of valid temperatures (in Celsius) that the
|
||||||
# this value). This parameter must be provided.
|
# heater must remain within. This controls a safety feature
|
||||||
|
# implemented in the micro-controller code - should the measured
|
||||||
|
# temperature ever fall outside this range then the micro-controller
|
||||||
|
# will go into a shutdown state. This check can help detect some
|
||||||
|
# heater and sensor hardware failures. Set this range just wide
|
||||||
|
# enough so that reasonable temperatures do not result in an
|
||||||
|
# error. These parameters must be provided.
|
||||||
|
|
||||||
# The heater_bed section describes a heated bed (if present - omit
|
# The heater_bed section describes a heated bed (if present - omit
|
||||||
# section if not present).
|
# section if not present).
|
||||||
@@ -234,12 +223,13 @@ pin: ar9
|
|||||||
# pin to be enabled for no more than half the time. This setting may
|
# pin to be enabled for no more than half the time. This setting may
|
||||||
# be used to limit the total power output (over extended periods) to
|
# be used to limit the total power output (over extended periods) to
|
||||||
# the fan. The default is 1.0.
|
# the fan. The default is 1.0.
|
||||||
#hard_pwm: 0
|
#cycle_time: 0.010
|
||||||
# Set this value to force hardware PWM instead of software PWM. Set
|
# The amount of time (in seconds) for each PWM power cycle to the
|
||||||
# to 1 to force a hardware PWM at the fastest rate; set to a higher
|
# fan. It is recommended this be 10 milliseconds or greater when
|
||||||
# number to force hardware PWM with the given cycle time in clock
|
# using software based PWM. The default is 0.010 seconds.
|
||||||
# ticks. The default is 0 which enables software PWM with a cycle
|
#hardware_pwm: False
|
||||||
# time of 10ms.
|
# Enable this to use hardware PWM instead of software PWM. The
|
||||||
|
# default is False.
|
||||||
#kick_start_time: 0.100
|
#kick_start_time: 0.100
|
||||||
# Time (in seconds) to run the fan at full speed when first enabling
|
# Time (in seconds) to run the fan at full speed when first enabling
|
||||||
# it (helps get the fan spinning). The default is 0.100 seconds.
|
# it (helps get the fan spinning). The default is 0.100 seconds.
|
||||||
@@ -253,7 +243,7 @@ serial: /dev/ttyACM0
|
|||||||
pin_map: arduino
|
pin_map: arduino
|
||||||
# This option may be used to enable Arduino pin name aliases. The
|
# This option may be used to enable Arduino pin name aliases. The
|
||||||
# default is to not enable the aliases.
|
# default is to not enable the aliases.
|
||||||
#restart_method: arduino
|
#restart_method:
|
||||||
# This controls the mechanism the host will use to reset the
|
# This controls the mechanism the host will use to reset the
|
||||||
# micro-controller. The choices are 'arduino', 'rpi_usb', and
|
# micro-controller. The choices are 'arduino', 'rpi_usb', and
|
||||||
# 'command'. The 'arduino' method (toggle DTR) is common on Arduino
|
# 'command'. The 'arduino' method (toggle DTR) is common on Arduino
|
||||||
@@ -262,7 +252,8 @@ pin_map: arduino
|
|||||||
# disables power to all USB ports to accomplish a micro-controller
|
# disables power to all USB ports to accomplish a micro-controller
|
||||||
# reset. The 'command' method involves sending a Klipper command to
|
# reset. The 'command' method involves sending a Klipper command to
|
||||||
# the micro-controller so that it can reset itself. The default is
|
# the micro-controller so that it can reset itself. The default is
|
||||||
# 'arduino'.
|
# 'arduino' if the micro-controller communicates over a serial port,
|
||||||
|
# 'command' otherwise.
|
||||||
|
|
||||||
# The printer section controls high level printer settings.
|
# The printer section controls high level printer settings.
|
||||||
[printer]
|
[printer]
|
||||||
@@ -298,3 +289,6 @@ max_z_accel: 30
|
|||||||
# centripetal velocity cornering algorithm. A larger number will
|
# centripetal velocity cornering algorithm. A larger number will
|
||||||
# permit higher "cornering speeds" at the junction of two moves. The
|
# permit higher "cornering speeds" at the junction of two moves. The
|
||||||
# default is 0.02mm.
|
# default is 0.02mm.
|
||||||
|
|
||||||
|
|
||||||
|
# Looking for more options? Check the example-extras.cfg file.
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ homing_speed: 50
|
|||||||
step_pin: P8_19
|
step_pin: P8_19
|
||||||
dir_pin: P8_18
|
dir_pin: P8_18
|
||||||
enable_pin: !P9_14
|
enable_pin: !P9_14
|
||||||
step_distance: 0.00025
|
step_distance: .0025
|
||||||
endstop_pin: ^P9_13
|
endstop_pin: ^P9_13
|
||||||
position_endstop: 0
|
position_endstop: 0
|
||||||
position_max: 200
|
position_max: 200
|
||||||
@@ -48,6 +48,7 @@ nozzle_diameter: 0.400
|
|||||||
filament_diameter: 1.750
|
filament_diameter: 1.750
|
||||||
heater_pin: P9_15
|
heater_pin: P9_15
|
||||||
sensor_type: EPCOS 100K B57560G104F
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
pullup_resistor: 2000
|
||||||
sensor_pin: P9_36
|
sensor_pin: P9_36
|
||||||
control: pid
|
control: pid
|
||||||
pid_Kp: 22.2
|
pid_Kp: 22.2
|
||||||
@@ -59,6 +60,7 @@ max_temp: 250
|
|||||||
[heater_bed]
|
[heater_bed]
|
||||||
heater_pin: P8_11
|
heater_pin: P8_11
|
||||||
sensor_type: EPCOS 100K B57560G104F
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
pullup_resistor: 2000
|
||||||
sensor_pin: P9_33
|
sensor_pin: P9_33
|
||||||
control: watermark
|
control: watermark
|
||||||
min_temp: 0
|
min_temp: 0
|
||||||
@@ -77,3 +79,8 @@ max_velocity: 300
|
|||||||
max_accel: 3000
|
max_accel: 3000
|
||||||
max_z_velocity: 5
|
max_z_velocity: 5
|
||||||
max_z_accel: 100
|
max_z_accel: 100
|
||||||
|
|
||||||
|
[output_pin machine_enable]
|
||||||
|
pin: P9_23
|
||||||
|
value: 1
|
||||||
|
shutdown_value: 0
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
# this config, the firmware should be compiled for the AVR
|
# this config, the firmware should be compiled for the AVR
|
||||||
# atmega1284p.
|
# atmega1284p.
|
||||||
|
|
||||||
# Note that the "make flash" command does not work with Melzi
|
# Note, a number of Melzi boards are shipped without a bootloader. In
|
||||||
# boards. The boards are typically flashed with this command:
|
# that case, an external programmer will be needed to flash a
|
||||||
# avrdude -p atmega1284p -c avrisp -P /dev/ttyUSB0 -U flash:w:out/klipper.elf.hex
|
# bootloader to the board (for example, see
|
||||||
|
# http://www.instructables.com/id/Flashing-a-Bootloader-to-the-CR-10/
|
||||||
|
# ). Once that is done, one should be able to use the standard "make
|
||||||
|
# flash" command to flash Klipper.
|
||||||
|
|
||||||
# See the example.cfg file for a description of available parameters.
|
# See the example.cfg file for a description of available parameters.
|
||||||
|
|
||||||
@@ -32,7 +35,7 @@ homing_speed: 50
|
|||||||
step_pin: PB3
|
step_pin: PB3
|
||||||
dir_pin: !PB2
|
dir_pin: !PB2
|
||||||
enable_pin: !PA5
|
enable_pin: !PA5
|
||||||
step_distance: 0.00025
|
step_distance: .0025
|
||||||
endstop_pin: ^!PC4
|
endstop_pin: ^!PC4
|
||||||
position_endstop: 0.5
|
position_endstop: 0.5
|
||||||
position_max: 200
|
position_max: 200
|
||||||
|
|||||||
109
config/generic-mini-rambo.cfg
Normal file
109
config/generic-mini-rambo.cfg
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# This file contains common pin mappings for Mini-RAMBo boards. To use
|
||||||
|
# this config, the firmware should be compiled for the AVR atmega2560.
|
||||||
|
|
||||||
|
# See the example.cfg file for a description of available parameters.
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PC0
|
||||||
|
dir_pin: PL1
|
||||||
|
enable_pin: !PA7
|
||||||
|
step_distance: .005
|
||||||
|
endstop_pin: ^PB6
|
||||||
|
#endstop_pin: ^PC7
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 250
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PC1
|
||||||
|
dir_pin: !PL0
|
||||||
|
enable_pin: !PA6
|
||||||
|
step_distance: .005
|
||||||
|
endstop_pin: ^PB5
|
||||||
|
#endstop_pin: ^PA2
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 210
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PC2
|
||||||
|
dir_pin: PL2
|
||||||
|
enable_pin: !PA5
|
||||||
|
step_distance: .0025
|
||||||
|
endstop_pin: ^PB4
|
||||||
|
#endstop_pin: ^PA1
|
||||||
|
position_endstop: 0.5
|
||||||
|
position_max: 200
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PC3
|
||||||
|
dir_pin: PL6
|
||||||
|
enable_pin: !PA4
|
||||||
|
step_distance: .002
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PE5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PF0
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.2
|
||||||
|
pid_Ki: 1.08
|
||||||
|
pid_Kd: 114
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PG5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PF2
|
||||||
|
control: watermark
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PH5
|
||||||
|
|
||||||
|
#[heater_fan nozzle_cooling_fan]
|
||||||
|
#pin: PH3
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyACM0
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 5
|
||||||
|
max_z_accel: 100
|
||||||
|
|
||||||
|
[output_pin stepper_xy_current]
|
||||||
|
pin: PL3
|
||||||
|
pwm: True
|
||||||
|
scale: 2.0
|
||||||
|
cycle_time: .002
|
||||||
|
hardware_pwm: True
|
||||||
|
static_value: 1.3
|
||||||
|
|
||||||
|
[output_pin stepper_z_current]
|
||||||
|
pin: PL4
|
||||||
|
pwm: True
|
||||||
|
scale: 2.0
|
||||||
|
cycle_time: .002
|
||||||
|
hardware_pwm: True
|
||||||
|
static_value: 1.3
|
||||||
|
|
||||||
|
[output_pin stepper_e_current]
|
||||||
|
pin: PL5
|
||||||
|
pwm: True
|
||||||
|
scale: 2.0
|
||||||
|
cycle_time: .002
|
||||||
|
hardware_pwm: True
|
||||||
|
static_value: 1.25
|
||||||
|
|
||||||
|
[static_digital_output stepper_config]
|
||||||
|
pins:
|
||||||
|
PG1, PG0,
|
||||||
|
PK7, PG2,
|
||||||
|
PK6, PK5,
|
||||||
|
PK3, PK4
|
||||||
|
|
||||||
|
[static_digital_output yellow_led]
|
||||||
|
pins: !PB7
|
||||||
76
config/generic-printrboard.cfg
Normal file
76
config/generic-printrboard.cfg
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# This file contains common pin mappings for Printrboard boards (rev B
|
||||||
|
# through D). To use this config the firmware should be compiled for
|
||||||
|
# the AVR at90usb1286.
|
||||||
|
|
||||||
|
# Note that the "make flash" command is unlikely to work on the
|
||||||
|
# Printrboard. See the RepRap Printrboard wiki page for instructions
|
||||||
|
# on flashing.
|
||||||
|
|
||||||
|
# See the example.cfg file for a description of available parameters.
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PA0
|
||||||
|
dir_pin: !PA1
|
||||||
|
enable_pin: !PE7
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^PE3
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PA2
|
||||||
|
dir_pin: PA3
|
||||||
|
enable_pin: !PE6
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^PB0
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PA4
|
||||||
|
dir_pin: !PA5
|
||||||
|
enable_pin: !PC7
|
||||||
|
step_distance: .0025
|
||||||
|
endstop_pin: ^PE4
|
||||||
|
position_endstop: 0.5
|
||||||
|
position_max: 200
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PA6
|
||||||
|
dir_pin: PA7
|
||||||
|
enable_pin: !PC3
|
||||||
|
step_distance: .002
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PC5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PF1
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.2
|
||||||
|
pid_Ki: 1.08
|
||||||
|
pid_Kd: 114
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PC4
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PF0
|
||||||
|
control: watermark
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PC6
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyACM0
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 5
|
||||||
|
max_z_accel: 100
|
||||||
@@ -29,7 +29,7 @@ homing_speed: 50
|
|||||||
step_pin: PC2
|
step_pin: PC2
|
||||||
dir_pin: PL2
|
dir_pin: PL2
|
||||||
enable_pin: !PA5
|
enable_pin: !PA5
|
||||||
step_distance: 0.00025
|
step_distance: .0025
|
||||||
endstop_pin: ^PB4
|
endstop_pin: ^PB4
|
||||||
#endstop_pin: ^PC7
|
#endstop_pin: ^PC7
|
||||||
position_endstop: 0.5
|
position_endstop: 0.5
|
||||||
@@ -103,7 +103,24 @@ pins:
|
|||||||
PK7, PG2,
|
PK7, PG2,
|
||||||
PK6, PK5,
|
PK6, PK5,
|
||||||
PK3, PK4,
|
PK3, PK4,
|
||||||
PK2, PK1
|
PK1, PK2
|
||||||
|
|
||||||
[static_digital_output yellow_led]
|
[static_digital_output yellow_led]
|
||||||
pins: !PB7
|
pins: !PB7
|
||||||
|
|
||||||
|
# "RepRapDiscount 2004 Smart Controller" type displays
|
||||||
|
#[display]
|
||||||
|
#lcd_type: hd44780
|
||||||
|
#rs_pin: PG4
|
||||||
|
#e_pin: PG3
|
||||||
|
#d4_pin: PJ2
|
||||||
|
#d5_pin: PJ3
|
||||||
|
#d6_pin: PJ7
|
||||||
|
#d7_pin: PJ4
|
||||||
|
|
||||||
|
# "RepRapDiscount 128x64 Full Graphic Smart Controller" type displays
|
||||||
|
#[display]
|
||||||
|
#lcd_type: st7920
|
||||||
|
#cs_pin: PG4
|
||||||
|
#sclk_pin: PJ2
|
||||||
|
#sid_pin: PG3
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ homing_speed: 50
|
|||||||
step_pin: ar46
|
step_pin: ar46
|
||||||
dir_pin: ar48
|
dir_pin: ar48
|
||||||
enable_pin: !ar62
|
enable_pin: !ar62
|
||||||
step_distance: 0.00025
|
step_distance: .0025
|
||||||
endstop_pin: ^ar18
|
endstop_pin: ^ar18
|
||||||
#endstop_pin: ^ar19
|
#endstop_pin: ^ar19
|
||||||
position_endstop: 0.5
|
position_endstop: 0.5
|
||||||
@@ -82,3 +82,20 @@ max_velocity: 300
|
|||||||
max_accel: 3000
|
max_accel: 3000
|
||||||
max_z_velocity: 5
|
max_z_velocity: 5
|
||||||
max_z_accel: 100
|
max_z_accel: 100
|
||||||
|
|
||||||
|
# "RepRapDiscount 2004 Smart Controller" type displays
|
||||||
|
#[display]
|
||||||
|
#lcd_type: hd44780
|
||||||
|
#rs_pin: ar16
|
||||||
|
#e_pin: ar17
|
||||||
|
#d4_pin: ar23
|
||||||
|
#d5_pin: ar25
|
||||||
|
#d6_pin: ar27
|
||||||
|
#d7_pin: ar29
|
||||||
|
|
||||||
|
# "RepRapDiscount 128x64 Full Graphic Smart Controller" type displays
|
||||||
|
#[display]
|
||||||
|
#lcd_type: st7920
|
||||||
|
#cs_pin: ar16
|
||||||
|
#sclk_pin: ar23
|
||||||
|
#sid_pin: ar17
|
||||||
|
|||||||
@@ -13,6 +13,62 @@
|
|||||||
|
|
||||||
# See the example.cfg file for a description of available parameters.
|
# See the example.cfg file for a description of available parameters.
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/rpmsg_pru30
|
||||||
|
pin_map: beaglebone
|
||||||
|
|
||||||
|
[mcu host]
|
||||||
|
serial: /tmp/klipper_host_mcu
|
||||||
|
|
||||||
|
# The "replicape" config section adds "replicape:stepper_x_enable"
|
||||||
|
# virtual stepper enable pins (for steppers x, y, z, e, and h) and
|
||||||
|
# "replicape:power_x" PWM output pins (for hotbed, e, h, fan0, fan1,
|
||||||
|
# fan2, and fan3) that may then be used elsewhere in the config file.
|
||||||
|
[replicape]
|
||||||
|
revision: B3
|
||||||
|
# The replicape hardware revision. Currently only revision "B3" is
|
||||||
|
# supported. This parameter must be provided.
|
||||||
|
#enable_pin: !P9_41
|
||||||
|
# The replicape global enable pin. The default is !P9_41.
|
||||||
|
host_mcu: host
|
||||||
|
# The name of the mcu config section that communicates with the
|
||||||
|
# Klipper "linux process" mcu instance. This parameter must be
|
||||||
|
# provided.
|
||||||
|
#standstill_power_down: False
|
||||||
|
# This parameter controls the CFG6_ENN line on all stepper
|
||||||
|
# motors. True sets the enable lines to "open". The default is
|
||||||
|
# False.
|
||||||
|
#servo0_enable: False
|
||||||
|
# This parameter controls whether end_stop_X_2 is used for endstops
|
||||||
|
# (via P9_11) or for servo_0 (via P9_14). The default is False.
|
||||||
|
#servo1_enable: False
|
||||||
|
# This parameter controls whether end_stop_Y_2 is used for endstops
|
||||||
|
# (via P9_28) or for servo_1 (via P9_16). The default is False.
|
||||||
|
stepper_x_microstep_mode: spread16
|
||||||
|
# This parameter controls the CFG1 and CFG2 pins of the given
|
||||||
|
# stepper motor driver. Available options are: disable, 1, 2,
|
||||||
|
# spread2, 4, 16, spread4, spread16, stealth4, and stealth16. The
|
||||||
|
# default is disable.
|
||||||
|
stepper_x_current: 0.5
|
||||||
|
# The configured maximum current (in Amps) of the stepper motor
|
||||||
|
# driver. This parameter must be provided if the stepper is not in a
|
||||||
|
# disable mode.
|
||||||
|
#stepper_x_chopper_off_time_high: False
|
||||||
|
# This parameter controls the CFG0 pin of the stepper motor driver
|
||||||
|
# (True sets CFG0 high, False sets it low). The default is False.
|
||||||
|
#stepper_x_chopper_hysteresis_high: False
|
||||||
|
# This parameter controls the CFG4 pin of the stepper motor driver
|
||||||
|
# (True sets CFG4 high, False sets it low). The default is False.
|
||||||
|
#stepper_x_chopper_blank_time_high: True
|
||||||
|
# This parameter controls the CFG5 pin of the stepper motor driver
|
||||||
|
# (True sets CFG5 high, False sets it low). The default is True.
|
||||||
|
stepper_y_microstep_mode: spread16
|
||||||
|
stepper_y_current: 0.5
|
||||||
|
stepper_z_microstep_mode: spread16
|
||||||
|
stepper_z_current: 0.5
|
||||||
|
stepper_e_microstep_mode: 16
|
||||||
|
stepper_e_current: 0.5
|
||||||
|
|
||||||
[stepper_x]
|
[stepper_x]
|
||||||
step_pin: P8_17
|
step_pin: P8_17
|
||||||
dir_pin: P8_26
|
dir_pin: P8_26
|
||||||
@@ -37,11 +93,18 @@ homing_speed: 50
|
|||||||
step_pin: P8_13
|
step_pin: P8_13
|
||||||
dir_pin: P8_14
|
dir_pin: P8_14
|
||||||
enable_pin: replicape:stepper_z_enable
|
enable_pin: replicape:stepper_z_enable
|
||||||
step_distance: 0.00025
|
step_distance: .0025
|
||||||
endstop_pin: ^P9_13
|
endstop_pin: ^P9_13
|
||||||
position_endstop: 0
|
position_endstop: 0
|
||||||
position_max: 200
|
position_max: 200
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 25
|
||||||
|
max_z_accel: 30
|
||||||
|
|
||||||
[extruder]
|
[extruder]
|
||||||
step_pin: P9_12
|
step_pin: P9_12
|
||||||
dir_pin: P8_15
|
dir_pin: P8_15
|
||||||
@@ -69,56 +132,3 @@ max_temp: 130
|
|||||||
|
|
||||||
[fan]
|
[fan]
|
||||||
pin: replicape:power_fan0
|
pin: replicape:power_fan0
|
||||||
|
|
||||||
[mcu]
|
|
||||||
serial: /dev/rpmsg_pru30
|
|
||||||
pin_map: beaglebone
|
|
||||||
|
|
||||||
[printer]
|
|
||||||
kinematics: cartesian
|
|
||||||
max_velocity: 300
|
|
||||||
max_accel: 3000
|
|
||||||
max_z_velocity: 25
|
|
||||||
max_z_accel: 30
|
|
||||||
|
|
||||||
[mcu host]
|
|
||||||
serial: /tmp/klipper_host_mcu
|
|
||||||
|
|
||||||
# The "replicape" config section adds "replicape:stepper_x_enable"
|
|
||||||
# virtual stepper enable pins (for steppers x, y, z, e, and h) and
|
|
||||||
# "replicape:power_x" PWM output pins (for hotbed, e, h, fan0, fan1,
|
|
||||||
# fan2, and fan3) that may then be used elsewhere in the config file.
|
|
||||||
[replicape]
|
|
||||||
revision: B3
|
|
||||||
# The replicape hardware revision. Currently only revision "B3" is
|
|
||||||
# supported. This parameter must be provided.
|
|
||||||
#enable_pin: !P9_41
|
|
||||||
# The replicape global enable pin. The default is !P9_41.
|
|
||||||
host_mcu: host
|
|
||||||
# The name of the mcu config section that communicates with the
|
|
||||||
# Klipper "linux process" mcu instance. This parameter must be
|
|
||||||
# provided.
|
|
||||||
stepper_x_microstep_mode: spread16
|
|
||||||
# This parameter controls the CFG1 and CFG2 pins of the given
|
|
||||||
# stepper motor driver. Available options are: disable, 1, 2,
|
|
||||||
# spread2, 4, 16, spread4, spread16, stealth4, and stealth16. The
|
|
||||||
# default is disable.
|
|
||||||
stepper_x_current: 0.5
|
|
||||||
# The configured maximum current (in Amps) of the stepper motor
|
|
||||||
# driver. This parameter must be provided if the stepper is not in a
|
|
||||||
# disable mode.
|
|
||||||
#stepper_x_chopper_off_time_high: False
|
|
||||||
# This parameter controls the CFG0 pin of the stepper motor driver
|
|
||||||
# (True sets CFG0 high, False sets it low). The default is False.
|
|
||||||
#stepper_x_chopper_hysteresis_high: False
|
|
||||||
# This parameter controls the CFG4 pin of the stepper motor driver
|
|
||||||
# (True sets CFG4 high, False sets it low). The default is False.
|
|
||||||
#stepper_x_chopper_blank_time_high: True
|
|
||||||
# This parameter controls the CFG5 pin of the stepper motor driver
|
|
||||||
# (True sets CFG5 high, False sets it low). The default is True.
|
|
||||||
stepper_y_microstep_mode: spread16
|
|
||||||
stepper_y_current: 0.5
|
|
||||||
stepper_z_microstep_mode: spread16
|
|
||||||
stepper_z_current: 0.5
|
|
||||||
stepper_e_microstep_mode: 16
|
|
||||||
stepper_e_current: 0.5
|
|
||||||
|
|||||||
79
config/printer-anet-a8-2017.cfg
Normal file
79
config/printer-anet-a8-2017.cfg
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# This file contains common pin mappings for Anet A8 printer from 2016
|
||||||
|
# and 2017. To use this config, the firmware should be compiled for
|
||||||
|
# the AVR atmega1284p.
|
||||||
|
|
||||||
|
# Note that the "make flash" command does not work with Anet boards -
|
||||||
|
# the boards are typically flashed with this command:
|
||||||
|
# avrdude -p atmega1284p -c arduino -b 57600 -P /dev/ttyUSB0 -U out/klipper.elf.hex
|
||||||
|
|
||||||
|
# See the example.cfg file for a description of available parameters.
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PD7
|
||||||
|
dir_pin: PC5
|
||||||
|
enable_pin: !PD6
|
||||||
|
step_distance: .01
|
||||||
|
endstop_pin: ^!PC2
|
||||||
|
position_endstop: -30
|
||||||
|
position_max: 220
|
||||||
|
position_min: -30
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PC6
|
||||||
|
dir_pin: PC7
|
||||||
|
enable_pin: !PD6
|
||||||
|
step_distance: .01
|
||||||
|
endstop_pin: ^!PC3
|
||||||
|
position_endstop: -8
|
||||||
|
position_min: -8
|
||||||
|
position_max: 220
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PB3
|
||||||
|
dir_pin: !PB2
|
||||||
|
enable_pin: !PA5
|
||||||
|
step_distance: .0025
|
||||||
|
endstop_pin: ^!PC4
|
||||||
|
position_endstop: 0.5
|
||||||
|
position_max: 240
|
||||||
|
homing_speed: 20
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PB1
|
||||||
|
dir_pin: PB0
|
||||||
|
enable_pin: !PD6
|
||||||
|
step_distance: .0105
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PD5
|
||||||
|
sensor_type: ATC Semitec 104GT-2
|
||||||
|
sensor_pin: PA7
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 2.151492
|
||||||
|
pid_Ki: 0.633897
|
||||||
|
pid_Kd: 230.042965
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PD4
|
||||||
|
sensor_type: ATC Semitec 104GT-2
|
||||||
|
sensor_pin: PA6
|
||||||
|
control: watermark
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PB4
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyUSB0
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 1000
|
||||||
|
max_z_velocity: 20
|
||||||
|
max_z_accel: 100
|
||||||
93
config/printer-anycubic-i3-mega-2017.cfg
Normal file
93
config/printer-anycubic-i3-mega-2017.cfg
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# This file contains pin mappings for the Anycubic i3 Mega with
|
||||||
|
# Ultrabase from 2017. (This config may work on an Anycubic i3 Mega v1
|
||||||
|
# prior to the Ultrabase if you comment out the definition of the
|
||||||
|
# endstop_pin in the stepper_z1 section.) To use this config, the
|
||||||
|
# firmware should be compiled for the AVR atmega2560.
|
||||||
|
|
||||||
|
# See the example.cfg file for a description of available parameters.
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: ar54
|
||||||
|
dir_pin: !ar55
|
||||||
|
enable_pin: !ar38
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^!ar3
|
||||||
|
position_min: -5
|
||||||
|
position_endstop: -5
|
||||||
|
position_max: 210
|
||||||
|
homing_speed: 30.0
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: ar60
|
||||||
|
dir_pin: ar61
|
||||||
|
enable_pin: !ar56
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^!ar42
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 210
|
||||||
|
homing_speed: 30.0
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: ar46
|
||||||
|
dir_pin: ar48
|
||||||
|
enable_pin: !ar62
|
||||||
|
step_distance: .0025
|
||||||
|
endstop_pin: ^!ar18
|
||||||
|
position_endstop: 0.0
|
||||||
|
position_max: 205
|
||||||
|
homing_speed: 5.0
|
||||||
|
|
||||||
|
[stepper_z1]
|
||||||
|
step_pin: ar36
|
||||||
|
dir_pin: ar34
|
||||||
|
enable_pin: !ar30
|
||||||
|
step_distance: .0025
|
||||||
|
endstop_pin: ^!ar43
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: ar26
|
||||||
|
dir_pin: ar28
|
||||||
|
enable_pin: !ar24
|
||||||
|
step_distance: .010799
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: ar10
|
||||||
|
sensor_type: ATC Semitec 104GT-2
|
||||||
|
sensor_pin: analog13
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 15.717
|
||||||
|
pid_Ki: 0.569
|
||||||
|
pid_Kd: 108.451
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 245
|
||||||
|
|
||||||
|
[heater_fan extruder_fan]
|
||||||
|
pin: ar44
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: ar8
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: analog14
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 74.883
|
||||||
|
pid_Ki: 1.809
|
||||||
|
pid_Kd: 775.038
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 110
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: ar9
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyUSB0
|
||||||
|
pin_map: arduino
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 10
|
||||||
|
max_z_accel: 60
|
||||||
|
|
||||||
|
[heater_fan stepstick_fan]
|
||||||
|
pin: ar7
|
||||||
88
config/printer-creality-cr10-2017.cfg
Normal file
88
config/printer-creality-cr10-2017.cfg
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# This file contains common pin mappings for the 2017 Creality
|
||||||
|
# CR-10. To use this config, the firmware should be compiled for the
|
||||||
|
# AVR atmega1284p.
|
||||||
|
|
||||||
|
# Note, a number of Melzi boards are shipped without a bootloader. In
|
||||||
|
# that case, an external programmer will be needed to flash a
|
||||||
|
# bootloader to the board (for example, see
|
||||||
|
# http://www.instructables.com/id/Flashing-a-Bootloader-to-the-CR-10/
|
||||||
|
# ). Once that is done, one should be able to use the standard "make
|
||||||
|
# flash" command to flash Klipper.
|
||||||
|
|
||||||
|
# See the example.cfg file for a description of available parameters.
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PD7
|
||||||
|
dir_pin: !PC5
|
||||||
|
enable_pin: !PD6
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^PC2
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 300
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PC6
|
||||||
|
dir_pin: !PC7
|
||||||
|
enable_pin: !PD6
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^PC3
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 300
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PB3
|
||||||
|
dir_pin: PB2
|
||||||
|
enable_pin: !PA5
|
||||||
|
step_distance: .0025
|
||||||
|
endstop_pin: ^PC4
|
||||||
|
position_endstop: 0.0
|
||||||
|
position_max: 400
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PB1
|
||||||
|
dir_pin: !PB0
|
||||||
|
enable_pin: !PD6
|
||||||
|
step_distance: 0.010526
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PD5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PA7
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.57
|
||||||
|
pid_Ki: 1.72
|
||||||
|
pid_Kd: 73.96
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PD4
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PA6
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 426.68
|
||||||
|
pid_Ki: 78.92
|
||||||
|
pid_Kd: 576.71
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PB4
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyUSB0
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 5
|
||||||
|
max_z_accel: 100
|
||||||
|
|
||||||
|
[display]
|
||||||
|
lcd_type: st7920
|
||||||
|
cs_pin: PA3
|
||||||
|
sclk_pin: PA1
|
||||||
|
sid_pin: PC1
|
||||||
75
config/printer-creality-cr10s-2017.cfg
Normal file
75
config/printer-creality-cr10s-2017.cfg
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# This file contains pin mappings for the 2017 Creality CR-10S. To use
|
||||||
|
# this config, the firmware should be compiled for the AVR atmega2560.
|
||||||
|
|
||||||
|
# See the example.cfg file for a description of available parameters.
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: ar54
|
||||||
|
dir_pin: ar55
|
||||||
|
enable_pin: !ar38
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^ar3
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 300
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: ar60
|
||||||
|
dir_pin: ar61
|
||||||
|
enable_pin: !ar56
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^ar14
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 300
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: ar46
|
||||||
|
dir_pin: !ar48
|
||||||
|
enable_pin: !ar62
|
||||||
|
step_distance: .0025
|
||||||
|
endstop_pin: ^ar18
|
||||||
|
position_endstop: 0.5
|
||||||
|
position_max: 200
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: ar26
|
||||||
|
dir_pin: ar28
|
||||||
|
enable_pin: !ar24
|
||||||
|
step_distance: .010526
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: ar10
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: analog13
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.2
|
||||||
|
pid_Ki: 1.08
|
||||||
|
pid_Kd: 114
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 250
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: ar8
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: analog14
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 690.34
|
||||||
|
pid_Ki: 111.47
|
||||||
|
pid_Kd: 1068.83
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: ar9
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyUSB0
|
||||||
|
pin_map: arduino
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 5
|
||||||
|
max_z_accel: 100
|
||||||
116
config/printer-lulzbot-taz6-2017.cfg
Normal file
116
config/printer-lulzbot-taz6-2017.cfg
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# This file contains pin mappings for the Lulzbot TAZ 6 circa 2017. To
|
||||||
|
# use this config, the firmware should be compiled for the AVR
|
||||||
|
# atmega2560.
|
||||||
|
|
||||||
|
# See the example.cfg file for a description of available parameters.
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PC0
|
||||||
|
dir_pin: PL1
|
||||||
|
enable_pin: !PA7
|
||||||
|
step_distance: .010000
|
||||||
|
endstop_pin: ^PB6
|
||||||
|
position_endstop: -20
|
||||||
|
position_min: -20
|
||||||
|
position_max: 300
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PC1
|
||||||
|
dir_pin: !PL0
|
||||||
|
enable_pin: !PA6
|
||||||
|
step_distance: .010000
|
||||||
|
endstop_pin: ^PA1
|
||||||
|
position_endstop: 306
|
||||||
|
position_min: -20
|
||||||
|
position_max: 306
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PC2
|
||||||
|
dir_pin: PL2
|
||||||
|
enable_pin: !PA5
|
||||||
|
step_distance: 0.000625
|
||||||
|
endstop_pin: ^!PB4
|
||||||
|
position_endstop: -0.7
|
||||||
|
position_min: -1.5
|
||||||
|
position_max: 270
|
||||||
|
homing_speed: 1
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PC3
|
||||||
|
dir_pin: !PL6
|
||||||
|
enable_pin: !PA4
|
||||||
|
step_distance: 0.001182
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 2.920
|
||||||
|
heater_pin: PH6
|
||||||
|
sensor_type: ATC Semitec 104GT-2
|
||||||
|
sensor_pin: PF0
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 28.79
|
||||||
|
pid_Ki: 1.91
|
||||||
|
pid_Kd: 108.51
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 300
|
||||||
|
min_extrude_temp: 140
|
||||||
|
|
||||||
|
#[extruder1]
|
||||||
|
#step_pin: PC4
|
||||||
|
#dir_pin: PL7
|
||||||
|
#enable_pin: !PA3
|
||||||
|
#heater_pin: PH4
|
||||||
|
#sensor_pin: PF1
|
||||||
|
#...
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PE5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PF2
|
||||||
|
control: watermark
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 130
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PH5
|
||||||
|
|
||||||
|
[heater_fan nozzle_cooling_fan]
|
||||||
|
pin: PH3
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyACM0
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 2
|
||||||
|
max_z_accel: 10
|
||||||
|
|
||||||
|
[ad5206 stepper_digipot]
|
||||||
|
enable_pin: PD7
|
||||||
|
scale: 2.08
|
||||||
|
# Channel 1 is E0, 2 is E1, 3 is unused, 4 is Z, 5 is X, 6 is Y
|
||||||
|
channel_1: 1.34
|
||||||
|
channel_2: 1.0
|
||||||
|
channel_4: 1.1
|
||||||
|
channel_5: 1.1
|
||||||
|
channel_6: 1.1
|
||||||
|
|
||||||
|
# Enable 16 micro-steps on steppers X, Y, Z, E0, E1
|
||||||
|
[static_digital_output stepper_config]
|
||||||
|
pins:
|
||||||
|
PG1, PG0,
|
||||||
|
PK7, PG2,
|
||||||
|
PK6, PK5,
|
||||||
|
PK3, PK4,
|
||||||
|
PK1, PK2
|
||||||
|
|
||||||
|
[static_digital_output yellow_led]
|
||||||
|
pins: !PB7
|
||||||
|
|
||||||
|
[display]
|
||||||
|
lcd_type: st7920
|
||||||
|
cs_pin: PG4
|
||||||
|
sclk_pin: PJ2
|
||||||
|
sid_pin: PG3
|
||||||
@@ -72,7 +72,8 @@ pin: PH5
|
|||||||
[heater_fan nozzle_fan]
|
[heater_fan nozzle_fan]
|
||||||
pin: PH3
|
pin: PH3
|
||||||
max_power: 0.61
|
max_power: 0.61
|
||||||
hard_pwm: 1
|
cycle_time: .000030
|
||||||
|
hardware_pwm: True
|
||||||
|
|
||||||
[mcu]
|
[mcu]
|
||||||
serial: /dev/ttyACM0
|
serial: /dev/ttyACM0
|
||||||
94
config/printer-seemecnc-rostock-max-v2-2015.cfg
Normal file
94
config/printer-seemecnc-rostock-max-v2-2015.cfg
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# This file constains the pin mappings for the SeeMeCNC Rostock Max
|
||||||
|
# (version 2) delta printer from 2015. To use this config, the
|
||||||
|
# firmware should be compiled for the AVR atmega2560.
|
||||||
|
|
||||||
|
# See the example.cfg file for a description of available parameters.
|
||||||
|
|
||||||
|
[stepper_a]
|
||||||
|
step_pin: PC0
|
||||||
|
dir_pin: !PL1
|
||||||
|
enable_pin: !PA7
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^PA2
|
||||||
|
homing_speed: 50
|
||||||
|
position_endstop: 380
|
||||||
|
arm_length: 290.800
|
||||||
|
|
||||||
|
[stepper_b]
|
||||||
|
step_pin: PC1
|
||||||
|
dir_pin: PL0
|
||||||
|
enable_pin: !PA6
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^PA1
|
||||||
|
|
||||||
|
[stepper_c]
|
||||||
|
step_pin: PC2
|
||||||
|
dir_pin: !PL2
|
||||||
|
enable_pin: !PA5
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^PC7
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PC3
|
||||||
|
dir_pin: !PL6
|
||||||
|
enable_pin: !PA4
|
||||||
|
step_distance: .010793
|
||||||
|
nozzle_diameter: 0.500
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PH6
|
||||||
|
sensor_type: ATC Semitec 104GT-2
|
||||||
|
sensor_pin: PF0
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 20.9700
|
||||||
|
pid_Ki: 1.3400
|
||||||
|
pid_Kd: 80.5600
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 300
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PE5
|
||||||
|
sensor_type: ATC Semitec 104GT-2
|
||||||
|
sensor_pin: PF2
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 46.510
|
||||||
|
pid_Ki: 1.040
|
||||||
|
pid_Kd: 500.000
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 300
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PH5
|
||||||
|
|
||||||
|
[heater_fan nozzle_cooling_fan]
|
||||||
|
pin: PH4
|
||||||
|
heater: extruder
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyACM0
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: delta
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 3000
|
||||||
|
max_z_velocity: 150
|
||||||
|
delta_radius: 174.75
|
||||||
|
|
||||||
|
[ad5206 stepper_digipot]
|
||||||
|
enable_pin: PD7
|
||||||
|
scale: 2.08
|
||||||
|
channel_1: 1.34
|
||||||
|
channel_2: 1.0
|
||||||
|
channel_4: 1.1
|
||||||
|
channel_5: 1.1
|
||||||
|
channel_6: 1.1
|
||||||
|
|
||||||
|
[static_digital_output stepper_config]
|
||||||
|
pins:
|
||||||
|
PG1, PG0,
|
||||||
|
PK7, PG2,
|
||||||
|
PK6, PK5,
|
||||||
|
PK3, PK4,
|
||||||
|
PK1, PK2
|
||||||
|
|
||||||
|
[static_digital_output yellow_led]
|
||||||
|
pins: !PB7
|
||||||
89
config/printer-tronxy-x5s-2017.cfg
Normal file
89
config/printer-tronxy-x5s-2017.cfg
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# This file contains pin mappings for the Tronxy X5S (circa 2017). To
|
||||||
|
# use this config, the firmware should be compiled for the AVR
|
||||||
|
# atmega1284p.
|
||||||
|
|
||||||
|
# Note, a number of Melzi boards are shipped without a bootloader. In
|
||||||
|
# that case, an external programmer will be needed to flash a
|
||||||
|
# bootloader to the board (for example, see
|
||||||
|
# http://www.instructables.com/id/Flashing-a-Bootloader-to-the-CR-10/
|
||||||
|
# ). Once that is done, one should be able to use the standard "make
|
||||||
|
# flash" command to flash Klipper.
|
||||||
|
|
||||||
|
# See the example.cfg file for a description of available parameters.
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PD7
|
||||||
|
dir_pin: !PC5
|
||||||
|
enable_pin: !PD6
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^!PC2
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 330
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PC6
|
||||||
|
dir_pin: !PC7
|
||||||
|
enable_pin: !PD6
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^!PC3
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 310
|
||||||
|
homing_speed: 50
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PB3
|
||||||
|
dir_pin: PB2
|
||||||
|
enable_pin: !PD6
|
||||||
|
step_distance: .0025
|
||||||
|
endstop_pin: ^!PC4
|
||||||
|
position_endstop: 0.5
|
||||||
|
position_max: 400
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PB1
|
||||||
|
dir_pin: PB0
|
||||||
|
enable_pin: !PD6
|
||||||
|
step_distance: .0111
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PD5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PA7
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 22.2
|
||||||
|
pid_Ki: 1.08
|
||||||
|
pid_Kd: 114
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 275
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PD4
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PA6
|
||||||
|
control: watermark
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 150
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PB4
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyUSB0
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: corexy
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 1000
|
||||||
|
max_z_velocity: 20
|
||||||
|
max_z_accel: 100
|
||||||
|
|
||||||
|
[display]
|
||||||
|
lcd_type: st7920
|
||||||
|
cs_pin: PA1
|
||||||
|
sclk_pin: PC0
|
||||||
|
sid_pin: PA3
|
||||||
|
|
||||||
|
# buttons are:
|
||||||
|
# PD2, PD3: encoder
|
||||||
|
# PA5: click
|
||||||
78
config/printer-wanhao-duplicator-i3-plus-2017.cfg
Normal file
78
config/printer-wanhao-duplicator-i3-plus-2017.cfg
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# This file contains pin mappings for the Wanhao Duplicator i3 Plus
|
||||||
|
# (circa 2017). To use this config, the firmware should be compiled
|
||||||
|
# for the AVR atmega2560.
|
||||||
|
# Pin numbers and other parameters were extracted from the
|
||||||
|
# official Marlin source available at:
|
||||||
|
# https://github.com/garychen99/Duplicator-i3-plus
|
||||||
|
|
||||||
|
# See the example.cfg file for a description of available parameters.
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PF7
|
||||||
|
dir_pin: !PK0
|
||||||
|
enable_pin: !PF6
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^!PF0
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 30.0
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PK2
|
||||||
|
dir_pin: !PK3
|
||||||
|
enable_pin: !PK1
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^!PA2
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 30.0
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PK5
|
||||||
|
dir_pin: PK7
|
||||||
|
enable_pin: !PK4
|
||||||
|
step_distance: .0025
|
||||||
|
endstop_pin: ^!PA1
|
||||||
|
position_endstop: 0.5
|
||||||
|
position_max: 180
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PF4
|
||||||
|
dir_pin: PF5
|
||||||
|
enable_pin: !PF3
|
||||||
|
step_distance: 0.010417
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PG5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PF1
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 30.850721
|
||||||
|
pid_Ki: .208175
|
||||||
|
pid_Kd: 192.298728
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 260
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PE5
|
||||||
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
|
sensor_pin: PK6
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 64.095903
|
||||||
|
pid_Ki: 1.649830
|
||||||
|
pid_Kd: 622.531455
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 110
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PE3
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyUSB0
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 300
|
||||||
|
max_accel: 800
|
||||||
|
max_z_velocity: 5
|
||||||
|
max_z_accel: 100
|
||||||
159
config/printer-wanhao-duplicator-i3-v2.1-2017.cfg
Normal file
159
config/printer-wanhao-duplicator-i3-v2.1-2017.cfg
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
# This file contains pin mappings and other appropriate default parameters
|
||||||
|
# for a Wanhao Duplicator i3 v2.1 and its clones
|
||||||
|
# (Monoprice Maker Select, Cocoon Create, etc.)
|
||||||
|
# See the files example.cfg and example-extras.cfg for a description of available parameters.
|
||||||
|
#
|
||||||
|
# This will probably work on older revisions (v1.0, v2.0) of the printer
|
||||||
|
# but is untested on those versions.
|
||||||
|
#
|
||||||
|
# For best results with klipper and the Wanhao Duplicator i3, follow these
|
||||||
|
# guidelines:
|
||||||
|
#
|
||||||
|
# - Flash a bootloader to the Melzi board in the printer
|
||||||
|
# See http://www.instructables.com/id/Using-an-Arduino-to-Flash-the-Melzi-Board-Wanhao-I/
|
||||||
|
#
|
||||||
|
# - Make sure the auto-reset jumper is *enabled* on the Melzi board
|
||||||
|
# (See step 1 in the bootloader tutorial above)
|
||||||
|
#
|
||||||
|
# - Locate the USB serial port for your printer in /dev/serial/by-id/ format.
|
||||||
|
# See https://github.com/KevinOConnor/klipper/blob/master/docs/FAQ.md#wheres-my-serial-port
|
||||||
|
# It will be something like:
|
||||||
|
# /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_ABCD1234-if00-port0
|
||||||
|
#
|
||||||
|
# - Configure klipper to compile firmware for the AVR atmega1284p
|
||||||
|
#
|
||||||
|
# - At this point, "make flash FLASH_DEVICE=..." should successfully
|
||||||
|
# flash your printer board. Use the /dev/serial/by-id/ format for
|
||||||
|
# FLASH_DEVICE to ensure consistent results.
|
||||||
|
# See https://github.com/KevinOConnor/klipper/blob/master/docs/FAQ.md#the-make-flash-command-doesnt-work
|
||||||
|
# if you have problems.
|
||||||
|
#
|
||||||
|
# - Copy this sample file you are currently reading to ~/printer.cfg,
|
||||||
|
# and customize the following parameters:
|
||||||
|
# * [extruder] > step_distance
|
||||||
|
#
|
||||||
|
# This is the inverse of "E steps" (extruder steps per mm) from the stock
|
||||||
|
# Wanhao Repetier-based firmware.
|
||||||
|
# (See https://3dprinterwiki.info/extruder-steps/ )
|
||||||
|
#
|
||||||
|
# For example, if your E-steps are set to 107.0 steps per mm,
|
||||||
|
# then step_distance should be (1 / 107.0) ~= .009346
|
||||||
|
#
|
||||||
|
# * [extruder] > PID parameters (pid_Kp, pid_Ki, pid_Kd)
|
||||||
|
# * [heater_bed] > PID parameters (pid_Kp, pid_Ki, pid_Kd)
|
||||||
|
#
|
||||||
|
# PID values from stock Wanhao firmware (Repetier) do not
|
||||||
|
# translate directly to klipper. You will need to run klipper's
|
||||||
|
# PID autotune function for the extruder and bed. After getting the
|
||||||
|
# klipper firmware up and running, run the PID_CALIBRATE procedures
|
||||||
|
# by sending these commands via octoprint terminal (one per autotune):
|
||||||
|
#
|
||||||
|
# extruder: PID_CALIBRATE HEATER=extruder TARGET=<temp>
|
||||||
|
# heated bed: PID_CALIBRATE HEATER=heater_bed TARGET=<temp>
|
||||||
|
#
|
||||||
|
# After the autotune process completes, PID parameter results
|
||||||
|
# can be found in the Octoprint terminal tab (if you're quick)
|
||||||
|
# or in /tmp/klippy.log.
|
||||||
|
#
|
||||||
|
# Enter the PID parameters into the appropriate sections of ~/printer.cfg .
|
||||||
|
#
|
||||||
|
# * [extruder] > max_temp
|
||||||
|
# * [heater_bed] > max_temp
|
||||||
|
#
|
||||||
|
# The max temps included in this printer config are limited to 230 for extruder
|
||||||
|
# and 70 for heated bed. If your printer has been modified to handle higher temps
|
||||||
|
# (like an upgraded hot end or a separate MOSFET for your heated bed), you may
|
||||||
|
# want to increase these values.
|
||||||
|
#
|
||||||
|
# * [mcu] > serial
|
||||||
|
#
|
||||||
|
# Enter the USB serial port of the printer in /dev/serial/by-id/ format
|
||||||
|
# for best results.
|
||||||
|
#
|
||||||
|
# - Power cycle the Wanhao Duplicator i3
|
||||||
|
#
|
||||||
|
# - Issue the command "RESTART" via the Octoprint terminal tab (similar to
|
||||||
|
# how you would send a manual gcode command, but send the word RESTART).
|
||||||
|
# This tells klipper to reload its config file and do an internal reset.
|
||||||
|
# You should then see a status screen appear on the printer's LCD.
|
||||||
|
#
|
||||||
|
# - Be sure to follow these instructions before attempting any prints:
|
||||||
|
# https://github.com/KevinOConnor/klipper/blob/master/docs/Config_checks.md
|
||||||
|
|
||||||
|
[stepper_x]
|
||||||
|
step_pin: PD7
|
||||||
|
dir_pin: PC5
|
||||||
|
enable_pin: !PD6
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^!PC2
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 40
|
||||||
|
|
||||||
|
[stepper_y]
|
||||||
|
step_pin: PC6
|
||||||
|
dir_pin: PC7
|
||||||
|
enable_pin: !PD6
|
||||||
|
step_distance: .0125
|
||||||
|
endstop_pin: ^!PC3
|
||||||
|
position_endstop: 0
|
||||||
|
position_max: 200
|
||||||
|
homing_speed: 40
|
||||||
|
|
||||||
|
[stepper_z]
|
||||||
|
step_pin: PB3
|
||||||
|
dir_pin: !PB2
|
||||||
|
enable_pin: !PA5
|
||||||
|
step_distance: 0.0025
|
||||||
|
endstop_pin: ^!PC4
|
||||||
|
position_endstop: 0.5
|
||||||
|
position_max: 180
|
||||||
|
homing_speed: 2
|
||||||
|
|
||||||
|
[extruder]
|
||||||
|
step_pin: PB1
|
||||||
|
dir_pin: !PB0
|
||||||
|
enable_pin: !PD6
|
||||||
|
step_distance: .009346
|
||||||
|
nozzle_diameter: 0.400
|
||||||
|
filament_diameter: 1.750
|
||||||
|
heater_pin: PD5
|
||||||
|
sensor_type: NTC 100K beta 3950
|
||||||
|
sensor_pin: PA7
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 18.214030
|
||||||
|
pid_Ki: 0.616380
|
||||||
|
pid_Kd: 134.556146
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 230
|
||||||
|
|
||||||
|
[heater_bed]
|
||||||
|
heater_pin: PD4
|
||||||
|
sensor_type: NTC 100K beta 3950
|
||||||
|
sensor_pin: PA6
|
||||||
|
control: pid
|
||||||
|
pid_Kp: 71.321
|
||||||
|
pid_Ki: 1.989
|
||||||
|
pid_Kd: 639.210
|
||||||
|
min_temp: 0
|
||||||
|
max_temp: 70
|
||||||
|
|
||||||
|
[fan]
|
||||||
|
pin: PB4
|
||||||
|
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/ttyUSB0
|
||||||
|
restart_method: command
|
||||||
|
|
||||||
|
[printer]
|
||||||
|
kinematics: cartesian
|
||||||
|
max_velocity: 200
|
||||||
|
max_accel: 1000
|
||||||
|
max_z_velocity: 2
|
||||||
|
max_z_accel: 100
|
||||||
|
|
||||||
|
[display]
|
||||||
|
lcd_type: st7920
|
||||||
|
cs_pin: PC1
|
||||||
|
sclk_pin: PD3
|
||||||
|
sid_pin: PC0
|
||||||
56
config/sample-bltouch.cfg
Normal file
56
config/sample-bltouch.cfg
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# This file provides example config file settings for the BLTouch
|
||||||
|
# automatic bed leveling sensor. This file is just a "snippet" of
|
||||||
|
# sections specific to the BLTouch - it must be added to a config file
|
||||||
|
# containing the configuration of the rest of the printer.
|
||||||
|
|
||||||
|
# Be sure to review and update this config with the appropriate pins
|
||||||
|
# and coordinates for your printer.
|
||||||
|
|
||||||
|
# See the "example.cfg" and "example-extras.cfg" files for a
|
||||||
|
# description of config parameters.
|
||||||
|
|
||||||
|
# Define the BLTouch servo
|
||||||
|
[servo bltouch]
|
||||||
|
pin: ar32
|
||||||
|
maximum_servo_angle: 180
|
||||||
|
minimum_pulse_width: 0.0006
|
||||||
|
maximum_pulse_width: 0.0024
|
||||||
|
|
||||||
|
# Define a probe using the BLTouch
|
||||||
|
[probe]
|
||||||
|
pin: ar30
|
||||||
|
activate_gcode:
|
||||||
|
SET_SERVO SERVO=bltouch ANGLE=10
|
||||||
|
SET_SERVO SERVO=bltouch ANGLE=60
|
||||||
|
G4 P200
|
||||||
|
deactivate_gcode:
|
||||||
|
SET_SERVO SERVO=bltouch ANGLE=90
|
||||||
|
|
||||||
|
# Example bed_tilt config section
|
||||||
|
[bed_tilt]
|
||||||
|
#x_adjust:
|
||||||
|
#y_adjust:
|
||||||
|
points:
|
||||||
|
100,100
|
||||||
|
10,10
|
||||||
|
10,100
|
||||||
|
10,190
|
||||||
|
100,10
|
||||||
|
100,190
|
||||||
|
190,10
|
||||||
|
190,100
|
||||||
|
190,190
|
||||||
|
probe_z_offset: 2.345
|
||||||
|
|
||||||
|
# If the BLTouch is used to home the Z axis, then define a
|
||||||
|
# homing_override section, use probe:z_virtual_endstop as the
|
||||||
|
# endstop_pin in the stepper_z section, and set the endstop_position
|
||||||
|
# in the stepper_z section to match the probe's probe_z_offset.
|
||||||
|
#[homing_override]
|
||||||
|
#set_position_z: 5
|
||||||
|
#gcode:
|
||||||
|
# ; G90 ; Uncomment these 2 lines to blindly lift the Z 2mm at start
|
||||||
|
# ; G1 Z7 F600
|
||||||
|
# G28 X0 Y0
|
||||||
|
# G1 X100 Y100 F3600
|
||||||
|
# G28 Z0
|
||||||
38
docs/CONTRIBUTING.md
Normal file
38
docs/CONTRIBUTING.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Contributing to Klipper
|
||||||
|
|
||||||
|
Thank you for contributing to Klipper! Please take a moment to read
|
||||||
|
this document.
|
||||||
|
|
||||||
|
## Creating a new issue
|
||||||
|
|
||||||
|
Please see the [contact page](Contact.md) for information on creating
|
||||||
|
an issue. In particular, **we need the klippy.log file** attached to
|
||||||
|
bug reports. Also, be sure to read the [FAQ](FAQ.md) to see if a
|
||||||
|
similar issue has already been raised.
|
||||||
|
|
||||||
|
## Submitting a pull request
|
||||||
|
|
||||||
|
Contributions of Code and documentation are managed through github
|
||||||
|
pull requests. Each commit should have a commit message formatted
|
||||||
|
similar to the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
module: Capitalized, short (50 chars or less) summary
|
||||||
|
|
||||||
|
More detailed explanatory text, if necessary. Wrap it to about 75
|
||||||
|
characters or so. In some contexts, the first line is treated as the
|
||||||
|
subject of an email and the rest of the text as the body. The blank
|
||||||
|
line separating the summary from the body is critical (unless you omit
|
||||||
|
the body entirely); tools like rebase can get confused if you run the
|
||||||
|
two together.
|
||||||
|
|
||||||
|
Further paragraphs come after blank lines..
|
||||||
|
|
||||||
|
Signed-off-by: My Name <myemail@example.org>
|
||||||
|
```
|
||||||
|
|
||||||
|
It is important to have a "Signed-off-by" line on each commit - it
|
||||||
|
certifies that you agree to the
|
||||||
|
[developer certificate of origin](developer-certificate-of-origin). It
|
||||||
|
must contain your real name (sorry, no pseudonyms or anonymous
|
||||||
|
contributions) and contain a current email address.
|
||||||
@@ -29,6 +29,8 @@ files.
|
|||||||
The **scripts/** directory contains build-time scripts useful for
|
The **scripts/** directory contains build-time scripts useful for
|
||||||
compiling the micro-controller code.
|
compiling the micro-controller code.
|
||||||
|
|
||||||
|
The **test/** directory contains automated test cases.
|
||||||
|
|
||||||
During compilation, the build may create an **out/** directory. This
|
During compilation, the build may create an **out/** directory. This
|
||||||
contains temporary build time objects. The final micro-controller
|
contains temporary build time objects. The final micro-controller
|
||||||
object that is built is **out/klipper.elf.hex** on AVR and
|
object that is built is **out/klipper.elf.hex** on AVR and
|
||||||
@@ -77,7 +79,7 @@ interrupts disabled.
|
|||||||
Much of the functionality of the micro-controller involves working
|
Much of the functionality of the micro-controller involves working
|
||||||
with General-Purpose Input/Output pins (GPIO). In order to abstract
|
with General-Purpose Input/Output pins (GPIO). In order to abstract
|
||||||
the low-level architecture specific code from the high-level task
|
the low-level architecture specific code from the high-level task
|
||||||
code, all GPIO events are implemented in architectures specific
|
code, all GPIO events are implemented in architecture specific
|
||||||
wrappers (eg, **src/avr/gpio.c**). The code is compiled with gcc's
|
wrappers (eg, **src/avr/gpio.c**). The code is compiled with gcc's
|
||||||
"-flto -fwhole-program" optimization which does an excellent job of
|
"-flto -fwhole-program" optimization which does an excellent job of
|
||||||
inlining functions across compilation units, so most of these tiny
|
inlining functions across compilation units, so most of these tiny
|
||||||
@@ -212,6 +214,162 @@ ToolHead and kinematic classes. It's this part of the code which
|
|||||||
specifies the movements and their timings. The remaining parts of the
|
specifies the movements and their timings. The remaining parts of the
|
||||||
processing is mostly just communication and plumbing.
|
processing is mostly just communication and plumbing.
|
||||||
|
|
||||||
|
Adding a host module
|
||||||
|
====================
|
||||||
|
|
||||||
|
The Klippy host code has a dynamic module loading capability. If a
|
||||||
|
config section named "[my_module]" is found in the printer config file
|
||||||
|
then the software will automatically attempt to load the python module
|
||||||
|
klippy/extras/my_module.py . This module system is the preferred
|
||||||
|
method for adding new functionality to Klipper.
|
||||||
|
|
||||||
|
The easiest way to add a new module is to use an existing module as a
|
||||||
|
reference - see **klippy/extras/servo.py** as an example.
|
||||||
|
|
||||||
|
The following may also be useful:
|
||||||
|
* Execution of the module starts in the module level `load_config()`
|
||||||
|
function (for config sections of the form [my_module]) or in
|
||||||
|
`load_config_prefix()` (for config sections of the form
|
||||||
|
[my_module my_name]). This function is passed a "config" object and
|
||||||
|
it must return a new "printer object" associated with the given
|
||||||
|
config section.
|
||||||
|
* During the process of instantiating a new printer object, the config
|
||||||
|
object can be used to read parameters from the given config
|
||||||
|
section. This is done using `config.get()`, `config.getfloat()`,
|
||||||
|
`config.getint()`, etc. methods. Be sure to read all values from the
|
||||||
|
config during the construction of the printer object - if the user
|
||||||
|
specifies a config parameter that is not read during this phase then
|
||||||
|
it will be assumed it is a typo in the config and an error will be
|
||||||
|
raised.
|
||||||
|
* Use the `config.get_printer()` method to obtain a reference to the
|
||||||
|
main "printer" class. This "printer" class stores references to all
|
||||||
|
the "printer objects" that have been instantiated. Use the
|
||||||
|
`printer.lookup_object()` method to find references to other printer
|
||||||
|
objects. Almost all functionality (even core kinematic modules) are
|
||||||
|
encapsulated in one of these printer objects. Note, though, that
|
||||||
|
when a new module is instantiated, not all other printer objects
|
||||||
|
will have been instantiated. The "gcode" and "pins" modules will
|
||||||
|
always be available, but for other modules it is a good idea to
|
||||||
|
defer the lookup.
|
||||||
|
* Define a `printer_state()` method if the code needs to be called
|
||||||
|
during printer setup and/or shutdown. This method is called twice
|
||||||
|
during setup (with "connect" and then "ready") and may also be
|
||||||
|
called at run-time (with "shutdown" or "disconnect"). It is common
|
||||||
|
to perform "printer object" lookup during the "connect" and "ready"
|
||||||
|
phases.
|
||||||
|
* If there is an error in the user's config, be sure to raise it
|
||||||
|
during the `load_config()` or `printer_state("connect")` phases. Use
|
||||||
|
either `raise config.error("my error")` or `raise
|
||||||
|
printer.config_error("my error")` to report the error.
|
||||||
|
* Use the "pins" module to configure a pin on a micro-controller. This
|
||||||
|
is typically done with something similar to
|
||||||
|
`printer.lookup_object("pins").setup_pin("pwm",
|
||||||
|
config.get("my_pin"))`. The returned object can then be commanded at
|
||||||
|
run-time.
|
||||||
|
* If the module needs access to system timing or external file
|
||||||
|
descriptors then use `printer.get_reactor()` to obtain access to the
|
||||||
|
global "event reactor" class. This reactor class allows one to
|
||||||
|
schedule timers, wait for input on file descriptors, and to "sleep"
|
||||||
|
the host code.
|
||||||
|
* Do not use global variables. All state should be stored in the
|
||||||
|
printer object returned from the `load_config()` function. This is
|
||||||
|
important as otherwise the RESTART command may not perform as
|
||||||
|
expected. Also, for similar reasons, if any external files (or
|
||||||
|
sockets) are opened then be sure to close them from the
|
||||||
|
`printer_state("disconnect")` callback.
|
||||||
|
* Avoid accessing the internal member variables (or calling methods
|
||||||
|
that start with an underscore) of other printer objects. Observing
|
||||||
|
this convention makes it easier to manage future changes.
|
||||||
|
* If submitting the module for inclusion in the main Klipper code, be
|
||||||
|
sure to place a copyright notice at the top of the module. See the
|
||||||
|
existing modules for the preferred format.
|
||||||
|
|
||||||
|
Adding new kinematics
|
||||||
|
=====================
|
||||||
|
|
||||||
|
This section provides some tips on adding support to Klipper for
|
||||||
|
additional types of printer kinematics. This type of activity requires
|
||||||
|
excellent understanding of the math formulas for the target
|
||||||
|
kinematics. It also requires software development skills - though one
|
||||||
|
should only need to update the host software (which is written in
|
||||||
|
Python).
|
||||||
|
|
||||||
|
Useful steps:
|
||||||
|
1. Start by studying the [above section](#code-flow-of-a-move-command)
|
||||||
|
and the [Kinematics document](Kinematics.md).
|
||||||
|
2. Review the existing kinematic classes in cartesian.py, corexy.py,
|
||||||
|
and delta.py. The kinematic classes are tasked with converting a
|
||||||
|
move in cartesian coordinates to the movement on each stepper. One
|
||||||
|
should be able to copy one of these files as a starting point.
|
||||||
|
3. Implement the `get_postion()` method in the new kinematics
|
||||||
|
class. This method converts the current stepper position of each
|
||||||
|
stepper axis (stored in millimeters) to a position in cartesian
|
||||||
|
space (also in millimeters).
|
||||||
|
4. Implement the `set_postion()` method. This is the inverse of
|
||||||
|
get_position() - it sets each axis position (in millimeters) given
|
||||||
|
a position in cartesian coordinates.
|
||||||
|
5. Implement the `move()` method. The goal of the move() method is to
|
||||||
|
convert a move defined in cartesian space to a series of stepper
|
||||||
|
step times that implement the requested movement.
|
||||||
|
* The `move()` method is passed a "print_time" parameter (which
|
||||||
|
stores a time in seconds) and a "move" class instance that fully
|
||||||
|
defines the movement. The goal is to repeatedly invoke the
|
||||||
|
`stepper.step()` method with the time (relative to print_time)
|
||||||
|
that each stepper should step at to obtain the desired motion.
|
||||||
|
* One "trick" to help with the movement calculations is to imagine
|
||||||
|
there is a physical rail between `move.start_pos` and
|
||||||
|
`move.end_pos` that confines the print head so that it can only
|
||||||
|
move along this straight line of motion. Then, if the head is
|
||||||
|
confined to that imaginary rail, the head is at `move.start_pos`,
|
||||||
|
only one stepper is enabled (all other steppers can move freely),
|
||||||
|
and the given stepper is stepped a single step, then one can
|
||||||
|
imagine that the head will move along the line of movement some
|
||||||
|
distance. Determine the formula converting this step distance to
|
||||||
|
distance along the line of movement. Once one has the distance
|
||||||
|
along the line of movement, one can figure out the time that the
|
||||||
|
head should be at that position (using the standard formulas for
|
||||||
|
velocity and acceleration). This time is the ideal step time for
|
||||||
|
the given stepper and it can be passed to the `stepper.step()`
|
||||||
|
method.
|
||||||
|
* The `stepper.step()` method must always be called with an
|
||||||
|
increasing time for a given stepper (steps must be scheduled in
|
||||||
|
the order they are to be executed). A common error during
|
||||||
|
kinematic development is to receive an "Internal error in
|
||||||
|
stepcompress" failure - this is generally due to the step()
|
||||||
|
method being invoked with a time earlier than the last scheduled
|
||||||
|
step. For example, if the last step in move1 is scheduled at a
|
||||||
|
time greater than the first step in move2 it will generally
|
||||||
|
result in the above error.
|
||||||
|
* Fractional steps. Be aware that a move request is given in
|
||||||
|
cartesian space and it is not confined to discreet
|
||||||
|
locations. Thus a move's start and end locations may translate to
|
||||||
|
a location on a stepper axis that is between two steps (a
|
||||||
|
fractional step). The code must handle this. The preferred
|
||||||
|
approach is to schedule the next step at the time a move would
|
||||||
|
position the stepper axis at least half way towards the next
|
||||||
|
possible step location. Incorrect handling of fractional steps is
|
||||||
|
a common cause of "Internal error in stepcompress" failures.
|
||||||
|
6. Other methods. The `home()`, `check_move()`, and other methods
|
||||||
|
should also be implemented. However, at the start of development
|
||||||
|
one can use empty code here.
|
||||||
|
7. Implement test cases. Create a g-code file with a series of moves
|
||||||
|
that can test important cases for the given kinematics. Follow the
|
||||||
|
[debugging documentation](Debugging.md) to convert this g-code file
|
||||||
|
to micro-controller commands. This is useful to exercise corner
|
||||||
|
cases and to check for regressions.
|
||||||
|
8. Optimize if needed. One may notice that the existing kinematic
|
||||||
|
classes do not call `stepper.step()`. This is purely an
|
||||||
|
optimization - the inner loop of the kinematic calculations were
|
||||||
|
moved to C to reduce load on the host cpu. All of the existing
|
||||||
|
kinematic classes started development using `stepper.step()` and
|
||||||
|
then were later optimized. The g-code to mcu command translation
|
||||||
|
(described in the previous step) is a useful tool during
|
||||||
|
optimization - if a code change is purely an optimization then it
|
||||||
|
should not impact the resulting text representation of the mcu
|
||||||
|
commands (though minor changes in output due to floating point
|
||||||
|
rounding are possible). So, one can use this system to detect
|
||||||
|
regressions.
|
||||||
|
|
||||||
Time
|
Time
|
||||||
====
|
====
|
||||||
|
|
||||||
|
|||||||
147
docs/Config_checks.md
Normal file
147
docs/Config_checks.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
This document provides a list of steps to help confirm the pin
|
||||||
|
settings in the Klipper printer.cfg file. It is a good idea to run
|
||||||
|
through these steps after following the steps in the
|
||||||
|
[installation document](Installation.md).
|
||||||
|
|
||||||
|
During this guide, it may be necessary to make changes to the Klipper
|
||||||
|
config file. Be sure to issue a RESTART command after every change to
|
||||||
|
the config file to ensure that the change takes effect (type "restart"
|
||||||
|
in the Octoprint terminal tab and then click "Send"). It's also a good
|
||||||
|
idea to issue a STATUS command after every RESTART to verify that the
|
||||||
|
config file is successfully loaded.
|
||||||
|
|
||||||
|
### Verify temperature
|
||||||
|
|
||||||
|
Start by verifying that temperatures are being properly
|
||||||
|
reported. Navigate to the Octoprint temperature tab.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Verify that the temperature of the nozzle and bed (if applicable) are
|
||||||
|
present and not increasing. If it is increasing, remove power from the
|
||||||
|
printer. If the temperatures are not accurate, review the
|
||||||
|
"sensor_type" and "sensor_pin" settings for the nozzle and/or bed.
|
||||||
|
|
||||||
|
### Verify M112
|
||||||
|
|
||||||
|
Navigate to the Octoprint terminal tab and issue an M112 command in
|
||||||
|
the terminal box. This command requests Klipper to go into a
|
||||||
|
"shutdown" state. It will cause Octoprint to disconnect from Klipper -
|
||||||
|
navigate to the Connection area and click on "Connect" to cause
|
||||||
|
Octoprint to reconnect. Then navigate to the Octoprint temperature tab
|
||||||
|
and verify that temperatures continue to update and the temperatures
|
||||||
|
are not increasing. If temperatures are increasing, remove power from
|
||||||
|
the printer.
|
||||||
|
|
||||||
|
The M112 command causes Klipper to go into a "shutdown" state. To
|
||||||
|
clear this state, issue a FIRMWARE_RESTART command in the Octoprint
|
||||||
|
terminal tab.
|
||||||
|
|
||||||
|
### Verify heaters
|
||||||
|
|
||||||
|
Navigate to the Octoprint temperature tab and type in 50 followed by
|
||||||
|
enter in the "Tool" temperature box. The extruder temperature in the
|
||||||
|
graph should start to increase (within about 30 seconds or so). Then
|
||||||
|
go to the "Tool" temperature drop-down box and select "Off". After
|
||||||
|
several minutes the temperature should start to return to its initial
|
||||||
|
room temperature value. If the temperature does not increase then
|
||||||
|
verify the "heater_pin" setting in the config.
|
||||||
|
|
||||||
|
If the printer has a heated bed then perform the above test again with
|
||||||
|
the bed.
|
||||||
|
|
||||||
|
### Verify stepper motor enable pin
|
||||||
|
|
||||||
|
Verify that all of the printer axes can manually move freely (the
|
||||||
|
stepper motors are disabled). If not, issue an M84 command to disable
|
||||||
|
the motors. If any of the axes still can not move freely, then verify
|
||||||
|
the stepper "enable_pin" configuration for the given axis. On most
|
||||||
|
commodity stepper motor drivers, the motor enable pin is "active low"
|
||||||
|
and therefore the enable pin should have a "!" before the pin (for
|
||||||
|
example, "enable_pin: !ar38").
|
||||||
|
|
||||||
|
### Verify endstops
|
||||||
|
|
||||||
|
Manually move all the printer axes so that none of them are in contact
|
||||||
|
with an endstop. Send a QUERY_ENDSTOPS command via the Octoprint
|
||||||
|
terminal tab. It should respond with the current state of all of the
|
||||||
|
configured endstops and they should all report a state of "open". For
|
||||||
|
each of the endstops, rerun the QUERY_ENDSTOPS command while manually
|
||||||
|
triggering the endstop. The QUERY_ENDSTOPS command should report the
|
||||||
|
endstop as "TRIGGERED".
|
||||||
|
|
||||||
|
If the endstop appears inverted (it reports "open" when triggered and
|
||||||
|
vice-versa) then add a "!" to the pin definition (for example,
|
||||||
|
"endstop_pin: ^!ar3"), or remove the "!" if there is already one
|
||||||
|
present.
|
||||||
|
|
||||||
|
If the endstop does not change at all then it generally indicates that
|
||||||
|
the endstop is connected to a different pin. However, it may also
|
||||||
|
require a change to the pullup setting of the pin (the '^' at the
|
||||||
|
start of the endstop_pin name - most printers will use a pullup
|
||||||
|
resistor and the '^' should be present).
|
||||||
|
|
||||||
|
### Verify stepper motor direction
|
||||||
|
|
||||||
|
Make sure the printer.cfg file does not have "homing_speed" set for
|
||||||
|
any axis (or set it to a value of 5 or less).
|
||||||
|
|
||||||
|
On cartesian style printers, manually move the X axis to a midway
|
||||||
|
point, issue a G28X0 command, and verify that the X motor moves slowly
|
||||||
|
towards the endstop defined for that axis. If the motor moves in the
|
||||||
|
wrong direction issue an M112 command to abort the move. A wrong
|
||||||
|
direction generally indicates that the "dir_pin" for the axis needs to
|
||||||
|
be inverted. This is done by adding a '!' to the "dir_pin" in the
|
||||||
|
printer config file (or removing it if one is already there). For
|
||||||
|
example, change "dir_pin: xyz" to "dir_pin: !xyz". Then RESTART and
|
||||||
|
retest the axis. If the axis does not move at all, then verify the
|
||||||
|
"enable_pin" and "step_pin" settings for the axis. For cartesian style
|
||||||
|
printers, repeat the test for the Y and Z axis with G28Y0 and G28Z0.
|
||||||
|
|
||||||
|
For delta style printers, manually move all three carriages to a
|
||||||
|
midway point and then issue a G28 command. Verify all three motors
|
||||||
|
move simultaneously upwards. If not, issue an M112 command and follow
|
||||||
|
the troubleshooting steps in the preceding paragraph.
|
||||||
|
|
||||||
|
### Verify extruder motor
|
||||||
|
|
||||||
|
To test the extruder motor it will be necessary to heat the extruder
|
||||||
|
to a printing temperature. Navigate to the Octoprint temperature tab
|
||||||
|
and select a target temperature from the temperature drop-down box (or
|
||||||
|
manually enter an appropriate temperature). Wait for the printer to
|
||||||
|
reach the desired temperature. Then navigate to the Octoprint control
|
||||||
|
tab and click the "Extrude" button. Verify that the extruder motor
|
||||||
|
turns in the correct direction. If it does not, see the
|
||||||
|
troubleshooting tips in the previous section to confirm the
|
||||||
|
"enable_pin", "step_pin", and "dir_pin" settings for the extruder.
|
||||||
|
|
||||||
|
### Calibrate PID settings
|
||||||
|
|
||||||
|
Klipper supports
|
||||||
|
[PID control](https://en.wikipedia.org/wiki/PID_controller) for the
|
||||||
|
extruder and bed heaters. In order to use this control mechanism it is
|
||||||
|
necessary to calibrate the PID settings on each printer. (PID settings
|
||||||
|
found in other firmwares or in the example configuration files often
|
||||||
|
work poorly.)
|
||||||
|
|
||||||
|
To calibrate the extruder, navigate to the OctoPrint terminal tab and
|
||||||
|
run the PID_CALIBRATE command. For example: `PID_CALIBRATE
|
||||||
|
HEATER=extruder TARGET=170`
|
||||||
|
|
||||||
|
At the completion of the tuning test, update the printer.cfg file with
|
||||||
|
the recommended pid_Kp, pid_Ki, and pid_Kd values.
|
||||||
|
|
||||||
|
If the printer has a heated bed and it supports being driven by PWM
|
||||||
|
(Pulse Width Modulation) then it is recommended to use PID control for
|
||||||
|
the bed. (When the bed heater is controlled using the PID algorithm it
|
||||||
|
may turn on and off ten times a second, which may not be suitable for
|
||||||
|
heaters using a mechanical switch.) A typical bed PID calibration
|
||||||
|
command is: `PID_CALIBRATE HEATER=heater_bed TARGET=60`
|
||||||
|
|
||||||
|
### Next steps
|
||||||
|
|
||||||
|
This guide is intended to help with basic verification of pin settings
|
||||||
|
in the Klipper configuration file. It may be necessary to perform
|
||||||
|
detailed printer calibration - a number of guides are available online
|
||||||
|
to help with this (for example, do a web search for "3d printer
|
||||||
|
calibration").
|
||||||
@@ -1,25 +1,29 @@
|
|||||||
This page provides information on how to contact the Klipper
|
This page provides information on how to contact the Klipper
|
||||||
developers.
|
developers.
|
||||||
|
|
||||||
Bug reporting
|
Issue reporting
|
||||||
=============
|
===============
|
||||||
|
|
||||||
Bug reports are submitted through github issues. All bug reports must
|
In order to report a problem or request a change in behavior, it is
|
||||||
|
necessary to collect the Klipper log file. The first step is to
|
||||||
|
**issue an M112 command** in the OctoPrint terminal window immediately
|
||||||
|
after the undesirable event occurs. This causes Klipper to go into a
|
||||||
|
"shutdown state" and it will cause additional debugging information to
|
||||||
|
be written to the log file.
|
||||||
|
|
||||||
|
Issue requests are submitted through Github. **All issues must
|
||||||
include the full /tmp/klippy.log log file from the session that
|
include the full /tmp/klippy.log log file from the session that
|
||||||
produced the error. To acquire this log file, ssh into the computer
|
produced the error.** An "scp" and/or "sftp" utility is needed to
|
||||||
running the klipper host software, and run:
|
acquire this log file. The "scp" utility comes standard with Linux and
|
||||||
|
MacOS desktops. There are freely available scp utilities for other
|
||||||
|
desktops (eg, WinSCP).
|
||||||
|
|
||||||
```
|
Use the scp utility to copy the `/tmp/klippy.log` file from the host
|
||||||
gzip -k /tmp/klippy.log
|
machine to your desktop. It is a good idea to compress the klippy.log
|
||||||
```
|
file before posting it (eg, using zip or gzip). Open a new issue at
|
||||||
|
https://github.com/KevinOConnor/klipper/issues , provide a description
|
||||||
Then scp the resulting `/tmp/klippy.log.gz` file from the host machine
|
of the problem, and **attach the `klippy.log` file to the issue**:
|
||||||
to your desktop. (If your desktop does not have scp installed, there
|

|
||||||
are a number of free scp programs available - just do a web search for
|
|
||||||
`windows scp` to find one.) Open a new issue at
|
|
||||||
https://github.com/KevinOConnor/klipper/issues , attach the
|
|
||||||
`klippy.log.gz` file to that issue, and provide a description of the
|
|
||||||
problem.
|
|
||||||
|
|
||||||
Mailing list
|
Mailing list
|
||||||
============
|
============
|
||||||
|
|||||||
@@ -115,17 +115,20 @@ gtkwave avrsim.vcd
|
|||||||
```
|
```
|
||||||
|
|
||||||
Manually sending commands to the micro-controller
|
Manually sending commands to the micro-controller
|
||||||
-------------------------------------------------
|
=================================================
|
||||||
|
|
||||||
Normally, Klippy would be used to translate gcode commands to Klipper
|
Normally, the host klippy.py process would be used to translate gcode
|
||||||
commands. However, it's also possible to manually send Klipper
|
commands to Klipper micro-controller commands. However, it's also
|
||||||
commands (functions marked with the DECL_COMMAND() macro in the
|
possible to manually send these MCU commands (functions marked with
|
||||||
Klipper source code). To do so, run:
|
the DECL_COMMAND() macro in the Klipper source code). To do so, run:
|
||||||
|
|
||||||
```
|
```
|
||||||
~/klippy-env/bin/python ./klippy/console.py /tmp/pseudoserial 250000
|
~/klippy-env/bin/python ./klippy/console.py /tmp/pseudoserial 250000
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See the "HELP" command within the tool for more information on its
|
||||||
|
functionality.
|
||||||
|
|
||||||
Generating load graphs
|
Generating load graphs
|
||||||
======================
|
======================
|
||||||
|
|
||||||
@@ -148,3 +151,23 @@ Then graphs can be produced with:
|
|||||||
```
|
```
|
||||||
|
|
||||||
One can then view the resulting **loadgraph.png** file.
|
One can then view the resulting **loadgraph.png** file.
|
||||||
|
|
||||||
|
Extracting information from the klippy.log file
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
The Klippy log file (/tmp/klippy.log) also contains debugging
|
||||||
|
information. There is a logextract.py script that may be useful when
|
||||||
|
analyzing a micro-controller shutdown or similar problem. It is
|
||||||
|
typically run with something like:
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir work_directory
|
||||||
|
cd work_directory
|
||||||
|
cp /tmp/klippy.log .
|
||||||
|
~/klipper/scripts/logextract.py ./klippy.log
|
||||||
|
```
|
||||||
|
|
||||||
|
The script will extract the printer config file and will extract MCU
|
||||||
|
shutdown information. The information dumps from an MCU shutdown (if
|
||||||
|
present) will be reordered by timestamp to assist in diagnosing cause
|
||||||
|
and effect scenarios.
|
||||||
|
|||||||
252
docs/FAQ.md
Normal file
252
docs/FAQ.md
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
Frequently asked questions
|
||||||
|
==========================
|
||||||
|
|
||||||
|
1. [How can I donate to the project?](#how-can-i-donate-to-the-project)
|
||||||
|
2. [How do I calculate the step_distance parameter in the printer config file?](#how-do-i-calculate-the-step_distance-parameter-in-the-printer-config-file)
|
||||||
|
3. [Where's my serial port?](#wheres-my-serial-port)
|
||||||
|
4. [The "make flash" command doesn't work](#the-make-flash-command-doesnt-work)
|
||||||
|
5. [How do I change the serial baud rate?](#how-do-i-change-the-serial-baud-rate)
|
||||||
|
6. [Can I run Klipper on something other than a Raspberry Pi 3?](#can-i-run-klipper-on-something-other-than-a-raspberry-pi-3)
|
||||||
|
7. [Why can't I move the stepper before homing the printer?](#why-cant-i-move-the-stepper-before-homing-the-printer)
|
||||||
|
8. [Why is the Z position_endstop set to 0.5 in the default configs?](#why-is-the-z-position_endstop-set-to-05-in-the-default-configs)
|
||||||
|
9. [I converted my config from Marlin and the X/Y axes work fine, but I just get a screeching noise when homing the Z axis](#i-converted-my-config-from-marlin-and-the-xy-axes-work-fine-but-i-just-get-a-screeching-noise-when-homing-the-z-axis)
|
||||||
|
10. [When I set "restart_method=command" my AVR device just hangs on a restart](#when-i-set-restart_methodcommand-my-avr-device-just-hangs-on-a-restart)
|
||||||
|
11. [Will the heaters be left on if the Raspberry Pi crashes?](#will-the-heaters-be-left-on-if-the-raspberry-pi-crashes)
|
||||||
|
12. [How do I upgrade to the latest software?](#how-do-i-upgrade-to-the-latest-software)
|
||||||
|
|
||||||
|
### How can I donate to the project?
|
||||||
|
|
||||||
|
Thanks. Kevin has a Patreon page at: https://www.patreon.com/koconnor
|
||||||
|
|
||||||
|
### How do I calculate the step_distance parameter in the printer config file?
|
||||||
|
|
||||||
|
If you know the steps per millimeter for the axis then use a
|
||||||
|
calculator to divide 1.0 by steps_per_mm. Then round this number to
|
||||||
|
six decimal places and place it in the config (six decimal places is
|
||||||
|
nano-meter precision).
|
||||||
|
|
||||||
|
The step_distance defines the distance that the axis will travel on
|
||||||
|
each motor driver pulse. It can also be calculated from the axis
|
||||||
|
pitch, motor step angle, and driver microstepping. If unsure, do a web
|
||||||
|
search for "calculate steps per mm" to find an online calculator.
|
||||||
|
|
||||||
|
### Where's my serial port?
|
||||||
|
|
||||||
|
The general way to find a USB serial port is to run `ls -l
|
||||||
|
/dev/serial/by-id/` from an ssh terminal on the host machine. It will
|
||||||
|
likely produce output similar to the following:
|
||||||
|
```
|
||||||
|
lrwxrwxrwx 1 root root 13 Jan 3 22:15 usb-UltiMachine__ultimachine.com__RAMBo_12345678912345678912-if00 -> ../../ttyACM0
|
||||||
|
```
|
||||||
|
|
||||||
|
The name found in the above command is stable and it is possible to
|
||||||
|
use it in the config file and while flashing the micro-controller
|
||||||
|
code. For example, a flash command might look similar to:
|
||||||
|
```
|
||||||
|
sudo service klipper stop
|
||||||
|
make flash FLASH_DEVICE=/dev/serial/by-id/usb-UltiMachine__ultimachine.com__RAMBo_12345678912345678912-if00
|
||||||
|
sudo service klipper start
|
||||||
|
```
|
||||||
|
and the updated config might look like:
|
||||||
|
```
|
||||||
|
[mcu]
|
||||||
|
serial: /dev/serial/by-id/usb-UltiMachine__ultimachine.com__RAMBo_12345678912345678912-if00
|
||||||
|
```
|
||||||
|
|
||||||
|
Be sure to copy-and-paste the name from the "ls" command that you ran
|
||||||
|
above as the name will be different for each printer.
|
||||||
|
|
||||||
|
### The "make flash" command doesn't work
|
||||||
|
|
||||||
|
The code attempts to flash the device using the most common method for
|
||||||
|
each platform. Unfortunately, there is a lot of variance in flashing
|
||||||
|
methods, so the "make flash" command may not work on all boards.
|
||||||
|
|
||||||
|
If you're having an intermittent failure or you do have a standard
|
||||||
|
setup, then double check that Klipper isn't running when flashing
|
||||||
|
(sudo service klipper stop), make sure OctoPrint isn't trying to
|
||||||
|
connect directly to the device (open the Connection tab in the web
|
||||||
|
page and click Disconnect if the Serial Port is set to the device),
|
||||||
|
and make sure FLASH_DEVICE is set correctly for your board (see the
|
||||||
|
[question above](#wheres-my-serial-port)).
|
||||||
|
|
||||||
|
However, if "make flash" just doesn't work for your board, then you
|
||||||
|
will need to manually flash. See if there is a config file in the
|
||||||
|
[config directory](../config) with specific instructions for flashing
|
||||||
|
the device. Also, check the board manufacturer's documentation to see
|
||||||
|
if it describes how to flash the device. Finally, on AVR devices, it
|
||||||
|
may be possible to manually flash the device using
|
||||||
|
[avrdude](http://www.nongnu.org/avrdude/) with custom command-line
|
||||||
|
parameters - see the avrdude documentation for further information.
|
||||||
|
|
||||||
|
### How do I change the serial baud rate?
|
||||||
|
|
||||||
|
The default baud rate is 250000 in both the Klipper micro-controller
|
||||||
|
configuration and in the Klipper host software. This works on almost
|
||||||
|
all micro-controllers and it is the recommended setting. (Most online
|
||||||
|
guides that refer to a baud rate of 115200 are outdated.)
|
||||||
|
|
||||||
|
If you need to change the baud rate, then the new rate will need to be
|
||||||
|
configured in the micro-controller (during **make menuconfig**) and
|
||||||
|
that updated code will need to be flashed to the micro-controller. The
|
||||||
|
Klipper printer.cfg file will also need to be updated to match that
|
||||||
|
baud rate (see the example.cfg file for details). For example:
|
||||||
|
```
|
||||||
|
[mcu]
|
||||||
|
baud: 250000
|
||||||
|
```
|
||||||
|
|
||||||
|
The baud rate shown on the OctoPrint web page has no impact on the
|
||||||
|
internal Klipper micro-controller baud rate. Always set the OctoPrint
|
||||||
|
baud rate to 250000 when using Klipper.
|
||||||
|
|
||||||
|
### Can I run Klipper on something other than a Raspberry Pi 3?
|
||||||
|
|
||||||
|
The recommended hardware is a Raspberry Pi 2 or a Raspberry
|
||||||
|
Pi 3.
|
||||||
|
|
||||||
|
Klipper will run on a Raspberry Pi 1 and on the Raspberry Pi Zero, but
|
||||||
|
these boards don't have enough processing power to run OctoPrint
|
||||||
|
well. It's not uncommon for print stalls to occur on these slower
|
||||||
|
machines (the printer may move faster than OctoPrint can send movement
|
||||||
|
commands) when printing directly from OctoPrint. If you wish to run on
|
||||||
|
one one of these slower boards anyway, consider using the
|
||||||
|
"virtual_sdcard" feature (see
|
||||||
|
[config/example-extras.cfg](../config/example-extras.cfg) for details)
|
||||||
|
when printing.
|
||||||
|
|
||||||
|
For running on the Beaglebone, see the
|
||||||
|
[Beaglebone specific installation instructions](beaglebone.md).
|
||||||
|
|
||||||
|
Klipper has been run on other machines. The Klipper host software
|
||||||
|
only requires Python running on a Linux (or similar)
|
||||||
|
computer. However, if you wish to run it on a different machine you
|
||||||
|
will need Linux admin knowledge to install the system prerequisites
|
||||||
|
for that particular machine. See the
|
||||||
|
[install-octopi.sh](../scripts/install-octopi.sh) script for further
|
||||||
|
information on the necessary Linux admin steps.
|
||||||
|
|
||||||
|
### Why can't I move the stepper before homing the printer?
|
||||||
|
|
||||||
|
The code does this to reduce the chance of accidentally commanding the
|
||||||
|
head into the bed or a wall. Once the printer is homed the software
|
||||||
|
attempts to verify each move is within the position_min/max defined in
|
||||||
|
the config file. If the motors are disabled (via an M84 or M18
|
||||||
|
command) then the motors will need to be homed again prior to
|
||||||
|
movement.
|
||||||
|
|
||||||
|
If you want to move the head after canceling a print via OctoPrint,
|
||||||
|
consider changing the OctoPrint cancel sequence to do that for
|
||||||
|
you. It's configured in OctoPrint via a web browser under:
|
||||||
|
Settings->GCODE Scripts
|
||||||
|
|
||||||
|
If you want to move the head after a print finishes, consider adding
|
||||||
|
the desired movement to the "custom g-code" section of your slicer.
|
||||||
|
|
||||||
|
### Why is the Z position_endstop set to 0.5 in the default configs?
|
||||||
|
|
||||||
|
For cartesian style printers the Z position_endstop specifies how far
|
||||||
|
the nozzle is from the bed when the endstop triggers. If possible, it
|
||||||
|
is recommended to use a Z-max endstop and home away from the bed (as
|
||||||
|
this reduces the potential for bed collisions). However, if one must
|
||||||
|
home towards the bed then it is recommended to position the endstop so
|
||||||
|
it triggers when the nozzle is still a small distance away from the
|
||||||
|
bed. This way, when homing the axis, it will stop before the nozzle
|
||||||
|
touches the bed.
|
||||||
|
|
||||||
|
Almost all mechanical switches can still move a small distance
|
||||||
|
(eg, 0.5mm) after they are triggered. So, for example, if the
|
||||||
|
position_endstop is set to 0.5mm then one may still command the
|
||||||
|
printer to move to Z0.2. The position_min config setting (which
|
||||||
|
defaults to 0) is used to specify the minimum Z position one may
|
||||||
|
command the printer to move to.
|
||||||
|
|
||||||
|
Note, the Z position_endstop specifies the distance from the nozzle to
|
||||||
|
the bed when the nozzle and bed (if applicable) are hot. It is typical
|
||||||
|
for thermal expansion to cause nozzle expansion of around .1mm, which
|
||||||
|
is also the typical thickness of a sheet of printer paper. Thus, it is
|
||||||
|
common to use the "paper test" to confirm calibration of the Z
|
||||||
|
height - check that the bed and nozzle are at room temperature, check
|
||||||
|
that there is no plastic on the head or bed, home the printer, place a
|
||||||
|
piece of paper between the nozzle and bed, and repeatedly command the
|
||||||
|
head to move closer to the bed checking each time if you feel a small
|
||||||
|
amount of friction when sliding the paper between bed and nozzle - if
|
||||||
|
all is calibrated well a small amount of friction would be felt when
|
||||||
|
the height is at Z0.
|
||||||
|
|
||||||
|
### I converted my config from Marlin and the X/Y axes work fine, but I just get a screeching noise when homing the Z axis
|
||||||
|
|
||||||
|
Short answer: Try reducing the max_z_velocity setting in the printer
|
||||||
|
config. Also, if the Z stepper is moving in the wrong direction, try
|
||||||
|
inverting the dir_pin setting in the config (eg, "dir_pin: !xyz"
|
||||||
|
instead of "dir_pin: xyz").
|
||||||
|
|
||||||
|
Long answer: In practice Marlin can typically only step at a rate of
|
||||||
|
around 10000 steps per second. If it is requested to move at a speed
|
||||||
|
that would require a higher step rate then Marlin will generally just
|
||||||
|
step as fast as it can. Klipper is able to achieve much higher step
|
||||||
|
rates, but the stepper motor may not have sufficient torque to move at
|
||||||
|
a higher speed. So, for a Z axis with a very precise step_distance the
|
||||||
|
actual obtainable max_z_velocity may be smaller than what is
|
||||||
|
configured in Marlin.
|
||||||
|
|
||||||
|
### When I set "restart_method=command" my AVR device just hangs on a restart
|
||||||
|
|
||||||
|
Some old versions of the AVR bootloader have a known bug in watchdog
|
||||||
|
event handling. This typically manifests when the printer.cfg file has
|
||||||
|
restart_method set to "command". When the bug occurs, the AVR device
|
||||||
|
will be unresponsive until power is removed and reapplied to the
|
||||||
|
device (the power or status LEDs may also blink repeatedly until the
|
||||||
|
power is removed).
|
||||||
|
|
||||||
|
The workaround is to use a restart_method other than "command" or to
|
||||||
|
flash an updated bootloader to the AVR device. Flashing a new
|
||||||
|
bootloader is a one time step that typically requires an external
|
||||||
|
programmer - search the web to find the instructions for your
|
||||||
|
particular device.
|
||||||
|
|
||||||
|
### Will the heaters be left on if the Raspberry Pi crashes?
|
||||||
|
|
||||||
|
The software has been designed to prevent that. Once the host enables
|
||||||
|
a heater, the host software needs to confirm that enablement every 5
|
||||||
|
seconds. If the micro-controller does not receive a confirmation every
|
||||||
|
5 seconds it goes into a "shutdown" state which is designed to turn
|
||||||
|
off all heaters and stepper motors.
|
||||||
|
|
||||||
|
See the "config_digital_out" command in the
|
||||||
|
[MCU commands](MCU_Commands.md) document for further details.
|
||||||
|
|
||||||
|
### How do I upgrade to the latest software?
|
||||||
|
|
||||||
|
The general way to upgrade is to ssh into the Raspberry Pi and run:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd ~/klipper
|
||||||
|
git pull
|
||||||
|
~/klipper/scripts/install-octopi.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Then one can recompile and flash the micro-controller code. For
|
||||||
|
example:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo service klipper stop
|
||||||
|
make flash FLASH_DEVICE=/dev/ttyACM0
|
||||||
|
sudo service klipper start
|
||||||
|
```
|
||||||
|
|
||||||
|
However, it's often the case that only the host software changes. In
|
||||||
|
this case, one can update and restart just the host software with:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd ~/klipper
|
||||||
|
git pull
|
||||||
|
sudo service klipper restart
|
||||||
|
```
|
||||||
|
|
||||||
|
If after using this shortcut the software warns about needing to
|
||||||
|
reflash the micro-controller or some other unusual error occurs, then
|
||||||
|
follow the full upgrade steps outlined above. Note that the RESTART
|
||||||
|
and FIRMWARE_RESTART g-code commands do not load new software - the
|
||||||
|
above "sudo service klipper restart" and "make flash" commands are
|
||||||
|
needed for a software change to take effect.
|
||||||
131
docs/G-Codes.md
Normal file
131
docs/G-Codes.md
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
This document describes the commands that Klipper supports. These are
|
||||||
|
commands that one may enter into the OctoPrint terminal tab.
|
||||||
|
|
||||||
|
# G-Code commands
|
||||||
|
|
||||||
|
Klipper supports the following standard G-Code commands:
|
||||||
|
- Move (G0 or G1): `G1 [X<pos>] [Y<pos>] [Z<pos>] [E<pos>] [F<speed>]`
|
||||||
|
- Dwell: `G4 P<milliseconds>`
|
||||||
|
- Move to origin: `G28 [X] [Y] [Z]`
|
||||||
|
- Turn off motors: `M18` or `M84`
|
||||||
|
- Wait for current moves to finish: `M400`
|
||||||
|
- Select tool: `T<index>`
|
||||||
|
- Use absolute/relative distances for extrusion: `M82`, `M83`
|
||||||
|
- Use absolute/relative coordinates: `G90`, `G91`
|
||||||
|
- Set position: `G92 [X<pos>] [Y<pos>] [Z<pos>] [E<pos>]`
|
||||||
|
- Set speed factor override percentage: `M220 S<percent>`
|
||||||
|
- Set extrude factor override percentage: `M221 S<percent>`
|
||||||
|
- Get extruder temperature: `M105`
|
||||||
|
- Set extruder temperature: `M104 [T<index>] [S<temperature>]`
|
||||||
|
- Set extruder temperature and wait: `M109 [T<index>] S<temperature>`
|
||||||
|
- Set bed temperature: `M140 [S<temperature>]`
|
||||||
|
- Set bed temperature and wait: `M190 S<temperature>`
|
||||||
|
- Set fan speed: `M106 S<value>`
|
||||||
|
- Turn fan off: `M107`
|
||||||
|
- Emergency stop: `M112`
|
||||||
|
- Get current position: `M114`
|
||||||
|
- Get firmware version: `M115`
|
||||||
|
- Set home offset: `M206 [X<pos>] [Y<pos>] [Z<pos>]`
|
||||||
|
|
||||||
|
For further details on the above commands see the
|
||||||
|
[RepRap G-Code documentation](http://reprap.org/wiki/G-code).
|
||||||
|
|
||||||
|
Klipper's goal is to support the G-Code commands produced by common
|
||||||
|
3rd party software (eg, OctoPrint, Printrun, Slic3r, Cura, etc.) in
|
||||||
|
their standard configurations. It is not a goal to support every
|
||||||
|
possible G-Code command. Instead, Klipper prefers human readable
|
||||||
|
["extended G-Code commands"](#extended-g-code-commands).
|
||||||
|
|
||||||
|
## G-Code SD card commands
|
||||||
|
|
||||||
|
Klipper also supports the following standard G-Code commands if the
|
||||||
|
"virtual_sdcard" config section is enabled:
|
||||||
|
- List SD card: `M20`
|
||||||
|
- Initialize SD card: `M21`
|
||||||
|
- Select SD file: `M23 <filename>`
|
||||||
|
- Start/resume SD print: `M24`
|
||||||
|
- Pause SD print: `M25`
|
||||||
|
- Set SD position: `M26 S<offset>`
|
||||||
|
- Report SD print status: `M27`
|
||||||
|
|
||||||
|
# Extended G-Code Commands
|
||||||
|
|
||||||
|
Klipper uses "extended" G-Code commands for general configuration and
|
||||||
|
status. These extended commands all follow a similar format - they
|
||||||
|
start with a command name and may be followed by one or more
|
||||||
|
parameters. For example: `SET_SERVO SERVO=myservo ANGLE=5.3`. In this
|
||||||
|
document, the commands and parameters are shown in uppercase, however
|
||||||
|
they are not case sensitive. (So, "SET_SERVO" and "set_servo" both run
|
||||||
|
the same command.)
|
||||||
|
|
||||||
|
The following standard commands are supported:
|
||||||
|
- `QUERY_ENDSTOPS`: Probe the axis endstops and report if they are
|
||||||
|
"triggered" or in an "open" state. This command is typically used to
|
||||||
|
verify that an endstop is working correctly.
|
||||||
|
- `GET_POSITION`: Return information on the current location of the
|
||||||
|
toolhead.
|
||||||
|
- `PID_CALIBRATE HEATER=<config_name> TARGET=<temperature>
|
||||||
|
[WRITE_FILE=1]`: Perform a PID calibration test. The specified
|
||||||
|
heater will be enabled until the specified target temperature is
|
||||||
|
reached, and then the heater will be turned off and on for several
|
||||||
|
cycles. If the WRITE_FILE parameter is enabled, then the file
|
||||||
|
/tmp/heattest.txt will be created with a log of all temperature
|
||||||
|
samples taken during the test.
|
||||||
|
- `RESTART`: This will cause the host software to reload its config
|
||||||
|
and perform an internal reset. This command will not clear error
|
||||||
|
state from the micro-controller (see FIRMWARE_RESTART) nor will it
|
||||||
|
load new software (see
|
||||||
|
[the FAQ](FAQ.md#how-do-i-upgrade-to-the-latest-software)).
|
||||||
|
- `FIRMWARE_RESTART`: This is similar to a RESTART command, but it
|
||||||
|
also clears any error state from the micro-controller.
|
||||||
|
- `STATUS`: Report the Klipper host software status.
|
||||||
|
- `HELP`: Report the list of available extended G-Code commands.
|
||||||
|
|
||||||
|
## Custom Pin Commands
|
||||||
|
|
||||||
|
The following command is available when an "output_pin" config section
|
||||||
|
is enabled:
|
||||||
|
- `SET_PIN PIN=config_name VALUE=<value>`
|
||||||
|
|
||||||
|
## Servo Commands
|
||||||
|
|
||||||
|
The following commands are available when a "servo" config section is
|
||||||
|
enabled:
|
||||||
|
- `SET_SERVO SERVO=config_name WIDTH=<seconds>`
|
||||||
|
- `SET_SERVO SERVO=config_name ANGLE=<degrees>`
|
||||||
|
|
||||||
|
## Probe
|
||||||
|
|
||||||
|
The following commands are available when a "probe" config section is
|
||||||
|
enabled:
|
||||||
|
- `PROBE`: Move the nozzle downwards until the probe triggers.
|
||||||
|
- `QUERY_PROBE`: Report the current status of the probe ("triggered"
|
||||||
|
or "open").
|
||||||
|
|
||||||
|
## Delta Calibration
|
||||||
|
|
||||||
|
The following commands are available when the "delta_calibrate" config
|
||||||
|
section is enabled:
|
||||||
|
- `DELTA_CALIBRATE`: This command will probe seven points on the bed
|
||||||
|
and recommend updated endstop positions, tower angles, and radius.
|
||||||
|
- `NEXT`: If manual bed probing is enabled, then one can use this
|
||||||
|
command to move to the next probing point during a DELTA_CALIBRATE
|
||||||
|
operation.
|
||||||
|
|
||||||
|
## Bed Tilt
|
||||||
|
|
||||||
|
The following commands are available when the "bed_tilt" config
|
||||||
|
section is enabled:
|
||||||
|
- `BED_TILT_CALIBRATE`: This command will probe the points specified
|
||||||
|
in the config and then recommend updated x and y tilt adjustments.
|
||||||
|
- `NEXT`: If manual bed probing is enabled, then one can use this
|
||||||
|
command to move to the next probing point during a
|
||||||
|
BED_TILT_CALIBRATE operation.
|
||||||
|
|
||||||
|
## Dual Carriages
|
||||||
|
|
||||||
|
The following command is available when the "dual_carriage" config
|
||||||
|
section is enabled:
|
||||||
|
- `SET_DUAL_CARRIAGE CARRIAGE=[0|1]`: This command will set the active
|
||||||
|
carriage. It is typically invoked from the activate_gcode and
|
||||||
|
deactivate_gcode fields in a multiple extruder configuration.
|
||||||
@@ -1,13 +1,9 @@
|
|||||||
These instructions assume the software will run on a Raspberry Pi
|
These instructions assume the software will run on a Raspberry Pi
|
||||||
computer in conjunction with OctoPrint. (See the
|
computer in conjunction with OctoPrint. It is recommended that a
|
||||||
[Beaglebone specific instructions](beaglebone.md) if using a
|
Raspberry Pi 2 or Raspberry Pi 3 computer be used as the host machine
|
||||||
Beaglebone.) It is recommended that a Raspberry Pi 2 or Raspberry Pi 3
|
(see the
|
||||||
computer be used as the host machine.
|
[FAQ](FAQ.md#can-i-run-klipper-on-something-other-than-a-raspberry-pi-3)
|
||||||
|
for other machines).
|
||||||
It should be possible to run the Klipper host software on any computer
|
|
||||||
running a recent Linux distribution, but doing so will require Linux
|
|
||||||
admin knowledge to translate these installation instructions to the
|
|
||||||
particulars of that machine.
|
|
||||||
|
|
||||||
Klipper currently supports Atmel ATmega based micro-controllers,
|
Klipper currently supports Atmel ATmega based micro-controllers,
|
||||||
Arduino Due (Atmel SAM3x8e ARM micro-controller), and
|
Arduino Due (Atmel SAM3x8e ARM micro-controller), and
|
||||||
@@ -23,9 +19,14 @@ release information. One should verify that OctoPi boots and that the
|
|||||||
OctoPrint web server works. After connecting to the OctoPrint web
|
OctoPrint web server works. After connecting to the OctoPrint web
|
||||||
page, follow the prompt to upgrade OctoPrint to v1.3.5 or later.
|
page, follow the prompt to upgrade OctoPrint to v1.3.5 or later.
|
||||||
|
|
||||||
After installing OctoPi and upgrading OctoPrint, ssh into the target
|
After installing OctoPi and upgrading OctoPrint, it will be necessary
|
||||||
machine (ssh pi@octopi -- password is "raspberry") and run the
|
to ssh into the target machine to run a handful of system commands. If
|
||||||
following commands:
|
using a Linux or MacOS desktop, then the "ssh" software should already
|
||||||
|
be installed on the desktop. There are free ssh clients available for
|
||||||
|
other desktops (eg,
|
||||||
|
[PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/)). Use the
|
||||||
|
ssh utility to connect to the Raspberry Pi (ssh pi@octopi -- password
|
||||||
|
is "raspberry") and run the following commands:
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/KevinOConnor/klipper
|
git clone https://github.com/KevinOConnor/klipper
|
||||||
@@ -40,15 +41,18 @@ minutes to complete.
|
|||||||
Building and flashing the micro-controller
|
Building and flashing the micro-controller
|
||||||
==========================================
|
==========================================
|
||||||
|
|
||||||
To compile the micro-controller code, start by configuring it:
|
To compile the micro-controller code, start by running these commands
|
||||||
|
on the Raspberry Pi:
|
||||||
|
|
||||||
```
|
```
|
||||||
cd ~/klipper/
|
cd ~/klipper/
|
||||||
make menuconfig
|
make menuconfig
|
||||||
```
|
```
|
||||||
|
|
||||||
Select the appropriate micro-controller and serial baud rate. Once
|
Select the appropriate micro-controller and review any other options
|
||||||
configured, run:
|
provided. For boards with serial ports, the default baud rate is
|
||||||
|
250000 (see the [FAQ](FAQ.md#how-do-i-change-the-serial-baud-rate) if
|
||||||
|
changing). Once configured, run:
|
||||||
|
|
||||||
```
|
```
|
||||||
make
|
make
|
||||||
@@ -62,25 +66,11 @@ make flash FLASH_DEVICE=/dev/ttyACM0
|
|||||||
sudo service klipper start
|
sudo service klipper start
|
||||||
```
|
```
|
||||||
|
|
||||||
Configuring Klipper
|
When flashing for the first time, make sure that OctoPrint is not
|
||||||
===================
|
connected directly to the printer (from the OctoPrint web page, under
|
||||||
|
the "Connection" section, click "Disconnect"). The most common
|
||||||
The Klipper configuration is stored in a text file on the Raspberry
|
communication device is **/dev/ttyACM0** - see the
|
||||||
Pi. Take a look at the example config files in the
|
[FAQ](FAQ.md#wheres-my-serial-port) for other possibilities.
|
||||||
[config directory](../config/). The
|
|
||||||
[example.cfg](../config/example.cfg) file contains documentation on
|
|
||||||
command parameters and it can also be used as an initial config file
|
|
||||||
template. However, for most printers, one of the other config files
|
|
||||||
may be a more concise starting point. The next step is to copy and
|
|
||||||
edit one of these config files - for example:
|
|
||||||
|
|
||||||
```
|
|
||||||
cp ~/klipper/config/example.cfg ~/printer.cfg
|
|
||||||
nano ~/printer.cfg
|
|
||||||
```
|
|
||||||
|
|
||||||
Make sure to review and update each setting that is appropriate for
|
|
||||||
the hardware.
|
|
||||||
|
|
||||||
Configuring OctoPrint to use Klipper
|
Configuring OctoPrint to use Klipper
|
||||||
====================================
|
====================================
|
||||||
@@ -105,13 +95,47 @@ try reloading the page.)
|
|||||||
Once connected, navigate to the "Terminal" tab and type "status"
|
Once connected, navigate to the "Terminal" tab and type "status"
|
||||||
(without the quotes) into the command entry box and click "Send". The
|
(without the quotes) into the command entry box and click "Send". The
|
||||||
terminal window will likely report there is an error opening the
|
terminal window will likely report there is an error opening the
|
||||||
config file - issue a "restart" command in the OctoPrint terminal to
|
config file - that means OctoPrint is successfully communicating with
|
||||||
load the config. A "status" command will report the printer is ready
|
Klipper. Proceed to the next section.
|
||||||
if the Klipper config file is successfully read and the
|
|
||||||
micro-controller is successfully found and configured. It is not
|
Configuring Klipper
|
||||||
unusual to have configuration errors during the initial setup - update
|
===================
|
||||||
the printer config file and issue "restart" until "status" reports the
|
|
||||||
printer is ready.
|
The Klipper configuration is stored in a text file on the Raspberry
|
||||||
|
Pi. Take a look at the example config files in the
|
||||||
|
[config directory](../config/). The
|
||||||
|
[example.cfg](../config/example.cfg) file contains documentation on
|
||||||
|
command parameters and it can also be used as an initial config file
|
||||||
|
template. However, for most printers, one of the other config files
|
||||||
|
may be a more concise starting point.
|
||||||
|
|
||||||
|
Arguably the easiest way to update the Klipper configuration file is
|
||||||
|
to use a desktop editor that supports editing files over the "scp"
|
||||||
|
and/or "sftp" protocols. There are freely available tools that support
|
||||||
|
this (eg, Notepad++, WinSCP, and Cyberduck). Use one of the example
|
||||||
|
config files as a starting point and save it as a file named
|
||||||
|
"printer.cfg" in the home directory of the pi user (ie,
|
||||||
|
/home/pi/printer.cfg).
|
||||||
|
|
||||||
|
Alternatively, one can also copy and edit the file directly on the
|
||||||
|
Raspberry Pi via ssh - for example:
|
||||||
|
|
||||||
|
```
|
||||||
|
cp ~/klipper/config/example.cfg ~/printer.cfg
|
||||||
|
nano ~/printer.cfg
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure to review and update each setting that is appropriate for
|
||||||
|
the hardware.
|
||||||
|
|
||||||
|
After creating and editing the file it will be necessary to issue a
|
||||||
|
"restart" command in the OctoPrint web terminal to load the config. A
|
||||||
|
"status" command will report the printer is ready if the Klipper
|
||||||
|
config file is successfully read and the micro-controller is
|
||||||
|
successfully found and configured. It is not unusual to have
|
||||||
|
configuration errors during the initial setup - update the printer
|
||||||
|
config file and issue "restart" until "status" reports the printer is
|
||||||
|
ready.
|
||||||
|
|
||||||
Klipper reports error messages via the OctoPrint terminal tab. The
|
Klipper reports error messages via the OctoPrint terminal tab. The
|
||||||
"status" command can be used to re-report error messages. The default
|
"status" command can be used to re-report error messages. The default
|
||||||
@@ -122,7 +146,13 @@ In addition to common g-code commands, Klipper supports a few extended
|
|||||||
commands - "status" and "restart" are examples of these commands. Use
|
commands - "status" and "restart" are examples of these commands. Use
|
||||||
the "help" command to get a list of other extended commands.
|
the "help" command to get a list of other extended commands.
|
||||||
|
|
||||||
|
After Klipper reports that the "printer is ready" go on to the
|
||||||
|
[config check document](Config_checks.md) to perform some basic checks
|
||||||
|
on the pin definitions in the config file.
|
||||||
|
|
||||||
Contacting the developers
|
Contacting the developers
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
See the [contact page](Contact.md) to ask questions or report a bug.
|
Be sure to see the [FAQ](FAQ.md) for answers to some common questions.
|
||||||
|
See the [contact page](Contact.md) to report a bug or to contact the
|
||||||
|
developers.
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ machine. The host code is intended to run on a low-cost
|
|||||||
general-purpose machine such as a Raspberry Pi, while the
|
general-purpose machine such as a Raspberry Pi, while the
|
||||||
micro-controller code is intended to run on commodity micro-controller
|
micro-controller code is intended to run on commodity micro-controller
|
||||||
chips. Read [features](Features.md) for reasons to use Klipper. See
|
chips. Read [features](Features.md) for reasons to use Klipper. See
|
||||||
[installation](Installation.md) to get started with Klipper.
|
[installation](Installation.md) to get started with Klipper. See
|
||||||
|
[config checks](Config_checks.md) for a guide to verify basic pin
|
||||||
|
settings in the config file.
|
||||||
|
|
||||||
The Klipper configuration is stored in a simple text file on the host
|
The Klipper configuration is stored in a simple text file on the host
|
||||||
machine. The [config/example.cfg](../config/example.cfg) file serves
|
machine. The [config/example.cfg](../config/example.cfg) file serves
|
||||||
@@ -13,7 +15,9 @@ as a reference for the config file. The
|
|||||||
on tuning the pressure advance config.
|
on tuning the pressure advance config.
|
||||||
|
|
||||||
The [kinematics](Kinematics.md) document provides some technical
|
The [kinematics](Kinematics.md) document provides some technical
|
||||||
details on how Klipper implements motion.
|
details on how Klipper implements motion. The [FAQ](FAQ.md) answers
|
||||||
|
some common questions. The [G-Codes](G-Codes.md) document lists
|
||||||
|
currently supported run-time commands.
|
||||||
|
|
||||||
The history of Klipper releases is available at
|
The history of Klipper releases is available at
|
||||||
[releases](Releases.md). See [contact](Contact.md) for information on
|
[releases](Releases.md). See [contact](Contact.md) for information on
|
||||||
@@ -25,7 +29,8 @@ Developer Documentation
|
|||||||
There are also several documents available for developers interested
|
There are also several documents available for developers interested
|
||||||
in understanding how Klipper works. Start with the
|
in understanding how Klipper works. Start with the
|
||||||
[code overview](Code_Overview.md) document - it provides information
|
[code overview](Code_Overview.md) document - it provides information
|
||||||
on the structure and layout of the Klipper code.
|
on the structure and layout of the Klipper code. See the
|
||||||
|
[contributing](CONTRIBUTING.md) document to submit improvements to Klipper.
|
||||||
|
|
||||||
See [protocol](Protocol.md) for information on the low-level messaging
|
See [protocol](Protocol.md) for information on the low-level messaging
|
||||||
protocol between host and micro-controller. See also
|
protocol between host and micro-controller. See also
|
||||||
|
|||||||
@@ -63,7 +63,10 @@ in good quality corners:
|
|||||||

|

|
||||||
|
|
||||||
Typical pressure_advance values are between 0.05 and 0.20 (the high
|
Typical pressure_advance values are between 0.05 and 0.20 (the high
|
||||||
end usually only with bowden extruders).
|
end usually only with bowden extruders). If there is no significant
|
||||||
|
improvement seen after increasing pressure_advance to 0.20, then
|
||||||
|
pressure advance is unlikely to improve the quality of prints. Return
|
||||||
|
to a default configuration with pressure_advance disabled.
|
||||||
|
|
||||||
It is not unusual for one corner of the test print to be consistently
|
It is not unusual for one corner of the test print to be consistently
|
||||||
different than the other three corners. This typically occurs when the
|
different than the other three corners. This typically occurs when the
|
||||||
|
|||||||
@@ -1,6 +1,37 @@
|
|||||||
History of Klipper releases. Please see
|
History of Klipper releases. Please see
|
||||||
[installation](Installation.md) for information on installing Klipper.
|
[installation](Installation.md) for information on installing Klipper.
|
||||||
|
|
||||||
|
Klipper 0.6.0
|
||||||
|
=============
|
||||||
|
|
||||||
|
Available on 20180331. Major changes in this release:
|
||||||
|
* Enhanced heater and thermistor hardware failure checks
|
||||||
|
* Support for Z probes
|
||||||
|
* Initial support for automatic parameter calibration on deltas (via a
|
||||||
|
new delta_calibrate command)
|
||||||
|
* Initial support for bed tilt compensation (via bed_tilt_calibrate
|
||||||
|
command)
|
||||||
|
* Initial support for "safe homing" and homing overrides
|
||||||
|
* Initial support for displaying status on RepRapDiscount style 2004
|
||||||
|
and 12864 displays
|
||||||
|
* New multi-extruder improvements:
|
||||||
|
* Support for shared heaters
|
||||||
|
* Initial support for dual carriages
|
||||||
|
* Support for configuring multiple steppers per axis (eg, dual Z)
|
||||||
|
* Support for custom digital and pwm output pins (with a new SET_PIN command)
|
||||||
|
* Initial support for a "virtual sdcard" that allows printing directly
|
||||||
|
from Klipper (helps on machines too slow to run OctoPrint well)
|
||||||
|
* Support for setting different arm lengths on each tower of a delta
|
||||||
|
* Support for G-Code M220/M221 commands (speed factor override /
|
||||||
|
extrude factor override)
|
||||||
|
* Several documentation updates:
|
||||||
|
* Many new example config files for common off-the-shelf printers
|
||||||
|
* New multiple MCU config example
|
||||||
|
* New bltouch sensor config example
|
||||||
|
* New FAQ, config check, and G-Code documents
|
||||||
|
* Initial support for continuous integration testing on all github commits
|
||||||
|
* Several bug fixes and code cleanups
|
||||||
|
|
||||||
Klipper 0.5.0
|
Klipper 0.5.0
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|||||||
@@ -44,10 +44,6 @@ Safety features
|
|||||||
endstop detection is a good idea because of spurious signals caused
|
endstop detection is a good idea because of spurious signals caused
|
||||||
by electrical noise.)
|
by electrical noise.)
|
||||||
|
|
||||||
* Support validating that heaters are heating at expected rates. This
|
|
||||||
can be useful to detect a sensor failure (eg, thermistor short) that
|
|
||||||
could otherwise cause the PID to command excessive heating.
|
|
||||||
|
|
||||||
Testing features
|
Testing features
|
||||||
================
|
================
|
||||||
|
|
||||||
@@ -71,10 +67,6 @@ Hardware features
|
|||||||
|
|
||||||
* Support for additional kinematics: scara, etc.
|
* Support for additional kinematics: scara, etc.
|
||||||
|
|
||||||
* Support shared motor enable GPIO lines.
|
|
||||||
|
|
||||||
* Support for bed-level probes.
|
|
||||||
|
|
||||||
* Possible support for touch panels attached to the micro-controller.
|
* Possible support for touch panels attached to the micro-controller.
|
||||||
(In general, it would be preferable to attach touch panels to the
|
(In general, it would be preferable to attach touch panels to the
|
||||||
host system and have octoprint interact with the panel directly, but
|
host system and have octoprint interact with the panel directly, but
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ sudo systemctl start octoprint
|
|||||||
Make sure the octoprint web server is accessible - it should be at:
|
Make sure the octoprint web server is accessible - it should be at:
|
||||||
[http://beaglebone:5000/](http://beaglebone:5000/)
|
[http://beaglebone:5000/](http://beaglebone:5000/)
|
||||||
|
|
||||||
|
|
||||||
Building the micro-controller code
|
Building the micro-controller code
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
@@ -92,3 +91,13 @@ Remaining configuration
|
|||||||
Complete the installation by configuring Klipper and Octoprint
|
Complete the installation by configuring Klipper and Octoprint
|
||||||
following the instructions in
|
following the instructions in
|
||||||
[the main installation document](Installation.md#configuring-klipper).
|
[the main installation document](Installation.md#configuring-klipper).
|
||||||
|
|
||||||
|
Printing on the Beaglebone
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Unfortunately, the Beaglebone processor can sometimes struggle to run
|
||||||
|
OctoPrint well. Print stalls have been known to occur on complex
|
||||||
|
prints (the printer may move faster than OctoPrint can send movement
|
||||||
|
commands). If this occurs, consider using the "virtual_sdcard" feature
|
||||||
|
(see [config/example-extras.cfg](../config/example-extras.cfg) for
|
||||||
|
details) to print directly from Klipper.
|
||||||
|
|||||||
37
docs/developer-certificate-of-origin
Normal file
37
docs/developer-certificate-of-origin
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
Developer Certificate of Origin
|
||||||
|
Version 1.1
|
||||||
|
|
||||||
|
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||||
|
1 Letterman Drive
|
||||||
|
Suite D4700
|
||||||
|
San Francisco, CA, 94129
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this
|
||||||
|
license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
Developer's Certificate of Origin 1.1
|
||||||
|
|
||||||
|
By making a contribution to this project, I certify that:
|
||||||
|
|
||||||
|
(a) The contribution was created in whole or in part by me and I
|
||||||
|
have the right to submit it under the open source license
|
||||||
|
indicated in the file; or
|
||||||
|
|
||||||
|
(b) The contribution is based upon previous work that, to the best
|
||||||
|
of my knowledge, is covered under an appropriate open source
|
||||||
|
license and I have the right under that license to submit that
|
||||||
|
work with modifications, whether created in whole or in part
|
||||||
|
by me, under the same open source license (unless I am
|
||||||
|
permitted to submit under a different license), as indicated
|
||||||
|
in the file; or
|
||||||
|
|
||||||
|
(c) The contribution was provided directly to me by some other
|
||||||
|
person who certified (a), (b) or (c) and I have not modified
|
||||||
|
it.
|
||||||
|
|
||||||
|
(d) I understand and agree that this project and the contribution
|
||||||
|
are public and that a record of the contribution (including all
|
||||||
|
personal information I submit with it, including my sign-off) is
|
||||||
|
maintained indefinitely and may be redistributed consistent with
|
||||||
|
this project or the open source license(s) involved.
|
||||||
BIN
docs/img/attach-issue.png
Normal file
BIN
docs/img/attach-issue.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/img/octoprint-temperature.png
Normal file
BIN
docs/img/octoprint-temperature.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
2
docs/issue_template.md
Normal file
2
docs/issue_template.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<!-- Klipper do something undesirable? YOU MUST ATTACH THE KLIPPER LOG FILE.
|
||||||
|
See: https://github.com/KevinOConnor/klipper/blob/master/docs/Contact.md -->
|
||||||
@@ -7,10 +7,39 @@ square_width = 5;
|
|||||||
square_size = 60;
|
square_size = 60;
|
||||||
square_height = 5;
|
square_height = 5;
|
||||||
|
|
||||||
difference() {
|
module hollow_square() {
|
||||||
|
difference() {
|
||||||
cube([square_size, square_size, square_height]);
|
cube([square_size, square_size, square_height]);
|
||||||
translate([square_width, square_width, -1])
|
translate([square_width, square_width, -1])
|
||||||
cube([square_size-2*square_width, square_size-2*square_width, square_height+2]);
|
cube([square_size-2*square_width, square_size-2*square_width,
|
||||||
translate([-.5, square_size/2 - 4, -1])
|
square_height+2]);
|
||||||
cube([1, 2, square_height+2]);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module notch() {
|
||||||
|
CUT = 0.01;
|
||||||
|
depth = .5;
|
||||||
|
width = 1;
|
||||||
|
translate([-depth, -width, -CUT])
|
||||||
|
cube([2*depth, 2*width, square_height + 2*CUT]);
|
||||||
|
}
|
||||||
|
|
||||||
|
module square_with_notches() {
|
||||||
|
difference() {
|
||||||
|
// Start with initial square
|
||||||
|
hollow_square();
|
||||||
|
// Remove four notches on inside perimeter
|
||||||
|
translate([square_width, square_size/2 - 4, 0])
|
||||||
|
notch();
|
||||||
|
translate([square_size/2, square_size - square_width, 0])
|
||||||
|
rotate([0, 0, 90])
|
||||||
|
notch();
|
||||||
|
translate([square_size - square_width, square_size/2, 0])
|
||||||
|
notch();
|
||||||
|
translate([square_size/2, square_width, 0])
|
||||||
|
rotate([0, 0, 90])
|
||||||
|
notch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
square_with_notches();
|
||||||
|
|||||||
@@ -2,20 +2,6 @@ solid OpenSCAD_Model
|
|||||||
facet normal -1 0 0
|
facet normal -1 0 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 0 0
|
vertex 0 0 0
|
||||||
vertex 0 26 5
|
|
||||||
vertex 0 26 0
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal -1 -0 0
|
|
||||||
outer loop
|
|
||||||
vertex 0 26 5
|
|
||||||
vertex 0 0 0
|
|
||||||
vertex 0 0 5
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal -1 0 0
|
|
||||||
outer loop
|
|
||||||
vertex 0 28 0
|
|
||||||
vertex 0 60 5
|
vertex 0 60 5
|
||||||
vertex 0 60 0
|
vertex 0 60 0
|
||||||
endloop
|
endloop
|
||||||
@@ -23,92 +9,176 @@ solid OpenSCAD_Model
|
|||||||
facet normal -1 -0 0
|
facet normal -1 -0 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 60 5
|
vertex 0 60 5
|
||||||
vertex 0 28 0
|
vertex 0 0 0
|
||||||
vertex 0 28 5
|
vertex 0 0 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 5 25 5
|
||||||
|
vertex 4.5 25 5
|
||||||
|
vertex 5 5 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 29 5 5
|
||||||
|
vertex 5 5 5
|
||||||
|
vertex 29 4.5 5
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 1
|
facet normal 0 0 1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 60 60 5
|
vertex 60 60 5
|
||||||
vertex 55 55 5
|
vertex 55.5 31 5
|
||||||
vertex 60 0 5
|
vertex 60 0 5
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 1
|
facet normal 0 0 1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 60 60 5
|
vertex 60 60 5
|
||||||
vertex 5 55 5
|
vertex 55 55 5
|
||||||
|
vertex 55.5 31 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 60 60 5
|
||||||
|
vertex 31 55.5 5
|
||||||
vertex 55 55 5
|
vertex 55 55 5
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 1
|
facet normal 0 0 1
|
||||||
outer loop
|
outer loop
|
||||||
|
vertex 60 60 5
|
||||||
|
vertex 29 55.5 5
|
||||||
|
vertex 31 55.5 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 29 55.5 5
|
||||||
vertex 5 55 5
|
vertex 5 55 5
|
||||||
vertex 0.5 28 5
|
vertex 29 55 5
|
||||||
vertex 5 5 5
|
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal -0 0 1
|
facet normal -0 0 1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 60 5
|
vertex 0 60 5
|
||||||
vertex 5 55 5
|
vertex 29 55.5 5
|
||||||
vertex 60 60 5
|
vertex 60 60 5
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 1
|
facet normal 0 0 1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 28 5
|
|
||||||
vertex 5 55 5
|
vertex 5 55 5
|
||||||
|
vertex 4.5 27 5
|
||||||
|
vertex 5 27 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 0 0 5
|
||||||
|
vertex 4.5 27 5
|
||||||
vertex 0 60 5
|
vertex 0 60 5
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 1
|
facet normal 0 0 1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 5 55 5
|
vertex 5 55 5
|
||||||
vertex 0 28 5
|
vertex 0 60 5
|
||||||
vertex 0.5 28 5
|
vertex 4.5 27 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 29 55.5 5
|
||||||
|
vertex 0 60 5
|
||||||
|
vertex 5 55 5
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal -0 0 1
|
facet normal -0 0 1
|
||||||
outer loop
|
outer loop
|
||||||
|
vertex 55.5 29 5
|
||||||
|
vertex 60 0 5
|
||||||
|
vertex 55.5 31 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 55 5 5
|
||||||
|
vertex 55.5 29 5
|
||||||
|
vertex 55 29 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 55.5 29 5
|
||||||
vertex 55 5 5
|
vertex 55 5 5
|
||||||
vertex 60 0 5
|
vertex 60 0 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 31 4.5 5
|
||||||
|
vertex 55 5 5
|
||||||
|
vertex 31 5 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 55 5 5
|
||||||
|
vertex 31 4.5 5
|
||||||
|
vertex 60 0 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal -0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 29 4.5 5
|
||||||
|
vertex 60 0 5
|
||||||
|
vertex 31 4.5 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 0 0 5
|
||||||
|
vertex 29 4.5 5
|
||||||
|
vertex 5 5 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 4.5 27 5
|
||||||
|
vertex 0 0 5
|
||||||
|
vertex 4.5 25 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 29 4.5 5
|
||||||
|
vertex 0 0 5
|
||||||
|
vertex 60 0 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 4.5 25 5
|
||||||
|
vertex 0 0 5
|
||||||
|
vertex 5 5 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 1
|
||||||
|
outer loop
|
||||||
|
vertex 55.5 31 5
|
||||||
vertex 55 55 5
|
vertex 55 55 5
|
||||||
endloop
|
vertex 55 31 5
|
||||||
endfacet
|
|
||||||
facet normal -0 0 1
|
|
||||||
outer loop
|
|
||||||
vertex 5 5 5
|
|
||||||
vertex 60 0 5
|
|
||||||
vertex 55 5 5
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal -0 0 1
|
|
||||||
outer loop
|
|
||||||
vertex 0.5 26 5
|
|
||||||
vertex 5 5 5
|
|
||||||
vertex 0.5 28 5
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal -0 0 1
|
|
||||||
outer loop
|
|
||||||
vertex 0 26 5
|
|
||||||
vertex 5 5 5
|
|
||||||
vertex 0.5 26 5
|
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 1
|
facet normal 0 0 1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 5 5 5
|
vertex 55 55 5
|
||||||
vertex 0 0 5
|
vertex 31 55.5 5
|
||||||
vertex 60 0 5
|
vertex 31 55 5
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 0 1
|
|
||||||
outer loop
|
|
||||||
vertex 0 0 5
|
|
||||||
vertex 5 5 5
|
|
||||||
vertex 0 26 5
|
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 1 -0 0
|
facet normal 1 -0 0
|
||||||
@@ -139,88 +209,172 @@ solid OpenSCAD_Model
|
|||||||
vertex 0 60 0
|
vertex 0 60 0
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
|
facet normal 0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 5 27 0
|
||||||
|
vertex 4.5 27 0
|
||||||
|
vertex 5 55 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 29 55 0
|
||||||
|
vertex 5 55 0
|
||||||
|
vertex 29 55.5 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
facet normal 0 0 -1
|
facet normal 0 0 -1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 60 0 0
|
vertex 60 0 0
|
||||||
vertex 55 5 0
|
vertex 55.5 29 0
|
||||||
vertex 60 60 0
|
vertex 60 60 0
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 -1
|
facet normal 0 0 -1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 60 0 0
|
vertex 60 0 0
|
||||||
vertex 5 5 0
|
vertex 55 5 0
|
||||||
|
vertex 55.5 29 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 60 0 0
|
||||||
|
vertex 31 4.5 0
|
||||||
vertex 55 5 0
|
vertex 55 5 0
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 -1
|
facet normal 0 0 -1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0.5 28 0
|
vertex 60 0 0
|
||||||
|
vertex 29 4.5 0
|
||||||
|
vertex 31 4.5 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 29 4.5 0
|
||||||
vertex 5 5 0
|
vertex 5 5 0
|
||||||
vertex 0.5 26 0
|
vertex 29 5 0
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 -1
|
facet normal 0 0 -1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 0 0
|
vertex 0 0 0
|
||||||
vertex 5 5 0
|
vertex 29 4.5 0
|
||||||
vertex 60 0 0
|
vertex 60 0 0
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 -1
|
facet normal 0 0 -1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 26 0
|
|
||||||
vertex 5 5 0
|
vertex 5 5 0
|
||||||
|
vertex 4.5 25 0
|
||||||
|
vertex 5 25 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal -0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 4.5 25 0
|
||||||
|
vertex 0 0 0
|
||||||
|
vertex 4.5 27 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal -0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 5 5 0
|
||||||
|
vertex 0 0 0
|
||||||
|
vertex 4.5 25 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal -0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 29 4.5 0
|
||||||
|
vertex 0 0 0
|
||||||
|
vertex 5 5 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 55.5 31 0
|
||||||
|
vertex 60 60 0
|
||||||
|
vertex 55.5 29 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 55 55 0
|
||||||
|
vertex 55.5 31 0
|
||||||
|
vertex 55 31 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 55.5 31 0
|
||||||
|
vertex 55 55 0
|
||||||
|
vertex 60 60 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 31 55.5 0
|
||||||
|
vertex 55 55 0
|
||||||
|
vertex 31 55 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 55 55 0
|
||||||
|
vertex 31 55.5 0
|
||||||
|
vertex 60 60 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 29 55.5 0
|
||||||
|
vertex 60 60 0
|
||||||
|
vertex 31 55.5 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 0 60 0
|
||||||
|
vertex 29 55.5 0
|
||||||
|
vertex 5 55 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 0 60 0
|
||||||
|
vertex 4.5 27 0
|
||||||
vertex 0 0 0
|
vertex 0 0 0
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 -1
|
facet normal 0 0 -1
|
||||||
outer loop
|
outer loop
|
||||||
vertex 5 5 0
|
vertex 29 55.5 0
|
||||||
vertex 0 26 0
|
|
||||||
vertex 0.5 26 0
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 0 -1
|
|
||||||
outer loop
|
|
||||||
vertex 55 55 0
|
|
||||||
vertex 60 60 0
|
|
||||||
vertex 55 5 0
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 0 -1
|
|
||||||
outer loop
|
|
||||||
vertex 5 55 0
|
|
||||||
vertex 60 60 0
|
|
||||||
vertex 55 55 0
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 0 -1
|
|
||||||
outer loop
|
|
||||||
vertex 5 5 0
|
|
||||||
vertex 0.5 28 0
|
|
||||||
vertex 5 55 0
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 0 -1
|
|
||||||
outer loop
|
|
||||||
vertex 0 28 0
|
|
||||||
vertex 5 55 0
|
|
||||||
vertex 0.5 28 0
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 0 -1
|
|
||||||
outer loop
|
|
||||||
vertex 5 55 0
|
|
||||||
vertex 0 60 0
|
vertex 0 60 0
|
||||||
vertex 60 60 0
|
vertex 60 60 0
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 0 -1
|
facet normal 0 0 -1
|
||||||
outer loop
|
outer loop
|
||||||
|
vertex 4.5 27 0
|
||||||
vertex 0 60 0
|
vertex 0 60 0
|
||||||
vertex 5 55 0
|
vertex 5 55 0
|
||||||
vertex 0 28 0
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal -0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 55.5 29 0
|
||||||
|
vertex 55 5 0
|
||||||
|
vertex 55 29 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal -0 0 -1
|
||||||
|
outer loop
|
||||||
|
vertex 55 5 0
|
||||||
|
vertex 31 4.5 0
|
||||||
|
vertex 31 5 0
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 -1 0
|
facet normal 0 -1 0
|
||||||
@@ -240,6 +394,20 @@ solid OpenSCAD_Model
|
|||||||
facet normal 1 -0 0
|
facet normal 1 -0 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 5 5 5
|
vertex 5 5 5
|
||||||
|
vertex 5 25 0
|
||||||
|
vertex 5 25 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 1 0 0
|
||||||
|
outer loop
|
||||||
|
vertex 5 25 0
|
||||||
|
vertex 5 5 5
|
||||||
|
vertex 5 5 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 1 -0 0
|
||||||
|
outer loop
|
||||||
|
vertex 5 27 5
|
||||||
vertex 5 55 0
|
vertex 5 55 0
|
||||||
vertex 5 55 5
|
vertex 5 55 5
|
||||||
endloop
|
endloop
|
||||||
@@ -247,13 +415,27 @@ solid OpenSCAD_Model
|
|||||||
facet normal 1 0 0
|
facet normal 1 0 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 5 55 0
|
vertex 5 55 0
|
||||||
vertex 5 5 5
|
vertex 5 27 5
|
||||||
vertex 5 5 0
|
vertex 5 27 0
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal -1 0 0
|
facet normal -1 0 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 55 5 0
|
vertex 55 5 0
|
||||||
|
vertex 55 29 5
|
||||||
|
vertex 55 29 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal -1 -0 0
|
||||||
|
outer loop
|
||||||
|
vertex 55 29 5
|
||||||
|
vertex 55 5 0
|
||||||
|
vertex 55 5 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal -1 0 0
|
||||||
|
outer loop
|
||||||
|
vertex 55 31 0
|
||||||
vertex 55 55 5
|
vertex 55 55 5
|
||||||
vertex 55 55 0
|
vertex 55 55 0
|
||||||
endloop
|
endloop
|
||||||
@@ -261,78 +443,232 @@ solid OpenSCAD_Model
|
|||||||
facet normal -1 -0 0
|
facet normal -1 -0 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 55 55 5
|
vertex 55 55 5
|
||||||
|
vertex 55 31 0
|
||||||
|
vertex 55 31 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 1 -0
|
||||||
|
outer loop
|
||||||
|
vertex 29 5 0
|
||||||
|
vertex 5 5 5
|
||||||
|
vertex 29 5 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 1 0
|
||||||
|
outer loop
|
||||||
|
vertex 5 5 5
|
||||||
|
vertex 29 5 0
|
||||||
|
vertex 5 5 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 1 -0
|
||||||
|
outer loop
|
||||||
vertex 55 5 0
|
vertex 55 5 0
|
||||||
|
vertex 31 5 5
|
||||||
vertex 55 5 5
|
vertex 55 5 5
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
|
facet normal 0 1 0
|
||||||
|
outer loop
|
||||||
|
vertex 31 5 5
|
||||||
|
vertex 55 5 0
|
||||||
|
vertex 31 5 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
facet normal 0 -1 0
|
facet normal 0 -1 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 5 55 0
|
vertex 5 55 0
|
||||||
vertex 55 55 5
|
vertex 29 55 5
|
||||||
vertex 5 55 5
|
vertex 5 55 5
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 -1 -0
|
facet normal 0 -1 -0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 55 55 5
|
vertex 29 55 5
|
||||||
vertex 5 55 0
|
vertex 5 55 0
|
||||||
vertex 55 55 0
|
vertex 29 55 0
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 1 -0
|
|
||||||
outer loop
|
|
||||||
vertex 55 5 0
|
|
||||||
vertex 5 5 5
|
|
||||||
vertex 55 5 5
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal 0 1 0
|
|
||||||
outer loop
|
|
||||||
vertex 5 5 5
|
|
||||||
vertex 55 5 0
|
|
||||||
vertex 5 5 0
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal -1 0 0
|
|
||||||
outer loop
|
|
||||||
vertex 0.5 26 0
|
|
||||||
vertex 0.5 28 5
|
|
||||||
vertex 0.5 28 0
|
|
||||||
endloop
|
|
||||||
endfacet
|
|
||||||
facet normal -1 -0 0
|
|
||||||
outer loop
|
|
||||||
vertex 0.5 28 5
|
|
||||||
vertex 0.5 26 0
|
|
||||||
vertex 0.5 26 5
|
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 -1 0
|
facet normal 0 -1 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 28 0
|
vertex 31 55 0
|
||||||
vertex 0.5 28 5
|
vertex 55 55 5
|
||||||
vertex 0 28 5
|
vertex 31 55 5
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 -1 -0
|
facet normal 0 -1 -0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0.5 28 5
|
vertex 55 55 5
|
||||||
vertex 0 28 0
|
vertex 31 55 0
|
||||||
vertex 0.5 28 0
|
vertex 55 55 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 1 -0 0
|
||||||
|
outer loop
|
||||||
|
vertex 4.5 25 5
|
||||||
|
vertex 4.5 27 0
|
||||||
|
vertex 4.5 27 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 1 0 0
|
||||||
|
outer loop
|
||||||
|
vertex 4.5 27 0
|
||||||
|
vertex 4.5 25 5
|
||||||
|
vertex 4.5 25 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 -1 0
|
||||||
|
outer loop
|
||||||
|
vertex 4.5 27 0
|
||||||
|
vertex 5 27 5
|
||||||
|
vertex 4.5 27 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 -1 -0
|
||||||
|
outer loop
|
||||||
|
vertex 5 27 5
|
||||||
|
vertex 4.5 27 0
|
||||||
|
vertex 5 27 0
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 1 -0
|
facet normal 0 1 -0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0.5 26 0
|
vertex 5 25 0
|
||||||
vertex 0 26 5
|
vertex 4.5 25 5
|
||||||
vertex 0.5 26 5
|
vertex 5 25 5
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
facet normal 0 1 0
|
facet normal 0 1 0
|
||||||
outer loop
|
outer loop
|
||||||
vertex 0 26 5
|
vertex 4.5 25 5
|
||||||
vertex 0.5 26 0
|
vertex 5 25 0
|
||||||
vertex 0 26 0
|
vertex 4.5 25 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 -1 0
|
||||||
|
outer loop
|
||||||
|
vertex 29 55.5 0
|
||||||
|
vertex 31 55.5 5
|
||||||
|
vertex 29 55.5 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 -1 -0
|
||||||
|
outer loop
|
||||||
|
vertex 31 55.5 5
|
||||||
|
vertex 29 55.5 0
|
||||||
|
vertex 31 55.5 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 1 -0 0
|
||||||
|
outer loop
|
||||||
|
vertex 29 55 5
|
||||||
|
vertex 29 55.5 0
|
||||||
|
vertex 29 55.5 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 1 0 0
|
||||||
|
outer loop
|
||||||
|
vertex 29 55.5 0
|
||||||
|
vertex 29 55 5
|
||||||
|
vertex 29 55 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal -1 0 0
|
||||||
|
outer loop
|
||||||
|
vertex 31 55 0
|
||||||
|
vertex 31 55.5 5
|
||||||
|
vertex 31 55.5 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal -1 -0 0
|
||||||
|
outer loop
|
||||||
|
vertex 31 55.5 5
|
||||||
|
vertex 31 55 0
|
||||||
|
vertex 31 55 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal -1 0 0
|
||||||
|
outer loop
|
||||||
|
vertex 55.5 29 0
|
||||||
|
vertex 55.5 31 5
|
||||||
|
vertex 55.5 31 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal -1 -0 0
|
||||||
|
outer loop
|
||||||
|
vertex 55.5 31 5
|
||||||
|
vertex 55.5 29 0
|
||||||
|
vertex 55.5 29 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 -1 0
|
||||||
|
outer loop
|
||||||
|
vertex 55 31 0
|
||||||
|
vertex 55.5 31 5
|
||||||
|
vertex 55 31 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 -1 -0
|
||||||
|
outer loop
|
||||||
|
vertex 55.5 31 5
|
||||||
|
vertex 55 31 0
|
||||||
|
vertex 55.5 31 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 1 -0
|
||||||
|
outer loop
|
||||||
|
vertex 55.5 29 0
|
||||||
|
vertex 55 29 5
|
||||||
|
vertex 55.5 29 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 1 0
|
||||||
|
outer loop
|
||||||
|
vertex 55 29 5
|
||||||
|
vertex 55.5 29 0
|
||||||
|
vertex 55 29 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 1 -0
|
||||||
|
outer loop
|
||||||
|
vertex 31 4.5 0
|
||||||
|
vertex 29 4.5 5
|
||||||
|
vertex 31 4.5 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 0 1 0
|
||||||
|
outer loop
|
||||||
|
vertex 29 4.5 5
|
||||||
|
vertex 31 4.5 0
|
||||||
|
vertex 29 4.5 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal -1 0 0
|
||||||
|
outer loop
|
||||||
|
vertex 31 4.5 0
|
||||||
|
vertex 31 5 5
|
||||||
|
vertex 31 5 0
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal -1 -0 0
|
||||||
|
outer loop
|
||||||
|
vertex 31 5 5
|
||||||
|
vertex 31 4.5 0
|
||||||
|
vertex 31 4.5 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 1 -0 0
|
||||||
|
outer loop
|
||||||
|
vertex 29 4.5 5
|
||||||
|
vertex 29 5 0
|
||||||
|
vertex 29 5 5
|
||||||
|
endloop
|
||||||
|
endfacet
|
||||||
|
facet normal 1 0 0
|
||||||
|
outer loop
|
||||||
|
vertex 29 5 0
|
||||||
|
vertex 29 4.5 5
|
||||||
|
vertex 29 4.5 0
|
||||||
endloop
|
endloop
|
||||||
endfacet
|
endfacet
|
||||||
endsolid OpenSCAD_Model
|
endsolid OpenSCAD_Model
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ StepList = (0, 1, 2)
|
|||||||
|
|
||||||
class CartKinematics:
|
class CartKinematics:
|
||||||
def __init__(self, toolhead, printer, config):
|
def __init__(self, toolhead, printer, config):
|
||||||
self.steppers = [stepper.PrinterHomingStepper(
|
self.printer = printer
|
||||||
printer, config.getsection('stepper_' + n), n)
|
self.steppers = [stepper.LookupMultiHomingStepper(
|
||||||
|
printer, config.getsection('stepper_' + n))
|
||||||
for n in ['x', 'y', 'z']]
|
for n in ['x', 'y', 'z']]
|
||||||
max_velocity, max_accel = toolhead.get_max_velocity()
|
max_velocity, max_accel = toolhead.get_max_velocity()
|
||||||
self.max_z_velocity = config.getfloat(
|
self.max_z_velocity = config.getfloat(
|
||||||
@@ -26,14 +27,34 @@ class CartKinematics:
|
|||||||
self.steppers[1].set_max_jerk(max_halt_velocity, max_accel)
|
self.steppers[1].set_max_jerk(max_halt_velocity, max_accel)
|
||||||
self.steppers[2].set_max_jerk(
|
self.steppers[2].set_max_jerk(
|
||||||
min(max_halt_velocity, self.max_z_velocity), max_accel)
|
min(max_halt_velocity, self.max_z_velocity), max_accel)
|
||||||
def set_position(self, newpos):
|
# Check for dual carriage support
|
||||||
|
self.dual_carriage_axis = None
|
||||||
|
self.dual_carriage_steppers = []
|
||||||
|
if config.has_section('dual_carriage'):
|
||||||
|
dc_config = config.getsection('dual_carriage')
|
||||||
|
self.dual_carriage_axis = dc_config.getchoice(
|
||||||
|
'axis', {'x': 0, 'y': 1})
|
||||||
|
dc_stepper = stepper.LookupMultiHomingStepper(printer, dc_config)
|
||||||
|
dc_stepper.set_max_jerk(max_halt_velocity, max_accel)
|
||||||
|
self.dual_carriage_steppers = [
|
||||||
|
self.steppers[self.dual_carriage_axis], dc_stepper]
|
||||||
|
printer.lookup_object('gcode').register_command(
|
||||||
|
'SET_DUAL_CARRIAGE', self.cmd_SET_DUAL_CARRIAGE,
|
||||||
|
desc=self.cmd_SET_DUAL_CARRIAGE_help)
|
||||||
|
def get_steppers(self, flags=""):
|
||||||
|
if flags == "Z":
|
||||||
|
return [self.steppers[2]]
|
||||||
|
return list(self.steppers)
|
||||||
|
def get_position(self):
|
||||||
|
return [s.mcu_stepper.get_commanded_position() for s in self.steppers]
|
||||||
|
def set_position(self, newpos, homing_axes):
|
||||||
for i in StepList:
|
for i in StepList:
|
||||||
self.steppers[i].mcu_stepper.set_position(newpos[i])
|
s = self.steppers[i]
|
||||||
def home(self, homing_state):
|
s.set_position(newpos[i])
|
||||||
# Each axis is homed independently and in order
|
if i in homing_axes:
|
||||||
for axis in homing_state.get_axes():
|
self.limits[i] = (s.position_min, s.position_max)
|
||||||
s = self.steppers[axis]
|
def _home_axis(self, homing_state, axis, stepper):
|
||||||
self.limits[axis] = (s.position_min, s.position_max)
|
s = stepper
|
||||||
# Determine moves
|
# Determine moves
|
||||||
if s.homing_positive_dir:
|
if s.homing_positive_dir:
|
||||||
pos = s.position_endstop - 1.5*(
|
pos = s.position_endstop - 1.5*(
|
||||||
@@ -46,28 +67,43 @@ class CartKinematics:
|
|||||||
rpos = s.position_endstop + s.homing_retract_dist
|
rpos = s.position_endstop + s.homing_retract_dist
|
||||||
r2pos = rpos + s.homing_retract_dist
|
r2pos = rpos + s.homing_retract_dist
|
||||||
# Initial homing
|
# Initial homing
|
||||||
homing_speed = s.get_homing_speed()
|
homing_speed = s.homing_speed
|
||||||
|
if axis == 2:
|
||||||
|
homing_speed = min(homing_speed, self.max_z_velocity)
|
||||||
homepos = [None, None, None, None]
|
homepos = [None, None, None, None]
|
||||||
homepos[axis] = s.position_endstop
|
homepos[axis] = s.position_endstop
|
||||||
coord = [None, None, None, None]
|
coord = [None, None, None, None]
|
||||||
coord[axis] = pos
|
coord[axis] = pos
|
||||||
homing_state.home(list(coord), homepos, [s], homing_speed)
|
homing_state.home(coord, homepos, s.get_endstops(), homing_speed)
|
||||||
# Retract
|
# Retract
|
||||||
coord[axis] = rpos
|
coord[axis] = rpos
|
||||||
homing_state.retract(list(coord), homing_speed)
|
homing_state.retract(coord, homing_speed)
|
||||||
# Home again
|
# Home again
|
||||||
coord[axis] = r2pos
|
coord[axis] = r2pos
|
||||||
homing_state.home(
|
homing_state.home(coord, homepos, s.get_endstops(),
|
||||||
list(coord), homepos, [s], homing_speed/2.0, second_home=True)
|
homing_speed/2.0, second_home=True)
|
||||||
# Set final homed position
|
# Set final homed position
|
||||||
coord[axis] = s.position_endstop + s.get_homed_offset()
|
coord[axis] = s.position_endstop + s.get_homed_offset()
|
||||||
homing_state.set_homed_position(coord)
|
homing_state.set_homed_position(coord)
|
||||||
def query_endstops(self, print_time, query_flags):
|
def home(self, homing_state):
|
||||||
return homing.query_endstops(print_time, query_flags, self.steppers)
|
# Each axis is homed independently and in order
|
||||||
|
for axis in homing_state.get_axes():
|
||||||
|
if axis == self.dual_carriage_axis:
|
||||||
|
dc1, dc2 = self.dual_carriage_steppers
|
||||||
|
altc = self.steppers[axis] == dc2
|
||||||
|
self._activate_carriage(0)
|
||||||
|
self._home_axis(homing_state, axis, dc1)
|
||||||
|
self._activate_carriage(1)
|
||||||
|
self._home_axis(homing_state, axis, dc2)
|
||||||
|
self._activate_carriage(altc)
|
||||||
|
else:
|
||||||
|
self._home_axis(homing_state, axis, self.steppers[axis])
|
||||||
def motor_off(self, print_time):
|
def motor_off(self, print_time):
|
||||||
self.limits = [(1.0, -1.0)] * 3
|
self.limits = [(1.0, -1.0)] * 3
|
||||||
for stepper in self.steppers:
|
for stepper in self.steppers:
|
||||||
stepper.motor_enable(print_time, 0)
|
stepper.motor_enable(print_time, 0)
|
||||||
|
for stepper in self.dual_carriage_steppers:
|
||||||
|
stepper.motor_enable(print_time, 0)
|
||||||
self.need_motor_enable = True
|
self.need_motor_enable = True
|
||||||
def _check_motor_enable(self, print_time, move):
|
def _check_motor_enable(self, print_time, move):
|
||||||
need_motor_enable = False
|
need_motor_enable = False
|
||||||
@@ -107,7 +143,7 @@ class CartKinematics:
|
|||||||
axis_d = move.axes_d[i]
|
axis_d = move.axes_d[i]
|
||||||
if not axis_d:
|
if not axis_d:
|
||||||
continue
|
continue
|
||||||
mcu_stepper = self.steppers[i].mcu_stepper
|
step_const = self.steppers[i].step_const
|
||||||
move_time = print_time
|
move_time = print_time
|
||||||
start_pos = move.start_pos[i]
|
start_pos = move.start_pos[i]
|
||||||
axis_r = abs(axis_d) / move.move_d
|
axis_r = abs(axis_d) / move.move_d
|
||||||
@@ -117,19 +153,38 @@ class CartKinematics:
|
|||||||
# Acceleration steps
|
# Acceleration steps
|
||||||
if move.accel_r:
|
if move.accel_r:
|
||||||
accel_d = move.accel_r * axis_d
|
accel_d = move.accel_r * axis_d
|
||||||
mcu_stepper.step_const(
|
step_const(move_time, start_pos, accel_d,
|
||||||
move_time, start_pos, accel_d, move.start_v * axis_r, accel)
|
move.start_v * axis_r, accel)
|
||||||
start_pos += accel_d
|
start_pos += accel_d
|
||||||
move_time += move.accel_t
|
move_time += move.accel_t
|
||||||
# Cruising steps
|
# Cruising steps
|
||||||
if move.cruise_r:
|
if move.cruise_r:
|
||||||
cruise_d = move.cruise_r * axis_d
|
cruise_d = move.cruise_r * axis_d
|
||||||
mcu_stepper.step_const(
|
step_const(move_time, start_pos, cruise_d, cruise_v, 0.)
|
||||||
move_time, start_pos, cruise_d, cruise_v, 0.)
|
|
||||||
start_pos += cruise_d
|
start_pos += cruise_d
|
||||||
move_time += move.cruise_t
|
move_time += move.cruise_t
|
||||||
# Deceleration steps
|
# Deceleration steps
|
||||||
if move.decel_r:
|
if move.decel_r:
|
||||||
decel_d = move.decel_r * axis_d
|
decel_d = move.decel_r * axis_d
|
||||||
mcu_stepper.step_const(
|
step_const(move_time, start_pos, decel_d, cruise_v, -accel)
|
||||||
move_time, start_pos, decel_d, cruise_v, -accel)
|
# Dual carriage support
|
||||||
|
def _activate_carriage(self, carriage):
|
||||||
|
toolhead = self.printer.lookup_object('toolhead')
|
||||||
|
toolhead.get_last_move_time()
|
||||||
|
dc_stepper = self.dual_carriage_steppers[carriage]
|
||||||
|
dc_axis = self.dual_carriage_axis
|
||||||
|
self.steppers[dc_axis] = dc_stepper
|
||||||
|
extruder_pos = toolhead.get_position()[3]
|
||||||
|
toolhead.set_position(self.get_position() + [extruder_pos])
|
||||||
|
if self.limits[dc_axis][0] <= self.limits[dc_axis][1]:
|
||||||
|
self.limits[dc_axis] = (
|
||||||
|
dc_stepper.position_min, dc_stepper.position_max)
|
||||||
|
self.need_motor_enable = True
|
||||||
|
cmd_SET_DUAL_CARRIAGE_help = "Set which carriage is active"
|
||||||
|
def cmd_SET_DUAL_CARRIAGE(self, params):
|
||||||
|
gcode = self.printer.lookup_object('gcode')
|
||||||
|
carriage = gcode.get_int('CARRIAGE', params)
|
||||||
|
if carriage not in (0, 1):
|
||||||
|
raise gcode.error("Invalid carriage")
|
||||||
|
self._activate_carriage(carriage)
|
||||||
|
gcode.reset_last_position()
|
||||||
|
|||||||
@@ -1,296 +0,0 @@
|
|||||||
# Code to configure miscellaneous chips
|
|
||||||
#
|
|
||||||
# Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
|
|
||||||
#
|
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
|
||||||
import pins, mcu
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# Statically configured output pins
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
class PrinterStaticDigitalOut:
|
|
||||||
def __init__(self, printer, config):
|
|
||||||
pin_list = [pin.strip() for pin in config.get('pins').split(',')]
|
|
||||||
for pin_desc in pin_list:
|
|
||||||
mcu_pin = pins.setup_pin(printer, 'digital_out', pin_desc)
|
|
||||||
mcu_pin.setup_static()
|
|
||||||
|
|
||||||
class PrinterStaticPWM:
|
|
||||||
def __init__(self, printer, config):
|
|
||||||
mcu_pwm = pins.setup_pin(printer, 'pwm', config.get('pin'))
|
|
||||||
mcu_pwm.setup_max_duration(0.)
|
|
||||||
hard_pwm = config.getint('hard_pwm', None, minval=1)
|
|
||||||
if hard_pwm is None:
|
|
||||||
mcu_pwm.setup_cycle_time(config.getfloat(
|
|
||||||
'cycle_time', 0.100, above=0.))
|
|
||||||
else:
|
|
||||||
mcu_pwm.setup_hard_pwm(hard_pwm)
|
|
||||||
scale = config.getfloat('scale', 1., above=0.)
|
|
||||||
value = config.getfloat('value', minval=0., maxval=scale)
|
|
||||||
mcu_pwm.setup_static_pwm(value / scale)
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# Servos
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
SERVO_MIN_TIME = 0.100
|
|
||||||
SERVO_SIGNAL_PERIOD = 0.020
|
|
||||||
|
|
||||||
class PrinterServo:
|
|
||||||
def __init__(self, printer, config):
|
|
||||||
self.mcu_servo = pins.setup_pin(printer, 'pwm', config.get('pin'))
|
|
||||||
self.mcu_servo.setup_max_duration(0.)
|
|
||||||
self.mcu_servo.setup_cycle_time(SERVO_SIGNAL_PERIOD)
|
|
||||||
self.min_width = config.getfloat(
|
|
||||||
'minimum_pulse_width', .001, above=0., below=SERVO_SIGNAL_PERIOD)
|
|
||||||
self.max_width = config.getfloat(
|
|
||||||
'maximum_pulse_width', .002
|
|
||||||
, above=self.min_width, below=SERVO_SIGNAL_PERIOD)
|
|
||||||
self.max_angle = config.getfloat('maximum_servo_angle', 180.)
|
|
||||||
self.angle_to_width = (self.max_width - self.min_width) / self.max_angle
|
|
||||||
self.width_to_value = 1. / SERVO_SIGNAL_PERIOD
|
|
||||||
self.last_value = self.last_value_time = 0.
|
|
||||||
def set_pwm(self, print_time, value):
|
|
||||||
if value == self.last_value:
|
|
||||||
return
|
|
||||||
print_time = max(self.last_value_time + SERVO_MIN_TIME, print_time)
|
|
||||||
self.mcu_servo.set_pwm(print_time, value)
|
|
||||||
self.last_value = value
|
|
||||||
self.last_value_time = print_time
|
|
||||||
# External commands
|
|
||||||
def set_angle(self, print_time, angle):
|
|
||||||
angle = max(0., min(self.max_angle, angle))
|
|
||||||
width = self.min_width + angle * self.angle_to_width
|
|
||||||
self.set_pwm(print_time, width * self.width_to_value)
|
|
||||||
def set_pulse_width(self, print_time, width):
|
|
||||||
width = max(self.min_width, min(self.max_width, width))
|
|
||||||
self.set_pwm(print_time, width * self.width_to_value)
|
|
||||||
|
|
||||||
def get_printer_servo(printer, name):
|
|
||||||
return printer.objects.get('servo ' + name)
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# AD5206 digipot
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
class ad5206:
|
|
||||||
def __init__(self, printer, config):
|
|
||||||
enable_pin_params = pins.get_printer_pins(printer).parse_pin_desc(
|
|
||||||
config.get('enable_pin'))
|
|
||||||
self.mcu = enable_pin_params['chip']
|
|
||||||
self.pin = enable_pin_params['pin']
|
|
||||||
self.mcu.add_config_object(self)
|
|
||||||
scale = config.getfloat('scale', 1., above=0.)
|
|
||||||
self.channels = [None] * 6
|
|
||||||
for i in range(len(self.channels)):
|
|
||||||
val = config.getfloat('channel_%d' % (i+1,), None,
|
|
||||||
minval=0., maxval=scale)
|
|
||||||
if val is not None:
|
|
||||||
self.channels[i] = int(val * 256. / scale + .5)
|
|
||||||
def build_config(self):
|
|
||||||
for i, val in enumerate(self.channels):
|
|
||||||
if val is not None:
|
|
||||||
self.mcu.add_config_cmd(
|
|
||||||
"send_spi_message pin=%s msg=%02x%02x" % (self.pin, i, val))
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# Replicape board
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
REPLICAPE_MAX_CURRENT = 3.84
|
|
||||||
REPLICAPE_SHIFT_REGISTER_BUS = 1
|
|
||||||
REPLICAPE_SHIFT_REGISTER_DEVICE = 1
|
|
||||||
REPLICAPE_PCA9685_BUS = 2
|
|
||||||
REPLICAPE_PCA9685_ADDRESS = 0x70
|
|
||||||
REPLICAPE_PCA9685_CYCLE_TIME = .001
|
|
||||||
|
|
||||||
class pca9685_pwm:
|
|
||||||
def __init__(self, replicape, channel, pin_params):
|
|
||||||
self._replicape = replicape
|
|
||||||
self._channel = channel
|
|
||||||
if pin_params['type'] not in ['digital_out', 'pwm']:
|
|
||||||
raise pins.error("Pin type not supported on replicape")
|
|
||||||
self._mcu = replicape.host_mcu
|
|
||||||
self._mcu.add_config_object(self)
|
|
||||||
self._bus = REPLICAPE_PCA9685_BUS
|
|
||||||
self._address = REPLICAPE_PCA9685_ADDRESS
|
|
||||||
self._cycle_time = REPLICAPE_PCA9685_CYCLE_TIME
|
|
||||||
self._max_duration = 2.
|
|
||||||
self._oid = None
|
|
||||||
self._invert = pin_params['invert']
|
|
||||||
self._shutdown_value = float(self._invert)
|
|
||||||
self._last_clock = 0
|
|
||||||
self._pwm_max = 0.
|
|
||||||
self._cmd_queue = self._mcu.alloc_command_queue()
|
|
||||||
self._set_cmd = None
|
|
||||||
self._static_value = None
|
|
||||||
def get_mcu(self):
|
|
||||||
return self._mcu
|
|
||||||
def setup_max_duration(self, max_duration):
|
|
||||||
self._max_duration = max_duration
|
|
||||||
def setup_cycle_time(self, cycle_time):
|
|
||||||
pass
|
|
||||||
def setup_hard_pwm(self, hard_cycle_ticks):
|
|
||||||
if hard_cycle_ticks:
|
|
||||||
raise pins.error("pca9685 does not support hard_pwm parameter")
|
|
||||||
def setup_static_pwm(self, value):
|
|
||||||
if self._invert:
|
|
||||||
value = 1. - value
|
|
||||||
self._static_value = max(0., min(1., value))
|
|
||||||
def setup_shutdown_value(self, value):
|
|
||||||
if self._invert:
|
|
||||||
value = 1. - value
|
|
||||||
self._shutdown_value = max(0., min(1., value))
|
|
||||||
if self._shutdown_value:
|
|
||||||
self._replicape.note_enable_on_shutdown()
|
|
||||||
def build_config(self):
|
|
||||||
self._pwm_max = self._mcu.get_constant_float("PCA9685_MAX")
|
|
||||||
cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time)
|
|
||||||
if self._static_value is not None:
|
|
||||||
value = int(self._static_value * self._pwm_max + 0.5)
|
|
||||||
self._mcu.add_config_cmd(
|
|
||||||
"set_pca9685_out bus=%d addr=%d channel=%d"
|
|
||||||
" cycle_ticks=%d value=%d" % (
|
|
||||||
self._bus, self._address, self._channel,
|
|
||||||
cycle_ticks, value))
|
|
||||||
return
|
|
||||||
self._oid = self._mcu.create_oid()
|
|
||||||
self._mcu.add_config_cmd(
|
|
||||||
"config_pca9685 oid=%d bus=%d addr=%d channel=%d cycle_ticks=%d"
|
|
||||||
" value=%d default_value=%d max_duration=%d" % (
|
|
||||||
self._oid, self._bus, self._address, self._channel, cycle_ticks,
|
|
||||||
self._invert * self._pwm_max,
|
|
||||||
self._shutdown_value * self._pwm_max,
|
|
||||||
self._mcu.seconds_to_clock(self._max_duration)))
|
|
||||||
self._set_cmd = self._mcu.lookup_command(
|
|
||||||
"schedule_pca9685_out oid=%c clock=%u value=%hu")
|
|
||||||
def set_pwm(self, print_time, value):
|
|
||||||
clock = self._mcu.print_time_to_clock(print_time)
|
|
||||||
if self._invert:
|
|
||||||
value = 1. - value
|
|
||||||
value = int(max(0., min(1., value)) * self._pwm_max + 0.5)
|
|
||||||
self._replicape.note_enable(print_time, self._channel, not not value)
|
|
||||||
msg = self._set_cmd.encode(self._oid, clock, value)
|
|
||||||
self._mcu.send(msg, minclock=self._last_clock, reqclock=clock
|
|
||||||
, cq=self._cmd_queue)
|
|
||||||
self._last_clock = clock
|
|
||||||
def set_digital(self, print_time, value):
|
|
||||||
if value:
|
|
||||||
self.set_pwm(print_time, 1.)
|
|
||||||
else:
|
|
||||||
self.set_pwm(print_time, 0.)
|
|
||||||
|
|
||||||
class ReplicapeDACEnable:
|
|
||||||
def __init__(self, replicape, channel, pin_params):
|
|
||||||
if pin_params['type'] != 'digital_out':
|
|
||||||
raise pins.error("Replicape virtual enable pin must be digital_out")
|
|
||||||
if pin_params['invert']:
|
|
||||||
raise pins.error("Replicape virtual enable pin can not be inverted")
|
|
||||||
self.mcu = replicape.host_mcu
|
|
||||||
self.value = replicape.stepper_dacs[channel]
|
|
||||||
self.pwm = pca9685_pwm(replicape, channel, pin_params)
|
|
||||||
self.last = 0
|
|
||||||
def get_mcu(self):
|
|
||||||
return self.mcu
|
|
||||||
def setup_max_duration(self, max_duration):
|
|
||||||
self.pwm.setup_max_duration(max_duration)
|
|
||||||
def set_digital(self, print_time, value):
|
|
||||||
if value:
|
|
||||||
self.pwm.set_pwm(print_time, self.value)
|
|
||||||
else:
|
|
||||||
self.pwm.set_pwm(print_time, 0.)
|
|
||||||
self.last = value
|
|
||||||
def get_last_setting(self):
|
|
||||||
return self.last
|
|
||||||
|
|
||||||
ReplicapeStepConfig = {
|
|
||||||
'disable': None,
|
|
||||||
'1': (1<<7)|(1<<5), '2': (1<<7)|(1<<5)|(1<<6), 'spread2': (1<<5),
|
|
||||||
'4': (1<<7)|(1<<5)|(1<<4), '16': (1<<7)|(1<<5)|(1<<6)|(1<<4),
|
|
||||||
'spread4': (1<<5)|(1<<4), 'spread16': (1<<7), 'stealth4': (1<<7)|(1<<6),
|
|
||||||
'stealth16': 0
|
|
||||||
}
|
|
||||||
|
|
||||||
class Replicape:
|
|
||||||
def __init__(self, printer, config):
|
|
||||||
pins.get_printer_pins(printer).register_chip('replicape', self)
|
|
||||||
revisions = {'B3': 'B3'}
|
|
||||||
config.getchoice('revision', revisions)
|
|
||||||
self.host_mcu = mcu.get_printer_mcu(printer, config.get('host_mcu'))
|
|
||||||
# Setup enable pin
|
|
||||||
self.mcu_enable = pins.setup_pin(
|
|
||||||
printer, 'digital_out', config.get('enable_pin', '!P9_41'))
|
|
||||||
self.mcu_enable.setup_max_duration(0.)
|
|
||||||
self.enabled_channels = {}
|
|
||||||
# Setup power pins
|
|
||||||
self.pins = {
|
|
||||||
"power_e": (pca9685_pwm, 5), "power_h": (pca9685_pwm, 3),
|
|
||||||
"power_hotbed": (pca9685_pwm, 4),
|
|
||||||
"power_fan0": (pca9685_pwm, 7), "power_fan1": (pca9685_pwm, 8),
|
|
||||||
"power_fan2": (pca9685_pwm, 9), "power_fan3": (pca9685_pwm, 10) }
|
|
||||||
# Setup stepper config
|
|
||||||
self.stepper_dacs = {}
|
|
||||||
shift_registers = [1] * 5
|
|
||||||
for port, name in enumerate('xyzeh'):
|
|
||||||
prefix = 'stepper_%s_' % (name,)
|
|
||||||
sc = config.getchoice(
|
|
||||||
prefix + 'microstep_mode', ReplicapeStepConfig, 'disable')
|
|
||||||
if sc is None:
|
|
||||||
continue
|
|
||||||
if config.getboolean(prefix + 'chopper_off_time_high', False):
|
|
||||||
sc |= 1<<3
|
|
||||||
if config.getboolean(prefix + 'chopper_hysteresis_high', False):
|
|
||||||
sc |= 1<<2
|
|
||||||
if config.getboolean(prefix + 'chopper_blank_time_high', True):
|
|
||||||
sc |= 1<<1
|
|
||||||
shift_registers[port] = sc
|
|
||||||
channel = port + 11
|
|
||||||
cur = config.getfloat(
|
|
||||||
prefix + 'current', above=0., maxval=REPLICAPE_MAX_CURRENT)
|
|
||||||
self.stepper_dacs[channel] = cur / REPLICAPE_MAX_CURRENT
|
|
||||||
self.pins[prefix + 'enable'] = (ReplicapeDACEnable, channel)
|
|
||||||
shift_registers.reverse()
|
|
||||||
self.host_mcu.add_config_cmd("send_spi bus=%d dev=%d msg=%s" % (
|
|
||||||
REPLICAPE_SHIFT_REGISTER_BUS, REPLICAPE_SHIFT_REGISTER_DEVICE,
|
|
||||||
"".join(["%02x" % (x,) for x in shift_registers])))
|
|
||||||
def note_enable_on_shutdown(self):
|
|
||||||
self.mcu_enable.setup_shutdown_value(1)
|
|
||||||
def note_enable(self, print_time, channel, is_enable):
|
|
||||||
if is_enable:
|
|
||||||
is_off = not self.enabled_channels
|
|
||||||
self.enabled_channels[channel] = 1
|
|
||||||
if is_off:
|
|
||||||
self.mcu_enable.set_digital(print_time, 1)
|
|
||||||
elif channel in self.enabled_channels:
|
|
||||||
del self.enabled_channels[channel]
|
|
||||||
if not self.enabled_channels:
|
|
||||||
self.mcu_enable.set_digital(print_time, 0)
|
|
||||||
def setup_pin(self, pin_params):
|
|
||||||
pin = pin_params['pin']
|
|
||||||
if pin not in self.pins:
|
|
||||||
raise pins.error("Unknown replicape pin %s" % (pin,))
|
|
||||||
pclass, channel = self.pins[pin]
|
|
||||||
return pclass(self, channel, pin_params)
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# Setup
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
def add_printer_objects(printer, config):
|
|
||||||
if config.has_section('replicape'):
|
|
||||||
printer.add_object('replicape', Replicape(
|
|
||||||
printer, config.getsection('replicape')))
|
|
||||||
for s in config.get_prefix_sections('static_digital_output '):
|
|
||||||
printer.add_object(s.section, PrinterStaticDigitalOut(printer, s))
|
|
||||||
for s in config.get_prefix_sections('static_pwm_output '):
|
|
||||||
printer.add_object(s.section, PrinterStaticPWM(printer, s))
|
|
||||||
for s in config.get_prefix_sections('servo '):
|
|
||||||
printer.add_object(s.section, PrinterServo(printer, s))
|
|
||||||
for s in config.get_prefix_sections('ad5206 '):
|
|
||||||
printer.add_object(s.section, ad5206(printer, s))
|
|
||||||
@@ -7,7 +7,7 @@ import logging, threading, math
|
|||||||
|
|
||||||
COMM_TIMEOUT = 3.5
|
COMM_TIMEOUT = 3.5
|
||||||
RTT_AGE = .000010 / (60. * 60.)
|
RTT_AGE = .000010 / (60. * 60.)
|
||||||
DECAY = 1. / (2. * 60.)
|
DECAY = 1. / 30.
|
||||||
TRANSMIT_EXTRA = .001
|
TRANSMIT_EXTRA = .001
|
||||||
|
|
||||||
class ClockSync:
|
class ClockSync:
|
||||||
@@ -29,20 +29,19 @@ class ClockSync:
|
|||||||
self.last_prediction_time = 0.
|
self.last_prediction_time = 0.
|
||||||
def connect(self, serial):
|
def connect(self, serial):
|
||||||
self.serial = serial
|
self.serial = serial
|
||||||
msgparser = serial.msgparser
|
self.mcu_freq = serial.msgparser.get_constant_float('CLOCK_FREQ')
|
||||||
self.mcu_freq = msgparser.get_constant_float('CLOCK_FREQ')
|
|
||||||
# Load initial clock and frequency
|
# Load initial clock and frequency
|
||||||
uptime_msg = msgparser.create_command('get_uptime')
|
get_uptime_cmd = serial.lookup_command('get_uptime')
|
||||||
params = serial.send_with_response(uptime_msg, 'uptime')
|
params = get_uptime_cmd.send_with_response(response='uptime')
|
||||||
self.last_clock = (params['high'] << 32) | params['clock']
|
self.last_clock = (params['high'] << 32) | params['clock']
|
||||||
self.clock_avg = self.last_clock
|
self.clock_avg = self.last_clock
|
||||||
self.time_avg = params['#sent_time']
|
self.time_avg = params['#sent_time']
|
||||||
self.clock_est = (self.time_avg, self.clock_avg, self.mcu_freq)
|
self.clock_est = (self.time_avg, self.clock_avg, self.mcu_freq)
|
||||||
self.prediction_variance = (.001 * self.mcu_freq)**2
|
self.prediction_variance = (.001 * self.mcu_freq)**2
|
||||||
# Enable periodic get_status timer
|
# Enable periodic get_status timer
|
||||||
self.status_cmd = msgparser.create_command('get_status')
|
self.status_cmd = serial.lookup_command('get_status')
|
||||||
for i in range(8):
|
for i in range(8):
|
||||||
params = serial.send_with_response(self.status_cmd, 'status')
|
params = self.status_cmd.send_with_response(response='status')
|
||||||
self._handle_status(params)
|
self._handle_status(params)
|
||||||
self.reactor.pause(0.100)
|
self.reactor.pause(0.100)
|
||||||
serial.register_callback(self._handle_status, 'status')
|
serial.register_callback(self._handle_status, 'status')
|
||||||
@@ -57,7 +56,7 @@ class ClockSync:
|
|||||||
serial.set_clock_est(freq, self.reactor.monotonic(), 0)
|
serial.set_clock_est(freq, self.reactor.monotonic(), 0)
|
||||||
# MCU clock querying (status callback invoked from background thread)
|
# MCU clock querying (status callback invoked from background thread)
|
||||||
def _status_event(self, eventtime):
|
def _status_event(self, eventtime):
|
||||||
self.serial.send(self.status_cmd)
|
self.status_cmd.send()
|
||||||
return eventtime + 1.0
|
return eventtime + 1.0
|
||||||
def _handle_status(self, params):
|
def _handle_status(self, params):
|
||||||
# Extend clock to 64bit
|
# Extend clock to 64bit
|
||||||
@@ -113,7 +112,7 @@ class ClockSync:
|
|||||||
pred_stddev = math.sqrt(self.prediction_variance)
|
pred_stddev = math.sqrt(self.prediction_variance)
|
||||||
self.serial.set_clock_est(new_freq, self.time_avg + TRANSMIT_EXTRA,
|
self.serial.set_clock_est(new_freq, self.time_avg + TRANSMIT_EXTRA,
|
||||||
int(self.clock_avg - 3. * pred_stddev))
|
int(self.clock_avg - 3. * pred_stddev))
|
||||||
self.clock_est = (self.time_avg - self.min_half_rtt,
|
self.clock_est = (self.time_avg + self.min_half_rtt,
|
||||||
self.clock_avg, new_freq)
|
self.clock_avg, new_freq)
|
||||||
#logging.debug("regr %.3f: freq=%.3f d=%d(%.3f)",
|
#logging.debug("regr %.3f: freq=%.3f d=%d(%.3f)",
|
||||||
# sent_time, new_freq, clock - exp_clock, pred_stddev)
|
# sent_time, new_freq, clock - exp_clock, pred_stddev)
|
||||||
@@ -165,6 +164,7 @@ class SecondarySync(ClockSync):
|
|||||||
ClockSync.__init__(self, reactor)
|
ClockSync.__init__(self, reactor)
|
||||||
self.main_sync = main_sync
|
self.main_sync = main_sync
|
||||||
self.clock_adj = (0., 1.)
|
self.clock_adj = (0., 1.)
|
||||||
|
self.last_sync_time = 0.
|
||||||
def connect(self, serial):
|
def connect(self, serial):
|
||||||
ClockSync.connect(self, serial)
|
ClockSync.connect(self, serial)
|
||||||
self.clock_adj = (0., self.mcu_freq)
|
self.clock_adj = (0., self.mcu_freq)
|
||||||
@@ -195,18 +195,25 @@ class SecondarySync(ClockSync):
|
|||||||
adjusted_offset, adjusted_freq = self.clock_adj
|
adjusted_offset, adjusted_freq = self.clock_adj
|
||||||
return "%s adj=%d" % (ClockSync.stats(self, eventtime), adjusted_freq)
|
return "%s adj=%d" % (ClockSync.stats(self, eventtime), adjusted_freq)
|
||||||
def calibrate_clock(self, print_time, eventtime):
|
def calibrate_clock(self, print_time, eventtime):
|
||||||
|
# Calculate: est_print_time = main_sync.estimatated_print_time()
|
||||||
ser_time, ser_clock, ser_freq = self.main_sync.clock_est
|
ser_time, ser_clock, ser_freq = self.main_sync.clock_est
|
||||||
main_mcu_freq = self.main_sync.mcu_freq
|
main_mcu_freq = self.main_sync.mcu_freq
|
||||||
|
est_main_clock = (eventtime - ser_time) * ser_freq + ser_clock
|
||||||
main_clock = (eventtime - ser_time) * ser_freq + ser_clock
|
est_print_time = est_main_clock / main_mcu_freq
|
||||||
print_time = max(print_time, main_clock / main_mcu_freq)
|
# Determine sync1_print_time and sync2_print_time
|
||||||
main_sync_clock = (print_time + 4.) * main_mcu_freq
|
sync1_print_time = max(print_time, est_print_time)
|
||||||
sync_time = ser_time + (main_sync_clock - ser_clock) / ser_freq
|
sync2_print_time = max(sync1_print_time + 4., self.last_sync_time,
|
||||||
|
print_time + 2.5 * (print_time - est_print_time))
|
||||||
print_clock = self.print_time_to_clock(print_time)
|
# Calc sync2_sys_time (inverse of main_sync.estimatated_print_time)
|
||||||
sync_clock = self.get_clock(sync_time)
|
sync2_main_clock = sync2_print_time * main_mcu_freq
|
||||||
adjusted_freq = .25 * (sync_clock - print_clock)
|
sync2_sys_time = ser_time + (sync2_main_clock - ser_clock) / ser_freq
|
||||||
adjusted_offset = print_time - print_clock / adjusted_freq
|
# Adjust freq so estimated print_time will match at sync2_print_time
|
||||||
|
sync1_clock = self.print_time_to_clock(sync1_print_time)
|
||||||
|
sync2_clock = self.get_clock(sync2_sys_time)
|
||||||
|
adjusted_freq = ((sync2_clock - sync1_clock)
|
||||||
|
/ (sync2_print_time - sync1_print_time))
|
||||||
|
adjusted_offset = sync1_print_time - sync1_clock / adjusted_freq
|
||||||
|
# Apply new values
|
||||||
self.clock_adj = (adjusted_offset, adjusted_freq)
|
self.clock_adj = (adjusted_offset, adjusted_freq)
|
||||||
|
self.last_sync_time = sync2_print_time
|
||||||
return self.clock_adj
|
return self.clock_adj
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ class KeyboardReader:
|
|||||||
self.clocksync.connect(self.ser)
|
self.clocksync.connect(self.ser)
|
||||||
self.ser.handle_default = self.handle_default
|
self.ser.handle_default = self.handle_default
|
||||||
self.mcu_freq = self.ser.msgparser.get_constant_float('CLOCK_FREQ')
|
self.mcu_freq = self.ser.msgparser.get_constant_float('CLOCK_FREQ')
|
||||||
mcu = self.ser.msgparser.get_constant('MCU')
|
mcu_type = self.ser.msgparser.get_constant('MCU')
|
||||||
self.pins = pins.get_pin_map(mcu)
|
self.pins = pins.PinResolver(mcu_type, validate_aliases=False)
|
||||||
self.reactor.unregister_timer(self.connect_timer)
|
self.reactor.unregister_timer(self.connect_timer)
|
||||||
self.output("="*20 + " connected " + "="*20)
|
self.output("="*20 + " connected " + "="*20)
|
||||||
return self.reactor.NEVER
|
return self.reactor.NEVER
|
||||||
@@ -71,8 +71,7 @@ class KeyboardReader:
|
|||||||
self.eval_globals['freq'] = self.mcu_freq
|
self.eval_globals['freq'] = self.mcu_freq
|
||||||
self.eval_globals['clock'] = self.clocksync.get_clock(eventtime)
|
self.eval_globals['clock'] = self.clocksync.get_clock(eventtime)
|
||||||
def command_PINS(self, parts):
|
def command_PINS(self, parts):
|
||||||
mcu = self.ser.msgparser.get_constant('MCU')
|
self.pins.update_aliases(parts[1])
|
||||||
self.pins = pins.get_pin_map(mcu, parts[1])
|
|
||||||
def command_SET(self, parts):
|
def command_SET(self, parts):
|
||||||
val = parts[2]
|
val = parts[2]
|
||||||
try:
|
try:
|
||||||
@@ -87,11 +86,10 @@ class KeyboardReader:
|
|||||||
self.output("Error: %s" % (str(e),))
|
self.output("Error: %s" % (str(e),))
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
msg = self.ser.msgparser.create_command(' '.join(parts[2:]))
|
self.ser.send(' '.join(parts[2:]), minclock=val)
|
||||||
except msgproto.error as e:
|
except msgproto.error as e:
|
||||||
self.output("Error: %s" % (str(e),))
|
self.output("Error: %s" % (str(e),))
|
||||||
return
|
return
|
||||||
self.ser.send(msg, minclock=val)
|
|
||||||
def command_FLOOD(self, parts):
|
def command_FLOOD(self, parts):
|
||||||
try:
|
try:
|
||||||
count = int(parts[1])
|
count = int(parts[1])
|
||||||
@@ -99,18 +97,18 @@ class KeyboardReader:
|
|||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.output("Error: %s" % (str(e),))
|
self.output("Error: %s" % (str(e),))
|
||||||
return
|
return
|
||||||
try:
|
msg = ' '.join(parts[3:])
|
||||||
msg = self.ser.msgparser.create_command(' '.join(parts[3:]))
|
|
||||||
except msgproto.error as e:
|
|
||||||
self.output("Error: %s" % (str(e),))
|
|
||||||
return
|
|
||||||
delay_clock = int(delay * self.mcu_freq)
|
delay_clock = int(delay * self.mcu_freq)
|
||||||
msg_clock = int(self.clocksync.get_clock(self.reactor.monotonic())
|
msg_clock = int(self.clocksync.get_clock(self.reactor.monotonic())
|
||||||
+ self.mcu_freq * .200)
|
+ self.mcu_freq * .200)
|
||||||
|
try:
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
next_clock = msg_clock + delay_clock
|
next_clock = msg_clock + delay_clock
|
||||||
self.ser.send(msg, minclock=msg_clock, reqclock=next_clock)
|
self.ser.send(msg, minclock=msg_clock, reqclock=next_clock)
|
||||||
msg_clock = next_clock
|
msg_clock = next_clock
|
||||||
|
except msgproto.error as e:
|
||||||
|
self.output("Error: %s" % (str(e),))
|
||||||
|
return
|
||||||
def command_SUPPRESS(self, parts):
|
def command_SUPPRESS(self, parts):
|
||||||
oid = None
|
oid = None
|
||||||
try:
|
try:
|
||||||
@@ -156,7 +154,7 @@ class KeyboardReader:
|
|||||||
self.output("Eval: %s" % (line,))
|
self.output("Eval: %s" % (line,))
|
||||||
if self.pins is not None:
|
if self.pins is not None:
|
||||||
try:
|
try:
|
||||||
line = pins.update_command(line, self.pins).strip()
|
line = self.pins.update_command(line).strip()
|
||||||
except:
|
except:
|
||||||
self.output("Unable to map pin: %s" % (line,))
|
self.output("Unable to map pin: %s" % (line,))
|
||||||
return None
|
return None
|
||||||
@@ -165,12 +163,7 @@ class KeyboardReader:
|
|||||||
if parts[0] in self.local_commands:
|
if parts[0] in self.local_commands:
|
||||||
self.local_commands[parts[0]](parts)
|
self.local_commands[parts[0]](parts)
|
||||||
return None
|
return None
|
||||||
try:
|
return line
|
||||||
msg = self.ser.msgparser.create_command(line)
|
|
||||||
except msgproto.error as e:
|
|
||||||
self.output("Error: %s" % (str(e),))
|
|
||||||
return None
|
|
||||||
return msg
|
|
||||||
def process_kbd(self, eventtime):
|
def process_kbd(self, eventtime):
|
||||||
self.data += os.read(self.fd, 4096)
|
self.data += os.read(self.fd, 4096)
|
||||||
|
|
||||||
@@ -185,7 +178,11 @@ class KeyboardReader:
|
|||||||
msg = self.translate(line.strip(), eventtime)
|
msg = self.translate(line.strip(), eventtime)
|
||||||
if msg is None:
|
if msg is None:
|
||||||
continue
|
continue
|
||||||
|
try:
|
||||||
self.ser.send(msg)
|
self.ser.send(msg)
|
||||||
|
except msgproto.error as e:
|
||||||
|
self.output("Error: %s" % (str(e),))
|
||||||
|
return None
|
||||||
self.data = kbdlines[-1]
|
self.data = kbdlines[-1]
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -10,9 +10,13 @@ StepList = (0, 1, 2)
|
|||||||
|
|
||||||
class CoreXYKinematics:
|
class CoreXYKinematics:
|
||||||
def __init__(self, toolhead, printer, config):
|
def __init__(self, toolhead, printer, config):
|
||||||
self.steppers = [stepper.PrinterHomingStepper(
|
self.steppers = [
|
||||||
printer, config.getsection('stepper_' + n), n)
|
stepper.PrinterHomingStepper(
|
||||||
for n in ['x', 'y', 'z']]
|
printer, config.getsection('stepper_x')),
|
||||||
|
stepper.PrinterHomingStepper(
|
||||||
|
printer, config.getsection('stepper_y')),
|
||||||
|
stepper.LookupMultiHomingStepper(
|
||||||
|
printer, config.getsection('stepper_z'))]
|
||||||
self.steppers[0].mcu_endstop.add_stepper(self.steppers[1].mcu_stepper)
|
self.steppers[0].mcu_endstop.add_stepper(self.steppers[1].mcu_stepper)
|
||||||
self.steppers[1].mcu_endstop.add_stepper(self.steppers[0].mcu_stepper)
|
self.steppers[1].mcu_endstop.add_stepper(self.steppers[0].mcu_stepper)
|
||||||
max_velocity, max_accel = toolhead.get_max_velocity()
|
max_velocity, max_accel = toolhead.get_max_velocity()
|
||||||
@@ -29,15 +33,24 @@ class CoreXYKinematics:
|
|||||||
self.steppers[1].set_max_jerk(max_xy_halt_velocity, max_accel)
|
self.steppers[1].set_max_jerk(max_xy_halt_velocity, max_accel)
|
||||||
self.steppers[2].set_max_jerk(
|
self.steppers[2].set_max_jerk(
|
||||||
min(max_halt_velocity, self.max_z_velocity), self.max_z_accel)
|
min(max_halt_velocity, self.max_z_velocity), self.max_z_accel)
|
||||||
def set_position(self, newpos):
|
def get_steppers(self, flags=""):
|
||||||
|
if flags == "Z":
|
||||||
|
return [self.steppers[2]]
|
||||||
|
return list(self.steppers)
|
||||||
|
def get_position(self):
|
||||||
|
pos = [s.mcu_stepper.get_commanded_position() for s in self.steppers]
|
||||||
|
return [0.5 * (pos[0] + pos[1]), 0.5 * (pos[0] - pos[1]), pos[2]]
|
||||||
|
def set_position(self, newpos, homing_axes):
|
||||||
pos = (newpos[0] + newpos[1], newpos[0] - newpos[1], newpos[2])
|
pos = (newpos[0] + newpos[1], newpos[0] - newpos[1], newpos[2])
|
||||||
for i in StepList:
|
for i in StepList:
|
||||||
self.steppers[i].mcu_stepper.set_position(pos[i])
|
s = self.steppers[i]
|
||||||
|
s.set_position(pos[i])
|
||||||
|
if i in homing_axes:
|
||||||
|
self.limits[i] = (s.position_min, s.position_max)
|
||||||
def home(self, homing_state):
|
def home(self, homing_state):
|
||||||
# Each axis is homed independently and in order
|
# Each axis is homed independently and in order
|
||||||
for axis in homing_state.get_axes():
|
for axis in homing_state.get_axes():
|
||||||
s = self.steppers[axis]
|
s = self.steppers[axis]
|
||||||
self.limits[axis] = (s.position_min, s.position_max)
|
|
||||||
# Determine moves
|
# Determine moves
|
||||||
if s.homing_positive_dir:
|
if s.homing_positive_dir:
|
||||||
pos = s.position_endstop - 1.5*(
|
pos = s.position_endstop - 1.5*(
|
||||||
@@ -50,25 +63,25 @@ class CoreXYKinematics:
|
|||||||
rpos = s.position_endstop + s.homing_retract_dist
|
rpos = s.position_endstop + s.homing_retract_dist
|
||||||
r2pos = rpos + s.homing_retract_dist
|
r2pos = rpos + s.homing_retract_dist
|
||||||
# Initial homing
|
# Initial homing
|
||||||
homing_speed = s.get_homing_speed()
|
homing_speed = s.homing_speed
|
||||||
|
if axis == 2:
|
||||||
|
homing_speed = min(homing_speed, self.max_z_velocity)
|
||||||
homepos = [None, None, None, None]
|
homepos = [None, None, None, None]
|
||||||
homepos[axis] = s.position_endstop
|
homepos[axis] = s.position_endstop
|
||||||
coord = [None, None, None, None]
|
coord = [None, None, None, None]
|
||||||
coord[axis] = pos
|
coord[axis] = pos
|
||||||
homing_state.home(list(coord), homepos, [s], homing_speed)
|
homing_state.home(coord, homepos, s.get_endstops(), homing_speed)
|
||||||
# Retract
|
# Retract
|
||||||
coord[axis] = rpos
|
coord[axis] = rpos
|
||||||
homing_state.retract(list(coord), homing_speed)
|
homing_state.retract(coord, homing_speed)
|
||||||
# Home again
|
# Home again
|
||||||
coord[axis] = r2pos
|
coord[axis] = r2pos
|
||||||
homing_state.home(
|
homing_state.home(coord, homepos, s.get_endstops(),
|
||||||
list(coord), homepos, [s], homing_speed/2.0, second_home=True)
|
homing_speed/2.0, second_home=True)
|
||||||
if axis == 2:
|
if axis == 2:
|
||||||
# Support endstop phase detection on Z axis
|
# Support endstop phase detection on Z axis
|
||||||
coord[axis] = s.position_endstop + s.get_homed_offset()
|
coord[axis] = s.position_endstop + s.get_homed_offset()
|
||||||
homing_state.set_homed_position(coord)
|
homing_state.set_homed_position(coord)
|
||||||
def query_endstops(self, print_time, query_flags):
|
|
||||||
return homing.query_endstops(print_time, query_flags, self.steppers)
|
|
||||||
def motor_off(self, print_time):
|
def motor_off(self, print_time):
|
||||||
self.limits = [(1.0, -1.0)] * 3
|
self.limits = [(1.0, -1.0)] * 3
|
||||||
for stepper in self.steppers:
|
for stepper in self.steppers:
|
||||||
@@ -122,7 +135,7 @@ class CoreXYKinematics:
|
|||||||
axis_d = axes_d[i]
|
axis_d = axes_d[i]
|
||||||
if not axis_d:
|
if not axis_d:
|
||||||
continue
|
continue
|
||||||
mcu_stepper = self.steppers[i].mcu_stepper
|
step_const = self.steppers[i].step_const
|
||||||
move_time = print_time
|
move_time = print_time
|
||||||
start_pos = move_start_pos[i]
|
start_pos = move_start_pos[i]
|
||||||
axis_r = abs(axis_d) / move.move_d
|
axis_r = abs(axis_d) / move.move_d
|
||||||
@@ -132,19 +145,17 @@ class CoreXYKinematics:
|
|||||||
# Acceleration steps
|
# Acceleration steps
|
||||||
if move.accel_r:
|
if move.accel_r:
|
||||||
accel_d = move.accel_r * axis_d
|
accel_d = move.accel_r * axis_d
|
||||||
mcu_stepper.step_const(
|
step_const(move_time, start_pos, accel_d,
|
||||||
move_time, start_pos, accel_d, move.start_v * axis_r, accel)
|
move.start_v * axis_r, accel)
|
||||||
start_pos += accel_d
|
start_pos += accel_d
|
||||||
move_time += move.accel_t
|
move_time += move.accel_t
|
||||||
# Cruising steps
|
# Cruising steps
|
||||||
if move.cruise_r:
|
if move.cruise_r:
|
||||||
cruise_d = move.cruise_r * axis_d
|
cruise_d = move.cruise_r * axis_d
|
||||||
mcu_stepper.step_const(
|
step_const(move_time, start_pos, cruise_d, cruise_v, 0.)
|
||||||
move_time, start_pos, cruise_d, cruise_v, 0.)
|
|
||||||
start_pos += cruise_d
|
start_pos += cruise_d
|
||||||
move_time += move.cruise_t
|
move_time += move.cruise_t
|
||||||
# Deceleration steps
|
# Deceleration steps
|
||||||
if move.decel_r:
|
if move.decel_r:
|
||||||
decel_d = move.decel_r * axis_d
|
decel_d = move.decel_r * axis_d
|
||||||
mcu_stepper.step_const(
|
step_const(move_time, start_pos, decel_d, cruise_v, -accel)
|
||||||
move_time, start_pos, decel_d, cruise_v, -accel)
|
|
||||||
|
|||||||
180
klippy/delta.py
180
klippy/delta.py
@@ -13,17 +13,30 @@ SLOW_RATIO = 3.
|
|||||||
|
|
||||||
class DeltaKinematics:
|
class DeltaKinematics:
|
||||||
def __init__(self, toolhead, printer, config):
|
def __init__(self, toolhead, printer, config):
|
||||||
self.steppers = [stepper.PrinterHomingStepper(
|
stepper_configs = [config.getsection('stepper_' + n)
|
||||||
printer, config.getsection('stepper_' + n), n)
|
|
||||||
for n in ['a', 'b', 'c']]
|
for n in ['a', 'b', 'c']]
|
||||||
|
stepper_a = stepper.PrinterHomingStepper(printer, stepper_configs[0])
|
||||||
|
stepper_b = stepper.PrinterHomingStepper(
|
||||||
|
printer, stepper_configs[1],
|
||||||
|
default_position=stepper_a.position_endstop)
|
||||||
|
stepper_c = stepper.PrinterHomingStepper(
|
||||||
|
printer, stepper_configs[2],
|
||||||
|
default_position=stepper_a.position_endstop)
|
||||||
|
self.steppers = [stepper_a, stepper_b, stepper_c]
|
||||||
self.need_motor_enable = self.need_home = True
|
self.need_motor_enable = self.need_home = True
|
||||||
radius = config.getfloat('delta_radius', above=0.)
|
self.radius = radius = config.getfloat('delta_radius', above=0.)
|
||||||
arm_length = config.getfloat('delta_arm_length', above=radius)
|
arm_length_a = stepper_configs[0].getfloat('arm_length', above=radius)
|
||||||
self.arm_length2 = arm_length**2
|
self.arm_lengths = arm_lengths = [
|
||||||
|
sconfig.getfloat('arm_length', arm_length_a, above=radius)
|
||||||
|
for sconfig in stepper_configs]
|
||||||
|
self.arm2 = [arm**2 for arm in arm_lengths]
|
||||||
|
self.endstops = [s.position_endstop + math.sqrt(arm2 - radius**2)
|
||||||
|
for s, arm2 in zip(self.steppers, self.arm2)]
|
||||||
self.limit_xy2 = -1.
|
self.limit_xy2 = -1.
|
||||||
tower_height_at_zeros = math.sqrt(self.arm_length2 - radius**2)
|
|
||||||
self.max_z = min([s.position_endstop for s in self.steppers])
|
self.max_z = min([s.position_endstop for s in self.steppers])
|
||||||
self.limit_z = self.max_z - (arm_length - tower_height_at_zeros)
|
self.min_z = config.getfloat('minimum_z_position', 0, maxval=self.max_z)
|
||||||
|
self.limit_z = min([ep - arm
|
||||||
|
for ep, arm in zip(self.endstops, arm_lengths)])
|
||||||
logging.info(
|
logging.info(
|
||||||
"Delta max build height %.2fmm (radius tapered above %.2fmm)" % (
|
"Delta max build height %.2fmm (radius tapered above %.2fmm)" % (
|
||||||
self.max_z, self.limit_z))
|
self.max_z, self.limit_z))
|
||||||
@@ -36,95 +49,69 @@ class DeltaKinematics:
|
|||||||
for s in self.steppers:
|
for s in self.steppers:
|
||||||
s.set_max_jerk(max_halt_velocity, self.max_accel)
|
s.set_max_jerk(max_halt_velocity, self.max_accel)
|
||||||
# Determine tower locations in cartesian space
|
# Determine tower locations in cartesian space
|
||||||
angles = [config.getsection('stepper_a').getfloat('angle', 210.),
|
self.angles = [sconfig.getfloat('angle', angle)
|
||||||
config.getsection('stepper_b').getfloat('angle', 330.),
|
for sconfig, angle in zip(stepper_configs,
|
||||||
config.getsection('stepper_c').getfloat('angle', 90.)]
|
[210., 330., 90.])]
|
||||||
self.towers = [(math.cos(math.radians(angle)) * radius,
|
self.towers = [(math.cos(math.radians(angle)) * radius,
|
||||||
math.sin(math.radians(angle)) * radius)
|
math.sin(math.radians(angle)) * radius)
|
||||||
for angle in angles]
|
for angle in self.angles]
|
||||||
# Find the point where an XY move could result in excessive
|
# Find the point where an XY move could result in excessive
|
||||||
# tower movement
|
# tower movement
|
||||||
half_min_step_dist = min([s.step_dist for s in self.steppers]) * .5
|
half_min_step_dist = min([s.step_dist for s in self.steppers]) * .5
|
||||||
|
min_arm_length = min(arm_lengths)
|
||||||
def ratio_to_dist(ratio):
|
def ratio_to_dist(ratio):
|
||||||
return (ratio * math.sqrt(self.arm_length2 / (ratio**2 + 1.)
|
return (ratio * math.sqrt(min_arm_length**2 / (ratio**2 + 1.)
|
||||||
- half_min_step_dist**2)
|
- half_min_step_dist**2)
|
||||||
+ half_min_step_dist)
|
+ half_min_step_dist)
|
||||||
self.slow_xy2 = (ratio_to_dist(SLOW_RATIO) - radius)**2
|
self.slow_xy2 = (ratio_to_dist(SLOW_RATIO) - radius)**2
|
||||||
self.very_slow_xy2 = (ratio_to_dist(2. * SLOW_RATIO) - radius)**2
|
self.very_slow_xy2 = (ratio_to_dist(2. * SLOW_RATIO) - radius)**2
|
||||||
self.max_xy2 = min(radius, arm_length - radius,
|
self.max_xy2 = min(radius, min_arm_length - radius,
|
||||||
ratio_to_dist(4. * SLOW_RATIO) - radius)**2
|
ratio_to_dist(4. * SLOW_RATIO) - radius)**2
|
||||||
logging.info(
|
logging.info(
|
||||||
"Delta max build radius %.2fmm (moves slowed past %.2fmm and %.2fmm)"
|
"Delta max build radius %.2fmm (moves slowed past %.2fmm and %.2fmm)"
|
||||||
% (math.sqrt(self.max_xy2), math.sqrt(self.slow_xy2),
|
% (math.sqrt(self.max_xy2), math.sqrt(self.slow_xy2),
|
||||||
math.sqrt(self.very_slow_xy2)))
|
math.sqrt(self.very_slow_xy2)))
|
||||||
self.set_position([0., 0., 0.])
|
self.set_position([0., 0., 0.], ())
|
||||||
|
def get_steppers(self, flags=""):
|
||||||
|
return list(self.steppers)
|
||||||
def _cartesian_to_actuator(self, coord):
|
def _cartesian_to_actuator(self, coord):
|
||||||
return [math.sqrt(self.arm_length2
|
return [math.sqrt(self.arm2[i] - (self.towers[i][0] - coord[0])**2
|
||||||
- (self.towers[i][0] - coord[0])**2
|
|
||||||
- (self.towers[i][1] - coord[1])**2) + coord[2]
|
- (self.towers[i][1] - coord[1])**2) + coord[2]
|
||||||
for i in StepList]
|
for i in StepList]
|
||||||
def _actuator_to_cartesian(self, pos):
|
def _actuator_to_cartesian(self, pos):
|
||||||
# Based on code from Smoothieware
|
return actuator_to_cartesian(self.towers, self.arm2, pos)
|
||||||
tower1 = list(self.towers[0]) + [pos[0]]
|
def get_position(self):
|
||||||
tower2 = list(self.towers[1]) + [pos[1]]
|
spos = [s.mcu_stepper.get_commanded_position() for s in self.steppers]
|
||||||
tower3 = list(self.towers[2]) + [pos[2]]
|
return self._actuator_to_cartesian(spos)
|
||||||
|
def set_position(self, newpos, homing_axes):
|
||||||
s12 = matrix_sub(tower1, tower2)
|
|
||||||
s23 = matrix_sub(tower2, tower3)
|
|
||||||
s13 = matrix_sub(tower1, tower3)
|
|
||||||
|
|
||||||
normal = matrix_cross(s12, s23)
|
|
||||||
|
|
||||||
magsq_s12 = matrix_magsq(s12)
|
|
||||||
magsq_s23 = matrix_magsq(s23)
|
|
||||||
magsq_s13 = matrix_magsq(s13)
|
|
||||||
|
|
||||||
inv_nmag_sq = 1.0 / matrix_magsq(normal)
|
|
||||||
q = 0.5 * inv_nmag_sq
|
|
||||||
|
|
||||||
a = q * magsq_s23 * matrix_dot(s12, s13)
|
|
||||||
b = -q * magsq_s13 * matrix_dot(s12, s23) # negate because we use s12 instead of s21
|
|
||||||
c = q * magsq_s12 * matrix_dot(s13, s23)
|
|
||||||
|
|
||||||
circumcenter = [tower1[0] * a + tower2[0] * b + tower3[0] * c,
|
|
||||||
tower1[1] * a + tower2[1] * b + tower3[1] * c,
|
|
||||||
tower1[2] * a + tower2[2] * b + tower3[2] * c]
|
|
||||||
|
|
||||||
r_sq = 0.5 * q * magsq_s12 * magsq_s23 * magsq_s13
|
|
||||||
dist = math.sqrt(inv_nmag_sq * (self.arm_length2 - r_sq))
|
|
||||||
|
|
||||||
return matrix_sub(circumcenter, matrix_mul(normal, dist))
|
|
||||||
def set_position(self, newpos):
|
|
||||||
pos = self._cartesian_to_actuator(newpos)
|
pos = self._cartesian_to_actuator(newpos)
|
||||||
for i in StepList:
|
for i in StepList:
|
||||||
self.steppers[i].mcu_stepper.set_position(pos[i])
|
self.steppers[i].set_position(pos[i])
|
||||||
self.limit_xy2 = -1.
|
self.limit_xy2 = -1.
|
||||||
|
if tuple(homing_axes) == StepList:
|
||||||
|
self.need_home = False
|
||||||
def home(self, homing_state):
|
def home(self, homing_state):
|
||||||
# All axes are homed simultaneously
|
# All axes are homed simultaneously
|
||||||
homing_state.set_axes([0, 1, 2])
|
homing_state.set_axes([0, 1, 2])
|
||||||
|
endstops = [es for s in self.steppers for es in s.get_endstops()]
|
||||||
s = self.steppers[0] # Assume homing speed same for all steppers
|
s = self.steppers[0] # Assume homing speed same for all steppers
|
||||||
self.need_home = False
|
|
||||||
# Initial homing
|
# Initial homing
|
||||||
homing_speed = s.get_homing_speed()
|
homing_speed = min(s.homing_speed, self.max_z_velocity)
|
||||||
homepos = [0., 0., self.max_z, None]
|
homepos = [0., 0., self.max_z, None]
|
||||||
coord = list(homepos)
|
coord = list(homepos)
|
||||||
coord[2] = -1.5 * math.sqrt(self.arm_length2-self.max_xy2)
|
coord[2] = -1.5 * math.sqrt(max(self.arm2)-self.max_xy2)
|
||||||
homing_state.home(list(coord), homepos, self.steppers, homing_speed)
|
homing_state.home(coord, homepos, endstops, homing_speed)
|
||||||
# Retract
|
# Retract
|
||||||
coord[2] = homepos[2] - s.homing_retract_dist
|
coord[2] = homepos[2] - s.homing_retract_dist
|
||||||
homing_state.retract(list(coord), homing_speed)
|
homing_state.retract(coord, homing_speed)
|
||||||
# Home again
|
# Home again
|
||||||
coord[2] -= s.homing_retract_dist
|
coord[2] -= s.homing_retract_dist
|
||||||
homing_state.home(list(coord), homepos, self.steppers
|
homing_state.home(coord, homepos, endstops,
|
||||||
, homing_speed/2.0, second_home=True)
|
homing_speed/2.0, second_home=True)
|
||||||
# Set final homed position
|
# Set final homed position
|
||||||
spos = self._cartesian_to_actuator(homepos)
|
spos = [ep + s.get_homed_offset()
|
||||||
spos = [spos[i] + self.steppers[i].position_endstop - self.max_z
|
for ep, s in zip(self.endstops, self.steppers)]
|
||||||
+ self.steppers[i].get_homed_offset()
|
|
||||||
for i in StepList]
|
|
||||||
homing_state.set_homed_position(self._actuator_to_cartesian(spos))
|
homing_state.set_homed_position(self._actuator_to_cartesian(spos))
|
||||||
def query_endstops(self, print_time, query_flags):
|
|
||||||
return homing.query_endstops(print_time, query_flags, self.steppers)
|
|
||||||
def motor_off(self, print_time):
|
def motor_off(self, print_time):
|
||||||
self.limit_xy2 = -1.
|
self.limit_xy2 = -1.
|
||||||
for stepper in self.steppers:
|
for stepper in self.steppers:
|
||||||
@@ -145,7 +132,7 @@ class DeltaKinematics:
|
|||||||
limit_xy2 = self.max_xy2
|
limit_xy2 = self.max_xy2
|
||||||
if end_pos[2] > self.limit_z:
|
if end_pos[2] > self.limit_z:
|
||||||
limit_xy2 = min(limit_xy2, (self.max_z - end_pos[2])**2)
|
limit_xy2 = min(limit_xy2, (self.max_z - end_pos[2])**2)
|
||||||
if xy2 > limit_xy2 or end_pos[2] < 0. or end_pos[2] > self.max_z:
|
if xy2 > limit_xy2 or end_pos[2] < self.min_z or end_pos[2] > self.max_z:
|
||||||
raise homing.EndstopMoveError(end_pos)
|
raise homing.EndstopMoveError(end_pos)
|
||||||
if move.axes_d[2]:
|
if move.axes_d[2]:
|
||||||
move.limit_speed(self.max_z_velocity, move.accel)
|
move.limit_speed(self.max_z_velocity, move.accel)
|
||||||
@@ -197,30 +184,42 @@ class DeltaKinematics:
|
|||||||
towery_d = self.towers[i][1] - origy
|
towery_d = self.towers[i][1] - origy
|
||||||
vt_startxy_d = (towerx_d*axes_d[0] + towery_d*axes_d[1])*inv_movexy_d
|
vt_startxy_d = (towerx_d*axes_d[0] + towery_d*axes_d[1])*inv_movexy_d
|
||||||
tangentxy_d2 = towerx_d**2 + towery_d**2 - vt_startxy_d**2
|
tangentxy_d2 = towerx_d**2 + towery_d**2 - vt_startxy_d**2
|
||||||
vt_arm_d = math.sqrt(self.arm_length2 - tangentxy_d2)
|
vt_arm_d = math.sqrt(self.arm2[i] - tangentxy_d2)
|
||||||
vt_startz = origz
|
vt_startz = origz
|
||||||
|
|
||||||
# Generate steps
|
# Generate steps
|
||||||
mcu_stepper = self.steppers[i].mcu_stepper
|
step_delta = self.steppers[i].step_delta
|
||||||
move_time = print_time
|
move_time = print_time
|
||||||
if accel_d:
|
if accel_d:
|
||||||
mcu_stepper.step_delta(
|
step_delta(move_time, accel_d, move.start_v, accel,
|
||||||
move_time, accel_d, move.start_v, accel,
|
|
||||||
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
|
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
|
||||||
vt_startz += accel_d * movez_r
|
vt_startz += accel_d * movez_r
|
||||||
vt_startxy_d -= accel_d * movexy_r
|
vt_startxy_d -= accel_d * movexy_r
|
||||||
move_time += move.accel_t
|
move_time += move.accel_t
|
||||||
if cruise_d:
|
if cruise_d:
|
||||||
mcu_stepper.step_delta(
|
step_delta(move_time, cruise_d, cruise_v, 0.,
|
||||||
move_time, cruise_d, cruise_v, 0.,
|
|
||||||
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
|
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
|
||||||
vt_startz += cruise_d * movez_r
|
vt_startz += cruise_d * movez_r
|
||||||
vt_startxy_d -= cruise_d * movexy_r
|
vt_startxy_d -= cruise_d * movexy_r
|
||||||
move_time += move.cruise_t
|
move_time += move.cruise_t
|
||||||
if decel_d:
|
if decel_d:
|
||||||
mcu_stepper.step_delta(
|
step_delta(move_time, decel_d, cruise_v, -accel,
|
||||||
move_time, decel_d, cruise_v, -accel,
|
|
||||||
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
|
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
|
||||||
|
# Helper functions for DELTA_CALIBRATE script
|
||||||
|
def get_stable_position(self):
|
||||||
|
return [int((ep - s.mcu_stepper.get_commanded_position())
|
||||||
|
/ s.mcu_stepper.get_step_dist() + .5)
|
||||||
|
* s.mcu_stepper.get_step_dist()
|
||||||
|
for ep, s in zip(self.endstops, self.steppers)]
|
||||||
|
def get_calibrate_params(self):
|
||||||
|
return {
|
||||||
|
'endstop_a': self.steppers[0].position_endstop,
|
||||||
|
'endstop_b': self.steppers[1].position_endstop,
|
||||||
|
'endstop_c': self.steppers[2].position_endstop,
|
||||||
|
'angle_a': self.angles[0], 'angle_b': self.angles[1],
|
||||||
|
'angle_c': self.angles[2], 'radius': self.radius,
|
||||||
|
'arm_a': self.arm_lengths[0], 'arm_b': self.arm_lengths[1],
|
||||||
|
'arm_c': self.arm_lengths[2] }
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
@@ -238,8 +237,49 @@ def matrix_dot(m1, m2):
|
|||||||
def matrix_magsq(m1):
|
def matrix_magsq(m1):
|
||||||
return m1[0]**2 + m1[1]**2 + m1[2]**2
|
return m1[0]**2 + m1[1]**2 + m1[2]**2
|
||||||
|
|
||||||
|
def matrix_add(m1, m2):
|
||||||
|
return [m1[0] + m2[0], m1[1] + m2[1], m1[2] + m2[2]]
|
||||||
|
|
||||||
def matrix_sub(m1, m2):
|
def matrix_sub(m1, m2):
|
||||||
return [m1[0] - m2[0], m1[1] - m2[1], m1[2] - m2[2]]
|
return [m1[0] - m2[0], m1[1] - m2[1], m1[2] - m2[2]]
|
||||||
|
|
||||||
def matrix_mul(m1, s):
|
def matrix_mul(m1, s):
|
||||||
return [m1[0]*s, m1[1]*s, m1[2]*s]
|
return [m1[0]*s, m1[1]*s, m1[2]*s]
|
||||||
|
|
||||||
|
def actuator_to_cartesian(towers, arm2, pos):
|
||||||
|
# Find nozzle position using trilateration (see wikipedia)
|
||||||
|
carriage1 = list(towers[0]) + [pos[0]]
|
||||||
|
carriage2 = list(towers[1]) + [pos[1]]
|
||||||
|
carriage3 = list(towers[2]) + [pos[2]]
|
||||||
|
|
||||||
|
s21 = matrix_sub(carriage2, carriage1)
|
||||||
|
s31 = matrix_sub(carriage3, carriage1)
|
||||||
|
|
||||||
|
d = math.sqrt(matrix_magsq(s21))
|
||||||
|
ex = matrix_mul(s21, 1. / d)
|
||||||
|
i = matrix_dot(ex, s31)
|
||||||
|
vect_ey = matrix_sub(s31, matrix_mul(ex, i))
|
||||||
|
ey = matrix_mul(vect_ey, 1. / math.sqrt(matrix_magsq(vect_ey)))
|
||||||
|
ez = matrix_cross(ex, ey)
|
||||||
|
j = matrix_dot(ey, s31)
|
||||||
|
|
||||||
|
x = (arm2[0] - arm2[1] + d**2) / (2. * d)
|
||||||
|
y = (arm2[0] - arm2[2] - x**2 + (x-i)**2 + j**2) / (2. * j)
|
||||||
|
z = -math.sqrt(arm2[0] - x**2 - y**2)
|
||||||
|
|
||||||
|
ex_x = matrix_mul(ex, x)
|
||||||
|
ey_y = matrix_mul(ey, y)
|
||||||
|
ez_z = matrix_mul(ez, z)
|
||||||
|
return matrix_add(carriage1, matrix_add(ex_x, matrix_add(ey_y, ez_z)))
|
||||||
|
|
||||||
|
def get_position_from_stable(spos, params):
|
||||||
|
angles = [params['angle_a'], params['angle_b'], params['angle_c']]
|
||||||
|
radius = params['radius']
|
||||||
|
radius2 = radius**2
|
||||||
|
towers = [(math.cos(angle) * radius, math.sin(angle) * radius)
|
||||||
|
for angle in map(math.radians, angles)]
|
||||||
|
arm2 = [a**2 for a in [params['arm_a'], params['arm_b'], params['arm_c']]]
|
||||||
|
endstops = [params['endstop_a'], params['endstop_b'], params['endstop_c']]
|
||||||
|
pos = [es + math.sqrt(a2 - radius2) - p
|
||||||
|
for es, a2, p in zip(endstops, arm2, spos)]
|
||||||
|
return actuator_to_cartesian(towers, arm2, pos)
|
||||||
|
|||||||
5
klippy/extras/__init__.py
Normal file
5
klippy/extras/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Package definition for the extras directory
|
||||||
|
#
|
||||||
|
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
32
klippy/extras/ad5206.py
Normal file
32
klippy/extras/ad5206.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# AD5206 digipot code
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017,2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import pins
|
||||||
|
|
||||||
|
class ad5206:
|
||||||
|
def __init__(self, config):
|
||||||
|
printer = config.get_printer()
|
||||||
|
enable_pin_params = pins.get_printer_pins(printer).lookup_pin(
|
||||||
|
'digital_out', config.get('enable_pin'))
|
||||||
|
if enable_pin_params['invert']:
|
||||||
|
raise pins.error("ad5206 can not invert pin")
|
||||||
|
self.mcu = enable_pin_params['chip']
|
||||||
|
self.pin = enable_pin_params['pin']
|
||||||
|
self.mcu.add_config_object(self)
|
||||||
|
scale = config.getfloat('scale', 1., above=0.)
|
||||||
|
self.channels = [None] * 6
|
||||||
|
for i in range(len(self.channels)):
|
||||||
|
val = config.getfloat('channel_%d' % (i+1,), None,
|
||||||
|
minval=0., maxval=scale)
|
||||||
|
if val is not None:
|
||||||
|
self.channels[i] = int(val * 256. / scale + .5)
|
||||||
|
def build_config(self):
|
||||||
|
for i, val in enumerate(self.channels):
|
||||||
|
if val is not None:
|
||||||
|
self.mcu.add_config_cmd(
|
||||||
|
"send_spi_message pin=%s msg=%02x%02x" % (self.pin, i, val))
|
||||||
|
|
||||||
|
def load_config_prefix(config):
|
||||||
|
return ad5206(config)
|
||||||
113
klippy/extras/bed_tilt.py
Normal file
113
klippy/extras/bed_tilt.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# Bed tilt compensation
|
||||||
|
#
|
||||||
|
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import logging
|
||||||
|
import probe, mathutil
|
||||||
|
|
||||||
|
class BedTilt:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.x_adjust = config.getfloat('x_adjust', 0.)
|
||||||
|
self.y_adjust = config.getfloat('y_adjust', 0.)
|
||||||
|
if config.get('points', None) is not None:
|
||||||
|
BedTiltCalibrate(config, self)
|
||||||
|
self.toolhead = None
|
||||||
|
gcode = self.printer.lookup_object('gcode')
|
||||||
|
gcode.set_move_transform(self)
|
||||||
|
def printer_state(self, state):
|
||||||
|
if state == 'connect':
|
||||||
|
self.toolhead = self.printer.lookup_object('toolhead')
|
||||||
|
def get_position(self):
|
||||||
|
x, y, z, e = self.toolhead.get_position()
|
||||||
|
return [x, y, z - x*self.x_adjust - y*self.y_adjust, e]
|
||||||
|
def move(self, newpos, speed):
|
||||||
|
x, y, z, e = newpos
|
||||||
|
self.toolhead.move([x, y, z + x*self.x_adjust + y*self.y_adjust, e],
|
||||||
|
speed)
|
||||||
|
|
||||||
|
# Helper script to calibrate the bed tilt
|
||||||
|
class BedTiltCalibrate:
|
||||||
|
def __init__(self, config, bedtilt):
|
||||||
|
self.bedtilt = bedtilt
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
points = config.get('points').split('\n')
|
||||||
|
try:
|
||||||
|
points = [line.split(',', 1) for line in points if line.strip()]
|
||||||
|
self.points = [(float(p[0].strip()), float(p[1].strip()))
|
||||||
|
for p in points]
|
||||||
|
except:
|
||||||
|
raise config.error("Unable to parse bed tilt points")
|
||||||
|
if len(self.points) < 3:
|
||||||
|
raise config.error("Need at least 3 points for bed_tilt_calibrate")
|
||||||
|
self.speed = config.getfloat('speed', 50., above=0.)
|
||||||
|
self.horizontal_move_z = config.getfloat('horizontal_move_z', 5.)
|
||||||
|
self.z_position_endstop = None
|
||||||
|
if config.has_section('stepper_z'):
|
||||||
|
zconfig = config.getsection('stepper_z')
|
||||||
|
self.z_position_endstop = zconfig.getfloat('position_endstop', None)
|
||||||
|
self.manual_probe = config.getboolean('manual_probe', None)
|
||||||
|
if self.manual_probe is None:
|
||||||
|
self.manual_probe = not config.has_section('probe')
|
||||||
|
self.gcode = self.printer.lookup_object('gcode')
|
||||||
|
self.gcode.register_command(
|
||||||
|
'BED_TILT_CALIBRATE', self.cmd_BED_TILT_CALIBRATE,
|
||||||
|
desc=self.cmd_BED_TILT_CALIBRATE_help)
|
||||||
|
cmd_BED_TILT_CALIBRATE_help = "Bed tilt calibration script"
|
||||||
|
def cmd_BED_TILT_CALIBRATE(self, params):
|
||||||
|
self.gcode.run_script("G28")
|
||||||
|
probe.ProbePointsHelper(
|
||||||
|
self.printer, self.points, self.horizontal_move_z,
|
||||||
|
self.speed, self.manual_probe, self)
|
||||||
|
def get_position(self):
|
||||||
|
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
||||||
|
return kin.get_position()
|
||||||
|
def finalize(self, z_offset, positions):
|
||||||
|
logging.info("Calculating bed_tilt with: %s", positions)
|
||||||
|
params = { 'x_adjust': self.bedtilt.x_adjust,
|
||||||
|
'y_adjust': self.bedtilt.y_adjust,
|
||||||
|
'z_adjust': z_offset }
|
||||||
|
logging.info("Initial bed_tilt parameters: %s", params)
|
||||||
|
def adjusted_height(pos, params):
|
||||||
|
x, y, z = pos
|
||||||
|
return (z - x*params['x_adjust'] - y*params['y_adjust']
|
||||||
|
- params['z_adjust'])
|
||||||
|
def errorfunc(params):
|
||||||
|
total_error = 0.
|
||||||
|
for pos in positions:
|
||||||
|
total_error += adjusted_height(pos, params)**2
|
||||||
|
return total_error
|
||||||
|
new_params = mathutil.coordinate_descent(
|
||||||
|
params.keys(), params, errorfunc)
|
||||||
|
logging.info("Calculated bed_tilt parameters: %s", new_params)
|
||||||
|
for pos in positions:
|
||||||
|
logging.info("orig: %s new: %s", adjusted_height(pos, params),
|
||||||
|
adjusted_height(pos, new_params))
|
||||||
|
z_diff = new_params['z_adjust'] - z_offset
|
||||||
|
if self.z_position_endstop is not None:
|
||||||
|
# Cartesian style robot
|
||||||
|
z_extra = ""
|
||||||
|
probe = self.printer.lookup_object('probe', None)
|
||||||
|
if probe is not None:
|
||||||
|
last_home_position = probe.last_home_position()
|
||||||
|
if last_home_position is not None:
|
||||||
|
# Using z_virtual_endstop
|
||||||
|
home_x, home_y = last_home_position[:2]
|
||||||
|
z_diff -= home_x * new_params['x_adjust']
|
||||||
|
z_diff -= home_y * new_params['y_adjust']
|
||||||
|
z_extra = " (when Z homing at %.3f,%.3f)" % (home_x, home_y)
|
||||||
|
z_adjust = "stepper_z position_endstop: %.6f%s\n" % (
|
||||||
|
self.z_position_endstop - z_diff, z_extra)
|
||||||
|
else:
|
||||||
|
# Delta (or other) style robot
|
||||||
|
z_adjust = "Add %.6f to endstop position\n" % (-z_diff,)
|
||||||
|
msg = "%sx_adjust: %.6f y_adjust: %.6f" % (
|
||||||
|
z_adjust, new_params['x_adjust'], new_params['y_adjust'])
|
||||||
|
logging.info("bed_tilt_calibrate: %s", msg)
|
||||||
|
self.gcode.respond_info(
|
||||||
|
"%s\nTo use these parameters, update the printer config file with\n"
|
||||||
|
"the above and then issue a RESTART command" % (msg,))
|
||||||
|
|
||||||
|
def load_config(config):
|
||||||
|
return BedTilt(config)
|
||||||
73
klippy/extras/delta_calibrate.py
Normal file
73
klippy/extras/delta_calibrate.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# Delta calibration support
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import math, logging
|
||||||
|
import probe, delta, mathutil
|
||||||
|
|
||||||
|
class DeltaCalibrate:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
if config.getsection('printer').get('kinematics') != 'delta':
|
||||||
|
raise config.error("Delta calibrate is only for delta printers")
|
||||||
|
self.radius = config.getfloat('radius', above=0.)
|
||||||
|
self.speed = config.getfloat('speed', 50., above=0.)
|
||||||
|
self.horizontal_move_z = config.getfloat('horizontal_move_z', 5.)
|
||||||
|
self.manual_probe = config.getboolean('manual_probe', None)
|
||||||
|
if self.manual_probe is None:
|
||||||
|
self.manual_probe = not config.has_section('probe')
|
||||||
|
self.gcode = self.printer.lookup_object('gcode')
|
||||||
|
self.gcode.register_command(
|
||||||
|
'DELTA_CALIBRATE', self.cmd_DELTA_CALIBRATE,
|
||||||
|
desc=self.cmd_DELTA_CALIBRATE_help)
|
||||||
|
cmd_DELTA_CALIBRATE_help = "Delta calibration script"
|
||||||
|
def cmd_DELTA_CALIBRATE(self, params):
|
||||||
|
# Setup probe points
|
||||||
|
points = [(0., 0.)]
|
||||||
|
scatter = [.95, .90, .85, .70, .75, .80]
|
||||||
|
for i in range(6):
|
||||||
|
r = math.radians(90. + 60. * i)
|
||||||
|
dist = self.radius * scatter[i]
|
||||||
|
points.append((math.cos(r) * dist, math.sin(r) * dist))
|
||||||
|
# Probe them
|
||||||
|
self.gcode.run_script("G28")
|
||||||
|
probe.ProbePointsHelper(self.printer, points, self.horizontal_move_z,
|
||||||
|
self.speed, self.manual_probe, self)
|
||||||
|
def get_position(self):
|
||||||
|
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
||||||
|
return kin.get_stable_position()
|
||||||
|
def finalize(self, z_offset, positions):
|
||||||
|
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
||||||
|
logging.info("Calculating delta_calibrate with: %s", positions)
|
||||||
|
params = kin.get_calibrate_params()
|
||||||
|
logging.info("Initial delta_calibrate parameters: %s", params)
|
||||||
|
adj_params = ('endstop_a', 'endstop_b', 'endstop_c', 'radius',
|
||||||
|
'angle_a', 'angle_b')
|
||||||
|
def delta_errorfunc(params):
|
||||||
|
total_error = 0.
|
||||||
|
for spos in positions:
|
||||||
|
x, y, z = delta.get_position_from_stable(spos, params)
|
||||||
|
total_error += (z - z_offset)**2
|
||||||
|
return total_error
|
||||||
|
new_params = mathutil.coordinate_descent(
|
||||||
|
adj_params, params, delta_errorfunc)
|
||||||
|
logging.info("Calculated delta_calibrate parameters: %s", new_params)
|
||||||
|
for spos in positions:
|
||||||
|
logging.info("orig: %s new: %s",
|
||||||
|
delta.get_position_from_stable(spos, params),
|
||||||
|
delta.get_position_from_stable(spos, new_params))
|
||||||
|
self.gcode.respond_info(
|
||||||
|
"stepper_a: position_endstop: %.6f angle: %.6f\n"
|
||||||
|
"stepper_b: position_endstop: %.6f angle: %.6f\n"
|
||||||
|
"stepper_c: position_endstop: %.6f angle: %.6f\n"
|
||||||
|
"radius: %.6f\n"
|
||||||
|
"To use these parameters, update the printer config file with\n"
|
||||||
|
"the above and then issue a RESTART command" % (
|
||||||
|
new_params['endstop_a'], new_params['angle_a'],
|
||||||
|
new_params['endstop_b'], new_params['angle_b'],
|
||||||
|
new_params['endstop_c'], new_params['angle_c'],
|
||||||
|
new_params['radius']))
|
||||||
|
|
||||||
|
def load_config(config):
|
||||||
|
return DeltaCalibrate(config)
|
||||||
602
klippy/extras/display.py
Normal file
602
klippy/extras/display.py
Normal file
@@ -0,0 +1,602 @@
|
|||||||
|
# Basic LCD display support
|
||||||
|
#
|
||||||
|
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
# Copyright (C) 2018 Aleph Objects, Inc <marcio@alephobjects.com>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import logging
|
||||||
|
|
||||||
|
BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# HD44780 (20x4 text) lcd chip
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
HD44780_DELAY = .000037
|
||||||
|
|
||||||
|
class HD44780:
|
||||||
|
char_right_arrow = '\x7e'
|
||||||
|
char_thermometer = '\x00'
|
||||||
|
char_heater_bed = '\x01'
|
||||||
|
char_speed_factor = '\x02'
|
||||||
|
char_clock = '\x03'
|
||||||
|
char_degrees = '\x04'
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
# pin config
|
||||||
|
ppins = self.printer.lookup_object('pins')
|
||||||
|
pins = [ppins.lookup_pin('digital_out', config.get(name + '_pin'))
|
||||||
|
for name in ['rs', 'e', 'd4', 'd5', 'd6', 'd7']]
|
||||||
|
mcu = None
|
||||||
|
for pin_params in pins:
|
||||||
|
if mcu is not None and pin_params['chip'] != mcu:
|
||||||
|
raise ppins.error("hd44780 all pins must be on same mcu")
|
||||||
|
mcu = pin_params['chip']
|
||||||
|
if pin_params['invert']:
|
||||||
|
raise ppins.error("hd44780 can not invert pin")
|
||||||
|
self.pins = [pin_params['pin'] for pin_params in pins]
|
||||||
|
self.mcu = mcu
|
||||||
|
self.oid = self.mcu.create_oid()
|
||||||
|
self.mcu.add_config_object(self)
|
||||||
|
self.send_data_cmd = self.send_cmds_cmd = None
|
||||||
|
# framebuffers
|
||||||
|
self.text_framebuffer = (bytearray(' '*80), bytearray('~'*80), 0x80)
|
||||||
|
self.glyph_framebuffer = (bytearray(64), bytearray('~'*64), 0x40)
|
||||||
|
self.framebuffers = [self.text_framebuffer, self.glyph_framebuffer]
|
||||||
|
def build_config(self):
|
||||||
|
self.mcu.add_config_cmd(
|
||||||
|
"config_hd44780 oid=%d rs_pin=%s e_pin=%s"
|
||||||
|
" d4_pin=%s d5_pin=%s d6_pin=%s d7_pin=%s delay_ticks=%d" % (
|
||||||
|
self.oid, self.pins[0], self.pins[1],
|
||||||
|
self.pins[2], self.pins[3], self.pins[4], self.pins[5],
|
||||||
|
self.mcu.seconds_to_clock(HD44780_DELAY)))
|
||||||
|
cmd_queue = self.mcu.alloc_command_queue()
|
||||||
|
self.send_cmds_cmd = self.mcu.lookup_command(
|
||||||
|
"hd44780_send_cmds oid=%c cmds=%*s", cq=cmd_queue)
|
||||||
|
self.send_data_cmd = self.mcu.lookup_command(
|
||||||
|
"hd44780_send_data oid=%c data=%*s", cq=cmd_queue)
|
||||||
|
def send(self, cmds, is_data=False):
|
||||||
|
cmd_type = self.send_cmds_cmd
|
||||||
|
if is_data:
|
||||||
|
cmd_type = self.send_data_cmd
|
||||||
|
cmd_type.send([self.oid, cmds], reqclock=BACKGROUND_PRIORITY_CLOCK)
|
||||||
|
#logging.debug("hd44780 %d %s", is_data, repr(cmds))
|
||||||
|
def flush(self):
|
||||||
|
# Find all differences in the framebuffers and send them to the chip
|
||||||
|
for new_data, old_data, fb_id in self.framebuffers:
|
||||||
|
if new_data == old_data:
|
||||||
|
continue
|
||||||
|
# Find the position of all changed bytes in this framebuffer
|
||||||
|
diffs = [[i, 1] for i, (nd, od) in enumerate(zip(new_data, old_data))
|
||||||
|
if nd != od]
|
||||||
|
# Batch together changes that are close to each other
|
||||||
|
for i in range(len(diffs)-2, -1, -1):
|
||||||
|
pos, count = diffs[i]
|
||||||
|
nextpos, nextcount = diffs[i+1]
|
||||||
|
if pos + 4 >= nextpos and nextcount < 16:
|
||||||
|
diffs[i][1] = nextcount + (nextpos - pos)
|
||||||
|
del diffs[i+1]
|
||||||
|
# Transmit changes
|
||||||
|
for pos, count in diffs:
|
||||||
|
chip_pos = pos
|
||||||
|
if fb_id == 0x80 and pos >= 40:
|
||||||
|
chip_pos += 0x40 - 40
|
||||||
|
self.send([fb_id + chip_pos])
|
||||||
|
self.send(new_data[pos:pos+count], is_data=True)
|
||||||
|
old_data[:] = new_data
|
||||||
|
def init(self):
|
||||||
|
curtime = self.printer.get_reactor().monotonic()
|
||||||
|
print_time = self.mcu.estimated_print_time(curtime)
|
||||||
|
# Program 4bit / 2-line mode and then issue 0x02 "Home" command
|
||||||
|
init = [[0x33], [0x33], [0x33, 0x22, 0x28, 0x02]]
|
||||||
|
# Reset (set positive direction ; enable display and hide cursor)
|
||||||
|
init.append([0x06, 0x0c])
|
||||||
|
for i, cmds in enumerate(init):
|
||||||
|
minclock = self.mcu.print_time_to_clock(print_time + i * .100)
|
||||||
|
self.send_cmds_cmd.send([self.oid, cmds], minclock=minclock)
|
||||||
|
# Add custom fonts
|
||||||
|
self.glyph_framebuffer[0][:len(HD44780_chars)] = HD44780_chars
|
||||||
|
for i in range(len(self.glyph_framebuffer[0])):
|
||||||
|
self.glyph_framebuffer[1][i] = self.glyph_framebuffer[0][i] ^ 1
|
||||||
|
self.flush()
|
||||||
|
def write_text(self, x, y, data):
|
||||||
|
if x + len(data) > 20:
|
||||||
|
data = data[:20 - min(x, 20)]
|
||||||
|
pos = [0, 40, 20, 60][y] + x
|
||||||
|
self.text_framebuffer[0][pos:pos+len(data)] = data
|
||||||
|
def clear(self):
|
||||||
|
self.text_framebuffer[0][:] = ' '*80
|
||||||
|
|
||||||
|
HD44780_chars = [
|
||||||
|
# Thermometer
|
||||||
|
0b00100,
|
||||||
|
0b01010,
|
||||||
|
0b01010,
|
||||||
|
0b01010,
|
||||||
|
0b01010,
|
||||||
|
0b10001,
|
||||||
|
0b10001,
|
||||||
|
0b01110,
|
||||||
|
# Heated bed
|
||||||
|
0b00000,
|
||||||
|
0b11111,
|
||||||
|
0b10101,
|
||||||
|
0b10001,
|
||||||
|
0b10101,
|
||||||
|
0b11111,
|
||||||
|
0b00000,
|
||||||
|
0b00000,
|
||||||
|
# Speed factor
|
||||||
|
0b11100,
|
||||||
|
0b10000,
|
||||||
|
0b11000,
|
||||||
|
0b10111,
|
||||||
|
0b00101,
|
||||||
|
0b00110,
|
||||||
|
0b00101,
|
||||||
|
0b00000,
|
||||||
|
# Clock
|
||||||
|
0b00000,
|
||||||
|
0b01110,
|
||||||
|
0b10011,
|
||||||
|
0b10101,
|
||||||
|
0b10001,
|
||||||
|
0b01110,
|
||||||
|
0b00000,
|
||||||
|
0b00000,
|
||||||
|
# Degrees
|
||||||
|
0b01100,
|
||||||
|
0b10010,
|
||||||
|
0b10010,
|
||||||
|
0b01100,
|
||||||
|
0b00000,
|
||||||
|
0b00000,
|
||||||
|
0b00000,
|
||||||
|
0b00000,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# ST7920 (128x64 graphics) lcd chip
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
ST7920_DELAY = .000020 # Spec says 72us, but faster is possible in practice
|
||||||
|
|
||||||
|
class ST7920:
|
||||||
|
char_right_arrow = '\x1a'
|
||||||
|
def __init__(self, config):
|
||||||
|
printer = config.get_printer()
|
||||||
|
# pin config
|
||||||
|
ppins = printer.lookup_object('pins')
|
||||||
|
pins = [ppins.lookup_pin('digital_out', config.get(name + '_pin'))
|
||||||
|
for name in ['cs', 'sclk', 'sid']]
|
||||||
|
mcu = None
|
||||||
|
for pin_params in pins:
|
||||||
|
if mcu is not None and pin_params['chip'] != mcu:
|
||||||
|
raise ppins.error("st7920 all pins must be on same mcu")
|
||||||
|
mcu = pin_params['chip']
|
||||||
|
if pin_params['invert']:
|
||||||
|
raise ppins.error("st7920 can not invert pin")
|
||||||
|
self.pins = [pin_params['pin'] for pin_params in pins]
|
||||||
|
self.mcu = mcu
|
||||||
|
self.oid = self.mcu.create_oid()
|
||||||
|
self.mcu.add_config_object(self)
|
||||||
|
self.send_data_cmd = self.send_cmds_cmd = None
|
||||||
|
self.is_extended = False
|
||||||
|
# framebuffers
|
||||||
|
self.text_framebuffer = (bytearray(' '*64), bytearray('~'*64), 0x80)
|
||||||
|
self.glyph_framebuffer = (bytearray(128), bytearray('~'*128), 0x40)
|
||||||
|
self.graphics_framebuffers = [(bytearray(32), bytearray('~'*32), i)
|
||||||
|
for i in range(32)]
|
||||||
|
self.framebuffers = ([self.text_framebuffer, self.glyph_framebuffer]
|
||||||
|
+ self.graphics_framebuffers)
|
||||||
|
def build_config(self):
|
||||||
|
self.mcu.add_config_cmd(
|
||||||
|
"config_st7920 oid=%u cs_pin=%s sclk_pin=%s sid_pin=%s"
|
||||||
|
" delay_ticks=%d" % (
|
||||||
|
self.oid, self.pins[0], self.pins[1], self.pins[2],
|
||||||
|
self.mcu.seconds_to_clock(ST7920_DELAY)))
|
||||||
|
cmd_queue = self.mcu.alloc_command_queue()
|
||||||
|
self.send_cmds_cmd = self.mcu.lookup_command(
|
||||||
|
"st7920_send_cmds oid=%c cmds=%*s", cq=cmd_queue)
|
||||||
|
self.send_data_cmd = self.mcu.lookup_command(
|
||||||
|
"st7920_send_data oid=%c data=%*s", cq=cmd_queue)
|
||||||
|
def send(self, cmds, is_data=False, is_extended=False):
|
||||||
|
cmd_type = self.send_cmds_cmd
|
||||||
|
if is_data:
|
||||||
|
cmd_type = self.send_data_cmd
|
||||||
|
elif self.is_extended != is_extended:
|
||||||
|
add_cmd = 0x22
|
||||||
|
if is_extended:
|
||||||
|
add_cmd = 0x26
|
||||||
|
cmds = [add_cmd] + cmds
|
||||||
|
self.is_extended = is_extended
|
||||||
|
cmd_type.send([self.oid, cmds], reqclock=BACKGROUND_PRIORITY_CLOCK)
|
||||||
|
#logging.debug("st7920 %d %s", is_data, repr(cmds))
|
||||||
|
def flush(self):
|
||||||
|
# Find all differences in the framebuffers and send them to the chip
|
||||||
|
for new_data, old_data, fb_id in self.framebuffers:
|
||||||
|
if new_data == old_data:
|
||||||
|
continue
|
||||||
|
# Find the position of all changed bytes in this framebuffer
|
||||||
|
diffs = [[i, 1] for i, (nd, od) in enumerate(zip(new_data, old_data))
|
||||||
|
if nd != od]
|
||||||
|
# Batch together changes that are close to each other
|
||||||
|
for i in range(len(diffs)-2, -1, -1):
|
||||||
|
pos, count = diffs[i]
|
||||||
|
nextpos, nextcount = diffs[i+1]
|
||||||
|
if pos + 5 >= nextpos and nextcount < 16:
|
||||||
|
diffs[i][1] = nextcount + (nextpos - pos)
|
||||||
|
del diffs[i+1]
|
||||||
|
# Transmit changes
|
||||||
|
for pos, count in diffs:
|
||||||
|
count += pos & 0x01
|
||||||
|
count += count & 0x01
|
||||||
|
pos = pos & ~0x01
|
||||||
|
chip_pos = pos >> 1
|
||||||
|
if fb_id < 0x40:
|
||||||
|
# Graphics framebuffer update
|
||||||
|
self.send([0x80 + fb_id, 0x80 + chip_pos], is_extended=True)
|
||||||
|
else:
|
||||||
|
self.send([fb_id + chip_pos])
|
||||||
|
self.send(new_data[pos:pos+count], is_data=True)
|
||||||
|
old_data[:] = new_data
|
||||||
|
def init(self):
|
||||||
|
cmds = [0x24, # Enter extended mode
|
||||||
|
0x40, # Clear vertical scroll address
|
||||||
|
0x02, # Enable CGRAM access
|
||||||
|
0x26, # Enable graphics
|
||||||
|
0x22, # Leave extended mode
|
||||||
|
0x02, # Home the display
|
||||||
|
0x06, # Set positive update direction
|
||||||
|
0x0c] # Enable display and hide cursor
|
||||||
|
self.send(cmds)
|
||||||
|
self.flush()
|
||||||
|
def load_glyph(self, glyph_id, data):
|
||||||
|
if len(data) > 32:
|
||||||
|
data = data[:32]
|
||||||
|
pos = min(glyph_id * 32, 96)
|
||||||
|
self.glyph_framebuffer[0][pos:pos+len(data)] = data
|
||||||
|
def write_text(self, x, y, data):
|
||||||
|
if x + len(data) > 16:
|
||||||
|
data = data[:16 - min(x, 16)]
|
||||||
|
pos = [0, 32, 16, 48][y] + x
|
||||||
|
self.text_framebuffer[0][pos:pos+len(data)] = data
|
||||||
|
def write_graphics(self, x, y, row, data):
|
||||||
|
if x + len(data) > 16:
|
||||||
|
data = data[:16 - min(x, 16)]
|
||||||
|
gfx_fb = y * 16 + row
|
||||||
|
if gfx_fb >= 32:
|
||||||
|
gfx_fb -= 32
|
||||||
|
x += 16
|
||||||
|
self.graphics_framebuffers[gfx_fb][0][x:x+len(data)] = data
|
||||||
|
def clear(self):
|
||||||
|
self.text_framebuffer[0][:] = ' '*64
|
||||||
|
zeros = bytearray(32)
|
||||||
|
for new_data, old_data, fb_id in self.graphics_framebuffers:
|
||||||
|
new_data[:] = zeros
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Icons
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
nozzle_icon = [
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000111111110000,
|
||||||
|
0b0001111111111000,
|
||||||
|
0b0001111111111000,
|
||||||
|
0b0001111111111000,
|
||||||
|
0b0000111111110000,
|
||||||
|
0b0000111111110000,
|
||||||
|
0b0001111111111000,
|
||||||
|
0b0001111111111000,
|
||||||
|
0b0001111111111000,
|
||||||
|
0b0000011111100000,
|
||||||
|
0b0000001111000000,
|
||||||
|
0b0000000110000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000
|
||||||
|
]
|
||||||
|
|
||||||
|
bed_icon = [
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0111111111111110,
|
||||||
|
0b0111111111111110,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000
|
||||||
|
]
|
||||||
|
|
||||||
|
heat1_icon = [
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0010001000100000,
|
||||||
|
0b0001000100010000,
|
||||||
|
0b0000100010001000,
|
||||||
|
0b0000100010001000,
|
||||||
|
0b0001000100010000,
|
||||||
|
0b0010001000100000,
|
||||||
|
0b0010001000100000,
|
||||||
|
0b0001000100010000,
|
||||||
|
0b0000100010001000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000
|
||||||
|
]
|
||||||
|
|
||||||
|
heat2_icon = [
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000100010001000,
|
||||||
|
0b0000100010001000,
|
||||||
|
0b0001000100010000,
|
||||||
|
0b0010001000100000,
|
||||||
|
0b0010001000100000,
|
||||||
|
0b0001000100010000,
|
||||||
|
0b0000100010001000,
|
||||||
|
0b0000100010001000,
|
||||||
|
0b0001000100010000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000
|
||||||
|
]
|
||||||
|
|
||||||
|
fan1_icon = [
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0111111111111110,
|
||||||
|
0b0111000000001110,
|
||||||
|
0b0110001111000110,
|
||||||
|
0b0100001111000010,
|
||||||
|
0b0100000110000010,
|
||||||
|
0b0101100000011010,
|
||||||
|
0b0101110110111010,
|
||||||
|
0b0101100000011010,
|
||||||
|
0b0100000110000010,
|
||||||
|
0b0100001111000010,
|
||||||
|
0b0110001111000110,
|
||||||
|
0b0111000000001110,
|
||||||
|
0b0111111111111110,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000
|
||||||
|
]
|
||||||
|
|
||||||
|
fan2_icon = [
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0111111111111110,
|
||||||
|
0b0111000000001110,
|
||||||
|
0b0110010000100110,
|
||||||
|
0b0100111001110010,
|
||||||
|
0b0101111001111010,
|
||||||
|
0b0100110000110010,
|
||||||
|
0b0100000110000010,
|
||||||
|
0b0100110000110010,
|
||||||
|
0b0101111001111010,
|
||||||
|
0b0100111001110010,
|
||||||
|
0b0110010000100110,
|
||||||
|
0b0111000000001110,
|
||||||
|
0b0111111111111110,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000
|
||||||
|
]
|
||||||
|
|
||||||
|
feedrate_icon = [
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0111111000000000,
|
||||||
|
0b0100000000000000,
|
||||||
|
0b0100000000000000,
|
||||||
|
0b0100000000000000,
|
||||||
|
0b0111111011111000,
|
||||||
|
0b0100000010000100,
|
||||||
|
0b0100000010000100,
|
||||||
|
0b0100000010000100,
|
||||||
|
0b0100000011111000,
|
||||||
|
0b0000000010001000,
|
||||||
|
0b0000000010000100,
|
||||||
|
0b0000000010000100,
|
||||||
|
0b0000000010000010,
|
||||||
|
0b0000000000000000,
|
||||||
|
0b0000000000000000
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# LCD screen updates
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
LCD_chips = { 'st7920': ST7920, 'hd44780': HD44780 }
|
||||||
|
|
||||||
|
class PrinterLCD:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.reactor = self.printer.get_reactor()
|
||||||
|
self.lcd_chip = config.getchoice('lcd_type', LCD_chips)(config)
|
||||||
|
self.lcd_type = config.get('lcd_type')
|
||||||
|
# printer objects
|
||||||
|
self.gcode = self.toolhead = self.sdcard = None
|
||||||
|
self.fan = self.extruder0 = self.extruder1 = self.heater_bed = None
|
||||||
|
# screen updating
|
||||||
|
self.screen_update_timer = self.reactor.register_timer(
|
||||||
|
self.screen_update_event)
|
||||||
|
# Initialization
|
||||||
|
FAN1_GLYPH, FAN2_GLYPH, BED1_GLYPH, BED2_GLYPH = 0, 1, 2, 3
|
||||||
|
def printer_state(self, state):
|
||||||
|
if state == 'ready':
|
||||||
|
self.lcd_chip.init()
|
||||||
|
# Load printer objects
|
||||||
|
self.gcode = self.printer.lookup_object('gcode')
|
||||||
|
self.toolhead = self.printer.lookup_object('toolhead')
|
||||||
|
self.sdcard = self.printer.lookup_object('virtual_sdcard', None)
|
||||||
|
self.fan = self.printer.lookup_object('fan', None)
|
||||||
|
self.extruder0 = self.printer.lookup_object('extruder0', None)
|
||||||
|
self.extruder1 = self.printer.lookup_object('extruder1', None)
|
||||||
|
self.heater_bed = self.printer.lookup_object('heater_bed', None)
|
||||||
|
# Load glyphs
|
||||||
|
self.load_glyph(self.BED1_GLYPH, heat1_icon)
|
||||||
|
self.load_glyph(self.BED2_GLYPH, heat2_icon)
|
||||||
|
self.load_glyph(self.FAN1_GLYPH, fan1_icon)
|
||||||
|
self.load_glyph(self.FAN2_GLYPH, fan2_icon)
|
||||||
|
# Start screen update timer
|
||||||
|
self.reactor.update_timer(self.screen_update_timer, self.reactor.NOW)
|
||||||
|
# ST7920 Glyphs
|
||||||
|
def load_glyph(self, glyph_id, data):
|
||||||
|
if self.lcd_type != 'st7920':
|
||||||
|
return
|
||||||
|
glyph = [0x00] * (len(data) * 2)
|
||||||
|
for i, bits in enumerate(data):
|
||||||
|
glyph[i*2] = (bits >> 8) & 0xff
|
||||||
|
glyph[i*2 + 1] = bits & 0xff
|
||||||
|
return self.lcd_chip.load_glyph(glyph_id, glyph)
|
||||||
|
def animate_glyphs(self, eventtime, x, y, glyph_id, do_animate):
|
||||||
|
frame = do_animate and int(eventtime) & 1
|
||||||
|
self.lcd_chip.write_text(x, y, (0, (glyph_id + frame)*2))
|
||||||
|
# Graphics drawing
|
||||||
|
def draw_icon(self, x, y, data):
|
||||||
|
for i, bits in enumerate(data):
|
||||||
|
self.lcd_chip.write_graphics(
|
||||||
|
x, y, i, [(bits >> 8) & 0xff, bits & 0xff])
|
||||||
|
def draw_progress_bar(self, x, y, width, value):
|
||||||
|
value = int(value * 100.)
|
||||||
|
data = [0x00] * width
|
||||||
|
char_pcnt = int(100/width)
|
||||||
|
for i in range(width):
|
||||||
|
if (i+1)*char_pcnt <= value:
|
||||||
|
# Draw completely filled bytes
|
||||||
|
data[i] |= 0xFF
|
||||||
|
elif (i*char_pcnt) < value:
|
||||||
|
# Draw partially filled bytes
|
||||||
|
data[i] |= (-1 << 8-((value % char_pcnt)*8/char_pcnt)) & 0xff
|
||||||
|
data[0] |= 0x80
|
||||||
|
data[-1] |= 0x01
|
||||||
|
self.lcd_chip.write_graphics(x, y, 0, [0xff]*width)
|
||||||
|
for i in range(1, 15):
|
||||||
|
self.lcd_chip.write_graphics(x, y, i, data)
|
||||||
|
self.lcd_chip.write_graphics(x, y, 15, [0xff]*width)
|
||||||
|
# Screen updating
|
||||||
|
def screen_update_event(self, eventtime):
|
||||||
|
self.lcd_chip.clear()
|
||||||
|
if self.lcd_type == 'hd44780':
|
||||||
|
self.screen_update_hd44780(eventtime)
|
||||||
|
else:
|
||||||
|
self.screen_update_st7920(eventtime)
|
||||||
|
self.lcd_chip.flush()
|
||||||
|
return eventtime + .500
|
||||||
|
def screen_update_hd44780(self, eventtime):
|
||||||
|
lcd_chip = self.lcd_chip
|
||||||
|
# Heaters
|
||||||
|
if self.extruder0 is not None:
|
||||||
|
info = self.extruder0.get_heater().get_status(eventtime)
|
||||||
|
lcd_chip.write_text(0, 0, lcd_chip.char_thermometer)
|
||||||
|
self.draw_heater(1, 0, info)
|
||||||
|
if self.extruder1 is not None:
|
||||||
|
info = self.extruder1.get_heater().get_status(eventtime)
|
||||||
|
lcd_chip.write_text(0, 1, lcd_chip.char_thermometer)
|
||||||
|
self.draw_heater(1, 1, info)
|
||||||
|
if self.heater_bed is not None:
|
||||||
|
info = self.heater_bed.get_status(eventtime)
|
||||||
|
lcd_chip.write_text(10, 0, lcd_chip.char_heater_bed)
|
||||||
|
self.draw_heater(11, 0, info)
|
||||||
|
# Fan speed
|
||||||
|
if self.fan is not None:
|
||||||
|
info = self.fan.get_status(eventtime)
|
||||||
|
lcd_chip.write_text(10, 1, "Fan")
|
||||||
|
self.draw_percent(14, 1, 4, info['speed'])
|
||||||
|
# G-Code speed factor
|
||||||
|
gcode_info = self.gcode.get_status(eventtime)
|
||||||
|
lcd_chip.write_text(0, 2, lcd_chip.char_speed_factor)
|
||||||
|
self.draw_percent(1, 2, 4, gcode_info['speed_factor'])
|
||||||
|
# SD card print progress
|
||||||
|
if self.sdcard is not None:
|
||||||
|
info = self.sdcard.get_status(eventtime)
|
||||||
|
lcd_chip.write_text(7, 2, "SD")
|
||||||
|
self.draw_percent(9, 2, 4, info['progress'])
|
||||||
|
# Printing time and status
|
||||||
|
toolhead_info = self.toolhead.get_status(eventtime)
|
||||||
|
lcd_chip.write_text(14, 2, lcd_chip.char_clock)
|
||||||
|
self.draw_time(15, 2, toolhead_info['printing_time'])
|
||||||
|
self.draw_status(0, 3, gcode_info, toolhead_info)
|
||||||
|
def screen_update_st7920(self, eventtime):
|
||||||
|
# Heaters
|
||||||
|
if self.extruder0 is not None:
|
||||||
|
info = self.extruder0.get_heater().get_status(eventtime)
|
||||||
|
self.draw_icon(0, 0, nozzle_icon)
|
||||||
|
self.draw_heater(2, 0, info)
|
||||||
|
extruder_count = 1
|
||||||
|
if self.extruder1 is not None:
|
||||||
|
info = self.extruder1.get_heater().get_status(eventtime)
|
||||||
|
self.draw_icon(0, 1, nozzle_icon)
|
||||||
|
self.draw_heater(2, 1, info)
|
||||||
|
extruder_count = 2
|
||||||
|
if self.heater_bed is not None:
|
||||||
|
info = self.heater_bed.get_status(eventtime)
|
||||||
|
self.draw_icon(0, extruder_count, bed_icon)
|
||||||
|
if info['target']:
|
||||||
|
self.animate_glyphs(eventtime, 0, extruder_count,
|
||||||
|
self.BED1_GLYPH, True)
|
||||||
|
self.draw_heater(2, extruder_count, info)
|
||||||
|
# Fan speed
|
||||||
|
if self.fan is not None:
|
||||||
|
info = self.fan.get_status(eventtime)
|
||||||
|
self.animate_glyphs(eventtime, 10, 0, self.FAN1_GLYPH,
|
||||||
|
info['speed'] != 0.)
|
||||||
|
self.draw_percent(12, 0, 4, info['speed'])
|
||||||
|
# SD card print progress
|
||||||
|
if self.sdcard is not None:
|
||||||
|
info = self.sdcard.get_status(eventtime)
|
||||||
|
if extruder_count == 1:
|
||||||
|
x, y, width = 0, 2, 10
|
||||||
|
else:
|
||||||
|
x, y, width = 10, 1, 6
|
||||||
|
self.draw_percent(x, y, width, info['progress'])
|
||||||
|
self.draw_progress_bar(x, y, width, info['progress'])
|
||||||
|
# G-Code speed factor
|
||||||
|
gcode_info = self.gcode.get_status(eventtime)
|
||||||
|
if extruder_count == 1:
|
||||||
|
self.draw_icon(10, 1, feedrate_icon)
|
||||||
|
self.draw_percent(12, 1, 4, gcode_info['speed_factor'])
|
||||||
|
# Printing time and status
|
||||||
|
toolhead_info = self.toolhead.get_status(eventtime)
|
||||||
|
self.draw_time(10, 2, toolhead_info['printing_time'])
|
||||||
|
self.draw_status(0, 3, gcode_info, toolhead_info)
|
||||||
|
# Screen update helpers
|
||||||
|
def draw_heater(self, x, y, info):
|
||||||
|
temperature, target = info['temperature'], info['target']
|
||||||
|
if target and abs(temperature - target) > 2.:
|
||||||
|
s = "%3.0f%s%.0f" % (
|
||||||
|
temperature, self.lcd_chip.char_right_arrow, target)
|
||||||
|
else:
|
||||||
|
s = "%3.0f" % (temperature,)
|
||||||
|
if self.lcd_type == 'hd44780':
|
||||||
|
s += self.lcd_chip.char_degrees
|
||||||
|
self.lcd_chip.write_text(x, y, s)
|
||||||
|
def draw_percent(self, x, y, width, value):
|
||||||
|
self.lcd_chip.write_text(x, y, ("%d%%" % (value * 100.,)).center(width))
|
||||||
|
def draw_time(self, x, y, seconds):
|
||||||
|
seconds = int(seconds)
|
||||||
|
self.lcd_chip.write_text(x, y, "%02d:%02d" % (
|
||||||
|
seconds // (60 * 60), (seconds // 60) % 60))
|
||||||
|
def draw_status(self, x, y, gcode_info, toolhead_info):
|
||||||
|
status = toolhead_info['status']
|
||||||
|
if status == 'Printing' or gcode_info['busy']:
|
||||||
|
pos = self.toolhead.get_position()
|
||||||
|
status = "X%-4.0fY%-4.0fZ%-5.2f" % (pos[0], pos[1], pos[2])
|
||||||
|
self.lcd_chip.write_text(x, y, status)
|
||||||
|
|
||||||
|
def load_config(config):
|
||||||
|
return PrinterLCD(config)
|
||||||
39
klippy/extras/fan.py
Normal file
39
klippy/extras/fan.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Printer cooling fan
|
||||||
|
#
|
||||||
|
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import pins
|
||||||
|
|
||||||
|
FAN_MIN_TIME = 0.100
|
||||||
|
|
||||||
|
class PrinterFan:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.last_fan_value = 0.
|
||||||
|
self.last_fan_time = 0.
|
||||||
|
self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.)
|
||||||
|
self.kick_start_time = config.getfloat('kick_start_time', 0.1, minval=0.)
|
||||||
|
printer = config.get_printer()
|
||||||
|
self.mcu_fan = pins.setup_pin(printer, 'pwm', config.get('pin'))
|
||||||
|
self.mcu_fan.setup_max_duration(0.)
|
||||||
|
cycle_time = config.getfloat('cycle_time', 0.010, above=0.)
|
||||||
|
hardware_pwm = config.getboolean('hardware_pwm', False)
|
||||||
|
self.mcu_fan.setup_cycle_time(cycle_time, hardware_pwm)
|
||||||
|
def set_speed(self, print_time, value):
|
||||||
|
value = max(0., min(self.max_power, value))
|
||||||
|
if value == self.last_fan_value:
|
||||||
|
return
|
||||||
|
print_time = max(self.last_fan_time + FAN_MIN_TIME, print_time)
|
||||||
|
if (value and value < self.max_power
|
||||||
|
and not self.last_fan_value and self.kick_start_time):
|
||||||
|
# Run fan at full speed for specified kick_start_time
|
||||||
|
self.mcu_fan.set_pwm(print_time, self.max_power)
|
||||||
|
print_time += self.kick_start_time
|
||||||
|
self.mcu_fan.set_pwm(print_time, value)
|
||||||
|
self.last_fan_time = print_time
|
||||||
|
self.last_fan_value = value
|
||||||
|
def get_status(self, eventtime):
|
||||||
|
return {'speed': self.last_fan_value}
|
||||||
|
|
||||||
|
def load_config(config):
|
||||||
|
return PrinterFan(config)
|
||||||
37
klippy/extras/heater_fan.py
Normal file
37
klippy/extras/heater_fan.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Support fans that are enabled when a heater is on
|
||||||
|
#
|
||||||
|
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import fan, extruder
|
||||||
|
|
||||||
|
PIN_MIN_TIME = 0.100
|
||||||
|
|
||||||
|
class PrinterHeaterFan:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.heater_name = config.get("heater", "extruder0")
|
||||||
|
self.heater_temp = config.getfloat("heater_temp", 50.0)
|
||||||
|
self.fan = fan.PrinterFan(config)
|
||||||
|
self.mcu = self.fan.mcu_fan.get_mcu()
|
||||||
|
max_power = self.fan.max_power
|
||||||
|
self.fan_speed = config.getfloat(
|
||||||
|
"fan_speed", max_power, minval=0., maxval=max_power)
|
||||||
|
self.fan.mcu_fan.setup_start_value(0., max_power)
|
||||||
|
def printer_state(self, state):
|
||||||
|
if state == 'ready':
|
||||||
|
self.heater = extruder.get_printer_heater(
|
||||||
|
self.printer, self.heater_name)
|
||||||
|
reactor = self.printer.get_reactor()
|
||||||
|
reactor.register_timer(self.callback, reactor.NOW)
|
||||||
|
def callback(self, eventtime):
|
||||||
|
current_temp, target_temp = self.heater.get_temp(eventtime)
|
||||||
|
power = 0.
|
||||||
|
if target_temp or current_temp > self.heater_temp:
|
||||||
|
power = self.fan_speed
|
||||||
|
print_time = self.mcu.estimated_print_time(eventtime) + PIN_MIN_TIME
|
||||||
|
self.fan.set_speed(print_time, power)
|
||||||
|
return eventtime + 1.
|
||||||
|
|
||||||
|
def load_config_prefix(config):
|
||||||
|
return PrinterHeaterFan(config)
|
||||||
39
klippy/extras/homing_override.py
Normal file
39
klippy/extras/homing_override.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Run user defined actions in place of a normal G28 homing command
|
||||||
|
#
|
||||||
|
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
|
class HomingOverride:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.start_pos = [config.getfloat('set_position_' + a, None)
|
||||||
|
for a in 'xyz']
|
||||||
|
self.script = config.get('gcode')
|
||||||
|
self.in_script = False
|
||||||
|
self.gcode = self.printer.lookup_object('gcode')
|
||||||
|
self.gcode.register_command("G28", self.cmd_G28)
|
||||||
|
def cmd_G28(self, params):
|
||||||
|
if self.in_script:
|
||||||
|
# Was called recursively - invoke the real G28 command
|
||||||
|
self.gcode.cmd_G28(params)
|
||||||
|
return
|
||||||
|
# Calculate forced position (if configured)
|
||||||
|
toolhead = self.printer.lookup_object('toolhead')
|
||||||
|
pos = toolhead.get_position()
|
||||||
|
homing_axes = []
|
||||||
|
for axis, loc in enumerate(self.start_pos):
|
||||||
|
if loc is not None:
|
||||||
|
pos[axis] = loc
|
||||||
|
homing_axes.append(axis)
|
||||||
|
toolhead.set_position(pos, homing_axes=homing_axes)
|
||||||
|
self.gcode.reset_last_position()
|
||||||
|
# Perform homing
|
||||||
|
try:
|
||||||
|
self.in_script = True
|
||||||
|
self.gcode.run_script(self.script)
|
||||||
|
finally:
|
||||||
|
self.in_script = False
|
||||||
|
|
||||||
|
def load_config(config):
|
||||||
|
return HomingOverride(config)
|
||||||
54
klippy/extras/multi_pin.py
Normal file
54
klippy/extras/multi_pin.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Virtual pin that propagates its changes to multiple output pins
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017,2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import pins
|
||||||
|
|
||||||
|
class PrinterMultiPin:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
try:
|
||||||
|
pins.get_printer_pins(self.printer).register_chip('multi_pin', self)
|
||||||
|
except pins.error:
|
||||||
|
pass
|
||||||
|
self.pin_type = None
|
||||||
|
self.pin_list = [pin.strip() for pin in config.get('pins').split(',')]
|
||||||
|
self.mcu_pins = []
|
||||||
|
def setup_pin(self, pin_params):
|
||||||
|
pin_name = pin_params['pin']
|
||||||
|
pin = self.printer.lookup_object('multi_pin ' + pin_name, None)
|
||||||
|
if pin is not self:
|
||||||
|
if pin is None:
|
||||||
|
raise pins.error("multi_pin %s not configured" % (pin_name,))
|
||||||
|
return pin.setup_pin(pin_params)
|
||||||
|
if self.pin_type is not None:
|
||||||
|
raise pins.error("Can't setup multi_pin %s twice" % (pin_name,))
|
||||||
|
self.pin_type = pin_params['type']
|
||||||
|
invert = ""
|
||||||
|
if pin_params['invert']:
|
||||||
|
invert = "!"
|
||||||
|
self.mcu_pins = [
|
||||||
|
pins.setup_pin(self.printer, self.pin_type, invert + pin_desc)
|
||||||
|
for pin_desc in self.pin_list]
|
||||||
|
return self
|
||||||
|
def get_mcu(self):
|
||||||
|
return self.mcu_pins[0].get_mcu()
|
||||||
|
def setup_max_duration(self, max_duration):
|
||||||
|
for mcu_pin in self.mcu_pins:
|
||||||
|
mcu_pin.setup_max_duration(max_duration)
|
||||||
|
def setup_start_value(self, start_value, shutdown_value):
|
||||||
|
for mcu_pin in self.mcu_pins:
|
||||||
|
mcu_pin.setup_start_value(start_value, shutdown_value)
|
||||||
|
def setup_cycle_time(self, cycle_time, hardware_pwm=False):
|
||||||
|
for mcu_pin in self.mcu_pins:
|
||||||
|
mcu_pin.setup_cycle_time(cycle_time, hardware_pwm)
|
||||||
|
def set_digital(self, print_time, value):
|
||||||
|
for mcu_pin in self.mcu_pins:
|
||||||
|
mcu_pin.set_digital(print_time, value)
|
||||||
|
def set_pwm(self, print_time, value):
|
||||||
|
for mcu_pin in self.mcu_pins:
|
||||||
|
mcu_pin.set_pwm(print_time, value)
|
||||||
|
|
||||||
|
def load_config_prefix(config):
|
||||||
|
return PrinterMultiPin(config)
|
||||||
69
klippy/extras/output_pin.py
Normal file
69
klippy/extras/output_pin.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Code to configure miscellaneous chips
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017,2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
|
PIN_MIN_TIME = 0.100
|
||||||
|
|
||||||
|
class PrinterOutputPin:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
ppins = self.printer.lookup_object('pins')
|
||||||
|
self.is_pwm = config.getboolean('pwm', False)
|
||||||
|
if self.is_pwm:
|
||||||
|
self.mcu_pin = ppins.setup_pin('pwm', config.get('pin'))
|
||||||
|
cycle_time = config.getfloat('cycle_time', 0.100, above=0.)
|
||||||
|
hardware_pwm = config.getboolean('hardware_pwm', False)
|
||||||
|
self.mcu_pin.setup_cycle_time(cycle_time, hardware_pwm)
|
||||||
|
self.scale = config.getfloat('scale', 1., above=0.)
|
||||||
|
else:
|
||||||
|
self.mcu_pin = ppins.setup_pin('digital_out', config.get('pin'))
|
||||||
|
self.scale = 1.
|
||||||
|
self.mcu_pin.setup_max_duration(0.)
|
||||||
|
self.last_value_time = 0.
|
||||||
|
static_value = config.getfloat('static_value', None,
|
||||||
|
minval=0., maxval=self.scale)
|
||||||
|
if static_value is not None:
|
||||||
|
self.is_static = True
|
||||||
|
self.last_value = static_value / self.scale
|
||||||
|
self.mcu_pin.setup_start_value(
|
||||||
|
self.last_value, self.last_value, True)
|
||||||
|
else:
|
||||||
|
self.is_static = False
|
||||||
|
self.last_value = config.getfloat(
|
||||||
|
'value', 0., minval=0., maxval=self.scale) / self.scale
|
||||||
|
shutdown_value = config.getfloat(
|
||||||
|
'shutdown_value', 0., minval=0., maxval=self.scale) / self.scale
|
||||||
|
self.mcu_pin.setup_start_value(self.last_value, shutdown_value)
|
||||||
|
self.gcode = self.printer.lookup_object('gcode')
|
||||||
|
self.gcode.register_command("SET_PIN", self.cmd_SET_PIN,
|
||||||
|
desc=self.cmd_SET_PIN_help)
|
||||||
|
cmd_SET_PIN_help = "Set the value of an output pin"
|
||||||
|
def cmd_SET_PIN(self, params):
|
||||||
|
pin_name = self.gcode.get_str('PIN', params)
|
||||||
|
pin = self.printer.lookup_object('output_pin ' + pin_name, None)
|
||||||
|
if pin is not self:
|
||||||
|
if pin is None:
|
||||||
|
raise self.gcode.error("Pin not configured")
|
||||||
|
return pin.cmd_SET_PIN(params)
|
||||||
|
if self.is_static:
|
||||||
|
raise self.gcode.error("Static pin can not be changed at run-time")
|
||||||
|
value = self.gcode.get_float('VALUE', params) / self.scale
|
||||||
|
if value == self.last_value:
|
||||||
|
return
|
||||||
|
print_time = self.printer.lookup_object('toolhead').get_last_move_time()
|
||||||
|
print_time = max(print_time, self.last_value_time + PIN_MIN_TIME)
|
||||||
|
if self.is_pwm:
|
||||||
|
if value < 0. or value > 1.:
|
||||||
|
raise self.gcode.error("Invalid pin value")
|
||||||
|
self.mcu_pin.set_pwm(print_time, value)
|
||||||
|
else:
|
||||||
|
if value not in [0., 1.]:
|
||||||
|
raise self.gcode.error("Invalid pin value")
|
||||||
|
self.mcu_pin.set_digital(print_time, value)
|
||||||
|
self.last_value = value
|
||||||
|
self.last_value_time = print_time
|
||||||
|
|
||||||
|
def load_config_prefix(config):
|
||||||
|
return PrinterOutputPin(config)
|
||||||
127
klippy/extras/pid_calibrate.py
Normal file
127
klippy/extras/pid_calibrate.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# Calibration of heater PID settings
|
||||||
|
#
|
||||||
|
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import math, logging
|
||||||
|
import extruder, heater
|
||||||
|
|
||||||
|
class PIDCalibrate:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.gcode = self.printer.lookup_object('gcode')
|
||||||
|
self.gcode.register_command(
|
||||||
|
'PID_CALIBRATE', self.cmd_PID_CALIBRATE,
|
||||||
|
desc=self.cmd_PID_CALIBRATE_help)
|
||||||
|
cmd_PID_CALIBRATE_help = "Run PID calibration test"
|
||||||
|
def cmd_PID_CALIBRATE(self, params):
|
||||||
|
heater_name = self.gcode.get_str('HEATER', params)
|
||||||
|
target = self.gcode.get_float('TARGET', params)
|
||||||
|
write_file = self.gcode.get_int('WRITE_FILE', params, 0)
|
||||||
|
try:
|
||||||
|
heater = extruder.get_printer_heater(self.printer, heater_name)
|
||||||
|
except self.printer.config_error as e:
|
||||||
|
raise self.gcode.error(str(e))
|
||||||
|
print_time = self.printer.lookup_object('toolhead').get_last_move_time()
|
||||||
|
calibrate = ControlAutoTune(heater)
|
||||||
|
old_control = heater.set_control(calibrate)
|
||||||
|
try:
|
||||||
|
heater.set_temp(print_time, target)
|
||||||
|
except heater.error as e:
|
||||||
|
raise self.gcode.error(str(e))
|
||||||
|
self.gcode.bg_temp(heater)
|
||||||
|
heater.set_control(old_control)
|
||||||
|
if write_file:
|
||||||
|
calibrate.write_file('/tmp/heattest.txt')
|
||||||
|
Kp, Ki, Kd = calibrate.calc_final_pid()
|
||||||
|
logging.info("Autotune: final: Kp=%f Ki=%f Kd=%f", Kp, Ki, Kd)
|
||||||
|
self.gcode.respond_info(
|
||||||
|
"PID parameters: pid_Kp=%.3f pid_Ki=%.3f pid_Kd=%.3f\n"
|
||||||
|
"To use these parameters, update the printer config file with\n"
|
||||||
|
"the above and then issue a RESTART command" % (Kp, Ki, Kd))
|
||||||
|
|
||||||
|
TUNE_PID_DELTA = 5.0
|
||||||
|
|
||||||
|
class ControlAutoTune:
|
||||||
|
def __init__(self, heater):
|
||||||
|
self.heater = heater
|
||||||
|
# Heating control
|
||||||
|
self.heating = False
|
||||||
|
self.peak = 0.
|
||||||
|
self.peak_time = 0.
|
||||||
|
# Peak recording
|
||||||
|
self.peaks = []
|
||||||
|
# Sample recording
|
||||||
|
self.last_pwm = 0.
|
||||||
|
self.pwm_samples = []
|
||||||
|
self.temp_samples = []
|
||||||
|
# Heater control
|
||||||
|
def set_pwm(self, read_time, value):
|
||||||
|
if value != self.last_pwm:
|
||||||
|
self.pwm_samples.append((read_time + heater.PWM_DELAY, value))
|
||||||
|
self.last_pwm = value
|
||||||
|
self.heater.set_pwm(read_time, value)
|
||||||
|
def adc_callback(self, read_time, temp):
|
||||||
|
self.temp_samples.append((read_time, temp))
|
||||||
|
if self.heating and temp >= self.heater.target_temp:
|
||||||
|
self.heating = False
|
||||||
|
self.check_peaks()
|
||||||
|
elif (not self.heating
|
||||||
|
and temp <= self.heater.target_temp - TUNE_PID_DELTA):
|
||||||
|
self.heating = True
|
||||||
|
self.check_peaks()
|
||||||
|
if self.heating:
|
||||||
|
self.set_pwm(read_time, self.heater.max_power)
|
||||||
|
if temp < self.peak:
|
||||||
|
self.peak = temp
|
||||||
|
self.peak_time = read_time
|
||||||
|
else:
|
||||||
|
self.set_pwm(read_time, 0.)
|
||||||
|
if temp > self.peak:
|
||||||
|
self.peak = temp
|
||||||
|
self.peak_time = read_time
|
||||||
|
def check_busy(self, eventtime):
|
||||||
|
if self.heating or len(self.peaks) < 12:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
# Analysis
|
||||||
|
def check_peaks(self):
|
||||||
|
self.peaks.append((self.peak, self.peak_time))
|
||||||
|
if self.heating:
|
||||||
|
self.peak = 9999999.
|
||||||
|
else:
|
||||||
|
self.peak = -9999999.
|
||||||
|
if len(self.peaks) < 4:
|
||||||
|
return
|
||||||
|
self.calc_pid(len(self.peaks)-1)
|
||||||
|
def calc_pid(self, pos):
|
||||||
|
temp_diff = self.peaks[pos][0] - self.peaks[pos-1][0]
|
||||||
|
time_diff = self.peaks[pos][1] - self.peaks[pos-2][1]
|
||||||
|
max_power = self.heater.max_power
|
||||||
|
Ku = 4. * (2. * max_power) / (abs(temp_diff) * math.pi)
|
||||||
|
Tu = time_diff
|
||||||
|
|
||||||
|
Ti = 0.5 * Tu
|
||||||
|
Td = 0.125 * Tu
|
||||||
|
Kp = 0.6 * Ku * heater.PID_PARAM_BASE
|
||||||
|
Ki = Kp / Ti
|
||||||
|
Kd = Kp * Td
|
||||||
|
logging.info("Autotune: raw=%f/%f Ku=%f Tu=%f Kp=%f Ki=%f Kd=%f",
|
||||||
|
temp_diff, max_power, Ku, Tu, Kp, Ki, Kd)
|
||||||
|
return Kp, Ki, Kd
|
||||||
|
def calc_final_pid(self):
|
||||||
|
cycle_times = [(self.peaks[pos][1] - self.peaks[pos-2][1], pos)
|
||||||
|
for pos in range(4, len(self.peaks))]
|
||||||
|
midpoint_pos = sorted(cycle_times)[len(cycle_times)/2][1]
|
||||||
|
return self.calc_pid(midpoint_pos)
|
||||||
|
# Offline analysis helper
|
||||||
|
def write_file(self, filename):
|
||||||
|
pwm = ["pwm: %.3f %.3f" % (time, value)
|
||||||
|
for time, value in self.pwm_samples]
|
||||||
|
out = ["%.3f %.3f" % (time, temp) for time, temp in self.temp_samples]
|
||||||
|
f = open(filename, "wb")
|
||||||
|
f.write('\n'.join(pwm + out))
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def load_config(config):
|
||||||
|
return PIDCalibrate(config)
|
||||||
189
klippy/extras/probe.py
Normal file
189
klippy/extras/probe.py
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
# Z-Probe support
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import pins, homing
|
||||||
|
|
||||||
|
HINT_TIMEOUT = """
|
||||||
|
Make sure to home the printer before probing. If the probe
|
||||||
|
did not move far enough to trigger, then consider reducing
|
||||||
|
the Z axis minimum position so the probe can travel further
|
||||||
|
(the Z minimum position can be negative).
|
||||||
|
"""
|
||||||
|
|
||||||
|
class PrinterProbe:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.speed = config.getfloat('speed', 5.0)
|
||||||
|
self.z_offset = config.getfloat('z_offset')
|
||||||
|
# Infer Z position to move to during a probe
|
||||||
|
if config.has_section('stepper_z'):
|
||||||
|
zconfig = config.getsection('stepper_z')
|
||||||
|
self.z_position = zconfig.getfloat('position_min', 0.)
|
||||||
|
else:
|
||||||
|
pconfig = config.getsection('printer')
|
||||||
|
self.z_position = pconfig.getfloat('minimum_z_position', 0.)
|
||||||
|
# Create an "endstop" object to handle the probe pin
|
||||||
|
ppins = self.printer.lookup_object('pins')
|
||||||
|
pin_params = ppins.lookup_pin('endstop', config.get('pin'))
|
||||||
|
mcu = pin_params['chip']
|
||||||
|
mcu.add_config_object(self)
|
||||||
|
self.mcu_probe = mcu.setup_pin(pin_params)
|
||||||
|
if (config.get('activate_gcode', None) is not None or
|
||||||
|
config.get('deactivate_gcode', None) is not None):
|
||||||
|
self.mcu_probe = ProbeEndstopWrapper(config, self.mcu_probe)
|
||||||
|
# Create z_virtual_endstop pin
|
||||||
|
ppins.register_chip('probe', self)
|
||||||
|
self.z_virtual_endstop = None
|
||||||
|
# Register PROBE/QUERY_PROBE commands
|
||||||
|
self.gcode = self.printer.lookup_object('gcode')
|
||||||
|
self.gcode.register_command(
|
||||||
|
'PROBE', self.cmd_PROBE, desc=self.cmd_PROBE_help)
|
||||||
|
self.gcode.register_command(
|
||||||
|
'QUERY_PROBE', self.cmd_QUERY_PROBE, desc=self.cmd_QUERY_PROBE_help)
|
||||||
|
def build_config(self):
|
||||||
|
toolhead = self.printer.lookup_object('toolhead')
|
||||||
|
z_steppers = toolhead.get_kinematics().get_steppers("Z")
|
||||||
|
for s in z_steppers:
|
||||||
|
for mcu_endstop, name in s.get_endstops():
|
||||||
|
for mcu_stepper in mcu_endstop.get_steppers():
|
||||||
|
self.mcu_probe.add_stepper(mcu_stepper)
|
||||||
|
def setup_pin(self, pin_params):
|
||||||
|
if (pin_params['pin'] != 'z_virtual_endstop'
|
||||||
|
or pin_params['type'] != 'endstop'):
|
||||||
|
raise pins.error("Probe virtual endstop only useful as endstop pin")
|
||||||
|
if pin_params['invert'] or pin_params['pullup']:
|
||||||
|
raise pins.error("Can not pullup/invert probe virtual endstop")
|
||||||
|
self.z_virtual_endstop = ProbeVirtualEndstop(
|
||||||
|
self.printer, self.mcu_probe)
|
||||||
|
return self.z_virtual_endstop
|
||||||
|
def last_home_position(self):
|
||||||
|
if self.z_virtual_endstop is None:
|
||||||
|
return None
|
||||||
|
return self.z_virtual_endstop.position
|
||||||
|
cmd_PROBE_help = "Probe Z-height at current XY position"
|
||||||
|
def cmd_PROBE(self, params):
|
||||||
|
toolhead = self.printer.lookup_object('toolhead')
|
||||||
|
homing_state = homing.Homing(toolhead)
|
||||||
|
pos = toolhead.get_position()
|
||||||
|
pos[2] = self.z_position
|
||||||
|
try:
|
||||||
|
homing_state.homing_move(
|
||||||
|
pos, [(self.mcu_probe, "probe")], self.speed, probe_pos=True)
|
||||||
|
except homing.EndstopError as e:
|
||||||
|
reason = str(e)
|
||||||
|
if "Timeout during endstop homing" in reason:
|
||||||
|
reason += HINT_TIMEOUT
|
||||||
|
raise self.gcode.error(reason)
|
||||||
|
self.gcode.reset_last_position()
|
||||||
|
cmd_QUERY_PROBE_help = "Return the status of the z-probe"
|
||||||
|
def cmd_QUERY_PROBE(self, params):
|
||||||
|
toolhead = self.printer.lookup_object('toolhead')
|
||||||
|
print_time = toolhead.get_last_move_time()
|
||||||
|
self.mcu_probe.query_endstop(print_time)
|
||||||
|
res = self.mcu_probe.query_endstop_wait()
|
||||||
|
self.gcode.respond_info(
|
||||||
|
"probe: %s" % (["open", "TRIGGERED"][not not res],))
|
||||||
|
|
||||||
|
# Endstop wrapper that enables running g-code scripts on setup
|
||||||
|
class ProbeEndstopWrapper:
|
||||||
|
def __init__(self, config, mcu_endstop):
|
||||||
|
self.mcu_endstop = mcu_endstop
|
||||||
|
self.gcode = config.get_printer().lookup_object('gcode')
|
||||||
|
self.activate_gcode = config.get('activate_gcode', "")
|
||||||
|
self.deactivate_gcode = config.get('deactivate_gcode', "")
|
||||||
|
# Wrappers
|
||||||
|
self.get_mcu = self.mcu_endstop.get_mcu
|
||||||
|
self.add_stepper = self.mcu_endstop.add_stepper
|
||||||
|
self.get_steppers = self.mcu_endstop.get_steppers
|
||||||
|
self.home_start = self.mcu_endstop.home_start
|
||||||
|
self.home_wait = self.mcu_endstop.home_wait
|
||||||
|
self.query_endstop = self.mcu_endstop.query_endstop
|
||||||
|
self.query_endstop_wait = self.mcu_endstop.query_endstop_wait
|
||||||
|
self.TimeoutError = self.mcu_endstop.TimeoutError
|
||||||
|
def home_prepare(self):
|
||||||
|
self.gcode.run_script(self.activate_gcode)
|
||||||
|
self.mcu_endstop.home_prepare()
|
||||||
|
def home_finalize(self):
|
||||||
|
self.gcode.run_script(self.deactivate_gcode)
|
||||||
|
self.mcu_endstop.home_finalize()
|
||||||
|
|
||||||
|
# Wrapper that records the last XY position of a virtual endstop probe
|
||||||
|
class ProbeVirtualEndstop:
|
||||||
|
def __init__(self, printer, mcu_endstop):
|
||||||
|
self.printer = printer
|
||||||
|
self.mcu_endstop = mcu_endstop
|
||||||
|
self.position = None
|
||||||
|
# Wrappers
|
||||||
|
self.get_mcu = self.mcu_endstop.get_mcu
|
||||||
|
self.add_stepper = self.mcu_endstop.add_stepper
|
||||||
|
self.get_steppers = self.mcu_endstop.get_steppers
|
||||||
|
self.home_start = self.mcu_endstop.home_start
|
||||||
|
self.home_wait = self.mcu_endstop.home_wait
|
||||||
|
self.query_endstop = self.mcu_endstop.query_endstop
|
||||||
|
self.query_endstop_wait = self.mcu_endstop.query_endstop_wait
|
||||||
|
self.home_prepare = self.mcu_endstop.home_prepare
|
||||||
|
self.TimeoutError = self.mcu_endstop.TimeoutError
|
||||||
|
def home_finalize(self):
|
||||||
|
self.position = self.printer.lookup_object('toolhead').get_position()
|
||||||
|
self.mcu_endstop.home_finalize()
|
||||||
|
|
||||||
|
# Helper code that can probe a series of points and report the
|
||||||
|
# position at each point.
|
||||||
|
class ProbePointsHelper:
|
||||||
|
def __init__(self, printer, probe_points, horizontal_move_z, speed,
|
||||||
|
manual_probe, callback):
|
||||||
|
self.printer = printer
|
||||||
|
self.probe_points = probe_points
|
||||||
|
self.horizontal_move_z = horizontal_move_z
|
||||||
|
self.speed = speed
|
||||||
|
self.manual_probe = manual_probe
|
||||||
|
self.callback = callback
|
||||||
|
self.toolhead = self.printer.lookup_object('toolhead')
|
||||||
|
self.results = []
|
||||||
|
self.busy = True
|
||||||
|
self.gcode = self.printer.lookup_object('gcode')
|
||||||
|
self.gcode.register_command(
|
||||||
|
'NEXT', self.cmd_NEXT, desc=self.cmd_NEXT_help)
|
||||||
|
# Begin probing
|
||||||
|
self.move_next()
|
||||||
|
if not manual_probe:
|
||||||
|
while self.busy:
|
||||||
|
self.gcode.run_script("PROBE")
|
||||||
|
self.cmd_NEXT({})
|
||||||
|
def move_next(self):
|
||||||
|
x, y = self.probe_points[len(self.results)]
|
||||||
|
curpos = self.toolhead.get_position()
|
||||||
|
curpos[0] = x
|
||||||
|
curpos[1] = y
|
||||||
|
curpos[2] = self.horizontal_move_z
|
||||||
|
self.toolhead.move(curpos, self.speed)
|
||||||
|
self.gcode.reset_last_position()
|
||||||
|
cmd_NEXT_help = "Move to the next XY position to probe"
|
||||||
|
def cmd_NEXT(self, params):
|
||||||
|
# Record current position
|
||||||
|
self.toolhead.wait_moves()
|
||||||
|
self.results.append(self.callback.get_position())
|
||||||
|
# Move to next position
|
||||||
|
curpos = self.toolhead.get_position()
|
||||||
|
curpos[2] = self.horizontal_move_z
|
||||||
|
self.toolhead.move(curpos, self.speed)
|
||||||
|
if len(self.results) == len(self.probe_points):
|
||||||
|
self.toolhead.get_last_move_time()
|
||||||
|
self.finalize(True)
|
||||||
|
return
|
||||||
|
self.move_next()
|
||||||
|
def finalize(self, success):
|
||||||
|
self.busy = False
|
||||||
|
self.gcode.reset_last_position()
|
||||||
|
self.gcode.register_command('NEXT', None)
|
||||||
|
if success:
|
||||||
|
z_offset = 0.
|
||||||
|
if not self.manual_probe:
|
||||||
|
probe = self.printer.lookup_object('probe')
|
||||||
|
z_offset = probe.z_offset
|
||||||
|
self.callback.finalize(z_offset, self.results)
|
||||||
|
|
||||||
|
def load_config(config):
|
||||||
|
return PrinterProbe(config)
|
||||||
229
klippy/extras/replicape.py
Normal file
229
klippy/extras/replicape.py
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
# Code to configure miscellaneous chips
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017,2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import logging
|
||||||
|
import pins, mcu
|
||||||
|
|
||||||
|
REPLICAPE_MAX_CURRENT = 3.84
|
||||||
|
REPLICAPE_SHIFT_REGISTER_BUS = 1
|
||||||
|
REPLICAPE_SHIFT_REGISTER_DEVICE = 1
|
||||||
|
REPLICAPE_PCA9685_BUS = 2
|
||||||
|
REPLICAPE_PCA9685_ADDRESS = 0x70
|
||||||
|
REPLICAPE_PCA9685_CYCLE_TIME = .001
|
||||||
|
PIN_MIN_TIME = 0.100
|
||||||
|
|
||||||
|
class pca9685_pwm:
|
||||||
|
def __init__(self, replicape, channel, pin_params):
|
||||||
|
self._replicape = replicape
|
||||||
|
self._channel = channel
|
||||||
|
if pin_params['type'] not in ['digital_out', 'pwm']:
|
||||||
|
raise pins.error("Pin type not supported on replicape")
|
||||||
|
self._mcu = replicape.host_mcu
|
||||||
|
self._mcu.add_config_object(self)
|
||||||
|
self._bus = REPLICAPE_PCA9685_BUS
|
||||||
|
self._address = REPLICAPE_PCA9685_ADDRESS
|
||||||
|
self._cycle_time = REPLICAPE_PCA9685_CYCLE_TIME
|
||||||
|
self._max_duration = 2.
|
||||||
|
self._oid = None
|
||||||
|
self._invert = pin_params['invert']
|
||||||
|
self._start_value = self._shutdown_value = float(self._invert)
|
||||||
|
self._is_static = False
|
||||||
|
self._last_clock = 0
|
||||||
|
self._pwm_max = 0.
|
||||||
|
self._set_cmd = None
|
||||||
|
def get_mcu(self):
|
||||||
|
return self._mcu
|
||||||
|
def setup_max_duration(self, max_duration):
|
||||||
|
self._max_duration = max_duration
|
||||||
|
def setup_cycle_time(self, cycle_time, hardware_pwm=False):
|
||||||
|
if hardware_pwm:
|
||||||
|
raise pins.error("pca9685 does not support hardware_pwm parameter")
|
||||||
|
if cycle_time != self._cycle_time:
|
||||||
|
logging.info("Ignoring pca9685 cycle time of %.6f (using %.6f)",
|
||||||
|
cycle_time, self._cycle_time)
|
||||||
|
def setup_start_value(self, start_value, shutdown_value, is_static=False):
|
||||||
|
if is_static and start_value != shutdown_value:
|
||||||
|
raise pins.error("Static pin can not have shutdown value")
|
||||||
|
if self._invert:
|
||||||
|
start_value = 1. - start_value
|
||||||
|
shutdown_value = 1. - shutdown_value
|
||||||
|
self._start_value = max(0., min(1., start_value))
|
||||||
|
self._shutdown_value = max(0., min(1., shutdown_value))
|
||||||
|
self._is_static = is_static
|
||||||
|
self._replicape.note_pwm_start_value(
|
||||||
|
self._channel, self._start_value, self._shutdown_value)
|
||||||
|
def build_config(self):
|
||||||
|
self._pwm_max = self._mcu.get_constant_float("PCA9685_MAX")
|
||||||
|
cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time)
|
||||||
|
if self._is_static:
|
||||||
|
self._mcu.add_config_cmd(
|
||||||
|
"set_pca9685_out bus=%d addr=%d channel=%d"
|
||||||
|
" cycle_ticks=%d value=%d" % (
|
||||||
|
self._bus, self._address, self._channel,
|
||||||
|
cycle_ticks, self._start_value * self._pwm_max))
|
||||||
|
return
|
||||||
|
self._oid = self._mcu.create_oid()
|
||||||
|
self._mcu.add_config_cmd(
|
||||||
|
"config_pca9685 oid=%d bus=%d addr=%d channel=%d cycle_ticks=%d"
|
||||||
|
" value=%d default_value=%d max_duration=%d" % (
|
||||||
|
self._oid, self._bus, self._address, self._channel, cycle_ticks,
|
||||||
|
self._start_value * self._pwm_max,
|
||||||
|
self._shutdown_value * self._pwm_max,
|
||||||
|
self._mcu.seconds_to_clock(self._max_duration)))
|
||||||
|
cmd_queue = self._mcu.alloc_command_queue()
|
||||||
|
self._set_cmd = self._mcu.lookup_command(
|
||||||
|
"schedule_pca9685_out oid=%c clock=%u value=%hu", cq=cmd_queue)
|
||||||
|
def set_pwm(self, print_time, value):
|
||||||
|
clock = self._mcu.print_time_to_clock(print_time)
|
||||||
|
if self._invert:
|
||||||
|
value = 1. - value
|
||||||
|
value = int(max(0., min(1., value)) * self._pwm_max + 0.5)
|
||||||
|
self._replicape.note_pwm_enable(print_time, self._channel, value)
|
||||||
|
self._set_cmd.send([self._oid, clock, value],
|
||||||
|
minclock=self._last_clock, reqclock=clock)
|
||||||
|
self._last_clock = clock
|
||||||
|
def set_digital(self, print_time, value):
|
||||||
|
if value:
|
||||||
|
self.set_pwm(print_time, 1.)
|
||||||
|
else:
|
||||||
|
self.set_pwm(print_time, 0.)
|
||||||
|
|
||||||
|
class ReplicapeDACEnable:
|
||||||
|
def __init__(self, replicape, channel, pin_params):
|
||||||
|
if pin_params['type'] != 'digital_out':
|
||||||
|
raise pins.error("Replicape virtual enable pin must be digital_out")
|
||||||
|
if pin_params['invert']:
|
||||||
|
raise pins.error("Replicape virtual enable pin can not be inverted")
|
||||||
|
self.mcu = replicape.host_mcu
|
||||||
|
self.value = replicape.stepper_dacs[channel]
|
||||||
|
self.pwm = pca9685_pwm(replicape, channel, pin_params)
|
||||||
|
def get_mcu(self):
|
||||||
|
return self.mcu
|
||||||
|
def setup_max_duration(self, max_duration):
|
||||||
|
self.pwm.setup_max_duration(max_duration)
|
||||||
|
def set_digital(self, print_time, value):
|
||||||
|
if value:
|
||||||
|
self.pwm.set_pwm(print_time, self.value)
|
||||||
|
else:
|
||||||
|
self.pwm.set_pwm(print_time, 0.)
|
||||||
|
|
||||||
|
ReplicapeStepConfig = {
|
||||||
|
'disable': None,
|
||||||
|
'1': (1<<7)|(1<<5), '2': (1<<7)|(1<<5)|(1<<6), 'spread2': (1<<5),
|
||||||
|
'4': (1<<7)|(1<<5)|(1<<4), '16': (1<<7)|(1<<5)|(1<<6)|(1<<4),
|
||||||
|
'spread4': (1<<5)|(1<<4), 'spread16': (1<<7), 'stealth4': (1<<7)|(1<<6),
|
||||||
|
'stealth16': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
class Replicape:
|
||||||
|
def __init__(self, config):
|
||||||
|
printer = config.get_printer()
|
||||||
|
pins.get_printer_pins(printer).register_chip('replicape', self)
|
||||||
|
revisions = {'B3': 'B3'}
|
||||||
|
config.getchoice('revision', revisions)
|
||||||
|
self.host_mcu = mcu.get_printer_mcu(printer, config.get('host_mcu'))
|
||||||
|
# Setup enable pin
|
||||||
|
self.mcu_pwm_enable = pins.setup_pin(
|
||||||
|
printer, 'digital_out', config.get('enable_pin', '!P9_41'))
|
||||||
|
self.mcu_pwm_enable.setup_max_duration(0.)
|
||||||
|
self.mcu_pwm_start_value = self.mcu_pwm_shutdown_value = False
|
||||||
|
# Setup power pins
|
||||||
|
self.pins = {
|
||||||
|
"power_e": (pca9685_pwm, 5), "power_h": (pca9685_pwm, 3),
|
||||||
|
"power_hotbed": (pca9685_pwm, 4),
|
||||||
|
"power_fan0": (pca9685_pwm, 7), "power_fan1": (pca9685_pwm, 8),
|
||||||
|
"power_fan2": (pca9685_pwm, 9), "power_fan3": (pca9685_pwm, 10) }
|
||||||
|
# Setup stepper config
|
||||||
|
self.send_spi_cmd = None
|
||||||
|
self.last_stepper_time = 0.
|
||||||
|
self.stepper_dacs = {}
|
||||||
|
shift_registers = [1, 0, 0, 1, 1]
|
||||||
|
for port, name in enumerate('xyzeh'):
|
||||||
|
prefix = 'stepper_%s_' % (name,)
|
||||||
|
sc = config.getchoice(
|
||||||
|
prefix + 'microstep_mode', ReplicapeStepConfig, 'disable')
|
||||||
|
if sc is None:
|
||||||
|
continue
|
||||||
|
sc |= shift_registers[port]
|
||||||
|
if config.getboolean(prefix + 'chopper_off_time_high', False):
|
||||||
|
sc |= 1<<3
|
||||||
|
if config.getboolean(prefix + 'chopper_hysteresis_high', False):
|
||||||
|
sc |= 1<<2
|
||||||
|
if config.getboolean(prefix + 'chopper_blank_time_high', True):
|
||||||
|
sc |= 1<<1
|
||||||
|
shift_registers[port] = sc
|
||||||
|
channel = port + 11
|
||||||
|
cur = config.getfloat(
|
||||||
|
prefix + 'current', above=0., maxval=REPLICAPE_MAX_CURRENT)
|
||||||
|
self.stepper_dacs[channel] = cur / REPLICAPE_MAX_CURRENT
|
||||||
|
self.pins[prefix + 'enable'] = (ReplicapeDACEnable, channel)
|
||||||
|
self.enabled_channels = {ch: False for cl, ch in self.pins.values()}
|
||||||
|
if config.getboolean('servo0_enable', False):
|
||||||
|
shift_registers[1] |= 1
|
||||||
|
if config.getboolean('servo1_enable', False):
|
||||||
|
shift_registers[2] |= 1
|
||||||
|
self.sr_disabled = tuple(reversed(shift_registers))
|
||||||
|
if [i for i in [0, 1, 2] if 11+i in self.stepper_dacs]:
|
||||||
|
# Enable xyz steppers
|
||||||
|
shift_registers[0] &= ~1
|
||||||
|
if [i for i in [3, 4] if 11+i in self.stepper_dacs]:
|
||||||
|
# Enable eh steppers
|
||||||
|
shift_registers[3] &= ~1
|
||||||
|
if (config.getboolean('standstill_power_down', False)
|
||||||
|
and self.stepper_dacs):
|
||||||
|
shift_registers[4] &= ~1
|
||||||
|
self.sr_enabled = tuple(reversed(shift_registers))
|
||||||
|
self.host_mcu.add_config_object(self)
|
||||||
|
self.host_mcu.add_config_cmd("send_spi bus=%d dev=%d msg=%s" % (
|
||||||
|
REPLICAPE_SHIFT_REGISTER_BUS, REPLICAPE_SHIFT_REGISTER_DEVICE,
|
||||||
|
"".join(["%02x" % (x,) for x in self.sr_disabled])))
|
||||||
|
def build_config(self):
|
||||||
|
cmd_queue = self.host_mcu.alloc_command_queue()
|
||||||
|
self.send_spi_cmd = self.host_mcu.lookup_command(
|
||||||
|
"send_spi bus=%u dev=%u msg=%*s", cq=cmd_queue)
|
||||||
|
def note_pwm_start_value(self, channel, start_value, shutdown_value):
|
||||||
|
self.mcu_pwm_start_value |= not not start_value
|
||||||
|
self.mcu_pwm_shutdown_value |= not not shutdown_value
|
||||||
|
self.mcu_pwm_enable.setup_start_value(
|
||||||
|
self.mcu_pwm_start_value, self.mcu_pwm_shutdown_value)
|
||||||
|
self.enabled_channels[channel] = not not start_value
|
||||||
|
def note_pwm_enable(self, print_time, channel, value):
|
||||||
|
is_enable = not not value
|
||||||
|
if self.enabled_channels[channel] == is_enable:
|
||||||
|
# Nothing to do
|
||||||
|
return
|
||||||
|
self.enabled_channels[channel] = is_enable
|
||||||
|
# Check if need to set the pca9685 enable pin
|
||||||
|
on_channels = [1 for c, e in self.enabled_channels.items() if e]
|
||||||
|
if not on_channels:
|
||||||
|
self.mcu_pwm_enable.set_digital(print_time, 0)
|
||||||
|
elif is_enable and len(on_channels) == 1:
|
||||||
|
self.mcu_pwm_enable.set_digital(print_time, 1)
|
||||||
|
# Check if need to set the stepper enable lines
|
||||||
|
if channel not in self.stepper_dacs:
|
||||||
|
return
|
||||||
|
on_dacs = [1 for c in self.stepper_dacs.keys()
|
||||||
|
if self.enabled_channels[c]]
|
||||||
|
if not on_dacs:
|
||||||
|
sr = self.sr_disabled
|
||||||
|
elif is_enable and len(on_dacs) == 1:
|
||||||
|
sr = self.sr_enabled
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
print_time = max(print_time, self.last_stepper_time + PIN_MIN_TIME)
|
||||||
|
clock = self.host_mcu.print_time_to_clock(print_time)
|
||||||
|
# XXX - the send_spi message should be scheduled
|
||||||
|
self.send_spi_cmd.send([REPLICAPE_SHIFT_REGISTER_BUS,
|
||||||
|
REPLICAPE_SHIFT_REGISTER_DEVICE, sr],
|
||||||
|
minclock=clock, reqclock=clock)
|
||||||
|
def setup_pin(self, pin_params):
|
||||||
|
pin = pin_params['pin']
|
||||||
|
if pin not in self.pins:
|
||||||
|
raise pins.error("Unknown replicape pin %s" % (pin,))
|
||||||
|
pclass, channel = self.pins[pin]
|
||||||
|
return pclass(self, channel, pin_params)
|
||||||
|
|
||||||
|
def load_config(config):
|
||||||
|
return Replicape(config)
|
||||||
59
klippy/extras/servo.py
Normal file
59
klippy/extras/servo.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Support for servos
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017,2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import pins
|
||||||
|
|
||||||
|
SERVO_SIGNAL_PERIOD = 0.020
|
||||||
|
PIN_MIN_TIME = 0.100
|
||||||
|
|
||||||
|
class PrinterServo:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.mcu_servo = pins.setup_pin(self.printer, 'pwm', config.get('pin'))
|
||||||
|
self.mcu_servo.setup_max_duration(0.)
|
||||||
|
self.mcu_servo.setup_cycle_time(SERVO_SIGNAL_PERIOD)
|
||||||
|
self.min_width = config.getfloat(
|
||||||
|
'minimum_pulse_width', .001, above=0., below=SERVO_SIGNAL_PERIOD)
|
||||||
|
self.max_width = config.getfloat(
|
||||||
|
'maximum_pulse_width', .002
|
||||||
|
, above=self.min_width, below=SERVO_SIGNAL_PERIOD)
|
||||||
|
self.max_angle = config.getfloat('maximum_servo_angle', 180.)
|
||||||
|
self.angle_to_width = (self.max_width - self.min_width) / self.max_angle
|
||||||
|
self.width_to_value = 1. / SERVO_SIGNAL_PERIOD
|
||||||
|
self.last_value = self.last_value_time = 0.
|
||||||
|
self.gcode = self.printer.lookup_object('gcode')
|
||||||
|
self.gcode.register_command("SET_SERVO", self.cmd_SET_SERVO,
|
||||||
|
desc=self.cmd_SET_SERVO_help)
|
||||||
|
def set_pwm(self, print_time, value):
|
||||||
|
if value == self.last_value:
|
||||||
|
return
|
||||||
|
print_time = max(print_time, self.last_value_time + PIN_MIN_TIME)
|
||||||
|
self.mcu_servo.set_pwm(print_time, value)
|
||||||
|
self.last_value = value
|
||||||
|
self.last_value_time = print_time
|
||||||
|
def set_angle(self, print_time, angle):
|
||||||
|
angle = max(0., min(self.max_angle, angle))
|
||||||
|
width = self.min_width + angle * self.angle_to_width
|
||||||
|
self.set_pwm(print_time, width * self.width_to_value)
|
||||||
|
def set_pulse_width(self, print_time, width):
|
||||||
|
width = max(self.min_width, min(self.max_width, width))
|
||||||
|
self.set_pwm(print_time, width * self.width_to_value)
|
||||||
|
cmd_SET_SERVO_help = "Set servo angle"
|
||||||
|
def cmd_SET_SERVO(self, params):
|
||||||
|
servo_name = self.gcode.get_str('SERVO', params)
|
||||||
|
servo = self.printer.lookup_object('servo ' + servo_name, None)
|
||||||
|
if servo is not self:
|
||||||
|
if servo is None:
|
||||||
|
raise self.gcode.error("Servo not configured")
|
||||||
|
return servo.cmd_SET_SERVO(params)
|
||||||
|
print_time = self.printer.lookup_object('toolhead').get_last_move_time()
|
||||||
|
if 'WIDTH' in params:
|
||||||
|
self.set_pulse_width(print_time,
|
||||||
|
self.gcode.get_float('WIDTH', params))
|
||||||
|
else:
|
||||||
|
self.set_angle(print_time, self.gcode.get_float('ANGLE', params))
|
||||||
|
|
||||||
|
def load_config_prefix(config):
|
||||||
|
return PrinterServo(config)
|
||||||
17
klippy/extras/static_digital_output.py
Normal file
17
klippy/extras/static_digital_output.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Set the state of a list of digital output pins
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
|
class PrinterStaticDigitalOut:
|
||||||
|
def __init__(self, config):
|
||||||
|
printer = config.get_printer()
|
||||||
|
ppins = printer.lookup_object('pins')
|
||||||
|
pin_list = [pin.strip() for pin in config.get('pins').split(',')]
|
||||||
|
for pin_desc in pin_list:
|
||||||
|
mcu_pin = ppins.setup_pin('digital_out', pin_desc)
|
||||||
|
mcu_pin.setup_start_value(1, 1, True)
|
||||||
|
|
||||||
|
def load_config_prefix(config):
|
||||||
|
return PrinterStaticDigitalOut(config)
|
||||||
74
klippy/extras/verify_heater.py
Normal file
74
klippy/extras/verify_heater.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Heater/sensor verification code
|
||||||
|
#
|
||||||
|
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import logging
|
||||||
|
import extruder
|
||||||
|
|
||||||
|
HINT_THERMAL = """
|
||||||
|
See the 'verify_heater' section in config/example-extras.cfg
|
||||||
|
for the parameters that control this check.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class HeaterCheck:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.printer = config.get_printer()
|
||||||
|
self.heater_name = config.get_name().split()[1]
|
||||||
|
self.heater = None
|
||||||
|
self.hysteresis = config.getfloat('hysteresis', 5., minval=0.)
|
||||||
|
self.max_error = config.getfloat('max_error', 120., minval=0.)
|
||||||
|
self.heating_gain = config.getfloat('heating_gain', 2., above=0.)
|
||||||
|
default_gain_time = 20.
|
||||||
|
if self.heater_name == 'heater_bed':
|
||||||
|
default_gain_time = 60.
|
||||||
|
self.check_gain_time = config.getfloat(
|
||||||
|
'check_gain_time', default_gain_time, minval=1.)
|
||||||
|
self.met_target = False
|
||||||
|
self.last_target = self.goal_temp = self.error = 0.
|
||||||
|
self.fault_systime = self.printer.get_reactor().NEVER
|
||||||
|
def printer_state(self, state):
|
||||||
|
if state == 'connect':
|
||||||
|
self.heater = extruder.get_printer_heater(
|
||||||
|
self.printer, self.heater_name)
|
||||||
|
logging.info("Starting heater checks for %s", self.heater_name)
|
||||||
|
reactor = self.printer.get_reactor()
|
||||||
|
reactor.register_timer(self.check_event, reactor.NOW)
|
||||||
|
def check_event(self, eventtime):
|
||||||
|
temp, target = self.heater.get_temp(eventtime)
|
||||||
|
if temp >= target - self.hysteresis:
|
||||||
|
# Temperature near target - reset checks
|
||||||
|
if not self.met_target and target:
|
||||||
|
logging.info("Heater %s within range of %.3f",
|
||||||
|
self.heater_name, target)
|
||||||
|
self.met_target = True
|
||||||
|
self.error = 0.
|
||||||
|
elif self.met_target:
|
||||||
|
self.error += (target - self.hysteresis) - temp
|
||||||
|
if target != self.last_target:
|
||||||
|
# Target changed - reset checks
|
||||||
|
logging.info("Heater %s approaching new target of %.3f",
|
||||||
|
self.heater_name, target)
|
||||||
|
self.met_target = False
|
||||||
|
self.goal_temp = temp + self.heating_gain
|
||||||
|
self.fault_systime = eventtime + self.check_gain_time
|
||||||
|
elif self.error >= self.max_error:
|
||||||
|
# Failure due to inability to maintain target temperature
|
||||||
|
return self.heater_fault()
|
||||||
|
elif temp >= self.goal_temp:
|
||||||
|
# Temperature approaching target - reset checks
|
||||||
|
self.goal_temp = temp + self.heating_gain
|
||||||
|
self.fault_systime = eventtime + self.check_gain_time
|
||||||
|
elif eventtime >= self.fault_systime:
|
||||||
|
# Failure due to inability to approach target temperature
|
||||||
|
return self.heater_fault()
|
||||||
|
self.last_target = target
|
||||||
|
return eventtime + 1.
|
||||||
|
def heater_fault(self):
|
||||||
|
msg = "Heater %s not heating at expected rate" % (self.heater_name,)
|
||||||
|
logging.error(msg)
|
||||||
|
self.printer.invoke_shutdown(msg + HINT_THERMAL)
|
||||||
|
return self.printer.get_reactor().NEVER
|
||||||
|
|
||||||
|
def load_config_prefix(config):
|
||||||
|
return HeaterCheck(config)
|
||||||
162
klippy/extras/virtual_sdcard.py
Normal file
162
klippy/extras/virtual_sdcard.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# Virtual sdcard support (print files directly from a host g-code file)
|
||||||
|
#
|
||||||
|
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import os, logging
|
||||||
|
|
||||||
|
class VirtualSD:
|
||||||
|
def __init__(self, config):
|
||||||
|
printer = config.get_printer()
|
||||||
|
# sdcard state
|
||||||
|
sd = config.get('path')
|
||||||
|
self.sdcard_dirname = os.path.normpath(os.path.expanduser(sd))
|
||||||
|
self.current_file = None
|
||||||
|
self.file_position = self.file_size = 0
|
||||||
|
# Work timer
|
||||||
|
self.reactor = printer.get_reactor()
|
||||||
|
self.must_pause_work = False
|
||||||
|
self.work_timer = None
|
||||||
|
# Register commands
|
||||||
|
self.gcode = printer.lookup_object('gcode')
|
||||||
|
for cmd in ['M20', 'M21', 'M23', 'M24', 'M25', 'M26', 'M27']:
|
||||||
|
self.gcode.register_command(cmd, getattr(self, 'cmd_' + cmd))
|
||||||
|
for cmd in ['M28', 'M29', 'M30']:
|
||||||
|
self.gcode.register_command(cmd, self.cmd_error)
|
||||||
|
def printer_state(self, state):
|
||||||
|
if state == 'shutdown' and self.work_timer is not None:
|
||||||
|
self.must_pause_work = True
|
||||||
|
def get_file_list(self):
|
||||||
|
dname = self.sdcard_dirname
|
||||||
|
try:
|
||||||
|
filenames = os.listdir(self.sdcard_dirname)
|
||||||
|
return [(fname, os.path.getsize(os.path.join(dname, fname)))
|
||||||
|
for fname in filenames]
|
||||||
|
except:
|
||||||
|
logging.exception("virtual_sdcard get_file_list")
|
||||||
|
raise self.gcode.error("Unable to get file list")
|
||||||
|
def get_status(self, eventtime):
|
||||||
|
progress = 0.
|
||||||
|
if self.work_timer is not None and self.file_size:
|
||||||
|
progress = float(self.file_position) / self.file_size
|
||||||
|
return {'progress': progress}
|
||||||
|
# G-Code commands
|
||||||
|
def cmd_error(self, params):
|
||||||
|
raise self.gcode.error("SD write not supported")
|
||||||
|
def cmd_M20(self, params):
|
||||||
|
# List SD card
|
||||||
|
files = self.get_file_list()
|
||||||
|
self.gcode.respond("Begin file list")
|
||||||
|
for fname, fsize in files:
|
||||||
|
self.gcode.respond("%s %d" % (fname, fsize))
|
||||||
|
self.gcode.respond("End file list")
|
||||||
|
def cmd_M21(self, params):
|
||||||
|
# Initialize SD card
|
||||||
|
self.gcode.respond("SD card ok")
|
||||||
|
def cmd_M23(self, params):
|
||||||
|
# Select SD file
|
||||||
|
if self.work_timer is not None:
|
||||||
|
raise self.gcode.error("SD busy")
|
||||||
|
if self.current_file is not None:
|
||||||
|
self.current_file.close()
|
||||||
|
self.current_file = None
|
||||||
|
self.file_position = self.file_size = 0
|
||||||
|
try:
|
||||||
|
orig = params['#original']
|
||||||
|
filename = orig[orig.find("M23") + 4:].split()[0].strip()
|
||||||
|
if '*' in filename:
|
||||||
|
filename = filename[:filename.find('*')].strip()
|
||||||
|
except:
|
||||||
|
raise self.gcode.error("Unable to extract filename")
|
||||||
|
if filename.startswith('/'):
|
||||||
|
filename = filename[1:]
|
||||||
|
files = self.get_file_list()
|
||||||
|
files_by_lower = { fname.lower(): fname for fname, fsize in files }
|
||||||
|
try:
|
||||||
|
fname = files_by_lower[filename.lower()]
|
||||||
|
fname = os.path.join(self.sdcard_dirname, fname)
|
||||||
|
f = open(fname, 'rb')
|
||||||
|
f.seek(0, os.SEEK_END)
|
||||||
|
fsize = f.tell()
|
||||||
|
f.seek(0)
|
||||||
|
except:
|
||||||
|
logging.exception("virtual_sdcard file open")
|
||||||
|
raise self.gcode.error("Unable to open file")
|
||||||
|
self.gcode.respond("File opened:%s Size:%d" % (filename, fsize))
|
||||||
|
self.gcode.respond("File selected")
|
||||||
|
self.current_file = f
|
||||||
|
self.file_position = 0
|
||||||
|
self.file_size = fsize
|
||||||
|
def cmd_M24(self, params):
|
||||||
|
# Start/resume SD print
|
||||||
|
if self.work_timer is not None:
|
||||||
|
raise self.gcode.error("SD busy")
|
||||||
|
self.must_pause_work = False
|
||||||
|
self.work_timer = self.reactor.register_timer(
|
||||||
|
self.work_handler, self.reactor.NOW)
|
||||||
|
def cmd_M25(self, params):
|
||||||
|
# Pause SD print
|
||||||
|
if self.work_timer is not None:
|
||||||
|
self.must_pause_work = True
|
||||||
|
def cmd_M26(self, params):
|
||||||
|
# Set SD position
|
||||||
|
if self.work_timer is not None:
|
||||||
|
raise self.gcode.error("SD busy")
|
||||||
|
pos = self.gcode.get_int('S', params)
|
||||||
|
self.file_position = pos
|
||||||
|
def cmd_M27(self, params):
|
||||||
|
# Report SD print status
|
||||||
|
if self.current_file is None or self.work_timer is None:
|
||||||
|
self.gcode.respond("Not SD printing.")
|
||||||
|
return
|
||||||
|
self.gcode.respond("SD printing byte %d/%d" % (
|
||||||
|
self.file_position, self.file_size))
|
||||||
|
# Background work timer
|
||||||
|
def work_handler(self, eventtime):
|
||||||
|
self.reactor.unregister_timer(self.work_timer)
|
||||||
|
try:
|
||||||
|
self.current_file.seek(self.file_position)
|
||||||
|
except:
|
||||||
|
logging.exception("virtual_sdcard seek")
|
||||||
|
self.gcode.respond_error("Unable to seek file")
|
||||||
|
self.work_timer = None
|
||||||
|
return self.reactor.NEVER
|
||||||
|
partial_input = ""
|
||||||
|
lines = []
|
||||||
|
while not self.must_pause_work:
|
||||||
|
if not lines:
|
||||||
|
# Read more data
|
||||||
|
try:
|
||||||
|
data = self.current_file.read(8192)
|
||||||
|
except:
|
||||||
|
logging.exception("virtual_sdcard read")
|
||||||
|
self.gcode.respond_error("Error on virtual sdcard read")
|
||||||
|
break
|
||||||
|
if not data:
|
||||||
|
# End of file
|
||||||
|
self.current_file.close()
|
||||||
|
self.current_file = None
|
||||||
|
self.gcode.respond("Done printing file")
|
||||||
|
break
|
||||||
|
lines = data.split('\n')
|
||||||
|
lines[0] = partial_input + lines[0]
|
||||||
|
partial_input = lines.pop()
|
||||||
|
lines.reverse()
|
||||||
|
continue
|
||||||
|
# Dispatch command
|
||||||
|
try:
|
||||||
|
res = self.gcode.process_batch(lines[-1])
|
||||||
|
if not res:
|
||||||
|
self.reactor.pause(self.reactor.monotonic() + 0.100)
|
||||||
|
continue
|
||||||
|
except self.gcode.error as e:
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
logging.exception("virtual_sdcard dispatch")
|
||||||
|
break
|
||||||
|
self.file_position += len(lines.pop()) + 1
|
||||||
|
self.work_timer = None
|
||||||
|
return self.reactor.NEVER
|
||||||
|
|
||||||
|
def load_config(config):
|
||||||
|
return VirtualSD(config)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Code for handling printer nozzle extruders
|
# Code for handling printer nozzle extruders
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import math, logging
|
import math, logging
|
||||||
@@ -10,9 +10,12 @@ EXTRUDE_DIFF_IGNORE = 1.02
|
|||||||
|
|
||||||
class PrinterExtruder:
|
class PrinterExtruder:
|
||||||
def __init__(self, printer, config):
|
def __init__(self, printer, config):
|
||||||
self.config = config
|
shared_heater = config.get('shared_heater', None)
|
||||||
|
if shared_heater is None:
|
||||||
self.heater = heater.PrinterHeater(printer, config)
|
self.heater = heater.PrinterHeater(printer, config)
|
||||||
self.stepper = stepper.PrinterStepper(printer, config, 'extruder')
|
else:
|
||||||
|
self.heater = get_printer_heater(printer, shared_heater)
|
||||||
|
self.stepper = stepper.PrinterStepper(printer, config)
|
||||||
self.nozzle_diameter = config.getfloat('nozzle_diameter', above=0.)
|
self.nozzle_diameter = config.getfloat('nozzle_diameter', above=0.)
|
||||||
filament_diameter = config.getfloat(
|
filament_diameter = config.getfloat(
|
||||||
'filament_diameter', minval=self.nozzle_diameter)
|
'filament_diameter', minval=self.nozzle_diameter)
|
||||||
@@ -21,12 +24,12 @@ class PrinterExtruder:
|
|||||||
'max_extrude_cross_section', 4. * self.nozzle_diameter**2
|
'max_extrude_cross_section', 4. * self.nozzle_diameter**2
|
||||||
, above=0.)
|
, above=0.)
|
||||||
self.max_extrude_ratio = max_cross_section / self.filament_area
|
self.max_extrude_ratio = max_cross_section / self.filament_area
|
||||||
toolhead = printer.objects['toolhead']
|
toolhead = printer.lookup_object('toolhead')
|
||||||
max_velocity, max_accel = toolhead.get_max_velocity()
|
max_velocity, max_accel = toolhead.get_max_velocity()
|
||||||
self.max_e_velocity = self.config.getfloat(
|
self.max_e_velocity = config.getfloat(
|
||||||
'max_extrude_only_velocity', max_velocity * self.max_extrude_ratio
|
'max_extrude_only_velocity', max_velocity * self.max_extrude_ratio
|
||||||
, above=0.)
|
, above=0.)
|
||||||
self.max_e_accel = self.config.getfloat(
|
self.max_e_accel = config.getfloat(
|
||||||
'max_extrude_only_accel', max_accel * self.max_extrude_ratio
|
'max_extrude_only_accel', max_accel * self.max_extrude_ratio
|
||||||
, above=0.)
|
, above=0.)
|
||||||
self.stepper.set_max_jerk(9999999.9, 9999999.9)
|
self.stepper.set_max_jerk(9999999.9, 9999999.9)
|
||||||
@@ -50,6 +53,8 @@ class PrinterExtruder:
|
|||||||
if is_active:
|
if is_active:
|
||||||
return self.activate_gcode
|
return self.activate_gcode
|
||||||
return self.deactivate_gcode
|
return self.deactivate_gcode
|
||||||
|
def stats(self, eventtime):
|
||||||
|
return self.heater.stats(eventtime)
|
||||||
def motor_off(self, print_time):
|
def motor_off(self, print_time):
|
||||||
self.stepper.motor_enable(print_time, 0)
|
self.stepper.motor_enable(print_time, 0)
|
||||||
self.need_motor_enable = True
|
self.need_motor_enable = True
|
||||||
@@ -70,8 +75,11 @@ class PrinterExtruder:
|
|||||||
inv_extrude_r = 1. / abs(move.extrude_r)
|
inv_extrude_r = 1. / abs(move.extrude_r)
|
||||||
move.limit_speed(self.max_e_velocity * inv_extrude_r
|
move.limit_speed(self.max_e_velocity * inv_extrude_r
|
||||||
, self.max_e_accel * inv_extrude_r)
|
, self.max_e_accel * inv_extrude_r)
|
||||||
elif (move.extrude_r > self.max_extrude_ratio
|
elif move.extrude_r > self.max_extrude_ratio:
|
||||||
and move.axes_d[3] > self.nozzle_diameter*self.max_extrude_ratio):
|
if move.axes_d[3] <= self.nozzle_diameter * self.max_extrude_ratio:
|
||||||
|
# Permit extrusion if amount extruded is tiny
|
||||||
|
move.extrude_r = self.max_extrude_ratio
|
||||||
|
return
|
||||||
area = move.axes_d[3] * self.filament_area / move.move_d
|
area = move.axes_d[3] * self.filament_area / move.move_d
|
||||||
logging.debug("Overextrude: %s vs %s (area=%.3f dist=%.3f)",
|
logging.debug("Overextrude: %s vs %s (area=%.3f dist=%.3f)",
|
||||||
move.extrude_r, self.max_extrude_ratio,
|
move.extrude_r, self.max_extrude_ratio,
|
||||||
@@ -189,29 +197,27 @@ class PrinterExtruder:
|
|||||||
decel_d -= extra_decel_d
|
decel_d -= extra_decel_d
|
||||||
|
|
||||||
# Prepare for steps
|
# Prepare for steps
|
||||||
mcu_stepper = self.stepper.mcu_stepper
|
step_const = self.stepper.step_const
|
||||||
move_time = print_time
|
move_time = print_time
|
||||||
|
|
||||||
# Acceleration steps
|
# Acceleration steps
|
||||||
if accel_d:
|
if accel_d:
|
||||||
mcu_stepper.step_const(move_time, start_pos, accel_d, start_v, accel)
|
step_const(move_time, start_pos, accel_d, start_v, accel)
|
||||||
start_pos += accel_d
|
start_pos += accel_d
|
||||||
move_time += accel_t
|
move_time += accel_t
|
||||||
# Cruising steps
|
# Cruising steps
|
||||||
if cruise_d:
|
if cruise_d:
|
||||||
mcu_stepper.step_const(move_time, start_pos, cruise_d, cruise_v, 0.)
|
step_const(move_time, start_pos, cruise_d, cruise_v, 0.)
|
||||||
start_pos += cruise_d
|
start_pos += cruise_d
|
||||||
move_time += cruise_t
|
move_time += cruise_t
|
||||||
# Deceleration steps
|
# Deceleration steps
|
||||||
if decel_d:
|
if decel_d:
|
||||||
mcu_stepper.step_const(
|
step_const(move_time, start_pos, decel_d, decel_v, -accel)
|
||||||
move_time, start_pos, decel_d, decel_v, -accel)
|
|
||||||
start_pos += decel_d
|
start_pos += decel_d
|
||||||
move_time += decel_t
|
move_time += decel_t
|
||||||
# Retraction steps
|
# Retraction steps
|
||||||
if retract_d:
|
if retract_d:
|
||||||
mcu_stepper.step_const(
|
step_const(move_time, start_pos, -retract_d, retract_v, accel)
|
||||||
move_time, start_pos, -retract_d, retract_v, accel)
|
|
||||||
start_pos -= retract_d
|
start_pos -= retract_d
|
||||||
self.extrude_pos = start_pos
|
self.extrude_pos = start_pos
|
||||||
|
|
||||||
@@ -244,17 +250,17 @@ def add_printer_objects(printer, config):
|
|||||||
def get_printer_extruders(printer):
|
def get_printer_extruders(printer):
|
||||||
out = []
|
out = []
|
||||||
for i in range(99):
|
for i in range(99):
|
||||||
extruder = printer.objects.get('extruder%d' % (i,))
|
extruder = printer.lookup_object('extruder%d' % (i,), None)
|
||||||
if extruder is None:
|
if extruder is None:
|
||||||
break
|
break
|
||||||
out.append(extruder)
|
out.append(extruder)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def get_printer_heater(printer, name):
|
def get_printer_heater(printer, name):
|
||||||
if name == 'heater_bed' and name in printer.objects:
|
if name == 'heater_bed':
|
||||||
return printer.objects[name]
|
return printer.lookup_object(name)
|
||||||
if name == 'extruder':
|
if name == 'extruder':
|
||||||
name = 'extruder0'
|
name = 'extruder0'
|
||||||
if name.startswith('extruder') and name in printer.objects:
|
if name.startswith('extruder'):
|
||||||
return printer.objects[name].get_heater()
|
return printer.lookup_object(name).get_heater()
|
||||||
raise printer.config_error("Unknown heater '%s'" % (name,))
|
raise printer.config_error("Unknown heater '%s'" % (name,))
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
# Printer fan support
|
|
||||||
#
|
|
||||||
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
|
|
||||||
#
|
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
|
||||||
import extruder, pins
|
|
||||||
|
|
||||||
FAN_MIN_TIME = 0.1
|
|
||||||
PWM_CYCLE_TIME = 0.010
|
|
||||||
|
|
||||||
class PrinterFan:
|
|
||||||
def __init__(self, printer, config):
|
|
||||||
self.last_fan_value = 0.
|
|
||||||
self.last_fan_time = 0.
|
|
||||||
self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.)
|
|
||||||
self.kick_start_time = config.getfloat('kick_start_time', 0.1, minval=0.)
|
|
||||||
self.mcu_fan = pins.setup_pin(printer, 'pwm', config.get('pin'))
|
|
||||||
self.mcu_fan.setup_max_duration(0.)
|
|
||||||
self.mcu_fan.setup_cycle_time(PWM_CYCLE_TIME)
|
|
||||||
self.mcu_fan.setup_hard_pwm(config.getint('hard_pwm', 0))
|
|
||||||
def set_speed(self, print_time, value):
|
|
||||||
value = max(0., min(self.max_power, value))
|
|
||||||
if value == self.last_fan_value:
|
|
||||||
return
|
|
||||||
print_time = max(self.last_fan_time + FAN_MIN_TIME, print_time)
|
|
||||||
if (value and value < self.max_power
|
|
||||||
and not self.last_fan_value and self.kick_start_time):
|
|
||||||
# Run fan at full speed for specified kick_start_time
|
|
||||||
self.mcu_fan.set_pwm(print_time, self.max_power)
|
|
||||||
print_time += self.kick_start_time
|
|
||||||
self.mcu_fan.set_pwm(print_time, value)
|
|
||||||
self.last_fan_time = print_time
|
|
||||||
self.last_fan_value = value
|
|
||||||
|
|
||||||
class PrinterHeaterFan:
|
|
||||||
def __init__(self, printer, config):
|
|
||||||
self.fan = PrinterFan(printer, config)
|
|
||||||
self.mcu = printer.objects['mcu']
|
|
||||||
heater = config.get("heater", "extruder0")
|
|
||||||
self.heater = extruder.get_printer_heater(printer, heater)
|
|
||||||
self.heater_temp = config.getfloat("heater_temp", 50.0)
|
|
||||||
max_power = self.fan.max_power
|
|
||||||
self.fan_speed = config.getfloat(
|
|
||||||
"fan_speed", max_power, minval=0., maxval=max_power)
|
|
||||||
self.fan.mcu_fan.setup_shutdown_value(max_power)
|
|
||||||
printer.reactor.register_timer(self.callback, printer.reactor.NOW)
|
|
||||||
def callback(self, eventtime):
|
|
||||||
current_temp, target_temp = self.heater.get_temp(eventtime)
|
|
||||||
if not current_temp and not target_temp and not self.fan.last_fan_time:
|
|
||||||
# Printer still starting
|
|
||||||
return eventtime + 1.
|
|
||||||
power = 0.
|
|
||||||
if target_temp or current_temp > self.heater_temp:
|
|
||||||
power = self.fan_speed
|
|
||||||
print_time = self.mcu.estimated_print_time(eventtime) + FAN_MIN_TIME
|
|
||||||
self.fan.set_speed(print_time, power)
|
|
||||||
return eventtime + 1.
|
|
||||||
|
|
||||||
def add_printer_objects(printer, config):
|
|
||||||
if config.has_section('fan'):
|
|
||||||
printer.add_object('fan', PrinterFan(printer, config.getsection('fan')))
|
|
||||||
for s in config.get_prefix_sections('heater_fan '):
|
|
||||||
printer.add_object(s.section, PrinterHeaterFan(printer, s))
|
|
||||||
490
klippy/gcode.py
490
klippy/gcode.py
@@ -1,95 +1,133 @@
|
|||||||
# Parse gcode commands
|
# Parse gcode commands
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import os, re, logging, collections
|
import os, re, logging, collections
|
||||||
import homing, extruder, chipmisc
|
import homing, extruder
|
||||||
|
|
||||||
# Parse out incoming GCode and find and translate head movements
|
class error(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Parse and handle G-Code commands
|
||||||
class GCodeParser:
|
class GCodeParser:
|
||||||
|
error = error
|
||||||
RETRY_TIME = 0.100
|
RETRY_TIME = 0.100
|
||||||
def __init__(self, printer, fd):
|
def __init__(self, printer, fd):
|
||||||
self.printer = printer
|
self.printer = printer
|
||||||
self.fd = fd
|
self.fd = fd
|
||||||
# Input handling
|
# Input handling
|
||||||
self.reactor = printer.reactor
|
self.reactor = printer.get_reactor()
|
||||||
self.is_processing_data = False
|
self.is_processing_data = False
|
||||||
self.is_fileinput = not not printer.get_start_args().get("debuginput")
|
self.is_fileinput = not not printer.get_start_args().get("debuginput")
|
||||||
self.fd_handle = None
|
self.fd_handle = None
|
||||||
if not self.is_fileinput:
|
if not self.is_fileinput:
|
||||||
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
|
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
|
||||||
self.partial_input = ""
|
self.partial_input = ""
|
||||||
|
self.pending_commands = []
|
||||||
self.bytes_read = 0
|
self.bytes_read = 0
|
||||||
self.input_log = collections.deque([], 50)
|
self.input_log = collections.deque([], 50)
|
||||||
# Command handling
|
# Command handling
|
||||||
self.is_printer_ready = False
|
self.is_printer_ready = False
|
||||||
self.gcode_handlers = {}
|
self.base_gcode_handlers = self.gcode_handlers = {}
|
||||||
self.build_handlers()
|
self.ready_gcode_handlers = {}
|
||||||
self.need_ack = False
|
self.gcode_help = {}
|
||||||
self.toolhead = self.fan = self.extruder = None
|
for cmd in self.all_handlers:
|
||||||
self.heaters = []
|
func = getattr(self, 'cmd_' + cmd)
|
||||||
self.speed = 25.0
|
wnr = getattr(self, 'cmd_' + cmd + '_when_not_ready', False)
|
||||||
|
desc = getattr(self, 'cmd_' + cmd + '_help', None)
|
||||||
|
self.register_command(cmd, func, wnr, desc)
|
||||||
|
for a in getattr(self, 'cmd_' + cmd + '_aliases', []):
|
||||||
|
self.register_command(a, func, wnr)
|
||||||
|
# G-Code coordinate manipulation
|
||||||
self.absolutecoord = self.absoluteextrude = True
|
self.absolutecoord = self.absoluteextrude = True
|
||||||
self.base_position = [0.0, 0.0, 0.0, 0.0]
|
self.base_position = [0.0, 0.0, 0.0, 0.0]
|
||||||
self.last_position = [0.0, 0.0, 0.0, 0.0]
|
self.last_position = [0.0, 0.0, 0.0, 0.0]
|
||||||
self.homing_add = [0.0, 0.0, 0.0, 0.0]
|
self.homing_add = [0.0, 0.0, 0.0, 0.0]
|
||||||
|
self.speed_factor = 1. / 60.
|
||||||
|
self.extrude_factor = 1.
|
||||||
|
self.move_transform = self.move_with_transform = None
|
||||||
|
self.position_with_transform = (lambda: [0., 0., 0., 0.])
|
||||||
|
# G-Code state
|
||||||
|
self.need_ack = False
|
||||||
|
self.toolhead = self.fan = self.extruder = None
|
||||||
|
self.heaters = []
|
||||||
|
self.speed = 25.0
|
||||||
self.axis2pos = {'X': 0, 'Y': 1, 'Z': 2, 'E': 3}
|
self.axis2pos = {'X': 0, 'Y': 1, 'Z': 2, 'E': 3}
|
||||||
def build_handlers(self):
|
def register_command(self, cmd, func, when_not_ready=False, desc=None):
|
||||||
handlers = self.all_handlers
|
if func is None:
|
||||||
if not self.is_printer_ready:
|
if cmd in self.ready_gcode_handlers:
|
||||||
handlers = [h for h in handlers
|
del self.ready_gcode_handlers[cmd]
|
||||||
if getattr(self, 'cmd_'+h+'_when_not_ready', False)]
|
if cmd in self.base_gcode_handlers:
|
||||||
gcode_handlers = { h: getattr(self, 'cmd_'+h) for h in handlers }
|
del self.base_gcode_handlers[cmd]
|
||||||
for h, f in list(gcode_handlers.items()):
|
return
|
||||||
aliases = getattr(self, 'cmd_'+h+'_aliases', [])
|
if not (len(cmd) >= 2 and not cmd[0].isupper() and cmd[1].isdigit()):
|
||||||
gcode_handlers.update({ a: f for a in aliases })
|
origfunc = func
|
||||||
self.gcode_handlers = gcode_handlers
|
func = lambda params: origfunc(self.get_extended_params(params))
|
||||||
|
self.ready_gcode_handlers[cmd] = func
|
||||||
|
if when_not_ready:
|
||||||
|
self.base_gcode_handlers[cmd] = func
|
||||||
|
if desc is not None:
|
||||||
|
self.gcode_help[cmd] = desc
|
||||||
|
def set_move_transform(self, transform):
|
||||||
|
if self.move_transform is not None:
|
||||||
|
raise self.printer.config_error(
|
||||||
|
"G-Code move transform already specified")
|
||||||
|
self.move_transform = transform
|
||||||
|
self.move_with_transform = transform.move
|
||||||
|
self.position_with_transform = transform.get_position
|
||||||
def stats(self, eventtime):
|
def stats(self, eventtime):
|
||||||
return "gcodein=%d" % (self.bytes_read,)
|
return False, "gcodein=%d" % (self.bytes_read,)
|
||||||
def connect(self):
|
def get_status(self, eventtime):
|
||||||
|
busy = self.is_processing_data
|
||||||
|
return {'speed_factor': self.speed_factor * 60., 'busy': busy}
|
||||||
|
def printer_state(self, state):
|
||||||
|
if state == 'shutdown':
|
||||||
|
if not self.is_printer_ready:
|
||||||
|
return
|
||||||
|
self.is_printer_ready = False
|
||||||
|
self.gcode_handlers = self.base_gcode_handlers
|
||||||
|
self.dump_debug()
|
||||||
|
if self.is_fileinput:
|
||||||
|
self.printer.request_exit()
|
||||||
|
return
|
||||||
|
if state != 'ready':
|
||||||
|
return
|
||||||
self.is_printer_ready = True
|
self.is_printer_ready = True
|
||||||
self.build_handlers()
|
self.gcode_handlers = self.ready_gcode_handlers
|
||||||
# Lookup printer components
|
# Lookup printer components
|
||||||
self.toolhead = self.printer.objects.get('toolhead')
|
self.toolhead = self.printer.lookup_object('toolhead')
|
||||||
|
if self.move_transform is None:
|
||||||
|
self.move_with_transform = self.toolhead.move
|
||||||
|
self.position_with_transform = self.toolhead.get_position
|
||||||
extruders = extruder.get_printer_extruders(self.printer)
|
extruders = extruder.get_printer_extruders(self.printer)
|
||||||
if extruders:
|
if extruders:
|
||||||
self.extruder = extruders[0]
|
self.extruder = extruders[0]
|
||||||
self.toolhead.set_extruder(self.extruder)
|
self.toolhead.set_extruder(self.extruder)
|
||||||
self.heaters = [ e.get_heater() for e in extruders ]
|
self.heaters = [ e.get_heater() for e in extruders ]
|
||||||
self.heaters.append(self.printer.objects.get('heater_bed'))
|
self.heaters.append(self.printer.lookup_object('heater_bed', None))
|
||||||
self.fan = self.printer.objects.get('fan')
|
self.fan = self.printer.lookup_object('fan', None)
|
||||||
if self.is_fileinput and self.fd_handle is None:
|
if self.is_fileinput and self.fd_handle is None:
|
||||||
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
|
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
|
||||||
def do_shutdown(self):
|
def reset_last_position(self):
|
||||||
if not self.is_printer_ready:
|
self.last_position = self.position_with_transform()
|
||||||
return
|
|
||||||
self.is_printer_ready = False
|
|
||||||
self.build_handlers()
|
|
||||||
self.dump_debug()
|
|
||||||
if self.is_fileinput:
|
|
||||||
self.printer.request_exit()
|
|
||||||
def motor_heater_off(self):
|
|
||||||
if self.toolhead is None:
|
|
||||||
return
|
|
||||||
self.toolhead.motor_off()
|
|
||||||
print_time = self.toolhead.get_last_move_time()
|
|
||||||
for heater in self.heaters:
|
|
||||||
if heater is not None:
|
|
||||||
heater.set_temp(print_time, 0.)
|
|
||||||
if self.fan is not None:
|
|
||||||
self.fan.set_speed(print_time, 0.)
|
|
||||||
def dump_debug(self):
|
def dump_debug(self):
|
||||||
out = []
|
out = []
|
||||||
out.append("Dumping gcode input %d blocks" % (
|
out.append("Dumping gcode input %d blocks" % (
|
||||||
len(self.input_log),))
|
len(self.input_log),))
|
||||||
for eventtime, data in self.input_log:
|
for eventtime, data in self.input_log:
|
||||||
out.append("Read %f: %s" % (eventtime, repr(data)))
|
out.append("Read %f: %s" % (eventtime, repr(data)))
|
||||||
|
out.append(
|
||||||
|
"gcode state: absolutecoord=%s absoluteextrude=%s"
|
||||||
|
" base_position=%s last_position=%s homing_add=%s"
|
||||||
|
" speed_factor=%s extrude_factor=%s speed=%s" % (
|
||||||
|
self.absolutecoord, self.absoluteextrude,
|
||||||
|
self.base_position, self.last_position, self.homing_add,
|
||||||
|
self.speed_factor, self.extrude_factor, self.speed))
|
||||||
logging.info("\n".join(out))
|
logging.info("\n".join(out))
|
||||||
# Parse input into commands
|
# Parse input into commands
|
||||||
args_r = re.compile('([A-Z_]+|[A-Z*])')
|
args_r = re.compile('([A-Z_]+|[A-Z*/])')
|
||||||
def process_commands(self, commands, need_ack=True):
|
def process_commands(self, commands, need_ack=True):
|
||||||
prev_need_ack = self.need_ack
|
|
||||||
for line in commands:
|
for line in commands:
|
||||||
# Ignore comments and leading/trailing spaces
|
# Ignore comments and leading/trailing spaces
|
||||||
line = origline = line.strip()
|
line = origline = line.strip()
|
||||||
@@ -105,8 +143,8 @@ class GCodeParser:
|
|||||||
# Skip line number at start of command
|
# Skip line number at start of command
|
||||||
del parts[:2]
|
del parts[:2]
|
||||||
if not parts:
|
if not parts:
|
||||||
self.cmd_default(params)
|
# Treat empty line as empty command
|
||||||
continue
|
parts = ['', '']
|
||||||
params['#command'] = cmd = parts[0] + parts[1].strip()
|
params['#command'] = cmd = parts[0] + parts[1].strip()
|
||||||
# Invoke handler for command
|
# Invoke handler for command
|
||||||
self.need_ack = need_ack
|
self.need_ack = need_ack
|
||||||
@@ -115,38 +153,78 @@ class GCodeParser:
|
|||||||
handler(params)
|
handler(params)
|
||||||
except error as e:
|
except error as e:
|
||||||
self.respond_error(str(e))
|
self.respond_error(str(e))
|
||||||
|
self.reset_last_position()
|
||||||
|
if not need_ack:
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
msg = 'Internal error on command:"%s"' % (cmd,)
|
msg = 'Internal error on command:"%s"' % (cmd,)
|
||||||
logging.exception(msg)
|
logging.exception(msg)
|
||||||
self.printer.invoke_shutdown(msg)
|
self.printer.invoke_shutdown(msg)
|
||||||
self.respond_error(msg)
|
self.respond_error(msg)
|
||||||
|
if not need_ack:
|
||||||
|
raise
|
||||||
self.ack()
|
self.ack()
|
||||||
self.need_ack = prev_need_ack
|
m112_r = re.compile('^(?:[nN][0-9]+)?\s*[mM]112(?:\s|$)')
|
||||||
def process_data(self, eventtime):
|
def process_data(self, eventtime):
|
||||||
|
# Read input, separate by newline, and add to pending_commands
|
||||||
data = os.read(self.fd, 4096)
|
data = os.read(self.fd, 4096)
|
||||||
self.input_log.append((eventtime, data))
|
self.input_log.append((eventtime, data))
|
||||||
self.bytes_read += len(data)
|
self.bytes_read += len(data)
|
||||||
lines = data.split('\n')
|
lines = data.split('\n')
|
||||||
lines[0] = self.partial_input + lines[0]
|
lines[0] = self.partial_input + lines[0]
|
||||||
self.partial_input = lines.pop()
|
self.partial_input = lines.pop()
|
||||||
|
pending_commands = self.pending_commands
|
||||||
|
pending_commands.extend(lines)
|
||||||
|
# Special handling for debug file input EOF
|
||||||
|
if not data and self.is_fileinput:
|
||||||
|
if not self.is_processing_data:
|
||||||
|
self.request_restart('exit')
|
||||||
|
pending_commands.append("")
|
||||||
|
# Handle case where multiple commands pending
|
||||||
|
if self.is_processing_data or len(pending_commands) > 1:
|
||||||
|
if len(pending_commands) < 20:
|
||||||
|
# Check for M112 out-of-order
|
||||||
|
for line in lines:
|
||||||
|
if self.m112_r.match(line) is not None:
|
||||||
|
self.cmd_M112({})
|
||||||
if self.is_processing_data:
|
if self.is_processing_data:
|
||||||
if not self.is_fileinput and not lines:
|
if len(pending_commands) >= 20:
|
||||||
return
|
# Stop reading input
|
||||||
self.reactor.unregister_fd(self.fd_handle)
|
self.reactor.unregister_fd(self.fd_handle)
|
||||||
self.fd_handle = None
|
self.fd_handle = None
|
||||||
if not self.is_fileinput and lines[0].strip().upper() == 'M112':
|
return
|
||||||
self.cmd_M112({})
|
# Process commands
|
||||||
while self.is_processing_data:
|
|
||||||
eventtime = self.reactor.pause(eventtime + 0.100)
|
|
||||||
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
|
|
||||||
self.is_processing_data = True
|
self.is_processing_data = True
|
||||||
self.process_commands(lines)
|
self.pending_commands = []
|
||||||
if not data and self.is_fileinput:
|
self.process_commands(pending_commands)
|
||||||
self.motor_heater_off()
|
if self.pending_commands:
|
||||||
if self.toolhead is not None:
|
self.process_pending()
|
||||||
self.toolhead.wait_moves()
|
|
||||||
self.printer.request_exit()
|
|
||||||
self.is_processing_data = False
|
self.is_processing_data = False
|
||||||
|
def process_pending(self):
|
||||||
|
pending_commands = self.pending_commands
|
||||||
|
while pending_commands:
|
||||||
|
self.pending_commands = []
|
||||||
|
self.process_commands(pending_commands)
|
||||||
|
pending_commands = self.pending_commands
|
||||||
|
if self.fd_handle is None:
|
||||||
|
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
|
||||||
|
def process_batch(self, command):
|
||||||
|
if self.is_processing_data:
|
||||||
|
return False
|
||||||
|
self.is_processing_data = True
|
||||||
|
try:
|
||||||
|
self.process_commands([command], need_ack=False)
|
||||||
|
finally:
|
||||||
|
if self.pending_commands:
|
||||||
|
self.process_pending()
|
||||||
|
self.is_processing_data = False
|
||||||
|
return True
|
||||||
|
def run_script(self, script):
|
||||||
|
prev_need_ack = self.need_ack
|
||||||
|
try:
|
||||||
|
self.process_commands(script.split('\n'), need_ack=False)
|
||||||
|
finally:
|
||||||
|
self.need_ack = prev_need_ack
|
||||||
# Response handling
|
# Response handling
|
||||||
def ack(self, msg=None):
|
def ack(self, msg=None):
|
||||||
if not self.need_ack or self.is_fileinput:
|
if not self.need_ack or self.is_fileinput:
|
||||||
@@ -168,29 +246,24 @@ class GCodeParser:
|
|||||||
logging.warning(msg)
|
logging.warning(msg)
|
||||||
lines = msg.strip().split('\n')
|
lines = msg.strip().split('\n')
|
||||||
if len(lines) > 1:
|
if len(lines) > 1:
|
||||||
self.respond_info("\n".join(lines[:-1]))
|
self.respond_info("\n".join(lines))
|
||||||
self.respond('!! %s' % (lines[-1].strip(),))
|
self.respond('!! %s' % (lines[0].strip(),))
|
||||||
# Parameter parsing helpers
|
# Parameter parsing helpers
|
||||||
def get_int(self, name, params, default=None):
|
class sentinel: pass
|
||||||
|
def get_str(self, name, params, default=sentinel, parser=str):
|
||||||
if name in params:
|
if name in params:
|
||||||
try:
|
try:
|
||||||
return int(params[name])
|
return parser(params[name])
|
||||||
except ValueError:
|
except:
|
||||||
raise error("Error on '%s': unable to parse %s" % (
|
raise error("Error on '%s': unable to parse %s" % (
|
||||||
params['#original'], params[name]))
|
params['#original'], params[name]))
|
||||||
if default is not None:
|
if default is not self.sentinel:
|
||||||
return default
|
|
||||||
raise error("Error on '%s': missing %s" % (params['#original'], name))
|
|
||||||
def get_float(self, name, params, default=None):
|
|
||||||
if name in params:
|
|
||||||
try:
|
|
||||||
return float(params[name])
|
|
||||||
except ValueError:
|
|
||||||
raise error("Error on '%s': unable to parse %s" % (
|
|
||||||
params['#original'], params[name]))
|
|
||||||
if default is not None:
|
|
||||||
return default
|
return default
|
||||||
raise error("Error on '%s': missing %s" % (params['#original'], name))
|
raise error("Error on '%s': missing %s" % (params['#original'], name))
|
||||||
|
def get_int(self, name, params, default=sentinel):
|
||||||
|
return self.get_str(name, params, default, parser=int)
|
||||||
|
def get_float(self, name, params, default=sentinel):
|
||||||
|
return self.get_str(name, params, default, parser=float)
|
||||||
extended_r = re.compile(
|
extended_r = re.compile(
|
||||||
r'^\s*(?:N[0-9]+\s*)?'
|
r'^\s*(?:N[0-9]+\s*)?'
|
||||||
r'(?P<cmd>[a-zA-Z_][a-zA-Z_]+)(?:\s+|$)'
|
r'(?P<cmd>[a-zA-Z_][a-zA-Z_]+)(?:\s+|$)'
|
||||||
@@ -250,9 +323,8 @@ class GCodeParser:
|
|||||||
try:
|
try:
|
||||||
heater.set_temp(print_time, temp)
|
heater.set_temp(print_time, temp)
|
||||||
except heater.error as e:
|
except heater.error as e:
|
||||||
self.respond_error(str(e))
|
raise error(str(e))
|
||||||
return
|
if wait and temp:
|
||||||
if wait:
|
|
||||||
self.bg_temp(heater)
|
self.bg_temp(heater)
|
||||||
def set_fan_speed(self, speed):
|
def set_fan_speed(self, speed):
|
||||||
if self.fan is None:
|
if self.fan is None:
|
||||||
@@ -261,7 +333,7 @@ class GCodeParser:
|
|||||||
return
|
return
|
||||||
print_time = self.toolhead.get_last_move_time()
|
print_time = self.toolhead.get_last_move_time()
|
||||||
self.fan.set_speed(print_time, speed)
|
self.fan.set_speed(print_time, speed)
|
||||||
# Individual command handlers
|
# G-Code special command handlers
|
||||||
def cmd_default(self, params):
|
def cmd_default(self, params):
|
||||||
if not self.is_printer_ready:
|
if not self.is_printer_ready:
|
||||||
self.respond_error(self.printer.get_state_message())
|
self.respond_error(self.printer.get_state_message())
|
||||||
@@ -285,50 +357,54 @@ class GCodeParser:
|
|||||||
e = extruders[index]
|
e = extruders[index]
|
||||||
if self.extruder is e:
|
if self.extruder is e:
|
||||||
return
|
return
|
||||||
deactivate_gcode = self.extruder.get_activate_gcode(False)
|
self.run_script(self.extruder.get_activate_gcode(False))
|
||||||
self.process_commands(deactivate_gcode.split('\n'), need_ack=False)
|
|
||||||
try:
|
try:
|
||||||
self.toolhead.set_extruder(e)
|
self.toolhead.set_extruder(e)
|
||||||
except homing.EndstopError as e:
|
except homing.EndstopError as e:
|
||||||
self.respond_error(str(e))
|
raise error(str(e))
|
||||||
return
|
|
||||||
self.extruder = e
|
self.extruder = e
|
||||||
self.last_position = self.toolhead.get_position()
|
self.reset_last_position()
|
||||||
activate_gcode = self.extruder.get_activate_gcode(True)
|
self.run_script(self.extruder.get_activate_gcode(True))
|
||||||
self.process_commands(activate_gcode.split('\n'), need_ack=False)
|
|
||||||
all_handlers = [
|
all_handlers = [
|
||||||
'G1', 'G4', 'G20', 'G28', 'G90', 'G91', 'G92',
|
'G1', 'G4', 'G28', 'M18', 'M400',
|
||||||
'M82', 'M83', 'M18', 'M105', 'M104', 'M109', 'M112', 'M114', 'M115',
|
'G20', 'M82', 'M83', 'G90', 'G91', 'G92', 'M114', 'M206', 'M220', 'M221',
|
||||||
'M140', 'M190', 'M106', 'M107', 'M206', 'M400',
|
'M105', 'M104', 'M109', 'M140', 'M190', 'M106', 'M107',
|
||||||
'IGNORE', 'QUERY_ENDSTOPS', 'PID_TUNE', 'SET_SERVO',
|
'M112', 'M115', 'IGNORE', 'QUERY_ENDSTOPS', 'GET_POSITION',
|
||||||
'RESTART', 'FIRMWARE_RESTART', 'ECHO', 'STATUS', 'HELP']
|
'RESTART', 'FIRMWARE_RESTART', 'ECHO', 'STATUS', 'HELP']
|
||||||
|
# G-Code movement commands
|
||||||
cmd_G1_aliases = ['G0']
|
cmd_G1_aliases = ['G0']
|
||||||
def cmd_G1(self, params):
|
def cmd_G1(self, params):
|
||||||
# Move
|
# Move
|
||||||
try:
|
try:
|
||||||
for a, p in self.axis2pos.items():
|
for axis in 'XYZ':
|
||||||
if a in params:
|
if axis in params:
|
||||||
v = float(params[a])
|
v = float(params[axis])
|
||||||
if (not self.absolutecoord
|
pos = self.axis2pos[axis]
|
||||||
or (p>2 and not self.absoluteextrude)):
|
if not self.absolutecoord:
|
||||||
# value relative to position of last move
|
# value relative to position of last move
|
||||||
self.last_position[p] += v
|
self.last_position[pos] += v
|
||||||
else:
|
else:
|
||||||
# value relative to base coordinate position
|
# value relative to base coordinate position
|
||||||
self.last_position[p] = v + self.base_position[p]
|
self.last_position[pos] = v + self.base_position[pos]
|
||||||
|
if 'E' in params:
|
||||||
|
v = float(params['E']) * self.extrude_factor
|
||||||
|
if not self.absolutecoord or not self.absoluteextrude:
|
||||||
|
# value relative to position of last move
|
||||||
|
self.last_position[3] += v
|
||||||
|
else:
|
||||||
|
# value relative to base coordinate position
|
||||||
|
self.last_position[3] = v + self.base_position[3]
|
||||||
if 'F' in params:
|
if 'F' in params:
|
||||||
speed = float(params['F']) / 60.
|
speed = float(params['F']) * self.speed_factor
|
||||||
if speed <= 0.:
|
if speed <= 0.:
|
||||||
raise ValueError()
|
raise error("Invalid speed in '%s'" % (params['#original'],))
|
||||||
self.speed = speed
|
self.speed = speed
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.last_position = self.toolhead.get_position()
|
|
||||||
raise error("Unable to parse move '%s'" % (params['#original'],))
|
raise error("Unable to parse move '%s'" % (params['#original'],))
|
||||||
try:
|
try:
|
||||||
self.toolhead.move(self.last_position, self.speed)
|
self.move_with_transform(self.last_position, self.speed)
|
||||||
except homing.EndstopError as e:
|
except homing.EndstopError as e:
|
||||||
self.respond_error(str(e))
|
raise error(str(e))
|
||||||
self.last_position = self.toolhead.get_position()
|
|
||||||
def cmd_G4(self, params):
|
def cmd_G4(self, params):
|
||||||
# Dwell
|
# Dwell
|
||||||
if 'S' in params:
|
if 'S' in params:
|
||||||
@@ -336,9 +412,6 @@ class GCodeParser:
|
|||||||
else:
|
else:
|
||||||
delay = self.get_float('P', params, 0.) / 1000.
|
delay = self.get_float('P', params, 0.) / 1000.
|
||||||
self.toolhead.dwell(delay)
|
self.toolhead.dwell(delay)
|
||||||
def cmd_G20(self, params):
|
|
||||||
# Set units to inches
|
|
||||||
self.respond_error('Machine does not support G20 (inches) command')
|
|
||||||
def cmd_G28(self, params):
|
def cmd_G28(self, params):
|
||||||
# Move to origin
|
# Move to origin
|
||||||
axes = []
|
axes = []
|
||||||
@@ -347,19 +420,33 @@ class GCodeParser:
|
|||||||
axes.append(self.axis2pos[axis])
|
axes.append(self.axis2pos[axis])
|
||||||
if not axes:
|
if not axes:
|
||||||
axes = [0, 1, 2]
|
axes = [0, 1, 2]
|
||||||
homing_state = homing.Homing(self.toolhead, axes)
|
homing_state = homing.Homing(self.toolhead)
|
||||||
if self.is_fileinput:
|
if self.is_fileinput:
|
||||||
homing_state.set_no_verify_retract()
|
homing_state.set_no_verify_retract()
|
||||||
try:
|
try:
|
||||||
self.toolhead.home(homing_state)
|
homing_state.home_axes(axes)
|
||||||
except homing.EndstopError as e:
|
except homing.EndstopError as e:
|
||||||
self.toolhead.motor_off()
|
raise error(str(e))
|
||||||
self.respond_error(str(e))
|
|
||||||
return
|
|
||||||
newpos = self.toolhead.get_position()
|
|
||||||
for axis in homing_state.get_axes():
|
for axis in homing_state.get_axes():
|
||||||
self.last_position[axis] = newpos[axis]
|
|
||||||
self.base_position[axis] = -self.homing_add[axis]
|
self.base_position[axis] = -self.homing_add[axis]
|
||||||
|
self.reset_last_position()
|
||||||
|
cmd_M18_aliases = ["M84"]
|
||||||
|
def cmd_M18(self, params):
|
||||||
|
# Turn off motors
|
||||||
|
self.toolhead.motor_off()
|
||||||
|
def cmd_M400(self, params):
|
||||||
|
# Wait for current moves to finish
|
||||||
|
self.toolhead.wait_moves()
|
||||||
|
# G-Code coordinate manipulation
|
||||||
|
def cmd_G20(self, params):
|
||||||
|
# Set units to inches
|
||||||
|
self.respond_error('Machine does not support G20 (inches) command')
|
||||||
|
def cmd_M82(self, params):
|
||||||
|
# Use absolute distances for extrusion
|
||||||
|
self.absoluteextrude = True
|
||||||
|
def cmd_M83(self, params):
|
||||||
|
# Use relative distances for extrusion
|
||||||
|
self.absoluteextrude = False
|
||||||
def cmd_G90(self, params):
|
def cmd_G90(self, params):
|
||||||
# Use absolute coordinates
|
# Use absolute coordinates
|
||||||
self.absolutecoord = True
|
self.absolutecoord = True
|
||||||
@@ -371,19 +458,40 @@ class GCodeParser:
|
|||||||
offsets = { p: self.get_float(a, params)
|
offsets = { p: self.get_float(a, params)
|
||||||
for a, p in self.axis2pos.items() if a in params }
|
for a, p in self.axis2pos.items() if a in params }
|
||||||
for p, offset in offsets.items():
|
for p, offset in offsets.items():
|
||||||
|
if p == 3:
|
||||||
|
offset *= self.extrude_factor
|
||||||
self.base_position[p] = self.last_position[p] - offset
|
self.base_position[p] = self.last_position[p] - offset
|
||||||
if not offsets:
|
if not offsets:
|
||||||
self.base_position = list(self.last_position)
|
self.base_position = list(self.last_position)
|
||||||
def cmd_M82(self, params):
|
cmd_M114_when_not_ready = True
|
||||||
# Use absolute distances for extrusion
|
def cmd_M114(self, params):
|
||||||
self.absoluteextrude = True
|
# Get Current Position
|
||||||
def cmd_M83(self, params):
|
p = [lp - bp for lp, bp in zip(self.last_position, self.base_position)]
|
||||||
# Use relative distances for extrusion
|
p[3] /= self.extrude_factor
|
||||||
self.absoluteextrude = False
|
self.respond("X:%.3f Y:%.3f Z:%.3f E:%.3f" % tuple(p))
|
||||||
cmd_M18_aliases = ["M84"]
|
def cmd_M206(self, params):
|
||||||
def cmd_M18(self, params):
|
# Set home offset
|
||||||
# Turn off motors
|
offsets = { self.axis2pos[a]: self.get_float(a, params)
|
||||||
self.toolhead.motor_off()
|
for a in 'XYZ' if a in params }
|
||||||
|
for p, offset in offsets.items():
|
||||||
|
self.base_position[p] += self.homing_add[p] - offset
|
||||||
|
self.homing_add[p] = offset
|
||||||
|
def cmd_M220(self, params):
|
||||||
|
# Set speed factor override percentage
|
||||||
|
value = self.get_float('S', params, 100.) / (60. * 100.)
|
||||||
|
if value <= 0.:
|
||||||
|
raise error("Invalid factor in '%s'" % (params['#original'],))
|
||||||
|
self.speed_factor = value
|
||||||
|
def cmd_M221(self, params):
|
||||||
|
# Set extrude factor override percentage
|
||||||
|
new_extrude_factor = self.get_float('S', params, 100.) / 100.
|
||||||
|
if new_extrude_factor <= 0.:
|
||||||
|
raise error("Invalid factor in '%s'" % (params['#original'],))
|
||||||
|
last_e_pos = self.last_position[3]
|
||||||
|
e_value = (last_e_pos - self.base_position[3]) / self.extrude_factor
|
||||||
|
self.base_position[3] = last_e_pos - e_value * new_extrude_factor
|
||||||
|
self.extrude_factor = new_extrude_factor
|
||||||
|
# G-Code temperature and fan commands
|
||||||
cmd_M105_when_not_ready = True
|
cmd_M105_when_not_ready = True
|
||||||
def cmd_M105(self, params):
|
def cmd_M105(self, params):
|
||||||
# Get Extruder Temperature
|
# Get Extruder Temperature
|
||||||
@@ -394,26 +502,6 @@ class GCodeParser:
|
|||||||
def cmd_M109(self, params):
|
def cmd_M109(self, params):
|
||||||
# Set Extruder Temperature and Wait
|
# Set Extruder Temperature and Wait
|
||||||
self.set_temp(params, wait=True)
|
self.set_temp(params, wait=True)
|
||||||
def cmd_M112(self, params):
|
|
||||||
# Emergency Stop
|
|
||||||
self.printer.invoke_shutdown("Shutdown due to M112 command")
|
|
||||||
cmd_M114_when_not_ready = True
|
|
||||||
def cmd_M114(self, params):
|
|
||||||
# Get Current Position
|
|
||||||
if self.toolhead is None:
|
|
||||||
self.cmd_default(params)
|
|
||||||
return
|
|
||||||
raw_pos = self.toolhead.query_endstops("get_mcu_position")
|
|
||||||
self.respond("X:%.3f Y:%.3f Z:%.3f E:%.3f Count %s" % (
|
|
||||||
self.last_position[0], self.last_position[1],
|
|
||||||
self.last_position[2], self.last_position[3],
|
|
||||||
" ".join(["%s:%d" % (n.upper(), p) for n, p in raw_pos])))
|
|
||||||
cmd_M115_when_not_ready = True
|
|
||||||
def cmd_M115(self, params):
|
|
||||||
# Get Firmware Version and Capabilities
|
|
||||||
software_version = self.printer.get_start_args().get('software_version')
|
|
||||||
kw = {"FIRMWARE_NAME": "Klipper", "FIRMWARE_VERSION": software_version}
|
|
||||||
self.ack(" ".join(["%s:%s" % (k, v) for k, v in kw.items()]))
|
|
||||||
def cmd_M140(self, params):
|
def cmd_M140(self, params):
|
||||||
# Set Bed Temperature
|
# Set Bed Temperature
|
||||||
self.set_temp(params, is_bed=True)
|
self.set_temp(params, is_bed=True)
|
||||||
@@ -426,16 +514,17 @@ class GCodeParser:
|
|||||||
def cmd_M107(self, params):
|
def cmd_M107(self, params):
|
||||||
# Turn fan off
|
# Turn fan off
|
||||||
self.set_fan_speed(0.)
|
self.set_fan_speed(0.)
|
||||||
def cmd_M206(self, params):
|
# G-Code miscellaneous commands
|
||||||
# Set home offset
|
cmd_M112_when_not_ready = True
|
||||||
offsets = { p: self.get_float(a, params)
|
def cmd_M112(self, params):
|
||||||
for a, p in self.axis2pos.items() if a in params }
|
# Emergency Stop
|
||||||
for p, offset in offsets.items():
|
self.printer.invoke_shutdown("Shutdown due to M112 command")
|
||||||
self.base_position[p] += self.homing_add[p] - offset
|
cmd_M115_when_not_ready = True
|
||||||
self.homing_add[p] = offset
|
def cmd_M115(self, params):
|
||||||
def cmd_M400(self, params):
|
# Get Firmware Version and Capabilities
|
||||||
# Wait for current moves to finish
|
software_version = self.printer.get_start_args().get('software_version')
|
||||||
self.toolhead.wait_moves()
|
kw = {"FIRMWARE_NAME": "Klipper", "FIRMWARE_VERSION": software_version}
|
||||||
|
self.ack(" ".join(["%s:%s" % (k, v) for k, v in kw.items()]))
|
||||||
cmd_IGNORE_when_not_ready = True
|
cmd_IGNORE_when_not_ready = True
|
||||||
cmd_IGNORE_aliases = ["G21", "M110", "M21"]
|
cmd_IGNORE_aliases = ["G21", "M110", "M21"]
|
||||||
def cmd_IGNORE(self, params):
|
def cmd_IGNORE(self, params):
|
||||||
@@ -445,57 +534,62 @@ class GCodeParser:
|
|||||||
cmd_QUERY_ENDSTOPS_aliases = ["M119"]
|
cmd_QUERY_ENDSTOPS_aliases = ["M119"]
|
||||||
def cmd_QUERY_ENDSTOPS(self, params):
|
def cmd_QUERY_ENDSTOPS(self, params):
|
||||||
# Get Endstop Status
|
# Get Endstop Status
|
||||||
if self.is_fileinput:
|
res = homing.query_endstops(self.toolhead)
|
||||||
return
|
|
||||||
try:
|
|
||||||
res = self.toolhead.query_endstops()
|
|
||||||
except homing.EndstopError as e:
|
|
||||||
self.respond_error(str(e))
|
|
||||||
return
|
|
||||||
self.respond(" ".join(["%s:%s" % (name, ["open", "TRIGGERED"][not not t])
|
self.respond(" ".join(["%s:%s" % (name, ["open", "TRIGGERED"][not not t])
|
||||||
for name, t in res]))
|
for name, t in res]))
|
||||||
cmd_PID_TUNE_help = "Run PID Tuning"
|
cmd_GET_POSITION_when_not_ready = True
|
||||||
cmd_PID_TUNE_aliases = ["M303"]
|
def cmd_GET_POSITION(self, params):
|
||||||
def cmd_PID_TUNE(self, params):
|
if self.toolhead is None:
|
||||||
# Run PID tuning
|
self.cmd_default(params)
|
||||||
heater_index = self.get_int('E', params, 0)
|
|
||||||
if (heater_index < -1 or heater_index >= len(self.heaters) - 1
|
|
||||||
or self.heaters[heater_index] is None):
|
|
||||||
self.respond_error("Heater not configured")
|
|
||||||
heater = self.heaters[heater_index]
|
|
||||||
temp = self.get_float('S', params)
|
|
||||||
heater.start_auto_tune(temp)
|
|
||||||
self.bg_temp(heater)
|
|
||||||
cmd_SET_SERVO_help = "Set servo angle"
|
|
||||||
def cmd_SET_SERVO(self, params):
|
|
||||||
params = self.get_extended_params(params)
|
|
||||||
name = params.get('SERVO')
|
|
||||||
if name is None:
|
|
||||||
raise error("Error on '%s': missing SERVO" % (params['#original'],))
|
|
||||||
s = chipmisc.get_printer_servo(self.printer, name)
|
|
||||||
if s is None:
|
|
||||||
raise error("Servo not configured")
|
|
||||||
print_time = self.toolhead.get_last_move_time()
|
|
||||||
if 'WIDTH' in params:
|
|
||||||
s.set_pulse_width(print_time, self.get_float('WIDTH', params))
|
|
||||||
return
|
return
|
||||||
s.set_angle(print_time, self.get_float('ANGLE', params))
|
kin = self.toolhead.get_kinematics()
|
||||||
def prep_restart(self):
|
steppers = kin.get_steppers()
|
||||||
|
mcu_pos = " ".join(["%s:%d" % (s.name, s.mcu_stepper.get_mcu_position())
|
||||||
|
for s in steppers])
|
||||||
|
stepper_pos = " ".join(
|
||||||
|
["%s:%.6f" % (s.name, s.mcu_stepper.get_commanded_position())
|
||||||
|
for s in steppers])
|
||||||
|
kinematic_pos = " ".join(["%s:%.6f" % (a, v)
|
||||||
|
for a, v in zip("XYZE", kin.get_position())])
|
||||||
|
toolhead_pos = " ".join(["%s:%.6f" % (a, v) for a, v in zip(
|
||||||
|
"XYZE", self.toolhead.get_position())])
|
||||||
|
gcode_pos = " ".join(["%s:%.6f" % (a, v)
|
||||||
|
for a, v in zip("XYZE", self.last_position)])
|
||||||
|
origin_pos = " ".join(["%s:%.6f" % (a, v)
|
||||||
|
for a, v in zip("XYZE", self.base_position)])
|
||||||
|
homing_pos = " ".join(["%s:%.6f" % (a, v)
|
||||||
|
for a, v in zip("XYZE", self.homing_add)])
|
||||||
|
self.respond_info(
|
||||||
|
"mcu: %s\n"
|
||||||
|
"stepper: %s\n"
|
||||||
|
"kinematic: %s\n"
|
||||||
|
"toolhead: %s\n"
|
||||||
|
"gcode: %s\n"
|
||||||
|
"gcode origin: %s\n"
|
||||||
|
"gcode homing: %s" % (
|
||||||
|
mcu_pos, stepper_pos, kinematic_pos, toolhead_pos,
|
||||||
|
gcode_pos, origin_pos, homing_pos))
|
||||||
|
def request_restart(self, result):
|
||||||
if self.is_printer_ready:
|
if self.is_printer_ready:
|
||||||
self.respond_info("Preparing to restart...")
|
self.respond_info("Preparing to restart...")
|
||||||
self.motor_heater_off()
|
self.toolhead.motor_off()
|
||||||
|
print_time = self.toolhead.get_last_move_time()
|
||||||
|
for heater in self.heaters:
|
||||||
|
if heater is not None:
|
||||||
|
heater.set_temp(print_time, 0.)
|
||||||
|
if self.fan is not None:
|
||||||
|
self.fan.set_speed(print_time, 0.)
|
||||||
self.toolhead.dwell(0.500)
|
self.toolhead.dwell(0.500)
|
||||||
self.toolhead.wait_moves()
|
self.toolhead.wait_moves()
|
||||||
|
self.printer.request_exit(result)
|
||||||
cmd_RESTART_when_not_ready = True
|
cmd_RESTART_when_not_ready = True
|
||||||
cmd_RESTART_help = "Reload config file and restart host software"
|
cmd_RESTART_help = "Reload config file and restart host software"
|
||||||
def cmd_RESTART(self, params):
|
def cmd_RESTART(self, params):
|
||||||
self.prep_restart()
|
self.request_restart('restart')
|
||||||
self.printer.request_exit('restart')
|
|
||||||
cmd_FIRMWARE_RESTART_when_not_ready = True
|
cmd_FIRMWARE_RESTART_when_not_ready = True
|
||||||
cmd_FIRMWARE_RESTART_help = "Restart firmware, host, and reload config"
|
cmd_FIRMWARE_RESTART_help = "Restart firmware, host, and reload config"
|
||||||
def cmd_FIRMWARE_RESTART(self, params):
|
def cmd_FIRMWARE_RESTART(self, params):
|
||||||
self.prep_restart()
|
self.request_restart('firmware_restart')
|
||||||
self.printer.request_exit('firmware_restart')
|
|
||||||
cmd_ECHO_when_not_ready = True
|
cmd_ECHO_when_not_ready = True
|
||||||
def cmd_ECHO(self, params):
|
def cmd_ECHO(self, params):
|
||||||
self.respond_info(params['#original'])
|
self.respond_info(params['#original'])
|
||||||
@@ -514,10 +608,6 @@ class GCodeParser:
|
|||||||
cmdhelp.append("Printer is not ready - not all commands available.")
|
cmdhelp.append("Printer is not ready - not all commands available.")
|
||||||
cmdhelp.append("Available extended commands:")
|
cmdhelp.append("Available extended commands:")
|
||||||
for cmd in sorted(self.gcode_handlers):
|
for cmd in sorted(self.gcode_handlers):
|
||||||
desc = getattr(self, 'cmd_'+cmd+'_help', None)
|
if cmd in self.gcode_help:
|
||||||
if desc is not None:
|
cmdhelp.append("%-10s: %s" % (cmd, self.gcode_help[cmd]))
|
||||||
cmdhelp.append("%-10s: %s" % (cmd, desc))
|
|
||||||
self.respond_info("\n".join(cmdhelp))
|
self.respond_info("\n".join(cmdhelp))
|
||||||
|
|
||||||
class error(Exception):
|
|
||||||
pass
|
|
||||||
|
|||||||
163
klippy/heater.py
163
klippy/heater.py
@@ -1,6 +1,6 @@
|
|||||||
# Printer heater support
|
# Printer heater support
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import math, logging, threading
|
import math, logging, threading
|
||||||
@@ -17,7 +17,8 @@ KELVIN_TO_CELCIUS = -273.15
|
|||||||
class Thermistor:
|
class Thermistor:
|
||||||
def __init__(self, config, params):
|
def __init__(self, config, params):
|
||||||
self.pullup = config.getfloat('pullup_resistor', 4700., above=0.)
|
self.pullup = config.getfloat('pullup_resistor', 4700., above=0.)
|
||||||
# Calculate Steinhart-Hart coefficents from temp measurements
|
# Calculate Steinhart-Hart coefficents from temp measurements.
|
||||||
|
# Arrange samples as 3 linear equations and solve for c1, c2, and c3.
|
||||||
inv_t1 = 1. / (params['t1'] - KELVIN_TO_CELCIUS)
|
inv_t1 = 1. / (params['t1'] - KELVIN_TO_CELCIUS)
|
||||||
inv_t2 = 1. / (params['t2'] - KELVIN_TO_CELCIUS)
|
inv_t2 = 1. / (params['t2'] - KELVIN_TO_CELCIUS)
|
||||||
inv_t3 = 1. / (params['t3'] - KELVIN_TO_CELCIUS)
|
inv_t3 = 1. / (params['t3'] - KELVIN_TO_CELCIUS)
|
||||||
@@ -35,6 +36,7 @@ class Thermistor:
|
|||||||
self.c2 = (inv_t12 - self.c3 * ln3_r12) / ln_r12
|
self.c2 = (inv_t12 - self.c3 * ln3_r12) / ln_r12
|
||||||
self.c1 = inv_t1 - self.c2 * ln_r1 - self.c3 * ln3_r1
|
self.c1 = inv_t1 - self.c2 * ln_r1 - self.c3 * ln3_r1
|
||||||
def calc_temp(self, adc):
|
def calc_temp(self, adc):
|
||||||
|
adc = max(.00001, min(.99999, adc))
|
||||||
r = self.pullup * adc / (1.0 - adc)
|
r = self.pullup * adc / (1.0 - adc)
|
||||||
ln_r = math.log(r)
|
ln_r = math.log(r)
|
||||||
inv_t = self.c1 + self.c2 * ln_r + self.c3 * ln_r**3
|
inv_t = self.c1 + self.c2 * ln_r + self.c3 * ln_r**3
|
||||||
@@ -42,6 +44,7 @@ class Thermistor:
|
|||||||
def calc_adc(self, temp):
|
def calc_adc(self, temp):
|
||||||
inv_t = 1. / (temp - KELVIN_TO_CELCIUS)
|
inv_t = 1. / (temp - KELVIN_TO_CELCIUS)
|
||||||
if self.c3:
|
if self.c3:
|
||||||
|
# Solve for ln_r using Cardano's formula
|
||||||
y = (self.c1 - inv_t) / (2. * self.c3)
|
y = (self.c1 - inv_t) / (2. * self.c3)
|
||||||
x = math.sqrt((self.c2 / (3. * self.c3))**3 + y**2)
|
x = math.sqrt((self.c2 / (3. * self.c3))**3 + y**2)
|
||||||
ln_r = math.pow(x - y, 1./3.) - math.pow(x + y, 1./3.)
|
ln_r = math.pow(x - y, 1./3.) - math.pow(x + y, 1./3.)
|
||||||
@@ -94,10 +97,10 @@ Sensors = {
|
|||||||
SAMPLE_TIME = 0.001
|
SAMPLE_TIME = 0.001
|
||||||
SAMPLE_COUNT = 8
|
SAMPLE_COUNT = 8
|
||||||
REPORT_TIME = 0.300
|
REPORT_TIME = 0.300
|
||||||
PWM_CYCLE_TIME = 0.100
|
|
||||||
MAX_HEAT_TIME = 5.0
|
MAX_HEAT_TIME = 5.0
|
||||||
AMBIENT_TEMP = 25.
|
AMBIENT_TEMP = 25.
|
||||||
PID_PARAM_BASE = 255.
|
PID_PARAM_BASE = 255.
|
||||||
|
PWM_DELAY = REPORT_TIME + SAMPLE_TIME*SAMPLE_COUNT
|
||||||
|
|
||||||
class error(Exception):
|
class error(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -105,10 +108,11 @@ class error(Exception):
|
|||||||
class PrinterHeater:
|
class PrinterHeater:
|
||||||
error = error
|
error = error
|
||||||
def __init__(self, printer, config):
|
def __init__(self, printer, config):
|
||||||
self.name = config.section
|
self.printer = printer
|
||||||
|
self.name = config.get_name()
|
||||||
sensor_params = config.getchoice('sensor_type', Sensors)
|
sensor_params = config.getchoice('sensor_type', Sensors)
|
||||||
self.sensor = sensor_params['class'](config, sensor_params)
|
self.sensor = sensor_params['class'](config, sensor_params)
|
||||||
self.min_temp = config.getfloat('min_temp', minval=0.)
|
self.min_temp = config.getfloat('min_temp', minval=KELVIN_TO_CELCIUS)
|
||||||
self.max_temp = config.getfloat('max_temp', above=self.min_temp)
|
self.max_temp = config.getfloat('max_temp', above=self.min_temp)
|
||||||
self.min_extrude_temp = config.getfloat(
|
self.min_extrude_temp = config.getfloat(
|
||||||
'min_extrude_temp', 170., minval=self.min_temp, maxval=self.max_temp)
|
'min_extrude_temp', 170., minval=self.min_temp, maxval=self.max_temp)
|
||||||
@@ -124,7 +128,9 @@ class PrinterHeater:
|
|||||||
self.mcu_pwm = pins.setup_pin(printer, 'digital_out', heater_pin)
|
self.mcu_pwm = pins.setup_pin(printer, 'digital_out', heater_pin)
|
||||||
else:
|
else:
|
||||||
self.mcu_pwm = pins.setup_pin(printer, 'pwm', heater_pin)
|
self.mcu_pwm = pins.setup_pin(printer, 'pwm', heater_pin)
|
||||||
self.mcu_pwm.setup_cycle_time(PWM_CYCLE_TIME)
|
pwm_cycle_time = config.getfloat(
|
||||||
|
'pwm_cycle_time', 0.100, above=0., maxval=REPORT_TIME)
|
||||||
|
self.mcu_pwm.setup_cycle_time(pwm_cycle_time)
|
||||||
self.mcu_pwm.setup_max_duration(MAX_HEAT_TIME)
|
self.mcu_pwm.setup_max_duration(MAX_HEAT_TIME)
|
||||||
self.mcu_adc = pins.setup_pin(printer, 'adc', config.get('sensor_pin'))
|
self.mcu_adc = pins.setup_pin(printer, 'adc', config.get('sensor_pin'))
|
||||||
adc_range = [self.sensor.calc_adc(self.min_temp),
|
adc_range = [self.sensor.calc_adc(self.min_temp),
|
||||||
@@ -137,7 +143,10 @@ class PrinterHeater:
|
|||||||
self.control = algo(self, config)
|
self.control = algo(self, config)
|
||||||
# pwm caching
|
# pwm caching
|
||||||
self.next_pwm_time = 0.
|
self.next_pwm_time = 0.
|
||||||
self.last_pwm_value = 0
|
self.last_pwm_value = 0.
|
||||||
|
# Load additional modules
|
||||||
|
printer.try_load_module(config, "verify_heater %s" % (self.name,))
|
||||||
|
printer.try_load_module(config, "pid_calibrate")
|
||||||
def set_pwm(self, read_time, value):
|
def set_pwm(self, read_time, value):
|
||||||
if self.target_temp <= 0.:
|
if self.target_temp <= 0.:
|
||||||
value = 0.
|
value = 0.
|
||||||
@@ -145,7 +154,7 @@ class PrinterHeater:
|
|||||||
and abs(value - self.last_pwm_value) < 0.05):
|
and abs(value - self.last_pwm_value) < 0.05):
|
||||||
# No significant change in value - can suppress update
|
# No significant change in value - can suppress update
|
||||||
return
|
return
|
||||||
pwm_time = read_time + REPORT_TIME + SAMPLE_TIME*SAMPLE_COUNT
|
pwm_time = read_time + PWM_DELAY
|
||||||
self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME
|
self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME
|
||||||
self.last_pwm_value = value
|
self.last_pwm_value = value
|
||||||
logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])",
|
logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])",
|
||||||
@@ -176,16 +185,25 @@ class PrinterHeater:
|
|||||||
def check_busy(self, eventtime):
|
def check_busy(self, eventtime):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
return self.control.check_busy(eventtime)
|
return self.control.check_busy(eventtime)
|
||||||
def start_auto_tune(self, degrees):
|
def set_control(self, control):
|
||||||
if degrees and (degrees < self.min_temp or degrees > self.max_temp):
|
|
||||||
raise error("Requested temperature (%.1f) out of range (%.1f:%.1f)"
|
|
||||||
% (degrees, self.min_temp, self.max_temp))
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.control = ControlAutoTune(self, self.control)
|
old_control = self.control
|
||||||
self.target_temp = degrees
|
self.control = control
|
||||||
def finish_auto_tune(self, old_control):
|
self.target_temp = 0.
|
||||||
self.control = old_control
|
return old_control
|
||||||
self.target_temp = 0
|
def stats(self, eventtime):
|
||||||
|
with self.lock:
|
||||||
|
target_temp = self.target_temp
|
||||||
|
last_temp = self.last_temp
|
||||||
|
last_pwm_value = self.last_pwm_value
|
||||||
|
is_active = target_temp or last_temp > 50.
|
||||||
|
return is_active, '%s: target=%.0f temp=%.1f pwm=%.3f' % (
|
||||||
|
self.name, target_temp, last_temp, last_pwm_value)
|
||||||
|
def get_status(self, eventtime):
|
||||||
|
with self.lock:
|
||||||
|
target_temp = self.target_temp
|
||||||
|
last_temp = self.last_temp
|
||||||
|
return {'temperature': last_temp, 'target': target_temp}
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
@@ -214,6 +232,9 @@ class ControlBangBang:
|
|||||||
# Proportional Integral Derivative (PID) control algo
|
# Proportional Integral Derivative (PID) control algo
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
PID_SETTLE_DELTA = 1.
|
||||||
|
PID_SETTLE_SLOPE = .1
|
||||||
|
|
||||||
class ControlPID:
|
class ControlPID:
|
||||||
def __init__(self, heater, config):
|
def __init__(self, heater, config):
|
||||||
self.heater = heater
|
self.heater = heater
|
||||||
@@ -254,112 +275,8 @@ class ControlPID:
|
|||||||
self.prev_temp_integ = temp_integ
|
self.prev_temp_integ = temp_integ
|
||||||
def check_busy(self, eventtime):
|
def check_busy(self, eventtime):
|
||||||
temp_diff = self.heater.target_temp - self.heater.last_temp
|
temp_diff = self.heater.target_temp - self.heater.last_temp
|
||||||
return abs(temp_diff) > 1. or abs(self.prev_temp_deriv) > 0.1
|
return (abs(temp_diff) > PID_SETTLE_DELTA
|
||||||
|
or abs(self.prev_temp_deriv) > PID_SETTLE_SLOPE)
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# Ziegler-Nichols PID autotuning
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
TUNE_PID_DELTA = 5.0
|
|
||||||
|
|
||||||
class ControlAutoTune:
|
|
||||||
def __init__(self, heater, old_control):
|
|
||||||
self.heater = heater
|
|
||||||
self.old_control = old_control
|
|
||||||
self.heating = False
|
|
||||||
self.peaks = []
|
|
||||||
self.peak = 0.
|
|
||||||
self.peak_time = 0.
|
|
||||||
def adc_callback(self, read_time, temp):
|
|
||||||
if self.heating and temp >= self.heater.target_temp:
|
|
||||||
self.heating = False
|
|
||||||
self.check_peaks()
|
|
||||||
elif (not self.heating
|
|
||||||
and temp <= self.heater.target_temp - TUNE_PID_DELTA):
|
|
||||||
self.heating = True
|
|
||||||
self.check_peaks()
|
|
||||||
if self.heating:
|
|
||||||
self.heater.set_pwm(read_time, self.heater.max_power)
|
|
||||||
if temp < self.peak:
|
|
||||||
self.peak = temp
|
|
||||||
self.peak_time = read_time
|
|
||||||
else:
|
|
||||||
self.heater.set_pwm(read_time, 0.)
|
|
||||||
if temp > self.peak:
|
|
||||||
self.peak = temp
|
|
||||||
self.peak_time = read_time
|
|
||||||
def check_peaks(self):
|
|
||||||
self.peaks.append((self.peak, self.peak_time))
|
|
||||||
if self.heating:
|
|
||||||
self.peak = 9999999.
|
|
||||||
else:
|
|
||||||
self.peak = -9999999.
|
|
||||||
if len(self.peaks) < 4:
|
|
||||||
return
|
|
||||||
temp_diff = self.peaks[-1][0] - self.peaks[-2][0]
|
|
||||||
time_diff = self.peaks[-1][1] - self.peaks[-3][1]
|
|
||||||
max_power = self.heater.max_power
|
|
||||||
Ku = 4. * (2. * max_power) / (abs(temp_diff) * math.pi)
|
|
||||||
Tu = time_diff
|
|
||||||
|
|
||||||
Kp = 0.6 * Ku
|
|
||||||
Ti = 0.5 * Tu
|
|
||||||
Td = 0.125 * Tu
|
|
||||||
Ki = Kp / Ti
|
|
||||||
Kd = Kp * Td
|
|
||||||
logging.info("Autotune: raw=%f/%f Ku=%f Tu=%f Kp=%f Ki=%f Kd=%f",
|
|
||||||
temp_diff, max_power, Ku, Tu, Kp * PID_PARAM_BASE,
|
|
||||||
Ki * PID_PARAM_BASE, Kd * PID_PARAM_BASE)
|
|
||||||
def check_busy(self, eventtime):
|
|
||||||
if self.heating or len(self.peaks) < 12:
|
|
||||||
return True
|
|
||||||
self.heater.finish_auto_tune(self.old_control)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# Tuning information test
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
class ControlBumpTest:
|
|
||||||
def __init__(self, heater, old_control):
|
|
||||||
self.heater = heater
|
|
||||||
self.old_control = old_control
|
|
||||||
self.temp_samples = {}
|
|
||||||
self.pwm_samples = {}
|
|
||||||
self.state = 0
|
|
||||||
def set_pwm(self, read_time, value):
|
|
||||||
self.pwm_samples[read_time + 2*REPORT_TIME] = value
|
|
||||||
self.heater.set_pwm(read_time, value)
|
|
||||||
def adc_callback(self, read_time, temp):
|
|
||||||
self.temp_samples[read_time] = temp
|
|
||||||
if not self.state:
|
|
||||||
self.set_pwm(read_time, 0.)
|
|
||||||
if len(self.temp_samples) >= 20:
|
|
||||||
self.state += 1
|
|
||||||
elif self.state == 1:
|
|
||||||
if temp < self.heater.target_temp:
|
|
||||||
self.set_pwm(read_time, self.heater.max_power)
|
|
||||||
return
|
|
||||||
self.set_pwm(read_time, 0.)
|
|
||||||
self.state += 1
|
|
||||||
elif self.state == 2:
|
|
||||||
self.set_pwm(read_time, 0.)
|
|
||||||
if temp <= (self.heater.target_temp + AMBIENT_TEMP) / 2.:
|
|
||||||
self.dump_stats()
|
|
||||||
self.state += 1
|
|
||||||
def dump_stats(self):
|
|
||||||
out = ["%.3f %.1f %d" % (time, temp, self.pwm_samples.get(time, -1.))
|
|
||||||
for time, temp in sorted(self.temp_samples.items())]
|
|
||||||
f = open("/tmp/heattest.txt", "wb")
|
|
||||||
f.write('\n'.join(out))
|
|
||||||
f.close()
|
|
||||||
def check_busy(self, eventtime):
|
|
||||||
if self.state < 3:
|
|
||||||
return True
|
|
||||||
self.heater.finish_auto_tune(self.old_control)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def add_printer_objects(printer, config):
|
def add_printer_objects(printer, config):
|
||||||
if config.has_section('heater_bed'):
|
if config.has_section('heater_bed'):
|
||||||
|
|||||||
134
klippy/homing.py
134
klippy/homing.py
@@ -3,16 +3,16 @@
|
|||||||
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging
|
import logging, math
|
||||||
|
|
||||||
HOMING_DELAY = 0.250
|
HOMING_STEP_DELAY = 0.00000025
|
||||||
ENDSTOP_SAMPLE_TIME = .000015
|
ENDSTOP_SAMPLE_TIME = .000015
|
||||||
ENDSTOP_SAMPLE_COUNT = 4
|
ENDSTOP_SAMPLE_COUNT = 4
|
||||||
|
|
||||||
class Homing:
|
class Homing:
|
||||||
def __init__(self, toolhead, changed_axes):
|
def __init__(self, toolhead):
|
||||||
self.toolhead = toolhead
|
self.toolhead = toolhead
|
||||||
self.changed_axes = changed_axes
|
self.changed_axes = []
|
||||||
self.verify_retract = True
|
self.verify_retract = True
|
||||||
def set_no_verify_retract(self):
|
def set_no_verify_retract(self):
|
||||||
self.verify_retract = False
|
self.verify_retract = False
|
||||||
@@ -29,50 +29,98 @@ class Homing:
|
|||||||
return thcoord
|
return thcoord
|
||||||
def retract(self, newpos, speed):
|
def retract(self, newpos, speed):
|
||||||
self.toolhead.move(self._fill_coord(newpos), speed)
|
self.toolhead.move(self._fill_coord(newpos), speed)
|
||||||
def home(self, forcepos, movepos, steppers, speed, second_home=False):
|
|
||||||
# Alter kinematics class to think printer is at forcepos
|
|
||||||
self.toolhead.set_position(self._fill_coord(forcepos))
|
|
||||||
# Start homing and issue move
|
|
||||||
if not second_home:
|
|
||||||
self.toolhead.dwell(HOMING_DELAY)
|
|
||||||
print_time = self.toolhead.get_last_move_time()
|
|
||||||
endstops = []
|
|
||||||
for s in steppers:
|
|
||||||
s.mcu_endstop.home_start(print_time, ENDSTOP_SAMPLE_TIME,
|
|
||||||
ENDSTOP_SAMPLE_COUNT, s.step_dist / speed)
|
|
||||||
endstops.append((s, s.mcu_stepper.get_mcu_position()))
|
|
||||||
self.toolhead.move(self._fill_coord(movepos), speed)
|
|
||||||
move_end_print_time = self.toolhead.get_last_move_time()
|
|
||||||
self.toolhead.reset_print_time(print_time)
|
|
||||||
for s, last_pos in endstops:
|
|
||||||
s.mcu_endstop.home_finalize(move_end_print_time)
|
|
||||||
# Wait for endstops to trigger
|
|
||||||
for s, last_pos in endstops:
|
|
||||||
try:
|
|
||||||
s.mcu_endstop.home_wait()
|
|
||||||
except s.mcu_endstop.error as e:
|
|
||||||
raise EndstopError("Failed to home stepper %s: %s" % (
|
|
||||||
s.name, str(e)))
|
|
||||||
post_home_pos = s.mcu_stepper.get_mcu_position()
|
|
||||||
if second_home and self.verify_retract and last_pos == post_home_pos:
|
|
||||||
raise EndstopError("Endstop %s still triggered after retract" % (
|
|
||||||
s.name,))
|
|
||||||
def set_homed_position(self, pos):
|
def set_homed_position(self, pos):
|
||||||
self.toolhead.set_position(self._fill_coord(pos))
|
self.toolhead.set_position(self._fill_coord(pos))
|
||||||
|
def _get_homing_speed(self, speed, endstops):
|
||||||
|
# Round the requested homing speed so that it is an even
|
||||||
|
# number of ticks per step.
|
||||||
|
speed = min(speed, self.toolhead.get_max_velocity()[0])
|
||||||
|
mcu_stepper = endstops[0][0].get_steppers()[0]
|
||||||
|
adjusted_freq = mcu_stepper.get_mcu().get_adjusted_freq()
|
||||||
|
dist_ticks = adjusted_freq * mcu_stepper.get_step_dist()
|
||||||
|
ticks_per_step = math.ceil(dist_ticks / speed)
|
||||||
|
return dist_ticks / ticks_per_step
|
||||||
|
def homing_move(self, movepos, endstops, speed, probe_pos=False):
|
||||||
|
# Start endstop checking
|
||||||
|
for mcu_endstop, name in endstops:
|
||||||
|
mcu_endstop.home_prepare()
|
||||||
|
print_time = self.toolhead.get_last_move_time()
|
||||||
|
for mcu_endstop, name in endstops:
|
||||||
|
min_step_dist = min([s.get_step_dist()
|
||||||
|
for s in mcu_endstop.get_steppers()])
|
||||||
|
mcu_endstop.home_start(
|
||||||
|
print_time, ENDSTOP_SAMPLE_TIME, ENDSTOP_SAMPLE_COUNT,
|
||||||
|
min_step_dist / speed)
|
||||||
|
# Issue move
|
||||||
|
movepos = self._fill_coord(movepos)
|
||||||
|
error = None
|
||||||
|
try:
|
||||||
|
self.toolhead.move(movepos, speed)
|
||||||
|
except EndstopError as e:
|
||||||
|
error = "Error during homing move: %s" % (str(e),)
|
||||||
|
# Wait for endstops to trigger
|
||||||
|
move_end_print_time = self.toolhead.get_last_move_time()
|
||||||
|
self.toolhead.reset_print_time(print_time)
|
||||||
|
for mcu_endstop, name in endstops:
|
||||||
|
try:
|
||||||
|
mcu_endstop.home_wait(move_end_print_time)
|
||||||
|
except mcu_endstop.TimeoutError as e:
|
||||||
|
if error is None:
|
||||||
|
error = "Failed to home %s: %s" % (name, str(e))
|
||||||
|
if probe_pos:
|
||||||
|
self.set_homed_position(
|
||||||
|
list(self.toolhead.get_kinematics().get_position()) + [None])
|
||||||
|
else:
|
||||||
|
self.toolhead.set_position(movepos)
|
||||||
|
for mcu_endstop, name in endstops:
|
||||||
|
mcu_endstop.home_finalize()
|
||||||
|
if error is not None:
|
||||||
|
raise EndstopError(error)
|
||||||
|
def home(self, forcepos, movepos, endstops, speed, second_home=False):
|
||||||
|
if second_home and forcepos == movepos:
|
||||||
|
return
|
||||||
|
# Alter kinematics class to think printer is at forcepos
|
||||||
|
homing_axes = [axis for axis in range(3) if forcepos[axis] is not None]
|
||||||
|
self.toolhead.set_position(
|
||||||
|
self._fill_coord(forcepos), homing_axes=homing_axes)
|
||||||
|
# Add a CPU delay when homing a large axis
|
||||||
|
if not second_home:
|
||||||
|
est_move_d = sum([abs(forcepos[i]-movepos[i])
|
||||||
|
for i in range(3) if movepos[i] is not None])
|
||||||
|
est_steps = sum([est_move_d / s.get_step_dist()
|
||||||
|
for es, n in endstops for s in es.get_steppers()])
|
||||||
|
self.toolhead.dwell(est_steps * HOMING_STEP_DELAY, check_stall=False)
|
||||||
|
speed = self._get_homing_speed(speed, endstops)
|
||||||
|
# Setup for retract verification
|
||||||
|
self.toolhead.get_last_move_time()
|
||||||
|
start_mcu_pos = [(s, name, s.get_mcu_position())
|
||||||
|
for es, name in endstops for s in es.get_steppers()]
|
||||||
|
# Issue homing move
|
||||||
|
self.homing_move(movepos, endstops, speed)
|
||||||
|
# Verify retract led to some movement on second home
|
||||||
|
if second_home and self.verify_retract:
|
||||||
|
for s, name, pos in start_mcu_pos:
|
||||||
|
if s.get_mcu_position() == pos:
|
||||||
|
raise EndstopError(
|
||||||
|
"Endstop %s still triggered after retract" % (name,))
|
||||||
|
def home_axes(self, axes):
|
||||||
|
self.changed_axes = axes
|
||||||
|
try:
|
||||||
|
self.toolhead.get_kinematics().home(self)
|
||||||
|
except EndstopError:
|
||||||
|
self.toolhead.motor_off()
|
||||||
|
raise
|
||||||
|
|
||||||
def query_endstops(print_time, query_flags, steppers):
|
def query_endstops(toolhead):
|
||||||
if query_flags == "get_mcu_position":
|
print_time = toolhead.get_last_move_time()
|
||||||
# Only the commanded position is requested
|
steppers = toolhead.get_kinematics().get_steppers()
|
||||||
return [(s.name.upper(), s.mcu_stepper.get_mcu_position())
|
|
||||||
for s in steppers]
|
|
||||||
for s in steppers:
|
|
||||||
s.mcu_endstop.query_endstop(print_time)
|
|
||||||
out = []
|
out = []
|
||||||
for s in steppers:
|
for s in steppers:
|
||||||
try:
|
for mcu_endstop, name in s.get_endstops():
|
||||||
out.append((s.name, s.mcu_endstop.query_endstop_wait()))
|
mcu_endstop.query_endstop(print_time)
|
||||||
except s.mcu_endstop.error as e:
|
for s in steppers:
|
||||||
raise EndstopError(str(e))
|
for mcu_endstop, name in s.get_endstops():
|
||||||
|
out.append((name, mcu_endstop.query_endstop_wait()))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
class EndstopError(Exception):
|
class EndstopError(Exception):
|
||||||
|
|||||||
177
klippy/klippy.py
177
klippy/klippy.py
@@ -1,19 +1,20 @@
|
|||||||
#!/usr/bin/env python2
|
#!/usr/bin/env python2
|
||||||
# Main code for host side printer firmware
|
# Main code for host side printer firmware
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import sys, optparse, ConfigParser, logging, time, threading
|
import sys, os, optparse, logging, time, threading
|
||||||
import util, reactor, queuelogger, msgproto, gcode
|
import collections, ConfigParser, importlib
|
||||||
import pins, mcu, chipmisc, toolhead, extruder, heater, fan
|
import util, reactor, queuelogger, msgproto
|
||||||
|
import gcode, pins, mcu, toolhead, extruder, heater
|
||||||
|
|
||||||
message_ready = "Printer is ready"
|
message_ready = "Printer is ready"
|
||||||
|
|
||||||
message_startup = """
|
message_startup = """
|
||||||
|
Printer is not ready
|
||||||
The klippy host software is attempting to connect. Please
|
The klippy host software is attempting to connect. Please
|
||||||
retry in a few moments.
|
retry in a few moments.
|
||||||
Printer is not ready
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
message_restart = """
|
message_restart = """
|
||||||
@@ -49,13 +50,18 @@ class ConfigWrapper:
|
|||||||
error = ConfigParser.Error
|
error = ConfigParser.Error
|
||||||
class sentinel:
|
class sentinel:
|
||||||
pass
|
pass
|
||||||
def __init__(self, printer, section):
|
def __init__(self, printer, fileconfig, section):
|
||||||
self.printer = printer
|
self.printer = printer
|
||||||
|
self.fileconfig = fileconfig
|
||||||
self.section = section
|
self.section = section
|
||||||
def get_wrapper(self, parser, option, default
|
def get_printer(self):
|
||||||
, minval=None, maxval=None, above=None, below=None):
|
return self.printer
|
||||||
|
def get_name(self):
|
||||||
|
return self.section
|
||||||
|
def _get_wrapper(self, parser, option, default,
|
||||||
|
minval=None, maxval=None, above=None, below=None):
|
||||||
if (default is not self.sentinel
|
if (default is not self.sentinel
|
||||||
and not self.printer.fileconfig.has_option(self.section, option)):
|
and not self.fileconfig.has_option(self.section, option)):
|
||||||
return default
|
return default
|
||||||
self.printer.all_config_options[
|
self.printer.all_config_options[
|
||||||
(self.section.lower(), option.lower())] = 1
|
(self.section.lower(), option.lower())] = 1
|
||||||
@@ -84,18 +90,16 @@ class ConfigWrapper:
|
|||||||
option, self.section, below))
|
option, self.section, below))
|
||||||
return v
|
return v
|
||||||
def get(self, option, default=sentinel):
|
def get(self, option, default=sentinel):
|
||||||
return self.get_wrapper(self.printer.fileconfig.get, option, default)
|
return self._get_wrapper(self.fileconfig.get, option, default)
|
||||||
def getint(self, option, default=sentinel, minval=None, maxval=None):
|
def getint(self, option, default=sentinel, minval=None, maxval=None):
|
||||||
return self.get_wrapper(
|
return self._get_wrapper(
|
||||||
self.printer.fileconfig.getint, option, default, minval, maxval)
|
self.fileconfig.getint, option, default, minval, maxval)
|
||||||
def getfloat(self, option, default=sentinel
|
def getfloat(self, option, default=sentinel,
|
||||||
, minval=None, maxval=None, above=None, below=None):
|
minval=None, maxval=None, above=None, below=None):
|
||||||
return self.get_wrapper(
|
return self._get_wrapper(self.fileconfig.getfloat, option, default,
|
||||||
self.printer.fileconfig.getfloat, option, default
|
minval, maxval, above, below)
|
||||||
, minval, maxval, above, below)
|
|
||||||
def getboolean(self, option, default=sentinel):
|
def getboolean(self, option, default=sentinel):
|
||||||
return self.get_wrapper(
|
return self._get_wrapper(self.fileconfig.getboolean, option, default)
|
||||||
self.printer.fileconfig.getboolean, option, default)
|
|
||||||
def getchoice(self, option, choices, default=sentinel):
|
def getchoice(self, option, choices, default=sentinel):
|
||||||
c = self.get(option, default)
|
c = self.get(option, default)
|
||||||
if c not in choices:
|
if c not in choices:
|
||||||
@@ -104,11 +108,11 @@ class ConfigWrapper:
|
|||||||
option, self.section))
|
option, self.section))
|
||||||
return choices[c]
|
return choices[c]
|
||||||
def getsection(self, section):
|
def getsection(self, section):
|
||||||
return ConfigWrapper(self.printer, section)
|
return ConfigWrapper(self.printer, self.fileconfig, section)
|
||||||
def has_section(self, section):
|
def has_section(self, section):
|
||||||
return self.printer.fileconfig.has_section(section)
|
return self.fileconfig.has_section(section)
|
||||||
def get_prefix_sections(self, prefix):
|
def get_prefix_sections(self, prefix):
|
||||||
return [self.getsection(s) for s in self.printer.fileconfig.sections()
|
return [self.getsection(s) for s in self.fileconfig.sections()
|
||||||
if s.startswith(prefix)]
|
if s.startswith(prefix)]
|
||||||
|
|
||||||
class ConfigLogger():
|
class ConfigLogger():
|
||||||
@@ -130,8 +134,8 @@ class Printer:
|
|||||||
if bglogger is not None:
|
if bglogger is not None:
|
||||||
bglogger.set_rollover_info("config", None)
|
bglogger.set_rollover_info("config", None)
|
||||||
self.reactor = reactor.Reactor()
|
self.reactor = reactor.Reactor()
|
||||||
self.objects = {}
|
gc = gcode.GCodeParser(self, input_fd)
|
||||||
self.gcode = gcode.GCodeParser(self, input_fd)
|
self.objects = collections.OrderedDict({'gcode': gc})
|
||||||
self.stats_timer = self.reactor.register_timer(self._stats)
|
self.stats_timer = self.reactor.register_timer(self._stats)
|
||||||
self.connect_timer = self.reactor.register_timer(
|
self.connect_timer = self.reactor.register_timer(
|
||||||
self._connect, self.reactor.NOW)
|
self._connect, self.reactor.NOW)
|
||||||
@@ -140,61 +144,104 @@ class Printer:
|
|||||||
self.is_shutdown = False
|
self.is_shutdown = False
|
||||||
self.async_shutdown_msg = ""
|
self.async_shutdown_msg = ""
|
||||||
self.run_result = None
|
self.run_result = None
|
||||||
self.fileconfig = None
|
self.stats_cb = []
|
||||||
self.mcus = []
|
self.state_cb = []
|
||||||
def get_start_args(self):
|
def get_start_args(self):
|
||||||
return self.start_args
|
return self.start_args
|
||||||
def _stats(self, eventtime, force_output=False):
|
def get_reactor(self):
|
||||||
toolhead = self.objects.get('toolhead')
|
return self.reactor
|
||||||
if toolhead is None:
|
def get_state_message(self):
|
||||||
return eventtime + 1.
|
return self.state_message
|
||||||
is_active = toolhead.check_active(eventtime)
|
|
||||||
if not is_active and not force_output:
|
|
||||||
return eventtime + 1.
|
|
||||||
out = []
|
|
||||||
out.append(self.gcode.stats(eventtime))
|
|
||||||
out.append(toolhead.stats(eventtime))
|
|
||||||
for m in self.mcus:
|
|
||||||
out.append(m.stats(eventtime))
|
|
||||||
logging.info("Stats %.1f: %s", eventtime, ' '.join(out))
|
|
||||||
return eventtime + 1.
|
|
||||||
def add_object(self, name, obj):
|
def add_object(self, name, obj):
|
||||||
|
if obj in self.objects:
|
||||||
|
raise self.config_error(
|
||||||
|
"Printer object '%s' already created" % (name,))
|
||||||
self.objects[name] = obj
|
self.objects[name] = obj
|
||||||
def _load_config(self):
|
def lookup_object(self, name, default=ConfigWrapper.sentinel):
|
||||||
self.fileconfig = ConfigParser.RawConfigParser()
|
if name in self.objects:
|
||||||
|
return self.objects[name]
|
||||||
|
if default is ConfigWrapper.sentinel:
|
||||||
|
raise self.config_error("Unknown config object '%s'" % (name,))
|
||||||
|
return default
|
||||||
|
def lookup_module_objects(self, module_name):
|
||||||
|
prefix = module_name + ' '
|
||||||
|
objs = [self.objects[n] for n in self.objects if n.startswith(prefix)]
|
||||||
|
if module_name in self.objects:
|
||||||
|
return [self.objects[module_name]] + objs
|
||||||
|
return objs
|
||||||
|
def set_rollover_info(self, name, info):
|
||||||
|
if self.bglogger is not None:
|
||||||
|
self.bglogger.set_rollover_info(name, info)
|
||||||
|
def _stats(self, eventtime, force_output=False):
|
||||||
|
stats = [cb(eventtime) for cb in self.stats_cb]
|
||||||
|
if max([s[0] for s in stats] + [force_output]):
|
||||||
|
logging.info("Stats %.1f: %s", eventtime,
|
||||||
|
' '.join([s[1] for s in stats]))
|
||||||
|
return eventtime + 1.
|
||||||
|
def try_load_module(self, config, section):
|
||||||
|
if section in self.objects:
|
||||||
|
return
|
||||||
|
module_parts = section.split()
|
||||||
|
module_name = module_parts[0]
|
||||||
|
py_name = os.path.join(os.path.dirname(__file__),
|
||||||
|
'extras', module_name + '.py')
|
||||||
|
if not os.path.exists(py_name):
|
||||||
|
return
|
||||||
|
mod = importlib.import_module('extras.' + module_name)
|
||||||
|
init_func = 'load_config'
|
||||||
|
if len(module_parts) > 1:
|
||||||
|
init_func = 'load_config_prefix'
|
||||||
|
init_func = getattr(mod, init_func, None)
|
||||||
|
if init_func is not None:
|
||||||
|
self.objects[section] = init_func(config.getsection(section))
|
||||||
|
def _read_config(self):
|
||||||
|
fileconfig = ConfigParser.RawConfigParser()
|
||||||
config_file = self.start_args['config_file']
|
config_file = self.start_args['config_file']
|
||||||
res = self.fileconfig.read(config_file)
|
res = fileconfig.read(config_file)
|
||||||
if not res:
|
if not res:
|
||||||
raise self.config_error("Unable to open config file %s" % (
|
raise self.config_error("Unable to open config file %s" % (
|
||||||
config_file,))
|
config_file,))
|
||||||
if self.bglogger is not None:
|
if self.bglogger is not None:
|
||||||
ConfigLogger(self.fileconfig, self.bglogger)
|
ConfigLogger(fileconfig, self.bglogger)
|
||||||
# Create printer components
|
# Create printer components
|
||||||
config = ConfigWrapper(self, 'printer')
|
config = ConfigWrapper(self, fileconfig, 'printer')
|
||||||
for m in [pins, mcu, chipmisc, toolhead, extruder, heater, fan]:
|
for m in [pins, mcu]:
|
||||||
|
m.add_printer_objects(self, config)
|
||||||
|
for section in fileconfig.sections():
|
||||||
|
self.try_load_module(config, section)
|
||||||
|
for m in [toolhead, extruder, heater]:
|
||||||
m.add_printer_objects(self, config)
|
m.add_printer_objects(self, config)
|
||||||
self.mcus = mcu.get_printer_mcus(self)
|
|
||||||
# Validate that there are no undefined parameters in the config file
|
# Validate that there are no undefined parameters in the config file
|
||||||
valid_sections = { s: 1 for s, o in self.all_config_options }
|
valid_sections = { s: 1 for s, o in self.all_config_options }
|
||||||
for section in self.fileconfig.sections():
|
for section in fileconfig.sections():
|
||||||
section = section.lower()
|
section = section.lower()
|
||||||
if section not in valid_sections:
|
if section not in valid_sections and section not in self.objects:
|
||||||
raise self.config_error("Unknown config file section '%s'" % (
|
raise self.config_error("Unknown config file section '%s'" % (
|
||||||
section,))
|
section,))
|
||||||
for option in self.fileconfig.options(section):
|
for option in fileconfig.options(section):
|
||||||
option = option.lower()
|
option = option.lower()
|
||||||
if (section, option) not in self.all_config_options:
|
if (section, option) not in self.all_config_options:
|
||||||
raise self.config_error(
|
raise self.config_error(
|
||||||
"Unknown option '%s' in section '%s'" % (
|
"Unknown option '%s' in section '%s'" % (
|
||||||
option, section))
|
option, section))
|
||||||
|
# Determine which printer objects have stats/state callbacks
|
||||||
|
self.stats_cb = [o.stats for o in self.objects.values()
|
||||||
|
if hasattr(o, 'stats')]
|
||||||
|
self.state_cb = [o.printer_state for o in self.objects.values()
|
||||||
|
if hasattr(o, 'printer_state')]
|
||||||
def _connect(self, eventtime):
|
def _connect(self, eventtime):
|
||||||
self.reactor.unregister_timer(self.connect_timer)
|
self.reactor.unregister_timer(self.connect_timer)
|
||||||
try:
|
try:
|
||||||
self._load_config()
|
self._read_config()
|
||||||
for m in self.mcus:
|
for cb in self.state_cb:
|
||||||
m.connect()
|
if self.state_message is not message_startup:
|
||||||
self.gcode.connect()
|
return self.reactor.NEVER
|
||||||
|
cb('connect')
|
||||||
self.state_message = message_ready
|
self.state_message = message_ready
|
||||||
|
for cb in self.state_cb:
|
||||||
|
if self.state_message is not message_ready:
|
||||||
|
return self.reactor.NEVER
|
||||||
|
cb('ready')
|
||||||
if self.start_args.get('debugoutput') is None:
|
if self.start_args.get('debugoutput') is None:
|
||||||
self.reactor.update_timer(self.stats_timer, self.reactor.NOW)
|
self.reactor.update_timer(self.stats_timer, self.reactor.NOW)
|
||||||
except (self.config_error, pins.error) as e:
|
except (self.config_error, pins.error) as e:
|
||||||
@@ -227,32 +274,24 @@ class Printer:
|
|||||||
run_result = self.run_result
|
run_result = self.run_result
|
||||||
try:
|
try:
|
||||||
if run_result == 'shutdown':
|
if run_result == 'shutdown':
|
||||||
self.invoke_shutdown(self.async_shutdown_msg, True)
|
self.invoke_shutdown(self.async_shutdown_msg)
|
||||||
continue
|
continue
|
||||||
self._stats(self.reactor.monotonic(), force_output=True)
|
self._stats(self.reactor.monotonic(), force_output=True)
|
||||||
for m in self.mcus:
|
|
||||||
if run_result == 'firmware_restart':
|
if run_result == 'firmware_restart':
|
||||||
|
for m in self.lookup_module_objects('mcu'):
|
||||||
m.microcontroller_restart()
|
m.microcontroller_restart()
|
||||||
m.disconnect()
|
for cb in self.state_cb:
|
||||||
|
cb('disconnect')
|
||||||
except:
|
except:
|
||||||
logging.exception("Unhandled exception during post run")
|
logging.exception("Unhandled exception during post run")
|
||||||
return run_result
|
return run_result
|
||||||
def get_state_message(self):
|
def invoke_shutdown(self, msg):
|
||||||
return self.state_message
|
|
||||||
def invoke_shutdown(self, msg, is_mcu_shutdown=False):
|
|
||||||
if self.is_shutdown:
|
if self.is_shutdown:
|
||||||
return
|
return
|
||||||
self.is_shutdown = True
|
self.is_shutdown = True
|
||||||
if is_mcu_shutdown:
|
|
||||||
self.state_message = "%s%s" % (msg, message_shutdown)
|
self.state_message = "%s%s" % (msg, message_shutdown)
|
||||||
else:
|
for cb in self.state_cb:
|
||||||
self.state_message = "%s%s" % (msg, message_restart)
|
cb('shutdown')
|
||||||
for m in self.mcus:
|
|
||||||
m.do_shutdown()
|
|
||||||
self.gcode.do_shutdown()
|
|
||||||
toolhead = self.objects.get('toolhead')
|
|
||||||
if toolhead is not None:
|
|
||||||
toolhead.do_shutdown()
|
|
||||||
def invoke_async_shutdown(self, msg):
|
def invoke_async_shutdown(self, msg):
|
||||||
self.async_shutdown_msg = msg
|
self.async_shutdown_msg = msg
|
||||||
self.request_exit("shutdown")
|
self.request_exit("shutdown")
|
||||||
|
|||||||
40
klippy/mathutil.py
Normal file
40
klippy/mathutil.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Simple math helper functions
|
||||||
|
#
|
||||||
|
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Helper code that implements coordinate descent
|
||||||
|
def coordinate_descent(adj_params, params, error_func):
|
||||||
|
# Define potential changes
|
||||||
|
params = dict(params)
|
||||||
|
dp = {param_name: 1. for param_name in adj_params}
|
||||||
|
# Calculate the error
|
||||||
|
best_err = error_func(params)
|
||||||
|
|
||||||
|
threshold = 0.00001
|
||||||
|
rounds = 0
|
||||||
|
|
||||||
|
while sum(dp.values()) > threshold and rounds < 10000:
|
||||||
|
rounds += 1
|
||||||
|
for param_name in adj_params:
|
||||||
|
orig = params[param_name]
|
||||||
|
params[param_name] = orig + dp[param_name]
|
||||||
|
err = error_func(params)
|
||||||
|
if err < best_err:
|
||||||
|
# There was some improvement
|
||||||
|
best_err = err
|
||||||
|
dp[param_name] *= 1.1
|
||||||
|
continue
|
||||||
|
params[param_name] = orig - dp[param_name]
|
||||||
|
err = error_func(params)
|
||||||
|
if err < best_err:
|
||||||
|
# There was some improvement
|
||||||
|
best_err = err
|
||||||
|
dp[param_name] *= 1.1
|
||||||
|
continue
|
||||||
|
params[param_name] = orig
|
||||||
|
dp[param_name] *= 0.9
|
||||||
|
logging.info("Coordinate descent best_err: %s rounds: %d", best_err, rounds)
|
||||||
|
return params
|
||||||
383
klippy/mcu.py
383
klippy/mcu.py
@@ -1,6 +1,6 @@
|
|||||||
# Interface to Klipper micro-controller code
|
# Interface to Klipper micro-controller code
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import sys, os, zlib, logging, math
|
import sys, os, zlib, logging, math
|
||||||
@@ -18,11 +18,10 @@ class MCU_stepper:
|
|||||||
self._step_pin = pin_params['pin']
|
self._step_pin = pin_params['pin']
|
||||||
self._invert_step = pin_params['invert']
|
self._invert_step = pin_params['invert']
|
||||||
self._dir_pin = self._invert_dir = None
|
self._dir_pin = self._invert_dir = None
|
||||||
self._commanded_pos = 0
|
self._commanded_pos = self._mcu_position_offset = 0.
|
||||||
self._step_dist = self._inv_step_dist = 1.
|
self._step_dist = self._inv_step_dist = 1.
|
||||||
self._mcu_position_offset = 0
|
|
||||||
self._min_stop_interval = 0.
|
self._min_stop_interval = 0.
|
||||||
self._reset_cmd = self._get_position_cmd = None
|
self._reset_cmd_id = self._get_position_cmd = None
|
||||||
self._ffi_lib = self._stepqueue = None
|
self._ffi_lib = self._stepqueue = None
|
||||||
def get_mcu(self):
|
def get_mcu(self):
|
||||||
return self._mcu
|
return self._mcu
|
||||||
@@ -45,62 +44,62 @@ class MCU_stepper:
|
|||||||
self._oid, self._step_pin, self._dir_pin,
|
self._oid, self._step_pin, self._dir_pin,
|
||||||
self._mcu.seconds_to_clock(min_stop_interval),
|
self._mcu.seconds_to_clock(min_stop_interval),
|
||||||
self._invert_step))
|
self._invert_step))
|
||||||
step_cmd = self._mcu.lookup_command(
|
self._mcu.add_config_cmd(
|
||||||
|
"reset_step_clock oid=%d clock=0" % (self._oid,), is_init=True)
|
||||||
|
step_cmd_id = self._mcu.lookup_command_id(
|
||||||
"queue_step oid=%c interval=%u count=%hu add=%hi")
|
"queue_step oid=%c interval=%u count=%hu add=%hi")
|
||||||
dir_cmd = self._mcu.lookup_command(
|
dir_cmd_id = self._mcu.lookup_command_id(
|
||||||
"set_next_step_dir oid=%c dir=%c")
|
"set_next_step_dir oid=%c dir=%c")
|
||||||
self._reset_cmd = self._mcu.lookup_command(
|
self._reset_cmd_id = self._mcu.lookup_command_id(
|
||||||
"reset_step_clock oid=%c clock=%u")
|
"reset_step_clock oid=%c clock=%u")
|
||||||
self._get_position_cmd = self._mcu.lookup_command(
|
self._get_position_cmd = self._mcu.lookup_command(
|
||||||
"stepper_get_position oid=%c")
|
"stepper_get_position oid=%c")
|
||||||
ffi_main, self._ffi_lib = chelper.get_ffi()
|
ffi_main, self._ffi_lib = chelper.get_ffi()
|
||||||
self._stepqueue = ffi_main.gc(self._ffi_lib.stepcompress_alloc(
|
self._stepqueue = ffi_main.gc(self._ffi_lib.stepcompress_alloc(
|
||||||
self._mcu.seconds_to_clock(max_error), step_cmd.msgid, dir_cmd.msgid,
|
self._mcu.seconds_to_clock(max_error), step_cmd_id, dir_cmd_id,
|
||||||
self._invert_dir, self._oid),
|
self._invert_dir, self._oid),
|
||||||
self._ffi_lib.stepcompress_free)
|
self._ffi_lib.stepcompress_free)
|
||||||
self._mcu.register_stepqueue(self._stepqueue)
|
self._mcu.register_stepqueue(self._stepqueue)
|
||||||
def get_oid(self):
|
def get_oid(self):
|
||||||
return self._oid
|
return self._oid
|
||||||
|
def get_step_dist(self):
|
||||||
|
return self._step_dist
|
||||||
def set_position(self, pos):
|
def set_position(self, pos):
|
||||||
if pos >= 0.:
|
steppos = pos * self._inv_step_dist
|
||||||
steppos = int(pos * self._inv_step_dist + 0.5)
|
|
||||||
else:
|
|
||||||
steppos = int(pos * self._inv_step_dist - 0.5)
|
|
||||||
self._mcu_position_offset += self._commanded_pos - steppos
|
self._mcu_position_offset += self._commanded_pos - steppos
|
||||||
self._commanded_pos = steppos
|
self._commanded_pos = steppos
|
||||||
def get_commanded_position(self):
|
def get_commanded_position(self):
|
||||||
return self._commanded_pos * self._step_dist
|
return self._commanded_pos * self._step_dist
|
||||||
def get_mcu_position(self):
|
def get_mcu_position(self):
|
||||||
return self._commanded_pos + self._mcu_position_offset
|
mcu_pos = self._commanded_pos + self._mcu_position_offset
|
||||||
|
if mcu_pos >= 0.:
|
||||||
|
return int(mcu_pos + 0.5)
|
||||||
|
return int(mcu_pos - 0.5)
|
||||||
def note_homing_start(self, homing_clock):
|
def note_homing_start(self, homing_clock):
|
||||||
ret = self._ffi_lib.stepcompress_set_homing(
|
ret = self._ffi_lib.stepcompress_set_homing(
|
||||||
self._stepqueue, homing_clock)
|
self._stepqueue, homing_clock)
|
||||||
if ret:
|
if ret:
|
||||||
raise error("Internal error in stepcompress")
|
raise error("Internal error in stepcompress")
|
||||||
def note_homing_finalized(self):
|
def note_homing_end(self, did_trigger=False):
|
||||||
ret = self._ffi_lib.stepcompress_set_homing(self._stepqueue, 0)
|
ret = self._ffi_lib.stepcompress_set_homing(self._stepqueue, 0)
|
||||||
if ret:
|
if ret:
|
||||||
raise error("Internal error in stepcompress")
|
raise error("Internal error in stepcompress")
|
||||||
ret = self._ffi_lib.stepcompress_reset(self._stepqueue, 0)
|
ret = self._ffi_lib.stepcompress_reset(self._stepqueue, 0)
|
||||||
if ret:
|
if ret:
|
||||||
raise error("Internal error in stepcompress")
|
raise error("Internal error in stepcompress")
|
||||||
def note_homing_triggered(self):
|
data = (self._reset_cmd_id, self._oid, 0)
|
||||||
cmd = self._get_position_cmd.encode(self._oid)
|
|
||||||
params = self._mcu.send_with_response(cmd, 'stepper_position', self._oid)
|
|
||||||
pos = params['pos']
|
|
||||||
if self._invert_dir:
|
|
||||||
pos = -pos
|
|
||||||
self._mcu_position_offset = pos - self._commanded_pos
|
|
||||||
def reset_step_clock(self, print_time):
|
|
||||||
clock = self._mcu.print_time_to_clock(print_time)
|
|
||||||
ret = self._ffi_lib.stepcompress_reset(self._stepqueue, clock)
|
|
||||||
if ret:
|
|
||||||
raise error("Internal error in stepcompress")
|
|
||||||
data = (self._reset_cmd.msgid, self._oid, clock & 0xffffffff)
|
|
||||||
ret = self._ffi_lib.stepcompress_queue_msg(
|
ret = self._ffi_lib.stepcompress_queue_msg(
|
||||||
self._stepqueue, data, len(data))
|
self._stepqueue, data, len(data))
|
||||||
if ret:
|
if ret:
|
||||||
raise error("Internal error in stepcompress")
|
raise error("Internal error in stepcompress")
|
||||||
|
if not did_trigger or self._mcu.is_fileoutput():
|
||||||
|
return
|
||||||
|
params = self._get_position_cmd.send_with_response(
|
||||||
|
[self._oid], response='stepper_position', response_oid=self._oid)
|
||||||
|
pos = params['pos']
|
||||||
|
if self._invert_dir:
|
||||||
|
pos = -pos
|
||||||
|
self._commanded_pos = pos - self._mcu_position_offset
|
||||||
def step(self, print_time, sdir):
|
def step(self, print_time, sdir):
|
||||||
count = self._ffi_lib.stepcompress_push(
|
count = self._ffi_lib.stepcompress_push(
|
||||||
self._stepqueue, print_time, sdir)
|
self._stepqueue, print_time, sdir)
|
||||||
@@ -129,7 +128,8 @@ class MCU_stepper:
|
|||||||
self._commanded_pos += count
|
self._commanded_pos += count
|
||||||
|
|
||||||
class MCU_endstop:
|
class MCU_endstop:
|
||||||
error = error
|
class TimeoutError(Exception):
|
||||||
|
pass
|
||||||
RETRY_QUERY = 1.000
|
RETRY_QUERY = 1.000
|
||||||
def __init__(self, mcu, pin_params):
|
def __init__(self, mcu, pin_params):
|
||||||
self._mcu = mcu
|
self._mcu = mcu
|
||||||
@@ -137,83 +137,88 @@ class MCU_endstop:
|
|||||||
self._pin = pin_params['pin']
|
self._pin = pin_params['pin']
|
||||||
self._pullup = pin_params['pullup']
|
self._pullup = pin_params['pullup']
|
||||||
self._invert = pin_params['invert']
|
self._invert = pin_params['invert']
|
||||||
self._cmd_queue = mcu.alloc_command_queue()
|
|
||||||
self._oid = self._home_cmd = self._query_cmd = None
|
self._oid = self._home_cmd = self._query_cmd = None
|
||||||
self._homing = False
|
self._homing = False
|
||||||
self._min_query_time = self._next_query_time = self._home_timeout = 0.
|
self._min_query_time = self._next_query_time = 0.
|
||||||
self._last_state = {}
|
self._last_state = {}
|
||||||
def get_mcu(self):
|
def get_mcu(self):
|
||||||
return self._mcu
|
return self._mcu
|
||||||
def add_stepper(self, stepper):
|
def add_stepper(self, stepper):
|
||||||
|
if stepper.get_mcu() is not self._mcu:
|
||||||
|
raise pins.error("Endstop and stepper must be on the same mcu")
|
||||||
self._steppers.append(stepper)
|
self._steppers.append(stepper)
|
||||||
|
def get_steppers(self):
|
||||||
|
return list(self._steppers)
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
self._oid = self._mcu.create_oid()
|
self._oid = self._mcu.create_oid()
|
||||||
self._mcu.add_config_cmd(
|
self._mcu.add_config_cmd(
|
||||||
"config_end_stop oid=%d pin=%s pull_up=%d stepper_count=%d" % (
|
"config_end_stop oid=%d pin=%s pull_up=%d stepper_count=%d" % (
|
||||||
self._oid, self._pin, self._pullup, len(self._steppers)))
|
self._oid, self._pin, self._pullup, len(self._steppers)))
|
||||||
|
self._mcu.add_config_cmd(
|
||||||
|
"end_stop_home oid=%d clock=0 sample_ticks=0 sample_count=0"
|
||||||
|
" rest_ticks=0 pin_value=0" % (self._oid,), is_init=True)
|
||||||
for i, s in enumerate(self._steppers):
|
for i, s in enumerate(self._steppers):
|
||||||
self._mcu.add_config_cmd(
|
self._mcu.add_config_cmd(
|
||||||
"end_stop_set_stepper oid=%d pos=%d stepper_oid=%d" % (
|
"end_stop_set_stepper oid=%d pos=%d stepper_oid=%d" % (
|
||||||
self._oid, i, s.get_oid()), is_init=True)
|
self._oid, i, s.get_oid()), is_init=True)
|
||||||
|
cmd_queue = self._mcu.alloc_command_queue()
|
||||||
self._home_cmd = self._mcu.lookup_command(
|
self._home_cmd = self._mcu.lookup_command(
|
||||||
"end_stop_home oid=%c clock=%u sample_ticks=%u sample_count=%c"
|
"end_stop_home oid=%c clock=%u sample_ticks=%u sample_count=%c"
|
||||||
" rest_ticks=%u pin_value=%c")
|
" rest_ticks=%u pin_value=%c", cq=cmd_queue)
|
||||||
self._query_cmd = self._mcu.lookup_command("end_stop_query oid=%c")
|
self._query_cmd = self._mcu.lookup_command("end_stop_query oid=%c",
|
||||||
|
cq=cmd_queue)
|
||||||
self._mcu.register_msg(self._handle_end_stop_state, "end_stop_state"
|
self._mcu.register_msg(self._handle_end_stop_state, "end_stop_state"
|
||||||
, self._oid)
|
, self._oid)
|
||||||
|
def home_prepare(self):
|
||||||
|
pass
|
||||||
def home_start(self, print_time, sample_time, sample_count, rest_time):
|
def home_start(self, print_time, sample_time, sample_count, rest_time):
|
||||||
clock = self._mcu.print_time_to_clock(print_time)
|
clock = self._mcu.print_time_to_clock(print_time)
|
||||||
rest_ticks = int(rest_time * self._mcu.get_adjusted_freq())
|
rest_ticks = int(rest_time * self._mcu.get_adjusted_freq())
|
||||||
self._homing = True
|
self._homing = True
|
||||||
self._min_query_time = self._mcu.monotonic()
|
self._min_query_time = self._mcu.monotonic()
|
||||||
self._next_query_time = print_time + self.RETRY_QUERY
|
self._next_query_time = self._min_query_time + self.RETRY_QUERY
|
||||||
msg = self._home_cmd.encode(
|
self._home_cmd.send(
|
||||||
self._oid, clock, self._mcu.seconds_to_clock(sample_time),
|
[self._oid, clock, self._mcu.seconds_to_clock(sample_time),
|
||||||
sample_count, rest_ticks, 1 ^ self._invert)
|
sample_count, rest_ticks, 1 ^ self._invert], reqclock=clock)
|
||||||
self._mcu.send(msg, reqclock=clock, cq=self._cmd_queue)
|
|
||||||
for s in self._steppers:
|
for s in self._steppers:
|
||||||
s.note_homing_start(clock)
|
s.note_homing_start(clock)
|
||||||
def home_finalize(self, print_time):
|
def home_wait(self, home_end_time):
|
||||||
for s in self._steppers:
|
|
||||||
s.note_homing_finalized()
|
|
||||||
self._home_timeout = print_time
|
|
||||||
def home_wait(self):
|
|
||||||
eventtime = self._mcu.monotonic()
|
eventtime = self._mcu.monotonic()
|
||||||
while self._check_busy(eventtime):
|
while self._check_busy(eventtime, home_end_time):
|
||||||
eventtime = self._mcu.pause(eventtime + 0.1)
|
eventtime = self._mcu.pause(eventtime + 0.1)
|
||||||
|
def home_finalize(self):
|
||||||
|
pass
|
||||||
def _handle_end_stop_state(self, params):
|
def _handle_end_stop_state(self, params):
|
||||||
logging.debug("end_stop_state %s", params)
|
logging.debug("end_stop_state %s", params)
|
||||||
self._last_state = params
|
self._last_state = params
|
||||||
def _check_busy(self, eventtime):
|
def _check_busy(self, eventtime, home_end_time=0.):
|
||||||
# Check if need to send an end_stop_query command
|
# Check if need to send an end_stop_query command
|
||||||
if self._mcu.is_fileoutput():
|
|
||||||
return False
|
|
||||||
print_time = self._mcu.estimated_print_time(eventtime)
|
|
||||||
last_sent_time = self._last_state.get('#sent_time', -1.)
|
last_sent_time = self._last_state.get('#sent_time', -1.)
|
||||||
if last_sent_time >= self._min_query_time:
|
if last_sent_time >= self._min_query_time or self._mcu.is_fileoutput():
|
||||||
if not self._homing:
|
if not self._homing:
|
||||||
return False
|
return False
|
||||||
if not self._last_state.get('homing', 0):
|
if not self._last_state.get('homing', 0):
|
||||||
for s in self._steppers:
|
for s in self._steppers:
|
||||||
s.note_homing_triggered()
|
s.note_homing_end(did_trigger=True)
|
||||||
self._homing = False
|
self._homing = False
|
||||||
return False
|
return False
|
||||||
if print_time > self._home_timeout:
|
last_sent_print_time = self._mcu.estimated_print_time(last_sent_time)
|
||||||
|
if last_sent_print_time > home_end_time:
|
||||||
# Timeout - disable endstop checking
|
# Timeout - disable endstop checking
|
||||||
msg = self._home_cmd.encode(self._oid, 0, 0, 0, 0, 0)
|
for s in self._steppers:
|
||||||
self._mcu.send(msg, reqclock=0, cq=self._cmd_queue)
|
s.note_homing_end()
|
||||||
raise error("Timeout during endstop homing")
|
self._homing = False
|
||||||
|
self._home_cmd.send([self._oid, 0, 0, 0, 0, 0])
|
||||||
|
raise self.TimeoutError("Timeout during endstop homing")
|
||||||
if self._mcu.is_shutdown():
|
if self._mcu.is_shutdown():
|
||||||
raise error("MCU is shutdown")
|
raise error("MCU is shutdown")
|
||||||
if print_time >= self._next_query_time:
|
if eventtime >= self._next_query_time:
|
||||||
self._next_query_time = print_time + self.RETRY_QUERY
|
self._next_query_time = eventtime + self.RETRY_QUERY
|
||||||
msg = self._query_cmd.encode(self._oid)
|
self._query_cmd.send([self._oid])
|
||||||
self._mcu.send(msg, cq=self._cmd_queue)
|
|
||||||
return True
|
return True
|
||||||
def query_endstop(self, print_time):
|
def query_endstop(self, print_time):
|
||||||
self._homing = False
|
self._homing = False
|
||||||
self._next_query_time = print_time
|
self._min_query_time = self._next_query_time = self._mcu.monotonic()
|
||||||
self._min_query_time = self._mcu.monotonic()
|
|
||||||
def query_endstop_wait(self):
|
def query_endstop_wait(self):
|
||||||
eventtime = self._mcu.monotonic()
|
eventtime = self._mcu.monotonic()
|
||||||
while self._check_busy(eventtime):
|
while self._check_busy(eventtime):
|
||||||
@@ -224,132 +229,122 @@ class MCU_digital_out:
|
|||||||
def __init__(self, mcu, pin_params):
|
def __init__(self, mcu, pin_params):
|
||||||
self._mcu = mcu
|
self._mcu = mcu
|
||||||
self._oid = None
|
self._oid = None
|
||||||
self._static_value = None
|
|
||||||
self._pin = pin_params['pin']
|
self._pin = pin_params['pin']
|
||||||
self._invert = self._shutdown_value = pin_params['invert']
|
self._invert = pin_params['invert']
|
||||||
|
self._start_value = self._shutdown_value = self._invert
|
||||||
|
self._is_static = False
|
||||||
self._max_duration = 2.
|
self._max_duration = 2.
|
||||||
self._last_clock = 0
|
self._last_clock = 0
|
||||||
self._last_value = None
|
|
||||||
self._cmd_queue = mcu.alloc_command_queue()
|
|
||||||
self._set_cmd = None
|
self._set_cmd = None
|
||||||
def get_mcu(self):
|
def get_mcu(self):
|
||||||
return self._mcu
|
return self._mcu
|
||||||
def setup_max_duration(self, max_duration):
|
def setup_max_duration(self, max_duration):
|
||||||
self._max_duration = max_duration
|
self._max_duration = max_duration
|
||||||
def setup_static(self):
|
def setup_start_value(self, start_value, shutdown_value, is_static=False):
|
||||||
self._static_value = not self._invert
|
if is_static and start_value != shutdown_value:
|
||||||
def setup_shutdown_value(self, value):
|
raise pins.error("Static pin can not have shutdown value")
|
||||||
self._shutdown_value = (not not value) ^ self._invert
|
self._start_value = (not not start_value) ^ self._invert
|
||||||
|
self._shutdown_value = (not not shutdown_value) ^ self._invert
|
||||||
|
self._is_static = is_static
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
if self._static_value is not None:
|
if self._is_static:
|
||||||
self._mcu.add_config_cmd("set_digital_out pin=%s value=%d" % (
|
self._mcu.add_config_cmd("set_digital_out pin=%s value=%d" % (
|
||||||
self._pin, self._static_value))
|
self._pin, self._start_value))
|
||||||
return
|
return
|
||||||
self._oid = self._mcu.create_oid()
|
self._oid = self._mcu.create_oid()
|
||||||
self._mcu.add_config_cmd(
|
self._mcu.add_config_cmd(
|
||||||
"config_digital_out oid=%d pin=%s value=%d default_value=%d"
|
"config_digital_out oid=%d pin=%s value=%d default_value=%d"
|
||||||
" max_duration=%d" % (
|
" max_duration=%d" % (
|
||||||
self._oid, self._pin, self._invert, self._shutdown_value,
|
self._oid, self._pin, self._start_value, self._shutdown_value,
|
||||||
self._mcu.seconds_to_clock(self._max_duration)))
|
self._mcu.seconds_to_clock(self._max_duration)))
|
||||||
|
cmd_queue = self._mcu.alloc_command_queue()
|
||||||
self._set_cmd = self._mcu.lookup_command(
|
self._set_cmd = self._mcu.lookup_command(
|
||||||
"schedule_digital_out oid=%c clock=%u value=%c")
|
"schedule_digital_out oid=%c clock=%u value=%c", cq=cmd_queue)
|
||||||
def set_digital(self, print_time, value):
|
def set_digital(self, print_time, value):
|
||||||
clock = self._mcu.print_time_to_clock(print_time)
|
clock = self._mcu.print_time_to_clock(print_time)
|
||||||
msg = self._set_cmd.encode(
|
self._set_cmd.send([self._oid, clock, (not not value) ^ self._invert],
|
||||||
self._oid, clock, (not not value) ^ self._invert)
|
minclock=self._last_clock, reqclock=clock)
|
||||||
self._mcu.send(msg, minclock=self._last_clock, reqclock=clock
|
|
||||||
, cq=self._cmd_queue)
|
|
||||||
self._last_clock = clock
|
self._last_clock = clock
|
||||||
self._last_value = value
|
|
||||||
def get_last_setting(self):
|
|
||||||
return self._last_value
|
|
||||||
def set_pwm(self, print_time, value):
|
def set_pwm(self, print_time, value):
|
||||||
self.set_digital(print_time, value >= 0.5)
|
self.set_digital(print_time, value >= 0.5)
|
||||||
|
|
||||||
class MCU_pwm:
|
class MCU_pwm:
|
||||||
def __init__(self, mcu, pin_params):
|
def __init__(self, mcu, pin_params):
|
||||||
self._mcu = mcu
|
self._mcu = mcu
|
||||||
self._hard_pwm = False
|
self._hardware_pwm = False
|
||||||
self._cycle_time = 0.100
|
self._cycle_time = 0.100
|
||||||
self._max_duration = 2.
|
self._max_duration = 2.
|
||||||
self._oid = None
|
self._oid = None
|
||||||
self._static_value = None
|
|
||||||
self._pin = pin_params['pin']
|
self._pin = pin_params['pin']
|
||||||
self._invert = pin_params['invert']
|
self._invert = pin_params['invert']
|
||||||
self._shutdown_value = float(self._invert)
|
self._start_value = self._shutdown_value = float(self._invert)
|
||||||
|
self._is_static = False
|
||||||
self._last_clock = 0
|
self._last_clock = 0
|
||||||
self._pwm_max = 0.
|
self._pwm_max = 0.
|
||||||
self._cmd_queue = mcu.alloc_command_queue()
|
|
||||||
self._set_cmd = None
|
self._set_cmd = None
|
||||||
def get_mcu(self):
|
def get_mcu(self):
|
||||||
return self._mcu
|
return self._mcu
|
||||||
def setup_max_duration(self, max_duration):
|
def setup_max_duration(self, max_duration):
|
||||||
self._max_duration = max_duration
|
self._max_duration = max_duration
|
||||||
def setup_cycle_time(self, cycle_time):
|
def setup_cycle_time(self, cycle_time, hardware_pwm=False):
|
||||||
self._cycle_time = cycle_time
|
self._cycle_time = cycle_time
|
||||||
self._hard_pwm = False
|
self._hardware_pwm = hardware_pwm
|
||||||
def setup_hard_pwm(self, hard_cycle_ticks):
|
def setup_start_value(self, start_value, shutdown_value, is_static=False):
|
||||||
if not hard_cycle_ticks:
|
if is_static and start_value != shutdown_value:
|
||||||
return
|
raise pins.error("Static pin can not have shutdown value")
|
||||||
self._cycle_time = hard_cycle_ticks
|
|
||||||
self._hard_pwm = True
|
|
||||||
def setup_static_pwm(self, value):
|
|
||||||
if self._invert:
|
if self._invert:
|
||||||
value = 1. - value
|
start_value = 1. - start_value
|
||||||
self._static_value = max(0., min(1., value))
|
shutdown_value = 1. - shutdown_value
|
||||||
def setup_shutdown_value(self, value):
|
self._start_value = max(0., min(1., start_value))
|
||||||
if self._invert:
|
self._shutdown_value = max(0., min(1., shutdown_value))
|
||||||
value = 1. - value
|
self._is_static = is_static
|
||||||
self._shutdown_value = max(0., min(1., value))
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
if self._hard_pwm:
|
cmd_queue = self._mcu.alloc_command_queue()
|
||||||
|
cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time)
|
||||||
|
if self._hardware_pwm:
|
||||||
self._pwm_max = self._mcu.get_constant_float("PWM_MAX")
|
self._pwm_max = self._mcu.get_constant_float("PWM_MAX")
|
||||||
if self._static_value is not None:
|
if self._is_static:
|
||||||
value = int(self._static_value * self._pwm_max + 0.5)
|
|
||||||
self._mcu.add_config_cmd(
|
self._mcu.add_config_cmd(
|
||||||
"set_pwm_out pin=%s cycle_ticks=%d value=%d" % (
|
"set_pwm_out pin=%s cycle_ticks=%d value=%d" % (
|
||||||
self._pin, self._cycle_time, value))
|
self._pin, cycle_ticks,
|
||||||
|
self._start_value * self._pwm_max))
|
||||||
return
|
return
|
||||||
self._oid = self._mcu.create_oid()
|
self._oid = self._mcu.create_oid()
|
||||||
self._mcu.add_config_cmd(
|
self._mcu.add_config_cmd(
|
||||||
"config_pwm_out oid=%d pin=%s cycle_ticks=%d value=%d"
|
"config_pwm_out oid=%d pin=%s cycle_ticks=%d value=%d"
|
||||||
" default_value=%d max_duration=%d" % (
|
" default_value=%d max_duration=%d" % (
|
||||||
self._oid, self._pin, self._cycle_time,
|
self._oid, self._pin, cycle_ticks,
|
||||||
self._invert * self._pwm_max,
|
self._start_value * self._pwm_max,
|
||||||
self._shutdown_value * self._pwm_max,
|
self._shutdown_value * self._pwm_max,
|
||||||
self._mcu.seconds_to_clock(self._max_duration)))
|
self._mcu.seconds_to_clock(self._max_duration)))
|
||||||
self._set_cmd = self._mcu.lookup_command(
|
self._set_cmd = self._mcu.lookup_command(
|
||||||
"schedule_pwm_out oid=%c clock=%u value=%hu")
|
"schedule_pwm_out oid=%c clock=%u value=%hu", cq=cmd_queue)
|
||||||
else:
|
else:
|
||||||
|
if (self._start_value not in [0., 1.]
|
||||||
|
or self._shutdown_value not in [0., 1.]):
|
||||||
|
raise pins.error(
|
||||||
|
"start and shutdown values must be 0.0 or 1.0 on soft pwm")
|
||||||
self._pwm_max = self._mcu.get_constant_float("SOFT_PWM_MAX")
|
self._pwm_max = self._mcu.get_constant_float("SOFT_PWM_MAX")
|
||||||
if self._static_value is not None:
|
if self._is_static:
|
||||||
if self._static_value not in [0., 1.]:
|
|
||||||
raise pins.error(
|
|
||||||
"static value must be 0.0 or 1.0 on soft pwm")
|
|
||||||
self._mcu.add_config_cmd("set_digital_out pin=%s value=%d" % (
|
self._mcu.add_config_cmd("set_digital_out pin=%s value=%d" % (
|
||||||
self._pin, self._static_value >= 0.5))
|
self._pin, self._start_value >= 0.5))
|
||||||
return
|
return
|
||||||
if self._shutdown_value not in [0., 1.]:
|
|
||||||
raise pins.error(
|
|
||||||
"shutdown value must be 0.0 or 1.0 on soft pwm")
|
|
||||||
self._oid = self._mcu.create_oid()
|
self._oid = self._mcu.create_oid()
|
||||||
self._mcu.add_config_cmd(
|
self._mcu.add_config_cmd(
|
||||||
"config_soft_pwm_out oid=%d pin=%s cycle_ticks=%d value=%d"
|
"config_soft_pwm_out oid=%d pin=%s cycle_ticks=%d value=%d"
|
||||||
" default_value=%d max_duration=%d" % (
|
" default_value=%d max_duration=%d" % (
|
||||||
self._oid, self._pin,
|
self._oid, self._pin, cycle_ticks,
|
||||||
self._mcu.seconds_to_clock(self._cycle_time),
|
self._start_value >= 0.5, self._shutdown_value >= 0.5,
|
||||||
self._invert, self._shutdown_value >= 0.5,
|
|
||||||
self._mcu.seconds_to_clock(self._max_duration)))
|
self._mcu.seconds_to_clock(self._max_duration)))
|
||||||
self._set_cmd = self._mcu.lookup_command(
|
self._set_cmd = self._mcu.lookup_command(
|
||||||
"schedule_soft_pwm_out oid=%c clock=%u value=%hu")
|
"schedule_soft_pwm_out oid=%c clock=%u value=%hu", cq=cmd_queue)
|
||||||
def set_pwm(self, print_time, value):
|
def set_pwm(self, print_time, value):
|
||||||
clock = self._mcu.print_time_to_clock(print_time)
|
clock = self._mcu.print_time_to_clock(print_time)
|
||||||
if self._invert:
|
if self._invert:
|
||||||
value = 1. - value
|
value = 1. - value
|
||||||
value = int(max(0., min(1., value)) * self._pwm_max + 0.5)
|
value = int(max(0., min(1., value)) * self._pwm_max + 0.5)
|
||||||
msg = self._set_cmd.encode(self._oid, clock, value)
|
self._set_cmd.send([self._oid, clock, value],
|
||||||
self._mcu.send(msg, minclock=self._last_clock, reqclock=clock
|
minclock=self._last_clock, reqclock=clock)
|
||||||
, cq=self._cmd_queue)
|
|
||||||
self._last_clock = clock
|
self._last_clock = clock
|
||||||
|
|
||||||
class MCU_adc:
|
class MCU_adc:
|
||||||
@@ -362,7 +357,6 @@ class MCU_adc:
|
|||||||
self._report_clock = 0
|
self._report_clock = 0
|
||||||
self._oid = self._callback = None
|
self._oid = self._callback = None
|
||||||
self._inv_max_adc = 0.
|
self._inv_max_adc = 0.
|
||||||
self._cmd_queue = mcu.alloc_command_queue()
|
|
||||||
def get_mcu(self):
|
def get_mcu(self):
|
||||||
return self._mcu
|
return self._mcu
|
||||||
def setup_minmax(self, sample_time, sample_count, minval=0., maxval=1.):
|
def setup_minmax(self, sample_time, sample_count, minval=0., maxval=1.):
|
||||||
@@ -408,7 +402,8 @@ class MCU:
|
|||||||
def __init__(self, printer, config, clocksync):
|
def __init__(self, printer, config, clocksync):
|
||||||
self._printer = printer
|
self._printer = printer
|
||||||
self._clocksync = clocksync
|
self._clocksync = clocksync
|
||||||
self._name = config.section
|
self._reactor = printer.get_reactor()
|
||||||
|
self._name = config.get_name()
|
||||||
if self._name.startswith('mcu '):
|
if self._name.startswith('mcu '):
|
||||||
self._name = self._name[4:]
|
self._name = self._name[4:]
|
||||||
# Serial port
|
# Serial port
|
||||||
@@ -418,19 +413,18 @@ class MCU:
|
|||||||
or self._serialport.startswith("/tmp/klipper_host_")):
|
or self._serialport.startswith("/tmp/klipper_host_")):
|
||||||
baud = config.getint('baud', 250000, minval=2400)
|
baud = config.getint('baud', 250000, minval=2400)
|
||||||
self._serial = serialhdl.SerialReader(
|
self._serial = serialhdl.SerialReader(
|
||||||
printer.reactor, self._serialport, baud)
|
self._reactor, self._serialport, baud)
|
||||||
# Restarts
|
# Restarts
|
||||||
self._restart_method = 'command'
|
self._restart_method = 'command'
|
||||||
if baud:
|
if baud:
|
||||||
rmethods = {m: m for m in ['arduino', 'command', 'rpi_usb']}
|
rmethods = {m: m for m in [None, 'arduino', 'command', 'rpi_usb']}
|
||||||
self._restart_method = config.getchoice(
|
self._restart_method = config.getchoice(
|
||||||
'restart_method', rmethods, 'arduino')
|
'restart_method', rmethods, None)
|
||||||
self._reset_cmd = self._config_reset_cmd = None
|
self._reset_cmd = self._config_reset_cmd = None
|
||||||
self._emergency_stop_cmd = None
|
self._emergency_stop_cmd = None
|
||||||
self._is_shutdown = False
|
self._is_shutdown = self._is_timeout = False
|
||||||
self._shutdown_msg = ""
|
self._shutdown_msg = ""
|
||||||
if printer.bglogger is not None:
|
printer.set_rollover_info(self._name, None)
|
||||||
printer.bglogger.set_rollover_info(self._name, None)
|
|
||||||
# Config building
|
# Config building
|
||||||
pins.get_printer_pins(printer).register_chip(self._name, self)
|
pins.get_printer_pins(printer).register_chip(self._name, self)
|
||||||
self._oid_count = 0
|
self._oid_count = 0
|
||||||
@@ -481,7 +475,7 @@ class MCU:
|
|||||||
logging.info("Attempting automated MCU '%s' restart: %s",
|
logging.info("Attempting automated MCU '%s' restart: %s",
|
||||||
self._name, reason)
|
self._name, reason)
|
||||||
self._printer.request_exit('firmware_restart')
|
self._printer.request_exit('firmware_restart')
|
||||||
self._printer.reactor.pause(self._printer.reactor.monotonic() + 2.000)
|
self._reactor.pause(self._reactor.monotonic() + 2.000)
|
||||||
raise error("Attempt MCU '%s' restart failed" % (self._name,))
|
raise error("Attempt MCU '%s' restart failed" % (self._name,))
|
||||||
def _connect_file(self, pace=False):
|
def _connect_file(self, pace=False):
|
||||||
# In a debugging mode. Open debug output file and read data dictionary
|
# In a debugging mode. Open debug output file and read data dictionary
|
||||||
@@ -521,26 +515,25 @@ class MCU:
|
|||||||
self._oid_count,))
|
self._oid_count,))
|
||||||
|
|
||||||
# Resolve pin names
|
# Resolve pin names
|
||||||
mcu = self._serial.msgparser.get_constant('MCU')
|
mcu_type = self._serial.msgparser.get_constant('MCU')
|
||||||
pnames = pins.get_pin_map(mcu, self._pin_map)
|
pin_resolver = pins.PinResolver(mcu_type)
|
||||||
updated_cmds = []
|
if self._pin_map is not None:
|
||||||
for cmd in self._config_cmds:
|
pin_resolver.update_aliases(self._pin_map)
|
||||||
try:
|
for i, cmd in enumerate(self._config_cmds):
|
||||||
updated_cmds.append(pins.update_command(cmd, pnames))
|
self._config_cmds[i] = pin_resolver.update_command(cmd)
|
||||||
except:
|
for i, cmd in enumerate(self._init_cmds):
|
||||||
raise pins.error("Unable to translate pin name: %s" % (cmd,))
|
self._init_cmds[i] = pin_resolver.update_command(cmd)
|
||||||
self._config_cmds = updated_cmds
|
|
||||||
|
|
||||||
# Calculate config CRC
|
# Calculate config CRC
|
||||||
self._config_crc = zlib.crc32('\n'.join(self._config_cmds)) & 0xffffffff
|
self._config_crc = zlib.crc32('\n'.join(self._config_cmds)) & 0xffffffff
|
||||||
self.add_config_cmd("finalize_config crc=%d" % (self._config_crc,))
|
self.add_config_cmd("finalize_config crc=%d" % (self._config_crc,))
|
||||||
def _send_config(self):
|
def _send_config(self):
|
||||||
msg = self.create_command("get_config")
|
get_config_cmd = self.lookup_command("get_config")
|
||||||
if self.is_fileoutput():
|
if self.is_fileoutput():
|
||||||
config_params = {
|
config_params = {
|
||||||
'is_config': 0, 'move_count': 500, 'crc': self._config_crc}
|
'is_config': 0, 'move_count': 500, 'crc': self._config_crc}
|
||||||
else:
|
else:
|
||||||
config_params = self.send_with_response(msg, 'config')
|
config_params = get_config_cmd.send_with_response(response='config')
|
||||||
if not config_params['is_config']:
|
if not config_params['is_config']:
|
||||||
if self._restart_method == 'rpi_usb':
|
if self._restart_method == 'rpi_usb':
|
||||||
# Only configure mcu after usb power reset
|
# Only configure mcu after usb power reset
|
||||||
@@ -549,9 +542,10 @@ class MCU:
|
|||||||
logging.info("Sending MCU '%s' printer configuration...",
|
logging.info("Sending MCU '%s' printer configuration...",
|
||||||
self._name)
|
self._name)
|
||||||
for c in self._config_cmds:
|
for c in self._config_cmds:
|
||||||
self.send(self.create_command(c))
|
self._serial.send(c)
|
||||||
if not self.is_fileoutput():
|
if not self.is_fileoutput():
|
||||||
config_params = self.send_with_response(msg, 'config')
|
config_params = get_config_cmd.send_with_response(
|
||||||
|
response='config')
|
||||||
if not config_params['is_config']:
|
if not config_params['is_config']:
|
||||||
if self._is_shutdown:
|
if self._is_shutdown:
|
||||||
raise error("MCU '%s' error during config: %s" % (
|
raise error("MCU '%s' error during config: %s" % (
|
||||||
@@ -566,23 +560,22 @@ class MCU:
|
|||||||
raise error("MCU '%s' CRC does not match config" % (self._name,))
|
raise error("MCU '%s' CRC does not match config" % (self._name,))
|
||||||
move_count = config_params['move_count']
|
move_count = config_params['move_count']
|
||||||
logging.info("Configured MCU '%s' (%d moves)", self._name, move_count)
|
logging.info("Configured MCU '%s' (%d moves)", self._name, move_count)
|
||||||
if self._printer.bglogger is not None:
|
|
||||||
msgparser = self._serial.msgparser
|
msgparser = self._serial.msgparser
|
||||||
info = [
|
info = [
|
||||||
"Configured MCU '%s' (%d moves)" % (self._name, move_count),
|
"Configured MCU '%s' (%d moves)" % (self._name, move_count),
|
||||||
"Loaded MCU '%s' %d commands (%s)" % (
|
"Loaded MCU '%s' %d commands (%s / %s)" % (
|
||||||
self._name, len(msgparser.messages_by_id),
|
self._name, len(msgparser.messages_by_id),
|
||||||
msgparser.version),
|
msgparser.version, msgparser.build_versions),
|
||||||
"MCU '%s' config: %s" % (self._name, " ".join(
|
"MCU '%s' config: %s" % (self._name, " ".join(
|
||||||
["%s=%s" % (k, v) for k, v in msgparser.config.items()]))]
|
["%s=%s" % (k, v) for k, v in msgparser.config.items()]))]
|
||||||
self._printer.bglogger.set_rollover_info(self._name, "\n".join(info))
|
self._printer.set_rollover_info(self._name, "\n".join(info))
|
||||||
self._steppersync = self._ffi_lib.steppersync_alloc(
|
self._steppersync = self._ffi_lib.steppersync_alloc(
|
||||||
self._serial.serialqueue, self._stepqueues, len(self._stepqueues),
|
self._serial.serialqueue, self._stepqueues, len(self._stepqueues),
|
||||||
move_count)
|
move_count)
|
||||||
self._ffi_lib.steppersync_set_time(self._steppersync, 0., self._mcu_freq)
|
self._ffi_lib.steppersync_set_time(self._steppersync, 0., self._mcu_freq)
|
||||||
for c in self._init_cmds:
|
for c in self._init_cmds:
|
||||||
self.send(self.create_command(c))
|
self._serial.send(c)
|
||||||
def connect(self):
|
def _connect(self):
|
||||||
if self.is_fileoutput():
|
if self.is_fileoutput():
|
||||||
self._connect_file()
|
self._connect_file()
|
||||||
else:
|
else:
|
||||||
@@ -597,6 +590,12 @@ class MCU:
|
|||||||
self._emergency_stop_cmd = self.lookup_command("emergency_stop")
|
self._emergency_stop_cmd = self.lookup_command("emergency_stop")
|
||||||
self._reset_cmd = self.try_lookup_command("reset")
|
self._reset_cmd = self.try_lookup_command("reset")
|
||||||
self._config_reset_cmd = self.try_lookup_command("config_reset")
|
self._config_reset_cmd = self.try_lookup_command("config_reset")
|
||||||
|
if (self._restart_method is None
|
||||||
|
and (self._reset_cmd is not None
|
||||||
|
or self.config_reset_cmd is not None)
|
||||||
|
and self._serial.msgparser.get_constant(
|
||||||
|
'SERIAL_BAUD', None) is None):
|
||||||
|
self._restart_method = 'command'
|
||||||
self.register_msg(self.handle_shutdown, 'shutdown')
|
self.register_msg(self.handle_shutdown, 'shutdown')
|
||||||
self.register_msg(self.handle_shutdown, 'is_shutdown')
|
self.register_msg(self.handle_shutdown, 'is_shutdown')
|
||||||
self.register_msg(self.handle_mcu_stats, 'stats')
|
self.register_msg(self.handle_mcu_stats, 'stats')
|
||||||
@@ -633,23 +632,19 @@ class MCU:
|
|||||||
def get_max_stepper_error(self):
|
def get_max_stepper_error(self):
|
||||||
return self._max_stepper_error
|
return self._max_stepper_error
|
||||||
# Wrapper functions
|
# Wrapper functions
|
||||||
def send(self, cmd, minclock=0, reqclock=0, cq=None):
|
|
||||||
self._serial.send(cmd, minclock, reqclock, cq=cq)
|
|
||||||
def send_with_response(self, cmd, name, oid=None):
|
|
||||||
return self._serial.send_with_response(cmd, name, oid)
|
|
||||||
def register_msg(self, cb, msg, oid=None):
|
def register_msg(self, cb, msg, oid=None):
|
||||||
self._serial.register_callback(cb, msg, oid)
|
self._serial.register_callback(cb, msg, oid)
|
||||||
def alloc_command_queue(self):
|
def alloc_command_queue(self):
|
||||||
return self._serial.alloc_command_queue()
|
return self._serial.alloc_command_queue()
|
||||||
def create_command(self, msg):
|
def lookup_command(self, msgformat, cq=None):
|
||||||
return self._serial.msgparser.create_command(msg)
|
return self._serial.lookup_command(msgformat, cq)
|
||||||
def lookup_command(self, msgformat):
|
|
||||||
return self._serial.msgparser.lookup_command(msgformat)
|
|
||||||
def try_lookup_command(self, msgformat):
|
def try_lookup_command(self, msgformat):
|
||||||
try:
|
try:
|
||||||
return self._serial.msgparser.lookup_command(msgformat)
|
return self.lookup_command(msgformat)
|
||||||
except self._serial.msgparser.error as e:
|
except self._serial.msgparser.error as e:
|
||||||
return None
|
return None
|
||||||
|
def lookup_command_id(self, msgformat):
|
||||||
|
return self._serial.msgparser.lookup_command(msgformat).msgid
|
||||||
def get_constant_float(self, name):
|
def get_constant_float(self, name):
|
||||||
return self._serial.msgparser.get_constant_float(name)
|
return self._serial.msgparser.get_constant_float(name)
|
||||||
def print_time_to_clock(self, print_time):
|
def print_time_to_clock(self, print_time):
|
||||||
@@ -663,38 +658,46 @@ class MCU:
|
|||||||
def clock32_to_clock64(self, clock32):
|
def clock32_to_clock64(self, clock32):
|
||||||
return self._clocksync.clock32_to_clock64(clock32)
|
return self._clocksync.clock32_to_clock64(clock32)
|
||||||
def pause(self, waketime):
|
def pause(self, waketime):
|
||||||
return self._printer.reactor.pause(waketime)
|
return self._reactor.pause(waketime)
|
||||||
def monotonic(self):
|
def monotonic(self):
|
||||||
return self._printer.reactor.monotonic()
|
return self._reactor.monotonic()
|
||||||
# Restarts
|
# Restarts
|
||||||
|
def _disconnect(self):
|
||||||
|
self._serial.disconnect()
|
||||||
|
if self._steppersync is not None:
|
||||||
|
self._ffi_lib.steppersync_free(self._steppersync)
|
||||||
|
self._steppersync = None
|
||||||
|
def _shutdown(self, force=False):
|
||||||
|
if self._emergency_stop_cmd is None or (self._is_shutdown and not force):
|
||||||
|
return
|
||||||
|
self._emergency_stop_cmd.send()
|
||||||
def _restart_arduino(self):
|
def _restart_arduino(self):
|
||||||
logging.info("Attempting MCU '%s' reset", self._name)
|
logging.info("Attempting MCU '%s' reset", self._name)
|
||||||
self.disconnect()
|
self._disconnect()
|
||||||
serialhdl.arduino_reset(self._serialport, self._printer.reactor)
|
serialhdl.arduino_reset(self._serialport, self._reactor)
|
||||||
def _restart_via_command(self):
|
def _restart_via_command(self):
|
||||||
reactor = self._printer.reactor
|
|
||||||
if ((self._reset_cmd is None and self._config_reset_cmd is None)
|
if ((self._reset_cmd is None and self._config_reset_cmd is None)
|
||||||
or not self._clocksync.is_active(reactor.monotonic())):
|
or not self._clocksync.is_active(self._reactor.monotonic())):
|
||||||
logging.info("Unable to issue reset command on MCU '%s'", self._name)
|
logging.info("Unable to issue reset command on MCU '%s'", self._name)
|
||||||
return
|
return
|
||||||
if self._reset_cmd is None:
|
if self._reset_cmd is None:
|
||||||
# Attempt reset via config_reset command
|
# Attempt reset via config_reset command
|
||||||
logging.info("Attempting MCU '%s' config_reset command", self._name)
|
logging.info("Attempting MCU '%s' config_reset command", self._name)
|
||||||
self._is_shutdown = True
|
self._is_shutdown = True
|
||||||
self.do_shutdown(force=True)
|
self._shutdown(force=True)
|
||||||
reactor.pause(reactor.monotonic() + 0.015)
|
self._reactor.pause(self._reactor.monotonic() + 0.015)
|
||||||
self.send(self._config_reset_cmd.encode())
|
self._config_reset_cmd.send()
|
||||||
else:
|
else:
|
||||||
# Attempt reset via reset command
|
# Attempt reset via reset command
|
||||||
logging.info("Attempting MCU '%s' reset command", self._name)
|
logging.info("Attempting MCU '%s' reset command", self._name)
|
||||||
self.send(self._reset_cmd.encode())
|
self._reset_cmd.send()
|
||||||
reactor.pause(reactor.monotonic() + 0.015)
|
self._reactor.pause(self._reactor.monotonic() + 0.015)
|
||||||
self.disconnect()
|
self._disconnect()
|
||||||
def _restart_rpi_usb(self):
|
def _restart_rpi_usb(self):
|
||||||
logging.info("Attempting MCU '%s' reset via rpi usb power", self._name)
|
logging.info("Attempting MCU '%s' reset via rpi usb power", self._name)
|
||||||
self.disconnect()
|
self._disconnect()
|
||||||
chelper.run_hub_ctrl(0)
|
chelper.run_hub_ctrl(0)
|
||||||
self._printer.reactor.pause(self._printer.reactor.monotonic() + 2.)
|
self._reactor.pause(self._reactor.monotonic() + 2.)
|
||||||
chelper.run_hub_ctrl(1)
|
chelper.run_hub_ctrl(1)
|
||||||
def microcontroller_restart(self):
|
def microcontroller_restart(self):
|
||||||
if self._restart_method == 'rpi_usb':
|
if self._restart_method == 'rpi_usb':
|
||||||
@@ -723,8 +726,10 @@ class MCU:
|
|||||||
return
|
return
|
||||||
offset, freq = self._clocksync.calibrate_clock(print_time, eventtime)
|
offset, freq = self._clocksync.calibrate_clock(print_time, eventtime)
|
||||||
self._ffi_lib.steppersync_set_time(self._steppersync, offset, freq)
|
self._ffi_lib.steppersync_set_time(self._steppersync, offset, freq)
|
||||||
if self._clocksync.is_active(eventtime) or self.is_fileoutput():
|
if (self._clocksync.is_active(eventtime) or self.is_fileoutput()
|
||||||
|
or self._is_timeout):
|
||||||
return
|
return
|
||||||
|
self._is_timeout = True
|
||||||
logging.info("Timeout with MCU '%s' (eventtime=%f)",
|
logging.info("Timeout with MCU '%s' (eventtime=%f)",
|
||||||
self._name, eventtime)
|
self._name, eventtime)
|
||||||
self._printer.invoke_shutdown("Lost communication with MCU '%s'" % (
|
self._printer.invoke_shutdown("Lost communication with MCU '%s'" % (
|
||||||
@@ -733,19 +738,17 @@ class MCU:
|
|||||||
msg = "%s: mcu_awake=%.03f mcu_task_avg=%.06f mcu_task_stddev=%.06f" % (
|
msg = "%s: mcu_awake=%.03f mcu_task_avg=%.06f mcu_task_stddev=%.06f" % (
|
||||||
self._name, self._mcu_tick_awake, self._mcu_tick_avg,
|
self._name, self._mcu_tick_awake, self._mcu_tick_avg,
|
||||||
self._mcu_tick_stddev)
|
self._mcu_tick_stddev)
|
||||||
return ' '.join([msg, self._serial.stats(eventtime),
|
return False, ' '.join([msg, self._serial.stats(eventtime),
|
||||||
self._clocksync.stats(eventtime)])
|
self._clocksync.stats(eventtime)])
|
||||||
def do_shutdown(self, force=False):
|
def printer_state(self, state):
|
||||||
if self._emergency_stop_cmd is None or (self._is_shutdown and not force):
|
if state == 'connect':
|
||||||
return
|
self._connect()
|
||||||
self.send(self._emergency_stop_cmd.encode())
|
elif state == 'disconnect':
|
||||||
def disconnect(self):
|
self._disconnect()
|
||||||
self._serial.disconnect()
|
elif state == 'shutdown':
|
||||||
if self._steppersync is not None:
|
self._shutdown()
|
||||||
self._ffi_lib.steppersync_free(self._steppersync)
|
|
||||||
self._steppersync = None
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.disconnect()
|
self._disconnect()
|
||||||
|
|
||||||
Common_MCU_errors = {
|
Common_MCU_errors = {
|
||||||
("Timer too close", "No next step", "Missed scheduling of next "): """
|
("Timer too close", "No next step", "Missed scheduling of next "): """
|
||||||
@@ -771,20 +774,14 @@ def error_help(msg):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
def add_printer_objects(printer, config):
|
def add_printer_objects(printer, config):
|
||||||
mainsync = clocksync.ClockSync(printer.reactor)
|
reactor = printer.get_reactor()
|
||||||
|
mainsync = clocksync.ClockSync(reactor)
|
||||||
printer.add_object('mcu', MCU(printer, config.getsection('mcu'), mainsync))
|
printer.add_object('mcu', MCU(printer, config.getsection('mcu'), mainsync))
|
||||||
for s in config.get_prefix_sections('mcu '):
|
for s in config.get_prefix_sections('mcu '):
|
||||||
printer.add_object(s.section, MCU(
|
printer.add_object(s.section, MCU(
|
||||||
printer, s, clocksync.SecondarySync(printer.reactor, mainsync)))
|
printer, s, clocksync.SecondarySync(reactor, mainsync)))
|
||||||
|
|
||||||
def get_printer_mcus(printer):
|
|
||||||
return [printer.objects[n] for n in sorted(printer.objects)
|
|
||||||
if n.startswith('mcu')]
|
|
||||||
|
|
||||||
def get_printer_mcu(printer, name):
|
def get_printer_mcu(printer, name):
|
||||||
mcu_name = name
|
if name == 'mcu':
|
||||||
if name != 'mcu':
|
return printer.lookup_object(name)
|
||||||
mcu_name = 'mcu ' + name
|
return printer.lookup_object('mcu ' + name)
|
||||||
if mcu_name not in printer.objects:
|
|
||||||
raise printer.config_error("Unknown MCU %s" % (name,))
|
|
||||||
return printer.objects[mcu_name]
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class MessageFormat:
|
|||||||
self.param_types = [MessageTypes[fmt] for name, fmt in argparts]
|
self.param_types = [MessageTypes[fmt] for name, fmt in argparts]
|
||||||
self.param_names = [(name, MessageTypes[fmt]) for name, fmt in argparts]
|
self.param_names = [(name, MessageTypes[fmt]) for name, fmt in argparts]
|
||||||
self.name_to_type = dict(self.param_names)
|
self.name_to_type = dict(self.param_names)
|
||||||
def encode(self, *params):
|
def encode(self, params):
|
||||||
out = []
|
out = []
|
||||||
out.append(self.msgid)
|
out.append(self.msgid)
|
||||||
for i, t in enumerate(self.param_types):
|
for i, t in enumerate(self.param_types):
|
||||||
@@ -188,7 +188,7 @@ class MessageParser:
|
|||||||
self.messages_by_name = {}
|
self.messages_by_name = {}
|
||||||
self.static_strings = {}
|
self.static_strings = {}
|
||||||
self.config = {}
|
self.config = {}
|
||||||
self.version = ""
|
self.version = self.build_versions = ""
|
||||||
self.raw_identify_data = ""
|
self.raw_identify_data = ""
|
||||||
self._init_messages(DefaultMessages, DefaultMessages.keys())
|
self._init_messages(DefaultMessages, DefaultMessages.keys())
|
||||||
def check_packet(self, s):
|
def check_packet(self, s):
|
||||||
@@ -318,17 +318,22 @@ class MessageParser:
|
|||||||
self.static_strings = { int(k): v for k, v in static_strings.items() }
|
self.static_strings = { int(k): v for k, v in static_strings.items() }
|
||||||
self.config.update(data.get('config', {}))
|
self.config.update(data.get('config', {}))
|
||||||
self.version = data.get('version', '')
|
self.version = data.get('version', '')
|
||||||
|
self.build_versions = data.get('build_versions', '')
|
||||||
except error as e:
|
except error as e:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception("process_identify error")
|
logging.exception("process_identify error")
|
||||||
raise error("Error during identify: %s" % (str(e),))
|
raise error("Error during identify: %s" % (str(e),))
|
||||||
def get_constant(self, name):
|
class sentinel: pass
|
||||||
try:
|
def get_constant(self, name, default=sentinel):
|
||||||
return self.config[name]
|
if name not in self.config:
|
||||||
except KeyError:
|
if default is not self.sentinel:
|
||||||
|
return default
|
||||||
raise error("Firmware constant '%s' not found" % (name,))
|
raise error("Firmware constant '%s' not found" % (name,))
|
||||||
def get_constant_float(self, name):
|
return self.config[name]
|
||||||
|
def get_constant_float(self, name, default=sentinel):
|
||||||
|
if name not in self.config and default is not self.sentinel:
|
||||||
|
return default
|
||||||
try:
|
try:
|
||||||
return float(self.config[name])
|
return float(self.config[name])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Pin name to pin number definitions
|
# Pin name to pin number definitions
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import re
|
import re
|
||||||
@@ -31,9 +31,9 @@ def beaglebone_pins():
|
|||||||
return gpios
|
return gpios
|
||||||
|
|
||||||
MCU_PINS = {
|
MCU_PINS = {
|
||||||
"atmega168": port_pins(4), "atmega328": port_pins(4),
|
"atmega168": port_pins(5), "atmega328": port_pins(5),
|
||||||
"atmega644p": port_pins(4), "atmega1284p": port_pins(4),
|
"atmega644p": port_pins(4), "atmega1284p": port_pins(4),
|
||||||
"at90usb1286": port_pins(6),
|
"at90usb1286": port_pins(6), "at90usb646": port_pins(6),
|
||||||
"atmega1280": port_pins(12), "atmega2560": port_pins(12),
|
"atmega1280": port_pins(12), "atmega2560": port_pins(12),
|
||||||
"sam3x8e": port_pins(4, 32),
|
"sam3x8e": port_pins(4, 32),
|
||||||
"pru": beaglebone_pins(),
|
"pru": beaglebone_pins(),
|
||||||
@@ -95,6 +95,7 @@ Arduino_Due_analog = [
|
|||||||
|
|
||||||
Arduino_from_mcu = {
|
Arduino_from_mcu = {
|
||||||
"atmega168": (Arduino_standard, Arduino_analog_standard),
|
"atmega168": (Arduino_standard, Arduino_analog_standard),
|
||||||
|
"atmega328": (Arduino_standard, Arduino_analog_standard),
|
||||||
"atmega644p": (Sanguino, Sanguino_analog),
|
"atmega644p": (Sanguino, Sanguino_analog),
|
||||||
"atmega1280": (Arduino_mega, Arduino_analog_mega),
|
"atmega1280": (Arduino_mega, Arduino_analog_mega),
|
||||||
"atmega2560": (Arduino_mega, Arduino_analog_mega),
|
"atmega2560": (Arduino_mega, Arduino_analog_mega),
|
||||||
@@ -151,20 +152,31 @@ def update_map_beaglebone(pins, mcu):
|
|||||||
# Command translation
|
# Command translation
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
# Obtains the pin mappings
|
|
||||||
def get_pin_map(mcu, mapping_name=None):
|
|
||||||
pins = dict(MCU_PINS.get(mcu, {}))
|
|
||||||
if mapping_name == 'arduino':
|
|
||||||
update_map_arduino(pins, mcu)
|
|
||||||
elif mapping_name == 'beaglebone':
|
|
||||||
update_map_beaglebone(pins, mcu)
|
|
||||||
return pins
|
|
||||||
|
|
||||||
# Translate pin names in a firmware command
|
|
||||||
re_pin = re.compile(r'(?P<prefix>[ _]pin=)(?P<name>[^ ]*)')
|
re_pin = re.compile(r'(?P<prefix>[ _]pin=)(?P<name>[^ ]*)')
|
||||||
def update_command(cmd, pmap):
|
|
||||||
|
class PinResolver:
|
||||||
|
def __init__(self, mcu_type, validate_aliases=True):
|
||||||
|
self.mcu_type = mcu_type
|
||||||
|
self.validate_aliases = validate_aliases
|
||||||
|
self.pins = dict(MCU_PINS.get(mcu_type, {}))
|
||||||
|
self.active_pins = {}
|
||||||
|
def update_aliases(self, mapping_name):
|
||||||
|
self.pins = dict(MCU_PINS.get(self.mcu_type, {}))
|
||||||
|
if mapping_name == 'arduino':
|
||||||
|
update_map_arduino(self.pins, self.mcu_type)
|
||||||
|
elif mapping_name == 'beaglebone':
|
||||||
|
update_map_beaglebone(self.pins, self.mcu_type)
|
||||||
|
def update_command(self, cmd):
|
||||||
def pin_fixup(m):
|
def pin_fixup(m):
|
||||||
return m.group('prefix') + str(pmap[m.group('name')])
|
name = m.group('name')
|
||||||
|
if name not in self.pins:
|
||||||
|
raise error("Unable to translate pin name: %s" % (cmd,))
|
||||||
|
pin_id = self.pins[name]
|
||||||
|
if (name != self.active_pins.setdefault(pin_id, name)
|
||||||
|
and self.validate_aliases):
|
||||||
|
raise error("pin %s is an alias for %s" % (
|
||||||
|
name, self.active_pins[pin_id]))
|
||||||
|
return m.group('prefix') + str(pin_id)
|
||||||
return re_pin.sub(pin_fixup, cmd)
|
return re_pin.sub(pin_fixup, cmd)
|
||||||
|
|
||||||
|
|
||||||
@@ -179,22 +191,49 @@ class PrinterPins:
|
|||||||
error = error
|
error = error
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.chips = {}
|
self.chips = {}
|
||||||
def parse_pin_desc(self, pin_desc, can_invert=False, can_pullup=False):
|
self.active_pins = {}
|
||||||
|
def lookup_pin(self, pin_type, pin_desc, share_type=None):
|
||||||
|
can_invert = pin_type in ['stepper', 'endstop', 'digital_out', 'pwm']
|
||||||
|
can_pullup = pin_type == 'endstop'
|
||||||
|
desc = pin_desc
|
||||||
pullup = invert = 0
|
pullup = invert = 0
|
||||||
if can_pullup and pin_desc.startswith('^'):
|
if can_pullup and desc.startswith('^'):
|
||||||
pullup = 1
|
pullup = 1
|
||||||
pin_desc = pin_desc[1:].strip()
|
desc = desc[1:].strip()
|
||||||
if can_invert and pin_desc.startswith('!'):
|
if can_invert and desc.startswith('!'):
|
||||||
invert = 1
|
invert = 1
|
||||||
pin_desc = pin_desc[1:].strip()
|
desc = desc[1:].strip()
|
||||||
if ':' not in pin_desc:
|
if ':' not in desc:
|
||||||
chip_name, pin = 'mcu', pin_desc
|
chip_name, pin = 'mcu', desc
|
||||||
else:
|
else:
|
||||||
chip_name, pin = [s.strip() for s in pin_desc.split(':', 1)]
|
chip_name, pin = [s.strip() for s in desc.split(':', 1)]
|
||||||
if chip_name not in self.chips:
|
if chip_name not in self.chips:
|
||||||
raise error("Unknown pin chip name '%s'" % (chip_name,))
|
raise error("Unknown pin chip name '%s'" % (chip_name,))
|
||||||
return {'chip': self.chips[chip_name], 'pin': pin,
|
if [c for c in '^!: ' if c in pin]:
|
||||||
'invert': invert, 'pullup': pullup}
|
format = ""
|
||||||
|
if can_pullup:
|
||||||
|
format += "[^] "
|
||||||
|
if can_invert:
|
||||||
|
format += "[!] "
|
||||||
|
raise error("Invalid pin description '%s'\n"
|
||||||
|
"Format is: %s[chip_name:] pin_name" % (
|
||||||
|
pin_desc, format))
|
||||||
|
share_name = "%s:%s" % (chip_name, pin)
|
||||||
|
if share_name in self.active_pins:
|
||||||
|
pin_params = self.active_pins[share_name]
|
||||||
|
if share_type is None or share_type != pin_params['share_type']:
|
||||||
|
raise error("pin %s used multiple times in config" % (pin,))
|
||||||
|
if invert != pin_params['invert'] or pullup != pin_params['pullup']:
|
||||||
|
raise error("Shared pin %s must have same polarity" % (pin,))
|
||||||
|
return pin_params
|
||||||
|
pin_params = {'chip': self.chips[chip_name], 'chip_name': chip_name,
|
||||||
|
'type': pin_type, 'share_type': share_type,
|
||||||
|
'pin': pin, 'invert': invert, 'pullup': pullup}
|
||||||
|
self.active_pins[share_name] = pin_params
|
||||||
|
return pin_params
|
||||||
|
def setup_pin(self, pin_type, pin_desc):
|
||||||
|
pin_params = self.lookup_pin(pin_type, pin_desc)
|
||||||
|
return pin_params['chip'].setup_pin(pin_params)
|
||||||
def register_chip(self, chip_name, chip):
|
def register_chip(self, chip_name, chip):
|
||||||
chip_name = chip_name.strip()
|
chip_name = chip_name.strip()
|
||||||
if chip_name in self.chips:
|
if chip_name in self.chips:
|
||||||
@@ -205,12 +244,7 @@ def add_printer_objects(printer, config):
|
|||||||
printer.add_object('pins', PrinterPins())
|
printer.add_object('pins', PrinterPins())
|
||||||
|
|
||||||
def get_printer_pins(printer):
|
def get_printer_pins(printer):
|
||||||
return printer.objects['pins']
|
return printer.lookup_object('pins')
|
||||||
|
|
||||||
def setup_pin(printer, pin_type, pin_desc):
|
def setup_pin(printer, pin_type, pin_desc):
|
||||||
ppins = get_printer_pins(printer)
|
return get_printer_pins(printer).setup_pin(pin_type, pin_desc)
|
||||||
can_invert = pin_type in ['stepper', 'endstop', 'digital_out', 'pwm']
|
|
||||||
can_pullup = pin_type == 'endstop'
|
|
||||||
pin_params = ppins.parse_pin_desc(pin_desc, can_invert, can_pullup)
|
|
||||||
pin_params['type'] = pin_type
|
|
||||||
return pin_params['chip'].setup_pin(pin_params)
|
|
||||||
|
|||||||
@@ -84,13 +84,14 @@ class SerialReader:
|
|||||||
msgparser.process_identify(identify_data)
|
msgparser.process_identify(identify_data)
|
||||||
self.msgparser = msgparser
|
self.msgparser = msgparser
|
||||||
self.register_callback(self.handle_unknown, '#unknown')
|
self.register_callback(self.handle_unknown, '#unknown')
|
||||||
logging.info("Loaded %d commands (%s)",
|
logging.info("Loaded %d commands (%s / %s)",
|
||||||
len(msgparser.messages_by_id), msgparser.version)
|
len(msgparser.messages_by_id),
|
||||||
|
msgparser.version, msgparser.build_versions)
|
||||||
logging.info("MCU config: %s", " ".join(
|
logging.info("MCU config: %s", " ".join(
|
||||||
["%s=%s" % (k, v) for k, v in msgparser.config.items()]))
|
["%s=%s" % (k, v) for k, v in msgparser.config.items()]))
|
||||||
# Setup baud adjust
|
# Setup baud adjust
|
||||||
mcu_baud = float(msgparser.config.get('SERIAL_BAUD', 0.))
|
mcu_baud = msgparser.get_constant_float('SERIAL_BAUD', None)
|
||||||
if mcu_baud:
|
if mcu_baud is not None:
|
||||||
baud_adjust = self.BITS_PER_BYTE / mcu_baud
|
baud_adjust = self.BITS_PER_BYTE / mcu_baud
|
||||||
self.ffi_lib.serialqueue_set_baud_adjust(
|
self.ffi_lib.serialqueue_set_baud_adjust(
|
||||||
self.serialqueue, baud_adjust)
|
self.serialqueue, baud_adjust)
|
||||||
@@ -125,17 +126,17 @@ class SerialReader:
|
|||||||
with self.lock:
|
with self.lock:
|
||||||
del self.handlers[name, oid]
|
del self.handlers[name, oid]
|
||||||
# Command sending
|
# Command sending
|
||||||
def send(self, cmd, minclock=0, reqclock=0, cq=None):
|
def raw_send(self, cmd, minclock, reqclock, cmd_queue):
|
||||||
|
self.ffi_lib.serialqueue_send(
|
||||||
|
self.serialqueue, cmd_queue, cmd, len(cmd), minclock, reqclock)
|
||||||
|
def send(self, msg, minclock=0, reqclock=0):
|
||||||
|
cmd = self.msgparser.create_command(msg)
|
||||||
|
self.raw_send(cmd, minclock, reqclock, self.default_cmd_queue)
|
||||||
|
def lookup_command(self, msgformat, cq=None):
|
||||||
if cq is None:
|
if cq is None:
|
||||||
cq = self.default_cmd_queue
|
cq = self.default_cmd_queue
|
||||||
self.ffi_lib.serialqueue_send(
|
cmd = self.msgparser.lookup_command(msgformat)
|
||||||
self.serialqueue, cq, cmd, len(cmd), minclock, reqclock)
|
return SerialCommand(self, cq, cmd)
|
||||||
def encode_and_send(self, data, minclock, reqclock, cq):
|
|
||||||
self.ffi_lib.serialqueue_encode_and_send(
|
|
||||||
self.serialqueue, cq, data, len(data), minclock, reqclock)
|
|
||||||
def send_with_response(self, cmd, name, oid=None):
|
|
||||||
src = SerialRetryCommand(self, cmd, name, oid)
|
|
||||||
return src.get_response()
|
|
||||||
def alloc_command_queue(self):
|
def alloc_command_queue(self):
|
||||||
return self.ffi_main.gc(self.ffi_lib.serialqueue_alloc_commandqueue(),
|
return self.ffi_main.gc(self.ffi_lib.serialqueue_alloc_commandqueue(),
|
||||||
self.ffi_lib.serialqueue_free_commandqueue)
|
self.ffi_lib.serialqueue_free_commandqueue)
|
||||||
@@ -174,6 +175,20 @@ class SerialReader:
|
|||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
|
# Wrapper around command sending
|
||||||
|
class SerialCommand:
|
||||||
|
def __init__(self, serial, cmd_queue, cmd):
|
||||||
|
self.serial = serial
|
||||||
|
self.cmd_queue = cmd_queue
|
||||||
|
self.cmd = cmd
|
||||||
|
def send(self, data=(), minclock=0, reqclock=0):
|
||||||
|
cmd = self.cmd.encode(data)
|
||||||
|
self.serial.raw_send(cmd, minclock, reqclock, self.cmd_queue)
|
||||||
|
def send_with_response(self, data=(), response=None, response_oid=None):
|
||||||
|
cmd = self.cmd.encode(data)
|
||||||
|
src = SerialRetryCommand(self.serial, cmd, response, response_oid)
|
||||||
|
return src.get_response()
|
||||||
|
|
||||||
# Class to retry sending of a query command until a given response is received
|
# Class to retry sending of a query command until a given response is received
|
||||||
class SerialRetryCommand:
|
class SerialRetryCommand:
|
||||||
TIMEOUT_TIME = 5.0
|
TIMEOUT_TIME = 5.0
|
||||||
@@ -194,7 +209,7 @@ class SerialRetryCommand:
|
|||||||
def send_event(self, eventtime):
|
def send_event(self, eventtime):
|
||||||
if self.response is not None:
|
if self.response is not None:
|
||||||
return self.serial.reactor.NEVER
|
return self.serial.reactor.NEVER
|
||||||
self.serial.send(self.cmd)
|
self.serial.raw_send(self.cmd, 0, 0, self.serial.default_cmd_queue)
|
||||||
return eventtime + self.RETRY_TIME
|
return eventtime + self.RETRY_TIME
|
||||||
def handle_callback(self, params):
|
def handle_callback(self, params):
|
||||||
last_sent_time = params['#sent_time']
|
last_sent_time = params['#sent_time']
|
||||||
@@ -216,7 +231,7 @@ class SerialBootStrap:
|
|||||||
def __init__(self, serial):
|
def __init__(self, serial):
|
||||||
self.serial = serial
|
self.serial = serial
|
||||||
self.identify_data = ""
|
self.identify_data = ""
|
||||||
self.identify_cmd = self.serial.msgparser.lookup_command(
|
self.identify_cmd = self.serial.lookup_command(
|
||||||
"identify offset=%u count=%c")
|
"identify offset=%u count=%c")
|
||||||
self.is_done = False
|
self.is_done = False
|
||||||
self.serial.register_callback(self.handle_identify, 'identify_response')
|
self.serial.register_callback(self.handle_identify, 'identify_response')
|
||||||
@@ -240,13 +255,11 @@ class SerialBootStrap:
|
|||||||
self.is_done = True
|
self.is_done = True
|
||||||
return
|
return
|
||||||
self.identify_data += msgdata
|
self.identify_data += msgdata
|
||||||
imsg = self.identify_cmd.encode(len(self.identify_data), 40)
|
self.identify_cmd.send([len(self.identify_data), 40])
|
||||||
self.serial.send(imsg)
|
|
||||||
def send_event(self, eventtime):
|
def send_event(self, eventtime):
|
||||||
if self.is_done:
|
if self.is_done:
|
||||||
return self.serial.reactor.NEVER
|
return self.serial.reactor.NEVER
|
||||||
imsg = self.identify_cmd.encode(len(self.identify_data), 40)
|
self.identify_cmd.send([len(self.identify_data), 40])
|
||||||
self.serial.send(imsg)
|
|
||||||
return eventtime + self.RETRY_TIME
|
return eventtime + self.RETRY_TIME
|
||||||
def handle_unknown(self, params):
|
def handle_unknown(self, params):
|
||||||
logging.debug("Unknown message %d (len %d) while identifying",
|
logging.debug("Unknown message %d (len %d) while identifying",
|
||||||
|
|||||||
@@ -147,7 +147,6 @@ pollreactor_check_timers(struct pollreactor *pr, double eventtime)
|
|||||||
static void
|
static void
|
||||||
pollreactor_run(struct pollreactor *pr)
|
pollreactor_run(struct pollreactor *pr)
|
||||||
{
|
{
|
||||||
pr->must_exit = 0;
|
|
||||||
double eventtime = get_monotonic();
|
double eventtime = get_monotonic();
|
||||||
while (! pr->must_exit) {
|
while (! pr->must_exit) {
|
||||||
int timeout = pollreactor_check_timers(pr, eventtime);
|
int timeout = pollreactor_check_timers(pr, eventtime);
|
||||||
@@ -390,6 +389,7 @@ struct serialqueue {
|
|||||||
#define MIN_RTO 0.025
|
#define MIN_RTO 0.025
|
||||||
#define MAX_RTO 5.000
|
#define MAX_RTO 5.000
|
||||||
#define MIN_REQTIME_DELTA 0.250
|
#define MIN_REQTIME_DELTA 0.250
|
||||||
|
#define MIN_BACKGROUND_DELTA 0.005
|
||||||
#define IDLE_QUERY_TIME 1.0
|
#define IDLE_QUERY_TIME 1.0
|
||||||
|
|
||||||
#define DEBUG_QUEUE_SENT 100
|
#define DEBUG_QUEUE_SENT 100
|
||||||
@@ -723,8 +723,13 @@ check_send_command(struct serialqueue *sq, double eventtime)
|
|||||||
if (!list_empty(&cq->ready_queue)) {
|
if (!list_empty(&cq->ready_queue)) {
|
||||||
struct queue_message *qm = list_first_entry(
|
struct queue_message *qm = list_first_entry(
|
||||||
&cq->ready_queue, struct queue_message, node);
|
&cq->ready_queue, struct queue_message, node);
|
||||||
if (qm->req_clock < min_ready_clock)
|
uint64_t req_clock = qm->req_clock;
|
||||||
min_ready_clock = qm->req_clock;
|
if (req_clock == BACKGROUND_PRIORITY_CLOCK)
|
||||||
|
req_clock = (uint64_t)(
|
||||||
|
(sq->idle_time - sq->last_clock_time + MIN_BACKGROUND_DELTA)
|
||||||
|
* sq->est_freq) + sq->last_clock;
|
||||||
|
if (req_clock < min_ready_clock)
|
||||||
|
min_ready_clock = req_clock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -908,7 +913,8 @@ serialqueue_send_batch(struct serialqueue *sq, struct command_queue *cq
|
|||||||
int len = 0;
|
int len = 0;
|
||||||
struct queue_message *qm;
|
struct queue_message *qm;
|
||||||
list_for_each_entry(qm, msgs, node) {
|
list_for_each_entry(qm, msgs, node) {
|
||||||
if (qm->min_clock + (1LL<<31) < qm->req_clock)
|
if (qm->min_clock + (1LL<<31) < qm->req_clock
|
||||||
|
&& qm->req_clock != BACKGROUND_PRIORITY_CLOCK)
|
||||||
qm->min_clock = qm->req_clock - (1LL<<31);
|
qm->min_clock = qm->req_clock - (1LL<<31);
|
||||||
len += qm->len;
|
len += qm->len;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
#include "list.h" // struct list_head
|
#include "list.h" // struct list_head
|
||||||
|
|
||||||
#define MAX_CLOCK 0x7fffffffffffffff
|
#define MAX_CLOCK 0x7fffffffffffffffLL
|
||||||
|
#define BACKGROUND_PRIORITY_CLOCK 0x7fffffff00000000LL
|
||||||
|
|
||||||
#define MESSAGE_MIN 5
|
#define MESSAGE_MIN 5
|
||||||
#define MESSAGE_MAX 64
|
#define MESSAGE_MAX 64
|
||||||
|
|||||||
@@ -423,11 +423,14 @@ queue_append_slow(struct stepcompress *sc, double rel_sc)
|
|||||||
|
|
||||||
if (sc->queue_next - sc->queue_pos > 65535 + 2000) {
|
if (sc->queue_next - sc->queue_pos > 65535 + 2000) {
|
||||||
// No point in keeping more than 64K steps in memory
|
// No point in keeping more than 64K steps in memory
|
||||||
int ret = stepcompress_flush(sc, *(sc->queue_next - 65535));
|
uint32_t flush = *(sc->queue_next-65535) - (uint32_t)sc->last_step_clock;
|
||||||
|
int ret = stepcompress_flush(sc, sc->last_step_clock + flush);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sc->queue_next >= sc->queue_end) {
|
||||||
|
// Make room in the queue
|
||||||
int in_use = sc->queue_next - sc->queue_pos;
|
int in_use = sc->queue_next - sc->queue_pos;
|
||||||
if (sc->queue_pos > sc->queue) {
|
if (sc->queue_pos > sc->queue) {
|
||||||
// Shuffle the internal queue to avoid having to allocate more ram
|
// Shuffle the internal queue to avoid having to allocate more ram
|
||||||
@@ -444,6 +447,8 @@ queue_append_slow(struct stepcompress *sc, double rel_sc)
|
|||||||
}
|
}
|
||||||
sc->queue_pos = sc->queue;
|
sc->queue_pos = sc->queue;
|
||||||
sc->queue_next = sc->queue + in_use;
|
sc->queue_next = sc->queue + in_use;
|
||||||
|
}
|
||||||
|
|
||||||
*sc->queue_next++ = abs_step_clock;
|
*sc->queue_next++ = abs_step_clock;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,57 @@
|
|||||||
# Printer stepper support
|
# Printer stepper support
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import math, logging
|
import math, logging
|
||||||
import homing, pins
|
import homing, pins
|
||||||
|
|
||||||
class PrinterStepper:
|
# Tracking of shared stepper enable pins
|
||||||
def __init__(self, printer, config, name):
|
class StepperEnablePin:
|
||||||
self.name = name
|
def __init__(self, mcu_enable, enable_count=0):
|
||||||
|
self.mcu_enable = mcu_enable
|
||||||
|
self.enable_count = enable_count
|
||||||
|
def set_enable(self, print_time, enable):
|
||||||
|
if enable:
|
||||||
|
if not self.enable_count:
|
||||||
|
self.mcu_enable.set_digital(print_time, 1)
|
||||||
|
self.enable_count += 1
|
||||||
|
else:
|
||||||
|
self.enable_count -= 1
|
||||||
|
if not self.enable_count:
|
||||||
|
self.mcu_enable.set_digital(print_time, 0)
|
||||||
|
|
||||||
self.step_dist = config.getfloat('step_distance', above=0.)
|
def lookup_enable_pin(printer, pin):
|
||||||
self.inv_step_dist = 1. / self.step_dist
|
if pin is None:
|
||||||
self.min_stop_interval = 0.
|
return StepperEnablePin(None, 9999)
|
||||||
|
pin_params = pins.get_printer_pins(printer).lookup_pin(
|
||||||
|
'digital_out', pin, 'stepper_enable')
|
||||||
|
enable = pin_params.get('class')
|
||||||
|
if enable is None:
|
||||||
|
mcu_enable = pin_params['chip'].setup_pin(pin_params)
|
||||||
|
mcu_enable.setup_max_duration(0.)
|
||||||
|
pin_params['class'] = enable = StepperEnablePin(mcu_enable)
|
||||||
|
return enable
|
||||||
|
|
||||||
|
# Code storing the definitions for a stepper motor
|
||||||
|
class PrinterStepper:
|
||||||
|
def __init__(self, printer, config):
|
||||||
|
self.name = config.get_name()
|
||||||
|
if self.name.startswith('stepper_'):
|
||||||
|
self.name = self.name[8:]
|
||||||
|
self.need_motor_enable = True
|
||||||
|
# Stepper definition
|
||||||
self.mcu_stepper = pins.setup_pin(
|
self.mcu_stepper = pins.setup_pin(
|
||||||
printer, 'stepper', config.get('step_pin'))
|
printer, 'stepper', config.get('step_pin'))
|
||||||
dir_pin_params = pins.get_printer_pins(printer).parse_pin_desc(
|
dir_pin_params = pins.get_printer_pins(printer).lookup_pin(
|
||||||
config.get('dir_pin'), can_invert=True)
|
'digital_out', config.get('dir_pin'))
|
||||||
self.mcu_stepper.setup_dir_pin(dir_pin_params)
|
self.mcu_stepper.setup_dir_pin(dir_pin_params)
|
||||||
|
self.step_dist = config.getfloat('step_distance', above=0.)
|
||||||
self.mcu_stepper.setup_step_distance(self.step_dist)
|
self.mcu_stepper.setup_step_distance(self.step_dist)
|
||||||
|
self.step = self.mcu_stepper.step
|
||||||
enable_pin = config.get('enable_pin', None)
|
self.step_const = self.mcu_stepper.step_const
|
||||||
self.mcu_enable = None
|
self.step_delta = self.mcu_stepper.step_delta
|
||||||
if enable_pin is not None:
|
self.enable = lookup_enable_pin(printer, config.get('enable_pin', None))
|
||||||
self.mcu_enable = pins.setup_pin(printer, 'digital_out', enable_pin)
|
|
||||||
self.mcu_enable.setup_max_duration(0.)
|
|
||||||
self.need_motor_enable = True
|
|
||||||
def _dist_to_time(self, dist, start_velocity, accel):
|
def _dist_to_time(self, dist, start_velocity, accel):
|
||||||
# Calculate the time it takes to travel a distance with constant accel
|
# Calculate the time it takes to travel a distance with constant accel
|
||||||
time_offset = start_velocity / accel
|
time_offset = start_velocity / accel
|
||||||
@@ -38,27 +64,34 @@ class PrinterStepper:
|
|||||||
2. * self.step_dist, max_halt_velocity, max_accel)
|
2. * self.step_dist, max_halt_velocity, max_accel)
|
||||||
min_stop_interval = second_last_step_time - last_step_time
|
min_stop_interval = second_last_step_time - last_step_time
|
||||||
self.mcu_stepper.setup_min_stop_interval(min_stop_interval)
|
self.mcu_stepper.setup_min_stop_interval(min_stop_interval)
|
||||||
|
def set_position(self, pos):
|
||||||
|
self.mcu_stepper.set_position(pos)
|
||||||
def motor_enable(self, print_time, enable=0):
|
def motor_enable(self, print_time, enable=0):
|
||||||
if enable and self.need_motor_enable:
|
if self.need_motor_enable != (not enable):
|
||||||
self.mcu_stepper.reset_step_clock(print_time)
|
self.enable.set_enable(print_time, enable)
|
||||||
if (self.mcu_enable is not None
|
|
||||||
and self.mcu_enable.get_last_setting() != enable):
|
|
||||||
self.mcu_enable.set_digital(print_time, enable)
|
|
||||||
self.need_motor_enable = not enable
|
self.need_motor_enable = not enable
|
||||||
|
|
||||||
|
# Support for stepper controlled linear axis with an endstop
|
||||||
class PrinterHomingStepper(PrinterStepper):
|
class PrinterHomingStepper(PrinterStepper):
|
||||||
def __init__(self, printer, config, name):
|
def __init__(self, printer, config, default_position=None):
|
||||||
PrinterStepper.__init__(self, printer, config, name)
|
PrinterStepper.__init__(self, printer, config)
|
||||||
|
# Endstop and its position
|
||||||
self.mcu_endstop = pins.setup_pin(
|
self.mcu_endstop = pins.setup_pin(
|
||||||
printer, 'endstop', config.get('endstop_pin'))
|
printer, 'endstop', config.get('endstop_pin'))
|
||||||
self.mcu_endstop.add_stepper(self.mcu_stepper)
|
self.mcu_endstop.add_stepper(self.mcu_stepper)
|
||||||
|
if default_position is None:
|
||||||
|
self.position_endstop = config.getfloat('position_endstop')
|
||||||
|
else:
|
||||||
|
self.position_endstop = config.getfloat(
|
||||||
|
'position_endstop', default_position)
|
||||||
|
# Axis range
|
||||||
self.position_min = config.getfloat('position_min', 0.)
|
self.position_min = config.getfloat('position_min', 0.)
|
||||||
self.position_max = config.getfloat(
|
self.position_max = config.getfloat(
|
||||||
'position_max', 0., above=self.position_min)
|
'position_max', 0., above=self.position_min)
|
||||||
self.position_endstop = config.getfloat('position_endstop')
|
# Homing mechanics
|
||||||
|
|
||||||
self.homing_speed = config.getfloat('homing_speed', 5.0, above=0.)
|
self.homing_speed = config.getfloat('homing_speed', 5.0, above=0.)
|
||||||
|
self.homing_retract_dist = config.getfloat(
|
||||||
|
'homing_retract_dist', 5., minval=0.)
|
||||||
self.homing_positive_dir = config.getboolean('homing_positive_dir', None)
|
self.homing_positive_dir = config.getboolean('homing_positive_dir', None)
|
||||||
if self.homing_positive_dir is None:
|
if self.homing_positive_dir is None:
|
||||||
axis_len = self.position_max - self.position_min
|
axis_len = self.position_max - self.position_min
|
||||||
@@ -69,9 +102,8 @@ class PrinterHomingStepper(PrinterStepper):
|
|||||||
else:
|
else:
|
||||||
raise config.error(
|
raise config.error(
|
||||||
"Unable to infer homing_positive_dir in section '%s'" % (
|
"Unable to infer homing_positive_dir in section '%s'" % (
|
||||||
config.section,))
|
config.get_name(),))
|
||||||
self.homing_retract_dist = config.getfloat(
|
# Endstop stepper phase position tracking
|
||||||
'homing_retract_dist', 5., above=0.)
|
|
||||||
self.homing_stepper_phases = config.getint(
|
self.homing_stepper_phases = config.getint(
|
||||||
'homing_stepper_phases', None, minval=0)
|
'homing_stepper_phases', None, minval=0)
|
||||||
endstop_accuracy = config.getfloat(
|
endstop_accuracy = config.getfloat(
|
||||||
@@ -81,7 +113,8 @@ class PrinterHomingStepper(PrinterStepper):
|
|||||||
self.homing_endstop_phase = config.getint(
|
self.homing_endstop_phase = config.getint(
|
||||||
'homing_endstop_phase', None, minval=0
|
'homing_endstop_phase', None, minval=0
|
||||||
, maxval=self.homing_stepper_phases-1)
|
, maxval=self.homing_stepper_phases-1)
|
||||||
if self.homing_endstop_phase is not None:
|
if (self.homing_endstop_phase is not None
|
||||||
|
and config.getboolean('homing_endstop_align_zero', False)):
|
||||||
# Adjust the endstop position so 0.0 is always at a full step
|
# Adjust the endstop position so 0.0 is always at a full step
|
||||||
micro_steps = self.homing_stepper_phases // 4
|
micro_steps = self.homing_stepper_phases // 4
|
||||||
phase_offset = (
|
phase_offset = (
|
||||||
@@ -99,32 +132,27 @@ class PrinterHomingStepper(PrinterStepper):
|
|||||||
self.homing_endstop_accuracy = self.homing_stepper_phases//2 - 1
|
self.homing_endstop_accuracy = self.homing_stepper_phases//2 - 1
|
||||||
elif self.homing_endstop_phase is not None:
|
elif self.homing_endstop_phase is not None:
|
||||||
self.homing_endstop_accuracy = int(math.ceil(
|
self.homing_endstop_accuracy = int(math.ceil(
|
||||||
endstop_accuracy * self.inv_step_dist / 2.))
|
endstop_accuracy * .5 / self.step_dist))
|
||||||
else:
|
else:
|
||||||
self.homing_endstop_accuracy = int(math.ceil(
|
self.homing_endstop_accuracy = int(math.ceil(
|
||||||
endstop_accuracy * self.inv_step_dist))
|
endstop_accuracy / self.step_dist))
|
||||||
if self.homing_endstop_accuracy >= self.homing_stepper_phases // 2:
|
if self.homing_endstop_accuracy >= self.homing_stepper_phases // 2:
|
||||||
logging.info("Endstop for %s is not accurate enough for stepper"
|
logging.info("Endstop for %s is not accurate enough for stepper"
|
||||||
" phase adjustment", name)
|
" phase adjustment", name)
|
||||||
self.homing_stepper_phases = None
|
self.homing_stepper_phases = None
|
||||||
if self.mcu_endstop.get_mcu().is_fileoutput():
|
if self.mcu_endstop.get_mcu().is_fileoutput():
|
||||||
self.homing_endstop_accuracy = self.homing_stepper_phases
|
self.homing_endstop_accuracy = self.homing_stepper_phases
|
||||||
def get_homing_speed(self):
|
def get_endstops(self):
|
||||||
# Round the configured homing speed so that it is an even
|
return [(self.mcu_endstop, self.name)]
|
||||||
# number of ticks per step.
|
|
||||||
adjusted_freq = self.mcu_stepper.get_mcu().get_adjusted_freq()
|
|
||||||
dist_ticks = adjusted_freq * self.step_dist
|
|
||||||
ticks_per_step = round(dist_ticks / self.homing_speed)
|
|
||||||
return dist_ticks / ticks_per_step
|
|
||||||
def get_homed_offset(self):
|
def get_homed_offset(self):
|
||||||
if not self.homing_stepper_phases or self.need_motor_enable:
|
if not self.homing_stepper_phases or self.need_motor_enable:
|
||||||
return 0
|
return 0.
|
||||||
pos = self.mcu_stepper.get_mcu_position()
|
pos = self.mcu_stepper.get_mcu_position()
|
||||||
pos %= self.homing_stepper_phases
|
pos %= self.homing_stepper_phases
|
||||||
if self.homing_endstop_phase is None:
|
if self.homing_endstop_phase is None:
|
||||||
logging.info("Setting %s endstop phase to %d", self.name, pos)
|
logging.info("Setting %s endstop phase to %d", self.name, pos)
|
||||||
self.homing_endstop_phase = pos
|
self.homing_endstop_phase = pos
|
||||||
return 0
|
return 0.
|
||||||
delta = (pos - self.homing_endstop_phase) % self.homing_stepper_phases
|
delta = (pos - self.homing_endstop_phase) % self.homing_stepper_phases
|
||||||
if delta >= self.homing_stepper_phases - self.homing_endstop_accuracy:
|
if delta >= self.homing_stepper_phases - self.homing_endstop_accuracy:
|
||||||
delta -= self.homing_stepper_phases
|
delta -= self.homing_stepper_phases
|
||||||
@@ -133,3 +161,48 @@ class PrinterHomingStepper(PrinterStepper):
|
|||||||
"Endstop %s incorrect phase (got %d vs %d)" % (
|
"Endstop %s incorrect phase (got %d vs %d)" % (
|
||||||
self.name, pos, self.homing_endstop_phase))
|
self.name, pos, self.homing_endstop_phase))
|
||||||
return delta * self.step_dist
|
return delta * self.step_dist
|
||||||
|
|
||||||
|
# Wrapper for dual stepper motor support
|
||||||
|
class PrinterMultiStepper(PrinterHomingStepper):
|
||||||
|
def __init__(self, printer, config):
|
||||||
|
PrinterHomingStepper.__init__(self, printer, config)
|
||||||
|
self.endstops = PrinterHomingStepper.get_endstops(self)
|
||||||
|
self.extras = []
|
||||||
|
self.all_step_const = [self.step_const]
|
||||||
|
for i in range(1, 99):
|
||||||
|
if not config.has_section(config.get_name() + str(i)):
|
||||||
|
break
|
||||||
|
extraconfig = config.getsection(config.get_name() + str(i))
|
||||||
|
extra = PrinterStepper(printer, extraconfig)
|
||||||
|
self.extras.append(extra)
|
||||||
|
self.all_step_const.append(extra.step_const)
|
||||||
|
extraendstop = extraconfig.get('endstop_pin', None)
|
||||||
|
if extraendstop is not None:
|
||||||
|
mcu_endstop = pins.setup_pin(printer, 'endstop', extraendstop)
|
||||||
|
mcu_endstop.add_stepper(extra.mcu_stepper)
|
||||||
|
self.endstops.append((mcu_endstop, extra.name))
|
||||||
|
else:
|
||||||
|
self.mcu_endstop.add_stepper(extra.mcu_stepper)
|
||||||
|
self.step_const = self.step_multi_const
|
||||||
|
def step_multi_const(self, print_time, start_pos, dist, start_v, accel):
|
||||||
|
for step_const in self.all_step_const:
|
||||||
|
step_const(print_time, start_pos, dist, start_v, accel)
|
||||||
|
def set_max_jerk(self, max_halt_velocity, max_accel):
|
||||||
|
PrinterHomingStepper.set_max_jerk(self, max_halt_velocity, max_accel)
|
||||||
|
for extra in self.extras:
|
||||||
|
extra.set_max_jerk(max_halt_velocity, max_accel)
|
||||||
|
def set_position(self, pos):
|
||||||
|
PrinterHomingStepper.set_position(self, pos)
|
||||||
|
for extra in self.extras:
|
||||||
|
extra.set_position(pos)
|
||||||
|
def motor_enable(self, print_time, enable=0):
|
||||||
|
PrinterHomingStepper.motor_enable(self, print_time, enable)
|
||||||
|
for extra in self.extras:
|
||||||
|
extra.motor_enable(print_time, enable)
|
||||||
|
def get_endstops(self):
|
||||||
|
return self.endstops
|
||||||
|
|
||||||
|
def LookupMultiHomingStepper(printer, config):
|
||||||
|
if not config.has_section(config.get_name() + '1'):
|
||||||
|
return PrinterHomingStepper(printer, config)
|
||||||
|
return PrinterMultiStepper(printer, config)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Code for coordinating events on the printer toolhead
|
# Code for coordinating events on the printer toolhead
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import math, logging
|
import math, logging
|
||||||
@@ -183,8 +183,8 @@ STALL_TIME = 0.100
|
|||||||
class ToolHead:
|
class ToolHead:
|
||||||
def __init__(self, printer, config):
|
def __init__(self, printer, config):
|
||||||
self.printer = printer
|
self.printer = printer
|
||||||
self.reactor = printer.reactor
|
self.reactor = printer.get_reactor()
|
||||||
self.all_mcus = mcu.get_printer_mcus(printer)
|
self.all_mcus = printer.lookup_module_objects('mcu')
|
||||||
self.mcu = self.all_mcus[0]
|
self.mcu = self.all_mcus[0]
|
||||||
self.max_velocity = config.getfloat('max_velocity', above=0.)
|
self.max_velocity = config.getfloat('max_velocity', above=0.)
|
||||||
self.max_accel = config.getfloat('max_accel', above=0.)
|
self.max_accel = config.getfloat('max_accel', above=0.)
|
||||||
@@ -192,7 +192,7 @@ class ToolHead:
|
|||||||
'max_accel_to_decel', self.max_accel * 0.5
|
'max_accel_to_decel', self.max_accel * 0.5
|
||||||
, above=0., maxval=self.max_accel)
|
, above=0., maxval=self.max_accel)
|
||||||
self.junction_deviation = config.getfloat(
|
self.junction_deviation = config.getfloat(
|
||||||
'junction_deviation', 0.02, above=0.)
|
'junction_deviation', 0.02, minval=0.)
|
||||||
self.move_queue = MoveQueue()
|
self.move_queue = MoveQueue()
|
||||||
self.commanded_pos = [0., 0., 0., 0.]
|
self.commanded_pos = [0., 0., 0., 0.]
|
||||||
# Print time tracking
|
# Print time tracking
|
||||||
@@ -205,10 +205,11 @@ class ToolHead:
|
|||||||
self.move_flush_time = config.getfloat(
|
self.move_flush_time = config.getfloat(
|
||||||
'move_flush_time', 0.050, above=0.)
|
'move_flush_time', 0.050, above=0.)
|
||||||
self.print_time = 0.
|
self.print_time = 0.
|
||||||
|
self.last_print_start_time = 0.
|
||||||
self.need_check_stall = -1.
|
self.need_check_stall = -1.
|
||||||
self.print_stall = 0
|
self.print_stall = 0
|
||||||
self.sync_print_time = True
|
self.sync_print_time = True
|
||||||
self.last_flush_from_idle = False
|
self.idle_flush_print_time = 0.
|
||||||
self.flush_timer = self.reactor.register_timer(self._flush_handler)
|
self.flush_timer = self.reactor.register_timer(self._flush_handler)
|
||||||
self.move_queue.set_flush_time(self.buffer_time_high)
|
self.move_queue.set_flush_time(self.buffer_time_high)
|
||||||
# Motor off tracking
|
# Motor off tracking
|
||||||
@@ -234,19 +235,17 @@ class ToolHead:
|
|||||||
if not self.sync_print_time:
|
if not self.sync_print_time:
|
||||||
return self.print_time
|
return self.print_time
|
||||||
self.sync_print_time = False
|
self.sync_print_time = False
|
||||||
est_print_time = self.mcu.estimated_print_time(self.reactor.monotonic())
|
|
||||||
if self.last_flush_from_idle and self.print_time > est_print_time:
|
|
||||||
self.print_stall += 1
|
|
||||||
self.last_flush_from_idle = False
|
|
||||||
self.need_motor_off = True
|
self.need_motor_off = True
|
||||||
self.print_time = max(
|
est_print_time = self.mcu.estimated_print_time(self.reactor.monotonic())
|
||||||
self.print_time, est_print_time + self.buffer_time_start)
|
if est_print_time + self.buffer_time_start > self.print_time:
|
||||||
|
self.print_time = est_print_time + self.buffer_time_start
|
||||||
|
self.last_print_start_time = self.print_time
|
||||||
self.reactor.update_timer(self.flush_timer, self.reactor.NOW)
|
self.reactor.update_timer(self.flush_timer, self.reactor.NOW)
|
||||||
return self.print_time
|
return self.print_time
|
||||||
def _flush_lookahead(self, must_sync=False):
|
def _flush_lookahead(self, must_sync=False):
|
||||||
sync_print_time = self.sync_print_time
|
sync_print_time = self.sync_print_time
|
||||||
self.move_queue.flush()
|
self.move_queue.flush()
|
||||||
self.last_flush_from_idle = False
|
self.idle_flush_print_time = 0.
|
||||||
if sync_print_time or must_sync:
|
if sync_print_time or must_sync:
|
||||||
self.sync_print_time = True
|
self.sync_print_time = True
|
||||||
self.move_queue.set_flush_time(self.buffer_time_high)
|
self.move_queue.set_flush_time(self.buffer_time_high)
|
||||||
@@ -265,6 +264,11 @@ class ToolHead:
|
|||||||
eventtime = self.reactor.monotonic()
|
eventtime = self.reactor.monotonic()
|
||||||
if self.sync_print_time:
|
if self.sync_print_time:
|
||||||
# Building initial queue - make sure to flush on idle input
|
# Building initial queue - make sure to flush on idle input
|
||||||
|
if self.idle_flush_print_time:
|
||||||
|
est_print_time = self.mcu.estimated_print_time(eventtime)
|
||||||
|
if est_print_time < self.idle_flush_print_time:
|
||||||
|
self.print_stall += 1
|
||||||
|
self.idle_flush_print_time = 0.
|
||||||
self.reactor.update_timer(self.flush_timer, eventtime + 0.100)
|
self.reactor.update_timer(self.flush_timer, eventtime + 0.100)
|
||||||
return
|
return
|
||||||
# Check if there are lots of queued moves and stall if so
|
# Check if there are lots of queued moves and stall if so
|
||||||
@@ -289,7 +293,7 @@ class ToolHead:
|
|||||||
# Under ran low buffer mark - flush lookahead queue
|
# Under ran low buffer mark - flush lookahead queue
|
||||||
self._flush_lookahead(must_sync=True)
|
self._flush_lookahead(must_sync=True)
|
||||||
if print_time != self.print_time:
|
if print_time != self.print_time:
|
||||||
self.last_flush_from_idle = True
|
self.idle_flush_print_time = self.print_time
|
||||||
except:
|
except:
|
||||||
logging.exception("Exception in flush_handler")
|
logging.exception("Exception in flush_handler")
|
||||||
self.printer.invoke_shutdown("Exception in flush_handler")
|
self.printer.invoke_shutdown("Exception in flush_handler")
|
||||||
@@ -310,10 +314,10 @@ class ToolHead:
|
|||||||
# Movement commands
|
# Movement commands
|
||||||
def get_position(self):
|
def get_position(self):
|
||||||
return list(self.commanded_pos)
|
return list(self.commanded_pos)
|
||||||
def set_position(self, newpos):
|
def set_position(self, newpos, homing_axes=()):
|
||||||
self._flush_lookahead()
|
self._flush_lookahead()
|
||||||
self.commanded_pos[:] = newpos
|
self.commanded_pos[:] = newpos
|
||||||
self.kin.set_position(newpos)
|
self.kin.set_position(newpos, homing_axes)
|
||||||
def move(self, newpos, speed):
|
def move(self, newpos, speed):
|
||||||
speed = min(speed, self.max_velocity)
|
speed = min(speed, self.max_velocity)
|
||||||
move = Move(self, self.commanded_pos, newpos, speed)
|
move = Move(self, self.commanded_pos, newpos, speed)
|
||||||
@@ -327,11 +331,10 @@ class ToolHead:
|
|||||||
self.move_queue.add_move(move)
|
self.move_queue.add_move(move)
|
||||||
if self.print_time > self.need_check_stall:
|
if self.print_time > self.need_check_stall:
|
||||||
self._check_stall()
|
self._check_stall()
|
||||||
def home(self, homing_state):
|
def dwell(self, delay, check_stall=True):
|
||||||
self.kin.home(homing_state)
|
|
||||||
def dwell(self, delay):
|
|
||||||
self.get_last_move_time()
|
self.get_last_move_time()
|
||||||
self.update_move_time(delay)
|
self.update_move_time(delay)
|
||||||
|
if check_stall:
|
||||||
self._check_stall()
|
self._check_stall()
|
||||||
def motor_off(self):
|
def motor_off(self):
|
||||||
self.dwell(STALL_TIME)
|
self.dwell(STALL_TIME)
|
||||||
@@ -349,9 +352,6 @@ class ToolHead:
|
|||||||
while (not self.sync_print_time
|
while (not self.sync_print_time
|
||||||
or self.print_time >= self.mcu.estimated_print_time(eventtime)):
|
or self.print_time >= self.mcu.estimated_print_time(eventtime)):
|
||||||
eventtime = self.reactor.pause(eventtime + 0.100)
|
eventtime = self.reactor.pause(eventtime + 0.100)
|
||||||
def query_endstops(self, query_flags=""):
|
|
||||||
last_move_time = self.get_last_move_time()
|
|
||||||
return self.kin.query_endstops(last_move_time, query_flags)
|
|
||||||
def set_extruder(self, extruder):
|
def set_extruder(self, extruder):
|
||||||
last_move_time = self.get_last_move_time()
|
last_move_time = self.get_last_move_time()
|
||||||
self.extruder.set_active(last_move_time, False)
|
self.extruder.set_active(last_move_time, False)
|
||||||
@@ -360,23 +360,32 @@ class ToolHead:
|
|||||||
self.move_queue.set_extruder(extruder)
|
self.move_queue.set_extruder(extruder)
|
||||||
self.commanded_pos[3] = extrude_pos
|
self.commanded_pos[3] = extrude_pos
|
||||||
# Misc commands
|
# Misc commands
|
||||||
def check_active(self, eventtime):
|
def stats(self, eventtime):
|
||||||
for m in self.all_mcus:
|
for m in self.all_mcus:
|
||||||
m.check_active(self.print_time, eventtime)
|
m.check_active(self.print_time, eventtime)
|
||||||
if not self.sync_print_time:
|
buffer_time = self.print_time - self.mcu.estimated_print_time(eventtime)
|
||||||
return True
|
is_active = buffer_time > -60. or not self.sync_print_time
|
||||||
return self.print_time + 60. > self.mcu.estimated_print_time(eventtime)
|
return is_active, "print_time=%.3f buffer_time=%.3f print_stall=%d" % (
|
||||||
def stats(self, eventtime):
|
self.print_time, max(buffer_time, 0.), self.print_stall)
|
||||||
est_print_time = self.mcu.estimated_print_time(eventtime)
|
def get_status(self, eventtime):
|
||||||
buffer_time = max(0., self.print_time - est_print_time)
|
buffer_time = self.print_time - self.mcu.estimated_print_time(eventtime)
|
||||||
return "print_time=%.3f buffer_time=%.3f print_stall=%d" % (
|
if buffer_time > -1. or not self.sync_print_time:
|
||||||
self.print_time, buffer_time, self.print_stall)
|
status = "Printing"
|
||||||
def do_shutdown(self):
|
elif self.need_motor_off:
|
||||||
|
status = "Ready"
|
||||||
|
else:
|
||||||
|
status = "Idle"
|
||||||
|
printing_time = self.print_time - self.last_print_start_time
|
||||||
|
return {'status': status, 'printing_time': printing_time}
|
||||||
|
def printer_state(self, state):
|
||||||
|
if state == 'shutdown':
|
||||||
try:
|
try:
|
||||||
self.move_queue.reset()
|
self.move_queue.reset()
|
||||||
self.reset_print_time()
|
self.reset_print_time()
|
||||||
except:
|
except:
|
||||||
logging.exception("Exception in do_shutdown")
|
logging.exception("Exception in toolhead shutdown")
|
||||||
|
def get_kinematics(self):
|
||||||
|
return self.kin
|
||||||
def get_max_velocity(self):
|
def get_max_velocity(self):
|
||||||
return self.max_velocity, self.max_accel
|
return self.max_velocity, self.max_accel
|
||||||
def get_max_axis_halt(self):
|
def get_max_axis_halt(self):
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ def create_pty(ptyname):
|
|||||||
except os.error:
|
except os.error:
|
||||||
pass
|
pass
|
||||||
os.symlink(os.ttyname(sfd), ptyname)
|
os.symlink(os.ttyname(sfd), ptyname)
|
||||||
fcntl.fcntl(mfd, fcntl.F_SETFL
|
set_nonblock(mfd)
|
||||||
, fcntl.fcntl(mfd, fcntl.F_GETFL) | os.O_NONBLOCK)
|
|
||||||
old = termios.tcgetattr(mfd)
|
old = termios.tcgetattr(mfd)
|
||||||
old[3] = old[3] & ~termios.ECHO
|
old[3] = old[3] & ~termios.ECHO
|
||||||
termios.tcsetattr(mfd, termios.TCSADRAIN, old)
|
termios.tcsetattr(mfd, termios.TCSADRAIN, old)
|
||||||
@@ -58,7 +57,7 @@ def get_git_version():
|
|||||||
if not os.path.exists(gitdir):
|
if not os.path.exists(gitdir):
|
||||||
logging.debug("No '.git' file/directory found")
|
logging.debug("No '.git' file/directory found")
|
||||||
return "?"
|
return "?"
|
||||||
prog = "git -C %s describe --tags --long --dirty" % (gitdir,)
|
prog = "git -C %s describe --always --tags --long --dirty" % (gitdir,)
|
||||||
try:
|
try:
|
||||||
process = subprocess.Popen(shlex.split(prog), stdout=subprocess.PIPE)
|
process = subprocess.Popen(shlex.split(prog), stdout=subprocess.PIPE)
|
||||||
output = process.communicate()[0]
|
output = process.communicate()[0]
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
#!/usr/bin/env python2
|
#!/usr/bin/env python2
|
||||||
# Script to interact with simulavr by simulating a serial port.
|
# Script to interact with simulavr by simulating a serial port.
|
||||||
#
|
#
|
||||||
# Copyright (C) 2015 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2015-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
import sys, optparse, time, os, pty, fcntl, termios, errno
|
import sys, optparse, time, os, pty, fcntl, termios, errno
|
||||||
import pysimulavr
|
import pysimulavr
|
||||||
|
|
||||||
@@ -107,23 +106,22 @@ class Pacing(pysimulavr.PySimulationMember):
|
|||||||
pysimulavr.PySimulationMember.__init__(self)
|
pysimulavr.PySimulationMember.__init__(self)
|
||||||
self.sc = pysimulavr.SystemClock.Instance()
|
self.sc = pysimulavr.SystemClock.Instance()
|
||||||
self.pacing_rate = 1. / (rate * SIMULAVR_FREQ)
|
self.pacing_rate = 1. / (rate * SIMULAVR_FREQ)
|
||||||
self.rel_time = self.next_rel_time = time.time()
|
self.next_check_clock = 0
|
||||||
self.rel_clock = self.next_rel_clock = self.sc.GetCurrentTime()
|
self.rel_time = time.time()
|
||||||
|
self.best_offset = 0.
|
||||||
self.delay = SIMULAVR_FREQ / 10000
|
self.delay = SIMULAVR_FREQ / 10000
|
||||||
self.sc.Add(self)
|
self.sc.Add(self)
|
||||||
def DoStep(self, trueHwStep):
|
def DoStep(self, trueHwStep):
|
||||||
curtime = time.time()
|
curtime = time.time()
|
||||||
clock = self.sc.GetCurrentTime()
|
clock = self.sc.GetCurrentTime()
|
||||||
clock_diff = clock - self.rel_clock
|
offset = clock * self.pacing_rate - (curtime - self.rel_time)
|
||||||
time_diff = curtime - self.rel_time
|
self.best_offset = max(self.best_offset, offset)
|
||||||
offset = clock_diff * self.pacing_rate - time_diff
|
|
||||||
if offset > 0.000050:
|
if offset > 0.000050:
|
||||||
time.sleep(offset)
|
time.sleep(offset - 0.000040)
|
||||||
if clock_diff > self.delay * 20:
|
if clock >= self.next_check_clock:
|
||||||
self.rel_clock = self.next_rel_clock
|
self.rel_time -= min(self.best_offset, 0.)
|
||||||
self.rel_time = self.next_rel_time
|
self.next_check_clock = clock + self.delay * 500
|
||||||
self.next_rel_clock = clock
|
self.best_offset = -999999999.
|
||||||
self.next_rel_time = curtime
|
|
||||||
return self.delay
|
return self.delay
|
||||||
|
|
||||||
# Forward data from a terminal device to the serial port pins
|
# Forward data from a terminal device to the serial port pins
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import sys, os, subprocess, optparse, logging, shlex, socket, time, traceback
|
||||||
import sys, os, subprocess, optparse, logging, shlex, socket, time
|
|
||||||
import json, zlib
|
import json, zlib
|
||||||
sys.path.append('./klippy')
|
sys.path.append('./klippy')
|
||||||
import msgproto
|
import msgproto
|
||||||
@@ -182,7 +181,7 @@ const uint8_t command_index_size PROGMEM = ARRAY_SIZE(command_index);
|
|||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def build_identify(cmd_by_id, msg_to_id, responses, static_strings
|
def build_identify(cmd_by_id, msg_to_id, responses, static_strings
|
||||||
, constants, version):
|
, constants, version, toolstr):
|
||||||
#commands, messages, static_strings
|
#commands, messages, static_strings
|
||||||
messages = dict((msgid, msg) for msg, msgid in msg_to_id.items())
|
messages = dict((msgid, msg) for msg, msgid in msg_to_id.items())
|
||||||
data = {}
|
data = {}
|
||||||
@@ -193,6 +192,7 @@ def build_identify(cmd_by_id, msg_to_id, responses, static_strings
|
|||||||
for i in range(len(static_strings)) }
|
for i in range(len(static_strings)) }
|
||||||
data['config'] = constants
|
data['config'] = constants
|
||||||
data['version'] = version
|
data['version'] = version
|
||||||
|
data['build_versions'] = toolstr
|
||||||
|
|
||||||
# Format compressed info into C code
|
# Format compressed info into C code
|
||||||
data = json.dumps(data)
|
data = json.dumps(data)
|
||||||
@@ -203,6 +203,9 @@ def build_identify(cmd_by_id, msg_to_id, responses, static_strings
|
|||||||
out.append('\n ')
|
out.append('\n ')
|
||||||
out.append(" 0x%02x," % (ord(zdata[i]),))
|
out.append(" 0x%02x," % (ord(zdata[i]),))
|
||||||
fmt = """
|
fmt = """
|
||||||
|
// version: %s
|
||||||
|
// build_versions: %s
|
||||||
|
|
||||||
const uint8_t command_identify_data[] PROGMEM = {%s
|
const uint8_t command_identify_data[] PROGMEM = {%s
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -210,7 +213,7 @@ const uint8_t command_identify_data[] PROGMEM = {%s
|
|||||||
const uint32_t command_identify_size PROGMEM
|
const uint32_t command_identify_size PROGMEM
|
||||||
= ARRAY_SIZE(command_identify_data);
|
= ARRAY_SIZE(command_identify_data);
|
||||||
"""
|
"""
|
||||||
return data, fmt % (''.join(out), len(zdata), len(data))
|
return data, fmt % (version, toolstr, ''.join(out), len(zdata), len(data))
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
@@ -241,7 +244,7 @@ def git_version():
|
|||||||
if not os.path.exists('.git'):
|
if not os.path.exists('.git'):
|
||||||
logging.debug("No '.git' file/directory found")
|
logging.debug("No '.git' file/directory found")
|
||||||
return ""
|
return ""
|
||||||
ver = check_output("git describe --tags --long --dirty").strip()
|
ver = check_output("git describe --always --tags --long --dirty").strip()
|
||||||
logging.debug("Got git version: %s" % (repr(ver),))
|
logging.debug("Got git version: %s" % (repr(ver),))
|
||||||
return ver
|
return ver
|
||||||
|
|
||||||
@@ -254,6 +257,36 @@ def build_version(extra):
|
|||||||
version = "%s-%s-%s%s" % (version, btime, hostname, extra)
|
version = "%s-%s-%s%s" % (version, btime, hostname, extra)
|
||||||
return version
|
return version
|
||||||
|
|
||||||
|
# Run "tool --version" for each specified tool and extract versions
|
||||||
|
def tool_versions(tools):
|
||||||
|
tools = [t.strip() for t in tools.split(';')]
|
||||||
|
versions = ['', '']
|
||||||
|
success = 0
|
||||||
|
for tool in tools:
|
||||||
|
# Extract first line from "tool --version" output
|
||||||
|
verstr = check_output("%s --version" % (tool,)).split('\n')[0]
|
||||||
|
# Check if this tool looks like a binutils program
|
||||||
|
isbinutils = 0
|
||||||
|
if verstr.startswith('GNU '):
|
||||||
|
isbinutils = 1
|
||||||
|
verstr = verstr[4:]
|
||||||
|
# Extract version information and exclude program name
|
||||||
|
if ' ' not in verstr:
|
||||||
|
continue
|
||||||
|
prog, ver = verstr.split(' ', 1)
|
||||||
|
if not prog or not ver:
|
||||||
|
continue
|
||||||
|
# Check for any version conflicts
|
||||||
|
if versions[isbinutils] and versions[isbinutils] != ver:
|
||||||
|
logging.debug("Mixed version %s vs %s" % (
|
||||||
|
repr(versions[isbinutils]), repr(ver)))
|
||||||
|
versions[isbinutils] = "mixed"
|
||||||
|
continue
|
||||||
|
versions[isbinutils] = ver
|
||||||
|
success += 1
|
||||||
|
cleanbuild = versions[0] and versions[1] and success == len(tools)
|
||||||
|
return cleanbuild, "gcc: %s binutils: %s" % (versions[0], versions[1])
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Main code
|
# Main code
|
||||||
@@ -266,6 +299,8 @@ def main():
|
|||||||
help="extra version string to append to version")
|
help="extra version string to append to version")
|
||||||
opts.add_option("-d", dest="write_dictionary",
|
opts.add_option("-d", dest="write_dictionary",
|
||||||
help="file to write mcu protocol dictionary")
|
help="file to write mcu protocol dictionary")
|
||||||
|
opts.add_option("-t", "--tools", dest="tools", default="",
|
||||||
|
help="list of build programs to extract version from")
|
||||||
opts.add_option("-v", action="store_true", dest="verbose",
|
opts.add_option("-v", action="store_true", dest="verbose",
|
||||||
help="enable debug messages")
|
help="enable debug messages")
|
||||||
|
|
||||||
@@ -349,12 +384,14 @@ def main():
|
|||||||
cmdcode = build_commands(cmd_by_id, messages_by_name, all_param_types)
|
cmdcode = build_commands(cmd_by_id, messages_by_name, all_param_types)
|
||||||
paramcode = build_param_types(all_param_types)
|
paramcode = build_param_types(all_param_types)
|
||||||
# Create identify information
|
# Create identify information
|
||||||
|
cleanbuild, toolstr = tool_versions(options.tools)
|
||||||
version = build_version(options.extra)
|
version = build_version(options.extra)
|
||||||
sys.stdout.write("Version: %s\n" % (version,))
|
sys.stdout.write("Version: %s\n" % (version,))
|
||||||
responses = [msg_to_id[msg] for msgname, msg in messages_by_name.items()
|
responses = [msg_to_id[msg] for msgname, msg in messages_by_name.items()
|
||||||
if msgname not in commands]
|
if msgname not in commands]
|
||||||
datadict, icode = build_identify(cmd_by_id, msg_to_id, responses
|
datadict, icode = build_identify(
|
||||||
, static_strings, constants, version)
|
cmd_by_id, msg_to_id, responses,
|
||||||
|
static_strings, constants, version, toolstr)
|
||||||
# Write output
|
# Write output
|
||||||
f = open(outcfile, 'wb')
|
f = open(outcfile, 'wb')
|
||||||
f.write(FILEHEADER + call_lists_code + static_strings_code
|
f.write(FILEHEADER + call_lists_code + static_strings_code
|
||||||
|
|||||||
@@ -1,18 +1,28 @@
|
|||||||
#!/usr/bin/env python2
|
#!/usr/bin/env python2
|
||||||
# Script to parse a logging file, extract the stats, and graph them
|
# Script to parse a logging file, extract the stats, and graph them
|
||||||
#
|
#
|
||||||
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import optparse, datetime
|
import optparse, datetime
|
||||||
import matplotlib.pyplot as plt, matplotlib.dates as mdates
|
import matplotlib
|
||||||
|
matplotlib.use('Agg')
|
||||||
|
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
|
||||||
|
import matplotlib.ticker
|
||||||
|
|
||||||
MAXBANDWIDTH=25000.
|
MAXBANDWIDTH=25000.
|
||||||
MAXBUFFER=2.
|
MAXBUFFER=2.
|
||||||
STATS_INTERVAL=5.
|
STATS_INTERVAL=5.
|
||||||
TASK_MAX=0.0025
|
TASK_MAX=0.0025
|
||||||
|
|
||||||
def parse_log(logname):
|
APPLY_PREFIX = ['mcu_awake', 'mcu_task_avg', 'mcu_task_stddev', 'bytes_write',
|
||||||
|
'bytes_read', 'bytes_retransmit', 'freq', 'adj']
|
||||||
|
|
||||||
|
def parse_log(logname, mcu):
|
||||||
|
if mcu is None:
|
||||||
|
mcu = "mcu"
|
||||||
|
mcu_prefix = mcu + ":"
|
||||||
|
apply_prefix = { p: 1 for p in APPLY_PREFIX }
|
||||||
f = open(logname, 'rb')
|
f = open(logname, 'rb')
|
||||||
out = []
|
out = []
|
||||||
for line in f:
|
for line in f:
|
||||||
@@ -21,7 +31,18 @@ def parse_log(logname):
|
|||||||
#if parts and parts[0] == 'INFO:root:shutdown:':
|
#if parts and parts[0] == 'INFO:root:shutdown:':
|
||||||
# break
|
# break
|
||||||
continue
|
continue
|
||||||
keyparts = dict(p.split('=', 1) for p in parts[2:])
|
prefix = ""
|
||||||
|
keyparts = {}
|
||||||
|
for p in parts[2:]:
|
||||||
|
if p.endswith(':'):
|
||||||
|
prefix = p
|
||||||
|
if prefix == mcu_prefix:
|
||||||
|
prefix = ''
|
||||||
|
continue
|
||||||
|
name, val = p.split('=', 1)
|
||||||
|
if name in apply_prefix:
|
||||||
|
name = prefix + name
|
||||||
|
keyparts[name] = val
|
||||||
if keyparts.get('bytes_write', '0') == '0':
|
if keyparts.get('bytes_write', '0') == '0':
|
||||||
continue
|
continue
|
||||||
keyparts['#sampletime'] = float(parts[1][:-1])
|
keyparts['#sampletime'] = float(parts[1][:-1])
|
||||||
@@ -30,27 +51,34 @@ def parse_log(logname):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
def find_print_restarts(data):
|
def find_print_restarts(data):
|
||||||
last_print_time = 0.
|
runoff_samples = {}
|
||||||
print_resets = []
|
last_runoff_start = last_buffer_time = last_sampletime = 0.
|
||||||
for d in data:
|
last_print_stall = 0
|
||||||
print_time = float(d.get('print_time', last_print_time))
|
for d in reversed(data):
|
||||||
if print_time < last_print_time:
|
# Check for buffer runoff
|
||||||
print_resets.append(d['#sampletime'])
|
sampletime = d['#sampletime']
|
||||||
last_print_time = 0.
|
buffer_time = float(d.get('buffer_time', 0.))
|
||||||
|
if (last_runoff_start and last_sampletime - sampletime < 5
|
||||||
|
and buffer_time > last_buffer_time):
|
||||||
|
runoff_samples[last_runoff_start][1].append(sampletime)
|
||||||
|
elif buffer_time < 1.:
|
||||||
|
last_runoff_start = sampletime
|
||||||
|
runoff_samples[last_runoff_start] = [False, [sampletime]]
|
||||||
else:
|
else:
|
||||||
last_print_time = print_time
|
last_runoff_start = 0.
|
||||||
sample_resets = {}
|
last_buffer_time = buffer_time
|
||||||
for d in data:
|
last_sampletime = sampletime
|
||||||
st = d['#sampletime']
|
# Check for print stall
|
||||||
while print_resets and st > print_resets[0]:
|
print_stall = int(d['print_stall'])
|
||||||
print_resets.pop(0)
|
if print_stall < last_print_stall:
|
||||||
if not print_resets:
|
if last_runoff_start:
|
||||||
break
|
runoff_samples[last_runoff_start][0] = True
|
||||||
if st + 2. * MAXBUFFER > print_resets[0]:
|
last_print_stall = print_stall
|
||||||
sample_resets[st] = 1
|
sample_resets = {sampletime: 1 for stall, samples in runoff_samples.values()
|
||||||
|
for sampletime in samples if not stall}
|
||||||
return sample_resets
|
return sample_resets
|
||||||
|
|
||||||
def plot_mcu(data, maxbw, outname, graph_awake=False):
|
def plot_mcu(data, maxbw, outname):
|
||||||
# Generate data for plot
|
# Generate data for plot
|
||||||
basetime = lasttime = data[0]['#sampletime']
|
basetime = lasttime = data[0]['#sampletime']
|
||||||
lastbw = float(data[0]['bytes_write']) + float(data[0]['bytes_retransmit'])
|
lastbw = float(data[0]['bytes_write']) + float(data[0]['bytes_retransmit'])
|
||||||
@@ -74,7 +102,7 @@ def plot_mcu(data, maxbw, outname, graph_awake=False):
|
|||||||
load = 0.
|
load = 0.
|
||||||
pt = float(d['print_time'])
|
pt = float(d['print_time'])
|
||||||
hb = float(d['buffer_time'])
|
hb = float(d['buffer_time'])
|
||||||
if pt <= 2. * MAXBUFFER or hb >= MAXBUFFER or st in sample_resets:
|
if hb >= MAXBUFFER or st in sample_resets:
|
||||||
hb = 0.
|
hb = 0.
|
||||||
else:
|
else:
|
||||||
hb = 100. * (MAXBUFFER - hb) / MAXBUFFER
|
hb = 100. * (MAXBUFFER - hb) / MAXBUFFER
|
||||||
@@ -87,34 +115,77 @@ def plot_mcu(data, maxbw, outname, graph_awake=False):
|
|||||||
lastbw = bw
|
lastbw = bw
|
||||||
|
|
||||||
# Build plot
|
# Build plot
|
||||||
fig, ax1 = plt.subplots()
|
fig, ax1 = matplotlib.pyplot.subplots()
|
||||||
ax1.set_title("MCU bandwidth and load utilization")
|
ax1.set_title("MCU bandwidth and load utilization")
|
||||||
ax1.set_xlabel('Time')
|
ax1.set_xlabel('Time')
|
||||||
ax1.set_ylabel('Usage (%)')
|
ax1.set_ylabel('Usage (%)')
|
||||||
if graph_awake:
|
ax1.plot_date(times, bwdeltas, 'g', label='Bandwidth', alpha=0.8)
|
||||||
ax1.plot_date(times, awake, 'b', label='Awake time')
|
ax1.plot_date(times, loads, 'r', label='MCU load', alpha=0.8)
|
||||||
ax1.plot_date(times, bwdeltas, 'g', label='Bandwidth')
|
ax1.plot_date(times, hostbuffers, 'c', label='Host buffer', alpha=0.8)
|
||||||
ax1.plot_date(times, loads, 'r', label='MCU load')
|
ax1.plot_date(times, awake, 'y', label='Awake time', alpha=0.6)
|
||||||
ax1.plot_date(times, hostbuffers, 'c', label='Host buffer')
|
fontP = matplotlib.font_manager.FontProperties()
|
||||||
ax1.legend(loc='best')
|
fontP.set_size('x-small')
|
||||||
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
|
ax1.legend(loc='best', prop=fontP)
|
||||||
#plt.gcf().autofmt_xdate()
|
ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M'))
|
||||||
ax1.grid(True)
|
ax1.grid(True)
|
||||||
plt.savefig(outname)
|
fig.set_size_inches(8, 6)
|
||||||
|
fig.savefig(outname)
|
||||||
|
|
||||||
|
def plot_frequency(data, outname, mcu):
|
||||||
|
all_keys = {}
|
||||||
|
for d in data:
|
||||||
|
all_keys.update(d)
|
||||||
|
one_mcu = mcu is not None
|
||||||
|
graph_keys = { key: ([], []) for key in all_keys
|
||||||
|
if (key in ("freq", "adj") or (not one_mcu and (
|
||||||
|
key.endswith(":freq") or key.endswith(":adj")))) }
|
||||||
|
basetime = lasttime = data[0]['#sampletime']
|
||||||
|
for d in data:
|
||||||
|
st = datetime.datetime.utcfromtimestamp(d['#sampletime'])
|
||||||
|
for key, (times, values) in graph_keys.items():
|
||||||
|
val = d.get(key)
|
||||||
|
if val not in (None, '0', '1'):
|
||||||
|
times.append(st)
|
||||||
|
values.append(float(val))
|
||||||
|
|
||||||
|
# Build plot
|
||||||
|
fig, ax1 = matplotlib.pyplot.subplots()
|
||||||
|
if one_mcu:
|
||||||
|
ax1.set_title("MCU '%s' frequency" % (mcu,))
|
||||||
|
else:
|
||||||
|
ax1.set_title("MCU frequency")
|
||||||
|
ax1.set_xlabel('Time')
|
||||||
|
ax1.set_ylabel('Frequency')
|
||||||
|
for key in sorted(graph_keys):
|
||||||
|
times, values = graph_keys[key]
|
||||||
|
ax1.plot_date(times, values, '.', label=key)
|
||||||
|
fontP = matplotlib.font_manager.FontProperties()
|
||||||
|
fontP.set_size('x-small')
|
||||||
|
ax1.legend(loc='best', prop=fontP)
|
||||||
|
ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M'))
|
||||||
|
ax1.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter('%d'))
|
||||||
|
ax1.grid(True)
|
||||||
|
fig.set_size_inches(8, 6)
|
||||||
|
fig.savefig(outname)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
usage = "%prog [options] <logfile> <outname>"
|
usage = "%prog [options] <logfile> <outname>"
|
||||||
opts = optparse.OptionParser(usage)
|
opts = optparse.OptionParser(usage)
|
||||||
opts.add_option("-a", "--awake", action="store_true"
|
opts.add_option("-f", "--frequency", action="store_true",
|
||||||
, help="graph mcu awake time")
|
help="graph mcu frequency")
|
||||||
|
opts.add_option("-m", "--mcu", type="string", dest="mcu", default=None,
|
||||||
|
help="limit stats to the given mcu")
|
||||||
options, args = opts.parse_args()
|
options, args = opts.parse_args()
|
||||||
if len(args) != 2:
|
if len(args) != 2:
|
||||||
opts.error("Incorrect number of arguments")
|
opts.error("Incorrect number of arguments")
|
||||||
logname, outname = args
|
logname, outname = args
|
||||||
data = parse_log(logname)
|
data = parse_log(logname, options.mcu)
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
plot_mcu(data, MAXBANDWIDTH, outname, graph_awake=options.awake)
|
if options.frequency:
|
||||||
|
plot_frequency(data, outname, options.mcu)
|
||||||
|
return
|
||||||
|
plot_mcu(data, MAXBANDWIDTH, outname)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
294
scripts/logextract.py
Executable file
294
scripts/logextract.py
Executable file
@@ -0,0 +1,294 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
# Script to extract config and shutdown information file a klippy.log file
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
#
|
||||||
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import sys, re, collections
|
||||||
|
|
||||||
|
def format_comment(line_num, line):
|
||||||
|
return "# %6d: %s" % (line_num, line)
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Config file extraction
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
class GatherConfig:
|
||||||
|
def __init__(self, configs, line_num, recent_lines, logname):
|
||||||
|
self.configs = configs
|
||||||
|
self.line_num = line_num
|
||||||
|
self.config_num = len(configs) + 1
|
||||||
|
self.filename = "%s.config%04d.cfg" % (logname, self.config_num)
|
||||||
|
self.config_lines = []
|
||||||
|
self.comments = []
|
||||||
|
def add_line(self, line_num, line):
|
||||||
|
if line != '=======================':
|
||||||
|
self.config_lines.append(line)
|
||||||
|
return True
|
||||||
|
self.finalize()
|
||||||
|
return False
|
||||||
|
def finalize(self):
|
||||||
|
lines = tuple(self.config_lines)
|
||||||
|
ch = self.configs.get(lines)
|
||||||
|
if ch is None:
|
||||||
|
self.configs[lines] = ch = self
|
||||||
|
else:
|
||||||
|
ch.comments.extend(self.comments)
|
||||||
|
ch.comments.append(format_comment(self.line_num, "config file"))
|
||||||
|
def add_comment(self, comment):
|
||||||
|
if comment is not None:
|
||||||
|
self.comments.append(comment)
|
||||||
|
def write_file(self):
|
||||||
|
f = open(self.filename, 'wb')
|
||||||
|
f.write('\n'.join(self.comments + self.config_lines))
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Shutdown extraction
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
stats_seq_s = r" send_seq=(?P<sseq>[0-9]+) receive_seq=(?P<rseq>[0-9]+) "
|
||||||
|
count_s = r"(?P<count>[0-9]+)"
|
||||||
|
time_s = r"(?P<time>[0-9]+[.][0-9]+)"
|
||||||
|
esttime_s = r"(?P<esttime>[0-9]+[.][0-9]+)"
|
||||||
|
shortseq_s = r"(?P<shortseq>[0-9a-f])"
|
||||||
|
stats_r = re.compile(r"^Stats " + time_s + ": ")
|
||||||
|
serial_dump_r = re.compile(r"^Dumping serial stats: .*" + stats_seq_s)
|
||||||
|
send_dump_r = re.compile(r"^Dumping send queue " + count_s + " messages$")
|
||||||
|
sent_r = re.compile(r"^Sent " + count_s + " " + esttime_s + " " + time_s
|
||||||
|
+ " [0-9]+: seq: 1" + shortseq_s + ",")
|
||||||
|
receive_dump_r = re.compile(r"^Dumping receive queue " + count_s + " messages$")
|
||||||
|
receive_r = re.compile(r"^Receive: " + count_s + " " + time_s + " " + esttime_s
|
||||||
|
+ " [0-9]+: seq: 1" + shortseq_s + ",")
|
||||||
|
gcode_r = re.compile(r"^Read " + time_s + ": ['\"]")
|
||||||
|
clock_r = re.compile(r"^clocksync state: .* clock_est=\((?P<st>[^ ]+)"
|
||||||
|
+ r" (?P<sc>[0-9]+) (?P<f>[^ ]+)\)")
|
||||||
|
repl_seq_r = re.compile(r": seq: 1" + shortseq_s)
|
||||||
|
repl_clock_r = re.compile(r"clock=(?P<clock>[0-9]+) ")
|
||||||
|
mcu_r = re.compile(r"MCU '(?P<mcu>[^']+)' (is_)?shutdown: (?P<reason>.*)$")
|
||||||
|
|
||||||
|
def add_high_bits(val, ref, mask):
|
||||||
|
half = (mask + 1) // 2
|
||||||
|
return ref + ((val - (ref & mask) + half) & mask) - half
|
||||||
|
|
||||||
|
class GatherShutdown:
|
||||||
|
class mcu_info:
|
||||||
|
def __init__(self):
|
||||||
|
self.sent_stream = []
|
||||||
|
self.receive_stream = []
|
||||||
|
self.sent_stream = []
|
||||||
|
self.receive_stream = []
|
||||||
|
self.sent_time_to_seq = {}
|
||||||
|
self.sent_seq_to_time = {}
|
||||||
|
self.receive_seq_to_time = {}
|
||||||
|
self.clock_est = 0., 0., 1.
|
||||||
|
self.shutdown_seq = None
|
||||||
|
self.send_count = self.receive_count = 0
|
||||||
|
def __init__(self, configs, line_num, recent_lines, logname):
|
||||||
|
self.shutdown_line_num = line_num
|
||||||
|
self.filename = "%s.shutdown%05d" % (logname, line_num)
|
||||||
|
self.comments = []
|
||||||
|
if configs:
|
||||||
|
configs_by_id = {c.config_num: c for c in configs.values()}
|
||||||
|
config = configs_by_id[max(configs_by_id.keys())]
|
||||||
|
config.add_comment(format_comment(line_num, recent_lines[-1][1]))
|
||||||
|
self.comments.append("# config %s" % (config.filename,))
|
||||||
|
self.stats_stream = []
|
||||||
|
self.gcode_stream = []
|
||||||
|
self.mcus = {}
|
||||||
|
self.mcu = None
|
||||||
|
self.first_stat_time = self.last_stat_time = None
|
||||||
|
for line_num, line in recent_lines:
|
||||||
|
self.parse_line(line_num, line)
|
||||||
|
self.first_stat_time = self.last_stat_time
|
||||||
|
def add_comment(self, comment):
|
||||||
|
if comment is not None:
|
||||||
|
self.comments.append(comment)
|
||||||
|
def check_stats_seq(self, ts, line):
|
||||||
|
# Parse stats
|
||||||
|
parts = line.split()
|
||||||
|
mcu = ""
|
||||||
|
keyparts = {}
|
||||||
|
for p in parts[2:]:
|
||||||
|
if p.endswith(':'):
|
||||||
|
mcu = p
|
||||||
|
continue
|
||||||
|
name, val = p.split('=', 1)
|
||||||
|
keyparts[mcu + name] = val
|
||||||
|
min_ts = 0
|
||||||
|
max_ts = 999999999999
|
||||||
|
for mcu, info in self.mcus.items():
|
||||||
|
sname = '%s:send_seq' % (mcu,)
|
||||||
|
rname = '%s:receive_seq' % (mcu,)
|
||||||
|
if sname not in keyparts:
|
||||||
|
continue
|
||||||
|
sseq = int(keyparts[sname])
|
||||||
|
rseq = int(keyparts[rname])
|
||||||
|
min_ts = max(min_ts, info.sent_seq_to_time.get(sseq-1, 0),
|
||||||
|
info.receive_seq_to_time.get(rseq, 0))
|
||||||
|
max_ts = min(max_ts, info.sent_seq_to_time.get(sseq, 999999999999),
|
||||||
|
info.receive_seq_to_time.get(rseq+1, 999999999999))
|
||||||
|
return min(max(ts, min_ts + 0.00000001), max_ts - 0.00000001)
|
||||||
|
def trans_clock(self, clock, ts):
|
||||||
|
sample_time, sample_clock, freq = self.mcu.clock_est
|
||||||
|
exp_clock = int(sample_clock + (ts - sample_time) * freq)
|
||||||
|
ext_clock = add_high_bits(clock, exp_clock, 0xffffffff)
|
||||||
|
return sample_time + (ext_clock - sample_clock) / freq
|
||||||
|
def annotate(self, line, seq, ts):
|
||||||
|
if seq is not None:
|
||||||
|
line = repl_seq_r.sub(r"\g<0>(%d)" % (seq,), line)
|
||||||
|
def clock_update(m):
|
||||||
|
return m.group(0)[:-1] + "(%.6f) " % (
|
||||||
|
self.trans_clock(int(m.group('clock')), ts),)
|
||||||
|
line = repl_clock_r.sub(clock_update, line)
|
||||||
|
return line
|
||||||
|
def add_line(self, line_num, line):
|
||||||
|
self.parse_line(line_num, line)
|
||||||
|
if (self.first_stat_time is not None
|
||||||
|
and self.last_stat_time > self.first_stat_time + 5.):
|
||||||
|
self.finalize()
|
||||||
|
return False
|
||||||
|
if (line.startswith('Git version') or line.startswith('Start printer at')
|
||||||
|
or line == '===== Config file ====='):
|
||||||
|
self.finalize()
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
def parse_line(self, line_num, line):
|
||||||
|
m = sent_r.match(line)
|
||||||
|
if m is not None:
|
||||||
|
shortseq = int(m.group('shortseq'), 16)
|
||||||
|
seq = (self.mcu.shutdown_seq + int(m.group('count'))
|
||||||
|
- self.mcu.send_count)
|
||||||
|
seq = add_high_bits(shortseq, seq, 0xf)
|
||||||
|
ts = float(m.group('time'))
|
||||||
|
esttime = float(m.group('esttime'))
|
||||||
|
self.mcu.sent_time_to_seq[(esttime, seq & 0xf)] = seq
|
||||||
|
self.mcu.sent_seq_to_time[seq] = ts
|
||||||
|
line = self.annotate(line, seq, ts)
|
||||||
|
self.mcu.sent_stream.append((ts, line_num, line))
|
||||||
|
return
|
||||||
|
m = receive_r.match(line)
|
||||||
|
if m is not None:
|
||||||
|
shortseq = int(m.group('shortseq'), 16)
|
||||||
|
ts = float(m.group('time'))
|
||||||
|
esttime = float(m.group('esttime'))
|
||||||
|
seq = self.mcu.sent_time_to_seq.get((esttime, (shortseq - 1) & 0xf))
|
||||||
|
if seq is not None:
|
||||||
|
self.mcu.receive_seq_to_time[seq + 1] = ts
|
||||||
|
line = self.annotate(line, seq, ts)
|
||||||
|
self.mcu.receive_stream.append((ts, line_num, line))
|
||||||
|
return
|
||||||
|
m = gcode_r.match(line)
|
||||||
|
if m is not None:
|
||||||
|
ts = float(m.group('time'))
|
||||||
|
self.gcode_stream.append((ts, line_num, line))
|
||||||
|
return
|
||||||
|
m = stats_r.match(line)
|
||||||
|
if m is not None:
|
||||||
|
ts = float(m.group('time'))
|
||||||
|
self.last_stat_time = ts
|
||||||
|
if self.first_stat_time is None:
|
||||||
|
self.first_stat_time = ts
|
||||||
|
self.stats_stream.append((ts, line_num, line))
|
||||||
|
return
|
||||||
|
m = mcu_r.match(line)
|
||||||
|
if m is not None:
|
||||||
|
mcu = m.group('mcu')
|
||||||
|
self.mcu = self.mcus.setdefault(mcu, self.mcu_info())
|
||||||
|
m = clock_r.match(line)
|
||||||
|
if m is not None:
|
||||||
|
st = float(m.group('st'))
|
||||||
|
sc = int(m.group('sc'))
|
||||||
|
f = float(m.group('f'))
|
||||||
|
self.mcu.clock_est = (st, sc, f)
|
||||||
|
m = serial_dump_r.match(line)
|
||||||
|
if m is not None:
|
||||||
|
self.mcu.shutdown_seq = int(m.group('rseq'))
|
||||||
|
m = send_dump_r.match(line)
|
||||||
|
if m is not None:
|
||||||
|
self.mcu.send_count = int(m.group('count'))
|
||||||
|
m = receive_dump_r.match(line)
|
||||||
|
if m is not None:
|
||||||
|
self.mcu.receive_count = int(m.group('count'))
|
||||||
|
self.stats_stream.append((None, line_num, line))
|
||||||
|
def finalize(self):
|
||||||
|
# Ignore old stats
|
||||||
|
stream_ts = [i[0] for mcu in self.mcus.values()
|
||||||
|
for i in mcu.sent_stream + mcu.receive_stream]
|
||||||
|
if not stream_ts:
|
||||||
|
return
|
||||||
|
min_stream_ts = min(stream_ts)
|
||||||
|
max_stream_ts = max(stream_ts)
|
||||||
|
for i, info in enumerate(self.stats_stream):
|
||||||
|
if info[0] is not None and info[0] >= min_stream_ts - 5.:
|
||||||
|
del self.stats_stream[:i]
|
||||||
|
break
|
||||||
|
# Improve accuracy of stats timestamps
|
||||||
|
last_ts = self.stats_stream[0][0]
|
||||||
|
for i, (ts, line_num, line) in enumerate(self.stats_stream):
|
||||||
|
if ts is not None:
|
||||||
|
last_ts = self.check_stats_seq(ts, line)
|
||||||
|
elif line_num >= self.shutdown_line_num and last_ts <= max_stream_ts:
|
||||||
|
last_ts = max_stream_ts + 0.00000001
|
||||||
|
self.stats_stream[i] = (last_ts, line_num, line)
|
||||||
|
# Make sure no timestamp goes backwards
|
||||||
|
streams = ([self.stats_stream, self.gcode_stream] +
|
||||||
|
[mcu.sent_stream for mcu in self.mcus.values()] +
|
||||||
|
[mcu.receive_stream for mcu in self.mcus.values()])
|
||||||
|
for s in streams:
|
||||||
|
for i in range(1, len(s)):
|
||||||
|
if s[i-1][0] > s[i][0]:
|
||||||
|
s[i] = (s[i-1][0], s[i][1], s[i][2])
|
||||||
|
# Produce output sorted by timestamp
|
||||||
|
out = [i for s in streams for i in s]
|
||||||
|
out.sort()
|
||||||
|
out = [i[2] for i in out]
|
||||||
|
f = open(self.filename, 'wb')
|
||||||
|
f.write('\n'.join(self.comments + out))
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Startup
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
def main():
|
||||||
|
logname = sys.argv[1]
|
||||||
|
last_git = last_start = None
|
||||||
|
configs = {}
|
||||||
|
handler = None
|
||||||
|
recent_lines = collections.deque([], 200)
|
||||||
|
# Parse log file
|
||||||
|
f = open(logname, 'rb')
|
||||||
|
for line_num, line in enumerate(f):
|
||||||
|
line = line.rstrip()
|
||||||
|
line_num += 1
|
||||||
|
recent_lines.append((line_num, line))
|
||||||
|
if handler is not None:
|
||||||
|
ret = handler.add_line(line_num, line)
|
||||||
|
if ret:
|
||||||
|
continue
|
||||||
|
recent_lines.clear()
|
||||||
|
handler = None
|
||||||
|
if line.startswith('Git version'):
|
||||||
|
last_git = format_comment(line_num, line)
|
||||||
|
elif line.startswith('Start printer at'):
|
||||||
|
last_start = format_comment(line_num, line)
|
||||||
|
elif line == '===== Config file =====':
|
||||||
|
handler = GatherConfig(configs, line_num, recent_lines, logname)
|
||||||
|
handler.add_comment(last_git)
|
||||||
|
handler.add_comment(last_start)
|
||||||
|
elif 'shutdown: ' in line or line.startswith('Dumping '):
|
||||||
|
handler = GatherShutdown(configs, line_num, recent_lines, logname)
|
||||||
|
handler.add_comment(last_git)
|
||||||
|
handler.add_comment(last_start)
|
||||||
|
if handler is not None:
|
||||||
|
handler.finalize()
|
||||||
|
# Write found config files
|
||||||
|
for cfg in configs.values():
|
||||||
|
cfg.write_file()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -22,6 +22,7 @@ def main():
|
|||||||
continue
|
continue
|
||||||
args = dict([p.split('=', 1) for p in parts[1:]])
|
args = dict([p.split('=', 1) for p in parts[1:]])
|
||||||
if parts[0] == 'config_stepper':
|
if parts[0] == 'config_stepper':
|
||||||
|
# steppers[oid] = [dir_cmds, dir, queue_cmds, pos steps, neg steps]
|
||||||
steppers[args['oid']] = [0, 0, 0, 0, 0]
|
steppers[args['oid']] = [0, 0, 0, 0, 0]
|
||||||
elif parts[0] == 'set_next_step_dir':
|
elif parts[0] == 'set_next_step_dir':
|
||||||
so = steppers[args['oid']]
|
so = steppers[args['oid']]
|
||||||
|
|||||||
44
scripts/travis-build.sh
Executable file
44
scripts/travis-build.sh
Executable file
@@ -0,0 +1,44 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Test script for travis-ci.org continuous integration.
|
||||||
|
|
||||||
|
# Stop script early on any error; check variables; be verbose
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
# Paths to tools installed by travis-install.sh
|
||||||
|
export PATH=${PWD}/gcc-arm-none-eabi-7-2017-q4-major/bin:${PATH}
|
||||||
|
PYTHON=${PWD}/python-env/bin/python
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Run compile tests for several different MCU types
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
DICTDIR=${PWD}/dict
|
||||||
|
mkdir -p ${DICTDIR}
|
||||||
|
|
||||||
|
for TARGET in test/configs/*.config ; do
|
||||||
|
echo "travis_fold:start:mcu_compile $TARGET"
|
||||||
|
echo "=============== Test compile $TARGET"
|
||||||
|
make clean
|
||||||
|
make distclean
|
||||||
|
unset CC
|
||||||
|
cp ${TARGET} .config
|
||||||
|
make olddefconfig
|
||||||
|
make V=1
|
||||||
|
cp out/klipper.dict ${DICTDIR}/$(basename ${TARGET} .config).dict
|
||||||
|
echo "travis_fold:end:mcu_compile $TARGET"
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Verify klippy host software
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
HOSTDIR=${PWD}/hosttest
|
||||||
|
mkdir -p ${HOSTDIR}
|
||||||
|
|
||||||
|
echo "travis_fold:start:klippy"
|
||||||
|
echo "=============== Test invoke klippy"
|
||||||
|
$PYTHON klippy/klippy.py config/example.cfg -i /dev/null -o ${HOSTDIR}/output -v -d ${DICTDIR}/atmega2560-16mhz.dict
|
||||||
|
$PYTHON klippy/parsedump.py ${DICTDIR}/atmega2560-16mhz.dict ${HOSTDIR}/output > ${HOSTDIR}/output-parsed
|
||||||
|
echo "travis_fold:end:klippy"
|
||||||
33
scripts/travis-install.sh
Executable file
33
scripts/travis-install.sh
Executable file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Build setup script for travis-ci.org continuous integration testing.
|
||||||
|
# See travis-build.sh for the actual test steps.
|
||||||
|
|
||||||
|
# Stop script early on any error; check variables; be verbose
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Install embedded arm gcc
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
echo "=============== Install embedded arm gcc"
|
||||||
|
GCC_ARM_URL="https://developer.arm.com/-/media/Files/downloads/gnu-rm/7-2017q4/gcc-arm-none-eabi-7-2017-q4-major-linux.tar.bz2"
|
||||||
|
GCC_ARM_SHA="96a029e2ae130a1210eaa69e309ea40463028eab18ba19c1086e4c2dafe69a6a gcc-arm-none-eabi-7-2017-q4-major-linux.tar.bz2"
|
||||||
|
GCC_ARM_FILE="$(basename ${GCC_ARM_URL})"
|
||||||
|
|
||||||
|
wget "$GCC_ARM_URL"
|
||||||
|
FOUND_SHA=`sha256sum "$GCC_ARM_FILE"`
|
||||||
|
if [ "$FOUND_SHA" != "$GCC_ARM_SHA" ]; then
|
||||||
|
echo "ERROR: Mismatch on gcc arm sha256"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
tar xf "$GCC_ARM_FILE"
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Create python virtualenv environment
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
echo "=============== Install python virtualenv"
|
||||||
|
virtualenv python-env
|
||||||
|
./python-env/bin/pip install cffi==1.6.0 pyserial==3.2.1 greenlet==0.4.10
|
||||||
@@ -36,6 +36,9 @@ config HAVE_GPIO_SPI
|
|||||||
config HAVE_GPIO_HARD_PWM
|
config HAVE_GPIO_HARD_PWM
|
||||||
bool
|
bool
|
||||||
default n
|
default n
|
||||||
|
config HAVE_USER_INTERFACE
|
||||||
|
bool
|
||||||
|
default n
|
||||||
|
|
||||||
config NO_UNSTEP_DELAY
|
config NO_UNSTEP_DELAY
|
||||||
# Slow micro-controllers do not require a delay before returning a
|
# Slow micro-controllers do not require a delay before returning a
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ src-$(CONFIG_HAVE_GPIO) += gpiocmds.c stepper.c endstop.c
|
|||||||
src-$(CONFIG_HAVE_GPIO_ADC) += adccmds.c
|
src-$(CONFIG_HAVE_GPIO_ADC) += adccmds.c
|
||||||
src-$(CONFIG_HAVE_GPIO_SPI) += spicmds.c
|
src-$(CONFIG_HAVE_GPIO_SPI) += spicmds.c
|
||||||
src-$(CONFIG_HAVE_GPIO_HARD_PWM) += pwmcmds.c
|
src-$(CONFIG_HAVE_GPIO_HARD_PWM) += pwmcmds.c
|
||||||
|
src-$(CONFIG_HAVE_USER_INTERFACE) += lcd_st7920.c lcd_hd44780.c
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ config AVR_SELECT
|
|||||||
select HAVE_GPIO_ADC
|
select HAVE_GPIO_ADC
|
||||||
select HAVE_GPIO_SPI
|
select HAVE_GPIO_SPI
|
||||||
select HAVE_GPIO_HARD_PWM
|
select HAVE_GPIO_HARD_PWM
|
||||||
|
select HAVE_USER_INTERFACE
|
||||||
select NO_UNSTEP_DELAY
|
select NO_UNSTEP_DELAY
|
||||||
|
|
||||||
config BOARD_DIRECTORY
|
config BOARD_DIRECTORY
|
||||||
@@ -23,6 +24,8 @@ choice
|
|||||||
bool "atmega1280"
|
bool "atmega1280"
|
||||||
config MACH_at90usb1286
|
config MACH_at90usb1286
|
||||||
bool "at90usb1286"
|
bool "at90usb1286"
|
||||||
|
config MACH_at90usb646
|
||||||
|
bool "at90usb646"
|
||||||
config MACH_atmega1284p
|
config MACH_atmega1284p
|
||||||
bool "atmega1284p"
|
bool "atmega1284p"
|
||||||
config MACH_atmega644p
|
config MACH_atmega644p
|
||||||
@@ -40,9 +43,17 @@ config MCU
|
|||||||
default "atmega1284p" if MACH_atmega1284p
|
default "atmega1284p" if MACH_atmega1284p
|
||||||
default "atmega644p" if MACH_atmega644p
|
default "atmega644p" if MACH_atmega644p
|
||||||
default "at90usb1286" if MACH_at90usb1286
|
default "at90usb1286" if MACH_at90usb1286
|
||||||
|
default "at90usb646" if MACH_at90usb646
|
||||||
default "atmega1280" if MACH_atmega1280
|
default "atmega1280" if MACH_atmega1280
|
||||||
default "atmega2560" if MACH_atmega2560
|
default "atmega2560" if MACH_atmega2560
|
||||||
|
|
||||||
|
config AVRDUDE_PROTOCOL
|
||||||
|
string
|
||||||
|
default "wiring" if MACH_atmega2560
|
||||||
|
default "avr109" if MACH_at90usb1286
|
||||||
|
default "avr109" if MACH_at90usb646
|
||||||
|
default "arduino"
|
||||||
|
|
||||||
choice
|
choice
|
||||||
prompt "Processor speed"
|
prompt "Processor speed"
|
||||||
config AVR_FREQ_20000000
|
config AVR_FREQ_20000000
|
||||||
@@ -62,7 +73,7 @@ config CLOCK_FREQ
|
|||||||
|
|
||||||
config CLEAR_PRESCALER
|
config CLEAR_PRESCALER
|
||||||
bool "Manually clear the CPU prescaler field at startup"
|
bool "Manually clear the CPU prescaler field at startup"
|
||||||
depends on MACH_at90usb1286
|
depends on MACH_at90usb1286 || MACH_at90usb646
|
||||||
default y
|
default y
|
||||||
help
|
help
|
||||||
Some AVR chips ship with a "clock prescaler" that causes the
|
Some AVR chips ship with a "clock prescaler" that causes the
|
||||||
@@ -85,7 +96,7 @@ config AVR_WATCHDOG
|
|||||||
default y
|
default y
|
||||||
config AVR_USBSERIAL
|
config AVR_USBSERIAL
|
||||||
bool "Use USB for communication (instead of serial)"
|
bool "Use USB for communication (instead of serial)"
|
||||||
depends on MACH_at90usb1286
|
depends on MACH_at90usb1286 || MACH_at90usb646
|
||||||
default y
|
default y
|
||||||
config AVR_SERIAL
|
config AVR_SERIAL
|
||||||
depends on !AVR_USBSERIAL
|
depends on !AVR_USBSERIAL
|
||||||
|
|||||||
@@ -24,6 +24,6 @@ $(OUT)klipper.elf.hex: $(OUT)klipper.elf
|
|||||||
$(Q)$(OBJCOPY) -j .text -j .data -O ihex $< $@
|
$(Q)$(OBJCOPY) -j .text -j .data -O ihex $< $@
|
||||||
|
|
||||||
flash: $(OUT)klipper.elf.hex
|
flash: $(OUT)klipper.elf.hex
|
||||||
@echo " Flashing $(FLASH_DEVICE) via avrdude"
|
@echo " Flashing $^ to $(FLASH_DEVICE) via avrdude"
|
||||||
$(Q)if [ -z $(FLASH_DEVICE) ]; then echo "Please specify FLASH_DEVICE"; exit 1; fi
|
$(Q)if [ -z $(FLASH_DEVICE) ]; then echo "Please specify FLASH_DEVICE"; exit 1; fi
|
||||||
$(Q)avrdude -p$(CONFIG_MCU) -cwiring -P"$(FLASH_DEVICE)" -D -U"flash:w:$(OUT)klipper.elf.hex:i"
|
$(Q)avrdude -p$(CONFIG_MCU) -c$(CONFIG_AVRDUDE_PROTOCOL) -P"$(FLASH_DEVICE)" -D -U"flash:w:$(OUT)klipper.elf.hex:i"
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ static const uint8_t pwm_pins[ARRAY_SIZE(pwm_regs)] PROGMEM = {
|
|||||||
# ifdef OCR3A
|
# ifdef OCR3A
|
||||||
GPIO('B', 6), GPIO('B', 7),
|
GPIO('B', 6), GPIO('B', 7),
|
||||||
# endif
|
# endif
|
||||||
#elif CONFIG_MACH_at90usb1286
|
#elif CONFIG_MACH_at90usb1286 || CONFIG_MACH_at90usb646
|
||||||
GPIO('B', 7), GPIO('D', 0),
|
GPIO('B', 7), GPIO('D', 0),
|
||||||
GPIO('B', 5), GPIO('B', 6), GPIO('B', 7),
|
GPIO('B', 5), GPIO('B', 6), GPIO('B', 7),
|
||||||
GPIO('B', 4), GPIO('D', 1),
|
GPIO('B', 4), GPIO('D', 1),
|
||||||
@@ -186,20 +186,20 @@ gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val)
|
|||||||
uint8_t flags = READP(p->flags), cs;
|
uint8_t flags = READP(p->flags), cs;
|
||||||
if (flags & GP_AFMT) {
|
if (flags & GP_AFMT) {
|
||||||
switch (cycle_time) {
|
switch (cycle_time) {
|
||||||
case 0 ... 8*510L - 1: cs = 1; break;
|
case 0 ... (1+8) * 510L / 2 - 1: cs = 1; break;
|
||||||
case 8*510L ... 32*510L - 1: cs = 2; break;
|
case (1+8) * 510L / 2 ... (8+32) * 510L / 2 - 1: cs = 2; break;
|
||||||
case 32*510L ... 64*510L - 1: cs = 3; break;
|
case (8+32) * 510L / 2 ... (32+64) * 510L / 2 - 1: cs = 3; break;
|
||||||
case 64*510L ... 128*510L - 1: cs = 4; break;
|
case (32+64) * 510L / 2 ... (64+128) * 510L / 2 - 1: cs = 4; break;
|
||||||
case 128*510L ... 256*510L - 1: cs = 5; break;
|
case (64+128) * 510L / 2 ... (128+256) * 510L / 2 - 1: cs = 5; break;
|
||||||
case 256*510L ... 1024*510L - 1: cs = 6; break;
|
case (128+256) * 510L / 2 ... (256+1024) * 510L / 2 - 1: cs = 6; break;
|
||||||
default: cs = 7; break;
|
default: cs = 7; break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (cycle_time) {
|
switch (cycle_time) {
|
||||||
case 0 ... 8*510L - 1: cs = 1; break;
|
case 0 ... (1+8) * 510L / 2 - 1: cs = 1; break;
|
||||||
case 8*510L ... 64*510L - 1: cs = 2; break;
|
case (1+8) * 510L / 2 ... (8+64) * 510L / 2 - 1: cs = 2; break;
|
||||||
case 64*510L ... 256*510L - 1: cs = 3; break;
|
case (8+64) * 510L / 2 ... (64+256) * 510L / 2 - 1: cs = 3; break;
|
||||||
case 256*510L ... 1024*510L - 1: cs = 4; break;
|
case (64+256) * 510L / 2 ... (256+1024) * 510L / 2 - 1: cs = 4; break;
|
||||||
default: cs = 5; break;
|
default: cs = 5; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,7 +252,7 @@ static const uint8_t adc_pins[] PROGMEM = {
|
|||||||
#elif CONFIG_MACH_atmega644p || CONFIG_MACH_atmega1284p
|
#elif CONFIG_MACH_atmega644p || CONFIG_MACH_atmega1284p
|
||||||
GPIO('A', 0), GPIO('A', 1), GPIO('A', 2), GPIO('A', 3),
|
GPIO('A', 0), GPIO('A', 1), GPIO('A', 2), GPIO('A', 3),
|
||||||
GPIO('A', 4), GPIO('A', 5), GPIO('A', 6), GPIO('A', 7),
|
GPIO('A', 4), GPIO('A', 5), GPIO('A', 6), GPIO('A', 7),
|
||||||
#elif CONFIG_MACH_at90usb1286
|
#elif CONFIG_MACH_at90usb1286 || CONFIG_MACH_at90usb646
|
||||||
GPIO('F', 0), GPIO('F', 1), GPIO('F', 2), GPIO('F', 3),
|
GPIO('F', 0), GPIO('F', 1), GPIO('F', 2), GPIO('F', 3),
|
||||||
GPIO('F', 4), GPIO('F', 5), GPIO('F', 6), GPIO('F', 7),
|
GPIO('F', 4), GPIO('F', 5), GPIO('F', 6), GPIO('F', 7),
|
||||||
#elif CONFIG_MACH_atmega1280 || CONFIG_MACH_atmega2560
|
#elif CONFIG_MACH_atmega1280 || CONFIG_MACH_atmega2560
|
||||||
@@ -280,7 +280,7 @@ gpio_adc_setup(uint8_t pin)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enable ADC
|
// Enable ADC
|
||||||
ADCSRA |= (1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2)|(1<<ADEN);
|
ADCSRA = (1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2)|(1<<ADEN);
|
||||||
|
|
||||||
// Disable digital input for this pin
|
// Disable digital input for this pin
|
||||||
#ifdef DIDR2
|
#ifdef DIDR2
|
||||||
@@ -316,7 +316,7 @@ gpio_adc_sample(struct gpio_adc g)
|
|||||||
#if defined(ADCSRB) && defined(MUX5)
|
#if defined(ADCSRB) && defined(MUX5)
|
||||||
// the MUX5 bit of ADCSRB selects whether we're reading from channels
|
// the MUX5 bit of ADCSRB selects whether we're reading from channels
|
||||||
// 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
|
// 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
|
||||||
ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((g.chan >> 3) & 0x01) << MUX5);
|
ADCSRB = ((g.chan >> 3) & 0x01) << MUX5;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ADMUX = ADMUX_DEFAULT | (g.chan & 0x07);
|
ADMUX = ADMUX_DEFAULT | (g.chan & 0x07);
|
||||||
@@ -352,7 +352,7 @@ gpio_adc_cancel_sample(struct gpio_adc g)
|
|||||||
static const uint8_t SS = GPIO('B', 2), SCK = GPIO('B', 5), MOSI = GPIO('B', 3);
|
static const uint8_t SS = GPIO('B', 2), SCK = GPIO('B', 5), MOSI = GPIO('B', 3);
|
||||||
#elif CONFIG_MACH_atmega644p || CONFIG_MACH_atmega1284p
|
#elif CONFIG_MACH_atmega644p || CONFIG_MACH_atmega1284p
|
||||||
static const uint8_t SS = GPIO('B', 4), SCK = GPIO('B', 7), MOSI = GPIO('B', 5);
|
static const uint8_t SS = GPIO('B', 4), SCK = GPIO('B', 7), MOSI = GPIO('B', 5);
|
||||||
#elif CONFIG_MACH_at90usb1286 || CONFIG_MACH_atmega1280 || CONFIG_MACH_atmega2560
|
#elif CONFIG_MACH_at90usb1286 || CONFIG_MACH_at90usb646 || CONFIG_MACH_atmega1280 || CONFIG_MACH_atmega2560
|
||||||
static const uint8_t SS = GPIO('B', 0), SCK = GPIO('B', 1), MOSI = GPIO('B', 2);
|
static const uint8_t SS = GPIO('B', 0), SCK = GPIO('B', 1), MOSI = GPIO('B', 2);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -93,12 +93,13 @@ DECL_SHUTDOWN(timer_reset);
|
|||||||
void
|
void
|
||||||
timer_init(void)
|
timer_init(void)
|
||||||
{
|
{
|
||||||
|
irqstatus_t flag = irq_save();
|
||||||
// no outputs
|
// no outputs
|
||||||
TCCR1A = 0;
|
TCCR1A = 0;
|
||||||
// Normal Mode
|
// Normal Mode
|
||||||
TCCR1B = 1<<CS10;
|
TCCR1B = 1<<CS10;
|
||||||
// Setup for first irq
|
// Setup for first irq
|
||||||
irqstatus_t flag = irq_save();
|
TCNT1 = 0;
|
||||||
timer_kick();
|
timer_kick();
|
||||||
timer_repeat_set(timer_get() + 50);
|
timer_repeat_set(timer_get() + 50);
|
||||||
timer_reset();
|
timer_reset();
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ soft_pwm_load_event(struct timer *timer)
|
|||||||
s->flags = flags;
|
s->flags = flags;
|
||||||
gpio_out_write(s->pin, flags & SPF_ON);
|
gpio_out_write(s->pin, flags & SPF_ON);
|
||||||
if (!(flags & SPF_TOGGLING)) {
|
if (!(flags & SPF_TOGGLING)) {
|
||||||
// Pin is in an always on (value=255) or always off (value=0) state
|
// Pin is in an always on (value=256) or always off (value=0) state
|
||||||
if (!(flags & SPF_CHECK_END))
|
if (!(flags & SPF_CHECK_END))
|
||||||
return SF_DONE;
|
return SF_DONE;
|
||||||
s->timer.waketime = s->end_time = s->end_time + s->max_duration;
|
s->timer.waketime = s->end_time = s->end_time + s->max_duration;
|
||||||
@@ -199,7 +199,7 @@ command_schedule_soft_pwm_out(uint32_t *args)
|
|||||||
s->end_time = time;
|
s->end_time = time;
|
||||||
s->next_on_duration = next_on_duration;
|
s->next_on_duration = next_on_duration;
|
||||||
s->next_off_duration = next_off_duration;
|
s->next_off_duration = next_off_duration;
|
||||||
s->flags |= next_flags;
|
s->flags = (s->flags & 0xf) | next_flags;
|
||||||
if (s->flags & SPF_TOGGLING && timer_is_before(s->timer.waketime, time)) {
|
if (s->flags & SPF_TOGGLING && timer_is_before(s->timer.waketime, time)) {
|
||||||
// soft_pwm_toggle_event() will schedule a load event when ready
|
// soft_pwm_toggle_event() will schedule a load event when ready
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
132
src/lcd_hd44780.c
Normal file
132
src/lcd_hd44780.c
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
// Commands for sending messages to a 4-bit hd44780 lcd driver
|
||||||
|
//
|
||||||
|
// Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
|
||||||
|
//
|
||||||
|
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
|
#include "basecmd.h" // oid_alloc
|
||||||
|
#include "board/gpio.h" // gpio_out_write
|
||||||
|
#include "board/irq.h" // irq_disable
|
||||||
|
#include "board/misc.h" // timer_from_us
|
||||||
|
#include "command.h" // DECL_COMMAND
|
||||||
|
#include "sched.h" // DECL_SHUTDOWN
|
||||||
|
|
||||||
|
struct hd44780 {
|
||||||
|
uint32_t last_cmd_time, cmd_wait_ticks;
|
||||||
|
uint8_t last;
|
||||||
|
struct gpio_out rs, e, d4, d5, d6, d7;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
* Transmit functions
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
// Write 4 bits to the hd44780 using the 4bit parallel interface
|
||||||
|
static __always_inline void
|
||||||
|
hd44780_xmit_bits(uint8_t toggle, struct gpio_out e, struct gpio_out d4
|
||||||
|
, struct gpio_out d5, struct gpio_out d6, struct gpio_out d7)
|
||||||
|
{
|
||||||
|
gpio_out_toggle(e);
|
||||||
|
if (toggle & 0x10)
|
||||||
|
gpio_out_toggle(d4);
|
||||||
|
if (toggle & 0x20)
|
||||||
|
gpio_out_toggle(d5);
|
||||||
|
if (toggle & 0x40)
|
||||||
|
gpio_out_toggle(d6);
|
||||||
|
if (toggle & 0x80)
|
||||||
|
gpio_out_toggle(d7);
|
||||||
|
gpio_out_toggle(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transmit 8 bits to the chip
|
||||||
|
static void
|
||||||
|
hd44780_xmit_byte(struct hd44780 *h, uint8_t data)
|
||||||
|
{
|
||||||
|
struct gpio_out e = h->e, d4 = h->d4, d5 = h->d5, d6 = h->d6, d7 = h->d7;
|
||||||
|
hd44780_xmit_bits(h->last ^ data, e, d4, d5, d6, d7);
|
||||||
|
h->last = data << 4;
|
||||||
|
hd44780_xmit_bits(data ^ h->last, e, d4, d5, d6, d7);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transmit a series of bytes to the chip
|
||||||
|
static void
|
||||||
|
hd44780_xmit(struct hd44780 *h, uint8_t len, uint8_t *data)
|
||||||
|
{
|
||||||
|
uint32_t last_cmd_time=h->last_cmd_time, cmd_wait_ticks=h->cmd_wait_ticks;
|
||||||
|
while (len--) {
|
||||||
|
uint8_t b = *data++;
|
||||||
|
while (timer_read_time() - last_cmd_time < cmd_wait_ticks)
|
||||||
|
irq_poll();
|
||||||
|
hd44780_xmit_byte(h, b);
|
||||||
|
last_cmd_time = timer_read_time();
|
||||||
|
}
|
||||||
|
h->last_cmd_time = last_cmd_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
* Interface
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
void
|
||||||
|
command_config_hd44780(uint32_t *args)
|
||||||
|
{
|
||||||
|
struct hd44780 *h = oid_alloc(args[0], command_config_hd44780, sizeof(*h));
|
||||||
|
h->rs = gpio_out_setup(args[1], 0);
|
||||||
|
h->e = gpio_out_setup(args[2], 0);
|
||||||
|
h->d4 = gpio_out_setup(args[3], 0);
|
||||||
|
h->d5 = gpio_out_setup(args[4], 0);
|
||||||
|
h->d6 = gpio_out_setup(args[5], 0);
|
||||||
|
h->d7 = gpio_out_setup(args[6], 0);
|
||||||
|
|
||||||
|
// Calibrate cmd_wait_ticks
|
||||||
|
irq_disable();
|
||||||
|
uint32_t start = timer_read_time();
|
||||||
|
hd44780_xmit_byte(h, 0);
|
||||||
|
uint32_t end = timer_read_time();
|
||||||
|
irq_enable();
|
||||||
|
uint32_t diff = end - start, delay_ticks = args[7];
|
||||||
|
if (delay_ticks > diff)
|
||||||
|
h->cmd_wait_ticks = delay_ticks - diff;
|
||||||
|
}
|
||||||
|
DECL_COMMAND(command_config_hd44780,
|
||||||
|
"config_hd44780 oid=%c rs_pin=%u e_pin=%u"
|
||||||
|
" d4_pin=%u d5_pin=%u d6_pin=%u d7_pin=%u delay_ticks=%u");
|
||||||
|
|
||||||
|
void
|
||||||
|
command_hd44780_send_cmds(uint32_t *args)
|
||||||
|
{
|
||||||
|
struct hd44780 *h = oid_lookup(args[0], command_config_hd44780);
|
||||||
|
gpio_out_write(h->rs, 0);
|
||||||
|
uint8_t len = args[1], *cmds = (void*)(size_t)args[2];
|
||||||
|
hd44780_xmit(h, len, cmds);
|
||||||
|
}
|
||||||
|
DECL_COMMAND(command_hd44780_send_cmds, "hd44780_send_cmds oid=%c cmds=%*s");
|
||||||
|
|
||||||
|
void
|
||||||
|
command_hd44780_send_data(uint32_t *args)
|
||||||
|
{
|
||||||
|
struct hd44780 *h = oid_lookup(args[0], command_config_hd44780);
|
||||||
|
gpio_out_write(h->rs, 1);
|
||||||
|
uint8_t len = args[1], *data = (void*)(size_t)args[2];
|
||||||
|
hd44780_xmit(h, len, data);
|
||||||
|
}
|
||||||
|
DECL_COMMAND(command_hd44780_send_data, "hd44780_send_data oid=%c data=%*s");
|
||||||
|
|
||||||
|
void
|
||||||
|
hd44780_shutdown(void)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
struct hd44780 *h;
|
||||||
|
foreach_oid(i, h, command_config_hd44780) {
|
||||||
|
gpio_out_write(h->rs, 0);
|
||||||
|
gpio_out_write(h->e, 0);
|
||||||
|
gpio_out_write(h->d4, 0);
|
||||||
|
gpio_out_write(h->d5, 0);
|
||||||
|
gpio_out_write(h->d6, 0);
|
||||||
|
gpio_out_write(h->d7, 0);
|
||||||
|
h->last = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DECL_SHUTDOWN(hd44780_shutdown);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user