Compare commits
229 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f1d0c2a7c | ||
|
|
02549c9299 | ||
|
|
98f2adbcb5 | ||
|
|
c9d21574d8 | ||
|
|
253517096e | ||
|
|
0fa35254c6 | ||
|
|
fc9fb7473c | ||
|
|
31ca2331d2 | ||
|
|
b5062a07d1 | ||
|
|
4112007314 | ||
|
|
a3162b17d9 | ||
|
|
e177d4f70d | ||
|
|
631b0e6c37 | ||
|
|
a7f339ad1c | ||
|
|
c1c0b2dd38 | ||
|
|
d7a1111955 | ||
|
|
d73340474b | ||
|
|
4f7237de44 | ||
|
|
4096745a58 | ||
|
|
a97e074022 | ||
|
|
917c6aa94a | ||
|
|
05bd6fda7e | ||
|
|
7c78de989d | ||
|
|
35a6d9ba87 | ||
|
|
c63754fc32 | ||
|
|
ccb93068fe | ||
|
|
a6fe355801 | ||
|
|
fe11c3e348 | ||
|
|
56d4422d31 | ||
|
|
e507848a8f | ||
|
|
70599667cb | ||
|
|
1878da228d | ||
|
|
37865d69a2 | ||
|
|
83eba902a3 | ||
|
|
ec805aee2e | ||
|
|
167b18b58f | ||
|
|
4b1a530330 | ||
|
|
19ffaa9ff0 | ||
|
|
0f5167a407 | ||
|
|
563ab5caa5 | ||
|
|
db5b5f121c | ||
|
|
1f417a8441 | ||
|
|
b74b09ea7a | ||
|
|
2cce67ad84 | ||
|
|
2cb935c300 | ||
|
|
b9623c1128 | ||
|
|
7a386cff7d | ||
|
|
839725e3c5 | ||
|
|
8920479f85 | ||
|
|
4c25eae9b4 | ||
|
|
a3a45b5037 | ||
|
|
dc645d76b4 | ||
|
|
daff83ee9a | ||
|
|
9f9e3e61d6 | ||
|
|
1592395036 | ||
|
|
8491b1f86a | ||
|
|
a7b4d70cc6 | ||
|
|
fa193e9618 | ||
|
|
050008f3c8 | ||
|
|
70e53cb080 | ||
|
|
7b03b04c78 | ||
|
|
7a7b98cc31 | ||
|
|
15d5837322 | ||
|
|
f9ebb8b23e | ||
|
|
77b94451de | ||
|
|
ca6245e974 | ||
|
|
1cdddeec30 | ||
|
|
657c908f88 | ||
|
|
d7a0e22d59 | ||
|
|
33b809714f | ||
|
|
b915a2ad7d | ||
|
|
85ed5cef7f | ||
|
|
df42b0d1ac | ||
|
|
98add22891 | ||
|
|
1d81bf5596 | ||
|
|
e4153a536f | ||
|
|
79f31238b0 | ||
|
|
8c712d959d | ||
|
|
c4b1a79db2 | ||
|
|
47f12f107d | ||
|
|
bfad970e4d | ||
|
|
49bdc6fbd1 | ||
|
|
57f279677f | ||
|
|
fff73c7735 | ||
|
|
e44678ceba | ||
|
|
85c0e9c574 | ||
|
|
0c3ec7f9a5 | ||
|
|
74e15b2eb5 | ||
|
|
565861f680 | ||
|
|
7c991399ac | ||
|
|
384c853a39 | ||
|
|
c0380d0280 | ||
|
|
a1c61563a0 | ||
|
|
aa0f1aaeb2 | ||
|
|
03ddd64b93 | ||
|
|
7fc9ba7d3a | ||
|
|
63b6bab5c3 | ||
|
|
eb4eeb6f73 | ||
|
|
6988507998 | ||
|
|
65be6d5146 | ||
|
|
6d05dd07f5 | ||
|
|
7436ec093a | ||
|
|
2b735daae5 | ||
|
|
f8b0c884b0 | ||
|
|
18f4d343f5 | ||
|
|
31e78c90e2 | ||
|
|
3238256b79 | ||
|
|
59b71d5d05 | ||
|
|
4dfa6c6ee4 | ||
|
|
60e488eb17 | ||
|
|
14340ac4df | ||
|
|
efbfc2b1ab | ||
|
|
d4f09bc20d | ||
|
|
d67f962a38 | ||
|
|
6de85d02ae | ||
|
|
f28eb902df | ||
|
|
9702d522a4 | ||
|
|
4f710b0470 | ||
|
|
5f29787dc7 | ||
|
|
1fbb36fa87 | ||
|
|
3cafcc2bc7 | ||
|
|
8d92c898ee | ||
|
|
9bf73cd72d | ||
|
|
f97cf5c3b6 | ||
|
|
5ff2d5aee6 | ||
|
|
1f474742eb | ||
|
|
0041a0079d | ||
|
|
9bb8b0c622 | ||
|
|
df6d3107f2 | ||
|
|
cbdc54843d | ||
|
|
d2027cb4a9 | ||
|
|
e60779bfe1 | ||
|
|
f66b1ac450 | ||
|
|
ff6a96665a | ||
|
|
4388a294a4 | ||
|
|
d21b9280f0 | ||
|
|
92649332ce | ||
|
|
be91c1229f | ||
|
|
168cb95bd5 | ||
|
|
1d796a4e24 | ||
|
|
8e6d5efdac | ||
|
|
0f2478b62f | ||
|
|
e5d7e593ec | ||
|
|
69b927bfe9 | ||
|
|
944d176856 | ||
|
|
cdd5a772e8 | ||
|
|
0a3c23bcf6 | ||
|
|
cb286ede9d | ||
|
|
16e3dbb18c | ||
|
|
a38437f378 | ||
|
|
8d6ecd9af8 | ||
|
|
342a7096ea | ||
|
|
60a4bda9d4 | ||
|
|
d5fc594317 | ||
|
|
64407dc5d2 | ||
|
|
b0329465ec | ||
|
|
0f70b420f2 | ||
|
|
21c4dea0e6 | ||
|
|
bcaf818c0e | ||
|
|
37bac916e7 | ||
|
|
affdbbf9ca | ||
|
|
4fcf0ff2ac | ||
|
|
c19af4fb2b | ||
|
|
f53897758d | ||
|
|
54002c4391 | ||
|
|
6a53eaefc0 | ||
|
|
4bc114336c | ||
|
|
47f1d377f5 | ||
|
|
566699f68a | ||
|
|
29ba92a551 | ||
|
|
38e9484f9f | ||
|
|
fec12030a9 | ||
|
|
bdfdf7ef55 | ||
|
|
9f65ae72c3 | ||
|
|
3434ea540c | ||
|
|
29131c873a | ||
|
|
ab1eb70d1c | ||
|
|
71256f9456 | ||
|
|
6179839215 | ||
|
|
0ca96e543c | ||
|
|
acb0b8f599 | ||
|
|
20d0936fa2 | ||
|
|
c24b7a7ef9 | ||
|
|
074495a13a | ||
|
|
e14d86d8b8 | ||
|
|
528c29c01c | ||
|
|
1bb7a22115 | ||
|
|
19ed67331d | ||
|
|
667b72870f | ||
|
|
4194ebf9df | ||
|
|
5beceaae5c | ||
|
|
9c1bf1387c | ||
|
|
fc6a31eac8 | ||
|
|
064e8bdd84 | ||
|
|
262ccbcf30 | ||
|
|
7567885115 | ||
|
|
ed715ec437 | ||
|
|
9a44a20a9d | ||
|
|
f335045273 | ||
|
|
4ea091339e | ||
|
|
8378b7345b | ||
|
|
4a71c7a2bd | ||
|
|
b2885a53cb | ||
|
|
46b6b4037d | ||
|
|
93d3a6e1d1 | ||
|
|
eebaeeff96 | ||
|
|
3a7a77d49e | ||
|
|
c87c090264 | ||
|
|
b26922978a | ||
|
|
5a5bd2596a | ||
|
|
38ca051381 | ||
|
|
91056809dd | ||
|
|
f75430e95f | ||
|
|
8e797e6830 | ||
|
|
73c4be3fd3 | ||
|
|
c552ba06b4 | ||
|
|
6bd5f4e44e | ||
|
|
6138d18f4b | ||
|
|
d028f42e99 | ||
|
|
860fc3e91d | ||
|
|
2e03d84755 | ||
|
|
f2b406fc5e | ||
|
|
f46bc0ef04 | ||
|
|
800d53db6a | ||
|
|
a9444d3399 | ||
|
|
4a16053c00 | ||
|
|
d0c61f0f76 | ||
|
|
451ffd567d | ||
|
|
f3a49604f1 |
2
Makefile
@@ -83,7 +83,7 @@ $(OUT)declfunc.lds: src/declfunc.lds.S
|
||||
|
||||
$(OUT)klipper.o: $(patsubst %.c, $(OUT)src/%.o,$(src-y)) $(OUT)declfunc.lds
|
||||
@echo " Linking $@"
|
||||
$(Q)$(CC) $(CFLAGS) -Wl,-r -Wl,-T,$(OUT)declfunc.lds -nostdlib $(patsubst %.c, $(OUT)src/%.o,$(src-y)) -o $@
|
||||
$(Q)$(CC) $(CFLAGS) $(CFLAGS_klipper.o) -Wl,-r -Wl,-T,$(OUT)declfunc.lds -nostdlib $(patsubst %.c, $(OUT)src/%.o,$(src-y)) -o $@
|
||||
|
||||
$(OUT)compile_time_request.o: $(OUT)klipper.o ./scripts/buildcommands.py
|
||||
@echo " Building $@"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Support for internal testing with the "simulavr" program. To use
|
||||
# this config, compile the firmware for an AVR atmega644p, disable the
|
||||
# AVR watchdog timer, set the MCU frequency to 20000000, and set the
|
||||
# serial baud rate to 115200.
|
||||
# serial baud rate to 250000.
|
||||
|
||||
[stepper_x]
|
||||
# Pins: PA5, PA4, PA1
|
||||
@@ -42,11 +42,11 @@ step_pin: ar19
|
||||
dir_pin: ar18
|
||||
enable_pin: ar25
|
||||
step_distance: .004242
|
||||
max_velocity: 200000
|
||||
max_accel: 3000
|
||||
nozzle_diameter: 0.500
|
||||
filament_diameter: 3.500
|
||||
heater_pin: ar4
|
||||
thermistor_pin: analog1
|
||||
thermistor_type: EPCOS 100K B57560G104F
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
sensor_pin: analog1
|
||||
control: pid
|
||||
pid_Kp: 22.2
|
||||
pid_Ki: 1.08
|
||||
@@ -57,19 +57,17 @@ max_temp: 210
|
||||
|
||||
[heater_bed]
|
||||
heater_pin: ar3
|
||||
thermistor_pin: analog0
|
||||
thermistor_type: EPCOS 100K B57560G104F
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
sensor_pin: analog0
|
||||
control: watermark
|
||||
min_temp: 0
|
||||
max_temp: 110
|
||||
|
||||
[fan]
|
||||
pin: ar14
|
||||
hard_pwm: 1
|
||||
|
||||
[mcu]
|
||||
serial: /tmp/pseudoserial
|
||||
baud: 250000
|
||||
pin_map: arduino
|
||||
|
||||
[printer]
|
||||
|
||||
84
config/example-corexy.cfg
Normal file
@@ -0,0 +1,84 @@
|
||||
# This file serves as documentation for config parameters of corexy
|
||||
# style printers. One may copy and edit this file to configure a new
|
||||
# corexy printer. Only parameters unique to corexy printers are
|
||||
# described here - see the "example.cfg" file for description of
|
||||
# common config parameters.
|
||||
|
||||
# DO NOT COPY THIS FILE WITHOUT CAREFULLY READING AND UPDATING IT
|
||||
# FIRST. Incorrectly configured parameters may cause damage.
|
||||
|
||||
# The stepper_x section is used to describe the X axis as well as the
|
||||
# stepper controlling the X+Y movement
|
||||
[stepper_x]
|
||||
step_pin: ar54
|
||||
dir_pin: ar55
|
||||
enable_pin: !ar38
|
||||
step_distance: .01
|
||||
endstop_pin: ^ar2
|
||||
homing_speed: 50.0
|
||||
position_min: 0
|
||||
position_endstop: 0
|
||||
position_max: 200
|
||||
|
||||
# The stepper_y section is used to describe the Y axis as well as the
|
||||
# stepper controlling the X-Y movement
|
||||
[stepper_y]
|
||||
step_pin: ar60
|
||||
dir_pin: ar61
|
||||
enable_pin: !ar56
|
||||
step_distance: .01
|
||||
endstop_pin: ^ar15
|
||||
homing_speed: 50.0
|
||||
position_min: 0
|
||||
position_endstop: 0
|
||||
position_max: 200
|
||||
|
||||
[stepper_z]
|
||||
step_pin: ar46
|
||||
dir_pin: ar48
|
||||
enable_pin: !ar62
|
||||
step_distance: .01
|
||||
endstop_pin: ^ar19
|
||||
position_min: 0.1
|
||||
position_endstop: 0.5
|
||||
position_max: 200
|
||||
|
||||
[extruder]
|
||||
step_pin: ar26
|
||||
dir_pin: ar28
|
||||
enable_pin: !ar24
|
||||
step_distance: .0022
|
||||
nozzle_diameter: 0.400
|
||||
filament_diameter: 1.750
|
||||
heater_pin: ar10
|
||||
sensor_type: ATC Semitec 104GT-2
|
||||
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: watermark
|
||||
min_temp: 0
|
||||
max_temp: 130
|
||||
|
||||
[fan]
|
||||
pin: ar9
|
||||
|
||||
[mcu]
|
||||
serial: /dev/ttyACM0
|
||||
pin_map: arduino
|
||||
|
||||
[printer]
|
||||
kinematics: corexy
|
||||
# This option must be "corexy" for corexy printers.
|
||||
max_velocity: 300
|
||||
max_accel: 3000
|
||||
max_z_velocity: 25
|
||||
max_z_accel: 30
|
||||
@@ -9,8 +9,7 @@
|
||||
|
||||
# The stepper_a section describes the stepper controlling the front
|
||||
# left tower (at 210 degrees). This section also controls the homing
|
||||
# parameters (homing_speed, homing_retract_dist) and maximum tower
|
||||
# length (position_max) for all towers.
|
||||
# parameters (homing_speed, homing_retract_dist) for all towers.
|
||||
[stepper_a]
|
||||
step_pin: ar54
|
||||
dir_pin: ar55
|
||||
@@ -19,7 +18,6 @@ step_distance: .01
|
||||
endstop_pin: ^ar2
|
||||
homing_speed: 50.0
|
||||
position_endstop: 297.05
|
||||
position_max: 297.55
|
||||
|
||||
# The stepper_b section describes the stepper controlling the front
|
||||
# right tower (at 330 degrees)
|
||||
@@ -46,11 +44,11 @@ step_pin: ar26
|
||||
dir_pin: ar28
|
||||
enable_pin: !ar24
|
||||
step_distance: .0022
|
||||
max_velocity: 200
|
||||
max_accel: 3000
|
||||
nozzle_diameter: 0.400
|
||||
filament_diameter: 1.750
|
||||
heater_pin: ar10
|
||||
thermistor_pin: analog13
|
||||
thermistor_type: ATC Semitec 104GT-2
|
||||
sensor_type: ATC Semitec 104GT-2
|
||||
sensor_pin: analog13
|
||||
control: pid
|
||||
pid_Kp: 22.2
|
||||
pid_Ki: 1.08
|
||||
@@ -60,8 +58,8 @@ max_temp: 250
|
||||
|
||||
[heater_bed]
|
||||
heater_pin: ar8
|
||||
thermistor_pin: analog14
|
||||
thermistor_type: EPCOS 100K B57560G104F
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
sensor_pin: analog14
|
||||
control: watermark
|
||||
min_temp: 0
|
||||
max_temp: 130
|
||||
@@ -69,27 +67,37 @@ max_temp: 130
|
||||
# Extruder print fan (omit section if fan not present)
|
||||
#[fan]
|
||||
#pin: ar9
|
||||
#hard_pwm: 1
|
||||
|
||||
[mcu]
|
||||
serial: /dev/ttyACM0
|
||||
baud: 250000
|
||||
pin_map: arduino
|
||||
|
||||
[printer]
|
||||
kinematics: delta
|
||||
# This option must be "delta" for linear delta printers
|
||||
# This option must be "delta" for linear delta printers.
|
||||
max_velocity: 300
|
||||
# Maximum velocity (in mm/s) of the toolhead relative to the
|
||||
# print. This limits the velocity of the toolhead relative to the
|
||||
# print - at the extreme end of the print envelope the delta axis
|
||||
# steppers themselves may briefly exceed this speed by up to 3
|
||||
# times. This parameter must be specified.
|
||||
max_accel: 3000
|
||||
# Maximum acceleration (in mm/s^2) of the toolhead relative to the
|
||||
# print. This limits the acceleration of the toolhead relative to
|
||||
# the print - at the extreme end of the print envelope the delta
|
||||
# axis steppers may briefly exceed this acceleration by up to 3
|
||||
# times. This parameter must be specified.
|
||||
max_z_velocity: 200
|
||||
# For delta printers this limits the maximum velocity (in mm/s) of
|
||||
# moves with z axis movement. This setting can be used to reduce the
|
||||
# maximum speed of up/down moves (which require a higher step rate
|
||||
# than other moves on a delta printer).
|
||||
# than other moves on a delta printer). The default is to use
|
||||
# max_velocity for max_z_velocity.
|
||||
delta_arm_length: 333.0
|
||||
# Length (in mm) of the diagonal rods that connect the linear axes
|
||||
# to the print head
|
||||
# to the print head. This parameter must be provided.
|
||||
delta_radius: 174.75
|
||||
# Radius (in mm) of the horizontal circle formed by the three linear
|
||||
# axis towers. This parameter may also be calculated as:
|
||||
# delta_radius = smooth_rod_offset - effector_offset - carriage_offset
|
||||
# This parameter must be provided.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# This file serves as documentation for config parameters. One may
|
||||
# copy and edit this file to configure a new cartesian style
|
||||
# printer. For delta style printers, see the "example-delta.cfg" file.
|
||||
# printer. For delta style printers, see the "example-delta.cfg"
|
||||
# file. For corexy/h-bot style printers, see the "example-corexy.cfg"
|
||||
# file.
|
||||
|
||||
# DO NOT COPY THIS FILE WITHOUT CAREFULLY READING AND UPDATING IT
|
||||
# FIRST. Incorrectly configured parameters may cause damage.
|
||||
@@ -19,27 +21,35 @@
|
||||
# the X axis in a cartesian robot
|
||||
[stepper_x]
|
||||
step_pin: ar29
|
||||
# Step GPIO pin (triggered high)
|
||||
# Step GPIO pin (triggered high). This parameter must be provided.
|
||||
dir_pin: !ar28
|
||||
# Direction GPIO pin (high indicates positive direction)
|
||||
# Direction GPIO pin (high indicates positive direction). This
|
||||
# parameter must be provided.
|
||||
enable_pin: !ar25
|
||||
# Enable pin (default is enable high; use ! to indicate enable low)
|
||||
# Enable pin (default is enable high; use ! to indicate enable
|
||||
# low). If this parameter is not provided then the stepper motor
|
||||
# driver must always be enabled.
|
||||
step_distance: .0225
|
||||
# Distance in mm that each step causes the axis to travel
|
||||
# Distance in mm that each step causes the axis to travel. This
|
||||
# parameter must be provided.
|
||||
endstop_pin: ^ar0
|
||||
# Endstop switch detection pin
|
||||
homing_speed: 50.0
|
||||
# Maximum velocity (in mm/s) of the stepper when homing
|
||||
homing_retract_dist: 5.0
|
||||
# Distance to backoff (in mm) before homing a second time during homing
|
||||
homing_positive_dir: False
|
||||
# If true, homes in a positive direction (away from zero)
|
||||
homing_stepper_phases: 0
|
||||
# Endstop switch detection pin. This parameter must be provided for
|
||||
# the X, Y, and Z steppers on cartesian style printers.
|
||||
#homing_speed: 5.0
|
||||
# Maximum velocity (in mm/s) of the stepper when homing. The default
|
||||
# is 5mm/s.
|
||||
#homing_retract_dist: 5.0
|
||||
# Distance to backoff (in mm) before homing a second time during
|
||||
# homing. The default is 5mm.
|
||||
#homing_positive_dir: False
|
||||
# If true, homes in a positive direction (away from zero). The
|
||||
# default is False.
|
||||
#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
|
||||
#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
|
||||
@@ -56,12 +66,14 @@ homing_endstop_accuracy: 0.200
|
||||
# phase will be used on all subsequent homes.
|
||||
position_min: -0.25
|
||||
# Minimum valid distance (in mm) the user may command the stepper to
|
||||
# move to
|
||||
# move to. The default is 0mm.
|
||||
position_endstop: 0
|
||||
# Location of the endstop (in mm)
|
||||
# Location of the endstop (in mm). This parameter must be provided
|
||||
# for the X, Y, and Z steppers on cartesian style printers.
|
||||
position_max: 200
|
||||
# Maximum valid distance (in mm) the user may command the stepper to
|
||||
# move to
|
||||
# move to. This parameter must be provided for the X, Y, and Z
|
||||
# steppers on cartesian style printers.
|
||||
|
||||
# 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
|
||||
@@ -99,84 +111,148 @@ step_pin: ar19
|
||||
dir_pin: ar18
|
||||
enable_pin: !ar25
|
||||
step_distance: .004242
|
||||
max_velocity: 200000
|
||||
nozzle_diameter: 0.500
|
||||
# Diameter of the nozzle orifice (in mm). This parameter must be
|
||||
# provided.
|
||||
filament_diameter: 3.500
|
||||
# Diameter of the raw filament (in mm) as it enters the
|
||||
# extruder. This parameter must be provided.
|
||||
#max_extrude_cross_section:
|
||||
# Maximum area of the cross section of an extrusion line (in
|
||||
# mm^2). If a move requests an extrusion rate that would exceed this
|
||||
# value it will cause an error to be returned. The default is: 4.0 *
|
||||
# nozzle_diameter^2
|
||||
#max_extrude_only_distance: 50.0
|
||||
# Maximum length (in mm of raw filament) that an extrude only move
|
||||
# may be. If an extrude only move requests a distance greater than
|
||||
# this value it will cause an error to be returned. The default is
|
||||
# 50mm.
|
||||
#max_extrude_only_velocity:
|
||||
# Maximum velocity (in mm/s) of the extruder motor for extrude only
|
||||
# moves.
|
||||
max_accel: 3000
|
||||
# moves. If this is not specified then it is calculated to match the
|
||||
# limit an XY printing move with a max_extrude_cross_section
|
||||
# extrusion would have.
|
||||
#max_extrude_only_accel:
|
||||
# Maximum acceleration (in mm/s^2) of the extruder motor for extrude
|
||||
# only moves.
|
||||
#
|
||||
# The remaining variables describe the extruder heater
|
||||
pressure_advance: 0.0
|
||||
# only moves. If this is not specified then it is calculated to
|
||||
# match the limit an XY printing move with a
|
||||
# max_extrude_cross_section extrusion would have.
|
||||
#pressure_advance: 0.0
|
||||
# The amount of raw filament to push into the extruder during
|
||||
# extruder acceleration. An equal amount of filament is retracted
|
||||
# during deceleration. It is measured in millimeters per
|
||||
# millimeter/second
|
||||
# millimeter/second. The default is 0, which disables pressure
|
||||
# advance.
|
||||
#pressure_advance_lookahead_time: 0.010
|
||||
# A time (in seconds) to "look ahead" at future extrusion moves when
|
||||
# calculating pressure advance. This is used to reduce the
|
||||
# application of pressure advance during cornering moves that would
|
||||
# otherwise cause retraction followed immediately by pressure
|
||||
# buildup. This setting only applies if pressure_advance is
|
||||
# non-zero. The default is 0.010 (10 milliseconds).
|
||||
#
|
||||
# The remaining variables describe the extruder heater
|
||||
heater_pin: ar4
|
||||
# PWM output pin controlling the heater
|
||||
thermistor_pin: analog1
|
||||
# Analog input pin connected to thermistor
|
||||
thermistor_type: EPCOS 100K B57560G104F
|
||||
# Type of thermistor (see klippy/heater.py for available types)
|
||||
pullup_resistor: 4700
|
||||
# The resistance (in ohms) of the pullup attached to the thermistor
|
||||
# PWM output pin controlling the heater. This parameter must be
|
||||
# provided.
|
||||
#max_power: 1.0
|
||||
# The maximum power (expressed as a value from 0.0 to 1.0) that the
|
||||
# heater_pin may be set to. The value 1.0 allows the pin to be set
|
||||
# fully enabled for extended periods, while a value of 0.5 would
|
||||
# allow the 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 the heater. The default is 1.0.
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
# Type of sensor - this may be "EPCOS 100K B57560G104F", "ATC
|
||||
# Semitec 104GT-2", or "AD595". This parameter must be provided.
|
||||
sensor_pin: analog1
|
||||
# Analog input pin connected to the sensor. This parameter must be
|
||||
# provided.
|
||||
#pullup_resistor: 4700
|
||||
# The resistance (in ohms) of the pullup attached to the
|
||||
# thermistor. This parameter is only valid when the sensor is a
|
||||
# thermistor. The default is 4700 ohms.
|
||||
#adc_voltage: 5.0
|
||||
# The ADC comparison voltage. This parameter is only valid when the
|
||||
# sensor is an AD595. The default is 5 volts.
|
||||
control: pid
|
||||
# Control algorithm (either pid or watermark)
|
||||
# Control algorithm (either pid or watermark). This parameter must
|
||||
# be provided.
|
||||
pid_Kp: 22.2
|
||||
# Kp is the "proportional" constant for the pid
|
||||
# Kp is the "proportional" constant for the pid. This parameter must
|
||||
# be provided for PID heaters.
|
||||
pid_Ki: 1.08
|
||||
# Ki is the "integral" constant for the pid
|
||||
# Ki is the "integral" constant for the pid. This parameter must be
|
||||
# provided for PID heaters.
|
||||
pid_Kd: 114
|
||||
# Kd is the "derivative" constant for the pid
|
||||
pid_deriv_time: 2.0
|
||||
# Kd is the "derivative" constant for the pid. This parameter must
|
||||
# be provided for PID heaters.
|
||||
#pid_deriv_time: 2.0
|
||||
# A time value (in seconds) over which the derivative in the pid
|
||||
# will be smoothed to reduce the impact of measurement noise
|
||||
pid_integral_max: 255
|
||||
# The maximum "windup" the integral term may accumulate
|
||||
min_extrude_temp: 170
|
||||
# will be smoothed to reduce the impact of measurement noise. The
|
||||
# default is 2 seconds.
|
||||
#pid_integral_max:
|
||||
# The maximum "windup" the integral term may accumulate. The default
|
||||
# is to use the same value as max_power.
|
||||
#min_extrude_temp: 170
|
||||
# The minimum temperature (in Celsius) at which extruder move
|
||||
# commands may be issued
|
||||
# commands may be issued. The default is 170 Celsius.
|
||||
min_temp: 0
|
||||
# Minimum temperature in Celsius (mcu will shutdown if not met)
|
||||
# Minimum temperature in Celsius (mcu will shutdown if not
|
||||
# met). This parameter must be provided.
|
||||
max_temp: 210
|
||||
# Maximum temperature (mcu will shutdown if temperature is above
|
||||
# this value)
|
||||
# this value). This parameter must be provided.
|
||||
|
||||
# The heater_bed section describes a heated bed (if present - omit
|
||||
# section if not present).
|
||||
[heater_bed]
|
||||
heater_pin: ar3
|
||||
thermistor_pin: analog0
|
||||
thermistor_type: EPCOS 100K B57560G104F
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
sensor_pin: analog0
|
||||
control: watermark
|
||||
max_delta: 2.0
|
||||
# The number of degrees in Celsius above the target temperature
|
||||
# before disabling the heater as well as the number of degrees below
|
||||
# the target before re-enabling the heater.
|
||||
#max_delta: 2.0
|
||||
# On 'watermark' controlled heaters this is the number of degrees in
|
||||
# Celsius above the target temperature before disabling the heater
|
||||
# as well as the number of degrees below the target before
|
||||
# re-enabling the heater. The default is 2 degrees Celsius.
|
||||
min_temp: 0
|
||||
max_temp: 110
|
||||
|
||||
# Extruder print fan (omit section if fan not present)
|
||||
[fan]
|
||||
pin: ar14
|
||||
# PWM output pin controlling the heater
|
||||
hard_pwm: 1
|
||||
# PWM output pin controlling the fan. This parameter must be
|
||||
# provided.
|
||||
#hard_pwm: 0
|
||||
# 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 (eg, 1024) to force hardware PWM with the given cycle time
|
||||
# in clock ticks.
|
||||
kick_start_time: 0.100
|
||||
# number to force hardware PWM with the given cycle time in clock
|
||||
# ticks. The default is 0 which enables software PWM with a cycle
|
||||
# time of 10ms.
|
||||
#kick_start_time: 0.100
|
||||
# Time (in seconds) to run the fan at full speed when first enabling
|
||||
# it (helps get the fan spinning)
|
||||
# it (helps get the fan spinning). The default is 0.100 seconds.
|
||||
|
||||
# Micro-controller information
|
||||
[mcu]
|
||||
serial: /dev/ttyACM0
|
||||
# The serial port to connect to the MCU
|
||||
baud: 250000
|
||||
# The baud rate to use
|
||||
# The serial port to connect to the MCU. The default is /dev/ttyS0
|
||||
#baud: 250000
|
||||
# The baud rate to use. The default is 250000.
|
||||
pin_map: arduino
|
||||
# This option may be used to enable Arduino pin name aliases
|
||||
# This option may be used to enable Arduino pin name aliases. The
|
||||
# default is to not enable the aliases.
|
||||
#restart_method: arduino
|
||||
# This controls the mechanism the host will use to reset the
|
||||
# micro-controller. The choices are 'arduino', 'rpi_usb', and
|
||||
# 'command'. The 'arduino' method (toggle DTR; set baud to 1200) is
|
||||
# common on Arduino boards and clones. The 'rpi_usb' method is
|
||||
# useful on Raspberry Pi boards with micro-controllers powered over
|
||||
# USB - it briefly disables power to all USB ports to accomplish a
|
||||
# micro-controller reset. The 'command' method involves sending a
|
||||
# Klipper command to the micro-controller so that it can reset
|
||||
# itself. The default is 'arduino'.
|
||||
custom:
|
||||
# This option may be used to specify a set of custom
|
||||
# micro-controller commands to be sent at the start of the
|
||||
@@ -187,25 +263,34 @@ custom:
|
||||
# The printer section controls high level printer settings
|
||||
[printer]
|
||||
kinematics: cartesian
|
||||
# This option must be "cartesian" for cartesian printers
|
||||
# This option must be "cartesian" for cartesian printers.
|
||||
max_velocity: 500
|
||||
# Maximum velocity (in mm/s) of the toolhead (relative to the
|
||||
# print)
|
||||
# print). This parameter must be specified.
|
||||
max_accel: 3000
|
||||
# Maximum acceleration (in mm/s^2) of the toolhead (relative to the
|
||||
# print)
|
||||
max_z_velocity: 250
|
||||
# print). This parameter must be specified.
|
||||
#max_accel_to_decel:
|
||||
# A pseudo acceleration (in mm/s^2) controlling how fast the
|
||||
# toolhead may go from acceleration to deceleration. It is used to
|
||||
# reduce the top speed of short zig-zag moves (and thus reduce
|
||||
# printer vibration from these moves). The default is half of
|
||||
# max_accel.
|
||||
max_z_velocity: 25
|
||||
# For cartesian printers this sets the maximum velocity (in mm/s) of
|
||||
# movement along the z axis. This setting can be used to restrict
|
||||
# the maximum speed of the z stepper motor on cartesian printers.
|
||||
# the maximum speed of the z stepper motor on cartesian
|
||||
# printers. The default is to use max_velocity for max_z_velocity.
|
||||
max_z_accel: 30
|
||||
# For cartesian printers this sets the maximum acceleration (in
|
||||
# mm/s^2) of movement along the z axis. It limits the acceleration
|
||||
# of the z stepper motor on cartesian printers.
|
||||
motor_off_time: 60
|
||||
# of the z stepper motor on cartesian printers. The default is to
|
||||
# use max_accel for max_z_accel.
|
||||
#motor_off_time: 600
|
||||
# Time (in seconds) of idle time before the printer will try to
|
||||
# disable active motors.
|
||||
junction_deviation: 0.02
|
||||
# disable active motors. The default is 600 seconds.
|
||||
#junction_deviation: 0.02
|
||||
# Distance (in mm) used to control the internal approximated
|
||||
# centripetal velocity cornering algorithm. A larger number will
|
||||
# permit higher "cornering speeds" at the junction of two moves.
|
||||
# permit higher "cornering speeds" at the junction of two moves. The
|
||||
# default is 0.02mm.
|
||||
|
||||
90
config/generic-rambo.cfg
Normal file
@@ -0,0 +1,90 @@
|
||||
# This file contains common pin mappings for 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: .0125
|
||||
endstop_pin: ^PB6
|
||||
homing_speed: 50
|
||||
position_endstop: 0
|
||||
position_max: 200
|
||||
|
||||
[stepper_y]
|
||||
step_pin: PC1
|
||||
dir_pin: !PL0
|
||||
enable_pin: !PA6
|
||||
step_distance: .0125
|
||||
endstop_pin: ^PB5
|
||||
homing_speed: 50
|
||||
position_endstop: 0
|
||||
position_max: 200
|
||||
|
||||
[stepper_z]
|
||||
step_pin: PC2
|
||||
dir_pin: PL2
|
||||
enable_pin: !PA5
|
||||
step_distance: 0.00025
|
||||
endstop_pin: ^PB4
|
||||
homing_speed: 5
|
||||
position_endstop: 0
|
||||
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: PH6
|
||||
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: PE5
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
sensor_pin: PF2
|
||||
control: watermark
|
||||
min_temp: 0
|
||||
max_temp: 130
|
||||
|
||||
[fan]
|
||||
pin: PH5
|
||||
|
||||
[mcu]
|
||||
serial: /dev/ttyACM0
|
||||
custom:
|
||||
# Turn off yellow led
|
||||
set_digital_out pin=PB7 value=0
|
||||
# Stepper micro-step pins
|
||||
set_digital_out pin=PG1 value=1
|
||||
set_digital_out pin=PG0 value=1
|
||||
set_digital_out pin=PK7 value=1
|
||||
set_digital_out pin=PG2 value=1
|
||||
set_digital_out pin=PK6 value=1
|
||||
set_digital_out pin=PK5 value=1
|
||||
set_digital_out pin=PK3 value=1
|
||||
set_digital_out pin=PK4 value=1
|
||||
# Initialize digipot
|
||||
send_spi_message pin=PD7 msg=0487 # X = ~0.75A
|
||||
send_spi_message pin=PD7 msg=0587 # Y = ~0.75A
|
||||
send_spi_message pin=PD7 msg=0387 # Z = ~0.75A
|
||||
send_spi_message pin=PD7 msg=00A5 # E0
|
||||
send_spi_message pin=PD7 msg=017D # E1
|
||||
|
||||
[printer]
|
||||
kinematics: cartesian
|
||||
max_velocity: 300
|
||||
max_accel: 3000
|
||||
max_z_velocity: 5
|
||||
max_z_accel: 100
|
||||
74
config/generic-ramps.cfg
Normal file
@@ -0,0 +1,74 @@
|
||||
# This file contains common pin mappings for RAMPS (v1.3 and later)
|
||||
# boards. RAMPS boards typically use a firmware compiled for the AVR
|
||||
# atmega2560 (though other AVR chips are also possible).
|
||||
|
||||
# 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
|
||||
homing_speed: 50
|
||||
position_endstop: 0
|
||||
position_max: 200
|
||||
|
||||
[stepper_y]
|
||||
step_pin: ar60
|
||||
dir_pin: !ar61
|
||||
enable_pin: !ar56
|
||||
step_distance: .0125
|
||||
endstop_pin: ^ar14
|
||||
homing_speed: 50
|
||||
position_endstop: 0
|
||||
position_max: 200
|
||||
|
||||
[stepper_z]
|
||||
step_pin: ar46
|
||||
dir_pin: ar48
|
||||
enable_pin: !ar62
|
||||
step_distance: 0.00025
|
||||
endstop_pin: ^ar18
|
||||
homing_speed: 5
|
||||
position_endstop: 0
|
||||
position_max: 200
|
||||
|
||||
[extruder]
|
||||
step_pin: ar26
|
||||
dir_pin: ar28
|
||||
enable_pin: !ar24
|
||||
step_distance: .002
|
||||
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: watermark
|
||||
min_temp: 0
|
||||
max_temp: 130
|
||||
|
||||
[fan]
|
||||
pin: ar9
|
||||
|
||||
[mcu]
|
||||
serial: /dev/ttyACM0
|
||||
pin_map: arduino
|
||||
|
||||
[printer]
|
||||
kinematics: cartesian
|
||||
max_velocity: 300
|
||||
max_accel: 3000
|
||||
max_z_velocity: 5
|
||||
max_z_accel: 100
|
||||
@@ -48,12 +48,12 @@ step_pin: PC3
|
||||
dir_pin: PL6
|
||||
enable_pin: !PA4
|
||||
step_distance: .004242
|
||||
max_velocity: 200000
|
||||
max_accel: 3000
|
||||
nozzle_diameter: 0.350
|
||||
filament_diameter: 1.750
|
||||
pressure_advance: 0.07
|
||||
heater_pin: PH6
|
||||
thermistor_pin: PF0
|
||||
thermistor_type: EPCOS 100K B57560G104F
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
sensor_pin: PF0
|
||||
control: pid
|
||||
pid_Kp: 7.0
|
||||
pid_Ki: 0.1
|
||||
@@ -63,19 +63,17 @@ max_temp: 210
|
||||
|
||||
[heater_bed]
|
||||
heater_pin: PE5
|
||||
thermistor_pin: PF2
|
||||
thermistor_type: EPCOS 100K B57560G104F
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
sensor_pin: PF2
|
||||
control: watermark
|
||||
min_temp: 0
|
||||
max_temp: 100
|
||||
|
||||
[fan]
|
||||
pin: PH5
|
||||
hard_pwm: 1
|
||||
|
||||
[mcu]
|
||||
serial: /dev/ttyACM0
|
||||
baud: 250000
|
||||
custom:
|
||||
# Nozzle fan
|
||||
set_pwm_out pin=PH3 cycle_ticks=1 value=155
|
||||
@@ -101,6 +99,5 @@ custom:
|
||||
kinematics: cartesian
|
||||
max_velocity: 500
|
||||
max_accel: 3000
|
||||
max_z_velocity: 250
|
||||
max_z_velocity: 25
|
||||
max_z_accel: 30
|
||||
motor_off_time: 600
|
||||
|
||||
@@ -17,7 +17,7 @@ host architectures. The build arranges for includes of
|
||||
src/generic/somefile.h).
|
||||
|
||||
The **klippy/** directory contains the C and Python source for the
|
||||
host part of the firmware.
|
||||
host part of the software.
|
||||
|
||||
The **lib/** directory contains external 3rd-party library code that
|
||||
is necessary to build some targets.
|
||||
@@ -54,13 +54,13 @@ functions should never pause, delay, or do any work that lasts more
|
||||
than a few micro-seconds. These functions schedule work at specific
|
||||
times by scheduling timers.
|
||||
|
||||
Timer functions are scheduled by calling sched_timer() (located in
|
||||
Timer functions are scheduled by calling sched_add_timer() (located in
|
||||
**src/sched.c**). The scheduler code will arrange for the given
|
||||
function to be called at the requested clock time. Timer interrupts
|
||||
are initially handled in an architecture specific interrupt handler
|
||||
(eg, **src/avr/timer.c**), but this just calls sched_timer_kick()
|
||||
located in **src/sched.c**. The timer interrupt leads to execution of
|
||||
schedule timer functions. Timer functions always run with interrupts
|
||||
(eg, **src/avr/timer.c**) which calls sched_timer_dispatch() located
|
||||
in **src/sched.c**. The timer interrupt leads to execution of schedule
|
||||
timer functions. Timer functions always run with interrupts
|
||||
disabled. The timer functions should always complete within a few
|
||||
micro-seconds. At completion of the timer event, the function may
|
||||
choose to reschedule itself.
|
||||
@@ -92,8 +92,8 @@ some functionality in C code.
|
||||
Initial execution starts in **klippy/klippy.py**. This reads the
|
||||
command-line arguments, opens the printer config file, instantiates
|
||||
the main printer objects, and starts the serial connection. The main
|
||||
execution of gcode commands is in the process_commands() method in
|
||||
**klippy/gcode.py**. This code translates the gcode commands into
|
||||
execution of G-code commands is in the process_commands() method in
|
||||
**klippy/gcode.py**. This code translates the G-code commands into
|
||||
printer object calls, which frequently translate the actions to
|
||||
commands to be executed on the micro-controller (as declared via the
|
||||
DECL_COMMAND macro in the micro-controller code).
|
||||
@@ -106,3 +106,106 @@ messages from the micro-controller in the Python code (see
|
||||
**klippy/serialhdl.py**). The fourth thread writes debug messages to
|
||||
the log (see **klippy/queuelogger.py**) so that the other threads
|
||||
never block on log writes.
|
||||
|
||||
Code flow of a move command
|
||||
===========================
|
||||
|
||||
A typical printer movement starts when a "G1" command is sent to the
|
||||
Klippy host and it completes when the corresponding step pulses are
|
||||
produced on the micro-controller. This section outlines the code flow
|
||||
of a typical move command. The [kinematics](Kinematics.md) document
|
||||
provides further information on the mechanics of moves.
|
||||
|
||||
* Processing for a move command starts in gcode.py. The goal of
|
||||
gcode.py is to translate G-code into internal calls. Changes in
|
||||
origin (eg, G92), changes in relative vs absolute positions (eg,
|
||||
G90), and unit changes (eg, F6000=100mm/s) are handled here. The
|
||||
code path for a move is: `process_data() -> process_commands() ->
|
||||
cmd_G1()`. Ultimately the ToolHead class is invoked to execute the
|
||||
actual request: `cmd_G1() -> ToolHead.move()`
|
||||
|
||||
* The ToolHead class (in toolhead.py) handles "look-ahead" and tracks
|
||||
the timing of printing actions. The codepath for a move is:
|
||||
`ToolHead.move() -> MoveQueue.add_move() -> MoveQueue.flush() ->
|
||||
Move.set_junction() -> Move.move()`.
|
||||
* ToolHead.move() creates a Move() object with the parameters of the
|
||||
move (in cartesian space and in units of seconds and millimeters).
|
||||
* MoveQueue.add_move() places the move object on the "look-ahead"
|
||||
queue.
|
||||
* MoveQueue.flush() determines the start and end velocities of each
|
||||
move.
|
||||
* Move.set_junction() implements the "trapezoid generator" on a
|
||||
move. The "trapezoid generator" breaks every move into three parts:
|
||||
a constant acceleration phase, followed by a constant velocity
|
||||
phase, followed by a constant deceleration phase. Every move
|
||||
contains these three phases in this order, but some phases may be of
|
||||
zero duration.
|
||||
* When Move.move() is called, everything about the move is known -
|
||||
its start location, its end location, its acceleration, its
|
||||
start/crusing/end velocity, and distance traveled during
|
||||
acceleration/cruising/deceleration. All the information is stored in
|
||||
the Move() class and is in cartesian space in units of millimeters
|
||||
and seconds. Times are stored relative to the start of the print.
|
||||
|
||||
The move is then handed off to the kinematics classes: `Move.move()
|
||||
-> kin.move()`
|
||||
|
||||
* The goal of the kinematics classes is to translate the movement in
|
||||
cartesian space to movement on each stepper. The kinematics classes
|
||||
are in cartesian.py, corexy.py, delta.py, and extruder.py. The
|
||||
kinematic class is given a chance to audit the move
|
||||
(`ToolHead.move() -> kin.check_move()`) before it goes on the
|
||||
look-ahead queue, but once the move arrives in *kin*.move() the
|
||||
kinematic class is required to handle the move as specified. The
|
||||
kinematic classes translate the three parts of each move
|
||||
(acceleration, constant "cruising" velocity, and deceleration) to
|
||||
the associated movement on each stepper. Note that the extruder is
|
||||
handled in its own kinematic class. Since the Move() class specifies
|
||||
the exact movement time and since step pulses are sent to the
|
||||
micro-controller with specific timing, stepper movements produced by
|
||||
the extruder class will be in sync with head movement even though
|
||||
the code is kept separate.
|
||||
|
||||
* For efficiency reasons, the stepper pulse times are generated in C
|
||||
code. The code flow is: `kin.move() -> MCU_Stepper.step_const() ->
|
||||
stepcompress_push_const()`, or for delta kinematics:
|
||||
`DeltaKinematics.move() -> MCU_Stepper.step_delta() ->
|
||||
stepcompress_push_delta()`. The MCU_Stepper code just performs unit
|
||||
and axis transformation (seconds to clock ticks and millimeters to
|
||||
step distances), and calls the C code. The C code calculates the
|
||||
stepper step times for each movement and fills an array (struct
|
||||
stepcompress.queue) with the corresponding micro-controller clock
|
||||
counter times (in 64bit integers) for every step. Here the
|
||||
"micro-controller clock counter" value directly corresponds to the
|
||||
micro-controller's hardware counter - it is relative to when the
|
||||
micro-controller was last powered up.
|
||||
|
||||
* The next major step is to compress the steps: `stepcompress_flush()
|
||||
-> compress_bisect_add()` (in stepcompress.c). This code generates
|
||||
and encodes a series of micro-controller "queue_step" commands that
|
||||
correspond to the list of stepper step times built in the previous
|
||||
stage. These "queue_step" commands are then queued, prioritized, and
|
||||
sent to the micro-controller (via stepcompress.c:steppersync and
|
||||
serialqueue.c:serialqueue).
|
||||
|
||||
* Processing of the queue_step commands on the micro-controller starts
|
||||
in command.c which parses the command and calls
|
||||
`command_queue_step()`. The command_queue_step() code (in stepper.c)
|
||||
just appends the parameters of each queue_step command to a per
|
||||
stepper queue. Under normal operation the queue_step command is
|
||||
parsed and queued at least 100ms before the time of its first
|
||||
step. Finally, the generation of stepper events is done in
|
||||
`stepper_event()`. It's called from the hardware timer interrupt at
|
||||
the scheduled time of the first step. The stepper_event() code
|
||||
generates a step pulse and then reschedules itself to run at the
|
||||
time of the next step pulse for the given queue_step parameters. The
|
||||
parameters for each queue_step command are "interval", "count", and
|
||||
"add". At a high-level, stepper_event() runs the following, 'count'
|
||||
times: `do_step(); next_wake_time = last_wake_time + interval;
|
||||
interval += add;`
|
||||
|
||||
The above may seem like a lot of complexity to execute a
|
||||
movement. However, the only really interesting parts are in the
|
||||
ToolHead and kinematic classes. It's this part of the code which
|
||||
specifies the movements and their timings. The remaining parts of the
|
||||
processing is mostly just communication and plumbing.
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
The Klippy host code has some tools to help in debugging the firmware.
|
||||
The Klippy host code has some tools to help in debugging.
|
||||
|
||||
Translating gcode files to firmware commands
|
||||
============================================
|
||||
Translating gcode files to micro-controller commands
|
||||
====================================================
|
||||
|
||||
The Klippy host code can run in a batch mode to produce the low-level
|
||||
firmware commands associated with a gcode file. Inspecting these
|
||||
low-level firmware commands is useful when trying to understand the
|
||||
micro-controller commands associated with a gcode file. Inspecting
|
||||
these low-level commands is useful when trying to understand the
|
||||
actions of the low-level hardware. It can also be useful to compare
|
||||
the difference in firmware commands after a code change.
|
||||
the difference in micro-controller commands after a code change.
|
||||
|
||||
To run Klippy in this batch mode, there is a one time step necessary
|
||||
to generate the firmware "data dictionary". This is done by compiling
|
||||
the firmware code to obtain the **out/klipper.dict** file:
|
||||
to generate the micro-controller "data dictionary". This is done by
|
||||
compiling the micro-controller code to obtain the **out/klipper.dict**
|
||||
file:
|
||||
|
||||
```
|
||||
make menuconfig
|
||||
@@ -34,13 +35,13 @@ output. This output can be translated to readable text with:
|
||||
```
|
||||
|
||||
The resulting file **test.txt** contains a human readable list of
|
||||
firmware commands.
|
||||
micro-controller commands.
|
||||
|
||||
The batch mode disables certain response / request commands in order
|
||||
to function. As a result, there will be some differences between
|
||||
actual firmware commands and the above output. The generated data is
|
||||
useful for testing and inspection; it is not useful for sending to a
|
||||
real micro-controller.
|
||||
actual commands and the above output. The generated data is useful for
|
||||
testing and inspection; it is not useful for sending to a real
|
||||
micro-controller.
|
||||
|
||||
Testing with simulavr
|
||||
=====================
|
||||
@@ -74,25 +75,20 @@ cd /patch/to/klipper
|
||||
make menuconfig
|
||||
```
|
||||
|
||||
and compile the firmware for an AVR atmega644p, disable the AVR
|
||||
watchdog timer, and set the MCU frequency to 20000000. Then one can
|
||||
compile Klipper (run `make`) and then start the simulation with:
|
||||
and compile the micro-controller software for an AVR atmega644p,
|
||||
disable the AVR watchdog timer, and set the MCU frequency
|
||||
to 20000000. Then one can compile Klipper (run `make`) and then start
|
||||
the simulation with:
|
||||
|
||||
```
|
||||
PYTHONPATH=/path/to/simulavr/src/python/ ./scripts/avrsim.py -m atmega644 -s 20000000 -b 250000 out/klipper.elf
|
||||
```
|
||||
|
||||
It may be necessary to create a python virtual environment to run
|
||||
Klippy on the target machine. To do so, run:
|
||||
|
||||
```
|
||||
virtualenv ~/klippy-env
|
||||
~/klippy-env/bin/pip install cffi==1.6.0 pyserial==2.7
|
||||
```
|
||||
|
||||
Then, with simulavr running in another window, one can run the
|
||||
following to read gcode from a file (eg, "test.gcode"), process it
|
||||
with Klippy, and send it to Klipper running in simulavr:
|
||||
with Klippy, and send it to Klipper running in simulavr (see
|
||||
[installation](Installation.md) for the steps necessary to build the
|
||||
python virtual environment):
|
||||
|
||||
```
|
||||
~/klippy-env/bin/python ./klippy/klippy.py config/avrsim.cfg -i test.gcode -v
|
||||
@@ -129,3 +125,26 @@ Klipper source code). To do so, run:
|
||||
```
|
||||
~/klippy-env/bin/python ./klippy/console.py /tmp/pseudoserial 250000
|
||||
```
|
||||
|
||||
Generating load graphs
|
||||
======================
|
||||
|
||||
The Klippy log file (/tmp/klippy.log) stores statistics on bandwidth,
|
||||
micro-controller load, and host buffer load. It can be useful to graph
|
||||
these statistics after a print.
|
||||
|
||||
To generate a graph, a one time step is necessary to install the
|
||||
"matplotlib" package:
|
||||
|
||||
```
|
||||
sudo apt-get update
|
||||
sudo apt-get install python-matplotlib
|
||||
```
|
||||
|
||||
Then graphs can be produced with:
|
||||
|
||||
```
|
||||
~/klipper/scripts/graphstats.py /tmp/klippy.log loadgraph.png
|
||||
```
|
||||
|
||||
One can then view the resulting **loadgraph.png** file.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
Klipper is an experimental 3d printer firmware. It has several
|
||||
compelling features:
|
||||
Klipper has several compelling features:
|
||||
|
||||
* High precision stepper movement. Klipper utilizes an application
|
||||
processor (such as a low-cost Raspberry Pi) when calculating printer
|
||||
@@ -7,17 +6,16 @@ compelling features:
|
||||
stepper motor, it compresses those events, transmits them to the
|
||||
micro-controller, and then the micro-controller executes each event
|
||||
at the requested time. Each stepper event is scheduled with a
|
||||
precision of no less than 50 micro-seconds. The software does not
|
||||
use kinematic estimations (such as the Bresenham algorithm) -
|
||||
instead it calculates precise step times based on the physics of
|
||||
acceleration and the physics of the machine kinematics. More precise
|
||||
stepper movement translates to quieter and more stable printer
|
||||
operation.
|
||||
precision of 25 micro-seconds or better. The software does not use
|
||||
kinematic estimations (such as the Bresenham algorithm) - instead it
|
||||
calculates precise step times based on the physics of acceleration
|
||||
and the physics of the machine kinematics. More precise stepper
|
||||
movement translates to quieter and more stable printer operation.
|
||||
|
||||
* Best in class performance. Klipper is able to achieve high stepping
|
||||
rates on both new and old micro-controllers. Even an old 8bit AVR
|
||||
micro-controller can obtain rates up to 150K steps per second. On
|
||||
more recent ARM micro-controllers, rates over 350K steps per second
|
||||
micro-controller can obtain rates over 175K steps per second. On
|
||||
more recent ARM micro-controllers, rates over 450K steps per second
|
||||
are possible. Higher stepper rates enable higher print
|
||||
velocities. The stepper event timing remains precise even at high
|
||||
speeds which improves overall stability.
|
||||
@@ -34,16 +32,21 @@ compelling features:
|
||||
micro-controller architectures as well.
|
||||
|
||||
* Simpler code. Klipper uses a very high level language (Python) for
|
||||
most code. The kinematics algorithms, the gcode parsing, the heating
|
||||
and thermistor algorithms, etc. are all written in Python. This
|
||||
makes it easier to develop new functionality.
|
||||
most code. The kinematics algorithms, the G-code parsing, the
|
||||
heating and thermistor algorithms, etc. are all written in
|
||||
Python. This makes it easier to develop new functionality.
|
||||
|
||||
* Advanced features. Klipper implements the "pressure advance"
|
||||
algorithm for extruders. When properly tuned, pressure advance
|
||||
reduces extruder ooze. Klipper also implements a novel "stepper
|
||||
phase endstop" algorithm that can dramatically improve the accuracy
|
||||
of typical endstop switches. When properly tuned it can improve a
|
||||
print's first layer bed adhesion.
|
||||
* Advanced features:
|
||||
* Klipper implements the "pressure advance" algorithm for
|
||||
extruders. When properly tuned, pressure advance reduces extruder
|
||||
ooze.
|
||||
* Klipper also implements a novel "stepper phase endstop" algorithm
|
||||
that can dramatically improve the accuracy of typical endstop
|
||||
switches. When properly tuned it can improve a print's first layer
|
||||
bed adhesion.
|
||||
* Support for limiting the top speed of short "zigzag" moves to
|
||||
reduce printer vibration and noise. See the
|
||||
[kinematics](Kinematics.md) document for more information.
|
||||
|
||||
To get started with Klipper, read the [installation](Installation.md)
|
||||
guide.
|
||||
@@ -65,12 +68,12 @@ Klipper supports many standard 3d printer features:
|
||||
gradually accelerate from standstill to cruising speed and then
|
||||
decelerate back to a standstill.
|
||||
|
||||
* "Lookahead" support. The incoming stream of G-Code movement commands
|
||||
are queued and analyzed - the acceleration between movements in a
|
||||
similar direction will be optimized to reduce print stalls and
|
||||
improve overall print time.
|
||||
* "Look-ahead" support. The incoming stream of G-Code movement
|
||||
commands are queued and analyzed - the acceleration between
|
||||
movements in a similar direction will be optimized to reduce print
|
||||
stalls and improve overall print time.
|
||||
|
||||
* Support for both delta printers and cartesian style printers.
|
||||
* Support for cartesian, delta, and corexy style printers.
|
||||
|
||||
Step Benchmarks
|
||||
===============
|
||||
@@ -80,6 +83,6 @@ represent total number of steps per second on the micro-controller.
|
||||
|
||||
| Micro-controller | 1 stepper active | 3 steppers active |
|
||||
| ----------------- | ---------------- | ----------------- |
|
||||
| 20Mhz AVR | 158.7K | 103K |
|
||||
| 16Mhz AVR | 126.9K | 82K |
|
||||
| Arduino Due (ARM) | 352.9K | 288K |
|
||||
| 20Mhz AVR | 177K | 117K |
|
||||
| 16Mhz AVR | 140K | 93K |
|
||||
| Arduino Due (ARM) | 462K | 406K |
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
This document provides high-level information on common firmware
|
||||
commands. It is not an authoritative reference for these commands, nor
|
||||
is it an exclusive list of all available firmware commands.
|
||||
|
||||
This document may be useful for users needing to configure a set of
|
||||
hardware actions that their printer may require at startup (via the
|
||||
"custom" field in the printer config file), and it may be useful for
|
||||
developers wishing to obtain a high-level feel for available firmware
|
||||
commands.
|
||||
|
||||
See the [protocol](Protocol.md) document for more information on the
|
||||
format of commands and their low-level transmission. The commands here
|
||||
are described using their "printf" style syntax - for those unfamiliar
|
||||
with that format, just note that where a '%...' sequence is seen it
|
||||
should be replaced with an actual integer.
|
||||
|
||||
Startup Commands
|
||||
================
|
||||
|
||||
It may be necessary to take certain one-time actions to configure the
|
||||
micro-controller and its peripherals. This section lists common
|
||||
commands available for that purpose. Unlike other firmware commands,
|
||||
these commands run as soon as they are received by the firmware and
|
||||
they do not require any particular setup.
|
||||
|
||||
These commands are most useful in the "custom" block of the "mcu"
|
||||
section of the printer configuration file. This feature is typically
|
||||
used to configure the initial settings of LEDs, to configure
|
||||
micro-stepping pins, to configure a digipot, etc.
|
||||
|
||||
Several of these commands will take a "pin=%u" parameter. The
|
||||
low-level firmware uses integer encodings of the hardware pin numbers,
|
||||
but to make things more readable the host will translate human
|
||||
readable pin names (eg, "PA3") to their equivalent integer
|
||||
encodings. By convention, any parameter named "pin" or that has a
|
||||
"_pin" suffix will use pin name translation by the host.
|
||||
|
||||
Common startup commands:
|
||||
|
||||
* set_digital_out pin=%u value=%c : This command immediately
|
||||
configures the given pin as a digital out GPIO and it sets it to
|
||||
either a low level (value=0) or a high level (value=1). This command
|
||||
may be useful for configuring the initial value of LEDs and for
|
||||
configuring the initial value of stepper driver micro-stepping pins.
|
||||
|
||||
* set_pwm_out pin=%u cycle_ticks=%u value=%c : This command will
|
||||
immediately configure the given pin to use hardware based
|
||||
pulse-width-modulation (PWM) with the given number of
|
||||
cycle_ticks. The "cycle_ticks" is the number of MCU clock ticks each
|
||||
power on and power off cycle should last. A cycle_ticks value of 1
|
||||
can be used to request the fastest possible cycle time. The "value"
|
||||
parameter is between 0 and 255 with 0 indicating a full off state
|
||||
and 255 indicating a full on state. This command may be useful for
|
||||
enabling CPU and nozzle cooling fans.
|
||||
|
||||
* send_spi_message pin=%u msg=%*s : This command can be used to
|
||||
transmit messages to a serial-peripheral-interface (SPI) component
|
||||
connected to the micro-controller. It has been used to configure the
|
||||
startup settings of AD5206 digipots. The 'pin' parameter specifies
|
||||
the chip select line to use during the transmission. The 'msg'
|
||||
indicates the binary message to transmit to the given chip.
|
||||
|
||||
Firmware configuration
|
||||
======================
|
||||
|
||||
Most commands in the firmware require an initial setup before they can
|
||||
be successfully invoked. This section provides a high-level overview
|
||||
of the micro-controller configuration process. This section and the
|
||||
following sections are likely only of interest to developers
|
||||
interested in the internal details of Klipper.
|
||||
|
||||
When the host first connects to the firmware it always starts by
|
||||
obtaining the firmware's data dictionary (see [protocol](Protocol.md)
|
||||
for more information). After the data dictionary is obtained the host
|
||||
will check if the firmware is in a "configured" state and configure it
|
||||
if not. Configuration involves the following phases:
|
||||
|
||||
* get_config : The host starts by checking if the firmware is already
|
||||
configured. The firmware responds to this command with a "config"
|
||||
response message. At micro-controller power-on the firmware always
|
||||
starts in an unconfigured state. It remains in this state until the
|
||||
host completes the configuration processes (by issuing a
|
||||
finalize_config command). If the firmware is already configured (and
|
||||
is configured with the desired settings) from a previous
|
||||
host/firmware session then no further action is needed by the host
|
||||
and the configuration process ends successfully.
|
||||
|
||||
* allocate_oids count=%c : This command is issued to inform the
|
||||
firmware the maximum number of object-ids (oid) that the host
|
||||
requires. It is only valid to issue this command once. An oid is an
|
||||
integer identifier allocated to each stepper, each endstop, and each
|
||||
schedulable gpio pin. The host determines in advance the number of
|
||||
oids it will require to operate the hardware and passes this to the
|
||||
firmware so that the firmware may allocate sufficient memory to
|
||||
store a mapping from oid to internal firmware object.
|
||||
|
||||
* config_XXX oid=%c ... : By convention any command starting with the
|
||||
"config_" prefix creates a new firmware object and assigns the given
|
||||
oid to it. For example, the config_digital_out command will
|
||||
configure the specified pin as a digital output GPIO and create an
|
||||
internal object that the host can use to schedule changes to the
|
||||
given GPIO. The oid parameter passed into the config command is
|
||||
selected by the host and must be between zero and the maximum count
|
||||
supplied in the allocate_oids command. The config commands may only
|
||||
be run when the firmware is not in a configured state (ie, prior to
|
||||
the host sending finalize_config) and after the allocate_oids
|
||||
command has been sent.
|
||||
|
||||
* finalize_config crc=%u : The finalize_config command transitions the
|
||||
firmware from an unconfigured state to a configured state. The crc
|
||||
parameter passed to the firmware is stored in the firmware and
|
||||
provided back to the host in "config" response messages. By
|
||||
convention, the host takes a 32bit CRC of the firmware configuration
|
||||
it will request and at the start of subsequent host/firmware
|
||||
communication sessions it checks that the CRC stored in the firmware
|
||||
exactly matches its desired CRC. If the CRC does not match then the
|
||||
host knows the firmware has not been configured in the state desired
|
||||
by the host.
|
||||
|
||||
Common firmware objects
|
||||
-----------------------
|
||||
|
||||
This section lists some commonly used config commands.
|
||||
|
||||
* config_digital_out oid=%c pin=%u default_value=%c max_duration=%u :
|
||||
This command creates an internal firmware object for the given GPIO
|
||||
'pin'. The pin will be configured in digital output mode and set to
|
||||
an initial value as specified by 'default_value' (0 for low, 1 for
|
||||
high). Creating a digital_out object allows the host to schedule
|
||||
GPIO updates for the given pin at specified times (see the
|
||||
schedule_digital_out command described below). Should the firmware
|
||||
go into shutdown mode then all configured digital_out objects will
|
||||
be set back to their default values. The 'max_duration' parameter is
|
||||
used to implement a safety check - if it is non-zero then it is the
|
||||
maximum number of clock ticks that the host may set the given GPIO
|
||||
to a non-default value without further updates. For example, if the
|
||||
default_value is zero and the max_duration is 16000 then if the host
|
||||
sets the gpio to a value of one then it must schedule another update
|
||||
to the gpio pin (to either zero or one) within 16000 clock
|
||||
ticks. This safety feature can be used with heater pins to ensure
|
||||
the host does not set the heater to a value of one and then go
|
||||
off-line.
|
||||
|
||||
* config_pwm_out oid=%c pin=%u cycle_ticks=%u default_value=%c
|
||||
max_duration=%u : This command creates an internal object for
|
||||
hardware based PWM pins that the host may schedule updates for. Its
|
||||
usage is analogous to config_digital_out - see the description of
|
||||
the 'set_pwm_out' and 'config_digital_out' commands for parameter
|
||||
description.
|
||||
|
||||
* config_soft_pwm_out oid=%c pin=%u cycle_ticks=%u default_value=%c
|
||||
max_duration=%u : This command creates an internal firmware object
|
||||
for software implemented PWM. Unlike hardware pwm pins, a software
|
||||
pwm object does not require any special hardware support (other than
|
||||
the ability to configure the pin as a digital output GPIO). Because
|
||||
the output switching is implemented in the software of the firmware,
|
||||
it is recommended that the cycle_ticks parameter correspond to a
|
||||
time of 10ms or greater. See the description of the 'set_pwm_out'
|
||||
and 'config_digital_out' commands for parameter description.
|
||||
|
||||
* config_analog_in oid=%c pin=%u : This command is used to configure a
|
||||
pin in analog input sampling mode. Once configured, the pin can be
|
||||
sampled at regular interval using the query_analog_in command (see
|
||||
below).
|
||||
|
||||
* config_stepper oid=%c step_pin=%c dir_pin=%c min_stop_interval=%u
|
||||
invert_step=%c : This command creates an internal stepper
|
||||
object. The 'step_pin' and 'dir_pin' parameters specify the step and
|
||||
direction pins respectively; this command will configure them in
|
||||
digital output mode. The 'invert_step' parameter specifies whether a
|
||||
step occurs on a rising edge (invert_step=0) or falling edge
|
||||
(invert_step=1). The 'min_stop_interval' implements a safety
|
||||
feature - it is checked when the firmware finishes all moves for a
|
||||
stepper - if it is non-zero it specifies the minimum number of clock
|
||||
ticks since the last step. It is used as a check on the maximum
|
||||
stepper velocity that a stepper may have before stopping.
|
||||
|
||||
* config_end_stop oid=%c pin=%c pull_up=%c stepper_oid=%c : This
|
||||
command creates an internal "endstop" object. It is used to specify
|
||||
the endstop pins and to enable "homing" operations (see the
|
||||
end_stop_home command below). The command will configure the
|
||||
specified pin in digital input mode. The 'pull_up' parameter
|
||||
determines whether hardware provided pullup resistors for the pin
|
||||
(if available) will be enabled. The 'stepper_oid' parameter
|
||||
specifies the oid of an associated stepper for the given endstop -
|
||||
it is used during homing operations.
|
||||
|
||||
Common commands
|
||||
===============
|
||||
|
||||
This section lists some commonly used run-time commands. It is likely
|
||||
only of interest to developers looking to gain insight into Klippy.
|
||||
|
||||
* schedule_digital_out oid=%c clock=%u value=%c : This command will
|
||||
schedule a change to a digital output GPIO pin at the given clock
|
||||
time. To use this command a 'config_digital_out' command with the
|
||||
same 'oid' parameter must have been issued during firmware
|
||||
configuration.
|
||||
|
||||
* schedule_pwm_out oid=%c clock=%u value=%c : Schedules a change to a
|
||||
hardware PWM output pin. See the 'schedule_digital_out' and
|
||||
'config_pwm_out' commands for more info.
|
||||
|
||||
* schedule_soft_pwm_out oid=%c clock=%u value=%c : Schedules a change
|
||||
to a software PWM output pin. See the 'schedule_digital_out' and
|
||||
'config_soft_pwm_out' commands for more info.
|
||||
|
||||
* query_analog_in oid=%c clock=%u sample_ticks=%u sample_count=%c
|
||||
rest_ticks=%u min_value=%hu max_value=%hu : This command sets up a
|
||||
recurring schedule of analog input samples. To use this command a
|
||||
'config_analog_in' command with the same 'oid' parameter must have
|
||||
been issued during firmware configuration. The samples will start as
|
||||
of 'clock' time, it will report on the obtained value every
|
||||
'rest_ticks' clock ticks, it will over-sample 'sample_count' number
|
||||
of times, and it will pause 'sample_ticks' number of clock ticks
|
||||
between over-sample samples. The 'min_value' and 'max_value'
|
||||
parameters implement a safety feature - the firmware will verify the
|
||||
sampled value (after any oversampling) is always between the
|
||||
supplied range. This is intended for use with pins attached to
|
||||
thermistors controlling heaters - it can be used to check that a
|
||||
heater is within a temperature range.
|
||||
|
||||
* get_status : This command causes the firmware to generate a "status"
|
||||
response message. The host sends this command once a second to
|
||||
obtain the value of the micro-controller clock and to estimate the
|
||||
drift between host and micro-controller clocks. It enables the host
|
||||
to accurately estimate the micro-controller clock.
|
||||
|
||||
Stepper commands
|
||||
----------------
|
||||
|
||||
* queue_step oid=%c interval=%u count=%hu add=%hi : This command
|
||||
schedules 'count' number of steps for the given stepper, with
|
||||
'interval' number of clock ticks between each step. The first step
|
||||
will be 'interval' number of clock ticks since the last scheduled
|
||||
step for the given stepper. If 'add' is non-zero then the interval
|
||||
will be adjusted by 'add' amount after each step. This command
|
||||
appends the given interval/count/add sequence to a per-stepper
|
||||
queue. There may be hundreds of these sequences queued during normal
|
||||
operation. New sequence are appended to the end of the queue and as
|
||||
each sequence completes its 'count' number of steps it is popped
|
||||
from the front of the queue. This system allows the firmware to
|
||||
queue potentially hundreds of thousands of steps - all with reliable
|
||||
and predictable schedule times.
|
||||
|
||||
* set_next_step_dir oid=%c dir=%c : This command specifies the value
|
||||
of the dir_pin that the next queue_step command will use.
|
||||
|
||||
* reset_step_clock oid=%c clock=%u : Normally, step timing is relative
|
||||
to the last step for a given stepper. This command resets the clock
|
||||
so that the next step is relative to the supplied 'clock' time. The
|
||||
host usually only sends this command at the start of a print.
|
||||
|
||||
* end_stop_home oid=%c clock=%u rest_ticks=%u pin_value=%c : This
|
||||
command is used during stepper "homing" operations. To use this
|
||||
command a 'config_end_stop' command with the same 'oid' parameter
|
||||
must have been issued during firmware configuration. When invoked,
|
||||
the firmware will sample the endstop pin every 'rest_ticks' clock
|
||||
ticks and check if it has a value equal to 'pin_value'. If the value
|
||||
matches then the movement queue for the associated stepper will be
|
||||
cleared and the stepper will come to an immediate halt. The host
|
||||
uses this command to implement homing - the host instructs the
|
||||
endstop to sample for the endstop trigger and then it issues a
|
||||
series of queue_step commands to the stepper to move it towards the
|
||||
endstop. Once the stepper hits the endstop, the trigger will be
|
||||
detected, the movement halted, and the host notified.
|
||||
|
||||
### Move queue
|
||||
|
||||
Each queue_step command utilizes an entry in the firmware "move
|
||||
queue". The firmware allocates this queue when it receives the
|
||||
"finalize_config" command, and it reports the number of available
|
||||
queue entries in "config" response messages.
|
||||
|
||||
It is the responsibility of the host to ensure that there is available
|
||||
space in the queue before sending a queue_step command. The host does
|
||||
this by calculating when each queue_step command completes and
|
||||
scheduling new queue_step commands accordingly.
|
||||
@@ -1,12 +1,15 @@
|
||||
Klipper is currently in an experimental state. These instructions
|
||||
assume the software will run on a Raspberry Pi computer in conjunction
|
||||
with OctoPrint. Klipper supports Atmel ATmega based micro-controllers
|
||||
and Arduino Due (Atmel SAM3x8e ARM micro-controllers) printers.
|
||||
These instructions assume the software will run on a Raspberry Pi
|
||||
computer in conjunction with OctoPrint. It is recommended that a
|
||||
Raspberry Pi 2 or Raspberry Pi 3 computer be used as the host
|
||||
machine.
|
||||
|
||||
It is recommended that a Raspberry Pi 2 or Raspberry Pi 3 computer be
|
||||
used as the host. The software will run on a first generation
|
||||
Raspberry Pi, but the combined load of OctoPrint, Klipper, and a web
|
||||
cam (if applicable) can overwhelm its CPU leading to print stalls.
|
||||
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 and
|
||||
Arduino Due (Atmel SAM3x8e ARM micro-controller) printers.
|
||||
|
||||
Prepping an OS image
|
||||
====================
|
||||
@@ -16,40 +19,29 @@ Raspberry Pi computer. Use OctoPi v0.13.0 or later - see the
|
||||
[octopi releases](https://github.com/guysoft/OctoPi/releases) for
|
||||
release information. One should verify that OctoPi boots and that the
|
||||
OctoPrint web server works. After connecting to the OctoPrint web
|
||||
page, follow the prompt to upgrade OctoPrint to v1.3.0 or later.
|
||||
page, follow the prompt to upgrade OctoPrint to v1.3.2 or later.
|
||||
|
||||
After installing OctoPi and upgrading OctoPrint, ssh into the target
|
||||
machine (ssh pi@octopi -- password is "raspberry") and run the
|
||||
following commands:
|
||||
|
||||
```
|
||||
sudo apt-get update
|
||||
sudo apt-get install libncurses-dev
|
||||
sudo apt-get install avrdude gcc-avr binutils-avr avr-libc # AVR toolchain
|
||||
sudo apt-get install bossa-cli libnewlib-arm-none-eabi # ARM toolchain
|
||||
```
|
||||
|
||||
The host software (Klippy) requires a one-time setup - run as the
|
||||
regular "pi" user:
|
||||
|
||||
```
|
||||
virtualenv ~/klippy-env
|
||||
~/klippy-env/bin/pip install cffi==1.6.0 pyserial==3.2.1 greenlet==0.4.10
|
||||
```
|
||||
|
||||
Building Klipper
|
||||
================
|
||||
|
||||
To obtain Klipper, run the following command on the target machine:
|
||||
|
||||
```
|
||||
git clone https://github.com/KevinOConnor/klipper
|
||||
cd klipper/
|
||||
./klipper/scripts/install-octopi.sh
|
||||
```
|
||||
|
||||
The above will download Klipper, install some system dependencies,
|
||||
setup Klipper to run at system startup, and start the Klipper host
|
||||
software. It will require an internet connection and it may take a few
|
||||
minutes to complete.
|
||||
|
||||
Building and flashing the micro-controller
|
||||
==========================================
|
||||
|
||||
To compile the micro-controller code, start by configuring it:
|
||||
|
||||
```
|
||||
cd ~/klipper/
|
||||
make menuconfig
|
||||
```
|
||||
|
||||
@@ -60,54 +52,39 @@ configured, run:
|
||||
make
|
||||
```
|
||||
|
||||
Ignore any warnings you may see about "misspelled signal handler" (it
|
||||
is due to a bug fixed in gcc v4.8.3).
|
||||
|
||||
Installing Klipper on an AVR micro-controller
|
||||
---------------------------------------------
|
||||
|
||||
The avrdude package can be used to install the micro-controller code
|
||||
on an AVR ATmega chip. The exact syntax of the avrdude command is
|
||||
different for each micro-controller. The following is an example
|
||||
command for atmega2560 chips:
|
||||
Finally, for common micro-controllers, the code can be flashed with:
|
||||
|
||||
```
|
||||
example-only$ avrdude -C/etc/avrdude.conf -v -patmega2560 -cwiring -P/dev/ttyACM0 -b115200 -D -Uflash:w:/home/pi/klipper/out/klipper.elf.hex:i
|
||||
sudo service klipper stop
|
||||
make flash FLASH_DEVICE=/dev/ttyACM0
|
||||
sudo service klipper start
|
||||
```
|
||||
|
||||
Installing Klipper on an Arduino Due
|
||||
------------------------------------
|
||||
Configuring Klipper
|
||||
===================
|
||||
|
||||
Klipper currently uses the Arduino Due USB programming port (it will
|
||||
not work when connected to the application USB port). The programming
|
||||
port is the USB port closest to the power supply. To flash Klipper to
|
||||
the Due connect it to the host machine and run:
|
||||
|
||||
```
|
||||
stty -F /dev/ttyACM0 1200
|
||||
bossac -i -p ttyACM0 -R -e -w -v -b ~/klipper/out/klipper.bin
|
||||
```
|
||||
|
||||
Setting up the printer configuration
|
||||
====================================
|
||||
|
||||
It is necessary to configure the printer. This is done by modifying a
|
||||
configuration file that resides on the host. Start by copying an
|
||||
example configuration and editing it. For example:
|
||||
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. 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
|
||||
nano ~/printer.cfg
|
||||
```
|
||||
|
||||
Make sure to look at and update each setting that is appropriate for
|
||||
Make sure to review and update each setting that is appropriate for
|
||||
the hardware.
|
||||
|
||||
Configuring OctoPrint to use Klippy
|
||||
===================================
|
||||
Configuring OctoPrint to use Klipper
|
||||
====================================
|
||||
|
||||
The OctoPrint web server needs to be configured to communicate with
|
||||
the Klippy host software. Using a web-browser, login to the OctoPrint
|
||||
the Klipper host software. Using a web browser, login to the OctoPrint
|
||||
web page, and navigate to the Settings tab. Then configure the
|
||||
following items:
|
||||
|
||||
@@ -115,40 +92,30 @@ Under "Serial Connection" in "Additional serial ports" add
|
||||
"/tmp/printer". Then click "Save".
|
||||
|
||||
Enter the Settings tab again and under "Serial Connection" change the
|
||||
"Serial Port" setting to "/tmp/printer". Change the Baudrate field to
|
||||
250000 (this buad rate field is not related to the firmware baudrate
|
||||
and may be safely left at 250000). Unselect the "Not only cancel
|
||||
ongoing prints but also disconnect..." checkbox.
|
||||
"Serial Port" setting to "/tmp/printer". Unselect the "Not only cancel
|
||||
ongoing prints but also disconnect..." checkbox. Click "Save".
|
||||
|
||||
Under the "Features" tab, unselect "Enable SD support". Then click
|
||||
"Save".
|
||||
|
||||
Running the host software
|
||||
=========================
|
||||
|
||||
The host software is executed by running the following as the regular
|
||||
"pi" user:
|
||||
|
||||
```
|
||||
~/klippy-env/bin/python ~/klipper/klippy/klippy.py ~/printer.cfg -l /tmp/klippy.log < /dev/null > /dev/null 2>&1 &
|
||||
```
|
||||
|
||||
Once Klippy is running, use a web-browser and navigate to the
|
||||
OctoPrint web site. Under the "Connection" tab (on the left of the
|
||||
main page) make sure the "Serial Port" is set to "/tmp/printer" and
|
||||
From the main page, under the "Connection" section (at the top left of
|
||||
the page) make sure the "Serial Port" is set to "/tmp/printer" and
|
||||
click "Connect". (If "/tmp/printer" is not an available selection then
|
||||
try reloading the page.)
|
||||
|
||||
Once connected, navigate to the "Terminal" tab and type "status"
|
||||
(without the quotes) into the command entry box and click "Send". If
|
||||
the Klippy config file was successfully read, and the micro-controller
|
||||
was successfully found and configured, then this command will report
|
||||
that the printer is ready. Klippy reports error messages via this
|
||||
terminal tab. The "status" command can be used to re-report error
|
||||
messages.
|
||||
(without the quotes) into the command entry box and click "Send". The
|
||||
terminal window will likely report there is an error opening the
|
||||
config file - issue a "restart" command in the OctoPrint 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.
|
||||
|
||||
In addition to common g-code commands, Klippy supports a few extended
|
||||
commands - "status" is an example of one of these commands. Use the
|
||||
"help" command to get a list of other extended commands. In
|
||||
particular, note the "restart" command - use this command to reload
|
||||
the Klippy config file after any changes.
|
||||
Klipper reports error messages via the OctoPrint terminal tab. The
|
||||
"status" command can be used to re-report error messages. The default
|
||||
Klipper startup script also places a log in **/tmp/klippy.log** which
|
||||
provides more detailed information.
|
||||
|
||||
In addition to common g-code commands, Klipper supports a few extended
|
||||
commands - "status" and "restart" are examples of these commands. Use
|
||||
the "help" command to get a list of other extended commands.
|
||||
|
||||
293
docs/Kinematics.md
Normal file
@@ -0,0 +1,293 @@
|
||||
This document provides an overview of how Klipper implements robot
|
||||
motion (its [kinematics](https://en.wikipedia.org/wiki/Kinematics)).
|
||||
The contents may be of interest to both developers interested in
|
||||
working on the Klipper software as well as users interested in better
|
||||
understanding the mechanics of their machines.
|
||||
|
||||
Acceleration
|
||||
============
|
||||
|
||||
Klipper implements a constant acceleration scheme whenever the print
|
||||
head changes velocity - the velocity is gradually changed to the new
|
||||
speed instead of suddenly jerking to it. Klipper always enforces
|
||||
acceleration between the tool head and the print. The filament leaving
|
||||
the extruder can be quite fragile - rapid jerks and/or extruder flow
|
||||
changes lead to poor quality and poor bed adhesion. Even when not
|
||||
extruding, if the print head is at the same level as the print then
|
||||
rapid jerking of the head can cause disruption of recently deposited
|
||||
filament. Limiting speed changes of the print head (relative to the
|
||||
print) reduces risks of disrupting the print.
|
||||
|
||||
It is also important to enforce a maximum acceleration of the stepper
|
||||
motors to ensure they do not skip or put excessive stress on the
|
||||
machine. Klipper limits the acceleration of each stepper by virtue of
|
||||
limiting the acceleration of the print head. Enforcing acceleration at
|
||||
the print head naturally also enforces acceleration at the steppers
|
||||
that control that print head (the inverse is not always true).
|
||||
|
||||
Klipper implements constant acceleration. The key formula for constant
|
||||
acceleration is:
|
||||
```
|
||||
velocity(time) = start_velocity + accel*time
|
||||
```
|
||||
|
||||
Trapezoid generator
|
||||
===================
|
||||
|
||||
Klipper uses a traditional "trapezoid generator" to model the motion
|
||||
of each move - each move has a start speed, it accelerates to a
|
||||
cruising speed at constant acceleration, it cruises at a constant
|
||||
speed, and then decelerates to the end speed using constant
|
||||
acceleration.
|
||||
|
||||

|
||||
|
||||
It's called a "trapezoid generator" because a velocity diagram of the
|
||||
move looks like a trapezoid.
|
||||
|
||||
The cruising speed is always greater than or equal to both the start
|
||||
speed and the end speed. The acceleration phase may be of zero
|
||||
duration (if the start speed is equal to the cruising speed), the
|
||||
cruising phase may be of zero duration (if the move immediately starts
|
||||
decelerating after acceleration), and/or the deceleration phase may be
|
||||
of zero duration (if the end speed is equal to the cruising speed).
|
||||
|
||||

|
||||
|
||||
Look-ahead
|
||||
==========
|
||||
|
||||
The "look-ahead" system is used to determine cornering speeds between
|
||||
moves.
|
||||
|
||||
Consider the following two moves contained on an XY plane:
|
||||
|
||||

|
||||
|
||||
In the above situation it is possible to fully decelerate after the
|
||||
first move and then fully accelerate at the start of the next move,
|
||||
but that is not ideal as all that acceleration and deceleration would
|
||||
greatly increase the print time and the frequent changes in extruder
|
||||
flow would result in poor print quality.
|
||||
|
||||
To solve this, the "look-ahead" mechanism queues multiple incoming
|
||||
moves and analyzes the angles between moves to determine a reasonable
|
||||
speed that can be obtained during the "junction" between two moves. If
|
||||
the next move is nearly in the same direction then the head need only
|
||||
slow down a little (if at all).
|
||||
|
||||

|
||||
|
||||
However, if the next move forms an acute angle (the head is going to
|
||||
travel in nearly a reverse direction on the next move) then only a
|
||||
small junction speed is permitted.
|
||||
|
||||

|
||||
|
||||
The junction speeds are determined using "approximated centripetal
|
||||
acceleration". Best
|
||||
[described by the author](https://onehossshay.wordpress.com/2011/09/24/improving_grbl_cornering_algorithm/).
|
||||
|
||||
Klipper implements look-ahead between moves contained in the XY plane
|
||||
that have similar extruder flow rates. Other moves are relatively rare
|
||||
and implementing look-ahead between them is unnecessary.
|
||||
|
||||
Key formula for look-ahead:
|
||||
```
|
||||
end_velocity^2 = start_velocity^2 + 2*accel*move_distance
|
||||
```
|
||||
|
||||
Smoothed look-ahead
|
||||
-------------------
|
||||
|
||||
Klipper also implements a mechanism for smoothing out the motions of
|
||||
short "zigzag" moves. Consider the following moves:
|
||||
|
||||

|
||||
|
||||
In the above, the frequent changes from acceleration to deceleration
|
||||
can cause the machine to vibrate which causes stress on the machine
|
||||
and increases the noise. To reduce this, Klipper tracks both regular
|
||||
move acceleration as well as a virtual "acceleration to deceleration"
|
||||
rate. Using this system, the top speed of these short "zigzag" moves
|
||||
are limited to smooth out the printer motion:
|
||||
|
||||

|
||||
|
||||
Specifically, the code calculates what the velocity of each move would
|
||||
be if it were limited to this virtual "acceleration to deceleration"
|
||||
rate (half the normal acceleration rate by default). In the above
|
||||
picture the dashed gray lines represent this virtual acceleration rate
|
||||
for the first move. If a move can not reach its full cruising speed
|
||||
using this virtual acceleration rate then its top speed is reduced to
|
||||
the maximum speed it could obtain at this virtual acceleration
|
||||
rate. For most moves the limit will be at or above the move's existing
|
||||
limits and no change in behavior is induced. For short zigzag moves,
|
||||
however, this limit reduces the top speed. Note that it does not
|
||||
change the actual acceleration within the move - the move continues to
|
||||
use the normal acceleration scheme up to its adjusted top-speed.
|
||||
|
||||
Generating steps
|
||||
================
|
||||
|
||||
Once the look-ahead process completes, the print head movement for the
|
||||
given move is fully known (time, start position, end position,
|
||||
velocity at each point) and it is possible to generate the step times
|
||||
for the move. This process is done within "kinematic classes" in the
|
||||
Klipper code. Outside of these kinematic classes, everything is
|
||||
tracked in millimeters, seconds, and in cartesian coordinate space.
|
||||
It's the task of the kinematic classes to convert from this generic
|
||||
coordinate system to the hardware specifics of the particular printer.
|
||||
|
||||
In general, the code determines each step time by first calculating
|
||||
where along the line of movement the head would be if a step is
|
||||
taken. It then calculates what time the head should be at that
|
||||
position. Determining the time along the line of movement can be done
|
||||
using the formulas for constant acceleration and constant velocity:
|
||||
|
||||
```
|
||||
time = sqrt(2*distance/accel + (start_velocity/accel)^2) - start_velocity/accel
|
||||
time = distance/cruise_velocity
|
||||
```
|
||||
|
||||
Cartesian Robots
|
||||
----------------
|
||||
|
||||
Generating steps for cartesian printers is the simplest case. The
|
||||
movement on each axis is directly related to the movement in cartesian
|
||||
space.
|
||||
|
||||
Delta Robots
|
||||
------------
|
||||
|
||||
To generate step times on Delta printers it is necessary to correlate
|
||||
the movement in cartesian space with the movement on each stepper
|
||||
tower.
|
||||
|
||||
To simplify the math, for each stepper tower, the code calculates the
|
||||
location of a "virtual tower" that is along the line of movement.
|
||||
This virtual tower is chosen at the point where the line of movement
|
||||
(extended infinitely in both directions) would be closest to the
|
||||
actual tower.
|
||||
|
||||

|
||||
|
||||
It is then possible to calculate where the head will be along the line
|
||||
of movement after each step is taken on the virtual tower.
|
||||
|
||||

|
||||
|
||||
The key formula is Pythagoras's theorem:
|
||||
```
|
||||
distance_to_tower^2 = arm_length^2 - tower_height^2
|
||||
```
|
||||
|
||||
One complexity is that if the print head passes the virtual tower
|
||||
location then the stepper direction must be reversed. In this case
|
||||
forward steps will be taken at the start of the move and reverse steps
|
||||
will be taken at the end of the move.
|
||||
|
||||
### Delta movements beyond simple XY plane ###
|
||||
|
||||
Movement calculation is more complicated if a single move contains
|
||||
both XY movement and Z movement. These moves are rare, but they must
|
||||
still be handled correctly. A virtual tower along the line of movement
|
||||
is still calculated, but in this case the tower is not at a 90 degree
|
||||
angle relative to the line of movement:
|
||||
|
||||

|
||||
|
||||
The code continues to calculate step times using the same general
|
||||
scheme as delta moves within an XY plane, but the slope of the tower
|
||||
must also be used in the calculations.
|
||||
|
||||
Should the move contain only Z movement (ie, no XY movement at all)
|
||||
then the same math is used - just in this case the tower is parallel
|
||||
to the line of movement.
|
||||
|
||||
### Stepper motor acceleration limits ###
|
||||
|
||||
With delta kinematics it is possible for a move that is accelerating
|
||||
in cartesian space to require an acceleration on a particular stepper
|
||||
motor greater than the move's acceleration. This can occur when a
|
||||
stepper arm is more horizontal than vertical and the line of movement
|
||||
is near that stepper's tower.
|
||||
|
||||
Klipper does enforce a maximum ceiling on stepper acceleration that is
|
||||
three times the maximum acceleration of a move in cartesian
|
||||
space. (Similarly, the maximum velocity of the stepper is limited to
|
||||
three times the maximum move velocity.) In order to enforce this
|
||||
limit, moves at the extreme edge of the build envelope (where a
|
||||
stepper arm may be nearly horizontal) will have a lower maximum
|
||||
acceleration and velocity.
|
||||
|
||||
Extruder kinematics
|
||||
-------------------
|
||||
|
||||
Klipper implements extruder motion in its own kinematic class. Since
|
||||
the timing and speed of each print head movement is fully known for
|
||||
each move, it's possible to calculate the step times for the extruder
|
||||
independently from the step time calculations of the print head
|
||||
movement.
|
||||
|
||||
Basic extruder movement is simple to calculate. The step time
|
||||
generation uses the same constant acceleration and constant velocity
|
||||
formulas that cartesian robots use.
|
||||
|
||||
### Pressure advance ###
|
||||
|
||||
Experimentation has shown that it's possible to improve the modeling
|
||||
of the extruder beyond the basic extruder formula. In the ideal case,
|
||||
as an extrusion move progresses, the same volume of filament should be
|
||||
deposited at each point along the move and there should be no volume
|
||||
extruded after the move. Unfortunately, it's common to find that the
|
||||
basic extrusion formulas cause too little filament to exit the
|
||||
extruder at the start of extrusion moves and for excess filament to
|
||||
extrude after extrusion ends. This is often referred to as "ooze".
|
||||
|
||||

|
||||
|
||||
The "pressure advance" system attempts to account for this by using a
|
||||
different model for the extruder. Instead of naively believing that
|
||||
each mm^3 of filament fed into the extruder will result in that amount
|
||||
of mm^3 immediately exiting the extruder, it uses a model based on
|
||||
pressure. Pressure increases when filament is pushed into the extruder
|
||||
(as in [Hooke's law](https://en.wikipedia.org/wiki/Hooke%27s_law)) and
|
||||
the pressure necessary to extrude is dominated by the flow rate
|
||||
through the nozzle orifice (as in
|
||||
[Poiseuille's law](https://en.wikipedia.org/wiki/Poiseuille_law)). The
|
||||
key idea is that the relationship between filament, pressure, and flow
|
||||
rate can be modeled using a linear coefficient:
|
||||
```
|
||||
extra_filament = pressure_advance_coefficient * extruder_velocity
|
||||
```
|
||||
|
||||
See the [pressure advance](Pressure_Advance.md) document for
|
||||
information on how to find this pressure advance coefficient.
|
||||
|
||||
Once configured, Klipper will push in an additional amount of filament
|
||||
during acceleration. The higher the desired filament flow rate, the
|
||||
more filament must be pushed in during acceleration to account for
|
||||
pressure. During head deceleration the extra filament is retracted
|
||||
(the extruder will have a negative velocity).
|
||||
|
||||

|
||||
|
||||
One may notice that the pressure advance algorithm can cause the
|
||||
extruder motor to make sudden velocity changes. This is tolerated
|
||||
based on the idea that the majority of the inertia in the system is in
|
||||
changing the extruder pressure. As long as the extruder pressure does
|
||||
not change rapidly the sudden changes in extruder motor velocity are
|
||||
tolerated.
|
||||
|
||||
One area where sudden velocity changes become problematic is during
|
||||
small changes in head speed due to cornering.
|
||||
|
||||

|
||||
|
||||
To prevent this, the Klipper pressure advance code utilizes the move
|
||||
look-ahead queue to detect intermittent speed changes. During a
|
||||
deceleration event the code finds the maximum upcoming head speed
|
||||
within a configurable time window. The pressure is then only adjusted
|
||||
to this found maximum. This can greatly reduce (or even completely
|
||||
eliminate) pressure changes during cornering.
|
||||
294
docs/MCU_Commands.md
Normal file
@@ -0,0 +1,294 @@
|
||||
This document provides information on the low-level micro-controller
|
||||
commands that are sent from the Klipper "host" software and processed
|
||||
by the Klipper micro-controller software. This document is not an
|
||||
authoritative reference for these commands, nor is it an exclusive
|
||||
list of all available commands.
|
||||
|
||||
This document may be useful for users needing to configure a set of
|
||||
hardware actions that their printer may require at startup (via the
|
||||
"custom" field in the printer config file), and it may be useful for
|
||||
developers wishing to obtain a high-level feel for low-level commands.
|
||||
|
||||
See the [protocol](Protocol.md) document for more information on the
|
||||
format of commands and their transmission. The commands here are
|
||||
described using their "printf" style syntax - for those unfamiliar
|
||||
with that format, just note that where a '%...' sequence is seen it
|
||||
should be replaced with an actual integer. For example, a description
|
||||
with "count=%c" could be replaced with the text "count=10".
|
||||
|
||||
Startup Commands
|
||||
================
|
||||
|
||||
It may be necessary to take certain one-time actions to configure the
|
||||
micro-controller and its peripherals. This section lists common
|
||||
commands available for that purpose. Unlike most micro-controller
|
||||
commands, these commands run as soon as they are received and they do
|
||||
not require any particular setup.
|
||||
|
||||
These commands are most useful in the "custom" block of the "mcu"
|
||||
section of the printer configuration file. This feature is typically
|
||||
used to configure the initial settings of LEDs, to configure
|
||||
micro-stepping pins, to configure a digipot, etc.
|
||||
|
||||
Several of these commands will take a "pin=%u" parameter. The
|
||||
low-level micro-controller software uses integer encodings of the
|
||||
hardware pin numbers, but to make things more readable the host will
|
||||
translate human readable pin names (eg, "PA3") to their equivalent
|
||||
integer encodings. By convention, any parameter named "pin" or that
|
||||
has a "_pin" suffix will use pin name translation by the
|
||||
host. Similarly, several commands take time parameters specified in
|
||||
clock ticks. One can specify a value for these parameters in seconds
|
||||
using the "TICKS()" macro - for example "cycle_ticks=TICKS(0.001)"
|
||||
would result in "cycle_ticks=16000" on a micro-controller with a 16Mhz
|
||||
clock.
|
||||
|
||||
Common startup commands:
|
||||
|
||||
* `set_digital_out pin=%u value=%c` : This command immediately
|
||||
configures the given pin as a digital out GPIO and it sets it to
|
||||
either a low level (value=0) or a high level (value=1). This command
|
||||
may be useful for configuring the initial value of LEDs and for
|
||||
configuring the initial value of stepper driver micro-stepping pins.
|
||||
|
||||
* `set_pwm_out pin=%u cycle_ticks=%u value=%c` : This command will
|
||||
immediately configure the given pin to use hardware based
|
||||
pulse-width-modulation (PWM) with the given number of
|
||||
cycle_ticks. The "cycle_ticks" is the number of MCU clock ticks each
|
||||
power on and power off cycle should last. A cycle_ticks value of 1
|
||||
can be used to request the fastest possible cycle time. The "value"
|
||||
parameter is between 0 and 255 with 0 indicating a full off state
|
||||
and 255 indicating a full on state. This command may be useful for
|
||||
enabling CPU and nozzle cooling fans.
|
||||
|
||||
* `send_spi_message pin=%u msg=%*s` : This command can be used to
|
||||
transmit messages to a serial-peripheral-interface (SPI) component
|
||||
connected to the micro-controller. It has been used to configure the
|
||||
startup settings of AD5206 digipots. The 'pin' parameter specifies
|
||||
the chip select line to use during the transmission. The 'msg'
|
||||
indicates the binary message to transmit to the given chip.
|
||||
|
||||
Low-level micro-controller configuration
|
||||
========================================
|
||||
|
||||
Most commands in the micro-controller require an initial setup before
|
||||
they can be successfully invoked. This section provides an overview of
|
||||
the configuration process. This section and the following sections are
|
||||
likely only of interest to developers interested in the internal
|
||||
details of Klipper.
|
||||
|
||||
When the host first connects to the micro-controller it always starts
|
||||
by obtaining a data dictionary (see [protocol](Protocol.md) for more
|
||||
information). After the data dictionary is obtained the host will
|
||||
check if the micro-controller is in a "configured" state and configure
|
||||
it if not. Configuration involves the following phases:
|
||||
|
||||
* `get_config` : The host starts by checking if the micro-controller
|
||||
is already configured. The micro-controller responds to this command
|
||||
with a "config" response message. The micro-controller software
|
||||
always starts in an unconfigured state at power-on. It remains in
|
||||
this state until the host completes the configuration processes (by
|
||||
issuing a finalize_config command). If the micro-controller is
|
||||
already configured from a previous session (and is configured with
|
||||
the desired settings) then no further action is needed by the host
|
||||
and the configuration process ends successfully.
|
||||
|
||||
* `allocate_oids count=%c` : This command is issued to inform the
|
||||
micro-controller of the maximum number of object-ids (oid) that the
|
||||
host requires. It is only valid to issue this command once. An oid
|
||||
is an integer identifier allocated to each stepper, each endstop,
|
||||
and each schedulable gpio pin. The host determines in advance the
|
||||
number of oids it will require to operate the hardware and passes
|
||||
this to the micro-controller so that it may allocate sufficient
|
||||
memory to store a mapping from oid to internal object.
|
||||
|
||||
* `config_XXX oid=%c ...` : By convention any command starting with
|
||||
the "config_" prefix creates a new micro-controller object and
|
||||
assigns the given oid to it. For example, the config_digital_out
|
||||
command will configure the specified pin as a digital output GPIO
|
||||
and create an internal object that the host can use to schedule
|
||||
changes to the given GPIO. The oid parameter passed into the config
|
||||
command is selected by the host and must be between zero and the
|
||||
maximum count supplied in the allocate_oids command. The config
|
||||
commands may only be run when the micro-controller is not in a
|
||||
configured state (ie, prior to the host sending finalize_config) and
|
||||
after the allocate_oids command has been sent.
|
||||
|
||||
* `finalize_config crc=%u` : The finalize_config command transitions
|
||||
the micro-controller from an unconfigured state to a configured
|
||||
state. The crc parameter passed to the micro-controller is stored
|
||||
and provided back to the host in "config" response messages. By
|
||||
convention, the host takes a 32bit CRC of the configuration it will
|
||||
request and at the start of subsequent communication sessions it
|
||||
checks that the CRC stored in the micro-controller exactly matches
|
||||
its desired CRC. If the CRC does not match then the host knows the
|
||||
micro-controller has not been configured in the state desired by the
|
||||
host.
|
||||
|
||||
Common micro-controller objects
|
||||
-------------------------------
|
||||
|
||||
This section lists some commonly used config commands.
|
||||
|
||||
* `config_digital_out oid=%c pin=%u default_value=%c
|
||||
max_duration=%u` : This command creates an internal micro-controller
|
||||
object for the given GPIO 'pin'. The pin will be configured in
|
||||
digital output mode and set to an initial value as specified by
|
||||
'default_value' (0 for low, 1 for high). Creating a digital_out
|
||||
object allows the host to schedule GPIO updates for the given pin at
|
||||
specified times (see the schedule_digital_out command described
|
||||
below). Should the micro-controller software go into shutdown mode
|
||||
then all configured digital_out objects will be set back to their
|
||||
default values. The 'max_duration' parameter is used to implement a
|
||||
safety check - if it is non-zero then it is the maximum number of
|
||||
clock ticks that the host may set the given GPIO to a non-default
|
||||
value without further updates. For example, if the default_value is
|
||||
zero and the max_duration is 16000 then if the host sets the gpio to
|
||||
a value of one then it must schedule another update to the gpio pin
|
||||
(to either zero or one) within 16000 clock ticks. This safety
|
||||
feature can be used with heater pins to ensure the host does not
|
||||
enable the heater and then go off-line.
|
||||
|
||||
* `config_pwm_out oid=%c pin=%u cycle_ticks=%u default_value=%c
|
||||
max_duration=%u` : This command creates an internal object for
|
||||
hardware based PWM pins that the host may schedule updates for. Its
|
||||
usage is analogous to config_digital_out - see the description of
|
||||
the 'set_pwm_out' and 'config_digital_out' commands for parameter
|
||||
description.
|
||||
|
||||
* `config_soft_pwm_out oid=%c pin=%u cycle_ticks=%u default_value=%c
|
||||
max_duration=%u` : This command creates an internal micro-controller
|
||||
object for software implemented PWM. Unlike hardware pwm pins, a
|
||||
software pwm object does not require any special hardware support
|
||||
(other than the ability to configure the pin as a digital output
|
||||
GPIO). Because the output switching is implemented in the
|
||||
micro-controller software, it is recommended that the cycle_ticks
|
||||
parameter correspond to a time of 10ms or greater. See the
|
||||
description of the 'set_pwm_out' and 'config_digital_out' commands
|
||||
for parameter description.
|
||||
|
||||
* `config_analog_in oid=%c pin=%u` : This command is used to configure
|
||||
a pin in analog input sampling mode. Once configured, the pin can be
|
||||
sampled at regular interval using the query_analog_in command (see
|
||||
below).
|
||||
|
||||
* `config_stepper oid=%c step_pin=%c dir_pin=%c min_stop_interval=%u
|
||||
invert_step=%c` : This command creates an internal stepper
|
||||
object. The 'step_pin' and 'dir_pin' parameters specify the step and
|
||||
direction pins respectively; this command will configure them in
|
||||
digital output mode. The 'invert_step' parameter specifies whether a
|
||||
step occurs on a rising edge (invert_step=0) or falling edge
|
||||
(invert_step=1). The 'min_stop_interval' implements a safety
|
||||
feature - it is checked when the micro-controller finishes all moves
|
||||
for a stepper - if it is non-zero it specifies the minimum number of
|
||||
clock ticks since the last step. It is used as a check on the
|
||||
maximum stepper velocity that a stepper may have before stopping.
|
||||
|
||||
* `config_end_stop oid=%c pin=%c pull_up=%c stepper_count=%c` : This
|
||||
command creates an internal "endstop" object. It is used to specify
|
||||
the endstop pins and to enable "homing" operations (see the
|
||||
end_stop_home command below). The command will configure the
|
||||
specified pin in digital input mode. The 'pull_up' parameter
|
||||
determines whether hardware provided pullup resistors for the pin
|
||||
(if available) will be enabled. The 'stepper_count' parameter
|
||||
specifies the maximum number of steppers that this endstop may need
|
||||
to halt during a homing operation (see end_stop_home below).
|
||||
|
||||
Common commands
|
||||
===============
|
||||
|
||||
This section lists some commonly used run-time commands. It is likely
|
||||
only of interest to developers looking to gain insight into Klipper.
|
||||
|
||||
* `schedule_digital_out oid=%c clock=%u value=%c` : This command will
|
||||
schedule a change to a digital output GPIO pin at the given clock
|
||||
time. To use this command a 'config_digital_out' command with the
|
||||
same 'oid' parameter must have been issued during micro-controller
|
||||
configuration.
|
||||
|
||||
* `schedule_pwm_out oid=%c clock=%u value=%c` : Schedules a change to
|
||||
a hardware PWM output pin. See the 'schedule_digital_out' and
|
||||
'config_pwm_out' commands for more info.
|
||||
|
||||
* `schedule_soft_pwm_out oid=%c clock=%u value=%c` : Schedules a
|
||||
change to a software PWM output pin. See the 'schedule_digital_out'
|
||||
and 'config_soft_pwm_out' commands for more info.
|
||||
|
||||
* `query_analog_in oid=%c clock=%u sample_ticks=%u sample_count=%c
|
||||
rest_ticks=%u min_value=%hu max_value=%hu` : This command sets up a
|
||||
recurring schedule of analog input samples. To use this command a
|
||||
'config_analog_in' command with the same 'oid' parameter must have
|
||||
been issued during micro-controller configuration. The samples will
|
||||
start as of 'clock' time, it will report on the obtained value every
|
||||
'rest_ticks' clock ticks, it will over-sample 'sample_count' number
|
||||
of times, and it will pause 'sample_ticks' number of clock ticks
|
||||
between over-sample samples. The 'min_value' and 'max_value'
|
||||
parameters implement a safety feature - the micro-controller
|
||||
software will verify the sampled value (after any oversampling) is
|
||||
always between the supplied range. This is intended for use with
|
||||
pins attached to thermistors controlling heaters - it can be used to
|
||||
check that a heater is within a temperature range.
|
||||
|
||||
* `get_status` : This command causes the micro-controller to generate
|
||||
a "status" response message. The host sends this command once a
|
||||
second to obtain the value of the micro-controller clock and to
|
||||
estimate the drift between host and micro-controller clocks. It
|
||||
enables the host to accurately estimate the micro-controller clock.
|
||||
|
||||
Stepper commands
|
||||
----------------
|
||||
|
||||
* `queue_step oid=%c interval=%u count=%hu add=%hi` : This command
|
||||
schedules 'count' number of steps for the given stepper, with
|
||||
'interval' number of clock ticks between each step. The first step
|
||||
will be 'interval' number of clock ticks since the last scheduled
|
||||
step for the given stepper. If 'add' is non-zero then the interval
|
||||
will be adjusted by 'add' amount after each step. This command
|
||||
appends the given interval/count/add sequence to a per-stepper
|
||||
queue. There may be hundreds of these sequences queued during normal
|
||||
operation. New sequence are appended to the end of the queue and as
|
||||
each sequence completes its 'count' number of steps it is popped
|
||||
from the front of the queue. This system allows the micro-controller
|
||||
to queue potentially hundreds of thousands of steps - all with
|
||||
reliable and predictable schedule times.
|
||||
|
||||
* `set_next_step_dir oid=%c dir=%c` : This command specifies the value
|
||||
of the dir_pin that the next queue_step command will use.
|
||||
|
||||
* `reset_step_clock oid=%c clock=%u` : Normally, step timing is
|
||||
relative to the last step for a given stepper. This command resets
|
||||
the clock so that the next step is relative to the supplied 'clock'
|
||||
time. The host usually only sends this command at the start of a
|
||||
print.
|
||||
|
||||
* `stepper_get_position oid=%c` : This command causes the
|
||||
micro-controller to generate a "stepper_position" response message
|
||||
with the stepper's current position. The position is the total
|
||||
number of steps generated with dir=1 minus the total number of steps
|
||||
generated with dir=0.
|
||||
|
||||
* `end_stop_home oid=%c clock=%u rest_ticks=%u pin_value=%c` : This
|
||||
command is used during stepper "homing" operations. To use this
|
||||
command a 'config_end_stop' command with the same 'oid' parameter
|
||||
must have been issued during micro-controller configuration. When
|
||||
this command is invoked, the micro-controller will sample the
|
||||
endstop pin every 'rest_ticks' clock ticks and check if it has a
|
||||
value equal to 'pin_value'. If the value matches then the movement
|
||||
queue for the associated stepper will be cleared and the stepper
|
||||
will come to an immediate halt. The host uses this command to
|
||||
implement homing - the host instructs the endstop to sample for the
|
||||
endstop trigger and then it issues a series of queue_step commands
|
||||
to move a stepper towards the endstop. Once the stepper hits the
|
||||
endstop, the trigger will be detected, the movement halted, and the
|
||||
host notified.
|
||||
|
||||
### Move queue
|
||||
|
||||
Each queue_step command utilizes an entry in the micro-controller
|
||||
"move queue". This queue is allocated when it receives the
|
||||
"finalize_config" command, and it reports the number of available
|
||||
queue entries in "config" response messages.
|
||||
|
||||
It is the responsibility of the host to ensure that there is available
|
||||
space in the queue before sending a queue_step command. The host does
|
||||
this by calculating when each queue_step command completes and
|
||||
scheduling new queue_step commands accordingly.
|
||||
@@ -1,18 +1,35 @@
|
||||
See [installation](Installation.md) for information on compiling,
|
||||
installing, and running Klipper. Read [features](Features.md) for a
|
||||
high-level description of useful capabilities. The history of releases
|
||||
is available at [releases](Releases.md).
|
||||
Welcome to the Klipper documentation. There are two parts to Klipper -
|
||||
code that runs on a micro-controller and code that runs on a "host"
|
||||
machine. The host code is intended to run on a low-cost
|
||||
general-purpose machine such as a Raspberry Pi, while the
|
||||
micro-controller code is intended to run on commodity micro-controller
|
||||
chips. Read [features](Features.md) for reasons to use Klipper. See
|
||||
[installation](Installation.md) to get started with Klipper.
|
||||
|
||||
The Klipper configuration is stored in a simple text file on the host
|
||||
machine. The [config/example.cfg](../config/example.cfg) file serves
|
||||
as a reference for the config file. The
|
||||
[Pressure Advance](Pressure_Advance.md) document contains information
|
||||
on tuning the pressure advance config.
|
||||
|
||||
The [kinematics](Kinematics.md) document provides some technical
|
||||
details on how Klipper implements motion.
|
||||
|
||||
The history of Klipper releases is available at
|
||||
[releases](Releases.md).
|
||||
|
||||
Developer Documentation
|
||||
=======================
|
||||
|
||||
There are also several documents available for developers interested
|
||||
in understanding how Klipper works:
|
||||
in understanding how Klipper works. Start with the
|
||||
[code overview](Code_Overview.md) document - it provides information
|
||||
on the structure and layout of the Klipper code.
|
||||
|
||||
See [code overview](Code_Overview.md) for information on the structure
|
||||
and layout of the Klipper code.
|
||||
|
||||
See [protocol](Protocol.md) for information on the messaging protocol
|
||||
between host and firmware. See also
|
||||
[firmware commands](Firmware_Commands.md) for a high-level description
|
||||
of common commands implemented in the firmware.
|
||||
See [protocol](Protocol.md) for information on the low-level messaging
|
||||
protocol between host and micro-controller. See also
|
||||
[MCU commands](MCU_Commands.md) for a description of low-level
|
||||
commands implemented in the micro-controller software.
|
||||
|
||||
See [debugging](Debugging.md) for information on how to test and debug
|
||||
Klipper.
|
||||
|
||||
74
docs/Pressure_Advance.md
Normal file
@@ -0,0 +1,74 @@
|
||||
This document provides information on tuning the "pressure advance"
|
||||
configuration variables for a particular nozzle and filament. The
|
||||
pressure advance feature can be helpful in reducing ooze. For more
|
||||
information on how pressure advance is implemented see the
|
||||
[kinematics](Kinematics.md) document.
|
||||
|
||||
Tuning pressure advance
|
||||
=======================
|
||||
|
||||
Pressure advance does two useful things - it reduces ooze during
|
||||
non-extrude moves and it reduces blobbing during cornering. This guide
|
||||
uses the second feature (reducing blobbing during cornering) as a
|
||||
mechanism for measuring and tuning the pressure advance configuration.
|
||||
|
||||
Start by changing the extruder section of the config file so that
|
||||
pressure_advance is set to 0.0. (Make sure to issue a RESTART command
|
||||
after each update to the config file so that the new configuration
|
||||
takes effect.) Then print at least 10 layers of a large hollow square
|
||||
at high speed (eg, 100mm/s). See
|
||||
[docs/prints/square.stl](prints/square.stl) file for an STL file that
|
||||
one may use. While the object is printing, make a note of which
|
||||
direction the head is moving during external perimeters. What many
|
||||
people see here is blobbing occurring at the corners - extra filament
|
||||
at the corner in the direction the head travels followed by a possible
|
||||
lack of filament on the side immediately after that corner:
|
||||
|
||||

|
||||
|
||||
This blobbing is the result of pressure in the extruder being released
|
||||
as a blob when the head slows down to corner.
|
||||
|
||||
The next step is to set pressure_advance_lookahead_time to 0.0, slowly
|
||||
increase pressure_advance (eg, start with 0.05), and reprint the test
|
||||
object. (Be sure to issue RESTART between each config change.) The
|
||||
goal is to attempt to eliminate the blobbing during cornering. (With
|
||||
pressure advance, the extruder will retract when the head slows down,
|
||||
thus countering the pressure buildup and ideally eliminate the
|
||||
blobbing.) If a test run is done with a pressure_advance setting that
|
||||
is too high, one typically sees a dimple in the corner followed by
|
||||
possible blobbing after the corner (too much filament is retracted
|
||||
during slow down and then too much filament is extruded during the
|
||||
following speed up after cornering):
|
||||
|
||||

|
||||
|
||||
The goal is to find the smallest pressure_advance value that results
|
||||
in good quality corners:
|
||||
|
||||

|
||||
|
||||
Typical pressure_advance values are between 0.05 and 0.20 (the high
|
||||
end usually only with bowden extruders).
|
||||
|
||||
Once a good pressure_advance value is found, return
|
||||
pressure_advance_lookahead_time to its default (0.010). This parameter
|
||||
controls how far in advance to check if a head slow-down is
|
||||
immediately followed by a speed-up - it reduces pointless pressure
|
||||
changes in the head. It's possible to tune this - higher values will
|
||||
decrease the number of pressure changes in the nozzle at the expense
|
||||
of permitting more blobbing during cornering. (Tuning this value is
|
||||
unlikely to impact ooze.) The default of 10ms should work well on most
|
||||
printers.
|
||||
|
||||
Although this tuning exercise directly improves the quality of
|
||||
corners, it's worth remembering that a good pressure advance
|
||||
configuration can reduce ooze throughout the print.
|
||||
|
||||
Finally, once pressure_advance is tuned in Klipper, it may still be
|
||||
useful to configure a small retract value in the slicer (eg, 0.75mm)
|
||||
and to utilize the slicer's "wipe on retract option" if available.
|
||||
These slicer settings may help counteract ooze caused by filament
|
||||
cohesion (filament pulled out of the nozzle due to the stickiness of
|
||||
the plastic). It is recommended to disable the slicer's "z-lift on
|
||||
retract" option.
|
||||
259
docs/Protocol.md
@@ -1,8 +1,9 @@
|
||||
The Klipper transmission protocol can be thought of, at a high level,
|
||||
as a series of command and response strings that are compressed,
|
||||
transmitted over a serial line, and then processed at the receiving
|
||||
side. An example series of commands in uncompressed human-readable
|
||||
format might look like:
|
||||
The Klipper messaging protocol is used for low-level communication
|
||||
between the Klipper host software and the Klipper micro-controller
|
||||
software. At a high level the protocol can be thought of as a series
|
||||
of command and response strings that are compressed, transmitted, and
|
||||
then processed at the receiving side. An example series of commands in
|
||||
uncompressed human-readable format might look like:
|
||||
|
||||
```
|
||||
set_digital_out pin=86 value=1
|
||||
@@ -12,34 +13,35 @@ queue_step oid=7 interval=7458 count=10 add=331
|
||||
queue_step oid=7 interval=11717 count=4 add=1281
|
||||
```
|
||||
|
||||
See the [firmware commands](Firmware_Commands.md) document for
|
||||
information on available commands. See the [debugging](Debugging.md)
|
||||
document for information on how to translate a G-Code file into its
|
||||
corresponding human-readable firmware commands.
|
||||
See the [mcu commands](MCU_Commands.md) document for information on
|
||||
available commands. See the [debugging](Debugging.md) document for
|
||||
information on how to translate a G-Code file into its corresponding
|
||||
human-readable micro-controller commands.
|
||||
|
||||
This page provides a high-level description of the Klipper
|
||||
transmission protocol itself. It describes how messages are declared,
|
||||
encoded in binary format (the "compression" scheme), and transmitted.
|
||||
This page provides a high-level description of the Klipper messaging
|
||||
protocol itself. It describes how messages are declared, encoded in
|
||||
binary format (the "compression" scheme), and transmitted.
|
||||
|
||||
The goal of the protocol is to enable an error-free communication
|
||||
channel between the host and firmware that is low-latency,
|
||||
low-bandwidth, and low-complexity for the firmware.
|
||||
channel between the host and micro-controller that is low-latency,
|
||||
low-bandwidth, and low-complexity for the micro-controller.
|
||||
|
||||
Firmware Interface
|
||||
==================
|
||||
Micro-controller Interface
|
||||
==========================
|
||||
|
||||
The Klipper transmission protocol can be thought of as a
|
||||
[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) mechanism
|
||||
between firmware and host. The firmware declares the commands that the
|
||||
host may invoke along with the response messages that it can
|
||||
generate. The host uses that information to command the firmware to
|
||||
perform actions and to interpret the results.
|
||||
between micro-controller and host. The micro-controller software
|
||||
declares the commands that the host may invoke along with the response
|
||||
messages that it can generate. The host uses that information to
|
||||
command the micro-controller to perform actions and to interpret the
|
||||
results.
|
||||
|
||||
Declaring commands
|
||||
------------------
|
||||
|
||||
The firmware declares a "command" by using the DECL_COMMAND() macro in
|
||||
the C code. For example:
|
||||
The micro-controller software declares a "command" by using the
|
||||
DECL_COMMAND() macro in the C code. For example:
|
||||
|
||||
```
|
||||
DECL_COMMAND(command_set_digital_out, "set_digital_out pin=%u value=%c");
|
||||
@@ -48,11 +50,11 @@ DECL_COMMAND(command_set_digital_out, "set_digital_out pin=%u value=%c");
|
||||
The above declares a command named "set_digital_out". This allows the
|
||||
host to "invoke" this command which would cause the
|
||||
command_set_digital_out() C function to be executed in the
|
||||
firmware. The above also indicates that the command takes two integer
|
||||
parameters. When the command_set_digital_out() C code is executed, it
|
||||
will be passed an array containing these two integers - the first
|
||||
corresponding to the 'pin' and the second corresponding to the
|
||||
'value'.
|
||||
micro-controller. The above also indicates that the command takes two
|
||||
integer parameters. When the command_set_digital_out() C code is
|
||||
executed, it will be passed an array containing these two integers -
|
||||
the first corresponding to the 'pin' and the second corresponding to
|
||||
the 'value'.
|
||||
|
||||
In general, the parameters are described with printf() style syntax
|
||||
(eg, "%u"). The formatting directly corresponds to the human-readable
|
||||
@@ -63,64 +65,61 @@ documentation. In this example, the "%c" is also used as documentation
|
||||
to indicate the expected integer is 1 byte in size (the declared
|
||||
integer size does not impact the parsing or encoding).
|
||||
|
||||
At firmware compile time, the build will collect all commands declared
|
||||
with DECL_COMMAND(), determine their parameters, and arrange for them
|
||||
to be callable.
|
||||
The micro-controller build will collect all commands declared with
|
||||
DECL_COMMAND(), determine their parameters, and arrange for them to be
|
||||
callable.
|
||||
|
||||
Declaring responses
|
||||
-------------------
|
||||
|
||||
To send information from the firmware to the host a "response" is
|
||||
generated. These are both declared and transmitted using the sendf() C
|
||||
macro. For example:
|
||||
To send information from the micro-controller to the host a "response"
|
||||
is generated. These are both declared and transmitted using the
|
||||
sendf() C macro. For example:
|
||||
|
||||
```
|
||||
sendf("status clock=%u status=%c", sched_read_time(), sched_is_shutdown());
|
||||
```
|
||||
|
||||
The above transmits a "status" response message that contains two
|
||||
integer parameters ("clock" and "status"). At firmware compile time
|
||||
the build automatically finds all sendf() calls and generates encoders
|
||||
for them. The first parameter of the sendf() function describes the
|
||||
integer parameters ("clock" and "status"). The micro-controller build
|
||||
automatically finds all sendf() calls and generates encoders for
|
||||
them. The first parameter of the sendf() function describes the
|
||||
response and it is in the same format as command declarations.
|
||||
|
||||
The host can arrange to register a callback function for each
|
||||
response. So, in effect, commands allow the host to invoke C functions
|
||||
in the firmware and responses allow the firmware to invoke code in the
|
||||
host.
|
||||
in the micro-controller and responses allow the micro-controller
|
||||
software to invoke code in the host.
|
||||
|
||||
The firmware should only invoke sendf() from command or task handlers,
|
||||
and it should not be invoked from interrupts or timers. The firmware
|
||||
does not need to issue a sendf() in response to a received command, it
|
||||
is not limited in the number of times sendf() may be invoked, and it
|
||||
may invoke sendf() at any time from a task handler.
|
||||
The sendf() macro should only be invoked from command or task
|
||||
handlers, and it should not be invoked from interrupts or timers. The
|
||||
code does not need to issue a sendf() in response to a received
|
||||
command, it is not limited in the number of times sendf() may be
|
||||
invoked, and it may invoke sendf() at any time from a task handler.
|
||||
|
||||
### Output responses
|
||||
|
||||
To simplify debugging, the firmware also has an output() C
|
||||
function. For example:
|
||||
To simplify debugging, there is also has an output() C function. For
|
||||
example:
|
||||
|
||||
```
|
||||
output("The value of %u is %s with size %u.", x, buf, buf_len);
|
||||
```
|
||||
|
||||
The output() function is similar in usage to printf() - it is intended
|
||||
to generate and format arbitrary messages for human consumption. It is
|
||||
a wrapper around sendf() and as with sendf() it should not be called
|
||||
from interrupts or timers.
|
||||
to generate and format arbitrary messages for human consumption.
|
||||
|
||||
Declaring constants
|
||||
-------------------
|
||||
|
||||
The firmware can also define constants to be exported. For example,
|
||||
the following:
|
||||
Constants can also be exported. For example, the following:
|
||||
|
||||
```
|
||||
DECL_CONSTANT(SERIAL_BAUD, 250000);
|
||||
```
|
||||
|
||||
would export a constant named "SERIAL_BAUD" with a value of 250000
|
||||
from the firmware to the host.
|
||||
from the micro-controller to the host.
|
||||
|
||||
Low-level message encoding
|
||||
==========================
|
||||
@@ -132,9 +131,9 @@ the transmission system.
|
||||
Message Blocks
|
||||
--------------
|
||||
|
||||
All data sent from host to firmware and vice-versa are contained in
|
||||
"message blocks". A message block has a two byte header and a three
|
||||
byte trailer. The format of a message block is:
|
||||
All data sent from host to micro-controller and vice-versa are
|
||||
contained in "message blocks". A message block has a two byte header
|
||||
and a three byte trailer. The format of a message block is:
|
||||
|
||||
```
|
||||
<1 byte length><1 byte sequence><n-byte content><2 byte crc><1 byte sync>
|
||||
@@ -162,10 +161,11 @@ present in the message block content.
|
||||
Message Block Contents
|
||||
----------------------
|
||||
|
||||
Each message block sent from host to firmware contains a series of
|
||||
zero or more message commands in its contents. Each command starts
|
||||
with a Variable Length Quantity (VLQ) encoded integer command-id
|
||||
followed by zero or more VLQ parameters for the given command.
|
||||
Each message block sent from host to micro-controller contains a
|
||||
series of zero or more message commands in its contents. Each command
|
||||
starts with a [Variable Length Quantity](#variable-length-quantities)
|
||||
(VLQ) encoded integer command-id followed by zero or more VLQ
|
||||
parameters for the given command.
|
||||
|
||||
As an example, the following four commands might be placed in a single
|
||||
message block:
|
||||
@@ -184,21 +184,22 @@ and encoded into the following eight VLQ integers:
|
||||
```
|
||||
|
||||
In order to encode and parse the message contents, both the host and
|
||||
firmware must agree on the command ids and the number of parameters
|
||||
each command has. So, in the above example, both the host and firmware
|
||||
would know that "id_set_digital_out" is always followed by two
|
||||
parameters, and "id_get_config" and "id_get_status" have zero
|
||||
parameters. The host and firmware share a "data dictionary" that maps
|
||||
the command descriptions (eg, "set_digital_out pin=%u value=%c") to
|
||||
their integer command-ids. When processing the data, the parser will
|
||||
know to expect a specific number of VLQ encoded parameters following a
|
||||
given command id.
|
||||
micro-controller must agree on the command ids and the number of
|
||||
parameters each command has. So, in the above example, both the host
|
||||
and micro-controller would know that "id_set_digital_out" is always
|
||||
followed by two parameters, and "id_get_config" and "id_get_status"
|
||||
have zero parameters. The host and micro-controller share a "data
|
||||
dictionary" that maps the command descriptions (eg, "set_digital_out
|
||||
pin=%u value=%c") to their integer command-ids. When processing the
|
||||
data, the parser will know to expect a specific number of VLQ encoded
|
||||
parameters following a given command id.
|
||||
|
||||
The message contents for blocks sent from firmware to host follow the
|
||||
same format. The identifiers in these messages are "response ids", but
|
||||
they serve the same purpose and follow the same encoding rules. In
|
||||
practice, message blocks sent from the firmware to the host never
|
||||
contain more than one response in the message block contents.
|
||||
The message contents for blocks sent from micro-controller to host
|
||||
follow the same format. The identifiers in these messages are
|
||||
"response ids", but they serve the same purpose and follow the same
|
||||
encoding rules. In practice, message blocks sent from the
|
||||
micro-controller to the host never contain more than one response in
|
||||
the message block contents.
|
||||
|
||||
### Variable Length Quantities
|
||||
|
||||
@@ -230,60 +231,62 @@ the length as a VLQ encoded integer followed by the contents itself:
|
||||
```
|
||||
|
||||
The command descriptions found in the data dictionary allow both the
|
||||
host and firmware to know which command parameters use simple VLQ
|
||||
encoding and which parameters use string encoding.
|
||||
host and micro-controller to know which command parameters use simple
|
||||
VLQ encoding and which parameters use string encoding.
|
||||
|
||||
Data Dictionary
|
||||
===============
|
||||
|
||||
In order for meaningful communications to be established between
|
||||
firmware and host, both sides must agree on a "data dictionary". This
|
||||
data dictionary contains the integer identifiers for commands and
|
||||
responses along with their descriptions.
|
||||
micro-controller and host, both sides must agree on a "data
|
||||
dictionary". This data dictionary contains the integer identifiers for
|
||||
commands and responses along with their descriptions.
|
||||
|
||||
At compile time the firmware build uses the contents of DECL_COMMAND()
|
||||
and sendf() macros to generate the data dictionary. The build
|
||||
The micro-controller build uses the contents of DECL_COMMAND() and
|
||||
sendf() macros to generate the data dictionary. The build
|
||||
automatically assigns unique identifiers to each command and
|
||||
response. This system allows both the host and firmware code to
|
||||
seamlessly use descriptive human-readable names while still using
|
||||
response. This system allows both the host and micro-controller code
|
||||
to seamlessly use descriptive human-readable names while still using
|
||||
minimal bandwidth.
|
||||
|
||||
The host queries the data dictionary when it first connects to the
|
||||
firmware. Once the host downloads the data dictionary from the
|
||||
firmware, it uses that data dictionary to encode all commands and to
|
||||
parse all responses from the firmware. The host must therefore handle
|
||||
a dynamic data dictionary. However, to keep the firmware simple, the
|
||||
firmware always uses its static (compiled in) data dictionary.
|
||||
micro-controller. Once the host downloads the data dictionary from the
|
||||
micro-controller, it uses that data dictionary to encode all commands
|
||||
and to parse all responses from the micro-controller. The host must
|
||||
therefore handle a dynamic data dictionary. However, to keep the
|
||||
micro-controller software simple, the micro-controller always uses its
|
||||
static (compiled in) data dictionary.
|
||||
|
||||
The data dictionary is queried by sending "identify" commands to the
|
||||
firmware. The firmware will respond to each identify command with an
|
||||
"identify_response" message. Since these two commands are needed prior
|
||||
to obtaining the data dictionary, their integer ids and parameter
|
||||
types are hard-coded in both the firmware and the host. The
|
||||
"identify_response" response id is 0, the "identify" command id
|
||||
is 1. Other than having hard-coded ids the identify command and its
|
||||
response are declared and transmitted the same way as other commands
|
||||
and responses. No other command or response is hard-coded.
|
||||
micro-controller. The micro-controller will respond to each identify
|
||||
command with an "identify_response" message. Since these two commands
|
||||
are needed prior to obtaining the data dictionary, their integer ids
|
||||
and parameter types are hard-coded in both the micro-controller and
|
||||
the host. The "identify_response" response id is 0, the "identify"
|
||||
command id is 1. Other than having hard-coded ids the identify command
|
||||
and its response are declared and transmitted the same way as other
|
||||
commands and responses. No other command or response is hard-coded.
|
||||
|
||||
The format of the transmitted data dictionary itself is a zlib
|
||||
compressed JSON string. The firmware compile process generates the
|
||||
string, compresses it, and stores it in the text section of the
|
||||
firmware. The data dictionary can be much larger than the maximum
|
||||
message block size - the host downloads it by sending multiple
|
||||
identify commands requesting progressive chunks of the data
|
||||
compressed JSON string. The micro-controller build process generates
|
||||
the string, compresses it, and stores it in the text section of the
|
||||
micro-controller flash. The data dictionary can be much larger than
|
||||
the maximum message block size - the host downloads it by sending
|
||||
multiple identify commands requesting progressive chunks of the data
|
||||
dictionary. Once all chunks are obtained the host will assemble the
|
||||
chunks, uncompress the data, and parse the contents.
|
||||
|
||||
In addition to information on the communication protocol, the data
|
||||
dictionary also contains firmware version, constants (as defined by
|
||||
DECL_CONSTANT), and static strings.
|
||||
dictionary also contains the software version, constants (as defined
|
||||
by DECL_CONSTANT), and static strings.
|
||||
|
||||
Static Strings
|
||||
--------------
|
||||
|
||||
To reduce bandwidth the data dictionary also contains a set of static
|
||||
strings known to the firmware. This is useful when sending messages
|
||||
from firmware to host. For example, if the firmware were to run:
|
||||
strings known to the micro-controller. This is useful when sending
|
||||
messages from micro-controller to host. For example, if the
|
||||
micro-controller were to run:
|
||||
|
||||
```
|
||||
shutdown("Unable to handle command");
|
||||
@@ -296,22 +299,22 @@ to their associated human-readable strings.
|
||||
Message flow
|
||||
============
|
||||
|
||||
Message commands sent from host to firmware are intended to be
|
||||
error-free. The firmware will check the CRC and sequence numbers in
|
||||
each message block to ensure the commands are accurate and
|
||||
in-order. The firmware always processes message blocks in-order -
|
||||
should it receive a block out-of-order it will discard it and any
|
||||
other out-of-order blocks until it receives blocks with the correct
|
||||
sequencing.
|
||||
Message commands sent from host to micro-controller are intended to be
|
||||
error-free. The micro-controller will check the CRC and sequence
|
||||
numbers in each message block to ensure the commands are accurate and
|
||||
in-order. The micro-controller always processes message blocks
|
||||
in-order - should it receive a block out-of-order it will discard it
|
||||
and any other out-of-order blocks until it receives blocks with the
|
||||
correct sequencing.
|
||||
|
||||
The low-level host code implements an automatic retransmission system
|
||||
for lost and corrupt message blocks sent to the firmware. To
|
||||
facilitate this, the firmware transmits an "ack message block" after
|
||||
each successfully received message block. The host schedules a timeout
|
||||
after sending each block and it will retransmit should the timeout
|
||||
expire without receiving a corresponding "ack". In addition, if the
|
||||
firmware detects a corrupt or out-of-order block it may transmit a
|
||||
"nak message block" to facilitate fast retransmission.
|
||||
for lost and corrupt message blocks sent to the micro-controller. To
|
||||
facilitate this, the micro-controller transmits an "ack message block"
|
||||
after each successfully received message block. The host schedules a
|
||||
timeout after sending each block and it will retransmit should the
|
||||
timeout expire without receiving a corresponding "ack". In addition,
|
||||
if the micro-controller detects a corrupt or out-of-order block it may
|
||||
transmit a "nak message block" to facilitate fast retransmission.
|
||||
|
||||
An "ack" is a message block with empty content (ie, a 5 byte message
|
||||
block) and a sequence number greater than the last received host
|
||||
@@ -326,15 +329,15 @@ in the event of transmission latency. The timeout, retransmit,
|
||||
windowing, and ack mechanism are inspired by similar mechanisms in
|
||||
[TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol).
|
||||
|
||||
In the other direction, message blocks sent from firmware to host are
|
||||
designed to be error-free, but they do not have assured
|
||||
In the other direction, message blocks sent from micro-controller to
|
||||
host are designed to be error-free, but they do not have assured
|
||||
transmission. (Responses should not be corrupt, but they may go
|
||||
missing.) This is done to keep the implementation in the firmware
|
||||
simple. There is no automatic retransmission system for responses -
|
||||
the high-level code is expected to be capable of handling an
|
||||
occasional missing response (usually by re-requesting the content or
|
||||
setting up a recurring schedule of response transmission). The
|
||||
sequence number field in message blocks sent to the host is always one
|
||||
greater than the last received sequence number of message blocks
|
||||
received from the host. It is not used to track sequences of response
|
||||
message blocks.
|
||||
missing.) This is done to keep the implementation in the
|
||||
micro-controller simple. There is no automatic retransmission system
|
||||
for responses - the high-level code is expected to be capable of
|
||||
handling an occasional missing response (usually by re-requesting the
|
||||
content or setting up a recurring schedule of response
|
||||
transmission). The sequence number field in message blocks sent to the
|
||||
host is always one greater than the last received sequence number of
|
||||
message blocks received from the host. It is not used to track
|
||||
sequences of response message blocks.
|
||||
|
||||
2
docs/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
Welcome to the Klipper documentation. The
|
||||
[overview document](Overview.md) is a good starting point.
|
||||
@@ -1,6 +1,26 @@
|
||||
History of Klipper releases. Please see
|
||||
[installation](Installation.md) for information on installing Klipper.
|
||||
|
||||
Klipper 0.4.0
|
||||
=============
|
||||
|
||||
Available on 20170503. Major changes in this release:
|
||||
|
||||
* Improved installation on Raspberry Pi machines. Most of the install
|
||||
is now scripted.
|
||||
* Support for corexy kinematics
|
||||
* Documentation updates: New Kinematics document, new Pressure Advance
|
||||
tuning guide, new example config files, and more
|
||||
* Stepper performance improvements (20Mhz AVRs over 175K steps per
|
||||
second, Arduino Due over 460K)
|
||||
* Support for automatic micro-controller resets. Support for resets
|
||||
via toggling USB power on Raspberry Pi.
|
||||
* The pressure advance algorithm now works with look-ahead to reduce
|
||||
pressure changes during cornering.
|
||||
* Support for limiting the top speed of short zigzag moves
|
||||
* Support for AD595 sensors
|
||||
* Several bug fixes and code cleanups
|
||||
|
||||
Klipper 0.3.0
|
||||
=============
|
||||
|
||||
|
||||
54
docs/Todo.md
@@ -1,5 +1,5 @@
|
||||
Klipper is currently in an experimental state. There are several
|
||||
features still to be implemented. In no particular order:
|
||||
There are several features still to be implemented in Klipper. In no
|
||||
particular order:
|
||||
|
||||
Host user interaction
|
||||
=====================
|
||||
@@ -9,13 +9,6 @@ Host user interaction
|
||||
find the error) and errors written to the log can be non-obvious to
|
||||
a user.
|
||||
|
||||
* Improve startup:
|
||||
|
||||
* Provide startup scripts so that Klippy can startup at system
|
||||
bootup.
|
||||
|
||||
* Allow loading of a new config without having to restart the mcu.
|
||||
|
||||
* Improve gcode interface:
|
||||
|
||||
* Provide a better way to handle print nozzle z offsets. The M206
|
||||
@@ -28,12 +21,6 @@ Host user interaction
|
||||
|
||||
* Improve logging:
|
||||
|
||||
* Automatically roll Klippy log files. The default log file should
|
||||
have the current date in the log file name.
|
||||
|
||||
* Report the Klippy git version in log file. Log the contents of the
|
||||
config file at startup.
|
||||
|
||||
* Possibly collate and report the statistics messages in the log in a
|
||||
more friendly way.
|
||||
|
||||
@@ -44,12 +31,12 @@ Host user interaction
|
||||
Safety features
|
||||
===============
|
||||
|
||||
* Support loading a valid step range into the firmware after
|
||||
homing. This would provide a sanity check in the firmware that would
|
||||
reduce the risk of the host commanding a stepper motor past its
|
||||
valid step range. To maintain high efficiency in the firmware, the
|
||||
firmware would only need to check periodically (eg, every 100ms)
|
||||
that the stepper is in range.
|
||||
* Support loading a valid step range into the micro-controller
|
||||
software after homing. This would provide a sanity check in the
|
||||
micro-controller that would reduce the risk of the host commanding a
|
||||
stepper motor past its valid step range. To maintain high
|
||||
efficiency, the micro-controller would only need to check
|
||||
periodically (eg, every 100ms) that the stepper is in range.
|
||||
|
||||
* Possibly support periodically querying the endstop switches and use
|
||||
multiple step ranges depending on the switch state. This would
|
||||
@@ -61,17 +48,14 @@ Safety features
|
||||
can be useful to detect a sensor failure (eg, thermistor short) that
|
||||
could otherwise cause the PID to command excessive heating.
|
||||
|
||||
* Possibly implement host based checking on the ratio between extrude
|
||||
amount and head movement.
|
||||
* Enforce acceleration and speed limits on extruder stepper motor.
|
||||
|
||||
Testing features
|
||||
================
|
||||
|
||||
* Complete the host based simulator. It's possible to compile the
|
||||
firmware for a "host simulator", but that simulator doesn't do
|
||||
anything currently. It would be useful to expand the code to support
|
||||
more error checks, kinematic simulations, and improved logging.
|
||||
micro-controller for a "host simulator", but that simulator doesn't
|
||||
do anything currently. It would be useful to expand the code to
|
||||
support more error checks, kinematic simulations, and improved
|
||||
logging.
|
||||
|
||||
Documentation
|
||||
=============
|
||||
@@ -81,20 +65,15 @@ Documentation
|
||||
* Add documentation describing how to perform bed-leveling accurately
|
||||
in Klipper. Improve description of stepper phase based bed leveling.
|
||||
|
||||
* Document the kinematic formulas in Klippy. Document how acceleration
|
||||
and jerk limits are enforced.
|
||||
|
||||
* Document how one can tune the pressure advance setting.
|
||||
|
||||
Hardware features
|
||||
=================
|
||||
|
||||
* Port firmware to more architectures:
|
||||
* Port to additional micro-controller architectures:
|
||||
* Beagle Bone Black PRU
|
||||
* Smoothieboard / NXP LPC1769 (ARM cortex-M3)
|
||||
* Unix based scheduling; Unix based real-time scheduling
|
||||
|
||||
* Support for additional kinematics: scara, corexy, etc.
|
||||
* Support for additional kinematics: scara, etc.
|
||||
|
||||
* Support shared motor enable GPIO lines.
|
||||
|
||||
@@ -108,11 +87,6 @@ Hardware features
|
||||
it would also be useful to handle panels already hardwired to the
|
||||
micro-controller.)
|
||||
|
||||
* The raspberry pi has the ability to cut power to its USB ports. This
|
||||
feature is useful for resetting micro-controllers that are powered
|
||||
over USB. It would be useful to have a high-level command interface
|
||||
in Klippy to request a micro-controller reset via this mechanism.
|
||||
|
||||
* Possibly support printers using multiple micro-controllers.
|
||||
|
||||
Misc features
|
||||
|
||||
BIN
docs/img/corner-blob.jpg
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
docs/img/corner-dimple.jpg
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
docs/img/corner-good.jpg
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
184
docs/img/corner.svg
Normal file
@@ -0,0 +1,184 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="54.904114mm"
|
||||
height="6.0860338mm"
|
||||
viewBox="0 0 194.54213 21.564687"
|
||||
id="svg3506"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="corner.svg">
|
||||
<defs
|
||||
id="defs3508">
|
||||
<marker
|
||||
inkscape:stockid="DiamondL"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="DiamondL"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path4399"
|
||||
d="M 0,-7.0710768 -7.0710894,0 0,7.0710589 7.0710462,0 0,-7.0710768 Z"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="scale(0.8,0.8)"
|
||||
inkscape:connector-curvature="0" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow2Lend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow2Lend"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path4341"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
|
||||
transform="matrix(-1.1,0,0,-1.1,-1.1,0)"
|
||||
inkscape:connector-curvature="0" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="marker4596"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path4598"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)"
|
||||
inkscape:connector-curvature="0" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow1Mend"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path4329"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)"
|
||||
inkscape:connector-curvature="0" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow1Mend-1"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4329-1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)" />
|
||||
</marker>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="2.49"
|
||||
inkscape:cx="95.030833"
|
||||
inkscape:cy="-0.17370789"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
showborder="false"
|
||||
inkscape:window-width="1068"
|
||||
inkscape:window-height="478"
|
||||
inkscape:window-x="378"
|
||||
inkscape:window-y="113"
|
||||
inkscape:window-maximized="0">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid6021"
|
||||
spacingx="9.9999997"
|
||||
spacingy="10.000001"
|
||||
originx="0.89299989"
|
||||
originy="-30.954583" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata3511">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-253.40821,-436.43703)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend)"
|
||||
d="m 345.38554,440.31401 96.88541,12.96764"
|
||||
id="path3514"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-1)"
|
||||
d="m 253.63788,454.62572 89.78715,-13.91164"
|
||||
id="path3514-5"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="189.09824"
|
||||
y="482.48389"
|
||||
id="text12656-9"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(0.98759291,-0.15703579,0.15703579,0.98759291,0,0)"
|
||||
inkscape:transform-center-x="1.3563414"
|
||||
inkscape:transform-center-y="-5.7099754"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5552"
|
||||
x="189.09824"
|
||||
y="482.48389">move 1</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="427.95532"
|
||||
y="379.5321"
|
||||
id="text12656-9-8"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(0.98949457,0.14457001,-0.14457001,0.98949457,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5554"
|
||||
x="427.95532"
|
||||
y="379.5321">move 2</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
BIN
docs/img/corner.svg.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
273
docs/img/delta-tower.svg
Normal file
@@ -0,0 +1,273 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="97.22496mm"
|
||||
height="32.550285mm"
|
||||
viewBox="0 0 344.49789 115.33566"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="delta-tower.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker6618"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Mend">
|
||||
<path
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)"
|
||||
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
id="path6620"
|
||||
inkscape:connector-curvature="0" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="marker6500"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path6502"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1;fill:#4b4b4b;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible;"
|
||||
id="marker6082"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
inkscape:collect="always">
|
||||
<path
|
||||
transform="scale(0.4) rotate(180) translate(10,0)"
|
||||
style="fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1;fill:#4b4b4b;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path6084" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="Arrow1Mend"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always">
|
||||
<path
|
||||
id="path5747"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1;fill:#4b4b4b;fill-opacity:1"
|
||||
transform="scale(0.4) rotate(180) translate(10,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mstart"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="Arrow1Mstart"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path5744"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
style="fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1;fill:#4b4b4b;fill-opacity:1"
|
||||
transform="scale(0.4) translate(10,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow1Mend-1"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4329-1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker6082-9"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
inkscape:collect="always">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)"
|
||||
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
id="path6084-2" />
|
||||
</marker>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.75"
|
||||
inkscape:cx="172.24895"
|
||||
inkscape:cy="45.708959"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="928"
|
||||
inkscape:window-height="628"
|
||||
inkscape:window-x="162"
|
||||
inkscape:window-y="50"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
showborder="false"
|
||||
inkscape:snap-global="false"
|
||||
showguides="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3436" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-135.22429,-249.96955)">
|
||||
<ellipse
|
||||
style="fill:#4d4d4d;stroke:#4b4b4b;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:2, 1, 0.5, 1;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path4255"
|
||||
cx="353.79568"
|
||||
cy="327.87662"
|
||||
rx="4.2857141"
|
||||
ry="4.8571429" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000006;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;marker-start:url(#Arrow1Mstart);marker-end:url(#Arrow1Mend)"
|
||||
d="M 181.50998,381.87664 359.22427,275.01949"
|
||||
id="path5510"
|
||||
inkscape:connector-curvature="0" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="371.22427"
|
||||
y="354.44806"
|
||||
id="text6058"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6060"
|
||||
x="371.22427"
|
||||
y="354.44806">stepper</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="371.22427"
|
||||
y="366.94806"
|
||||
id="tspan9337">tower</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="166.65283"
|
||||
y="304.16235"
|
||||
id="text6062"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6064"
|
||||
x="166.65283"
|
||||
y="304.16235">line of movement</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:10.0000006px;line-height:125%;font-family:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;-inkscape-font-specification:'DejaVu Sans, Normal';font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr;"
|
||||
x="252.93855"
|
||||
y="384.16235"
|
||||
id="text6066"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6068"
|
||||
x="252.93855"
|
||||
y="384.16235">move</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000006;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#marker6618)"
|
||||
d="m 382.65284,344.73378 c -0.19273,-13.52091 -9.87887,-14.83602 -20.57143,-14.85715"
|
||||
id="path6070"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#marker6500)"
|
||||
d="m 254.65284,374.44806 c 3.39239,-12.86009 -2.06023,-20.09154 -15.42857,-22.28571"
|
||||
id="path6072"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker6082)"
|
||||
d="m 257.50997,296.16234 c 31.05376,-3.13332 32.38959,-1.23784 42.28572,10.28572"
|
||||
id="path6074"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-1)"
|
||||
d="m 219.61431,358.80126 57.78715,-34.48307"
|
||||
id="path3514-5"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:0.50000003;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 351.22427,323.59092 -20.57143,-32"
|
||||
id="path3358"
|
||||
inkscape:connector-curvature="0" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="244.36713"
|
||||
y="257.30521"
|
||||
id="text4460"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4462"
|
||||
x="244.36713"
|
||||
y="257.30521">virtual tower</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="244.36713"
|
||||
y="269.80521"
|
||||
id="tspan5867">location</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker6082-9)"
|
||||
d="m 310.90661,257.14111 c 21.29088,8.40268 18.35244,16.28958 20.57143,29.7143"
|
||||
id="path6074-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
BIN
docs/img/delta-tower.svg.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
208
docs/img/lookahead-slow.svg
Normal file
@@ -0,0 +1,208 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="97.22496mm"
|
||||
height="32.550285mm"
|
||||
viewBox="0 0 344.49789 115.33566"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="lookahead-slow.svg"
|
||||
inkscape:export-filename="/home/kevin/src/reprap/firmware/klipper/docs/img/lookahead-slow.svg.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4">
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow1Mend-1"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4329-1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow1Mend"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path4329"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)"
|
||||
inkscape:connector-curvature="0" />
|
||||
</marker>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.15"
|
||||
inkscape:cx="3.4198125"
|
||||
inkscape:cy="101.26451"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1091"
|
||||
inkscape:window-height="588"
|
||||
inkscape:window-x="149"
|
||||
inkscape:window-y="422"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
showborder="false"
|
||||
inkscape:snap-global="false"
|
||||
showguides="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3436" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-135.22429,-249.96955)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
|
||||
id="path3347"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="434.04257"
|
||||
y="365.1282"
|
||||
id="text3349"
|
||||
sodipodi:linespacing="100%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3351"
|
||||
x="434.04257"
|
||||
y="365.1282">time</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-313.86618"
|
||||
y="140.27856"
|
||||
id="text3353"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3355"
|
||||
x="-313.86618"
|
||||
y="140.27856">velocity</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 -5.31915,8.51063"
|
||||
id="path3359"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.62366331px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 179.63013,351.45141 16.05677,-60.94328 43.25999,0 16.53759,47.11348"
|
||||
id="path3361"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.7558428px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 254.72406,337.27551 22.65101,-77.77178 43.89917,0 24.69858,91.73948"
|
||||
id="path3361-7"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="195.45749"
|
||||
y="286.52051"
|
||||
id="text12656-9"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan7078"
|
||||
x="195.45749"
|
||||
y="286.52051">move A</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="280.5639"
|
||||
y="254.60564"
|
||||
id="text12656-9-3"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan7080"
|
||||
x="280.5639"
|
||||
y="254.60564">move B</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend)"
|
||||
d="m 74.333855,283.02668 -147.83244,52.19984"
|
||||
id="path3514"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-1)"
|
||||
d="m -79.633985,251.6122 154.13499,31.81455"
|
||||
id="path3514-5"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="36.7374"
|
||||
y="252.31848"
|
||||
id="text12656-9-1"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(0.96753827,0.25272454,-0.25272454,0.96753827,0,0)"
|
||||
inkscape:transform-center-x="-0.91382951"
|
||||
inkscape:transform-center-y="-4.9145266"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan7074"
|
||||
x="36.7374"
|
||||
y="252.31848">move A</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-126.26086"
|
||||
y="304.35226"
|
||||
id="text12656-9-8"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(0.93839918,-0.34555314,0.34555314,0.93839918,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan7076"
|
||||
x="-126.26086"
|
||||
y="304.35226">move B</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.2 KiB |
BIN
docs/img/lookahead-slow.svg.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
136
docs/img/lookahead.svg
Normal file
@@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="97.22496mm"
|
||||
height="32.550285mm"
|
||||
viewBox="0 0 344.49789 115.33566"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="lookahead.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.94"
|
||||
inkscape:cx="116.54041"
|
||||
inkscape:cy="45.708959"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1091"
|
||||
inkscape:window-height="588"
|
||||
inkscape:window-x="149"
|
||||
inkscape:window-y="422"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
showborder="false"
|
||||
inkscape:snap-global="false"
|
||||
showguides="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3436" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-135.22429,-249.96955)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
|
||||
id="path3347"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="434.04257"
|
||||
y="365.1282"
|
||||
id="text3349"
|
||||
sodipodi:linespacing="100%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3351"
|
||||
x="434.04257"
|
||||
y="365.1282">time</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-313.86618"
|
||||
y="140.27856"
|
||||
id="text3353"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3355"
|
||||
x="-313.86618"
|
||||
y="140.27856">velocity</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 -5.31915,8.51063"
|
||||
id="path3359"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.62366331px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 179.63013,351.45141 16.05677,-60.94328 61.3451,0 4.83546,8.81561"
|
||||
id="path3361"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.7558428px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 261.70791,300.17937 13.5395,-40.67564 59.85662,0 24.69858,91.73948"
|
||||
id="path3361-7"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="200.77664"
|
||||
y="286.52051"
|
||||
id="text12656-9"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5532"
|
||||
x="200.77664"
|
||||
y="286.52051">move 1</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="280.5639"
|
||||
y="255.66946"
|
||||
id="text12656-9-3"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5534"
|
||||
x="280.5639"
|
||||
y="255.66946">move 2</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
BIN
docs/img/lookahead.svg.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
207
docs/img/ooze.svg
Normal file
@@ -0,0 +1,207 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="98.140816mm"
|
||||
height="63.537022mm"
|
||||
viewBox="0 0 347.74305 225.13119"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="ooze.svg"
|
||||
inkscape:export-filename="/home/kevin/src/reprap/firmware/klipper/docs/img/ooze.svg.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.94"
|
||||
inkscape:cx="199.68782"
|
||||
inkscape:cy="58.510649"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1091"
|
||||
inkscape:window-height="588"
|
||||
inkscape:window-x="266"
|
||||
inkscape:window-y="106"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
showborder="false"
|
||||
inkscape:snap-global="false"
|
||||
showguides="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3436"
|
||||
originx="-1.5695746e-05"
|
||||
originy="109.79552" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-135.22431,-249.96955)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
|
||||
id="path3347"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-346.84067"
|
||||
y="139.75046"
|
||||
id="text3353"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12733"
|
||||
x="-346.84067"
|
||||
y="139.75046">head velocity</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 -5.31915,8.51063"
|
||||
id="path3359"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 179.63013,351.45141 16.05677,-60.94328 120.91957,-1.06383 16.53759,62.00711"
|
||||
id="path3361"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.78742969px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 332.99641,351.24986 16.72764,-63.00287 133.24331,0"
|
||||
id="path3361-7"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="200.11789"
|
||||
y="284.45413"
|
||||
id="text12656"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12658"
|
||||
x="200.11789"
|
||||
y="284.45413">extrude move</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="356.50089"
|
||||
y="283.39032"
|
||||
id="text12660"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12662"
|
||||
x="356.50089"
|
||||
y="283.39032">non-extrude move</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 152.72419,471.73218 1.06383,102.12766 327.12768,-0.53192 -7.97872,5.85107"
|
||||
id="path3347-0"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="436.76678"
|
||||
y="586.62585"
|
||||
id="text3349-3"
|
||||
sodipodi:linespacing="100%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3351-4"
|
||||
x="436.76678"
|
||||
y="586.62585">time</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-551.11292"
|
||||
y="125.37186"
|
||||
id="text3353-0"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4197"
|
||||
x="-551.11292"
|
||||
y="125.37186">actual</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4199"
|
||||
x="-551.11292"
|
||||
y="140.99686">filament</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 152.72419,471.73218 -5.31915,8.51063"
|
||||
id="path3359-9"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 182.35432,572.94905 c 17.46262,-43.80215 52.67413,-56.54375 92.65253,-60.94329 l 48.57915,-1.06383 c 24.916,55.4715 110.00504,59.23318 151.64398,62.00712"
|
||||
id="path3361-1"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150.87843,360.66993 1.06383,102.12766 327.12768,-0.53192 -7.97872,5.85107"
|
||||
id="path3347-8"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-444.27307"
|
||||
y="124.17301"
|
||||
id="text3353-6"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4193"
|
||||
x="-444.27307"
|
||||
y="124.17301">desired</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4195"
|
||||
x="-444.27307"
|
||||
y="139.79802">filament</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150.87843,360.66993 -5.31915,8.51063"
|
||||
id="path3359-8"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 180.50856,461.8868 16.05678,-60.94329 120.91958,-1.06383 16.53759,62.00712"
|
||||
id="path3361-4"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.8 KiB |
BIN
docs/img/ooze.svg.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
207
docs/img/pressure-advance.svg
Normal file
@@ -0,0 +1,207 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="102.61139mm"
|
||||
height="99.266594mm"
|
||||
viewBox="0 0 363.58366 351.73204"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="pressure-advance.svg"
|
||||
inkscape:export-filename="/home/kevin/src/reprap/firmware/klipper/docs/img/pressure-advance.svg.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.97968464"
|
||||
inkscape:cx="147.06528"
|
||||
inkscape:cy="169.55487"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1091"
|
||||
inkscape:window-height="588"
|
||||
inkscape:window-x="419"
|
||||
inkscape:window-y="273"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
showborder="false"
|
||||
inkscape:snap-global="false"
|
||||
showguides="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3436"
|
||||
originx="17.089787"
|
||||
originy="126.61899" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-118.13451,-140.19216)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150.47693,249.82417 0.58688,123.9666 m 0,-21.42857 327.12765,-0.53192 -7.97871,5.85107"
|
||||
id="path3347"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-334.86746"
|
||||
y="122.91875"
|
||||
id="text3353"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12816"
|
||||
x="-334.86746"
|
||||
y="122.91875">extruder</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12818"
|
||||
x="-334.86746"
|
||||
y="138.54375">velocity</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 -5.31915,8.51063"
|
||||
id="path3359"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 316.60647,311.78473 16.53759,62.00711 m -154.57776,-52.12767 16.05677,-60.94328 1.06383,29.78724 m 0,0 120.91957,-1.06383 0,22.34043"
|
||||
id="path3361"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 148.75077,140.45716 1.06383,102.12766 327.12769,-0.53192 -7.97872,5.85107"
|
||||
id="path3347-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-237.05733"
|
||||
y="140.25932"
|
||||
id="text3353-2"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12733-6"
|
||||
x="-237.05733"
|
||||
y="140.25932">head velocity</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 148.75077,140.45716 -5.31915,8.51063"
|
||||
id="path3359-7"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 178.3809,241.67403 16.05679,-60.94329 120.91958,-1.06383 16.53759,62.00712"
|
||||
id="path3361-5"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.78742969px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 331.74721,241.47248 16.72764,-63.00288 133.24332,0"
|
||||
id="path3361-7-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="198.86868"
|
||||
y="174.67674"
|
||||
id="text12656-9"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12658-8"
|
||||
x="198.86868"
|
||||
y="174.67674">extrude move</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="355.25165"
|
||||
y="173.61293"
|
||||
id="text12660-7"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12662-2"
|
||||
x="355.25165"
|
||||
y="173.61293">non-extrude move</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 151.94226,384.0742 1.06383,102.12766 327.12769,-0.53192 -7.97872,5.85107"
|
||||
id="path3347-2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-467.65732"
|
||||
y="122.73453"
|
||||
id="text3353-1"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12868"
|
||||
x="-467.65732"
|
||||
y="122.73453">extruder</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12870"
|
||||
x="-467.65732"
|
||||
y="138.35953">pressure</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 151.94226,384.0742 -5.31915,8.51063"
|
||||
id="path3359-5"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 181.57239,485.29107 16.05679,-60.94329 120.91958,-1.06383 16.53759,62.00712"
|
||||
id="path3361-9"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="438.03586"
|
||||
y="499.53384"
|
||||
id="text3349"
|
||||
sodipodi:linespacing="100%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3351"
|
||||
x="438.03586"
|
||||
y="499.53384">time</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.8 KiB |
BIN
docs/img/pressure-advance.svg.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
180
docs/img/pressure-cornering.svg
Normal file
@@ -0,0 +1,180 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="102.04809mm"
|
||||
height="61.494076mm"
|
||||
viewBox="0 0 361.58771 217.8924"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="pressure-cornering.svg"
|
||||
inkscape:export-filename="/home/kevin/src/reprap/firmware/klipper/docs/img/pressure-cornering.svg.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.98"
|
||||
inkscape:cx="110.74341"
|
||||
inkscape:cy="35.715236"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1091"
|
||||
inkscape:window-height="588"
|
||||
inkscape:window-x="656"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
showborder="false"
|
||||
inkscape:snap-global="false"
|
||||
showguides="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3436"
|
||||
originx="17.089805"
|
||||
originy="-7.2206491" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-118.13449,-140.19216)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
|
||||
id="path3347"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-334.86746"
|
||||
y="122.91875"
|
||||
id="text3353"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12816"
|
||||
x="-334.86746"
|
||||
y="122.91875">extruder</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12818"
|
||||
x="-334.86746"
|
||||
y="138.54375">velocity</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 -5.31915,8.51063"
|
||||
id="path3359"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 178.5663,321.66417 16.05677,-60.94328 1.06383,29.78724 m 0,0 117.21969,-0.77616 5.99519,7.3871 5.9952,-6.89861 131.50541,-0.77616"
|
||||
id="path3361"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 148.75077,140.45716 1.06383,102.12766 327.12769,-0.53192 -7.97872,5.85107"
|
||||
id="path3347-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.50000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-237.05733"
|
||||
y="140.25932"
|
||||
id="text3353-2"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12733-6"
|
||||
x="-237.05733"
|
||||
y="140.25932">head velocity</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 148.75077,140.45716 -5.31915,8.51063"
|
||||
id="path3359-7"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.00000024;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 178.3809,241.67403 16.05679,-60.94329 120.91958,-1.06383 4.29269,10.98671"
|
||||
id="path3361-5"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 319.50231,193.5133 4.48274,-15.0437 133.24332,0"
|
||||
id="path3361-7-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="198.86868"
|
||||
y="174.67674"
|
||||
id="text12656-9"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12891"
|
||||
x="198.86868"
|
||||
y="174.67674">first extrude</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.25px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="355.25165"
|
||||
y="173.61293"
|
||||
id="text12660-7"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12893"
|
||||
x="355.25165"
|
||||
y="173.61293">second extrude</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:4, 2, 1, 2;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 314.05288,288.86296 0,17.34694 5.10204,17.34694 -1.02041,-48.97959 7.14286,-17.34694 0,31.63265"
|
||||
id="path12895"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="435.80841"
|
||||
y="366.8262"
|
||||
id="text3349"
|
||||
sodipodi:linespacing="100%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3351"
|
||||
x="435.80841"
|
||||
y="366.8262">time</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.4 KiB |
BIN
docs/img/pressure-cornering.svg.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
132
docs/img/smoothed.svg
Normal file
@@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="97.22496mm"
|
||||
height="32.550285mm"
|
||||
viewBox="0 0 344.49789 115.33566"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="smoothed.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.75"
|
||||
inkscape:cx="167.32577"
|
||||
inkscape:cy="45.708959"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="840"
|
||||
inkscape:window-height="628"
|
||||
inkscape:window-x="162"
|
||||
inkscape:window-y="50"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
showborder="false"
|
||||
inkscape:snap-global="false"
|
||||
showguides="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3436" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-135.22429,-249.96955)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
|
||||
id="path3347"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="434.04257"
|
||||
y="365.1282"
|
||||
id="text3349"
|
||||
sodipodi:linespacing="100%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3351"
|
||||
x="434.04257"
|
||||
y="365.1282">time</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-313.86618"
|
||||
y="140.27856"
|
||||
id="text3353"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3355"
|
||||
x="-313.86618"
|
||||
y="140.27856">velocity</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 -5.31915,8.51063"
|
||||
id="path3359"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 165.50998,351.59092 15.42857,-35.42857 35.71428,0 11.71429,36.57143"
|
||||
id="path3362"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.07142997px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 227.83578,352.85633 15.76217,-47.37239 22.00135,0 5.58242,17.6933"
|
||||
id="path3362-3"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.21482575px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 271.35434,323.73296 11.15454,-30.54076 19.27119,-0.11152 8.73883,29.51288"
|
||||
id="path3362-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.01949775px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 311.26144,322.49329 11.63197,-21.06975 15.33656,0 13.51652,50.22357"
|
||||
id="path3362-7"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2.00000012, 1.00000006, 0.50000003, 1.00000006;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 165.50999,351.59092 33.14285,-34.28571 29.14286,34.28571"
|
||||
id="path3426"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
BIN
docs/img/smoothed.svg.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
102
docs/img/trapezoid.svg
Normal file
@@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="97.22496mm"
|
||||
height="32.550285mm"
|
||||
viewBox="0 0 344.49789 115.33566"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="trapezoid.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.94"
|
||||
inkscape:cx="294.7319"
|
||||
inkscape:cy="45.708959"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1091"
|
||||
inkscape:window-height="588"
|
||||
inkscape:window-x="113"
|
||||
inkscape:window-y="31"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
showborder="false" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-135.22429,-249.96955)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
|
||||
id="path3347"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="434.04257"
|
||||
y="365.1282"
|
||||
id="text3349"
|
||||
sodipodi:linespacing="100%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3351"
|
||||
x="434.04257"
|
||||
y="365.1282">time</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-313.86618"
|
||||
y="140.27856"
|
||||
id="text3353"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3355"
|
||||
x="-313.86618"
|
||||
y="140.27856">velocity</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 -5.31915,8.51063"
|
||||
id="path3359"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 179.78723,351.29837 41.48937,-60.63829 158.51063,0 37.23405,60.63829"
|
||||
id="path3361"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
BIN
docs/img/trapezoid.svg.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
182
docs/img/trapezoids.svg
Normal file
@@ -0,0 +1,182 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="97.22496mm"
|
||||
height="32.550285mm"
|
||||
viewBox="0 0 344.49789 115.33566"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="trapezoids.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="2.67"
|
||||
inkscape:cx="164.48301"
|
||||
inkscape:cy="76.011989"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1091"
|
||||
inkscape:window-height="588"
|
||||
inkscape:window-x="113"
|
||||
inkscape:window-y="31"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
showborder="false"
|
||||
inkscape:snap-global="false"
|
||||
showguides="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3436" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-135.22429,-249.96955)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
|
||||
id="path3347"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="434.04257"
|
||||
y="365.1282"
|
||||
id="text3349"
|
||||
sodipodi:linespacing="100%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3351"
|
||||
x="434.04257"
|
||||
y="365.1282">time</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-313.86618"
|
||||
y="140.27856"
|
||||
id="text3353"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3355"
|
||||
x="-313.86618"
|
||||
y="140.27856">velocity</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 -5.31915,8.51063"
|
||||
id="path3359"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.62366331px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 179.63013,351.45141 16.05677,-60.94328 61.3451,0 6.96312,32.21987"
|
||||
id="path3361"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.7558428px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 263.83557,321.45597 11.41184,-61.95224 59.85662,0 9.80496,29.88826"
|
||||
id="path3361-7"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.7558428px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 344.93248,289.80297 59.85663,0 15.12411,61.95225"
|
||||
id="path3361-7-7"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 263.86975,321.69491 0.37453,30.52957"
|
||||
id="path3412"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 345.25947,289.31929 0.14909,62.46104"
|
||||
id="path3414"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 222.45831,289.77329 -0.0598,62.07666"
|
||||
id="path3416"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:20.00000119px;line-height:125%;font-family:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;-inkscape-font-specification:'DejaVu Sans, Normal';font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr;"
|
||||
x="193.73491"
|
||||
y="338.70944"
|
||||
id="text3418"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3420"
|
||||
x="193.73491"
|
||||
y="338.70944">1</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:20.00000191px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="236.28812"
|
||||
y="338.70947"
|
||||
id="text3422"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3434"
|
||||
x="236.28812"
|
||||
y="338.70947">2</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:20.00000191px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="294.79874"
|
||||
y="335.51794"
|
||||
id="text3426"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3428"
|
||||
x="294.79874"
|
||||
y="335.51794">3</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:20.00000119px;line-height:125%;font-family:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;-inkscape-font-specification:'DejaVu Sans, Normal';font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr;"
|
||||
x="367.13916"
|
||||
y="337.6456"
|
||||
id="text3430"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3432"
|
||||
x="367.13916"
|
||||
y="337.6456">4</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.3 KiB |
BIN
docs/img/trapezoids.svg.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
241
docs/img/virtual-tower.svg
Normal file
@@ -0,0 +1,241 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64.619751mm"
|
||||
height="27.45583mm"
|
||||
viewBox="0 0 228.96762 97.284438"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="virtual-tower.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker6618"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Mend">
|
||||
<path
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)"
|
||||
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
id="path6620"
|
||||
inkscape:connector-curvature="0" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="marker6500"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path6502"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)"
|
||||
inkscape:connector-curvature="0" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow1Mend"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always">
|
||||
<path
|
||||
id="path5747"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)"
|
||||
inkscape:connector-curvature="0" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mstart"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow1Mstart"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path5744"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(0.4,0,0,0.4,4,0)"
|
||||
inkscape:connector-curvature="0" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow1Mend-1"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4329-1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker6082"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
inkscape:collect="always">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)"
|
||||
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
id="path6084" />
|
||||
</marker>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.75"
|
||||
inkscape:cx="169.7719"
|
||||
inkscape:cy="-4.0565054"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="922"
|
||||
inkscape:window-height="628"
|
||||
inkscape:window-x="162"
|
||||
inkscape:window-y="50"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
showborder="false"
|
||||
inkscape:snap-global="false"
|
||||
showguides="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3436"
|
||||
originx="-14.085234"
|
||||
originy="-95.286988" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-149.30952,-172.73378)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Arrow1Mstart);marker-end:url(#Arrow1Mend)"
|
||||
d="m 176.36712,256.16235 200.00001,-0.57143"
|
||||
id="path5510"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-254.01012"
|
||||
y="369.20834"
|
||||
id="text6058"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(-0.01833576,-0.99983189,0.99983189,-0.01833576,0,0)"
|
||||
inkscape:transform-center-x="-8.0000002"
|
||||
inkscape:transform-center-y="12.571429"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan10365"
|
||||
x="-254.01012"
|
||||
y="369.20834">virtual tower</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="148.36713"
|
||||
y="269.87662"
|
||||
id="text6062"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6064"
|
||||
x="148.36713"
|
||||
y="269.87662">line of movement</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="237.50998"
|
||||
y="251.01949"
|
||||
id="text6066"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6068"
|
||||
x="237.50998"
|
||||
y="251.01949">move</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:7.00000048;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 358.08141,255.01949 0,-82.28571"
|
||||
id="path10351"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 220.36713,255.01949 357.50998,173.30521"
|
||||
id="path10373"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="200.36713"
|
||||
y="187.59093"
|
||||
id="text10375"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan10377"
|
||||
x="200.36713"
|
||||
y="187.59093">virtual arm</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker6082)"
|
||||
d="m 261.76375,182.85539 c 22.73118,-0.70136 26.45506,3.7437 40.00001,18.28573"
|
||||
id="path6074"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-1)"
|
||||
d="m 219.61431,255.37268 70.93001,0.37408"
|
||||
id="path3514-5"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.5 KiB |
BIN
docs/img/virtual-tower.svg.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
241
docs/img/xy+z-tower.svg
Normal file
@@ -0,0 +1,241 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64.619751mm"
|
||||
height="27.45583mm"
|
||||
viewBox="0 0 228.96762 97.284438"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="xy+z-tower.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker6618"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Mend">
|
||||
<path
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)"
|
||||
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
id="path6620"
|
||||
inkscape:connector-curvature="0" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="marker6500"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path6502"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)"
|
||||
inkscape:connector-curvature="0" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow1Mend"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always">
|
||||
<path
|
||||
id="path5747"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)"
|
||||
inkscape:connector-curvature="0" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mstart"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow1Mstart"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path5744"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(0.4,0,0,0.4,4,0)"
|
||||
inkscape:connector-curvature="0" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow1Mend-1"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4329-1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1"
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:isstock="true"
|
||||
style="overflow:visible"
|
||||
id="marker6082"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto"
|
||||
inkscape:stockid="Arrow1Mend"
|
||||
inkscape:collect="always">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
transform="matrix(-0.4,0,0,-0.4,-4,0)"
|
||||
style="fill:#4b4b4b;fill-opacity:1;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1pt;stroke-opacity:1"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 Z"
|
||||
id="path6084" />
|
||||
</marker>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.75"
|
||||
inkscape:cx="169.7719"
|
||||
inkscape:cy="-4.0565054"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="922"
|
||||
inkscape:window-height="628"
|
||||
inkscape:window-x="162"
|
||||
inkscape:window-y="50"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
showborder="false"
|
||||
inkscape:snap-global="false"
|
||||
showguides="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3436"
|
||||
originx="-14.085234"
|
||||
originy="-95.286988" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-149.30952,-172.73378)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Arrow1Mstart);marker-end:url(#Arrow1Mend)"
|
||||
d="m 176.36712,256.16235 200.00001,-0.57143"
|
||||
id="path5510"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-119.27526"
|
||||
y="434.37329"
|
||||
id="text6058"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(0.32454206,-0.94587127,0.94587127,0.32454206,0,0)"
|
||||
inkscape:transform-center-x="-3.9400734"
|
||||
inkscape:transform-center-y="14.789029"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan10365"
|
||||
x="-119.27526"
|
||||
y="434.37329">virtual tower</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="148.36713"
|
||||
y="269.87662"
|
||||
id="text6062"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6064"
|
||||
x="148.36713"
|
||||
y="269.87662">line of movement</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="237.50998"
|
||||
y="251.01949"
|
||||
id="text6066"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6068"
|
||||
x="237.50998"
|
||||
y="251.01949">move</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:7.00000048;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 354.70239,255.76769 28.12779,-77.32895"
|
||||
id="path10351"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 220.36713,255.01949 380.93855,178.44807"
|
||||
id="path10373"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.00000095px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="200.36713"
|
||||
y="187.59093"
|
||||
id="text10375"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan10377"
|
||||
x="200.36713"
|
||||
y="187.59093">virtual arm</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#4b4b4b;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker6082)"
|
||||
d="m 261.76375,182.85539 c 22.73118,-0.70136 26.45506,3.7437 40.00001,18.28573"
|
||||
id="path6074"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow1Mend-1)"
|
||||
d="m 219.61431,255.37268 70.93001,0.37408"
|
||||
id="path3514-5"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.5 KiB |
BIN
docs/img/xy+z-tower.svg.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
126
docs/img/zigzag.svg
Normal file
@@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="97.22496mm"
|
||||
height="32.550285mm"
|
||||
viewBox="0 0 344.49789 115.33566"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="zigzag.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.75"
|
||||
inkscape:cx="167.32577"
|
||||
inkscape:cy="45.708959"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="840"
|
||||
inkscape:window-height="628"
|
||||
inkscape:window-x="160"
|
||||
inkscape:window-y="35"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
showborder="false"
|
||||
inkscape:snap-global="false"
|
||||
showguides="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3436" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-135.22429,-249.96955)">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 1.06383,102.12765 327.12765,-0.53192 -7.97871,5.85107"
|
||||
id="path3347"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="434.04257"
|
||||
y="365.1282"
|
||||
id="text3349"
|
||||
sodipodi:linespacing="100%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3351"
|
||||
x="434.04257"
|
||||
y="365.1282">time</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.5px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="-313.86618"
|
||||
y="140.27856"
|
||||
id="text3353"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="matrix(-0.01601372,-0.99987177,0.99987177,-0.01601372,0,0)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3355"
|
||||
x="-313.86618"
|
||||
y="140.27856">velocity</tspan></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 150,250.23455 -5.31915,8.51063"
|
||||
id="path3359"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 165.50998,351.59092 24.57143,-73.14286 23.42857,73.14286"
|
||||
id="path3362"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 214.08032,352.31321 24.57144,-73.14287 13.14286,43.42858"
|
||||
id="path3362-3"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.09757805px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 252.42963,323.78006 17.64919,-60.34771 14.22362,59.20485"
|
||||
id="path3362-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 284.36604,322.59892 14.85715,-44.00001 3.71429,0 16.85715,73.14287"
|
||||
id="path3362-7"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
BIN
docs/img/zigzag.svg.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
14
docs/prints/square.scad
Normal file
@@ -0,0 +1,14 @@
|
||||
// Test square
|
||||
//
|
||||
// Generate STL using OpenSCAD:
|
||||
// openscad square.scad -o square.stl
|
||||
|
||||
square_width = 5;
|
||||
square_size = 60;
|
||||
square_height = 5;
|
||||
|
||||
difference() {
|
||||
cube([square_size, square_size, square_height]);
|
||||
translate([square_width, square_width, -1])
|
||||
cube([square_size-2*square_width, square_size-2*square_width, square_height+2]);
|
||||
}
|
||||
226
docs/prints/square.stl
Normal file
@@ -0,0 +1,226 @@
|
||||
solid OpenSCAD_Model
|
||||
facet normal -1 0 0
|
||||
outer loop
|
||||
vertex 0 0 0
|
||||
vertex 0 60 5
|
||||
vertex 0 60 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -1 -0 0
|
||||
outer loop
|
||||
vertex 0 60 5
|
||||
vertex 0 0 0
|
||||
vertex 0 0 5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 1
|
||||
outer loop
|
||||
vertex 60 60 5
|
||||
vertex 55 55 5
|
||||
vertex 60 0 5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 1
|
||||
outer loop
|
||||
vertex 60 60 5
|
||||
vertex 5 55 5
|
||||
vertex 55 55 5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 1
|
||||
outer loop
|
||||
vertex 5 55 5
|
||||
vertex 0 60 5
|
||||
vertex 5 5 5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -0 0 1
|
||||
outer loop
|
||||
vertex 0 60 5
|
||||
vertex 5 55 5
|
||||
vertex 60 60 5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -0 0 1
|
||||
outer loop
|
||||
vertex 55 5 5
|
||||
vertex 60 0 5
|
||||
vertex 55 55 5
|
||||
endloop
|
||||
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 5 5 5
|
||||
vertex 0 0 5
|
||||
vertex 60 0 5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 1
|
||||
outer loop
|
||||
vertex 0 0 5
|
||||
vertex 5 5 5
|
||||
vertex 0 60 5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 1 -0 0
|
||||
outer loop
|
||||
vertex 60 0 5
|
||||
vertex 60 60 0
|
||||
vertex 60 60 5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 1 0 0
|
||||
outer loop
|
||||
vertex 60 60 0
|
||||
vertex 60 0 5
|
||||
vertex 60 0 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 1 -0
|
||||
outer loop
|
||||
vertex 60 60 0
|
||||
vertex 0 60 5
|
||||
vertex 60 60 5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 1 0
|
||||
outer loop
|
||||
vertex 0 60 5
|
||||
vertex 60 60 0
|
||||
vertex 0 60 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 -1
|
||||
outer loop
|
||||
vertex 60 0 0
|
||||
vertex 55 5 0
|
||||
vertex 60 60 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 -1
|
||||
outer loop
|
||||
vertex 60 0 0
|
||||
vertex 5 5 0
|
||||
vertex 55 5 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -0 0 -1
|
||||
outer loop
|
||||
vertex 5 5 0
|
||||
vertex 0 0 0
|
||||
vertex 5 55 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 -1
|
||||
outer loop
|
||||
vertex 0 0 0
|
||||
vertex 5 5 0
|
||||
vertex 60 0 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 55 0
|
||||
vertex 0 60 0
|
||||
vertex 60 60 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 0 -1
|
||||
outer loop
|
||||
vertex 0 60 0
|
||||
vertex 5 55 0
|
||||
vertex 0 0 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -1 0
|
||||
outer loop
|
||||
vertex 0 0 0
|
||||
vertex 60 0 5
|
||||
vertex 0 0 5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -1 -0
|
||||
outer loop
|
||||
vertex 60 0 5
|
||||
vertex 0 0 0
|
||||
vertex 60 0 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 1 -0 0
|
||||
outer loop
|
||||
vertex 5 5 5
|
||||
vertex 5 55 0
|
||||
vertex 5 55 5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 1 0 0
|
||||
outer loop
|
||||
vertex 5 55 0
|
||||
vertex 5 5 5
|
||||
vertex 5 5 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -1 0 0
|
||||
outer loop
|
||||
vertex 55 5 0
|
||||
vertex 55 55 5
|
||||
vertex 55 55 0
|
||||
endloop
|
||||
endfacet
|
||||
facet normal -1 -0 0
|
||||
outer loop
|
||||
vertex 55 55 5
|
||||
vertex 55 5 0
|
||||
vertex 55 5 5
|
||||
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 0 -1 0
|
||||
outer loop
|
||||
vertex 5 55 0
|
||||
vertex 55 55 5
|
||||
vertex 5 55 5
|
||||
endloop
|
||||
endfacet
|
||||
facet normal 0 -1 -0
|
||||
outer loop
|
||||
vertex 55 55 5
|
||||
vertex 5 55 0
|
||||
vertex 55 55 0
|
||||
endloop
|
||||
endfacet
|
||||
endsolid OpenSCAD_Model
|
||||
@@ -13,21 +13,19 @@ class CartKinematics:
|
||||
self.steppers = [stepper.PrinterStepper(
|
||||
printer, config.getsection('stepper_' + n), n)
|
||||
for n in ['x', 'y', 'z']]
|
||||
self.max_z_velocity = config.getfloat('max_z_velocity', 9999999.9)
|
||||
self.max_z_accel = config.getfloat('max_z_accel', 9999999.9)
|
||||
self.max_z_velocity = config.getfloat(
|
||||
'max_z_velocity', 9999999.9, above=0.)
|
||||
self.max_z_accel = config.getfloat(
|
||||
'max_z_accel', 9999999.9, above=0.)
|
||||
self.need_motor_enable = True
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
def set_max_jerk(self, max_xy_halt_velocity, max_accel):
|
||||
def set_max_jerk(self, max_xy_halt_velocity, max_velocity, max_accel):
|
||||
self.steppers[0].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(0., self.max_z_accel)
|
||||
def build_config(self):
|
||||
for stepper in self.steppers:
|
||||
stepper.build_config()
|
||||
def set_position(self, newpos):
|
||||
for i in StepList:
|
||||
s = self.steppers[i]
|
||||
s.mcu_stepper.set_position(int(newpos[i]*s.inv_step_dist + 0.5))
|
||||
self.steppers[i].mcu_stepper.set_position(newpos[i])
|
||||
def home(self, homing_state):
|
||||
# Each axis is homed independently and in order
|
||||
for axis in homing_state.get_axes():
|
||||
@@ -58,7 +56,7 @@ class CartKinematics:
|
||||
homing_state.home(
|
||||
list(coord), homepos, [s], s.homing_speed/2.0, second_home=True)
|
||||
# Set final homed position
|
||||
coord[axis] = s.position_endstop + s.get_homed_offset()*s.step_dist
|
||||
coord[axis] = s.position_endstop + s.get_homed_offset()
|
||||
homing_state.set_homed_position(coord)
|
||||
def motor_off(self, move_time):
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
@@ -102,46 +100,33 @@ class CartKinematics:
|
||||
def move(self, move_time, move):
|
||||
if self.need_motor_enable:
|
||||
self._check_motor_enable(move_time, move)
|
||||
inv_accel = 1. / move.accel
|
||||
inv_cruise_v = 1. / move.cruise_v
|
||||
for i in StepList:
|
||||
if not move.axes_d[i]:
|
||||
axis_d = move.axes_d[i]
|
||||
if not axis_d:
|
||||
continue
|
||||
mcu_stepper = self.steppers[i].mcu_stepper
|
||||
mcu_time = mcu_stepper.print_to_mcu_time(move_time)
|
||||
step_pos = mcu_stepper.commanded_position
|
||||
inv_step_dist = self.steppers[i].inv_step_dist
|
||||
step_offset = step_pos - move.start_pos[i] * inv_step_dist
|
||||
steps = move.axes_d[i] * inv_step_dist
|
||||
move_step_d = move.move_d / abs(steps)
|
||||
start_pos = move.start_pos[i]
|
||||
axis_r = abs(axis_d) / move.move_d
|
||||
accel = move.accel * axis_r
|
||||
cruise_v = move.cruise_v * axis_r
|
||||
|
||||
# Acceleration steps
|
||||
accel_multiplier = 2.0 * move_step_d * inv_accel
|
||||
if move.accel_r:
|
||||
#t = sqrt(2*pos/accel + (start_v/accel)**2) - start_v/accel
|
||||
accel_time_offset = move.start_v * inv_accel
|
||||
accel_sqrt_offset = accel_time_offset**2
|
||||
accel_steps = move.accel_r * steps
|
||||
count = mcu_stepper.step_sqrt(
|
||||
mcu_time - accel_time_offset, accel_steps, step_offset
|
||||
, accel_sqrt_offset, accel_multiplier)
|
||||
step_offset += count - accel_steps
|
||||
accel_d = move.accel_r * axis_d
|
||||
mcu_stepper.step_const(
|
||||
mcu_time, start_pos, accel_d, move.start_v * axis_r, accel)
|
||||
start_pos += accel_d
|
||||
mcu_time += move.accel_t
|
||||
# Cruising steps
|
||||
if move.cruise_r:
|
||||
#t = pos/cruise_v
|
||||
cruise_multiplier = move_step_d * inv_cruise_v
|
||||
cruise_steps = move.cruise_r * steps
|
||||
count = mcu_stepper.step_factor(
|
||||
mcu_time, cruise_steps, step_offset, cruise_multiplier)
|
||||
step_offset += count - cruise_steps
|
||||
cruise_d = move.cruise_r * axis_d
|
||||
mcu_stepper.step_const(
|
||||
mcu_time, start_pos, cruise_d, cruise_v, 0.)
|
||||
start_pos += cruise_d
|
||||
mcu_time += move.cruise_t
|
||||
# Deceleration steps
|
||||
if move.decel_r:
|
||||
#t = cruise_v/accel - sqrt((cruise_v/accel)**2 - 2*pos/accel)
|
||||
decel_time_offset = move.cruise_v * inv_accel
|
||||
decel_sqrt_offset = decel_time_offset**2
|
||||
decel_steps = move.decel_r * steps
|
||||
count = mcu_stepper.step_sqrt(
|
||||
mcu_time + decel_time_offset, decel_steps, step_offset
|
||||
, decel_sqrt_offset, -accel_multiplier)
|
||||
decel_d = move.decel_r * axis_d
|
||||
mcu_stepper.step_const(
|
||||
mcu_time, start_pos, decel_d, cruise_v, -accel)
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
# Wrapper around C helper code
|
||||
#
|
||||
# Copyright (C) 2016 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.
|
||||
import os, logging
|
||||
import cffi
|
||||
|
||||
COMPILE_CMD = "gcc -Wall -g -O -shared -fPIC -o %s %s"
|
||||
|
||||
######################################################################
|
||||
# c_helper.so compiling
|
||||
######################################################################
|
||||
|
||||
COMPILE_CMD = "gcc -Wall -g -O2 -shared -fPIC -o %s %s"
|
||||
SOURCE_FILES = ['stepcompress.c', 'serialqueue.c', 'pyhelper.c']
|
||||
DEST_LIB = "c_helper.so"
|
||||
OTHER_FILES = ['list.h', 'serialqueue.h', 'pyhelper.h']
|
||||
@@ -16,31 +21,22 @@ defs_stepcompress = """
|
||||
, uint32_t queue_step_msgid, uint32_t set_next_step_dir_msgid
|
||||
, uint32_t invert_sdir, uint32_t oid);
|
||||
void stepcompress_free(struct stepcompress *sc);
|
||||
void stepcompress_push(struct stepcompress *sc, double step_clock
|
||||
int stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock);
|
||||
int stepcompress_set_homing(struct stepcompress *sc, uint64_t homing_clock);
|
||||
int stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len);
|
||||
|
||||
int stepcompress_push(struct stepcompress *sc, double step_clock
|
||||
, int32_t sdir);
|
||||
int32_t stepcompress_push_factor(struct stepcompress *sc
|
||||
, double steps, double step_offset
|
||||
, double clock_offset, double factor);
|
||||
int32_t stepcompress_push_sqrt(struct stepcompress *sc
|
||||
, double steps, double step_offset
|
||||
, double clock_offset, double sqrt_offset, double factor);
|
||||
int32_t stepcompress_push_delta_const(struct stepcompress *sc
|
||||
, double clock_offset, double dist, double start_pos
|
||||
, double inv_velocity, double step_dist, double height
|
||||
, double closestxy_d, double closest_height2, double movez_r);
|
||||
int32_t stepcompress_push_delta_accel(struct stepcompress *sc
|
||||
, double clock_offset, double dist, double start_pos
|
||||
, double accel_multiplier, double step_dist, double height
|
||||
, double closestxy_d, double closest_height2, double movez_r);
|
||||
void stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock);
|
||||
void stepcompress_queue_msg(struct stepcompress *sc
|
||||
, uint32_t *data, int len);
|
||||
uint32_t stepcompress_get_errors(struct stepcompress *sc);
|
||||
int32_t stepcompress_push_const(struct stepcompress *sc, double clock_offset
|
||||
, double step_offset, double steps, double start_sv, double accel);
|
||||
int32_t stepcompress_push_delta(struct stepcompress *sc
|
||||
, double clock_offset, double move_sd, double start_sv, double accel
|
||||
, double height, double startxy_sd, double arm_d, double movez_r);
|
||||
|
||||
struct steppersync *steppersync_alloc(struct serialqueue *sq
|
||||
, struct stepcompress **sc_list, int sc_num, int move_num);
|
||||
void steppersync_free(struct steppersync *ss);
|
||||
void steppersync_flush(struct steppersync *ss, uint64_t move_clock);
|
||||
int steppersync_flush(struct steppersync *ss, uint64_t move_clock);
|
||||
"""
|
||||
|
||||
defs_serialqueue = """
|
||||
@@ -65,7 +61,6 @@ defs_serialqueue = """
|
||||
void serialqueue_set_baud_adjust(struct serialqueue *sq, double baud_adjust);
|
||||
void serialqueue_set_clock_est(struct serialqueue *sq, double est_clock
|
||||
, double last_ack_time, uint64_t last_ack_clock);
|
||||
void serialqueue_flush_ready(struct serialqueue *sq);
|
||||
void serialqueue_get_stats(struct serialqueue *sq, char *buf, int len);
|
||||
int serialqueue_extract_old(struct serialqueue *sq, int sentq
|
||||
, struct pull_queue_message *q, int max);
|
||||
@@ -73,6 +68,7 @@ defs_serialqueue = """
|
||||
|
||||
defs_pyhelper = """
|
||||
void set_python_logging_callback(void (*func)(const char *));
|
||||
double get_monotonic(void);
|
||||
"""
|
||||
|
||||
# Return the list of file modification times
|
||||
@@ -88,14 +84,14 @@ def get_mtimes(srcdir, filelist):
|
||||
return out
|
||||
|
||||
# Check if the code needs to be compiled
|
||||
def check_build_code(srcdir):
|
||||
src_times = get_mtimes(srcdir, SOURCE_FILES + OTHER_FILES)
|
||||
obj_times = get_mtimes(srcdir, [DEST_LIB])
|
||||
def check_build_code(srcdir, target, sources, cmd, other_files=[]):
|
||||
src_times = get_mtimes(srcdir, sources + other_files)
|
||||
obj_times = get_mtimes(srcdir, [target])
|
||||
if not obj_times or max(src_times) > min(obj_times):
|
||||
logging.info("Building C code module")
|
||||
srcfiles = [os.path.join(srcdir, fname) for fname in SOURCE_FILES]
|
||||
destlib = os.path.join(srcdir, DEST_LIB)
|
||||
os.system(COMPILE_CMD % (destlib, ' '.join(srcfiles)))
|
||||
logging.info("Building C code module %s" % (target,))
|
||||
srcfiles = [os.path.join(srcdir, fname) for fname in sources]
|
||||
destlib = os.path.join(srcdir, target)
|
||||
os.system(cmd % (destlib, ' '.join(srcfiles)))
|
||||
|
||||
FFI_main = None
|
||||
FFI_lib = None
|
||||
@@ -106,7 +102,8 @@ def get_ffi():
|
||||
global FFI_main, FFI_lib, pyhelper_logging_callback
|
||||
if FFI_lib is None:
|
||||
srcdir = os.path.dirname(os.path.realpath(__file__))
|
||||
check_build_code(srcdir)
|
||||
check_build_code(srcdir, DEST_LIB, SOURCE_FILES, COMPILE_CMD
|
||||
, OTHER_FILES)
|
||||
FFI_main = cffi.FFI()
|
||||
FFI_main.cdef(defs_stepcompress)
|
||||
FFI_main.cdef(defs_serialqueue)
|
||||
@@ -119,3 +116,20 @@ def get_ffi():
|
||||
"void(const char *)", logging_callback)
|
||||
FFI_lib.set_python_logging_callback(pyhelper_logging_callback)
|
||||
return FFI_main, FFI_lib
|
||||
|
||||
|
||||
######################################################################
|
||||
# hub-ctrl hub power controller
|
||||
######################################################################
|
||||
|
||||
HC_COMPILE_CMD = "gcc -Wall -g -O2 -o %s %s -lusb"
|
||||
HC_SOURCE_FILES = ['hub-ctrl.c']
|
||||
HC_SOURCE_DIR = '../lib/hub-ctrl'
|
||||
HC_TARGET = "hub-ctrl"
|
||||
HC_CMD = "sudo %s/hub-ctrl -h 0 -P 2 -p %d"
|
||||
|
||||
def run_hub_ctrl(enable_power):
|
||||
srcdir = os.path.dirname(os.path.realpath(__file__))
|
||||
hubdir = os.path.join(srcdir, HC_SOURCE_DIR)
|
||||
check_build_code(hubdir, HC_TARGET, HC_SOURCE_FILES, HC_COMPILE_CMD)
|
||||
os.system(HC_CMD % (hubdir, enable_power))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# Script to implement a test console with firmware over serial port
|
||||
#
|
||||
# Copyright (C) 2016 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.
|
||||
import sys, optparse, os, re, logging
|
||||
@@ -16,6 +16,7 @@ class KeyboardReader:
|
||||
self.reactor = reactor
|
||||
self.fd = sys.stdin.fileno()
|
||||
util.set_nonblock(self.fd)
|
||||
self.mcu_freq = 0
|
||||
self.pins = None
|
||||
self.data = ""
|
||||
reactor.register_fd(self.fd, self.process_kbd)
|
||||
@@ -24,49 +25,51 @@ class KeyboardReader:
|
||||
self.eval_globals = {}
|
||||
def connect(self, eventtime):
|
||||
self.ser.connect()
|
||||
self.ser.handle_default = self.handle_default
|
||||
self.mcu_freq = self.ser.msgparser.get_constant_float('CLOCK_FREQ')
|
||||
mcu = self.ser.msgparser.get_constant('MCU')
|
||||
self.pins = pins.get_pin_map(mcu)
|
||||
self.reactor.unregister_timer(self.connect_timer)
|
||||
return self.reactor.NEVER
|
||||
def output(self, msg):
|
||||
sys.stdout.write("%s\n" % (msg,))
|
||||
sys.stdout.flush()
|
||||
def handle_default(self, params):
|
||||
self.output(self.ser.msgparser.format_params(params))
|
||||
def update_evals(self, eventtime):
|
||||
f = int(self.ser.msgparser.config.get('CLOCK_FREQ', 1))
|
||||
c = self.ser.get_clock(eventtime)
|
||||
self.eval_globals['freq'] = f
|
||||
self.eval_globals['clock'] = int(c)
|
||||
self.eval_globals['freq'] = self.mcu_freq
|
||||
self.eval_globals['clock'] = self.ser.get_clock(eventtime)
|
||||
def set_pin_map(self, parts):
|
||||
mcu = self.ser.msgparser.config['MCU']
|
||||
self.pins = pins.map_pins(parts[1], mcu)
|
||||
mcu = self.ser.msgparser.get_constant('MCU')
|
||||
self.pins = pins.get_pin_map(mcu, parts[1])
|
||||
def set_var(self, parts):
|
||||
val = parts[2]
|
||||
try:
|
||||
val = int(val)
|
||||
except ValueError:
|
||||
try:
|
||||
val = float(val)
|
||||
except ValueError:
|
||||
pass
|
||||
self.eval_globals[parts[1]] = val
|
||||
def lookup_pin(self, value):
|
||||
if self.pins is None:
|
||||
self.pins = pins.mcu_to_pins(self.ser.msgparser.config['MCU'])
|
||||
return self.pins[value]
|
||||
def translate(self, line, eventtime):
|
||||
evalparts = re_eval.split(line)
|
||||
if len(evalparts) > 1:
|
||||
self.update_evals(eventtime)
|
||||
try:
|
||||
for i in range(1, len(evalparts), 2):
|
||||
evalparts[i] = str(eval(evalparts[i], self.eval_globals))
|
||||
e = eval(evalparts[i], self.eval_globals)
|
||||
if type(e) == type(0.):
|
||||
e = int(e)
|
||||
evalparts[i] = str(e)
|
||||
except:
|
||||
print "Unable to evaluate: ", line
|
||||
self.output("Unable to evaluate: %s" % (line,))
|
||||
return None
|
||||
line = ''.join(evalparts)
|
||||
print "Eval:", line
|
||||
if self.pins is None and self.ser.msgparser.config:
|
||||
self.pins = pins.mcu_to_pins(self.ser.msgparser.config['MCU'])
|
||||
self.output("Eval: %s" % (line,))
|
||||
if self.pins is not None:
|
||||
try:
|
||||
line = pins.update_command(line, self.pins).strip()
|
||||
line = pins.update_command(
|
||||
line, self.mcu_freq, self.pins).strip()
|
||||
except:
|
||||
print "Unable to map pin: ", line
|
||||
self.output("Unable to map pin: %s" % (line,))
|
||||
return None
|
||||
if line:
|
||||
parts = line.split()
|
||||
@@ -76,7 +79,7 @@ class KeyboardReader:
|
||||
try:
|
||||
msg = self.ser.msgparser.create_command(line)
|
||||
except msgproto.error, e:
|
||||
print "Error:", e
|
||||
self.output("Error: %s" % (str(e),))
|
||||
return None
|
||||
return msg
|
||||
def process_kbd(self, eventtime):
|
||||
|
||||
146
klippy/corexy.py
Normal file
@@ -0,0 +1,146 @@
|
||||
# Code for handling the kinematics of corexy robots
|
||||
#
|
||||
# Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging
|
||||
import stepper, homing
|
||||
|
||||
StepList = (0, 1, 2)
|
||||
|
||||
class CoreXYKinematics:
|
||||
def __init__(self, printer, config):
|
||||
self.steppers = [stepper.PrinterStepper(
|
||||
printer, config.getsection('stepper_' + n), n)
|
||||
for n in ['x', 'y', 'z']]
|
||||
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.max_z_velocity = config.getfloat(
|
||||
'max_z_velocity', 9999999.9, above=0.)
|
||||
self.max_z_accel = config.getfloat(
|
||||
'max_z_accel', 9999999.9, above=0.)
|
||||
self.need_motor_enable = True
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
def set_max_jerk(self, max_xy_halt_velocity, max_velocity, max_accel):
|
||||
self.steppers[0].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(0., self.max_z_accel)
|
||||
def set_position(self, newpos):
|
||||
pos = (newpos[0] + newpos[1], newpos[0] - newpos[1], newpos[2])
|
||||
for i in StepList:
|
||||
self.steppers[i].mcu_stepper.set_position(pos[i])
|
||||
def home(self, homing_state):
|
||||
# Each axis is homed independently and in order
|
||||
for axis in homing_state.get_axes():
|
||||
s = self.steppers[axis]
|
||||
self.limits[axis] = (s.position_min, s.position_max)
|
||||
# Determine moves
|
||||
if s.homing_positive_dir:
|
||||
pos = s.position_endstop - 1.5*(
|
||||
s.position_endstop - s.position_min)
|
||||
rpos = s.position_endstop - s.homing_retract_dist
|
||||
r2pos = rpos - s.homing_retract_dist
|
||||
else:
|
||||
pos = s.position_endstop + 1.5*(
|
||||
s.position_max - s.position_endstop)
|
||||
rpos = s.position_endstop + s.homing_retract_dist
|
||||
r2pos = rpos + s.homing_retract_dist
|
||||
# Initial homing
|
||||
homepos = [None, None, None, None]
|
||||
homepos[axis] = s.position_endstop
|
||||
coord = [None, None, None, None]
|
||||
coord[axis] = pos
|
||||
homing_state.home(list(coord), homepos, [s], s.homing_speed)
|
||||
# Retract
|
||||
coord[axis] = rpos
|
||||
homing_state.retract(list(coord), s.homing_speed)
|
||||
# Home again
|
||||
coord[axis] = r2pos
|
||||
homing_state.home(
|
||||
list(coord), homepos, [s], s.homing_speed/2.0, second_home=True)
|
||||
if axis == 2:
|
||||
# Support endstop phase detection on Z axis
|
||||
coord[axis] = s.position_endstop + s.get_homed_offset()
|
||||
homing_state.set_homed_position(coord)
|
||||
def motor_off(self, move_time):
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
for stepper in self.steppers:
|
||||
stepper.motor_enable(move_time, 0)
|
||||
self.need_motor_enable = True
|
||||
def _check_motor_enable(self, move_time, move):
|
||||
if move.axes_d[0] or move.axes_d[1]:
|
||||
self.steppers[0].motor_enable(move_time, 1)
|
||||
self.steppers[1].motor_enable(move_time, 1)
|
||||
if move.axes_d[2]:
|
||||
self.steppers[2].motor_enable(move_time, 1)
|
||||
need_motor_enable = False
|
||||
for i in StepList:
|
||||
need_motor_enable |= self.steppers[i].need_motor_enable
|
||||
self.need_motor_enable = need_motor_enable
|
||||
def query_endstops(self, print_time):
|
||||
endstops = [(s, s.query_endstop(print_time)) for s in self.steppers]
|
||||
return [(s.name, es.query_endstop_wait()) for s, es in endstops]
|
||||
def _check_endstops(self, move):
|
||||
end_pos = move.end_pos
|
||||
for i in StepList:
|
||||
if (move.axes_d[i]
|
||||
and (end_pos[i] < self.limits[i][0]
|
||||
or end_pos[i] > self.limits[i][1])):
|
||||
if self.limits[i][0] > self.limits[i][1]:
|
||||
raise homing.EndstopMoveError(
|
||||
end_pos, "Must home axis first")
|
||||
raise homing.EndstopMoveError(end_pos)
|
||||
def check_move(self, move):
|
||||
limits = self.limits
|
||||
xpos, ypos = move.end_pos[:2]
|
||||
if (xpos < limits[0][0] or xpos > limits[0][1]
|
||||
or ypos < limits[1][0] or ypos > limits[1][1]):
|
||||
self._check_endstops(move)
|
||||
if not move.axes_d[2]:
|
||||
# Normal XY move - use defaults
|
||||
return
|
||||
# Move with Z - update velocity and accel for slower Z axis
|
||||
self._check_endstops(move)
|
||||
z_ratio = move.move_d / abs(move.axes_d[2])
|
||||
move.limit_speed(
|
||||
self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
|
||||
def move(self, move_time, move):
|
||||
if self.need_motor_enable:
|
||||
self._check_motor_enable(move_time, move)
|
||||
sxp = move.start_pos[0]
|
||||
syp = move.start_pos[1]
|
||||
move_start_pos = (sxp + syp, sxp - syp, move.start_pos[2])
|
||||
exp = move.end_pos[0]
|
||||
eyp = move.end_pos[1]
|
||||
axes_d = ((exp + eyp) - move_start_pos[0],
|
||||
(exp - eyp) - move_start_pos[1], move.axes_d[2])
|
||||
for i in StepList:
|
||||
axis_d = axes_d[i]
|
||||
if not axis_d:
|
||||
continue
|
||||
mcu_stepper = self.steppers[i].mcu_stepper
|
||||
mcu_time = mcu_stepper.print_to_mcu_time(move_time)
|
||||
start_pos = move_start_pos[i]
|
||||
axis_r = abs(axis_d) / move.move_d
|
||||
accel = move.accel * axis_r
|
||||
cruise_v = move.cruise_v * axis_r
|
||||
|
||||
# Acceleration steps
|
||||
if move.accel_r:
|
||||
accel_d = move.accel_r * axis_d
|
||||
mcu_stepper.step_const(
|
||||
mcu_time, start_pos, accel_d, move.start_v * axis_r, accel)
|
||||
start_pos += accel_d
|
||||
mcu_time += move.accel_t
|
||||
# Cruising steps
|
||||
if move.cruise_r:
|
||||
cruise_d = move.cruise_r * axis_d
|
||||
mcu_stepper.step_const(
|
||||
mcu_time, start_pos, cruise_d, cruise_v, 0.)
|
||||
start_pos += cruise_d
|
||||
mcu_time += move.cruise_t
|
||||
# Deceleration steps
|
||||
if move.decel_r:
|
||||
decel_d = move.decel_r * axis_d
|
||||
mcu_stepper.step_const(
|
||||
mcu_time, start_pos, decel_d, cruise_v, -accel)
|
||||
216
klippy/delta.py
@@ -1,6 +1,6 @@
|
||||
# Code for handling the kinematics of linear delta robots
|
||||
#
|
||||
# Copyright (C) 2016 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.
|
||||
import math, logging
|
||||
@@ -8,40 +8,61 @@ import stepper, homing
|
||||
|
||||
StepList = (0, 1, 2)
|
||||
|
||||
# Slow moves once the ratio of tower to XY movement exceeds SLOW_RATIO
|
||||
SLOW_RATIO = 3.
|
||||
|
||||
class DeltaKinematics:
|
||||
def __init__(self, printer, config):
|
||||
self.config = config
|
||||
self.steppers = [stepper.PrinterStepper(
|
||||
printer, config.getsection('stepper_' + n), n)
|
||||
for n in ['a', 'b', 'c']]
|
||||
self.need_motor_enable = True
|
||||
self.max_z_velocity = config.getfloat('max_z_velocity', 9999999.9)
|
||||
radius = config.getfloat('delta_radius')
|
||||
arm_length = config.getfloat('delta_arm_length')
|
||||
self.need_motor_enable = self.need_home = True
|
||||
self.max_velocity = self.max_z_velocity = self.max_accel = 0.
|
||||
radius = config.getfloat('delta_radius', above=0.)
|
||||
arm_length = config.getfloat('delta_arm_length', above=radius)
|
||||
self.arm_length2 = arm_length**2
|
||||
self.max_xy2 = min(radius, arm_length - radius)**2
|
||||
self.limit_xy2 = -1.
|
||||
tower_height_at_zeros = math.sqrt(self.arm_length2 - radius**2)
|
||||
self.max_z = self.steppers[0].position_max
|
||||
self.max_z = max([s.position_endstop for s in self.steppers])
|
||||
self.limit_z = self.max_z - (arm_length - tower_height_at_zeros)
|
||||
logging.info(
|
||||
"Delta max build height %.2fmm (radius tapered above %.2fmm)" % (
|
||||
self.max_z, self.limit_z))
|
||||
sin = lambda angle: math.sin(math.radians(angle))
|
||||
cos = lambda angle: math.cos(math.radians(angle))
|
||||
self.towers = [
|
||||
(cos(210.)*radius, sin(210.)*radius),
|
||||
(cos(330.)*radius, sin(330.)*radius),
|
||||
(cos(90.)*radius, sin(90.)*radius)]
|
||||
def set_max_jerk(self, max_xy_halt_velocity, max_accel):
|
||||
# XXX - this sets conservative values
|
||||
# Find the point where an XY move could result in excessive
|
||||
# tower movement
|
||||
half_min_step_dist = min([s.step_dist for s in self.steppers]) * .5
|
||||
def ratio_to_dist(ratio):
|
||||
return (ratio * math.sqrt(self.arm_length2 / (ratio**2 + 1.)
|
||||
- half_min_step_dist**2)
|
||||
+ half_min_step_dist)
|
||||
self.slow_xy2 = (ratio_to_dist(SLOW_RATIO) - radius)**2
|
||||
self.very_slow_xy2 = (ratio_to_dist(2. * SLOW_RATIO) - radius)**2
|
||||
self.max_xy2 = min(radius, arm_length - radius,
|
||||
ratio_to_dist(4. * SLOW_RATIO) - radius)**2
|
||||
logging.info(
|
||||
"Delta max build radius %.2fmm (moves slowed past %.2fmm and %.2fmm)"
|
||||
% (math.sqrt(self.max_xy2), math.sqrt(self.slow_xy2),
|
||||
math.sqrt(self.very_slow_xy2)))
|
||||
self.set_position([0., 0., 0.])
|
||||
def set_max_jerk(self, max_xy_halt_velocity, max_velocity, max_accel):
|
||||
self.max_velocity = max_velocity
|
||||
max_z_velocity = self.config.getfloat(
|
||||
'max_z_velocity', max_velocity, above=0.)
|
||||
self.max_z_velocity = min(max_velocity, max_z_velocity)
|
||||
self.max_accel = max_accel
|
||||
for stepper in self.steppers:
|
||||
stepper.set_max_jerk(max_xy_halt_velocity, max_accel)
|
||||
def build_config(self):
|
||||
for stepper in self.steppers:
|
||||
stepper.build_config()
|
||||
self.set_position([0., 0., 0.])
|
||||
def _cartesian_to_actuator(self, coord):
|
||||
return [int((math.sqrt(self.arm_length2
|
||||
return [math.sqrt(self.arm_length2
|
||||
- (self.towers[i][0] - coord[0])**2
|
||||
- (self.towers[i][1] - coord[1])**2) + coord[2])
|
||||
* self.steppers[i].inv_step_dist + 0.5)
|
||||
- (self.towers[i][1] - coord[1])**2) + coord[2]
|
||||
for i in StepList]
|
||||
def _actuator_to_cartesian(self, pos):
|
||||
# Based on code from Smoothieware
|
||||
@@ -78,13 +99,14 @@ class DeltaKinematics:
|
||||
pos = self._cartesian_to_actuator(newpos)
|
||||
for i in StepList:
|
||||
self.steppers[i].mcu_stepper.set_position(pos[i])
|
||||
self.limit_xy2 = -1.
|
||||
def home(self, homing_state):
|
||||
# All axes are homed simultaneously
|
||||
homing_state.set_axes([0, 1, 2])
|
||||
s = self.steppers[0] # Assume homing parameters same for all steppers
|
||||
self.limit_xy2 = self.max_xy2
|
||||
s = self.steppers[0] # Assume homing speed same for all steppers
|
||||
self.need_home = False
|
||||
# Initial homing
|
||||
homepos = [0., 0., s.position_endstop, None]
|
||||
homepos = [0., 0., self.max_z, None]
|
||||
coord = list(homepos)
|
||||
coord[2] = -1.5 * math.sqrt(self.arm_length2-self.max_xy2)
|
||||
homing_state.home(list(coord), homepos, self.steppers, s.homing_speed)
|
||||
@@ -96,15 +118,14 @@ class DeltaKinematics:
|
||||
homing_state.home(list(coord), homepos, self.steppers
|
||||
, s.homing_speed/2.0, second_home=True)
|
||||
# Set final homed position
|
||||
coord = [(s.mcu_stepper.commanded_position + s.get_homed_offset())
|
||||
* s.step_dist
|
||||
coord = [s.mcu_stepper.get_commanded_position() + s.get_homed_offset()
|
||||
for s in self.steppers]
|
||||
homing_state.set_homed_position(self._actuator_to_cartesian(coord))
|
||||
def motor_off(self, move_time):
|
||||
self.limit_xy2 = -1.
|
||||
for stepper in self.steppers:
|
||||
stepper.motor_enable(move_time, 0)
|
||||
self.need_motor_enable = True
|
||||
self.need_motor_enable = self.need_home = True
|
||||
def _check_motor_enable(self, move_time):
|
||||
for i in StepList:
|
||||
self.steppers[i].motor_enable(move_time, 1)
|
||||
@@ -115,127 +136,90 @@ class DeltaKinematics:
|
||||
def check_move(self, move):
|
||||
end_pos = move.end_pos
|
||||
xy2 = end_pos[0]**2 + end_pos[1]**2
|
||||
if xy2 > self.limit_xy2 or end_pos[2] < 0.:
|
||||
if self.limit_xy2 < 0.:
|
||||
if xy2 <= self.limit_xy2 and not move.axes_d[2]:
|
||||
# Normal XY move
|
||||
return
|
||||
if self.need_home:
|
||||
raise homing.EndstopMoveError(end_pos, "Must home first")
|
||||
raise homing.EndstopMoveError(end_pos)
|
||||
limit_xy2 = self.max_xy2
|
||||
if end_pos[2] > self.limit_z:
|
||||
if end_pos[2] > self.max_z or 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:
|
||||
raise homing.EndstopMoveError(end_pos)
|
||||
if move.axes_d[2]:
|
||||
move.limit_speed(self.max_z_velocity, 9999999.9)
|
||||
move.limit_speed(self.max_z_velocity, move.accel)
|
||||
limit_xy2 = -1.
|
||||
# Limit the speed/accel of this move if is is at the extreme
|
||||
# end of the build envelope
|
||||
extreme_xy2 = max(xy2, move.start_pos[0]**2 + move.start_pos[1]**2)
|
||||
if extreme_xy2 > self.slow_xy2:
|
||||
r = 0.5
|
||||
if extreme_xy2 > self.very_slow_xy2:
|
||||
r = 0.25
|
||||
max_velocity = self.max_velocity
|
||||
if move.axes_d[2]:
|
||||
max_velocity = self.max_z_velocity
|
||||
move.limit_speed(max_velocity * r, self.max_accel * r)
|
||||
limit_xy2 = -1.
|
||||
self.limit_xy2 = min(limit_xy2, self.slow_xy2)
|
||||
def move(self, move_time, move):
|
||||
if self.need_motor_enable:
|
||||
self._check_motor_enable(move_time)
|
||||
axes_d = move.axes_d
|
||||
move_d = movexy_d = move.move_d
|
||||
move_d = move.move_d
|
||||
movexy_r = 1.
|
||||
movez_r = 0.
|
||||
inv_movexy_d = 1. / movexy_d
|
||||
inv_movexy_d = 1. / move_d
|
||||
if not axes_d[0] and not axes_d[1]:
|
||||
if not axes_d[2]:
|
||||
return
|
||||
# Z only move
|
||||
movez_r = axes_d[2] * inv_movexy_d
|
||||
movexy_d = movexy_r = inv_movexy_d = 0.
|
||||
movexy_r = inv_movexy_d = 0.
|
||||
elif axes_d[2]:
|
||||
# XY+Z move
|
||||
movexy_d = math.sqrt(axes_d[0]**2 + axes_d[1]**2)
|
||||
movexy_r = movexy_d * inv_movexy_d
|
||||
movez_r = axes_d[2] * inv_movexy_d
|
||||
inv_movexy_d = 1. / movexy_d
|
||||
|
||||
if self.need_motor_enable:
|
||||
self._check_motor_enable(move_time)
|
||||
|
||||
origx, origy, origz = move.start_pos[:3]
|
||||
|
||||
accel_t = move.accel_t
|
||||
cruise_end_t = accel_t + move.cruise_t
|
||||
accel = move.accel
|
||||
cruise_v = move.cruise_v
|
||||
accel_d = move.accel_r * move_d
|
||||
cruise_end_d = accel_d + move.cruise_r * move_d
|
||||
|
||||
inv_cruise_v = 1. / move.cruise_v
|
||||
inv_accel = 1. / move.accel
|
||||
accel_time_offset = move.start_v * inv_accel
|
||||
accel_multiplier = 2.0 * inv_accel
|
||||
accel_offset = move.start_v**2 * 0.5 * inv_accel
|
||||
decel_time_offset = move.cruise_v * inv_accel + cruise_end_t
|
||||
decel_offset = move.cruise_v**2 * 0.5 * inv_accel + cruise_end_d
|
||||
cruise_d = move.cruise_r * move_d
|
||||
decel_d = move.decel_r * move_d
|
||||
|
||||
for i in StepList:
|
||||
# Find point on line of movement closest to tower
|
||||
# Calculate a virtual tower along the line of movement at
|
||||
# the point closest to this stepper's tower.
|
||||
towerx_d = self.towers[i][0] - origx
|
||||
towery_d = self.towers[i][1] - origy
|
||||
closestxy_d = (towerx_d*axes_d[0] + towery_d*axes_d[1])*inv_movexy_d
|
||||
tangentxy_d2 = towerx_d**2 + towery_d**2 - closestxy_d**2
|
||||
closest_height2 = self.arm_length2 - tangentxy_d2
|
||||
|
||||
# Calculate accel/cruise/decel portions of move
|
||||
reversexy_d = closestxy_d + math.sqrt(closest_height2)*movez_r
|
||||
accel_up_d = cruise_up_d = decel_up_d = 0.
|
||||
accel_down_d = cruise_down_d = decel_down_d = 0.
|
||||
if reversexy_d <= 0.:
|
||||
accel_down_d = accel_d
|
||||
cruise_down_d = cruise_end_d
|
||||
decel_down_d = move_d
|
||||
elif reversexy_d >= movexy_d:
|
||||
accel_up_d = accel_d
|
||||
cruise_up_d = cruise_end_d
|
||||
decel_up_d = move_d
|
||||
elif reversexy_d < accel_d * movexy_r:
|
||||
accel_up_d = reversexy_d * move_d * inv_movexy_d
|
||||
accel_down_d = accel_d
|
||||
cruise_down_d = cruise_end_d
|
||||
decel_down_d = move_d
|
||||
elif reversexy_d < cruise_end_d * movexy_r:
|
||||
accel_up_d = accel_d
|
||||
cruise_up_d = reversexy_d * move_d * inv_movexy_d
|
||||
cruise_down_d = cruise_end_d
|
||||
decel_down_d = move_d
|
||||
else:
|
||||
accel_up_d = accel_d
|
||||
cruise_up_d = cruise_end_d
|
||||
decel_up_d = reversexy_d * move_d * inv_movexy_d
|
||||
decel_down_d = move_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
|
||||
vt_arm_d = math.sqrt(self.arm_length2 - tangentxy_d2)
|
||||
vt_startz = origz
|
||||
|
||||
# Generate steps
|
||||
mcu_stepper = self.steppers[i].mcu_stepper
|
||||
mcu_time = mcu_stepper.print_to_mcu_time(move_time)
|
||||
step_pos = mcu_stepper.commanded_position
|
||||
step_dist = self.steppers[i].step_dist
|
||||
height = step_pos*step_dist - origz
|
||||
if accel_up_d > 0.:
|
||||
count = mcu_stepper.step_delta_accel(
|
||||
mcu_time - accel_time_offset, accel_up_d,
|
||||
accel_offset, accel_multiplier, step_dist,
|
||||
height, closestxy_d, closest_height2, movez_r)
|
||||
height += count * step_dist
|
||||
if cruise_up_d > 0.:
|
||||
count = mcu_stepper.step_delta_const(
|
||||
mcu_time + accel_t, cruise_up_d,
|
||||
-accel_d, inv_cruise_v, step_dist,
|
||||
height, closestxy_d, closest_height2, movez_r)
|
||||
height += count * step_dist
|
||||
if decel_up_d > 0.:
|
||||
count = mcu_stepper.step_delta_accel(
|
||||
mcu_time + decel_time_offset, decel_up_d,
|
||||
-decel_offset, -accel_multiplier, step_dist,
|
||||
height, closestxy_d, closest_height2, movez_r)
|
||||
height += count * step_dist
|
||||
if accel_down_d > 0.:
|
||||
count = mcu_stepper.step_delta_accel(
|
||||
mcu_time - accel_time_offset, accel_down_d,
|
||||
accel_offset, accel_multiplier, -step_dist,
|
||||
height, closestxy_d, closest_height2, movez_r)
|
||||
height += count * step_dist
|
||||
if cruise_down_d > 0.:
|
||||
count = mcu_stepper.step_delta_const(
|
||||
mcu_time + accel_t, cruise_down_d,
|
||||
-accel_d, inv_cruise_v, -step_dist,
|
||||
height, closestxy_d, closest_height2, movez_r)
|
||||
height += count * step_dist
|
||||
if decel_down_d > 0.:
|
||||
count = mcu_stepper.step_delta_accel(
|
||||
mcu_time + decel_time_offset, decel_down_d,
|
||||
-decel_offset, -accel_multiplier, -step_dist,
|
||||
height, closestxy_d, closest_height2, movez_r)
|
||||
if accel_d:
|
||||
mcu_stepper.step_delta(
|
||||
mcu_time, accel_d, move.start_v, accel,
|
||||
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
|
||||
vt_startz += accel_d * movez_r
|
||||
vt_startxy_d -= accel_d * movexy_r
|
||||
mcu_time += move.accel_t
|
||||
if cruise_d:
|
||||
mcu_stepper.step_delta(
|
||||
mcu_time, cruise_d, cruise_v, 0.,
|
||||
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
|
||||
vt_startz += cruise_d * movez_r
|
||||
vt_startxy_d -= cruise_d * movexy_r
|
||||
mcu_time += move.cruise_t
|
||||
if decel_d:
|
||||
mcu_stepper.step_delta(
|
||||
mcu_time, decel_d, cruise_v, -accel,
|
||||
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
|
||||
|
||||
|
||||
######################################################################
|
||||
|
||||
@@ -3,45 +3,127 @@
|
||||
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging
|
||||
import math, logging
|
||||
import stepper, heater, homing
|
||||
|
||||
EXTRUDE_DIFF_IGNORE = 1.02
|
||||
|
||||
class PrinterExtruder:
|
||||
def __init__(self, printer, config):
|
||||
self.config = config
|
||||
self.heater = heater.PrinterHeater(printer, config)
|
||||
self.stepper = stepper.PrinterStepper(printer, config, 'extruder')
|
||||
self.pressure_advance = config.getfloat('pressure_advance', 0.)
|
||||
self.max_e_velocity = config.getfloat('max_velocity')
|
||||
self.max_e_accel = config.getfloat('max_accel')
|
||||
self.nozzle_diameter = config.getfloat('nozzle_diameter', above=0.)
|
||||
filament_diameter = config.getfloat(
|
||||
'filament_diameter', minval=self.nozzle_diameter)
|
||||
filament_area = math.pi * (filament_diameter * .5)**2
|
||||
max_cross_section = config.getfloat(
|
||||
'max_extrude_cross_section', 4. * self.nozzle_diameter**2
|
||||
, above=0.)
|
||||
self.max_extrude_ratio = max_cross_section / filament_area
|
||||
self.max_e_dist = config.getfloat(
|
||||
'max_extrude_only_distance', 50., minval=0.)
|
||||
self.max_e_velocity = self.max_e_accel = None
|
||||
self.pressure_advance = config.getfloat(
|
||||
'pressure_advance', 0., minval=0.)
|
||||
self.pressure_advance_lookahead_time = 0.
|
||||
if self.pressure_advance:
|
||||
self.pressure_advance_lookahead_time = config.getfloat(
|
||||
'pressure_advance_lookahead_time', 0.010, minval=0.)
|
||||
self.need_motor_enable = True
|
||||
self.extrude_pos = 0.
|
||||
def build_config(self):
|
||||
self.heater.build_config()
|
||||
def set_max_jerk(self, max_xy_halt_velocity, max_velocity, max_accel):
|
||||
self.max_e_velocity = self.config.getfloat(
|
||||
'max_extrude_only_velocity', max_velocity * self.max_extrude_ratio
|
||||
, above=0.)
|
||||
self.max_e_accel = self.config.getfloat(
|
||||
'max_extrude_only_accel', max_accel * self.max_extrude_ratio
|
||||
, above=0.)
|
||||
self.stepper.set_max_jerk(9999999.9, 9999999.9)
|
||||
self.stepper.build_config()
|
||||
def motor_off(self, move_time):
|
||||
self.stepper.motor_enable(move_time, 0)
|
||||
self.need_motor_enable = True
|
||||
def check_move(self, move):
|
||||
move.extrude_r = move.axes_d[3] / move.move_d
|
||||
move.extrude_max_corner_v = 0.
|
||||
if not self.heater.can_extrude:
|
||||
raise homing.EndstopMoveError(
|
||||
move.end_pos, "Extrude below minimum temp")
|
||||
if (not move.do_calc_junction
|
||||
and not move.axes_d[0] and not move.axes_d[1]
|
||||
and not move.axes_d[2]):
|
||||
# Extrude only move - limit accel and velocity
|
||||
move.limit_speed(self.max_e_velocity, self.max_e_accel)
|
||||
if not move.is_kinematic_move or move.extrude_r < 0.:
|
||||
# Extrude only move (or retraction move) - limit accel and velocity
|
||||
if abs(move.axes_d[3]) > self.max_e_dist:
|
||||
raise homing.EndstopMoveError(
|
||||
move.end_pos, "Extrude move too long")
|
||||
inv_extrude_r = 1. / abs(move.extrude_r)
|
||||
move.limit_speed(self.max_e_velocity * inv_extrude_r
|
||||
, self.max_e_accel * inv_extrude_r)
|
||||
elif (move.extrude_r > self.max_extrude_ratio
|
||||
and move.axes_d[3] > self.nozzle_diameter*self.max_extrude_ratio):
|
||||
logging.debug("Overextrude: %s vs %s" % (
|
||||
move.extrude_r, self.max_extrude_ratio))
|
||||
raise homing.EndstopMoveError(
|
||||
move.end_pos, "Move exceeds maximum extrusion cross section")
|
||||
def calc_junction(self, prev_move, move):
|
||||
extrude = move.axes_d[3]
|
||||
prev_extrude = prev_move.axes_d[3]
|
||||
if extrude or prev_extrude:
|
||||
if not extrude or not prev_extrude:
|
||||
# Extrude move to non-extrude move - disable lookahead
|
||||
return 0.
|
||||
if ((move.extrude_r > prev_move.extrude_r * EXTRUDE_DIFF_IGNORE
|
||||
or prev_move.extrude_r > move.extrude_r * EXTRUDE_DIFF_IGNORE)
|
||||
and abs(move.move_d * prev_move.extrude_r - extrude) >= .001):
|
||||
# Extrude ratio between moves is too different
|
||||
return 0.
|
||||
move.extrude_r = prev_move.extrude_r
|
||||
return move.max_cruise_v2
|
||||
def lookahead(self, moves, flush_count, lazy):
|
||||
lookahead_t = self.pressure_advance_lookahead_time
|
||||
if not lookahead_t:
|
||||
return flush_count
|
||||
# Calculate max_corner_v - the speed the head will accelerate
|
||||
# to after cornering.
|
||||
for i in range(flush_count):
|
||||
move = moves[i]
|
||||
if not move.decel_t:
|
||||
continue
|
||||
cruise_v = move.cruise_v
|
||||
max_corner_v = 0.
|
||||
sum_t = lookahead_t
|
||||
for j in range(i+1, flush_count):
|
||||
fmove = moves[j]
|
||||
if not fmove.max_start_v2:
|
||||
break
|
||||
if fmove.cruise_v > max_corner_v:
|
||||
if (not max_corner_v
|
||||
and not fmove.accel_t and not fmove.cruise_t):
|
||||
# Start timing after any full decel moves
|
||||
continue
|
||||
if sum_t >= fmove.accel_t:
|
||||
max_corner_v = fmove.cruise_v
|
||||
else:
|
||||
max_corner_v = max(
|
||||
max_corner_v, fmove.start_v + fmove.accel * sum_t)
|
||||
if max_corner_v >= cruise_v:
|
||||
break
|
||||
sum_t -= fmove.accel_t + fmove.cruise_t + fmove.decel_t
|
||||
if sum_t <= 0.:
|
||||
break
|
||||
else:
|
||||
if lazy:
|
||||
return i
|
||||
move.extrude_max_corner_v = max_corner_v
|
||||
return flush_count
|
||||
def move(self, move_time, move):
|
||||
if self.need_motor_enable:
|
||||
self.stepper.motor_enable(move_time, 1)
|
||||
self.need_motor_enable = False
|
||||
axis_d = move.axes_d[3]
|
||||
extrude_r = abs(axis_d) / move.move_d
|
||||
inv_accel = 1. / (move.accel * extrude_r)
|
||||
|
||||
start_v = move.start_v * extrude_r
|
||||
cruise_v = move.cruise_v * extrude_r
|
||||
end_v = move.end_v * extrude_r
|
||||
axis_r = abs(axis_d) / move.move_d
|
||||
accel = move.accel * axis_r
|
||||
start_v = move.start_v * axis_r
|
||||
cruise_v = move.cruise_v * axis_r
|
||||
end_v = move.end_v * axis_r
|
||||
accel_t, cruise_t, decel_t = move.accel_t, move.cruise_t, move.decel_t
|
||||
accel_d = move.accel_r * axis_d
|
||||
cruise_d = move.cruise_r * axis_d
|
||||
@@ -55,25 +137,19 @@ class PrinterExtruder:
|
||||
if (axis_d >= 0. and (move.axes_d[0] or move.axes_d[1])
|
||||
and self.pressure_advance):
|
||||
# Increase accel_d and start_v when accelerating
|
||||
move_extrude_r = move.extrude_r
|
||||
pressure_advance = self.pressure_advance * move.extrude_r
|
||||
prev_pressure_d = start_pos - move.start_pos[3]
|
||||
if accel_t:
|
||||
npd = move.cruise_v * move_extrude_r * self.pressure_advance
|
||||
if accel_d:
|
||||
npd = move.cruise_v * pressure_advance
|
||||
extra_accel_d = npd - prev_pressure_d
|
||||
if extra_accel_d > 0.:
|
||||
accel_d += extra_accel_d
|
||||
start_v += extra_accel_d / accel_t
|
||||
prev_pressure_d += extra_accel_d
|
||||
# Update decel and retract parameters when decelerating
|
||||
if decel_t:
|
||||
if move.corner_min:
|
||||
npd = move.corner_max*move_extrude_r * self.pressure_advance
|
||||
extra_decel_d = prev_pressure_d - npd
|
||||
if move.end_v > move.corner_min:
|
||||
extra_decel_d *= ((move.cruise_v - move.end_v)
|
||||
/ (move.cruise_v - move.corner_min))
|
||||
else:
|
||||
npd = move.end_v * move_extrude_r * self.pressure_advance
|
||||
emcv = move.extrude_max_corner_v
|
||||
if decel_d and emcv < move.cruise_v:
|
||||
npd = max(emcv, move.end_v) * pressure_advance
|
||||
extra_decel_d = prev_pressure_d - npd
|
||||
if extra_decel_d > 0.:
|
||||
extra_decel_v = extra_decel_d / decel_t
|
||||
@@ -87,7 +163,7 @@ class PrinterExtruder:
|
||||
decel_t = decel_d = 0.
|
||||
elif end_v < 0.:
|
||||
# Split decel phase into decel and retraction
|
||||
retract_t = -end_v * inv_accel
|
||||
retract_t = -end_v / accel
|
||||
retract_d = -end_v * 0.5 * retract_t
|
||||
decel_t -= retract_t
|
||||
decel_d = decel_v * 0.5 * decel_t
|
||||
@@ -96,53 +172,41 @@ class PrinterExtruder:
|
||||
decel_d -= extra_decel_d
|
||||
|
||||
# Prepare for steps
|
||||
inv_step_dist = self.stepper.inv_step_dist
|
||||
step_dist = self.stepper.step_dist
|
||||
mcu_stepper = self.stepper.mcu_stepper
|
||||
mcu_time = mcu_stepper.print_to_mcu_time(move_time)
|
||||
step_pos = mcu_stepper.commanded_position
|
||||
step_offset = step_pos - start_pos * inv_step_dist
|
||||
|
||||
# Acceleration steps
|
||||
accel_multiplier = 2.0 * step_dist * inv_accel
|
||||
if accel_d:
|
||||
#t = sqrt(2*pos/accel + (start_v/accel)**2) - start_v/accel
|
||||
accel_time_offset = start_v * inv_accel
|
||||
accel_sqrt_offset = accel_time_offset**2
|
||||
accel_steps = accel_d * inv_step_dist
|
||||
count = mcu_stepper.step_sqrt(
|
||||
mcu_time - accel_time_offset, accel_steps, step_offset
|
||||
, accel_sqrt_offset, accel_multiplier)
|
||||
step_offset += count - accel_steps
|
||||
mcu_stepper.step_const(mcu_time, start_pos, accel_d, start_v, accel)
|
||||
start_pos += accel_d
|
||||
mcu_time += accel_t
|
||||
# Cruising steps
|
||||
if cruise_d:
|
||||
#t = pos/cruise_v
|
||||
cruise_multiplier = step_dist / cruise_v
|
||||
cruise_steps = cruise_d * inv_step_dist
|
||||
count = mcu_stepper.step_factor(
|
||||
mcu_time, cruise_steps, step_offset, cruise_multiplier)
|
||||
step_offset += count - cruise_steps
|
||||
mcu_stepper.step_const(mcu_time, start_pos, cruise_d, cruise_v, 0.)
|
||||
start_pos += cruise_d
|
||||
mcu_time += cruise_t
|
||||
# Deceleration steps
|
||||
if decel_d:
|
||||
#t = cruise_v/accel - sqrt((cruise_v/accel)**2 - 2*pos/accel)
|
||||
decel_time_offset = decel_v * inv_accel
|
||||
decel_sqrt_offset = decel_time_offset**2
|
||||
decel_steps = decel_d * inv_step_dist
|
||||
count = mcu_stepper.step_sqrt(
|
||||
mcu_time + decel_time_offset, decel_steps, step_offset
|
||||
, decel_sqrt_offset, -accel_multiplier)
|
||||
step_offset += count - decel_steps
|
||||
mcu_stepper.step_const(mcu_time, start_pos, decel_d, decel_v, -accel)
|
||||
start_pos += decel_d
|
||||
mcu_time += decel_t
|
||||
# Retraction steps
|
||||
if retract_d:
|
||||
#t = sqrt(2*pos/accel + (start_v/accel)**2) - start_v/accel
|
||||
accel_time_offset = retract_v * inv_accel
|
||||
accel_sqrt_offset = accel_time_offset**2
|
||||
accel_steps = -retract_d * inv_step_dist
|
||||
count = mcu_stepper.step_sqrt(
|
||||
mcu_time - accel_time_offset, accel_steps, step_offset
|
||||
, accel_sqrt_offset, accel_multiplier)
|
||||
mcu_stepper.step_const(
|
||||
mcu_time, start_pos, -retract_d, retract_v, accel)
|
||||
start_pos -= retract_d
|
||||
self.extrude_pos = start_pos
|
||||
|
||||
self.extrude_pos = start_pos + accel_d + cruise_d + decel_d - retract_d
|
||||
# Dummy extruder class used when a printer has no extruder at all
|
||||
class DummyExtruder:
|
||||
def set_max_jerk(self, max_xy_halt_velocity, max_velocity, max_accel):
|
||||
pass
|
||||
def motor_off(self, move_time):
|
||||
pass
|
||||
def check_move(self, move):
|
||||
raise homing.EndstopMoveError(
|
||||
move.end_pos, "Extrude when no extruder present")
|
||||
def calc_junction(self, prev_move, move):
|
||||
return move.max_cruise_v2
|
||||
def lookahead(self, moves, flush_count, lazy):
|
||||
return flush_count
|
||||
|
||||
@@ -5,30 +5,27 @@
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
FAN_MIN_TIME = 0.1
|
||||
PWM_CYCLE_TIME = 0.010
|
||||
|
||||
class PrinterFan:
|
||||
def __init__(self, printer, config):
|
||||
self.printer = printer
|
||||
self.config = config
|
||||
self.mcu_fan = None
|
||||
self.last_fan_value = 0
|
||||
self.last_fan_value = 0.
|
||||
self.last_fan_time = 0.
|
||||
self.kick_start_time = config.getfloat('kick_start_time', 0.1)
|
||||
def build_config(self):
|
||||
pin = self.config.get('pin')
|
||||
hard_pwm = self.config.getint('hard_pwm', 128)
|
||||
self.mcu_fan = self.printer.mcu.create_pwm(pin, hard_pwm, 0)
|
||||
self.kick_start_time = config.getfloat('kick_start_time', 0.1, minval=0.)
|
||||
pin = config.get('pin')
|
||||
hard_pwm = config.getint('hard_pwm', 0)
|
||||
self.mcu_fan = printer.mcu.create_pwm(pin, PWM_CYCLE_TIME, hard_pwm, 0.)
|
||||
# External commands
|
||||
def set_speed(self, print_time, value):
|
||||
value = max(0, min(255, int(value*255. + 0.5)))
|
||||
value = max(0., min(1., value))
|
||||
if value == self.last_fan_value:
|
||||
return
|
||||
mcu_time = self.mcu_fan.print_to_mcu_time(print_time)
|
||||
mcu_time = max(self.last_fan_time + FAN_MIN_TIME, mcu_time)
|
||||
if (value and value < 255
|
||||
if (value and value < 1.
|
||||
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(mcu_time, 255)
|
||||
self.mcu_fan.set_pwm(mcu_time, 1.)
|
||||
mcu_time += self.kick_start_time
|
||||
self.mcu_fan.set_pwm(mcu_time, value)
|
||||
self.last_fan_time = mcu_time
|
||||
|
||||
213
klippy/gcode.py
@@ -3,7 +3,7 @@
|
||||
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import os, re, logging, collections, time
|
||||
import os, re, logging, collections
|
||||
import homing
|
||||
|
||||
# Parse out incoming GCode and find and translate head movements
|
||||
@@ -23,7 +23,7 @@ class GCodeParser:
|
||||
self.bytes_read = 0
|
||||
self.input_log = collections.deque([], 50)
|
||||
# Command handling
|
||||
self.gcode_handlers = {}
|
||||
self.gcode_handlers = self.build_handlers(False)
|
||||
self.is_printer_ready = False
|
||||
self.need_ack = False
|
||||
self.toolhead = self.heater_nozzle = self.heater_bed = self.fan = None
|
||||
@@ -33,50 +33,47 @@ class GCodeParser:
|
||||
self.last_position = [0.0, 0.0, 0.0, 0.0]
|
||||
self.homing_add = [0.0, 0.0, 0.0, 0.0]
|
||||
self.axis2pos = {'X': 0, 'Y': 1, 'Z': 2, 'E': 3}
|
||||
self.build_handlers()
|
||||
def build_config(self):
|
||||
self.toolhead = self.printer.objects['toolhead']
|
||||
self.heater_nozzle = None
|
||||
extruder = self.printer.objects.get('extruder')
|
||||
if extruder:
|
||||
self.heater_nozzle = extruder.heater
|
||||
self.heater_bed = self.printer.objects.get('heater_bed')
|
||||
self.fan = self.printer.objects.get('fan')
|
||||
def build_handlers(self):
|
||||
handlers = ['G1', 'G4', 'G20', 'G21', 'G28', 'G90', 'G91', 'G92',
|
||||
'M18', 'M82', 'M83', 'M105', 'M110', 'M112', 'M114', 'M206',
|
||||
'HELP', 'QUERY_ENDSTOPS', 'RESTART', 'CLEAR_SHUTDOWN',
|
||||
'STATUS']
|
||||
if self.heater_nozzle is not None:
|
||||
handlers.extend(['M104', 'M109', 'PID_TUNE'])
|
||||
if self.heater_bed is not None:
|
||||
handlers.extend(['M140', 'M190'])
|
||||
if self.fan is not None:
|
||||
handlers.extend(['M106', 'M107'])
|
||||
if not self.is_printer_ready:
|
||||
def build_handlers(self, is_ready):
|
||||
handlers = self.all_handlers
|
||||
if not is_ready:
|
||||
handlers = [h for h in handlers
|
||||
if getattr(self, 'cmd_'+h+'_when_not_ready', False)]
|
||||
self.gcode_handlers = dict((h, getattr(self, 'cmd_'+h))
|
||||
for h in handlers)
|
||||
for h, f in self.gcode_handlers.items():
|
||||
gcode_handlers = dict((h, getattr(self, 'cmd_'+h)) for h in handlers)
|
||||
for h, f in gcode_handlers.items():
|
||||
aliases = getattr(self, 'cmd_'+h+'_aliases', [])
|
||||
self.gcode_handlers.update(dict([(a, f) for a in aliases]))
|
||||
gcode_handlers.update(dict([(a, f) for a in aliases]))
|
||||
return gcode_handlers
|
||||
def stats(self, eventtime):
|
||||
return "gcodein=%d" % (self.bytes_read,)
|
||||
def set_printer_ready(self, is_ready):
|
||||
if self.is_printer_ready == is_ready:
|
||||
return
|
||||
self.is_printer_ready = is_ready
|
||||
self.build_handlers()
|
||||
if is_ready and self.is_fileinput and self.fd_handle is None:
|
||||
self.gcode_handlers = self.build_handlers(is_ready)
|
||||
if not is_ready:
|
||||
# Printer is shutdown (could be running in a background thread)
|
||||
return
|
||||
# Lookup printer components
|
||||
self.toolhead = self.printer.objects.get('toolhead')
|
||||
self.heater_nozzle = None
|
||||
extruder = self.printer.objects.get('extruder')
|
||||
if extruder:
|
||||
self.heater_nozzle = extruder.heater
|
||||
self.heater_bed = self.printer.objects.get('heater_bed')
|
||||
self.fan = self.printer.objects.get('fan')
|
||||
if self.is_fileinput and self.fd_handle is None:
|
||||
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
|
||||
def motor_heater_off(self):
|
||||
if self.toolhead is not None:
|
||||
if self.toolhead is None:
|
||||
return
|
||||
self.toolhead.motor_off()
|
||||
print_time = self.toolhead.get_last_move_time()
|
||||
if self.heater_nozzle is not None:
|
||||
self.heater_nozzle.set_temp(0., 0.)
|
||||
self.heater_nozzle.set_temp(print_time, 0.)
|
||||
if self.heater_bed is not None:
|
||||
self.heater_bed.set_temp(0., 0.)
|
||||
self.heater_bed.set_temp(print_time, 0.)
|
||||
if self.fan is not None:
|
||||
self.fan.set_speed(print_time, 0.)
|
||||
def dump_debug(self):
|
||||
logging.info("Dumping gcode input %d blocks" % (
|
||||
len(self.input_log),))
|
||||
@@ -109,10 +106,15 @@ class GCodeParser:
|
||||
handler = self.gcode_handlers.get(cmd, self.cmd_default)
|
||||
try:
|
||||
handler(params)
|
||||
except error, e:
|
||||
self.respond_error(str(e))
|
||||
except:
|
||||
logging.exception("Exception in command handler")
|
||||
self.toolhead.force_shutdown()
|
||||
self.respond_error('Internal error on command:"%s"' % (cmd,))
|
||||
if self.is_fileinput:
|
||||
self.printer.request_exit('exit_eof')
|
||||
break
|
||||
self.ack()
|
||||
def process_data(self, eventtime):
|
||||
data = os.read(self.fd, 4096)
|
||||
@@ -136,7 +138,7 @@ class GCodeParser:
|
||||
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
|
||||
if not data and self.is_fileinput:
|
||||
self.motor_heater_off()
|
||||
self.printer.request_exit_eof()
|
||||
self.printer.request_exit('exit_eof')
|
||||
# Response handling
|
||||
def ack(self, msg=None):
|
||||
if not self.need_ack or self.is_fileinput:
|
||||
@@ -159,6 +161,27 @@ class GCodeParser:
|
||||
if len(lines) > 1:
|
||||
self.respond_info("\n".join(lines[:-1]))
|
||||
self.respond('!! %s' % (lines[-1].strip(),))
|
||||
# Parameter parsing helpers
|
||||
def get_int(self, name, params, default=None):
|
||||
if name in params:
|
||||
try:
|
||||
return int(params[name])
|
||||
except ValueError:
|
||||
raise error("Error on '%s': unable to parse %s" % (
|
||||
params['#original'], params[name]))
|
||||
if default is not None:
|
||||
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
|
||||
raise error("Error on '%s': missing %s" % (params['#original'], name))
|
||||
# Temperature wrappers
|
||||
def get_temp(self):
|
||||
if not self.is_printer_ready:
|
||||
@@ -175,17 +198,32 @@ class GCodeParser:
|
||||
def bg_temp(self, heater):
|
||||
if self.is_fileinput:
|
||||
return
|
||||
eventtime = time.time()
|
||||
eventtime = self.reactor.monotonic()
|
||||
while self.is_printer_ready and heater.check_busy(eventtime):
|
||||
self.toolhead.reset_motor_off_time(eventtime)
|
||||
print_time = self.toolhead.get_last_move_time()
|
||||
self.respond(self.get_temp())
|
||||
eventtime = self.reactor.pause(eventtime + 1.)
|
||||
def set_temp(self, heater, params, wait=False):
|
||||
temp = self.get_float('S', params, 0.)
|
||||
if heater is None:
|
||||
if temp > 0.:
|
||||
self.respond_error("Heater not configured")
|
||||
return
|
||||
print_time = self.toolhead.get_last_move_time()
|
||||
temp = float(params.get('S', '0'))
|
||||
try:
|
||||
heater.set_temp(print_time, temp)
|
||||
except heater.error, e:
|
||||
self.respond_error(str(e))
|
||||
return
|
||||
if wait:
|
||||
self.bg_temp(heater)
|
||||
def set_fan_speed(self, speed):
|
||||
if self.fan is None:
|
||||
if speed:
|
||||
self.respond_info("Fan not configured")
|
||||
return
|
||||
print_time = self.toolhead.get_last_move_time()
|
||||
self.fan.set_speed(print_time, speed)
|
||||
# Individual command handlers
|
||||
def cmd_default(self, params):
|
||||
if not self.is_printer_ready:
|
||||
@@ -196,20 +234,34 @@ class GCodeParser:
|
||||
logging.debug(params['#original'])
|
||||
return
|
||||
self.respond('echo:Unknown command:"%s"' % (cmd,))
|
||||
all_handlers = [
|
||||
'G1', 'G4', 'G20', 'G28', 'G90', 'G91', 'G92',
|
||||
'M82', 'M83', 'M18', 'M105', 'M104', 'M109', 'M112', 'M114', 'M115',
|
||||
'M140', 'M190', 'M106', 'M107', 'M206', 'M400',
|
||||
'IGNORE', 'QUERY_ENDSTOPS', 'PID_TUNE', 'RESTART', 'FIRMWARE_RESTART',
|
||||
'STATUS', 'HELP']
|
||||
cmd_G1_aliases = ['G0']
|
||||
def cmd_G1(self, params):
|
||||
# Move
|
||||
try:
|
||||
for a, p in self.axis2pos.items():
|
||||
if a in params:
|
||||
v = float(params[a])
|
||||
if not self.absolutecoord or (p>2 and not self.absoluteextrude):
|
||||
if (not self.absolutecoord
|
||||
or (p>2 and not self.absoluteextrude)):
|
||||
# value relative to position of last move
|
||||
self.last_position[p] += v
|
||||
else:
|
||||
# value relative to base coordinate position
|
||||
self.last_position[p] = v + self.base_position[p]
|
||||
if 'F' in params:
|
||||
self.speed = float(params['F']) / 60.
|
||||
speed = float(params['F']) / 60.
|
||||
if speed <= 0.:
|
||||
raise ValueError()
|
||||
self.speed = speed
|
||||
except ValueError, e:
|
||||
self.last_position = self.toolhead.get_position()
|
||||
raise error("Unable to parse move '%s'" % (params['#original'],))
|
||||
try:
|
||||
self.toolhead.move(self.last_position, self.speed)
|
||||
except homing.EndstopError, e:
|
||||
@@ -218,16 +270,13 @@ class GCodeParser:
|
||||
def cmd_G4(self, params):
|
||||
# Dwell
|
||||
if 'S' in params:
|
||||
delay = float(params['S'])
|
||||
delay = self.get_float('S', params)
|
||||
else:
|
||||
delay = float(params.get('P', '0')) / 1000.
|
||||
delay = self.get_float('P', params, 0.) / 1000.
|
||||
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_G21(self, params):
|
||||
# Set units to millimeters
|
||||
pass
|
||||
def cmd_G28(self, params):
|
||||
# Move to origin
|
||||
axes = []
|
||||
@@ -257,12 +306,11 @@ class GCodeParser:
|
||||
self.absolutecoord = False
|
||||
def cmd_G92(self, params):
|
||||
# Set position
|
||||
mcount = 0
|
||||
for a, p in self.axis2pos.items():
|
||||
if a in params:
|
||||
self.base_position[p] = self.last_position[p] - float(params[a])
|
||||
mcount += 1
|
||||
if not mcount:
|
||||
offsets = { p: self.get_float(a, params)
|
||||
for a, p in self.axis2pos.items() if a in params }
|
||||
for p, offset in offsets.items():
|
||||
self.base_position[p] = self.last_position[p] - offset
|
||||
if not offsets:
|
||||
self.base_position = list(self.last_position)
|
||||
def cmd_M82(self, params):
|
||||
# Use absolute distances for extrusion
|
||||
@@ -284,10 +332,6 @@ class GCodeParser:
|
||||
def cmd_M109(self, params):
|
||||
# Set Extruder Temperature and Wait
|
||||
self.set_temp(self.heater_nozzle, params, wait=True)
|
||||
cmd_M110_when_not_ready = True
|
||||
def cmd_M110(self, params):
|
||||
# Set Current Line Number
|
||||
pass
|
||||
def cmd_M112(self, params):
|
||||
# Emergency Stop
|
||||
self.toolhead.force_shutdown()
|
||||
@@ -302,6 +346,12 @@ class GCodeParser:
|
||||
self.last_position[0], self.last_position[1],
|
||||
self.last_position[2], self.last_position[3],
|
||||
kinpos[0], kinpos[1], kinpos[2]))
|
||||
cmd_M115_when_not_ready = True
|
||||
def cmd_M115(self, params):
|
||||
# Get Firmware Version and Capabilities
|
||||
kw = {"FIRMWARE_NAME": "Klipper"
|
||||
, "FIRMWARE_VERSION": self.printer.software_version}
|
||||
self.ack(" ".join(["%s:%s" % (k, v) for k, v in kw.items()]))
|
||||
def cmd_M140(self, params):
|
||||
# Set Bed Temperature
|
||||
self.set_temp(self.heater_bed, params)
|
||||
@@ -310,19 +360,25 @@ class GCodeParser:
|
||||
self.set_temp(self.heater_bed, params, wait=True)
|
||||
def cmd_M106(self, params):
|
||||
# Set fan speed
|
||||
print_time = self.toolhead.get_last_move_time()
|
||||
self.fan.set_speed(print_time, float(params.get('S', '255')) / 255.)
|
||||
self.set_fan_speed(self.get_float('S', params, 255.) / 255.)
|
||||
def cmd_M107(self, params):
|
||||
# Turn fan off
|
||||
print_time = self.toolhead.get_last_move_time()
|
||||
self.fan.set_speed(print_time, 0)
|
||||
self.set_fan_speed(0.)
|
||||
def cmd_M206(self, params):
|
||||
# Set home offset
|
||||
for a, p in self.axis2pos.items():
|
||||
if a in params:
|
||||
v = float(params[a])
|
||||
self.base_position[p] += self.homing_add[p] - v
|
||||
self.homing_add[p] = v
|
||||
offsets = { p: self.get_float(a, params)
|
||||
for a, p in self.axis2pos.items() 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_M400(self, params):
|
||||
# Wait for current moves to finish
|
||||
self.toolhead.wait_moves()
|
||||
cmd_IGNORE_when_not_ready = True
|
||||
cmd_IGNORE_aliases = ["G21", "M110", "M21"]
|
||||
def cmd_IGNORE(self, params):
|
||||
# Commands that are just silently accepted
|
||||
pass
|
||||
cmd_QUERY_ENDSTOPS_help = "Report on the status of each endstop"
|
||||
cmd_QUERY_ENDSTOPS_aliases = ["M119"]
|
||||
def cmd_QUERY_ENDSTOPS(self, params):
|
||||
@@ -340,23 +396,29 @@ class GCodeParser:
|
||||
cmd_PID_TUNE_aliases = ["M303"]
|
||||
def cmd_PID_TUNE(self, params):
|
||||
# Run PID tuning
|
||||
heater = int(params.get('E', '0'))
|
||||
heater = self.get_int('E', params, 0)
|
||||
heater = {0: self.heater_nozzle, -1: self.heater_bed}[heater]
|
||||
temp = float(params.get('S', '60'))
|
||||
if heater is None:
|
||||
self.respond_error("Heater not configured")
|
||||
temp = self.get_float('S', params)
|
||||
heater.start_auto_tune(temp)
|
||||
self.bg_temp(heater)
|
||||
cmd_CLEAR_SHUTDOWN_when_not_ready = True
|
||||
cmd_CLEAR_SHUTDOWN_help = "Clear a firmware shutdown and restart"
|
||||
def cmd_CLEAR_SHUTDOWN(self, params):
|
||||
if self.toolhead is None:
|
||||
self.cmd_default(params)
|
||||
return
|
||||
self.printer.mcu.clear_shutdown()
|
||||
self.printer.request_restart()
|
||||
def prep_restart(self):
|
||||
if self.is_printer_ready:
|
||||
self.respond_info("Preparing to restart...")
|
||||
self.motor_heater_off()
|
||||
self.toolhead.dwell(0.500)
|
||||
self.toolhead.wait_moves()
|
||||
cmd_RESTART_when_not_ready = True
|
||||
cmd_RESTART_help = "Reload config file and restart host software"
|
||||
def cmd_RESTART(self, params):
|
||||
self.printer.request_restart()
|
||||
self.prep_restart()
|
||||
self.printer.request_exit('restart')
|
||||
cmd_FIRMWARE_RESTART_when_not_ready = True
|
||||
cmd_FIRMWARE_RESTART_help = "Restart firmware, host, and reload config"
|
||||
def cmd_FIRMWARE_RESTART(self, params):
|
||||
self.prep_restart()
|
||||
self.printer.request_exit('firmware_restart')
|
||||
cmd_STATUS_when_not_ready = True
|
||||
cmd_STATUS_help = "Report the printer status"
|
||||
def cmd_STATUS(self, params):
|
||||
@@ -371,8 +433,11 @@ class GCodeParser:
|
||||
if not self.is_printer_ready:
|
||||
cmdhelp.append("Printer is not ready - not all commands available.")
|
||||
cmdhelp.append("Available extended commands:")
|
||||
for cmd in self.gcode_handlers:
|
||||
for cmd in sorted(self.gcode_handlers):
|
||||
desc = getattr(self, 'cmd_'+cmd+'_help', None)
|
||||
if desc is not None:
|
||||
cmdhelp.append("%-10s: %s" % (cmd, desc))
|
||||
self.respond_info("\n".join(cmdhelp))
|
||||
|
||||
class error(Exception):
|
||||
pass
|
||||
|
||||
142
klippy/heater.py
@@ -5,84 +5,110 @@
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import math, logging, threading
|
||||
|
||||
# Mapping from name to Steinhart-Hart coefficients
|
||||
Thermistors = {
|
||||
# Available sensors
|
||||
Sensors = {
|
||||
# Common thermistors and their Steinhart-Hart coefficients
|
||||
"EPCOS 100K B57560G104F": (
|
||||
"thermistor",
|
||||
0.000722136308968056, 0.000216766566488498, 8.92935804531095e-08),
|
||||
"ATC Semitec 104GT-2": (
|
||||
"thermistor",
|
||||
0.000809651054275124, 0.000211636030735685, 7.07420883993973e-08),
|
||||
# Linear style conversion chips and their gain/offset
|
||||
"AD595": ("linear", 300.0 / 3.022, 0.),
|
||||
}
|
||||
|
||||
SAMPLE_TIME = 0.001
|
||||
SAMPLE_COUNT = 8
|
||||
REPORT_TIME = 0.300
|
||||
PWM_CYCLE_TIME = 0.100
|
||||
KELVIN_TO_CELCIUS = -273.15
|
||||
MAX_HEAT_TIME = 5.0
|
||||
AMBIENT_TEMP = 25.
|
||||
PWM_MAX = 255
|
||||
PID_PARAM_BASE = 255.
|
||||
|
||||
class error(Exception):
|
||||
pass
|
||||
|
||||
class PrinterHeater:
|
||||
error = error
|
||||
def __init__(self, printer, config):
|
||||
self.printer = printer
|
||||
self.config = config
|
||||
self.mcu_pwm = self.mcu_adc = None
|
||||
self.thermistor_c = config.getchoice('thermistor_type', Thermistors)
|
||||
self.pullup_r = config.getfloat('pullup_resistor', 4700.)
|
||||
self.min_extrude_temp = config.getfloat('min_extrude_temp', 170.)
|
||||
self.can_extrude = (self.min_extrude_temp <= 0.)
|
||||
self.name = config.section
|
||||
sensor_params = config.getchoice('sensor_type', Sensors)
|
||||
self.is_linear_sensor = (sensor_params[0] == 'linear')
|
||||
if self.is_linear_sensor:
|
||||
adc_voltage = config.getfloat('adc_voltage', 5., above=0.)
|
||||
self.sensor_coef = sensor_params[1] * adc_voltage, sensor_params[2]
|
||||
else:
|
||||
pullup = config.getfloat('pullup_resistor', 4700., above=0.)
|
||||
self.sensor_coef = sensor_params[1:] + (pullup,)
|
||||
self.min_temp = config.getfloat('min_temp', minval=0.)
|
||||
self.max_temp = config.getfloat('max_temp', above=self.min_temp)
|
||||
self.min_extrude_temp = config.getfloat(
|
||||
'min_extrude_temp', 170., minval=self.min_temp, maxval=self.max_temp)
|
||||
self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.)
|
||||
self.can_extrude = (self.min_extrude_temp <= 0.
|
||||
or printer.mcu.is_fileoutput())
|
||||
self.lock = threading.Lock()
|
||||
self.last_temp = 0.
|
||||
self.last_temp_time = 0.
|
||||
self.target_temp = 0.
|
||||
self.control = None
|
||||
algos = {'watermark': ControlBangBang, 'pid': ControlPID}
|
||||
algo = config.getchoice('control', algos)
|
||||
heater_pin = config.get('heater_pin')
|
||||
sensor_pin = config.get('sensor_pin')
|
||||
if algo is ControlBangBang and self.max_power == 1.:
|
||||
self.mcu_pwm = printer.mcu.create_digital_out(
|
||||
heater_pin, MAX_HEAT_TIME)
|
||||
else:
|
||||
self.mcu_pwm = printer.mcu.create_pwm(
|
||||
heater_pin, PWM_CYCLE_TIME, 0, MAX_HEAT_TIME)
|
||||
self.mcu_adc = printer.mcu.create_adc(sensor_pin)
|
||||
adc_range = [self.calc_adc(self.min_temp), self.calc_adc(self.max_temp)]
|
||||
self.mcu_adc.set_minmax(SAMPLE_TIME, SAMPLE_COUNT,
|
||||
minval=min(adc_range), maxval=max(adc_range))
|
||||
self.mcu_adc.set_adc_callback(REPORT_TIME, self.adc_callback)
|
||||
self.control = algo(self, config)
|
||||
# pwm caching
|
||||
self.next_pwm_time = 0.
|
||||
self.last_pwm_value = 0
|
||||
def build_config(self):
|
||||
heater_pin = self.config.get('heater_pin')
|
||||
thermistor_pin = self.config.get('thermistor_pin')
|
||||
self.mcu_pwm = self.printer.mcu.create_pwm(heater_pin, 0, MAX_HEAT_TIME)
|
||||
self.mcu_adc = self.printer.mcu.create_adc(thermistor_pin)
|
||||
min_adc = self.calc_adc(self.config.getfloat('max_temp'))
|
||||
max_adc = self.calc_adc(self.config.getfloat('min_temp'))
|
||||
self.mcu_adc.set_minmax(
|
||||
SAMPLE_TIME, SAMPLE_COUNT, minval=min_adc, maxval=max_adc)
|
||||
self.mcu_adc.set_adc_callback(REPORT_TIME, self.adc_callback)
|
||||
algos = {'watermark': ControlBangBang, 'pid': ControlPID}
|
||||
self.control = self.config.getchoice('control', algos)(self, self.config)
|
||||
if self.printer.mcu.is_fileoutput():
|
||||
self.can_extrude = True
|
||||
def set_pwm(self, read_time, value):
|
||||
if value:
|
||||
if self.target_temp <= 0.:
|
||||
return
|
||||
if (read_time < self.next_pwm_time
|
||||
and abs(value - self.last_pwm_value) < 15):
|
||||
return
|
||||
elif not self.last_pwm_value:
|
||||
value = 0.
|
||||
if ((read_time < self.next_pwm_time or not self.last_pwm_value)
|
||||
and abs(value - self.last_pwm_value) < 0.05):
|
||||
# No significant change in value - can suppress update
|
||||
return
|
||||
pwm_time = read_time + REPORT_TIME + SAMPLE_TIME*SAMPLE_COUNT
|
||||
self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME
|
||||
self.last_pwm_value = value
|
||||
logging.debug("pwm=%d@%.3f (%.3f)" % (value, read_time, pwm_time))
|
||||
logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])" % (
|
||||
self.name, value, pwm_time,
|
||||
self.last_temp, self.last_temp_time, self.target_temp))
|
||||
self.mcu_pwm.set_pwm(pwm_time, value)
|
||||
# Temperature calculation
|
||||
def calc_temp(self, adc):
|
||||
r = self.pullup_r * adc / (1.0 - adc)
|
||||
if self.is_linear_sensor:
|
||||
gain, offset = self.sensor_coef
|
||||
return adc * gain + offset
|
||||
c1, c2, c3, pullup = self.sensor_coef
|
||||
r = pullup * adc / (1.0 - adc)
|
||||
ln_r = math.log(r)
|
||||
c1, c2, c3 = self.thermistor_c
|
||||
temp_inv = c1 + c2*ln_r + c3*math.pow(ln_r, 3)
|
||||
return 1.0/temp_inv + KELVIN_TO_CELCIUS
|
||||
def calc_adc(self, temp):
|
||||
if temp is None:
|
||||
return None
|
||||
c1, c2, c3 = self.thermistor_c
|
||||
if self.is_linear_sensor:
|
||||
gain, offset = self.sensor_coef
|
||||
return (temp - offset) / gain
|
||||
c1, c2, c3, pullup = self.sensor_coef
|
||||
temp -= KELVIN_TO_CELCIUS
|
||||
temp_inv = 1./temp
|
||||
y = (c1 - temp_inv) / (2*c3)
|
||||
x = math.sqrt(math.pow(c2 / (3.*c3), 3.) + math.pow(y, 2.))
|
||||
r = math.exp(math.pow(x-y, 1./3.) - math.pow(x+y, 1./3.))
|
||||
return r / (self.pullup_r + r)
|
||||
return r / (pullup + r)
|
||||
def adc_callback(self, read_time, read_value):
|
||||
temp = self.calc_temp(read_value)
|
||||
with self.lock:
|
||||
@@ -93,6 +119,9 @@ class PrinterHeater:
|
||||
#logging.debug("temp: %.3f %f = %f" % (read_time, read_value, temp))
|
||||
# External commands
|
||||
def set_temp(self, print_time, degrees):
|
||||
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:
|
||||
self.target_temp = degrees
|
||||
def get_temp(self):
|
||||
@@ -113,7 +142,7 @@ class PrinterHeater:
|
||||
class ControlBangBang:
|
||||
def __init__(self, heater, config):
|
||||
self.heater = heater
|
||||
self.max_delta = config.getfloat('max_delta', 2.0)
|
||||
self.max_delta = config.getfloat('max_delta', 2.0, above=0.)
|
||||
self.heating = False
|
||||
def adc_callback(self, read_time, temp):
|
||||
if self.heating and temp >= self.heater.target_temp+self.max_delta:
|
||||
@@ -121,9 +150,9 @@ class ControlBangBang:
|
||||
elif not self.heating and temp <= self.heater.target_temp-self.max_delta:
|
||||
self.heating = True
|
||||
if self.heating:
|
||||
self.heater.set_pwm(read_time, PWM_MAX)
|
||||
self.heater.set_pwm(read_time, self.heater.max_power)
|
||||
else:
|
||||
self.heater.set_pwm(read_time, 0)
|
||||
self.heater.set_pwm(read_time, 0.)
|
||||
def check_busy(self, eventtime):
|
||||
return self.heater.last_temp < self.heater.target_temp-self.max_delta
|
||||
|
||||
@@ -135,11 +164,11 @@ class ControlBangBang:
|
||||
class ControlPID:
|
||||
def __init__(self, heater, config):
|
||||
self.heater = heater
|
||||
self.Kp = config.getfloat('pid_Kp')
|
||||
self.Ki = config.getfloat('pid_Ki')
|
||||
self.Kd = config.getfloat('pid_Kd')
|
||||
self.min_deriv_time = config.getfloat('pid_deriv_time', 2.)
|
||||
imax = config.getint('pid_integral_max', PWM_MAX)
|
||||
self.Kp = config.getfloat('pid_Kp') / PID_PARAM_BASE
|
||||
self.Ki = config.getfloat('pid_Ki') / PID_PARAM_BASE
|
||||
self.Kd = config.getfloat('pid_Kd') / PID_PARAM_BASE
|
||||
self.min_deriv_time = config.getfloat('pid_deriv_time', 2., above=0.)
|
||||
imax = config.getfloat('pid_integral_max', heater.max_power, minval=0.)
|
||||
self.temp_integ_max = imax / self.Ki
|
||||
self.prev_temp = AMBIENT_TEMP
|
||||
self.prev_temp_time = 0.
|
||||
@@ -159,10 +188,10 @@ class ControlPID:
|
||||
temp_integ = self.prev_temp_integ + temp_err * time_diff
|
||||
temp_integ = max(0., min(self.temp_integ_max, temp_integ))
|
||||
# Calculate output
|
||||
co = int(self.Kp*temp_err + self.Ki*temp_integ - self.Kd*temp_deriv)
|
||||
co = self.Kp*temp_err + self.Ki*temp_integ - self.Kd*temp_deriv
|
||||
#logging.debug("pid: %f@%.3f -> diff=%f deriv=%f err=%f integ=%f co=%d" % (
|
||||
# temp, read_time, temp_diff, temp_deriv, temp_err, temp_integ, co))
|
||||
bounded_co = max(0, min(PWM_MAX, co))
|
||||
bounded_co = max(0., min(self.heater.max_power, co))
|
||||
self.heater.set_pwm(read_time, bounded_co)
|
||||
# Store state for next measurement
|
||||
self.prev_temp = temp
|
||||
@@ -198,12 +227,12 @@ class ControlAutoTune:
|
||||
self.heating = True
|
||||
self.check_peaks()
|
||||
if self.heating:
|
||||
self.heater.set_pwm(read_time, PWM_MAX)
|
||||
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)
|
||||
self.heater.set_pwm(read_time, 0.)
|
||||
if temp > self.peak:
|
||||
self.peak = temp
|
||||
self.peak_time = read_time
|
||||
@@ -217,8 +246,8 @@ class ControlAutoTune:
|
||||
return
|
||||
temp_diff = self.peaks[-1][0] - self.peaks[-2][0]
|
||||
time_diff = self.peaks[-1][1] - self.peaks[-3][1]
|
||||
pwm_diff = PWM_MAX - 0
|
||||
Ku = 4. * (2. * pwm_diff) / (abs(temp_diff) * math.pi)
|
||||
max_power = self.heater.max_power
|
||||
Ku = 4. * (2. * max_power) / (abs(temp_diff) * math.pi)
|
||||
Tu = time_diff
|
||||
|
||||
Kp = 0.6 * Ku
|
||||
@@ -226,8 +255,9 @@ class ControlAutoTune:
|
||||
Td = 0.125 * Tu
|
||||
Ki = Kp / Ti
|
||||
Kd = Kp * Td
|
||||
logging.info("Autotune: raw=%f/%d Ku=%f Tu=%f Kp=%f Ki=%f Kd=%f" % (
|
||||
temp_diff, pwm_diff, Ku, Tu, Kp, Ki, Kd))
|
||||
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
|
||||
@@ -253,17 +283,17 @@ class ControlBumpTest:
|
||||
def adc_callback(self, read_time, temp):
|
||||
self.temp_samples[read_time] = temp
|
||||
if not self.state:
|
||||
self.set_pwm(read_time, 0)
|
||||
self.set_pwm(read_time, 0.)
|
||||
if len(self.temp_samples) >= 20:
|
||||
self.state += 1
|
||||
elif self.state == 1:
|
||||
if temp < self.target_temp:
|
||||
self.set_pwm(read_time, PWM_MAX)
|
||||
self.set_pwm(read_time, self.heater.max_power)
|
||||
return
|
||||
self.set_pwm(read_time, 0)
|
||||
self.set_pwm(read_time, 0.)
|
||||
self.state += 1
|
||||
elif self.state == 2:
|
||||
self.set_pwm(read_time, 0)
|
||||
self.set_pwm(read_time, 0.)
|
||||
if temp <= (self.target_temp + AMBIENT_TEMP) / 2.:
|
||||
self.dump_stats()
|
||||
self.state += 1
|
||||
|
||||
163
klippy/klippy.py
@@ -6,6 +6,9 @@
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import sys, optparse, ConfigParser, logging, time, threading
|
||||
import gcode, toolhead, util, mcu, fan, heater, extruder, reactor, queuelogger
|
||||
import msgproto
|
||||
|
||||
message_ready = "Printer is ready"
|
||||
|
||||
message_startup = """
|
||||
The klippy host software is attempting to connect. Please
|
||||
@@ -19,17 +22,26 @@ command to reload the config and restart the host software.
|
||||
Printer is halted
|
||||
"""
|
||||
|
||||
message_protocol_error = """
|
||||
This type of error is frequently caused by running an older
|
||||
version of the firmware on the micro-controller (fix by
|
||||
recompiling and flashing the firmware).
|
||||
Once the underlying issue is corrected, use the "RESTART"
|
||||
command to reload the config and restart the host software.
|
||||
Protocol error connecting to printer
|
||||
"""
|
||||
|
||||
message_mcu_connect_error = """
|
||||
This is an unrecoverable error. Please manually restart the
|
||||
micro-controller and then issue the "RESTART" command to
|
||||
restart the host software.
|
||||
Once the underlying issue is corrected, use the
|
||||
"FIRMWARE_RESTART" command to reset the firmware, reload the
|
||||
config, and restart the host software.
|
||||
Error configuring printer
|
||||
"""
|
||||
|
||||
message_shutdown = """
|
||||
Once the underlying issue is corrected, the "CLEAR_SHUTDOWN"
|
||||
command can be used to clear the firmware flag and restart
|
||||
the host software.
|
||||
Once the underlying issue is corrected, use the
|
||||
"FIRMWARE_RESTART" command to reset the firmware, reload the
|
||||
config, and restart the host software.
|
||||
Printer is shutdown
|
||||
"""
|
||||
|
||||
@@ -40,26 +52,47 @@ class ConfigWrapper:
|
||||
def __init__(self, printer, section):
|
||||
self.printer = printer
|
||||
self.section = section
|
||||
def get_wrapper(self, parser, option, default):
|
||||
def get_wrapper(self, parser, option, default
|
||||
, minval=None, maxval=None, above=None, below=None):
|
||||
if (default is not self.sentinel
|
||||
and not self.printer.fileconfig.has_option(self.section, option)):
|
||||
return default
|
||||
self.printer.all_config_options[
|
||||
(self.section.lower(), option.lower())] = 1
|
||||
try:
|
||||
return parser(self.section, option)
|
||||
v = parser(self.section, option)
|
||||
except self.error, e:
|
||||
raise
|
||||
except:
|
||||
raise self.error("Unable to parse option '%s' in section '%s'" % (
|
||||
option, self.section))
|
||||
if minval is not None and v < minval:
|
||||
raise self.error(
|
||||
"Option '%s' in section '%s' must have minimum of %s" % (
|
||||
option, self.section, minval))
|
||||
if maxval is not None and v > maxval:
|
||||
raise self.error(
|
||||
"Option '%s' in section '%s' must have maximum of %s" % (
|
||||
option, self.section, maxval))
|
||||
if above is not None and v <= above:
|
||||
raise self.error(
|
||||
"Option '%s' in section '%s' must be above %s" % (
|
||||
option, self.section, above))
|
||||
if below is not None and v >= below:
|
||||
raise self.error(
|
||||
"Option '%s' in section '%s' must be below %s" % (
|
||||
option, self.section, below))
|
||||
return v
|
||||
def get(self, option, default=sentinel):
|
||||
return self.get_wrapper(self.printer.fileconfig.get, option, default)
|
||||
def getint(self, option, default=sentinel):
|
||||
return self.get_wrapper(self.printer.fileconfig.getint, option, default)
|
||||
def getfloat(self, option, default=sentinel):
|
||||
def getint(self, option, default=sentinel, minval=None, maxval=None):
|
||||
return self.get_wrapper(
|
||||
self.printer.fileconfig.getfloat, option, default)
|
||||
self.printer.fileconfig.getint, option, default, minval, maxval)
|
||||
def getfloat(self, option, default=sentinel
|
||||
, minval=None, maxval=None, above=None, below=None):
|
||||
return self.get_wrapper(
|
||||
self.printer.fileconfig.getfloat, option, default
|
||||
, minval, maxval, above, below)
|
||||
def getboolean(self, option, default=sentinel):
|
||||
return self.get_wrapper(
|
||||
self.printer.fileconfig.getboolean, option, default)
|
||||
@@ -73,10 +106,28 @@ class ConfigWrapper:
|
||||
def getsection(self, section):
|
||||
return ConfigWrapper(self.printer, section)
|
||||
|
||||
class ConfigLogger():
|
||||
def __init__(self, cfg, bglogger):
|
||||
self.lines = ["===== Config file ====="]
|
||||
cfg.write(self)
|
||||
self.lines.append("=======================")
|
||||
data = "\n".join(self.lines)
|
||||
logging.info(data)
|
||||
bglogger.set_rollover_info("config", data)
|
||||
def write(self, data):
|
||||
self.lines.append(data.strip())
|
||||
|
||||
class Printer:
|
||||
def __init__(self, conffile, input_fd, is_fileinput=False):
|
||||
def __init__(self, conffile, input_fd, startup_state
|
||||
, is_fileinput=False, version="?", bglogger=None):
|
||||
self.conffile = conffile
|
||||
self.startup_state = startup_state
|
||||
self.software_version = version
|
||||
self.bglogger = bglogger
|
||||
if bglogger is not None:
|
||||
bglogger.set_rollover_info("config", None)
|
||||
self.reactor = reactor.Reactor()
|
||||
self.objects = {}
|
||||
self.gcode = gcode.GCodeParser(self, input_fd, is_fileinput)
|
||||
self.stats_timer = self.reactor.register_timer(self.stats)
|
||||
self.connect_timer = self.reactor.register_timer(
|
||||
@@ -88,23 +139,25 @@ class Printer:
|
||||
self.run_result = None
|
||||
self.fileconfig = None
|
||||
self.mcu = None
|
||||
self.objects = {}
|
||||
def set_fileoutput(self, debugoutput, dictionary):
|
||||
self.debugoutput = debugoutput
|
||||
self.dictionary = dictionary
|
||||
def stats(self, eventtime):
|
||||
def stats(self, eventtime, force_output=False):
|
||||
if self.need_dump_debug:
|
||||
# Call dump_debug here so it is executed in the main thread
|
||||
self.gcode.dump_debug()
|
||||
self.need_dump_debug = False
|
||||
toolhead = self.objects.get('toolhead')
|
||||
if toolhead is None or self.mcu is None:
|
||||
return
|
||||
is_active, thstats = toolhead.stats(eventtime)
|
||||
if not is_active and not force_output:
|
||||
return
|
||||
out = []
|
||||
out.append(self.gcode.stats(eventtime))
|
||||
toolhead = self.objects.get('toolhead')
|
||||
if toolhead is not None:
|
||||
out.append(toolhead.stats(eventtime))
|
||||
if self.mcu is not None:
|
||||
out.append(thstats)
|
||||
out.append(self.mcu.stats(eventtime))
|
||||
logging.info("Stats %.0f: %s" % (eventtime, ' '.join(out)))
|
||||
logging.info("Stats %.1f: %s" % (eventtime, ' '.join(out)))
|
||||
return eventtime + 1.
|
||||
def load_config(self):
|
||||
self.fileconfig = ConfigParser.RawConfigParser()
|
||||
@@ -112,24 +165,23 @@ class Printer:
|
||||
if not res:
|
||||
raise ConfigParser.Error("Unable to open config file %s" % (
|
||||
self.conffile,))
|
||||
if self.bglogger is not None:
|
||||
ConfigLogger(self.fileconfig, self.bglogger)
|
||||
self.mcu = mcu.MCU(self, ConfigWrapper(self, 'mcu'))
|
||||
if self.fileconfig.has_section('fan'):
|
||||
self.objects['fan'] = fan.PrinterFan(
|
||||
self, ConfigWrapper(self, 'fan'))
|
||||
if self.debugoutput is not None:
|
||||
self.mcu.connect_file(self.debugoutput, self.dictionary)
|
||||
if self.fileconfig.has_section('extruder'):
|
||||
self.objects['extruder'] = extruder.PrinterExtruder(
|
||||
self, ConfigWrapper(self, 'extruder'))
|
||||
if self.fileconfig.has_section('fan'):
|
||||
self.objects['fan'] = fan.PrinterFan(
|
||||
self, ConfigWrapper(self, 'fan'))
|
||||
if self.fileconfig.has_section('heater_bed'):
|
||||
self.objects['heater_bed'] = heater.PrinterHeater(
|
||||
self, ConfigWrapper(self, 'heater_bed'))
|
||||
self.objects['toolhead'] = toolhead.ToolHead(
|
||||
self, ConfigWrapper(self, 'printer'))
|
||||
def build_config(self):
|
||||
for oname in sorted(self.objects.keys()):
|
||||
self.objects[oname].build_config()
|
||||
self.gcode.build_config()
|
||||
self.mcu.build_config()
|
||||
def validate_config(self):
|
||||
# Validate that there are no undefined parameters in the config file
|
||||
valid_sections = dict([(s, 1) for s, o in self.all_config_options])
|
||||
for section in self.fileconfig.sections():
|
||||
section = section.lower()
|
||||
@@ -147,17 +199,17 @@ class Printer:
|
||||
self.load_config()
|
||||
if self.debugoutput is None:
|
||||
self.reactor.update_timer(self.stats_timer, self.reactor.NOW)
|
||||
else:
|
||||
self.mcu.connect_file(self.debugoutput, self.dictionary)
|
||||
self.mcu.connect()
|
||||
self.build_config()
|
||||
self.validate_config()
|
||||
self.gcode.set_printer_ready(True)
|
||||
self.state_message = "Printer is ready"
|
||||
self.state_message = message_ready
|
||||
except ConfigParser.Error, e:
|
||||
logging.exception("Config error")
|
||||
self.state_message = "%s%s" % (str(e), message_restart)
|
||||
self.reactor.update_timer(self.stats_timer, self.reactor.NEVER)
|
||||
except msgproto.error, e:
|
||||
logging.exception("Protocol error")
|
||||
self.state_message = "%s%s" % (str(e), message_protocol_error)
|
||||
self.reactor.update_timer(self.stats_timer, self.reactor.NEVER)
|
||||
except mcu.error, e:
|
||||
logging.exception("MCU error during connect")
|
||||
self.state_message = "%s%s" % (str(e), message_mcu_connect_error)
|
||||
@@ -170,6 +222,10 @@ class Printer:
|
||||
self.reactor.unregister_timer(self.connect_timer)
|
||||
return self.reactor.NEVER
|
||||
def run(self):
|
||||
systime = time.time()
|
||||
monotime = self.reactor.monotonic()
|
||||
logging.info("Start printer at %s (%.1f %.1f)" % (
|
||||
time.asctime(time.localtime(systime)), systime, monotime))
|
||||
try:
|
||||
self.reactor.run()
|
||||
except:
|
||||
@@ -179,7 +235,7 @@ class Printer:
|
||||
def get_state_message(self):
|
||||
return self.state_message
|
||||
def note_shutdown(self, msg):
|
||||
if self.state_message == 'Running':
|
||||
if self.state_message == message_ready:
|
||||
self.need_dump_debug = True
|
||||
self.state_message = "Firmware shutdown: %s%s" % (
|
||||
msg, message_shutdown)
|
||||
@@ -191,15 +247,22 @@ class Printer:
|
||||
def disconnect(self):
|
||||
try:
|
||||
if self.mcu is not None:
|
||||
self.stats(time.time())
|
||||
self.stats(self.reactor.monotonic(), force_output=True)
|
||||
self.mcu.disconnect()
|
||||
except:
|
||||
logging.exception("Unhandled exception during disconnect")
|
||||
def request_restart(self):
|
||||
self.run_result = "restart"
|
||||
self.reactor.end()
|
||||
def request_exit_eof(self):
|
||||
self.run_result = "exit_eof"
|
||||
def firmware_restart(self):
|
||||
try:
|
||||
if self.mcu is not None:
|
||||
self.stats(self.reactor.monotonic(), force_output=True)
|
||||
self.mcu.microcontroller_restart()
|
||||
self.mcu.disconnect()
|
||||
except:
|
||||
logging.exception("Unhandled exception during firmware_restart")
|
||||
def get_startup_state(self):
|
||||
return self.startup_state
|
||||
def request_exit(self, result="exit"):
|
||||
self.run_result = result
|
||||
self.reactor.end()
|
||||
|
||||
|
||||
@@ -250,11 +313,22 @@ def main():
|
||||
else:
|
||||
logging.basicConfig(level=debuglevel)
|
||||
logging.info("Starting Klippy...")
|
||||
software_version = util.get_git_version()
|
||||
if bglogger is not None:
|
||||
lines = ["Args: %s" % (sys.argv,),
|
||||
"Git version: %s" % (repr(software_version),),
|
||||
"CPU: %s" % (util.get_cpu_info(),),
|
||||
"Python: %s" % (repr(sys.version),)]
|
||||
lines = "\n".join(lines)
|
||||
logging.info(lines)
|
||||
bglogger.set_rollover_info('versions', lines)
|
||||
|
||||
# Start firmware
|
||||
res = 'startup'
|
||||
while 1:
|
||||
is_fileinput = debuginput is not None
|
||||
printer = Printer(conffile, input_fd, is_fileinput)
|
||||
printer = Printer(
|
||||
conffile, input_fd, res, is_fileinput, software_version, bglogger)
|
||||
if debugoutput:
|
||||
proto_dict = read_dictionary(options.read_dictionary)
|
||||
printer.set_fileoutput(debugoutput, proto_dict)
|
||||
@@ -264,6 +338,11 @@ def main():
|
||||
time.sleep(1.)
|
||||
logging.info("Restarting printer")
|
||||
continue
|
||||
elif res == 'firmware_restart':
|
||||
printer.firmware_restart()
|
||||
time.sleep(1.)
|
||||
logging.info("Restarting printer")
|
||||
continue
|
||||
elif res == 'exit_eof':
|
||||
printer.disconnect()
|
||||
break
|
||||
|
||||
556
klippy/mcu.py
@@ -1,9 +1,9 @@
|
||||
# Multi-processor safe interface to micro-controller
|
||||
#
|
||||
# Copyright (C) 2016 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.
|
||||
import sys, zlib, logging, time, math
|
||||
import sys, os, zlib, logging, math
|
||||
import serialhdl, pins, chelper
|
||||
|
||||
class error(Exception):
|
||||
@@ -19,139 +19,181 @@ def parse_pin_extras(pin, can_pullup=False):
|
||||
pin = pin[1:].strip()
|
||||
return pin, pullup, invert
|
||||
|
||||
STEPCOMPRESS_ERROR_RET = -989898989
|
||||
|
||||
class MCU_stepper:
|
||||
def __init__(self, mcu, step_pin, dir_pin, min_stop_interval, max_error):
|
||||
def __init__(self, mcu, step_pin, dir_pin):
|
||||
self._mcu = mcu
|
||||
self._oid = mcu.create_oid()
|
||||
step_pin, pullup, invert_step = parse_pin_extras(step_pin)
|
||||
dir_pin, pullup, self._invert_dir = parse_pin_extras(dir_pin)
|
||||
self._mcu_freq = mcu.get_mcu_freq()
|
||||
min_stop_interval = int(min_stop_interval * self._mcu_freq)
|
||||
max_error = int(max_error * self._mcu_freq)
|
||||
self.commanded_position = 0
|
||||
self._oid = mcu.create_oid(self)
|
||||
self._step_pin, pullup, self._invert_step = parse_pin_extras(step_pin)
|
||||
self._dir_pin, pullup, self._invert_dir = parse_pin_extras(dir_pin)
|
||||
self._commanded_pos = 0
|
||||
self._step_dist = self._inv_step_dist = 1.
|
||||
self._velocity_factor = self._accel_factor = 0.
|
||||
self._mcu_position_offset = 0
|
||||
mcu.add_config_cmd(
|
||||
"config_stepper oid=%d step_pin=%s dir_pin=%s"
|
||||
" min_stop_interval=%d invert_step=%d" % (
|
||||
self._oid, step_pin, dir_pin, min_stop_interval, invert_step))
|
||||
mcu.register_stepper(self)
|
||||
self._step_cmd = mcu.lookup_command(
|
||||
"queue_step oid=%c interval=%u count=%hu add=%hi")
|
||||
self._dir_cmd = mcu.lookup_command(
|
||||
"set_next_step_dir oid=%c dir=%c")
|
||||
self._reset_cmd = mcu.lookup_command(
|
||||
"reset_step_clock oid=%c clock=%u")
|
||||
ffi_main, self.ffi_lib = chelper.get_ffi()
|
||||
self._stepqueue = ffi_main.gc(self.ffi_lib.stepcompress_alloc(
|
||||
max_error, self._step_cmd.msgid
|
||||
, self._dir_cmd.msgid, self._invert_dir, self._oid),
|
||||
self.ffi_lib.stepcompress_free)
|
||||
self._mcu_freq = self._min_stop_interval = 0.
|
||||
self._reset_cmd = self._get_position_cmd = None
|
||||
self._ffi_lib = self._stepqueue = None
|
||||
self.print_to_mcu_time = mcu.print_to_mcu_time
|
||||
def set_min_stop_interval(self, min_stop_interval):
|
||||
self._min_stop_interval = min_stop_interval
|
||||
def set_step_distance(self, step_dist):
|
||||
self._step_dist = step_dist
|
||||
self._inv_step_dist = 1. / step_dist
|
||||
def build_config(self):
|
||||
self._mcu_freq = self._mcu.get_mcu_freq()
|
||||
self._velocity_factor = 1. / (self._mcu_freq * self._step_dist)
|
||||
self._accel_factor = 1. / (self._mcu_freq**2 * self._step_dist)
|
||||
max_error = self._mcu.get_max_stepper_error()
|
||||
min_stop_interval = max(0., self._min_stop_interval - max_error)
|
||||
self._mcu.add_config_cmd(
|
||||
"config_stepper oid=%d step_pin=%s dir_pin=%s"
|
||||
" min_stop_interval=TICKS(%.9f) invert_step=%d" % (
|
||||
self._oid, self._step_pin, self._dir_pin,
|
||||
min_stop_interval, self._invert_step))
|
||||
self._mcu.register_stepper(self)
|
||||
step_cmd = self._mcu.lookup_command(
|
||||
"queue_step oid=%c interval=%u count=%hu add=%hi")
|
||||
dir_cmd = self._mcu.lookup_command(
|
||||
"set_next_step_dir oid=%c dir=%c")
|
||||
self._reset_cmd = self._mcu.lookup_command(
|
||||
"reset_step_clock oid=%c clock=%u")
|
||||
self._get_position_cmd = self._mcu.lookup_command(
|
||||
"stepper_get_position oid=%c")
|
||||
ffi_main, self._ffi_lib = chelper.get_ffi()
|
||||
max_error = int(max_error * self._mcu_freq)
|
||||
self._stepqueue = ffi_main.gc(self._ffi_lib.stepcompress_alloc(
|
||||
max_error, step_cmd.msgid, dir_cmd.msgid,
|
||||
self._invert_dir, self._oid),
|
||||
self._ffi_lib.stepcompress_free)
|
||||
def get_oid(self):
|
||||
return self._oid
|
||||
def get_invert_dir(self):
|
||||
return self._invert_dir
|
||||
def set_position(self, pos):
|
||||
self._mcu_position_offset += self.commanded_position - pos
|
||||
self.commanded_position = pos
|
||||
def set_mcu_position(self, pos):
|
||||
self._mcu_position_offset = pos - self.commanded_position
|
||||
if pos >= 0.:
|
||||
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._commanded_pos = steppos
|
||||
def get_commanded_position(self):
|
||||
return self._commanded_pos * self._step_dist
|
||||
def get_mcu_position(self):
|
||||
return self.commanded_position + self._mcu_position_offset
|
||||
def note_stepper_stop(self):
|
||||
self.ffi_lib.stepcompress_reset(self._stepqueue, 0)
|
||||
return self._commanded_pos + self._mcu_position_offset
|
||||
def note_homing_start(self, homing_clock):
|
||||
ret = self._ffi_lib.stepcompress_set_homing(
|
||||
self._stepqueue, homing_clock)
|
||||
if ret:
|
||||
raise error("Internal error in stepcompress")
|
||||
def note_homing_finalized(self):
|
||||
ret = self._ffi_lib.stepcompress_set_homing(self._stepqueue, 0)
|
||||
if ret:
|
||||
raise error("Internal error in stepcompress")
|
||||
ret = self._ffi_lib.stepcompress_reset(self._stepqueue, 0)
|
||||
if ret:
|
||||
raise error("Internal error in stepcompress")
|
||||
def note_homing_triggered(self):
|
||||
params = self._mcu.serial.send_with_response(
|
||||
self._get_position_cmd.encode(self._oid),
|
||||
'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, mcu_time):
|
||||
clock = int(mcu_time * self._mcu_freq)
|
||||
self.ffi_lib.stepcompress_reset(self._stepqueue, clock)
|
||||
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)
|
||||
self.ffi_lib.stepcompress_queue_msg(self._stepqueue, data, len(data))
|
||||
ret = self._ffi_lib.stepcompress_queue_msg(
|
||||
self._stepqueue, data, len(data))
|
||||
if ret:
|
||||
raise error("Internal error in stepcompress")
|
||||
def step(self, mcu_time, sdir):
|
||||
clock = mcu_time * self._mcu_freq
|
||||
self.ffi_lib.stepcompress_push(self._stepqueue, clock, sdir)
|
||||
ret = self._ffi_lib.stepcompress_push(self._stepqueue, clock, sdir)
|
||||
if ret:
|
||||
raise error("Internal error in stepcompress")
|
||||
if sdir:
|
||||
self.commanded_position += 1
|
||||
self._commanded_pos += 1
|
||||
else:
|
||||
self.commanded_position -= 1
|
||||
def step_sqrt(self, mcu_time, steps, step_offset, sqrt_offset, factor):
|
||||
clock = mcu_time * self._mcu_freq
|
||||
mcu_freq2 = self._mcu_freq**2
|
||||
count = self.ffi_lib.stepcompress_push_sqrt(
|
||||
self._stepqueue, steps, step_offset, clock
|
||||
, sqrt_offset * mcu_freq2, factor * mcu_freq2)
|
||||
self.commanded_position += count
|
||||
return count
|
||||
def step_factor(self, mcu_time, steps, step_offset, factor):
|
||||
clock = mcu_time * self._mcu_freq
|
||||
count = self.ffi_lib.stepcompress_push_factor(
|
||||
self._stepqueue, steps, step_offset, clock, factor * self._mcu_freq)
|
||||
self.commanded_position += count
|
||||
return count
|
||||
def step_delta_const(self, mcu_time, dist, start_pos
|
||||
, inv_velocity, step_dist
|
||||
, height, closestxy_d, closest_height2, movez_r):
|
||||
clock = mcu_time * self._mcu_freq
|
||||
count = self.ffi_lib.stepcompress_push_delta_const(
|
||||
self._stepqueue, clock, dist, start_pos
|
||||
, inv_velocity * self._mcu_freq, step_dist
|
||||
, height, closestxy_d, closest_height2, movez_r)
|
||||
self.commanded_position += count
|
||||
return count
|
||||
def step_delta_accel(self, mcu_time, dist, start_pos
|
||||
, accel_multiplier, step_dist
|
||||
, height, closestxy_d, closest_height2, movez_r):
|
||||
clock = mcu_time * self._mcu_freq
|
||||
mcu_freq2 = self._mcu_freq**2
|
||||
count = self.ffi_lib.stepcompress_push_delta_accel(
|
||||
self._stepqueue, clock, dist, start_pos
|
||||
, accel_multiplier * mcu_freq2, step_dist
|
||||
, height, closestxy_d, closest_height2, movez_r)
|
||||
self.commanded_position += count
|
||||
return count
|
||||
def get_errors(self):
|
||||
return self.ffi_lib.stepcompress_get_errors(self._stepqueue)
|
||||
self._commanded_pos -= 1
|
||||
def step_const(self, mcu_time, start_pos, dist, start_v, accel):
|
||||
inv_step_dist = self._inv_step_dist
|
||||
step_offset = self._commanded_pos - start_pos * inv_step_dist
|
||||
count = self._ffi_lib.stepcompress_push_const(
|
||||
self._stepqueue, mcu_time * self._mcu_freq, step_offset,
|
||||
dist * inv_step_dist, start_v * self._velocity_factor,
|
||||
accel * self._accel_factor)
|
||||
if count == STEPCOMPRESS_ERROR_RET:
|
||||
raise error("Internal error in stepcompress")
|
||||
self._commanded_pos += count
|
||||
def step_delta(self, mcu_time, dist, start_v, accel
|
||||
, height_base, startxy_d, arm_d, movez_r):
|
||||
inv_step_dist = self._inv_step_dist
|
||||
height = self._commanded_pos - height_base * inv_step_dist
|
||||
count = self._ffi_lib.stepcompress_push_delta(
|
||||
self._stepqueue, mcu_time * self._mcu_freq, dist * inv_step_dist,
|
||||
start_v * self._velocity_factor, accel * self._accel_factor,
|
||||
height, startxy_d * inv_step_dist, arm_d * inv_step_dist, movez_r)
|
||||
if count == STEPCOMPRESS_ERROR_RET:
|
||||
raise error("Internal error in stepcompress")
|
||||
self._commanded_pos += count
|
||||
|
||||
class MCU_endstop:
|
||||
error = error
|
||||
RETRY_QUERY = 1.000
|
||||
def __init__(self, mcu, pin, stepper):
|
||||
def __init__(self, mcu, pin):
|
||||
self._mcu = mcu
|
||||
self._oid = mcu.create_oid()
|
||||
self._stepper = stepper
|
||||
stepper_oid = stepper.get_oid()
|
||||
pin, pullup, self._invert = parse_pin_extras(pin, can_pullup=True)
|
||||
self._oid = mcu.create_oid(self)
|
||||
self._steppers = []
|
||||
self._pin, self._pullup, self._invert = parse_pin_extras(
|
||||
pin, can_pullup=True)
|
||||
self._cmd_queue = mcu.alloc_command_queue()
|
||||
mcu.add_config_cmd(
|
||||
"config_end_stop oid=%d pin=%s pull_up=%d stepper_oid=%d" % (
|
||||
self._oid, pin, pullup, stepper_oid))
|
||||
self._home_cmd = mcu.lookup_command(
|
||||
"end_stop_home oid=%c clock=%u rest_ticks=%u pin_value=%c")
|
||||
mcu.register_msg(self._handle_end_stop_state, "end_stop_state"
|
||||
, self._oid)
|
||||
self._query_cmd = mcu.lookup_command("end_stop_query oid=%c")
|
||||
self._home_cmd = self._query_cmd = None
|
||||
self._homing = False
|
||||
self._min_query_time = 0.
|
||||
self._min_query_time = self._mcu_freq = 0.
|
||||
self._next_query_clock = self._home_timeout_clock = 0
|
||||
self._mcu_freq = mcu.get_mcu_freq()
|
||||
self._retry_query_ticks = int(self._mcu_freq * self.RETRY_QUERY)
|
||||
self._retry_query_ticks = 0
|
||||
self._last_state = {}
|
||||
mcu.add_init_callback(self._init_callback)
|
||||
self.print_to_mcu_time = mcu.print_to_mcu_time
|
||||
def add_stepper(self, stepper):
|
||||
self._steppers.append(stepper)
|
||||
def build_config(self):
|
||||
self._mcu_freq = self._mcu.get_mcu_freq()
|
||||
self._mcu.add_config_cmd(
|
||||
"config_end_stop oid=%d pin=%s pull_up=%d stepper_count=%d" % (
|
||||
self._oid, self._pin, self._pullup, len(self._steppers)))
|
||||
self._retry_query_ticks = int(self._mcu_freq * self.RETRY_QUERY)
|
||||
self._home_cmd = self._mcu.lookup_command(
|
||||
"end_stop_home oid=%c clock=%u rest_ticks=%u pin_value=%c")
|
||||
self._query_cmd = self._mcu.lookup_command("end_stop_query oid=%c")
|
||||
self._mcu.register_msg(self._handle_end_stop_state, "end_stop_state"
|
||||
, self._oid)
|
||||
def _init_callback(self):
|
||||
set_cmd = self._mcu.lookup_command(
|
||||
"end_stop_set_stepper oid=%c pos=%c stepper_oid=%c")
|
||||
for i, s in enumerate(self._steppers):
|
||||
msg = set_cmd.encode(self._oid, i, s.get_oid())
|
||||
self._mcu.send(msg, cq=self._cmd_queue)
|
||||
def home_start(self, mcu_time, rest_time):
|
||||
clock = int(mcu_time * self._mcu_freq)
|
||||
rest_ticks = int(rest_time * self._mcu_freq)
|
||||
self._homing = True
|
||||
self._min_query_time = time.time()
|
||||
self._min_query_time = self._mcu.monotonic()
|
||||
self._next_query_clock = clock + self._retry_query_ticks
|
||||
msg = self._home_cmd.encode(
|
||||
self._oid, clock, rest_ticks, 1 ^ self._invert)
|
||||
self._mcu.send(msg, reqclock=clock, cq=self._cmd_queue)
|
||||
for s in self._steppers:
|
||||
s.note_homing_start(clock)
|
||||
def home_finalize(self, mcu_time):
|
||||
# XXX - this flushes the serial port of messages ready to be
|
||||
# sent, but doesn't flush messages if they had an unmet minclock
|
||||
self._mcu.serial.send_flush()
|
||||
self._stepper.note_stepper_stop()
|
||||
for s in self._steppers:
|
||||
s.note_homing_finalized()
|
||||
self._home_timeout_clock = int(mcu_time * self._mcu_freq)
|
||||
def home_wait(self):
|
||||
eventtime = time.time()
|
||||
eventtime = self._mcu.monotonic()
|
||||
while self._check_busy(eventtime):
|
||||
eventtime = self._mcu.pause(eventtime + 0.1)
|
||||
def _handle_end_stop_state(self, params):
|
||||
@@ -166,10 +208,8 @@ class MCU_endstop:
|
||||
if not self._homing:
|
||||
return False
|
||||
if not self._last_state.get('homing', 0):
|
||||
pos = self._last_state.get('pos', 0)
|
||||
if self._stepper.get_invert_dir():
|
||||
pos = -pos
|
||||
self._stepper.set_mcu_position(pos)
|
||||
for s in self._steppers:
|
||||
s.note_homing_triggered()
|
||||
self._homing = False
|
||||
return False
|
||||
if (self._mcu.serial.get_clock(last_sent_time)
|
||||
@@ -189,10 +229,10 @@ class MCU_endstop:
|
||||
def query_endstop(self, mcu_time):
|
||||
clock = int(mcu_time * self._mcu_freq)
|
||||
self._homing = False
|
||||
self._min_query_time = time.time()
|
||||
self._min_query_time = self._mcu.monotonic()
|
||||
self._next_query_clock = clock
|
||||
def query_endstop_wait(self):
|
||||
eventtime = time.time()
|
||||
eventtime = self._mcu.monotonic()
|
||||
while self._check_busy(eventtime):
|
||||
eventtime = self._mcu.pause(eventtime + 0.1)
|
||||
return self._last_state.get('pin', self._invert) ^ self._invert
|
||||
@@ -200,18 +240,22 @@ class MCU_endstop:
|
||||
class MCU_digital_out:
|
||||
def __init__(self, mcu, pin, max_duration):
|
||||
self._mcu = mcu
|
||||
self._oid = mcu.create_oid()
|
||||
self._oid = mcu.create_oid(self)
|
||||
pin, pullup, self._invert = parse_pin_extras(pin)
|
||||
self._last_clock = 0
|
||||
self._last_value = None
|
||||
self._mcu_freq = mcu.get_mcu_freq()
|
||||
self._mcu_freq = 0.
|
||||
self._cmd_queue = mcu.alloc_command_queue()
|
||||
mcu.add_config_cmd(
|
||||
"config_digital_out oid=%d pin=%s default_value=%d"
|
||||
" max_duration=%d" % (self._oid, pin, self._invert, max_duration))
|
||||
self._set_cmd = mcu.lookup_command(
|
||||
"schedule_digital_out oid=%c clock=%u value=%c")
|
||||
" max_duration=TICKS(%f)" % (
|
||||
self._oid, pin, self._invert, max_duration))
|
||||
self._set_cmd = None
|
||||
self.print_to_mcu_time = mcu.print_to_mcu_time
|
||||
def build_config(self):
|
||||
self._mcu_freq = self._mcu.get_mcu_freq()
|
||||
self._set_cmd = self._mcu.lookup_command(
|
||||
"schedule_digital_out oid=%c clock=%u value=%c")
|
||||
def set_digital(self, mcu_time, value):
|
||||
clock = int(mcu_time * self._mcu_freq)
|
||||
msg = self._set_cmd.encode(self._oid, clock, value ^ self._invert)
|
||||
@@ -223,33 +267,46 @@ class MCU_digital_out:
|
||||
return self._last_value
|
||||
def set_pwm(self, mcu_time, value):
|
||||
dval = 0
|
||||
if value > 127:
|
||||
if value >= 0.5:
|
||||
dval = 1
|
||||
self.set_digital(mcu_time, dval)
|
||||
|
||||
class MCU_pwm:
|
||||
def __init__(self, mcu, pin, cycle_ticks, max_duration, hard_pwm=True):
|
||||
PWM_MAX = 255.
|
||||
def __init__(self, mcu, pin, cycle_time, hard_cycle_ticks, max_duration):
|
||||
self._mcu = mcu
|
||||
self._oid = mcu.create_oid()
|
||||
self._hard_cycle_ticks = hard_cycle_ticks
|
||||
self._oid = mcu.create_oid(self)
|
||||
pin, pullup, self._invert = parse_pin_extras(pin)
|
||||
self._last_clock = 0
|
||||
self._mcu_freq = mcu.get_mcu_freq()
|
||||
self._mcu_freq = 0.
|
||||
self._cmd_queue = mcu.alloc_command_queue()
|
||||
if hard_pwm:
|
||||
if hard_cycle_ticks:
|
||||
mcu.add_config_cmd(
|
||||
"config_pwm_out oid=%d pin=%s cycle_ticks=%d default_value=0"
|
||||
" max_duration=%d" % (self._oid, pin, cycle_ticks, max_duration))
|
||||
self._set_cmd = mcu.lookup_command(
|
||||
"schedule_pwm_out oid=%c clock=%u value=%c")
|
||||
"config_pwm_out oid=%d pin=%s cycle_ticks=%d default_value=%d"
|
||||
" max_duration=TICKS(%f)" % (
|
||||
self._oid, pin, hard_cycle_ticks, self._invert,
|
||||
max_duration))
|
||||
else:
|
||||
mcu.add_config_cmd(
|
||||
"config_soft_pwm_out oid=%d pin=%s cycle_ticks=%d"
|
||||
" default_value=0 max_duration=%d" % (
|
||||
self._oid, pin, cycle_ticks, max_duration))
|
||||
self._set_cmd = mcu.lookup_command(
|
||||
"schedule_soft_pwm_out oid=%c clock=%u value=%c")
|
||||
"config_soft_pwm_out oid=%d pin=%s cycle_ticks=TICKS(%f)"
|
||||
" default_value=%d max_duration=TICKS(%f)" % (
|
||||
self._oid, pin, cycle_time, self._invert, max_duration))
|
||||
self._set_cmd = None
|
||||
self.print_to_mcu_time = mcu.print_to_mcu_time
|
||||
def build_config(self):
|
||||
self._mcu_freq = self._mcu.get_mcu_freq()
|
||||
if self._hard_cycle_ticks:
|
||||
self._set_cmd = self._mcu.lookup_command(
|
||||
"schedule_pwm_out oid=%c clock=%u value=%c")
|
||||
else:
|
||||
self._set_cmd = self._mcu.lookup_command(
|
||||
"schedule_soft_pwm_out oid=%c clock=%u value=%c")
|
||||
def set_pwm(self, mcu_time, value):
|
||||
clock = int(mcu_time * self._mcu_freq)
|
||||
if self._invert:
|
||||
value = 1. - value
|
||||
value = int(value * self.PWM_MAX + 0.5)
|
||||
msg = self._set_cmd.encode(self._oid, clock, value)
|
||||
self._mcu.send(msg, minclock=self._last_clock, reqclock=clock
|
||||
, cq=self._cmd_queue)
|
||||
@@ -258,41 +315,46 @@ class MCU_pwm:
|
||||
class MCU_adc:
|
||||
def __init__(self, mcu, pin):
|
||||
self._mcu = mcu
|
||||
self._oid = mcu.create_oid()
|
||||
self._min_sample = 0
|
||||
self._max_sample = 0xffff
|
||||
self._sample_ticks = 0
|
||||
self._sample_count = 1
|
||||
self._oid = mcu.create_oid(self)
|
||||
self._min_sample = self._max_sample = 0.
|
||||
self._sample_time = self._report_time = 0.
|
||||
self._sample_count = 0
|
||||
self._report_clock = 0
|
||||
self._callback = None
|
||||
self._inv_max_adc = 0.
|
||||
self._mcu_freq = mcu.get_mcu_freq()
|
||||
self._mcu_freq = 0.
|
||||
self._cmd_queue = mcu.alloc_command_queue()
|
||||
mcu.add_config_cmd("config_analog_in oid=%d pin=%s" % (self._oid, pin))
|
||||
self._query_cmd = None
|
||||
mcu.add_init_callback(self._init_callback)
|
||||
mcu.register_msg(self._handle_analog_in_state, "analog_in_state"
|
||||
, self._oid)
|
||||
self._query_cmd = mcu.lookup_command(
|
||||
self._query_cmd = None
|
||||
def build_config(self):
|
||||
self._mcu_freq = self._mcu.get_mcu_freq()
|
||||
self._query_cmd = self._mcu.lookup_command(
|
||||
"query_analog_in oid=%c clock=%u sample_ticks=%u sample_count=%c"
|
||||
" rest_ticks=%u min_value=%hu max_value=%hu")
|
||||
def set_minmax(self, sample_time, sample_count, minval=None, maxval=None):
|
||||
self._sample_ticks = int(sample_time * self._mcu_freq)
|
||||
def set_minmax(self, sample_time, sample_count, minval=0., maxval=1.):
|
||||
self._sample_time = sample_time
|
||||
self._sample_count = sample_count
|
||||
if minval is None:
|
||||
minval = 0
|
||||
if maxval is None:
|
||||
maxval = 0xffff
|
||||
mcu_adc_max = float(self._mcu.serial.msgparser.config["ADC_MAX"])
|
||||
max_adc = sample_count * mcu_adc_max
|
||||
self._min_sample = int(minval * max_adc)
|
||||
self._max_sample = min(0xffff, int(math.ceil(maxval * max_adc)))
|
||||
self._inv_max_adc = 1.0 / max_adc
|
||||
self._min_sample = minval
|
||||
self._max_sample = maxval
|
||||
def _init_callback(self):
|
||||
if not self._sample_count:
|
||||
return
|
||||
last_clock, last_clock_time = self._mcu.get_last_clock()
|
||||
clock = last_clock + int(self._mcu_freq * (1.0 + self._oid * 0.01)) # XXX
|
||||
sample_ticks = int(self._sample_time * self._mcu_freq)
|
||||
mcu_adc_max = self._mcu.serial.msgparser.get_constant_float("ADC_MAX")
|
||||
max_adc = self._sample_count * mcu_adc_max
|
||||
self._inv_max_adc = 1.0 / max_adc
|
||||
self._report_clock = int(self._report_time * self._mcu_freq)
|
||||
self._mcu.register_msg(self._handle_analog_in_state, "analog_in_state"
|
||||
, self._oid)
|
||||
min_sample = int(self._min_sample * max_adc)
|
||||
max_sample = min(0xffff, int(math.ceil(self._max_sample * max_adc)))
|
||||
msg = self._query_cmd.encode(
|
||||
self._oid, clock, self._sample_ticks, self._sample_count
|
||||
, self._report_clock, self._min_sample, self._max_sample)
|
||||
self._oid, clock, sample_ticks, self._sample_count
|
||||
, self._report_clock, min_sample, max_sample)
|
||||
self._mcu.send(msg, reqclock=clock, cq=self._cmd_queue)
|
||||
def _handle_analog_in_state(self, params):
|
||||
last_value = params['value'] * self._inv_max_adc
|
||||
@@ -301,7 +363,7 @@ class MCU_adc:
|
||||
if self._callback is not None:
|
||||
self._callback(last_read_time, last_value)
|
||||
def set_adc_callback(self, report_time, callback):
|
||||
self._report_clock = int(report_time * self._mcu_freq)
|
||||
self._report_time = report_time
|
||||
self._callback = callback
|
||||
|
||||
class MCU:
|
||||
@@ -309,60 +371,91 @@ class MCU:
|
||||
COMM_TIMEOUT = 3.5
|
||||
def __init__(self, printer, config):
|
||||
self._printer = printer
|
||||
self._config = config
|
||||
# Serial port
|
||||
baud = config.getint('baud', 115200)
|
||||
serialport = config.get('serial', '/dev/ttyS0')
|
||||
self.serial = serialhdl.SerialReader(printer.reactor, serialport, baud)
|
||||
baud = config.getint('baud', 250000)
|
||||
self._serialport = config.get('serial', '/dev/ttyS0')
|
||||
self.serial = serialhdl.SerialReader(
|
||||
printer.reactor, self._serialport, baud)
|
||||
self.is_shutdown = False
|
||||
self._shutdown_msg = ""
|
||||
self._is_fileoutput = False
|
||||
self._timeout_timer = printer.reactor.register_timer(
|
||||
self.timeout_handler)
|
||||
rmethods = {m: m for m in ['arduino', 'command', 'rpi_usb']}
|
||||
self._restart_method = config.getchoice(
|
||||
'restart_method', rmethods, 'arduino')
|
||||
# Config building
|
||||
self._emergency_stop_cmd = self._clear_shutdown_cmd = None
|
||||
self._num_oids = 0
|
||||
if printer.bglogger is not None:
|
||||
printer.bglogger.set_rollover_info("mcu", None)
|
||||
self._config_error = config.error
|
||||
self._emergency_stop_cmd = self._reset_cmd = None
|
||||
self._oids = []
|
||||
self._config_cmds = []
|
||||
self._config_crc = None
|
||||
self._init_callbacks = []
|
||||
self._pin_map = config.get('pin_map', None)
|
||||
self._custom = config.get('custom', '')
|
||||
# Move command queuing
|
||||
ffi_main, self.ffi_lib = chelper.get_ffi()
|
||||
ffi_main, self._ffi_lib = chelper.get_ffi()
|
||||
self._max_stepper_error = config.getfloat(
|
||||
'max_stepper_error', 0.000025, minval=0.)
|
||||
self._steppers = []
|
||||
self._steppersync = None
|
||||
# Print time to clock epoch calculations
|
||||
self._print_start_time = 0.
|
||||
self._mcu_freq = 0.
|
||||
# Stats
|
||||
self._stats_sumsq_base = 0.
|
||||
self._mcu_tick_avg = 0.
|
||||
self._mcu_tick_stddev = 0.
|
||||
def handle_mcu_stats(self, params):
|
||||
logging.debug("mcu stats: %s" % (params,))
|
||||
count = params['count']
|
||||
tick_sum = params['sum']
|
||||
c = 1.0 / (count * self._mcu_freq)
|
||||
self._mcu_tick_avg = tick_sum * c
|
||||
tick_sumsq = params['sumsq']
|
||||
tick_sumavgsq = ((tick_sum // (256*count)) * count)**2
|
||||
self._mcu_tick_stddev = c * 256. * math.sqrt(
|
||||
count * tick_sumsq - tick_sumavgsq)
|
||||
tick_sumsq = params['sumsq'] * self._stats_sumsq_base
|
||||
self._mcu_tick_stddev = c * math.sqrt(count*tick_sumsq - tick_sum**2)
|
||||
def handle_shutdown(self, params):
|
||||
if self.is_shutdown:
|
||||
return
|
||||
self.is_shutdown = True
|
||||
logging.info("%s: %s" % (params['#name'], params['#msg']))
|
||||
self._shutdown_msg = params['#msg']
|
||||
logging.info("%s: %s" % (params['#name'], self._shutdown_msg))
|
||||
pst = self._print_start_time
|
||||
logging.info("Clock last synchronized at %.6f (%d)" % (
|
||||
pst, int(pst * self._mcu_freq)))
|
||||
self.serial.dump_debug()
|
||||
self._printer.note_shutdown(params['#msg'])
|
||||
self._printer.note_shutdown(self._shutdown_msg)
|
||||
# Connection phase
|
||||
def _check_restart(self, reason):
|
||||
if self._printer.get_startup_state() == 'firmware_restart':
|
||||
return
|
||||
logging.info("Attempting automated firmware restart: %s" % (reason,))
|
||||
self._printer.request_exit('firmware_restart')
|
||||
self._printer.reactor.pause(self._printer.reactor.monotonic() + 2.000)
|
||||
raise error("Attempt firmware restart failed")
|
||||
def connect(self):
|
||||
if not self._is_fileoutput:
|
||||
if (self._restart_method == 'rpi_usb'
|
||||
and not os.path.exists(self._serialport)):
|
||||
# Try toggling usb power
|
||||
self._check_restart("enable power")
|
||||
self.serial.connect()
|
||||
self._printer.reactor.update_timer(
|
||||
self._timeout_timer, time.time() + self.COMM_TIMEOUT)
|
||||
self._mcu_freq = float(self.serial.msgparser.config['CLOCK_FREQ'])
|
||||
self._timeout_timer, self.monotonic() + self.COMM_TIMEOUT)
|
||||
self._mcu_freq = self.serial.msgparser.get_constant_float('CLOCK_FREQ')
|
||||
self._stats_sumsq_base = self.serial.msgparser.get_constant_float(
|
||||
'STATS_SUMSQ_BASE')
|
||||
self._emergency_stop_cmd = self.lookup_command("emergency_stop")
|
||||
self._clear_shutdown_cmd = self.lookup_command("clear_shutdown")
|
||||
try:
|
||||
self._reset_cmd = self.lookup_command("reset")
|
||||
except self.serial.msgparser.error, e:
|
||||
pass
|
||||
self.register_msg(self.handle_shutdown, 'shutdown')
|
||||
self.register_msg(self.handle_shutdown, 'is_shutdown')
|
||||
self.register_msg(self.handle_mcu_stats, 'stats')
|
||||
self._build_config()
|
||||
self._send_config()
|
||||
def connect_file(self, debugoutput, dictionary, pace=False):
|
||||
self._is_fileoutput = True
|
||||
self.serial.connect_file(debugoutput, dictionary)
|
||||
@@ -370,7 +463,7 @@ class MCU:
|
||||
def dummy_set_print_start_time(eventtime):
|
||||
pass
|
||||
def dummy_get_print_buffer_time(eventtime, last_move_end):
|
||||
return 0.250
|
||||
return 1.250
|
||||
self.set_print_start_time = dummy_set_print_start_time
|
||||
self.get_print_buffer_time = dummy_get_print_buffer_time
|
||||
def timeout_handler(self, eventtime):
|
||||
@@ -385,29 +478,45 @@ class MCU:
|
||||
def disconnect(self):
|
||||
self.serial.disconnect()
|
||||
if self._steppersync is not None:
|
||||
self.ffi_lib.steppersync_free(self._steppersync)
|
||||
self._ffi_lib.steppersync_free(self._steppersync)
|
||||
self._steppersync = None
|
||||
def stats(self, eventtime):
|
||||
stats = self.serial.stats(eventtime)
|
||||
stats += " mcu_task_avg=%.06f mcu_task_stddev=%.06f" % (
|
||||
return "%s mcu_task_avg=%.06f mcu_task_stddev=%.06f" % (
|
||||
self.serial.stats(eventtime),
|
||||
self._mcu_tick_avg, self._mcu_tick_stddev)
|
||||
err = 0
|
||||
for s in self._steppers:
|
||||
err += s.get_errors()
|
||||
if err:
|
||||
stats += " step_errors=%d" % (err,)
|
||||
return stats
|
||||
def force_shutdown(self):
|
||||
self.send(self._emergency_stop_cmd.encode())
|
||||
def clear_shutdown(self):
|
||||
logging.info("Sending clear_shutdown command")
|
||||
self.send(self._clear_shutdown_cmd.encode())
|
||||
def microcontroller_restart(self):
|
||||
reactor = self._printer.reactor
|
||||
if self._restart_method == 'rpi_usb':
|
||||
logging.info("Attempting a microcontroller reset via rpi usb power")
|
||||
self.disconnect()
|
||||
chelper.run_hub_ctrl(0)
|
||||
reactor.pause(reactor.monotonic() + 2.000)
|
||||
chelper.run_hub_ctrl(1)
|
||||
return
|
||||
if self._restart_method == 'command':
|
||||
last_clock, last_clock_time = self.serial.get_last_clock()
|
||||
eventtime = reactor.monotonic()
|
||||
if (self._reset_cmd is None
|
||||
or eventtime > last_clock_time + self.COMM_TIMEOUT):
|
||||
logging.info("Unable to issue reset command")
|
||||
return
|
||||
# Attempt reset via command
|
||||
logging.info("Attempting a microcontroller reset command")
|
||||
self.send(self._reset_cmd.encode())
|
||||
reactor.pause(reactor.monotonic() + 0.015)
|
||||
self.disconnect()
|
||||
return
|
||||
# Attempt reset via arduino mechanism
|
||||
logging.info("Attempting a microcontroller reset")
|
||||
self.disconnect()
|
||||
serialhdl.arduino_reset(self._serialport, reactor)
|
||||
def is_fileoutput(self):
|
||||
return self._is_fileoutput
|
||||
# Configuration phase
|
||||
def _add_custom(self):
|
||||
data = self._config.get('custom', '')
|
||||
for line in data.split('\n'):
|
||||
for line in self._custom.split('\n'):
|
||||
line = line.strip()
|
||||
cpos = line.find('#')
|
||||
if cpos >= 0:
|
||||
@@ -415,33 +524,30 @@ class MCU:
|
||||
if not line:
|
||||
continue
|
||||
self.add_config_cmd(line)
|
||||
def build_config(self):
|
||||
def _build_config(self):
|
||||
# Build config commands
|
||||
for oid in self._oids:
|
||||
oid.build_config()
|
||||
self._add_custom()
|
||||
self._config_cmds.insert(0, "allocate_oids count=%d" % (
|
||||
self._num_oids,))
|
||||
len(self._oids),))
|
||||
|
||||
# Resolve pin names
|
||||
mcu = self.serial.msgparser.config['MCU']
|
||||
pin_map = self._config.get('pin_map', None)
|
||||
if pin_map is None:
|
||||
pnames = pins.mcu_to_pins(mcu)
|
||||
else:
|
||||
pnames = pins.map_pins(pin_map, mcu)
|
||||
mcu = self.serial.msgparser.get_constant('MCU')
|
||||
pnames = pins.get_pin_map(mcu, self._pin_map)
|
||||
updated_cmds = []
|
||||
for cmd in self._config_cmds:
|
||||
try:
|
||||
updated_cmds.append(pins.update_command(cmd, pnames))
|
||||
updated_cmds.append(pins.update_command(
|
||||
cmd, self._mcu_freq, pnames))
|
||||
except:
|
||||
raise self._config.error("Unable to translate pin name: %s" % (
|
||||
raise self._config_error("Unable to translate pin name: %s" % (
|
||||
cmd,))
|
||||
self._config_cmds = updated_cmds
|
||||
|
||||
# Calculate config CRC
|
||||
self._config_crc = zlib.crc32('\n'.join(self._config_cmds)) & 0xffffffff
|
||||
self.add_config_cmd("finalize_config crc=%d" % (self._config_crc,))
|
||||
|
||||
self._send_config()
|
||||
def _send_config(self):
|
||||
msg = self.create_command("get_config")
|
||||
if self._is_fileoutput:
|
||||
@@ -450,25 +556,45 @@ class MCU:
|
||||
else:
|
||||
config_params = self.serial.send_with_response(msg, 'config')
|
||||
if not config_params['is_config']:
|
||||
if self._restart_method == 'rpi_usb':
|
||||
# Only configure mcu after usb power reset
|
||||
self._check_restart("full reset before config")
|
||||
# Send config commands
|
||||
logging.info("Sending printer configuration...")
|
||||
for c in self._config_cmds:
|
||||
self.send(self.create_command(c))
|
||||
if not self._is_fileoutput:
|
||||
config_params = self.serial.send_with_response(msg, 'config')
|
||||
if not config_params['is_config']:
|
||||
if self.is_shutdown:
|
||||
raise error("Firmware error during config: %s" % (
|
||||
self._shutdown_msg,))
|
||||
raise error("Unable to configure printer")
|
||||
elif self._printer.get_startup_state() == 'firmware_restart':
|
||||
raise error("Failed automated reset of micro-controller")
|
||||
if self._config_crc != config_params['crc']:
|
||||
self._check_restart("CRC mismatch")
|
||||
raise error("Printer CRC does not match config")
|
||||
logging.info("Configured")
|
||||
move_count = config_params['move_count']
|
||||
logging.info("Configured (%d moves)" % (move_count,))
|
||||
if self._printer.bglogger is not None:
|
||||
msgparser = self.serial.msgparser
|
||||
info = [
|
||||
"Configured (%d moves)" % (move_count,),
|
||||
"Loaded %d commands (%s)" % (
|
||||
len(msgparser.messages_by_id), msgparser.version),
|
||||
"MCU config: %s" % (" ".join(
|
||||
["%s=%s" % (k, v) for k, v in msgparser.config.items()]))]
|
||||
self._printer.bglogger.set_rollover_info("mcu", "\n".join(info))
|
||||
stepqueues = tuple(s._stepqueue for s in self._steppers)
|
||||
self._steppersync = self.ffi_lib.steppersync_alloc(
|
||||
self.serial.serialqueue, stepqueues, len(stepqueues),
|
||||
config_params['move_count'])
|
||||
self._steppersync = self._ffi_lib.steppersync_alloc(
|
||||
self.serial.serialqueue, stepqueues, len(stepqueues), move_count)
|
||||
for cb in self._init_callbacks:
|
||||
cb()
|
||||
# Config creation helpers
|
||||
def create_oid(self):
|
||||
oid = self._num_oids
|
||||
self._num_oids += 1
|
||||
return oid
|
||||
def create_oid(self, oid):
|
||||
self._oids.append(oid)
|
||||
return len(self._oids) - 1
|
||||
def add_config_cmd(self, cmd):
|
||||
self._config_cmds.append(cmd)
|
||||
def add_init_callback(self, callback):
|
||||
@@ -484,26 +610,24 @@ class MCU:
|
||||
def create_command(self, msg):
|
||||
return self.serial.msgparser.create_command(msg)
|
||||
# Wrappers for mcu object creation
|
||||
def create_stepper(self, step_pin, dir_pin, min_stop_interval, max_error):
|
||||
return MCU_stepper(self, step_pin, dir_pin, min_stop_interval, max_error)
|
||||
def create_endstop(self, pin, stepper):
|
||||
return MCU_endstop(self, pin, stepper)
|
||||
def create_stepper(self, step_pin, dir_pin):
|
||||
return MCU_stepper(self, step_pin, dir_pin)
|
||||
def create_endstop(self, pin):
|
||||
return MCU_endstop(self, pin)
|
||||
def create_digital_out(self, pin, max_duration=2.):
|
||||
max_duration = int(max_duration * self._mcu_freq)
|
||||
return MCU_digital_out(self, pin, max_duration)
|
||||
def create_pwm(self, pin, hard_cycle_ticks, max_duration=2.):
|
||||
max_duration = int(max_duration * self._mcu_freq)
|
||||
if hard_cycle_ticks:
|
||||
return MCU_pwm(self, pin, hard_cycle_ticks, max_duration)
|
||||
def create_pwm(self, pin, cycle_time, hard_cycle_ticks=0, max_duration=2.):
|
||||
if hard_cycle_ticks < 0:
|
||||
return MCU_digital_out(self, pin, max_duration)
|
||||
cycle_ticks = int(self._mcu_freq / 10.)
|
||||
return MCU_pwm(self, pin, cycle_ticks, max_duration, hard_pwm=False)
|
||||
return MCU_pwm(self, pin, cycle_time, hard_cycle_ticks, max_duration)
|
||||
def create_adc(self, pin):
|
||||
return MCU_adc(self, pin)
|
||||
# Clock syncing
|
||||
def set_print_start_time(self, eventtime):
|
||||
est_mcu_time = self.serial.get_clock(eventtime) / self._mcu_freq
|
||||
clock = self.serial.get_clock(eventtime)
|
||||
logging.debug("Synchronizing mcu clock at %.6f to %d" % (
|
||||
eventtime, clock))
|
||||
est_mcu_time = clock / self._mcu_freq
|
||||
self._print_start_time = est_mcu_time
|
||||
def get_print_buffer_time(self, eventtime, print_time):
|
||||
if self.is_shutdown:
|
||||
@@ -517,14 +641,22 @@ class MCU:
|
||||
return self._mcu_freq
|
||||
def get_last_clock(self):
|
||||
return self.serial.get_last_clock()
|
||||
def get_max_stepper_error(self):
|
||||
return self._max_stepper_error
|
||||
# Move command queuing
|
||||
def send(self, cmd, minclock=0, reqclock=0, cq=None):
|
||||
self.serial.send(cmd, minclock, reqclock, cq=cq)
|
||||
def flush_moves(self, print_time):
|
||||
if self._steppersync is None:
|
||||
return
|
||||
mcu_time = print_time + self._print_start_time
|
||||
clock = int(mcu_time * self._mcu_freq)
|
||||
self.ffi_lib.steppersync_flush(self._steppersync, clock)
|
||||
ret = self._ffi_lib.steppersync_flush(self._steppersync, clock)
|
||||
if ret:
|
||||
raise error("Internal error in stepcompress")
|
||||
def pause(self, waketime):
|
||||
return self._printer.reactor.pause(waketime)
|
||||
def monotonic(self):
|
||||
return self._printer.reactor.monotonic()
|
||||
def __del__(self):
|
||||
self.disconnect()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Protocol definitions for firmware communication
|
||||
#
|
||||
# Copyright (C) 2016 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.
|
||||
import json, zlib, logging
|
||||
@@ -106,8 +106,8 @@ class MessageFormat:
|
||||
self.name = parts[0]
|
||||
argparts = [arg.split('=') for arg in parts[1:]]
|
||||
self.param_types = [MessageTypes[fmt] for name, fmt in argparts]
|
||||
self.param_names = [name for name, fmt in argparts]
|
||||
self.name_to_type = dict(zip(self.param_names, self.param_types))
|
||||
self.param_names = [(name, MessageTypes[fmt]) for name, fmt in argparts]
|
||||
self.name_to_type = dict(self.param_names)
|
||||
def encode(self, *params):
|
||||
out = []
|
||||
out.append(self.msgid)
|
||||
@@ -117,26 +117,24 @@ class MessageFormat:
|
||||
def encode_by_name(self, **params):
|
||||
out = []
|
||||
out.append(self.msgid)
|
||||
for name, t in zip(self.param_names, self.param_types):
|
||||
for name, t in self.param_names:
|
||||
t.encode(out, params[name])
|
||||
return out
|
||||
def parse(self, s, pos):
|
||||
pos += 1
|
||||
out = {}
|
||||
for t, name in zip(self.param_types, self.param_names):
|
||||
for name, t in self.param_names:
|
||||
v, pos = t.parse(s, pos)
|
||||
out[name] = v
|
||||
return out, pos
|
||||
def dump(self, s, pos):
|
||||
pos += 1
|
||||
def format_params(self, params):
|
||||
out = []
|
||||
for t in self.param_types:
|
||||
v, pos = t.parse(s, pos)
|
||||
for name, t in self.param_names:
|
||||
v = params[name]
|
||||
if not t.is_int:
|
||||
v = repr(v)
|
||||
out.append(v)
|
||||
outmsg = self.debugformat % tuple(out)
|
||||
return outmsg, pos
|
||||
return self.debugformat % tuple(out)
|
||||
|
||||
class OutputFormat:
|
||||
name = '#output'
|
||||
@@ -164,17 +162,13 @@ class OutputFormat:
|
||||
out = []
|
||||
for t in self.param_types:
|
||||
v, pos = t.parse(s, pos)
|
||||
if not t.is_int:
|
||||
v = repr(v)
|
||||
out.append(v)
|
||||
outmsg = self.debugformat % tuple(out)
|
||||
return {'#msg': outmsg}, pos
|
||||
def dump(self, s, pos):
|
||||
pos += 1
|
||||
out = []
|
||||
for t in self.param_types:
|
||||
v, pos = t.parse(s, pos)
|
||||
out.append(v)
|
||||
outmsg = self.debugformat % tuple(out)
|
||||
return outmsg, pos
|
||||
def format_params(self, params):
|
||||
return "#output %s" % (params['#msg'],)
|
||||
|
||||
class UnknownFormat:
|
||||
name = '#unknown'
|
||||
@@ -182,8 +176,11 @@ class UnknownFormat:
|
||||
msgid = s[pos]
|
||||
msg = str(bytearray(s))
|
||||
return {'#msgid': msgid, '#msg': msg}, len(s)-MESSAGE_TRAILER_SIZE
|
||||
def format_params(self, params):
|
||||
return "#unknown %s" % (repr(params['#msg']),)
|
||||
|
||||
class MessageParser:
|
||||
error = error
|
||||
def __init__(self):
|
||||
self.unknown = UnknownFormat()
|
||||
self.messages_by_id = {}
|
||||
@@ -220,11 +217,20 @@ class MessageParser:
|
||||
while 1:
|
||||
msgid = s[pos]
|
||||
mid = self.messages_by_id.get(msgid, self.unknown)
|
||||
params, pos = mid.dump(s, pos)
|
||||
out.append("%s" % (params,))
|
||||
params, pos = mid.parse(s, pos)
|
||||
out.append(mid.format_params(params))
|
||||
if pos >= len(s)-MESSAGE_TRAILER_SIZE:
|
||||
break
|
||||
return out
|
||||
def format_params(self, params):
|
||||
name = params.get('#name')
|
||||
mid = self.messages_by_name.get(name)
|
||||
if mid is not None:
|
||||
return mid.format_params(params)
|
||||
msg = params.get('#msg')
|
||||
if msg is not None:
|
||||
return "%s %s" % (name, msg)
|
||||
return str(params)
|
||||
def parse(self, s):
|
||||
msgid = s[MESSAGE_HEADER_SIZE]
|
||||
mid = self.messages_by_id.get(msgid, self.unknown)
|
||||
@@ -308,3 +314,15 @@ class MessageParser:
|
||||
self.static_strings = data.get('static_strings', [])
|
||||
self.config.update(data.get('config', {}))
|
||||
self.version = data.get('version', '')
|
||||
def get_constant(self, name):
|
||||
try:
|
||||
return self.config[name]
|
||||
except KeyError:
|
||||
raise error("Firmware constant '%s' not found" % (name,))
|
||||
def get_constant_float(self, name):
|
||||
try:
|
||||
return float(self.config[name])
|
||||
except ValueError:
|
||||
raise error("Firmware constant '%s' not a float" % (name,))
|
||||
except KeyError:
|
||||
raise error("Firmware constant '%s' not found" % (name,))
|
||||
|
||||
@@ -16,25 +16,13 @@ def port_pins(port_count, bit_count=8):
|
||||
pins['P%c%d' % (portchr, portbit)] = port * bit_count + portbit
|
||||
return pins
|
||||
|
||||
PINS_atmega164 = port_pins(4)
|
||||
PINS_atmega1280 = port_pins(12)
|
||||
|
||||
MCU_PINS = {
|
||||
"atmega168": PINS_atmega164, "atmega644p": PINS_atmega164,
|
||||
"atmega168": port_pins(4), "atmega644p": port_pins(4),
|
||||
"at90usb1286": port_pins(5),
|
||||
"atmega1280": PINS_atmega1280, "atmega2560": PINS_atmega1280,
|
||||
"atmega1280": port_pins(12), "atmega2560": port_pins(12),
|
||||
"sam3x8e": port_pins(4, 32)
|
||||
}
|
||||
|
||||
def mcu_to_pins(mcu):
|
||||
return MCU_PINS.get(mcu, {})
|
||||
|
||||
re_pin = re.compile(r'(?P<prefix>[ _]pin=)(?P<name>[^ ]*)')
|
||||
def update_command(cmd, pmap):
|
||||
def fixup(m):
|
||||
return m.group('prefix') + str(pmap[m.group('name')])
|
||||
return re_pin.sub(fixup, cmd)
|
||||
|
||||
|
||||
######################################################################
|
||||
# Arduino mappings
|
||||
@@ -96,12 +84,28 @@ Arduino_from_mcu = {
|
||||
"sam3x8e": (Arduino_Due, Arduino_Due_analog),
|
||||
}
|
||||
|
||||
def map_pins(name, mcu):
|
||||
|
||||
######################################################################
|
||||
# External commands
|
||||
######################################################################
|
||||
|
||||
# Obtains the pin mappings
|
||||
def get_pin_map(mcu, mapping_name=None):
|
||||
pins = MCU_PINS.get(mcu, {})
|
||||
if name == 'arduino':
|
||||
if mapping_name == 'arduino':
|
||||
dpins, apins = Arduino_from_mcu.get(mcu, [])
|
||||
for i in range(len(dpins)):
|
||||
pins['ar' + str(i)] = pins[dpins[i]]
|
||||
for i in range(len(apins)):
|
||||
pins['analog%d' % (i,)] = pins[apins[i]]
|
||||
return pins
|
||||
|
||||
# Translate pin names and tick times in a firmware command
|
||||
re_pin = re.compile(r'(?P<prefix>[ _]pin=)(?P<name>[^ ]*)')
|
||||
re_ticks = re.compile(r'TICKS\((?P<ticks>[^)]*)\)')
|
||||
def update_command(cmd, mcu_freq, pmap):
|
||||
def pin_fixup(m):
|
||||
return m.group('prefix') + str(pmap[m.group('name')])
|
||||
def ticks_fixup(m):
|
||||
return str(int(mcu_freq * float(m.group('ticks'))))
|
||||
return re_ticks.sub(ticks_fixup, re_pin.sub(pin_fixup, cmd))
|
||||
|
||||
@@ -9,17 +9,20 @@
|
||||
#include <stdint.h> // uint8_t
|
||||
#include <stdio.h> // fprintf
|
||||
#include <string.h> // strerror
|
||||
#include <sys/time.h> // gettimeofday
|
||||
#include <time.h> // struct timespec
|
||||
#include "pyhelper.h" // get_time
|
||||
#include "pyhelper.h" // get_monotonic
|
||||
|
||||
// Return the current system time as a double
|
||||
// Return the monotonic system time as a double
|
||||
double
|
||||
get_time(void)
|
||||
get_monotonic(void)
|
||||
{
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.;
|
||||
struct timespec ts;
|
||||
int ret = clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
if (ret) {
|
||||
report_errno("clock_gettime", ret);
|
||||
return 0.;
|
||||
}
|
||||
return (double)ts.tv_sec + (double)ts.tv_nsec * .000000001;
|
||||
}
|
||||
|
||||
// Fill a 'struct timespec' with a system time stored in a double
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#ifndef PYHELPER_H
|
||||
#define PYHELPER_H
|
||||
|
||||
double get_time(void);
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
double get_monotonic(void);
|
||||
struct timespec fill_time(double time);
|
||||
void set_python_logging_callback(void (*func)(const char *));
|
||||
void errorf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Code to implement asynchronous logging from a background thread
|
||||
#
|
||||
# Copyright (C) 2016 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.
|
||||
import logging, threading, Queue
|
||||
import logging, logging.handlers, threading, Queue, time
|
||||
|
||||
# Class to forward all messages through a queue to a background thread
|
||||
class QueueHandler(logging.Handler):
|
||||
@@ -21,27 +21,39 @@ class QueueHandler(logging.Handler):
|
||||
self.handleError(record)
|
||||
|
||||
# Class to poll a queue in a background thread and log each message
|
||||
class QueueListener(object):
|
||||
def __init__(self, handler):
|
||||
self.handler = handler
|
||||
self.queue = Queue.Queue()
|
||||
self.thread = threading.Thread(target=self._bg_thread)
|
||||
self.thread.start()
|
||||
class QueueListener(logging.handlers.TimedRotatingFileHandler):
|
||||
def __init__(self, filename):
|
||||
logging.handlers.TimedRotatingFileHandler.__init__(
|
||||
self, filename, when='midnight', backupCount=5)
|
||||
self.bg_queue = Queue.Queue()
|
||||
self.bg_thread = threading.Thread(target=self._bg_thread)
|
||||
self.bg_thread.start()
|
||||
self.rollover_info = {}
|
||||
def _bg_thread(self):
|
||||
while 1:
|
||||
record = self.queue.get(True)
|
||||
record = self.bg_queue.get(True)
|
||||
if record is None:
|
||||
break
|
||||
self.handler.handle(record)
|
||||
self.handle(record)
|
||||
def stop(self):
|
||||
self.queue.put_nowait(None)
|
||||
self.thread.join()
|
||||
self.bg_queue.put_nowait(None)
|
||||
self.bg_thread.join()
|
||||
def set_rollover_info(self, name, info):
|
||||
self.rollover_info[name] = info
|
||||
def doRollover(self):
|
||||
logging.handlers.TimedRotatingFileHandler.doRollover(self)
|
||||
lines = [self.rollover_info[name]
|
||||
for name in sorted(self.rollover_info)
|
||||
if self.rollover_info[name]]
|
||||
lines.append(
|
||||
"=============== Log rollover at %s ===============" % (
|
||||
time.asctime(),))
|
||||
self.emit(logging.makeLogRecord(
|
||||
{'msg': "\n".join(lines), 'level': logging.INFO}))
|
||||
|
||||
def setup_bg_logging(filename, debuglevel):
|
||||
logoutput = open(filename, 'wb')
|
||||
handler = logging.StreamHandler(logoutput)
|
||||
ql = QueueListener(handler)
|
||||
qh = QueueHandler(ql.queue)
|
||||
ql = QueueListener(filename)
|
||||
qh = QueueHandler(ql.bg_queue)
|
||||
root = logging.getLogger()
|
||||
root.addHandler(qh)
|
||||
root.setLevel(debuglevel)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# File descriptor and timer event helper
|
||||
#
|
||||
# Copyright (C) 2016 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.
|
||||
import select, time, math
|
||||
import select, math, time
|
||||
import greenlet
|
||||
import chelper
|
||||
|
||||
class ReactorTimer:
|
||||
def __init__(self, callback, waketime):
|
||||
@@ -33,6 +34,7 @@ class SelectReactor:
|
||||
self._process = False
|
||||
self._g_dispatch = None
|
||||
self._greenlets = []
|
||||
self.monotonic = chelper.get_ffi()[1].get_monotonic
|
||||
# Timers
|
||||
def _note_time(self, t):
|
||||
nexttime = t.waketime
|
||||
@@ -67,11 +69,19 @@ class SelectReactor:
|
||||
self._note_time(t)
|
||||
if eventtime >= self._next_timer:
|
||||
return 0.
|
||||
return min(1., max(.001, self._next_timer - time.time()))
|
||||
return min(1., max(.001, self._next_timer - self.monotonic()))
|
||||
# Greenlets
|
||||
def _sys_pause(self, waketime):
|
||||
# Pause using system sleep for when reactor not running
|
||||
delay = waketime - self.monotonic()
|
||||
if delay > 0.:
|
||||
time.sleep(delay)
|
||||
return self.monotonic()
|
||||
def pause(self, waketime):
|
||||
g = greenlet.getcurrent()
|
||||
if g is not self._g_dispatch:
|
||||
if self._g_dispatch is None:
|
||||
return self._sys_pause(waketime)
|
||||
return self._g_dispatch.switch(waketime)
|
||||
if self._greenlets:
|
||||
g_next = self._greenlets.pop()
|
||||
@@ -95,20 +105,21 @@ class SelectReactor:
|
||||
self._fds.pop(self._fds.index(handler))
|
||||
# Main loop
|
||||
def _dispatch_loop(self):
|
||||
self._process = True
|
||||
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
||||
eventtime = time.time()
|
||||
eventtime = self.monotonic()
|
||||
while self._process:
|
||||
timeout = self._check_timers(eventtime)
|
||||
res = select.select(self._fds, [], [], timeout)
|
||||
eventtime = time.time()
|
||||
eventtime = self.monotonic()
|
||||
for fd in res[0]:
|
||||
fd.callback(eventtime)
|
||||
if g_dispatch is not self._g_dispatch:
|
||||
self._end_greenlet(g_dispatch)
|
||||
eventtime = self.monotonic()
|
||||
break
|
||||
self._g_dispatch = None
|
||||
def run(self):
|
||||
self._process = True
|
||||
g_next = ReactorGreenlet(run=self._dispatch_loop)
|
||||
g_next.switch()
|
||||
def end(self):
|
||||
@@ -134,17 +145,17 @@ class PollReactor(SelectReactor):
|
||||
self._fds = fds
|
||||
# Main loop
|
||||
def _dispatch_loop(self):
|
||||
self._process = True
|
||||
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
||||
eventtime = time.time()
|
||||
eventtime = self.monotonic()
|
||||
while self._process:
|
||||
timeout = int(math.ceil(self._check_timers(eventtime) * 1000.))
|
||||
res = self._poll.poll(timeout)
|
||||
eventtime = time.time()
|
||||
timeout = self._check_timers(eventtime)
|
||||
res = self._poll.poll(int(math.ceil(timeout * 1000.)))
|
||||
eventtime = self.monotonic()
|
||||
for fd, event in res:
|
||||
self._fds[fd](eventtime)
|
||||
if g_dispatch is not self._g_dispatch:
|
||||
self._end_greenlet(g_dispatch)
|
||||
eventtime = self.monotonic()
|
||||
break
|
||||
self._g_dispatch = None
|
||||
|
||||
@@ -168,17 +179,17 @@ class EPollReactor(SelectReactor):
|
||||
self._fds = fds
|
||||
# Main loop
|
||||
def _dispatch_loop(self):
|
||||
self._process = True
|
||||
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
||||
eventtime = time.time()
|
||||
eventtime = self.monotonic()
|
||||
while self._process:
|
||||
timeout = self._check_timers(eventtime)
|
||||
res = self._epoll.poll(timeout)
|
||||
eventtime = time.time()
|
||||
eventtime = self.monotonic()
|
||||
for fd, event in res:
|
||||
self._fds[fd](eventtime)
|
||||
if g_dispatch is not self._g_dispatch:
|
||||
self._end_greenlet(g_dispatch)
|
||||
eventtime = self.monotonic()
|
||||
break
|
||||
self._g_dispatch = None
|
||||
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import time, logging, threading
|
||||
import logging, threading
|
||||
import serial
|
||||
|
||||
import msgproto, chelper, util
|
||||
|
||||
class error(Exception):
|
||||
pass
|
||||
|
||||
class SerialReader:
|
||||
BITS_PER_BYTE = 10.
|
||||
def __init__(self, reactor, serialport, baud):
|
||||
@@ -30,8 +33,7 @@ class SerialReader:
|
||||
self.lock = threading.Lock()
|
||||
self.background_thread = None
|
||||
# Message handlers
|
||||
self.status_timer = self.reactor.register_timer(
|
||||
self._status_event, self.reactor.NOW)
|
||||
self.status_timer = self.reactor.register_timer(self._status_event)
|
||||
self.status_cmd = None
|
||||
handlers = {
|
||||
'#unknown': self.handle_unknown,
|
||||
@@ -60,10 +62,10 @@ class SerialReader:
|
||||
# Initial connection
|
||||
logging.info("Starting serial connect")
|
||||
while 1:
|
||||
starttime = time.time()
|
||||
starttime = self.reactor.monotonic()
|
||||
try:
|
||||
self.ser = serial.Serial(self.serialport, self.baud, timeout=0)
|
||||
except OSError, e:
|
||||
except (OSError, serial.SerialException), e:
|
||||
logging.warn("Unable to open port: %s" % (e,))
|
||||
self.reactor.pause(starttime + 5.)
|
||||
continue
|
||||
@@ -78,8 +80,6 @@ class SerialReader:
|
||||
if identify_data is None:
|
||||
logging.warn("Timeout on serial connect")
|
||||
self.disconnect()
|
||||
self.ser.close()
|
||||
self.ser = None
|
||||
continue
|
||||
break
|
||||
msgparser = msgproto.MessageParser()
|
||||
@@ -88,14 +88,29 @@ class SerialReader:
|
||||
self.register_callback(self.handle_unknown, '#unknown')
|
||||
logging.info("Loaded %d commands (%s)" % (
|
||||
len(msgparser.messages_by_id), msgparser.version))
|
||||
# Setup for runtime
|
||||
logging.info("MCU config: %s" % (" ".join(
|
||||
["%s=%s" % (k, v) for k, v in msgparser.config.items()])))
|
||||
# Setup baud adjust
|
||||
mcu_baud = float(msgparser.config.get('SERIAL_BAUD', 0.))
|
||||
if mcu_baud:
|
||||
baud_adjust = self.BITS_PER_BYTE / mcu_baud
|
||||
self.ffi_lib.serialqueue_set_baud_adjust(
|
||||
self.serialqueue, baud_adjust)
|
||||
# Enable periodic get_status timer
|
||||
get_status = msgparser.lookup_command('get_status')
|
||||
self.status_cmd = get_status.encode()
|
||||
self.reactor.update_timer(self.status_timer, self.reactor.NOW)
|
||||
# Load initial last_ack_clock/last_ack_time
|
||||
uptime_msg = msgparser.create_command('get_uptime')
|
||||
params = self.send_with_response(uptime_msg, 'uptime')
|
||||
self.last_ack_clock = (params['high'] << 32) | params['clock']
|
||||
self.last_ack_time = params['#receive_time']
|
||||
# Make sure est_clock is calculated
|
||||
starttime = eventtime = self.reactor.monotonic()
|
||||
while not self.est_clock:
|
||||
if eventtime > starttime + 5.:
|
||||
raise error("timeout on est_clock calculation")
|
||||
eventtime = self.reactor.pause(eventtime + 0.010)
|
||||
def connect_file(self, debugoutput, dictionary, pace=False):
|
||||
self.ser = debugoutput
|
||||
self.msgparser.process_identify(dictionary, decompress=False)
|
||||
@@ -104,19 +119,21 @@ class SerialReader:
|
||||
est_clock = float(self.msgparser.config['CLOCK_FREQ'])
|
||||
self.serialqueue = self.ffi_lib.serialqueue_alloc(self.ser.fileno(), 1)
|
||||
self.est_clock = est_clock
|
||||
self.last_ack_time = time.time()
|
||||
self.last_ack_time = self.reactor.monotonic()
|
||||
self.last_ack_clock = 0
|
||||
self.ffi_lib.serialqueue_set_clock_est(
|
||||
self.serialqueue, self.est_clock, self.last_ack_time
|
||||
, self.last_ack_clock)
|
||||
def disconnect(self):
|
||||
if self.serialqueue is None:
|
||||
return
|
||||
if self.serialqueue is not None:
|
||||
self.ffi_lib.serialqueue_exit(self.serialqueue)
|
||||
if self.background_thread is not None:
|
||||
self.background_thread.join()
|
||||
self.ffi_lib.serialqueue_free(self.serialqueue)
|
||||
self.background_thread = self.serialqueue = None
|
||||
if self.ser is not None:
|
||||
self.ser.close()
|
||||
self.ser = None
|
||||
def stats(self, eventtime):
|
||||
if self.serialqueue is None:
|
||||
return ""
|
||||
@@ -127,8 +144,6 @@ class SerialReader:
|
||||
self.est_clock, self.last_ack_time, self.last_ack_clock)
|
||||
return sqstats + tstats
|
||||
def _status_event(self, eventtime):
|
||||
if self.status_cmd is None:
|
||||
return eventtime + 0.1
|
||||
self.send(self.status_cmd)
|
||||
return eventtime + 1.0
|
||||
# Serial response callbacks
|
||||
@@ -162,11 +177,9 @@ class SerialReader:
|
||||
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):
|
||||
src = SerialRetryCommand(self, cmd, name)
|
||||
def send_with_response(self, cmd, name, oid=None):
|
||||
src = SerialRetryCommand(self, cmd, name, oid)
|
||||
return src.get_response()
|
||||
def send_flush(self):
|
||||
self.ffi_lib.serialqueue_flush_ready(self.serialqueue)
|
||||
def alloc_command_queue(self):
|
||||
return self.ffi_main.gc(self.ffi_lib.serialqueue_alloc_commandqueue(),
|
||||
self.ffi_lib.serialqueue_free_commandqueue)
|
||||
@@ -226,16 +239,21 @@ class SerialReader:
|
||||
|
||||
# Class to retry sending of a query command until a given response is received
|
||||
class SerialRetryCommand:
|
||||
TIMEOUT_TIME = 5.0
|
||||
RETRY_TIME = 0.500
|
||||
def __init__(self, serial, cmd, name):
|
||||
def __init__(self, serial, cmd, name, oid=None):
|
||||
self.serial = serial
|
||||
self.cmd = cmd
|
||||
self.name = name
|
||||
self.oid = oid
|
||||
self.response = None
|
||||
self.min_query_time = time.time()
|
||||
self.serial.register_callback(self.handle_callback, self.name)
|
||||
self.min_query_time = self.serial.reactor.monotonic()
|
||||
self.serial.register_callback(self.handle_callback, self.name, self.oid)
|
||||
self.send_timer = self.serial.reactor.register_timer(
|
||||
self.send_event, self.serial.reactor.NOW)
|
||||
def unregister(self):
|
||||
self.serial.unregister_callback(self.name, self.oid)
|
||||
self.serial.reactor.unregister_timer(self.send_timer)
|
||||
def send_event(self, eventtime):
|
||||
if self.response is not None:
|
||||
return self.serial.reactor.NEVER
|
||||
@@ -246,11 +264,13 @@ class SerialRetryCommand:
|
||||
if last_sent_time >= self.min_query_time:
|
||||
self.response = params
|
||||
def get_response(self):
|
||||
eventtime = time.time()
|
||||
eventtime = self.serial.reactor.monotonic()
|
||||
while self.response is None:
|
||||
eventtime = self.serial.reactor.pause(eventtime + 0.05)
|
||||
self.serial.unregister_callback(self.name)
|
||||
self.serial.reactor.unregister_timer(self.send_timer)
|
||||
if eventtime > self.min_query_time + self.TIMEOUT_TIME:
|
||||
self.unregister()
|
||||
raise error("Timeout on wait for '%s' response" % (self.name,))
|
||||
self.unregister()
|
||||
return self.response
|
||||
|
||||
# Code to start communication and download message type dictionary
|
||||
@@ -267,7 +287,7 @@ class SerialBootStrap:
|
||||
self.send_timer = self.serial.reactor.register_timer(
|
||||
self.send_event, self.serial.reactor.NOW)
|
||||
def get_identify_data(self, timeout):
|
||||
eventtime = time.time()
|
||||
eventtime = self.serial.reactor.monotonic()
|
||||
while not self.is_done and eventtime <= timeout:
|
||||
eventtime = self.serial.reactor.pause(eventtime + 0.05)
|
||||
self.serial.unregister_callback('identify_response')
|
||||
@@ -305,10 +325,23 @@ def stk500v2_leave(ser, reactor):
|
||||
ser.read(1)
|
||||
# Send stk500v2 leave programmer sequence
|
||||
ser.baudrate = 115200
|
||||
reactor.pause(time.time() + 0.100)
|
||||
reactor.pause(reactor.monotonic() + 0.100)
|
||||
ser.read(4096)
|
||||
ser.write('\x1b\x01\x00\x01\x0e\x11\x04')
|
||||
reactor.pause(time.time() + 0.050)
|
||||
reactor.pause(reactor.monotonic() + 0.050)
|
||||
res = ser.read(4096)
|
||||
logging.debug("Got %s from stk500v2" % (repr(res),))
|
||||
ser.baudrate = origbaud
|
||||
|
||||
# Attempt an arduino style reset on a serial port
|
||||
def arduino_reset(serialport, reactor):
|
||||
# First try opening the port at 1200 baud
|
||||
ser = serial.Serial(serialport, 1200, timeout=0)
|
||||
ser.read(1)
|
||||
reactor.pause(reactor.monotonic() + 0.100)
|
||||
# Then try toggling DTR
|
||||
ser.dtr = True
|
||||
reactor.pause(reactor.monotonic() + 0.100)
|
||||
ser.dtr = False
|
||||
reactor.pause(reactor.monotonic() + 0.100)
|
||||
ser.close()
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include <termios.h> // tcflush
|
||||
#include <unistd.h> // pipe
|
||||
#include "list.h" // list_add_tail
|
||||
#include "pyhelper.h" // get_time
|
||||
#include "pyhelper.h" // get_monotonic
|
||||
#include "serialqueue.h" // struct queue_message
|
||||
|
||||
|
||||
@@ -149,11 +149,11 @@ static void
|
||||
pollreactor_run(struct pollreactor *pr)
|
||||
{
|
||||
pr->must_exit = 0;
|
||||
double eventtime = get_time();
|
||||
double eventtime = get_monotonic();
|
||||
while (! pr->must_exit) {
|
||||
int timeout = pollreactor_check_timers(pr, eventtime);
|
||||
int ret = poll(pr->fds, pr->num_fds, timeout);
|
||||
eventtime = get_time();
|
||||
eventtime = get_monotonic();
|
||||
if (ret > 0) {
|
||||
int i;
|
||||
for (i=0; i<pr->num_fds; i++)
|
||||
@@ -356,7 +356,6 @@ struct serialqueue {
|
||||
struct list_head pending_queues;
|
||||
int ready_bytes, stalled_bytes;
|
||||
uint64_t need_kick_clock;
|
||||
int can_delay_writes;
|
||||
// Received messages
|
||||
struct list_head receive_queue;
|
||||
// Debugging
|
||||
@@ -488,7 +487,8 @@ handle_message(struct serialqueue *sq, double eventtime, int len)
|
||||
if (rseq != sq->receive_seq)
|
||||
// New sequence number
|
||||
update_receive_seq(sq, eventtime, rseq);
|
||||
else if (len == MESSAGE_MIN && rseq > sq->retransmit_seq)
|
||||
else if (len == MESSAGE_MIN && rseq > sq->retransmit_seq
|
||||
&& !list_empty(&sq->sent_queue))
|
||||
// Duplicate sequence number in an empty message is a nak
|
||||
pollreactor_update_timer(&sq->pr, SQPT_RETRANSMIT, PR_NOW);
|
||||
|
||||
@@ -496,7 +496,7 @@ handle_message(struct serialqueue *sq, double eventtime, int len)
|
||||
// Add message to receive queue
|
||||
struct queue_message *qm = message_fill(sq->input_buf, len);
|
||||
qm->sent_time = sq->last_receive_sent_time;
|
||||
qm->receive_time = eventtime;
|
||||
qm->receive_time = get_monotonic(); // must be time post read()
|
||||
list_add_tail(&qm->node, &sq->receive_queue);
|
||||
check_wake_receive(sq);
|
||||
}
|
||||
@@ -695,11 +695,9 @@ check_send_command(struct serialqueue *sq, double eventtime)
|
||||
// Check for messages to send
|
||||
if (sq->ready_bytes >= MESSAGE_PAYLOAD_MAX)
|
||||
return PR_NOW;
|
||||
if (! sq->can_delay_writes) {
|
||||
if (! sq->est_clock) {
|
||||
if (sq->ready_bytes)
|
||||
return PR_NOW;
|
||||
if (sq->est_clock)
|
||||
sq->can_delay_writes = 1;
|
||||
sq->need_kick_clock = MAX_CLOCK;
|
||||
return PR_NEVER;
|
||||
}
|
||||
@@ -986,16 +984,6 @@ serialqueue_set_clock_est(struct serialqueue *sq, double est_clock
|
||||
pthread_mutex_unlock(&sq->lock);
|
||||
}
|
||||
|
||||
// Flush all messages in a "ready" state
|
||||
void
|
||||
serialqueue_flush_ready(struct serialqueue *sq)
|
||||
{
|
||||
pthread_mutex_lock(&sq->lock);
|
||||
sq->can_delay_writes = 0;
|
||||
pthread_mutex_unlock(&sq->lock);
|
||||
kick_bg_thread(sq);
|
||||
}
|
||||
|
||||
// Return a string buffer containing statistics for the serial port
|
||||
void
|
||||
serialqueue_get_stats(struct serialqueue *sq, char *buf, int len)
|
||||
|
||||
@@ -61,7 +61,6 @@ void serialqueue_pull(struct serialqueue *sq, struct pull_queue_message *pqm);
|
||||
void serialqueue_set_baud_adjust(struct serialqueue *sq, double baud_adjust);
|
||||
void serialqueue_set_clock_est(struct serialqueue *sq, double est_clock
|
||||
, double last_ack_time, uint64_t last_ack_clock);
|
||||
void serialqueue_flush_ready(struct serialqueue *sq);
|
||||
void serialqueue_get_stats(struct serialqueue *sq, char *buf, int len);
|
||||
int serialqueue_extract_old(struct serialqueue *sq, int sentq
|
||||
, struct pull_queue_message *q, int max);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Stepper pulse schedule compression
|
||||
//
|
||||
// Copyright (C) 2016 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.
|
||||
//
|
||||
@@ -31,10 +31,8 @@ struct stepcompress {
|
||||
uint64_t *queue, *queue_end, *queue_pos, *queue_next;
|
||||
// Internal tracking
|
||||
uint32_t max_error;
|
||||
// Error checking
|
||||
uint32_t errors;
|
||||
// Message generation
|
||||
uint64_t last_step_clock;
|
||||
uint64_t last_step_clock, homing_clock;
|
||||
struct list_head msg_queue;
|
||||
uint32_t queue_step_msgid, set_next_step_dir_msgid, oid;
|
||||
int sdir, invert_sdir;
|
||||
@@ -174,6 +172,9 @@ compress_bisect_add(struct stepcompress *sc)
|
||||
zerointerval = interval;
|
||||
zerocount = count;
|
||||
}
|
||||
if (count > 0x200)
|
||||
// No 'add' will improve sequence; avoid integer overflow
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if a greater or lesser add could extend the sequence
|
||||
@@ -221,27 +222,30 @@ compress_bisect_add(struct stepcompress *sc)
|
||||
* Step compress checking
|
||||
****************************************************************/
|
||||
|
||||
#define ERROR_RET -989898989
|
||||
|
||||
// Verify that a given 'step_move' matches the actual step times
|
||||
static void
|
||||
static int
|
||||
check_line(struct stepcompress *sc, struct step_move move)
|
||||
{
|
||||
if (!CHECK_LINES)
|
||||
return;
|
||||
return 0;
|
||||
if (move.count == 1) {
|
||||
if (move.interval != (uint32_t)(*sc->queue_pos - sc->last_step_clock)
|
||||
|| *sc->queue_pos < sc->last_step_clock) {
|
||||
errorf("Count 1 point out of range: %d %d %d"
|
||||
, move.interval, move.count, move.add);
|
||||
sc->errors++;
|
||||
errorf("stepcompress o=%d i=%d c=%d a=%d:"
|
||||
" Count 1 point out of range (%lld)"
|
||||
, sc->oid, move.interval, move.count, move.add
|
||||
, (long long)(*sc->queue_pos - sc->last_step_clock));
|
||||
return ERROR_RET;
|
||||
}
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
int err = 0;
|
||||
if (!move.count || (!move.interval && !move.add)
|
||||
|| move.interval >= 0x80000000) {
|
||||
errorf("Point out of range: %d %d %d"
|
||||
, move.interval, move.count, move.add);
|
||||
err++;
|
||||
errorf("stepcompress o=%d i=%d c=%d a=%d: Invalid sequence"
|
||||
, sc->oid, move.interval, move.count, move.add);
|
||||
return ERROR_RET;
|
||||
}
|
||||
uint32_t interval = move.interval, p = 0;
|
||||
uint16_t i;
|
||||
@@ -249,18 +253,21 @@ check_line(struct stepcompress *sc, struct step_move move)
|
||||
struct points point = minmax_point(sc, sc->queue_pos + i);
|
||||
p += interval;
|
||||
if (p < point.minp || p > point.maxp) {
|
||||
errorf("Point %d of %d: %d not in %d:%d"
|
||||
, i+1, move.count, p, point.minp, point.maxp);
|
||||
err++;
|
||||
errorf("stepcompress o=%d i=%d c=%d a=%d: Point %d: %d not in %d:%d"
|
||||
, sc->oid, move.interval, move.count, move.add
|
||||
, i+1, p, point.minp, point.maxp);
|
||||
return ERROR_RET;
|
||||
}
|
||||
if (interval >= 0x80000000) {
|
||||
errorf("Point %d of %d: interval overflow %d"
|
||||
, i+1, move.count, interval);
|
||||
err++;
|
||||
errorf("stepcompress o=%d i=%d c=%d a=%d:"
|
||||
" Point %d: interval overflow %d"
|
||||
, sc->oid, move.interval, move.count, move.add
|
||||
, i+1, interval);
|
||||
return ERROR_RET;
|
||||
}
|
||||
interval += move.add;
|
||||
}
|
||||
sc->errors += err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -268,22 +275,6 @@ check_line(struct stepcompress *sc, struct step_move move)
|
||||
* Step compress interface
|
||||
****************************************************************/
|
||||
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
|
||||
// Wrapper around sqrt() to handle small negative numbers
|
||||
static double
|
||||
_safe_sqrt(double v)
|
||||
{
|
||||
if (v > -0.001)
|
||||
// Due to floating point truncation, it's possible to get a
|
||||
// small negative number - treat it as zero.
|
||||
return 0.;
|
||||
return sqrt(v);
|
||||
}
|
||||
static inline double safe_sqrt(double v) {
|
||||
return likely(v >= 0.) ? sqrt(v) : _safe_sqrt(v);
|
||||
}
|
||||
|
||||
// Allocate a new 'stepcompress' object
|
||||
struct stepcompress *
|
||||
stepcompress_alloc(uint32_t max_error, uint32_t queue_step_msgid
|
||||
@@ -314,14 +305,16 @@ stepcompress_free(struct stepcompress *sc)
|
||||
}
|
||||
|
||||
// Convert previously scheduled steps into commands for the mcu
|
||||
static void
|
||||
static int
|
||||
stepcompress_flush(struct stepcompress *sc, uint64_t move_clock)
|
||||
{
|
||||
if (sc->queue_pos >= sc->queue_next)
|
||||
return;
|
||||
return 0;
|
||||
while (move_clock > sc->last_step_clock) {
|
||||
struct step_move move = compress_bisect_add(sc);
|
||||
check_line(sc, move);
|
||||
int ret = check_line(sc, move);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
uint32_t msg[5] = {
|
||||
sc->queue_step_msgid, sc->oid, move.interval, move.count, move.add
|
||||
@@ -332,10 +325,13 @@ stepcompress_flush(struct stepcompress *sc, uint64_t move_clock)
|
||||
// Be careful with 32bit overflow
|
||||
sc->last_step_clock = qm->req_clock = *sc->queue_pos;
|
||||
} else {
|
||||
uint32_t addfactor = move.count*(move.count-1)/2;
|
||||
int32_t addfactor = move.count*(move.count-1)/2;
|
||||
uint32_t ticks = move.add*addfactor + move.interval*move.count;
|
||||
sc->last_step_clock += ticks;
|
||||
}
|
||||
if (sc->homing_clock)
|
||||
// When homing, all steps should be sent prior to homing_clock
|
||||
qm->min_clock = qm->req_clock = sc->homing_clock;
|
||||
list_add_tail(&qm->node, &sc->msg_queue);
|
||||
|
||||
if (sc->queue_pos + move.count >= sc->queue_next) {
|
||||
@@ -344,232 +340,325 @@ stepcompress_flush(struct stepcompress *sc, uint64_t move_clock)
|
||||
}
|
||||
sc->queue_pos += move.count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Send the set_next_step_dir command
|
||||
static void
|
||||
static int
|
||||
set_next_step_dir(struct stepcompress *sc, int sdir)
|
||||
{
|
||||
if (sc->sdir == sdir)
|
||||
return 0;
|
||||
sc->sdir = sdir;
|
||||
stepcompress_flush(sc, UINT64_MAX);
|
||||
int ret = stepcompress_flush(sc, UINT64_MAX);
|
||||
if (ret)
|
||||
return ret;
|
||||
uint32_t msg[3] = {
|
||||
sc->set_next_step_dir_msgid, sc->oid, sdir ^ sc->invert_sdir
|
||||
};
|
||||
struct queue_message *qm = message_alloc_and_encode(msg, 3);
|
||||
qm->req_clock = sc->last_step_clock;
|
||||
qm->req_clock = sc->homing_clock ?: sc->last_step_clock;
|
||||
list_add_tail(&qm->node, &sc->msg_queue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if the internal queue needs to be expanded, and expand if so
|
||||
static inline void
|
||||
check_expand(struct stepcompress *sc, int sdir, int count)
|
||||
static int
|
||||
_check_push(struct stepcompress *sc)
|
||||
{
|
||||
if (sdir != sc->sdir)
|
||||
set_next_step_dir(sc, sdir);
|
||||
if (sc->queue_next + count > sc->queue_end)
|
||||
expand_queue(sc, count);
|
||||
if (sc->queue_next - sc->queue_pos > 65535 + 2000) {
|
||||
// No point in keeping more than 64K steps in memory
|
||||
int ret = stepcompress_flush(sc, *(sc->queue_next - 65535));
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Schedule a step event at the specified step_clock time
|
||||
void
|
||||
stepcompress_push(struct stepcompress *sc, double step_clock, int32_t sdir)
|
||||
{
|
||||
sdir = !!sdir;
|
||||
check_expand(sc, sdir, 1);
|
||||
step_clock += 0.5;
|
||||
*sc->queue_next++ = step_clock;
|
||||
}
|
||||
|
||||
// Schedule 'steps' number of steps with a constant time between steps
|
||||
// using the formula: step_clock = clock_offset + step_num*factor
|
||||
int32_t
|
||||
stepcompress_push_factor(struct stepcompress *sc
|
||||
, double steps, double step_offset
|
||||
, double clock_offset, double factor)
|
||||
{
|
||||
// Calculate number of steps to take
|
||||
int sdir = 1;
|
||||
if (steps < 0) {
|
||||
sdir = 0;
|
||||
steps = -steps;
|
||||
step_offset = -step_offset;
|
||||
}
|
||||
int count = steps + .5 - step_offset;
|
||||
if (count <= 0 || count > 1000000) {
|
||||
if (count && steps)
|
||||
errorf("push_factor invalid count %d %f %f %f %f"
|
||||
, sc->oid, steps, step_offset, clock_offset, factor);
|
||||
expand_queue(sc, 1);
|
||||
return 0;
|
||||
}
|
||||
check_expand(sc, sdir, count);
|
||||
|
||||
// Calculate each step time
|
||||
uint64_t *qn = sc->queue_next, *end = &qn[count];
|
||||
clock_offset += 0.5;
|
||||
double pos = step_offset + .5;
|
||||
while (qn < end) {
|
||||
*qn++ = clock_offset + pos*factor;
|
||||
pos += 1.0;
|
||||
}
|
||||
sc->queue_next = qn;
|
||||
return sdir ? count : -count;
|
||||
}
|
||||
|
||||
// Schedule 'steps' number of steps using the formula:
|
||||
// step_clock = clock_offset + sqrt(step_num*factor + sqrt_offset)
|
||||
int32_t
|
||||
stepcompress_push_sqrt(struct stepcompress *sc, double steps, double step_offset
|
||||
, double clock_offset, double sqrt_offset, double factor)
|
||||
static inline int
|
||||
check_push(struct stepcompress *sc, uint64_t **pqnext, uint64_t **pqend
|
||||
, uint64_t c)
|
||||
{
|
||||
// Calculate number of steps to take
|
||||
int sdir = 1;
|
||||
if (steps < 0) {
|
||||
sdir = 0;
|
||||
steps = -steps;
|
||||
step_offset = -step_offset;
|
||||
if (unlikely(*pqnext >= *pqend)) {
|
||||
sc->queue_next = *pqnext;
|
||||
int ret = _check_push(sc);
|
||||
if (ret)
|
||||
return ret;
|
||||
*pqnext = sc->queue_next;
|
||||
*pqend = sc->queue_end;
|
||||
}
|
||||
int count = steps + .5 - step_offset;
|
||||
if (count <= 0 || count > 1000000) {
|
||||
if (count && steps)
|
||||
errorf("push_sqrt invalid count %d %f %f %f %f %f"
|
||||
, sc->oid, steps, step_offset, clock_offset, sqrt_offset
|
||||
, factor);
|
||||
*(*pqnext)++ = c;
|
||||
return 0;
|
||||
}
|
||||
check_expand(sc, sdir, count);
|
||||
|
||||
// Calculate each step time
|
||||
uint64_t *qn = sc->queue_next, *end = &qn[count];
|
||||
clock_offset += 0.5;
|
||||
double pos = step_offset + .5 + sqrt_offset/factor;
|
||||
while (qn < end) {
|
||||
double v = safe_sqrt(pos*factor);
|
||||
*qn++ = clock_offset + (factor >= 0. ? v : -v);
|
||||
pos += 1.0;
|
||||
}
|
||||
sc->queue_next = qn;
|
||||
return sdir ? count : -count;
|
||||
}
|
||||
|
||||
// Schedule 'count' number of steps using the delta kinematic const speed
|
||||
int32_t
|
||||
stepcompress_push_delta_const(
|
||||
struct stepcompress *sc, double clock_offset, double dist, double start_pos
|
||||
, double inv_velocity, double step_dist
|
||||
, double height, double closestxy_d, double closest_height2, double movez_r)
|
||||
{
|
||||
// Calculate number of steps to take
|
||||
double movexy_r = movez_r ? sqrt(1. - movez_r*movez_r) : 1.;
|
||||
double reldist = closestxy_d - movexy_r*dist;
|
||||
double end_height = safe_sqrt(closest_height2 - reldist*reldist);
|
||||
int count = (end_height - height + movez_r*dist) / step_dist + .5;
|
||||
if (count <= 0 || count > 1000000) {
|
||||
if (count)
|
||||
errorf("push_delta_const invalid count %d %d %f %f %f %f %f %f %f %f"
|
||||
, sc->oid, count, clock_offset, dist, step_dist, start_pos
|
||||
, closest_height2, height, movez_r, inv_velocity);
|
||||
return 0;
|
||||
}
|
||||
check_expand(sc, step_dist > 0., count);
|
||||
|
||||
// Calculate each step time
|
||||
uint64_t *qn = sc->queue_next, *end = &qn[count];
|
||||
clock_offset += 0.5;
|
||||
start_pos += movexy_r*closestxy_d;
|
||||
height += .5 * step_dist;
|
||||
if (!movez_r) {
|
||||
// Optmized case for common XY only moves (no Z movement)
|
||||
while (qn < end) {
|
||||
double v = safe_sqrt(closest_height2 - height*height);
|
||||
double pos = start_pos + (step_dist > 0. ? -v : v);
|
||||
*qn++ = clock_offset + pos * inv_velocity;
|
||||
height += step_dist;
|
||||
}
|
||||
} else if (!movexy_r) {
|
||||
// Optmized case for Z only moves
|
||||
double v = (step_dist > 0. ? -end_height : end_height);
|
||||
while (qn < end) {
|
||||
double pos = start_pos + movez_r*height + v;
|
||||
*qn++ = clock_offset + pos * inv_velocity;
|
||||
height += step_dist;
|
||||
}
|
||||
} else {
|
||||
// General case (handles XY+Z moves)
|
||||
while (qn < end) {
|
||||
double relheight = movexy_r*height - movez_r*closestxy_d;
|
||||
double v = safe_sqrt(closest_height2 - relheight*relheight);
|
||||
double pos = start_pos + movez_r*height + (step_dist > 0. ? -v : v);
|
||||
*qn++ = clock_offset + pos * inv_velocity;
|
||||
height += step_dist;
|
||||
}
|
||||
}
|
||||
sc->queue_next = qn;
|
||||
return step_dist > 0. ? count : -count;
|
||||
}
|
||||
|
||||
// Schedule 'count' number of steps using delta kinematic acceleration
|
||||
int32_t
|
||||
stepcompress_push_delta_accel(
|
||||
struct stepcompress *sc, double clock_offset, double dist, double start_pos
|
||||
, double accel_multiplier, double step_dist
|
||||
, double height, double closestxy_d, double closest_height2, double movez_r)
|
||||
{
|
||||
// Calculate number of steps to take
|
||||
double movexy_r = movez_r ? sqrt(1. - movez_r*movez_r) : 1.;
|
||||
double reldist = closestxy_d - movexy_r*dist;
|
||||
double end_height = safe_sqrt(closest_height2 - reldist*reldist);
|
||||
int count = (end_height - height + movez_r*dist) / step_dist + .5;
|
||||
if (count <= 0 || count > 1000000) {
|
||||
if (count)
|
||||
errorf("push_delta_accel invalid count %d %d %f %f %f %f %f %f %f %f"
|
||||
, sc->oid, count, clock_offset, dist, step_dist, start_pos
|
||||
, closest_height2, height, movez_r, accel_multiplier);
|
||||
return 0;
|
||||
}
|
||||
check_expand(sc, step_dist > 0., count);
|
||||
|
||||
// Calculate each step time
|
||||
uint64_t *qn = sc->queue_next, *end = &qn[count];
|
||||
clock_offset += 0.5;
|
||||
start_pos += movexy_r*closestxy_d;
|
||||
height += .5 * step_dist;
|
||||
while (qn < end) {
|
||||
double relheight = movexy_r*height - movez_r*closestxy_d;
|
||||
double v = safe_sqrt(closest_height2 - relheight*relheight);
|
||||
double pos = start_pos + movez_r*height + (step_dist > 0. ? -v : v);
|
||||
v = safe_sqrt(pos * accel_multiplier);
|
||||
*qn++ = clock_offset + (accel_multiplier >= 0. ? v : -v);
|
||||
height += step_dist;
|
||||
}
|
||||
sc->queue_next = qn;
|
||||
return step_dist > 0. ? count : -count;
|
||||
}
|
||||
|
||||
// Reset the internal state of the stepcompress object
|
||||
void
|
||||
int
|
||||
stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock)
|
||||
{
|
||||
stepcompress_flush(sc, UINT64_MAX);
|
||||
int ret = stepcompress_flush(sc, UINT64_MAX);
|
||||
if (ret)
|
||||
return ret;
|
||||
sc->last_step_clock = last_step_clock;
|
||||
sc->sdir = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Indicate the stepper is in homing mode (or done homing if zero)
|
||||
int
|
||||
stepcompress_set_homing(struct stepcompress *sc, uint64_t homing_clock)
|
||||
{
|
||||
int ret = stepcompress_flush(sc, UINT64_MAX);
|
||||
if (ret)
|
||||
return ret;
|
||||
sc->homing_clock = homing_clock;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Queue an mcu command to go out in order with stepper commands
|
||||
void
|
||||
int
|
||||
stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len)
|
||||
{
|
||||
stepcompress_flush(sc, UINT64_MAX);
|
||||
int ret = stepcompress_flush(sc, UINT64_MAX);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
struct queue_message *qm = message_alloc_and_encode(data, len);
|
||||
qm->req_clock = sc->last_step_clock;
|
||||
qm->req_clock = sc->homing_clock ?: sc->last_step_clock;
|
||||
list_add_tail(&qm->node, &sc->msg_queue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Return the count of internal errors found
|
||||
uint32_t
|
||||
stepcompress_get_errors(struct stepcompress *sc)
|
||||
|
||||
/****************************************************************
|
||||
* Motion to step conversions
|
||||
****************************************************************/
|
||||
|
||||
// Wrapper around sqrt() to handle small negative numbers
|
||||
static double
|
||||
_safe_sqrt(double v)
|
||||
{
|
||||
return sc->errors;
|
||||
// Due to floating point truncation, it's possible to get a small
|
||||
// negative number - treat it as zero.
|
||||
if (v < -0.001)
|
||||
errorf("safe_sqrt of %.9f", v);
|
||||
return 0.;
|
||||
}
|
||||
static inline double safe_sqrt(double v) {
|
||||
return likely(v >= 0.) ? sqrt(v) : _safe_sqrt(v);
|
||||
}
|
||||
|
||||
// Schedule a step event at the specified step_clock time
|
||||
int
|
||||
stepcompress_push(struct stepcompress *sc, double step_clock, int32_t sdir)
|
||||
{
|
||||
int ret = set_next_step_dir(sc, !!sdir);
|
||||
if (ret)
|
||||
return ret;
|
||||
step_clock += 0.5;
|
||||
uint64_t *qnext = sc->queue_next, *qend = sc->queue_end;
|
||||
ret = check_push(sc, &qnext, &qend, step_clock);
|
||||
if (ret)
|
||||
return ret;
|
||||
sc->queue_next = qnext;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Common suffixes: _sd is step distance (a unit length the same
|
||||
// distance the stepper moves on each step), _sv is step velocity (in
|
||||
// units of step distance per clock tick), _sd2 is step distance
|
||||
// squared, _r is ratio (scalar usually between 0.0 and 1.0). Times
|
||||
// are represented as clock ticks (a unit of time determined by a
|
||||
// micro-controller tick) and acceleration is in units of step
|
||||
// distance per clock ticks squared.
|
||||
|
||||
// Schedule 'steps' number of steps at constant acceleration. If
|
||||
// acceleration is zero (ie, constant velocity) it uses the formula:
|
||||
// step_clock = clock_offset + step_num/start_sv
|
||||
// Otherwise it uses the formula:
|
||||
// step_clock = (clock_offset + sqrt(2*step_num/accel + (start_sv/accel)**2)
|
||||
// - start_sv/accel)
|
||||
int32_t
|
||||
stepcompress_push_const(
|
||||
struct stepcompress *sc, double clock_offset
|
||||
, double step_offset, double steps, double start_sv, double accel)
|
||||
{
|
||||
// Calculate number of steps to take
|
||||
int sdir = 1;
|
||||
if (steps < 0) {
|
||||
sdir = 0;
|
||||
steps = -steps;
|
||||
step_offset = -step_offset;
|
||||
}
|
||||
int count = steps + .5 - step_offset;
|
||||
if (count <= 0 || count > 10000000) {
|
||||
if (count && steps) {
|
||||
errorf("push_const invalid count %d %f %f %f %f %f"
|
||||
, sc->oid, clock_offset, step_offset, steps
|
||||
, start_sv, accel);
|
||||
return ERROR_RET;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int ret = set_next_step_dir(sc, sdir);
|
||||
if (ret)
|
||||
return ret;
|
||||
int res = sdir ? count : -count;
|
||||
|
||||
// Calculate each step time
|
||||
clock_offset += 0.5;
|
||||
double pos = step_offset + .5;
|
||||
uint64_t *qnext = sc->queue_next, *qend = sc->queue_end;
|
||||
if (!accel) {
|
||||
// Move at constant velocity (zero acceleration)
|
||||
double inv_cruise_sv = 1. / start_sv;
|
||||
while (count--) {
|
||||
uint64_t c = clock_offset + pos*inv_cruise_sv;
|
||||
int ret = check_push(sc, &qnext, &qend, c);
|
||||
if (ret)
|
||||
return ret;
|
||||
pos += 1.0;
|
||||
}
|
||||
} else {
|
||||
// Move with constant acceleration
|
||||
double inv_accel = 1. / accel;
|
||||
clock_offset -= start_sv * inv_accel;
|
||||
pos += .5 * start_sv*start_sv * inv_accel;
|
||||
double accel_multiplier = 2. * inv_accel;
|
||||
while (count--) {
|
||||
double v = safe_sqrt(pos * accel_multiplier);
|
||||
uint64_t c = clock_offset + (accel_multiplier >= 0. ? v : -v);
|
||||
int ret = check_push(sc, &qnext, &qend, c);
|
||||
if (ret)
|
||||
return ret;
|
||||
pos += 1.0;
|
||||
}
|
||||
}
|
||||
sc->queue_next = qnext;
|
||||
return res;
|
||||
}
|
||||
|
||||
// Schedule steps using delta kinematics
|
||||
static int32_t
|
||||
_stepcompress_push_delta(
|
||||
struct stepcompress *sc, int sdir
|
||||
, double clock_offset, double move_sd, double start_sv, double accel
|
||||
, double height, double startxy_sd, double arm_sd, double movez_r)
|
||||
{
|
||||
// Calculate number of steps to take
|
||||
double movexy_r = movez_r ? sqrt(1. - movez_r*movez_r) : 1.;
|
||||
double arm_sd2 = arm_sd * arm_sd;
|
||||
double endxy_sd = startxy_sd - movexy_r*move_sd;
|
||||
double end_height = safe_sqrt(arm_sd2 - endxy_sd*endxy_sd);
|
||||
int count = (end_height + movez_r*move_sd - height) * (sdir ? 1. : -1.) + .5;
|
||||
if (count <= 0 || count > 10000000) {
|
||||
if (count) {
|
||||
errorf("push_delta invalid count %d %d %f %f %f %f %f %f %f %f"
|
||||
, sc->oid, count, clock_offset, move_sd, start_sv, accel
|
||||
, height, startxy_sd, arm_sd, movez_r);
|
||||
return ERROR_RET;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int ret = set_next_step_dir(sc, sdir);
|
||||
if (ret)
|
||||
return ret;
|
||||
int res = sdir ? count : -count;
|
||||
|
||||
// Calculate each step time
|
||||
clock_offset += 0.5;
|
||||
height += (sdir ? .5 : -.5);
|
||||
uint64_t *qnext = sc->queue_next, *qend = sc->queue_end;
|
||||
if (!accel) {
|
||||
// Move at constant velocity (zero acceleration)
|
||||
double inv_cruise_sv = 1. / start_sv;
|
||||
if (!movez_r) {
|
||||
// Optimized case for common XY only moves (no Z movement)
|
||||
while (count--) {
|
||||
double v = safe_sqrt(arm_sd2 - height*height);
|
||||
double pos = startxy_sd + (sdir ? -v : v);
|
||||
uint64_t c = clock_offset + pos * inv_cruise_sv;
|
||||
int ret = check_push(sc, &qnext, &qend, c);
|
||||
if (ret)
|
||||
return ret;
|
||||
height += (sdir ? 1. : -1.);
|
||||
}
|
||||
} else if (!movexy_r) {
|
||||
// Optimized case for Z only moves
|
||||
double pos = (sdir ? height-end_height : end_height-height);
|
||||
while (count--) {
|
||||
uint64_t c = clock_offset + pos * inv_cruise_sv;
|
||||
int ret = check_push(sc, &qnext, &qend, c);
|
||||
if (ret)
|
||||
return ret;
|
||||
pos += 1.;
|
||||
}
|
||||
} else {
|
||||
// General case (handles XY+Z moves)
|
||||
double start_pos = movexy_r*startxy_sd, zoffset = movez_r*startxy_sd;
|
||||
while (count--) {
|
||||
double relheight = movexy_r*height - zoffset;
|
||||
double v = safe_sqrt(arm_sd2 - relheight*relheight);
|
||||
double pos = start_pos + movez_r*height + (sdir ? -v : v);
|
||||
uint64_t c = clock_offset + pos * inv_cruise_sv;
|
||||
int ret = check_push(sc, &qnext, &qend, c);
|
||||
if (ret)
|
||||
return ret;
|
||||
height += (sdir ? 1. : -1.);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Move with constant acceleration
|
||||
double start_pos = movexy_r*startxy_sd, zoffset = movez_r*startxy_sd;
|
||||
double inv_accel = 1. / accel;
|
||||
clock_offset -= start_sv * inv_accel;
|
||||
start_pos += 0.5 * start_sv*start_sv * inv_accel;
|
||||
double accel_multiplier = 2. * inv_accel;
|
||||
while (count--) {
|
||||
double relheight = movexy_r*height - zoffset;
|
||||
double v = safe_sqrt(arm_sd2 - relheight*relheight);
|
||||
double pos = start_pos + movez_r*height + (sdir ? -v : v);
|
||||
v = safe_sqrt(pos * accel_multiplier);
|
||||
uint64_t c = clock_offset + (accel_multiplier >= 0. ? v : -v);
|
||||
int ret = check_push(sc, &qnext, &qend, c);
|
||||
if (ret)
|
||||
return ret;
|
||||
height += (sdir ? 1. : -1.);
|
||||
}
|
||||
}
|
||||
sc->queue_next = qnext;
|
||||
return res;
|
||||
}
|
||||
|
||||
int32_t
|
||||
stepcompress_push_delta(
|
||||
struct stepcompress *sc, double clock_offset, double move_sd
|
||||
, double start_sv, double accel
|
||||
, double height, double startxy_sd, double arm_sd, double movez_r)
|
||||
{
|
||||
double reversexy_sd = startxy_sd + arm_sd*movez_r;
|
||||
if (reversexy_sd <= 0.)
|
||||
// All steps are in down direction
|
||||
return _stepcompress_push_delta(
|
||||
sc, 0, clock_offset, move_sd, start_sv, accel
|
||||
, height, startxy_sd, arm_sd, movez_r);
|
||||
double movexy_r = movez_r ? sqrt(1. - movez_r*movez_r) : 1.;
|
||||
if (reversexy_sd >= move_sd * movexy_r)
|
||||
// All steps are in up direction
|
||||
return _stepcompress_push_delta(
|
||||
sc, 1, clock_offset, move_sd, start_sv, accel
|
||||
, height, startxy_sd, arm_sd, movez_r);
|
||||
// Steps in both up and down direction
|
||||
int res1 = _stepcompress_push_delta(
|
||||
sc, 1, clock_offset, reversexy_sd / movexy_r, start_sv, accel
|
||||
, height, startxy_sd, arm_sd, movez_r);
|
||||
if (res1 == ERROR_RET)
|
||||
return res1;
|
||||
int res2 = _stepcompress_push_delta(
|
||||
sc, 0, clock_offset, move_sd, start_sv, accel
|
||||
, height + res1, startxy_sd, arm_sd, movez_r);
|
||||
if (res2 == ERROR_RET)
|
||||
return res2;
|
||||
return res1 + res2;
|
||||
}
|
||||
|
||||
|
||||
@@ -655,13 +744,16 @@ heap_replace(struct steppersync *ss, uint64_t req_clock)
|
||||
}
|
||||
|
||||
// Find and transmit any scheduled steps prior to the given 'move_clock'
|
||||
void
|
||||
int
|
||||
steppersync_flush(struct steppersync *ss, uint64_t move_clock)
|
||||
{
|
||||
// Flush each stepcompress to the specified move_clock
|
||||
int i;
|
||||
for (i=0; i<ss->sc_num; i++)
|
||||
stepcompress_flush(ss->sc_list[i], move_clock);
|
||||
for (i=0; i<ss->sc_num; i++) {
|
||||
int ret = stepcompress_flush(ss->sc_list[i], move_clock);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Order commands by the reqclock of each pending command
|
||||
struct list_head msgs;
|
||||
@@ -701,4 +793,5 @@ steppersync_flush(struct steppersync *ss, uint64_t move_clock)
|
||||
// Transmit commands
|
||||
if (!list_empty(&msgs))
|
||||
serialqueue_send_batch(ss->sq, ss->cq, &msgs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -8,24 +8,26 @@ import homing
|
||||
|
||||
class PrinterStepper:
|
||||
def __init__(self, printer, config, name):
|
||||
self.printer = printer
|
||||
self.config = config
|
||||
self.name = name
|
||||
self.mcu_stepper = self.mcu_enable = self.mcu_endstop = None
|
||||
|
||||
self.step_dist = config.getfloat('step_distance')
|
||||
self.step_dist = config.getfloat('step_distance', above=0.)
|
||||
self.inv_step_dist = 1. / self.step_dist
|
||||
self.min_stop_interval = 0.
|
||||
|
||||
self.homing_speed = config.getfloat('homing_speed', 5.0)
|
||||
self.homing_speed = config.getfloat('homing_speed', 5.0, above=0.)
|
||||
self.homing_positive_dir = config.getboolean(
|
||||
'homing_positive_dir', False)
|
||||
self.homing_retract_dist = config.getfloat('homing_retract_dist', 5.)
|
||||
self.homing_stepper_phases = config.getint('homing_stepper_phases', None)
|
||||
self.homing_endstop_phase = config.getint('homing_endstop_phase', None)
|
||||
endstop_accuracy = config.getfloat('homing_endstop_accuracy', None)
|
||||
self.homing_endstop_accuracy = None
|
||||
self.homing_retract_dist = config.getfloat(
|
||||
'homing_retract_dist', 5., above=0.)
|
||||
self.homing_stepper_phases = config.getint(
|
||||
'homing_stepper_phases', None, minval=0)
|
||||
endstop_accuracy = config.getfloat(
|
||||
'homing_endstop_accuracy', None, above=0.)
|
||||
self.homing_endstop_accuracy = self.homing_endstop_phase = None
|
||||
if self.homing_stepper_phases:
|
||||
self.homing_endstop_phase = config.getint(
|
||||
'homing_endstop_phase', None, minval=0
|
||||
, maxval=self.homing_stepper_phases-1)
|
||||
if endstop_accuracy is None:
|
||||
self.homing_endstop_accuracy = self.homing_stepper_phases//2 - 1
|
||||
elif self.homing_endstop_phase is not None:
|
||||
@@ -36,34 +38,40 @@ class PrinterStepper:
|
||||
endstop_accuracy * self.inv_step_dist))
|
||||
if self.homing_endstop_accuracy >= self.homing_stepper_phases/2:
|
||||
logging.info("Endstop for %s is not accurate enough for stepper"
|
||||
" phase adjustment" % (self.config.section,))
|
||||
" phase adjustment" % (name,))
|
||||
self.homing_stepper_phases = None
|
||||
if printer.mcu.is_fileoutput():
|
||||
self.homing_endstop_accuracy = self.homing_stepper_phases
|
||||
self.position_min = self.position_endstop = self.position_max = None
|
||||
if config.get('endstop_pin', None) is not None:
|
||||
self.position_min = config.getfloat('position_min', 0.)
|
||||
self.position_endstop = config.getfloat('position_endstop')
|
||||
self.position_max = config.getfloat('position_max', 0.)
|
||||
|
||||
self.need_motor_enable = True
|
||||
def set_max_jerk(self, max_halt_velocity, max_accel):
|
||||
jc = max_halt_velocity / max_accel
|
||||
inv_max_step_accel = self.step_dist / max_accel
|
||||
self.min_stop_interval = (math.sqrt(3.*inv_max_step_accel + jc**2)
|
||||
- math.sqrt(inv_max_step_accel + jc**2))
|
||||
def build_config(self):
|
||||
max_error = self.config.getfloat('max_error', 0.000050)
|
||||
step_pin = self.config.get('step_pin')
|
||||
dir_pin = self.config.get('dir_pin')
|
||||
min_stop_interval = max(0., self.min_stop_interval - max_error)
|
||||
mcu = self.printer.mcu
|
||||
self.mcu_stepper = mcu.create_stepper(
|
||||
step_pin, dir_pin, min_stop_interval, max_error)
|
||||
enable_pin = self.config.get('enable_pin', None)
|
||||
endstop_pin = config.get('endstop_pin', None)
|
||||
step_pin = config.get('step_pin')
|
||||
dir_pin = config.get('dir_pin')
|
||||
mcu = printer.mcu
|
||||
self.mcu_stepper = mcu.create_stepper(step_pin, dir_pin)
|
||||
self.mcu_stepper.set_step_distance(self.step_dist)
|
||||
enable_pin = config.get('enable_pin', None)
|
||||
if enable_pin is not None:
|
||||
self.mcu_enable = mcu.create_digital_out(enable_pin, 0)
|
||||
endstop_pin = self.config.get('endstop_pin', None)
|
||||
if endstop_pin is not None:
|
||||
self.mcu_endstop = mcu.create_endstop(endstop_pin, self.mcu_stepper)
|
||||
self.mcu_endstop = mcu.create_endstop(endstop_pin)
|
||||
self.mcu_endstop.add_stepper(self.mcu_stepper)
|
||||
self.position_min = config.getfloat('position_min', 0.)
|
||||
self.position_max = config.getfloat(
|
||||
'position_max', 0., above=self.position_min)
|
||||
self.position_endstop = config.getfloat('position_endstop')
|
||||
self.need_motor_enable = True
|
||||
def _dist_to_time(self, dist, start_velocity, accel):
|
||||
# Calculate the time it takes to travel a distance with constant accel
|
||||
time_offset = start_velocity / accel
|
||||
return math.sqrt(2. * dist / accel + time_offset**2) - time_offset
|
||||
def set_max_jerk(self, max_halt_velocity, max_accel):
|
||||
# Calculate the firmware's maximum halt interval time
|
||||
last_step_time = self._dist_to_time(
|
||||
self.step_dist, max_halt_velocity, max_accel)
|
||||
second_last_step_time = self._dist_to_time(
|
||||
2. * self.step_dist, max_halt_velocity, max_accel)
|
||||
min_stop_interval = second_last_step_time - last_step_time
|
||||
self.mcu_stepper.set_min_stop_interval(min_stop_interval)
|
||||
def motor_enable(self, move_time, enable=0):
|
||||
if enable and self.need_motor_enable:
|
||||
mcu_time = self.mcu_stepper.print_to_mcu_time(move_time)
|
||||
@@ -87,8 +95,7 @@ class PrinterStepper:
|
||||
pos = self.mcu_stepper.get_mcu_position()
|
||||
pos %= self.homing_stepper_phases
|
||||
if self.homing_endstop_phase is None:
|
||||
logging.info("Setting %s endstop phase to %d" % (
|
||||
self.config.section, pos))
|
||||
logging.info("Setting %s endstop phase to %d" % (self.name, pos))
|
||||
self.homing_endstop_phase = pos
|
||||
return 0
|
||||
delta = (pos - self.homing_endstop_phase) % self.homing_stepper_phases
|
||||
@@ -98,4 +105,4 @@ class PrinterStepper:
|
||||
raise homing.EndstopError(
|
||||
"Endstop %s incorrect phase (got %d vs %d)" % (
|
||||
self.name, pos, self.homing_endstop_phase))
|
||||
return delta
|
||||
return delta * self.step_dist
|
||||
|
||||
@@ -3,148 +3,171 @@
|
||||
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import math, logging, time
|
||||
import cartesian, delta
|
||||
|
||||
EXTRUDE_DIFF_IGNORE = 1.02
|
||||
import math, logging
|
||||
import cartesian, corexy, delta, extruder
|
||||
|
||||
# Common suffixes: _d is distance (in mm), _v is velocity (in
|
||||
# mm/second), _t is time (in seconds), _r is ratio (scalar between
|
||||
# 0.0 and 1.0)
|
||||
# mm/second), _v2 is velocity squared (mm^2/s^2), _t is time (in
|
||||
# seconds), _r is ratio (scalar between 0.0 and 1.0)
|
||||
|
||||
# Class to track each move request
|
||||
class Move:
|
||||
def __init__(self, toolhead, start_pos, end_pos, speed, accel):
|
||||
def __init__(self, toolhead, start_pos, end_pos, speed):
|
||||
self.toolhead = toolhead
|
||||
self.start_pos = tuple(start_pos)
|
||||
self.end_pos = tuple(end_pos)
|
||||
self.accel = accel
|
||||
self.do_calc_junction = True
|
||||
self.accel = toolhead.max_accel
|
||||
self.is_kinematic_move = True
|
||||
self.axes_d = axes_d = [end_pos[i] - start_pos[i] for i in (0, 1, 2, 3)]
|
||||
if axes_d[2]:
|
||||
# Move with Z
|
||||
move_d = math.sqrt(sum([d*d for d in axes_d[:3]]))
|
||||
self.do_calc_junction = False
|
||||
else:
|
||||
move_d = math.sqrt(axes_d[0]**2 + axes_d[1]**2)
|
||||
self.move_d = move_d = math.sqrt(sum([d*d for d in axes_d[:3]]))
|
||||
if not move_d:
|
||||
# Extrude only move
|
||||
move_d = abs(axes_d[3])
|
||||
if not move_d:
|
||||
# No move
|
||||
self.move_d = 0.
|
||||
return
|
||||
self.do_calc_junction = False
|
||||
self.move_d = move_d
|
||||
self.extrude_r = axes_d[3] / move_d
|
||||
# Junction speeds are velocities squared. The junction_delta
|
||||
# is the maximum amount of this squared-velocity that can
|
||||
# change in this move.
|
||||
self.junction_max = speed**2
|
||||
self.junction_delta = 2.0 * move_d * accel
|
||||
self.junction_start_max = 0.
|
||||
self.move_d = move_d = abs(axes_d[3])
|
||||
self.is_kinematic_move = False
|
||||
self.min_move_t = move_d / speed
|
||||
# Junction speeds are tracked in velocity squared. The
|
||||
# delta_v2 is the maximum amount of this squared-velocity that
|
||||
# can change in this move.
|
||||
self.max_start_v2 = 0.
|
||||
self.max_cruise_v2 = speed**2
|
||||
self.delta_v2 = 2.0 * move_d * self.accel
|
||||
self.max_smoothed_v2 = 0.
|
||||
self.smooth_delta_v2 = 2.0 * move_d * toolhead.max_accel_to_decel
|
||||
def limit_speed(self, speed, accel):
|
||||
self.junction_max = min(self.junction_max, speed**2)
|
||||
speed2 = speed**2
|
||||
if speed2 < self.max_cruise_v2:
|
||||
self.max_cruise_v2 = speed2
|
||||
self.min_move_t = self.move_d / speed
|
||||
self.accel = min(self.accel, accel)
|
||||
self.junction_delta = 2.0 * self.move_d * self.accel
|
||||
self.delta_v2 = 2.0 * self.move_d * self.accel
|
||||
self.smooth_delta_v2 = min(self.smooth_delta_v2, self.delta_v2)
|
||||
def calc_junction(self, prev_move):
|
||||
if not self.do_calc_junction or not prev_move.do_calc_junction:
|
||||
axes_d = self.axes_d
|
||||
prev_axes_d = prev_move.axes_d
|
||||
if (axes_d[2] or prev_axes_d[2] or self.accel != prev_move.accel
|
||||
or not self.is_kinematic_move or not prev_move.is_kinematic_move):
|
||||
return
|
||||
# Find max junction_start_velocity between two moves
|
||||
if (self.extrude_r > prev_move.extrude_r * EXTRUDE_DIFF_IGNORE
|
||||
or prev_move.extrude_r > self.extrude_r * EXTRUDE_DIFF_IGNORE):
|
||||
# Extrude ratio between moves is too different
|
||||
return
|
||||
self.extrude_r = prev_move.extrude_r
|
||||
# Allow extruder to calculate its maximum junction
|
||||
extruder_v2 = self.toolhead.extruder.calc_junction(prev_move, self)
|
||||
# Find max velocity using approximated centripetal velocity as
|
||||
# described at:
|
||||
# https://onehossshay.wordpress.com/2011/09/24/improving_grbl_cornering_algorithm/
|
||||
junction_cos_theta = -((self.axes_d[0] * prev_move.axes_d[0]
|
||||
+ self.axes_d[1] * prev_move.axes_d[1])
|
||||
junction_cos_theta = -((axes_d[0] * prev_axes_d[0]
|
||||
+ axes_d[1] * prev_axes_d[1])
|
||||
/ (self.move_d * prev_move.move_d))
|
||||
if junction_cos_theta > 0.999999:
|
||||
return
|
||||
junction_cos_theta = max(junction_cos_theta, -0.999999)
|
||||
sin_theta_d2 = math.sqrt(0.5*(1.0-junction_cos_theta))
|
||||
R = self.toolhead.junction_deviation * sin_theta_d2 / (1. - sin_theta_d2)
|
||||
self.junction_start_max = min(
|
||||
R * self.accel, self.junction_max, prev_move.junction_max
|
||||
, prev_move.junction_start_max + prev_move.junction_delta)
|
||||
def process(self, junction_start, junction_cruise, junction_end
|
||||
, cornering_min, cornering_max):
|
||||
self.max_start_v2 = min(
|
||||
R * self.accel, self.max_cruise_v2, prev_move.max_cruise_v2
|
||||
, extruder_v2, prev_move.max_start_v2 + prev_move.delta_v2)
|
||||
self.max_smoothed_v2 = min(
|
||||
self.max_start_v2
|
||||
, prev_move.max_smoothed_v2 + prev_move.smooth_delta_v2)
|
||||
def set_junction(self, start_v2, cruise_v2, end_v2):
|
||||
# Determine accel, cruise, and decel portions of the move distance
|
||||
inv_junction_delta = 1. / self.junction_delta
|
||||
accel_r = (junction_cruise-junction_start) * inv_junction_delta
|
||||
decel_r = (junction_cruise-junction_end) * inv_junction_delta
|
||||
cruise_r = 1. - accel_r - decel_r
|
||||
self.accel_r, self.cruise_r, self.decel_r = accel_r, cruise_r, decel_r
|
||||
inv_delta_v2 = 1. / self.delta_v2
|
||||
self.accel_r = accel_r = (cruise_v2 - start_v2) * inv_delta_v2
|
||||
self.decel_r = decel_r = (cruise_v2 - end_v2) * inv_delta_v2
|
||||
self.cruise_r = cruise_r = 1. - accel_r - decel_r
|
||||
# Determine move velocities
|
||||
start_v = math.sqrt(junction_start)
|
||||
cruise_v = math.sqrt(junction_cruise)
|
||||
end_v = math.sqrt(junction_end)
|
||||
self.start_v, self.cruise_v, self.end_v = start_v, cruise_v, end_v
|
||||
self.corner_min = math.sqrt(cornering_min)
|
||||
self.corner_max = math.sqrt(cornering_max)
|
||||
self.start_v = start_v = math.sqrt(start_v2)
|
||||
self.cruise_v = cruise_v = math.sqrt(cruise_v2)
|
||||
self.end_v = end_v = math.sqrt(end_v2)
|
||||
# Determine time spent in each portion of move (time is the
|
||||
# distance divided by average velocity)
|
||||
accel_t = accel_r * self.move_d / ((start_v + cruise_v) * 0.5)
|
||||
cruise_t = cruise_r * self.move_d / cruise_v
|
||||
decel_t = decel_r * self.move_d / ((end_v + cruise_v) * 0.5)
|
||||
self.accel_t, self.cruise_t, self.decel_t = accel_t, cruise_t, decel_t
|
||||
self.accel_t = accel_r * self.move_d / ((start_v + cruise_v) * 0.5)
|
||||
self.cruise_t = cruise_r * self.move_d / cruise_v
|
||||
self.decel_t = decel_r * self.move_d / ((end_v + cruise_v) * 0.5)
|
||||
def move(self):
|
||||
# Generate step times for the move
|
||||
next_move_time = self.toolhead.get_next_move_time()
|
||||
if self.is_kinematic_move:
|
||||
self.toolhead.kin.move(next_move_time, self)
|
||||
if self.axes_d[3]:
|
||||
self.toolhead.extruder.move(next_move_time, self)
|
||||
self.toolhead.update_move_time(accel_t + cruise_t + decel_t)
|
||||
self.toolhead.update_move_time(
|
||||
self.accel_t + self.cruise_t + self.decel_t)
|
||||
|
||||
LOOKAHEAD_FLUSH_TIME = 0.250
|
||||
|
||||
# Class to track a list of pending move requests and to facilitate
|
||||
# "look-ahead" across moves to reduce acceleration between moves.
|
||||
class MoveQueue:
|
||||
def __init__(self):
|
||||
def __init__(self, extruder_lookahead):
|
||||
self.extruder_lookahead = extruder_lookahead
|
||||
self.queue = []
|
||||
self.junction_flush = 0.
|
||||
self.leftover = 0
|
||||
self.junction_flush = LOOKAHEAD_FLUSH_TIME
|
||||
def reset(self):
|
||||
del self.queue[:]
|
||||
self.leftover = 0
|
||||
self.junction_flush = LOOKAHEAD_FLUSH_TIME
|
||||
def set_flush_time(self, flush_time):
|
||||
self.junction_flush = flush_time
|
||||
def flush(self, lazy=False):
|
||||
flush_count = len(self.queue)
|
||||
move_info = [None] * flush_count
|
||||
self.junction_flush = LOOKAHEAD_FLUSH_TIME
|
||||
update_flush_count = lazy
|
||||
queue = self.queue
|
||||
flush_count = len(queue)
|
||||
# Traverse queue from last to first move and determine maximum
|
||||
# junction speed assuming the robot comes to a complete stop
|
||||
# after the last move.
|
||||
next_junction_end = cornering_min = cornering_max = 0.
|
||||
for i in range(flush_count-1, -1, -1):
|
||||
move = self.queue[i]
|
||||
reachable_start = next_junction_end + move.junction_delta
|
||||
junction_start = min(move.junction_start_max, reachable_start)
|
||||
junction_cruise = min((junction_start + reachable_start) * .5
|
||||
, move.junction_max)
|
||||
move_info[i] = (junction_start, junction_cruise, next_junction_end
|
||||
, cornering_min, cornering_max)
|
||||
if reachable_start > junction_start:
|
||||
cornering_min = junction_start
|
||||
if junction_start + move.junction_delta > next_junction_end:
|
||||
cornering_max = junction_cruise
|
||||
if lazy:
|
||||
delayed = []
|
||||
next_end_v2 = next_smoothed_v2 = peak_cruise_v2 = 0.
|
||||
for i in range(flush_count-1, self.leftover-1, -1):
|
||||
move = queue[i]
|
||||
reachable_start_v2 = next_end_v2 + move.delta_v2
|
||||
start_v2 = min(move.max_start_v2, reachable_start_v2)
|
||||
reachable_smoothed_v2 = next_smoothed_v2 + move.smooth_delta_v2
|
||||
smoothed_v2 = min(move.max_smoothed_v2, reachable_smoothed_v2)
|
||||
if smoothed_v2 < reachable_smoothed_v2:
|
||||
# It's possible for this move to accelerate
|
||||
if (smoothed_v2 + move.smooth_delta_v2 > next_smoothed_v2
|
||||
or delayed):
|
||||
# This move can decelerate or this is a full accel
|
||||
# move after a full decel move
|
||||
if update_flush_count and peak_cruise_v2:
|
||||
flush_count = i
|
||||
lazy = False
|
||||
next_junction_end = junction_start
|
||||
if lazy:
|
||||
flush_count = 0
|
||||
update_flush_count = False
|
||||
peak_cruise_v2 = min(move.max_cruise_v2, (
|
||||
smoothed_v2 + reachable_smoothed_v2) * .5)
|
||||
if delayed:
|
||||
# Propagate peak_cruise_v2 to any delayed moves
|
||||
if not update_flush_count and i < flush_count:
|
||||
for m, ms_v2, me_v2 in delayed:
|
||||
mc_v2 = min(peak_cruise_v2, ms_v2)
|
||||
m.set_junction(min(ms_v2, mc_v2), mc_v2
|
||||
, min(me_v2, mc_v2))
|
||||
del delayed[:]
|
||||
if not update_flush_count and i < flush_count:
|
||||
cruise_v2 = min((start_v2 + reachable_start_v2) * .5
|
||||
, move.max_cruise_v2, peak_cruise_v2)
|
||||
move.set_junction(min(start_v2, cruise_v2), cruise_v2
|
||||
, min(next_end_v2, cruise_v2))
|
||||
else:
|
||||
# Delay calculating this move until peak_cruise_v2 is known
|
||||
delayed.append((move, start_v2, next_end_v2))
|
||||
next_end_v2 = start_v2
|
||||
next_smoothed_v2 = smoothed_v2
|
||||
if update_flush_count:
|
||||
return
|
||||
# Allow extruder to do its lookahead
|
||||
move_count = self.extruder_lookahead(queue, flush_count, lazy)
|
||||
# Generate step times for all moves ready to be flushed
|
||||
for i in range(flush_count):
|
||||
self.queue[i].process(*move_info[i])
|
||||
for move in queue[:move_count]:
|
||||
move.move()
|
||||
# Remove processed moves from the queue
|
||||
del self.queue[:flush_count]
|
||||
if self.queue:
|
||||
self.junction_flush = 2. * self.queue[-1].junction_max
|
||||
self.leftover = flush_count - move_count
|
||||
del queue[:move_count]
|
||||
def add_move(self, move):
|
||||
self.queue.append(move)
|
||||
if len(self.queue) == 1:
|
||||
self.junction_flush = 2. * move.junction_max
|
||||
return
|
||||
move.calc_junction(self.queue[-2])
|
||||
self.junction_flush -= move.junction_delta
|
||||
self.junction_flush -= move.min_move_t
|
||||
if self.junction_flush <= 0.:
|
||||
# There are enough queued moves to return to zero velocity
|
||||
# from the first move's maximum possible velocity, so at
|
||||
@@ -159,58 +182,94 @@ class ToolHead:
|
||||
self.printer = printer
|
||||
self.reactor = printer.reactor
|
||||
self.extruder = printer.objects.get('extruder')
|
||||
if self.extruder is None:
|
||||
self.extruder = extruder.DummyExtruder()
|
||||
kintypes = {'cartesian': cartesian.CartKinematics,
|
||||
'corexy': corexy.CoreXYKinematics,
|
||||
'delta': delta.DeltaKinematics}
|
||||
self.kin = config.getchoice('kinematics', kintypes)(printer, config)
|
||||
self.max_speed = config.getfloat('max_velocity')
|
||||
self.max_accel = config.getfloat('max_accel')
|
||||
self.junction_deviation = config.getfloat('junction_deviation', 0.02)
|
||||
self.move_queue = MoveQueue()
|
||||
self.max_speed = config.getfloat('max_velocity', above=0.)
|
||||
self.max_accel = config.getfloat('max_accel', above=0.)
|
||||
self.max_accel_to_decel = config.getfloat(
|
||||
'max_accel_to_decel', self.max_accel * 0.5
|
||||
, above=0., maxval=self.max_accel)
|
||||
self.junction_deviation = config.getfloat(
|
||||
'junction_deviation', 0.02, above=0.)
|
||||
self.move_queue = MoveQueue(self.extruder.lookahead)
|
||||
self.commanded_pos = [0., 0., 0., 0.]
|
||||
# Print time tracking
|
||||
self.buffer_time_high = config.getfloat('buffer_time_high', 5.000)
|
||||
self.buffer_time_low = config.getfloat('buffer_time_low', 0.150)
|
||||
self.move_flush_time = config.getfloat('move_flush_time', 0.050)
|
||||
self.motor_off_delay = config.getfloat('motor_off_time', 60.000)
|
||||
self.buffer_time_low = config.getfloat(
|
||||
'buffer_time_low', 1.000, above=0.)
|
||||
self.buffer_time_high = config.getfloat(
|
||||
'buffer_time_high', 2.000, above=self.buffer_time_low)
|
||||
self.buffer_time_start = config.getfloat(
|
||||
'buffer_time_start', 0.250, above=0.)
|
||||
self.move_flush_time = config.getfloat(
|
||||
'move_flush_time', 0.050, above=0.)
|
||||
self.print_time = 0.
|
||||
self.last_print_end_time = self.reactor.monotonic()
|
||||
self.need_check_stall = -1.
|
||||
self.print_time_stall = 0
|
||||
self.motor_off_time = self.reactor.NEVER
|
||||
self.print_stall = 0
|
||||
self.synch_print_time = True
|
||||
self.forced_synch = False
|
||||
self.flush_timer = self.reactor.register_timer(self._flush_handler)
|
||||
def build_config(self):
|
||||
self.kin.set_max_jerk(0.005 * self.max_accel, self.max_accel) # XXX
|
||||
self.kin.build_config()
|
||||
self.move_queue.set_flush_time(self.buffer_time_high)
|
||||
# Motor off tracking
|
||||
self.motor_off_time = config.getfloat(
|
||||
'motor_off_time', 600.000, minval=0.)
|
||||
self.motor_off_timer = self.reactor.register_timer(
|
||||
self._motor_off_handler)
|
||||
# Determine the maximum velocity a cartesian axis could have
|
||||
# before cornering. The 8. was determined experimentally.
|
||||
xy_halt = math.sqrt(8. * self.junction_deviation * self.max_accel)
|
||||
self.kin.set_max_jerk(xy_halt, self.max_speed, self.max_accel)
|
||||
self.extruder.set_max_jerk(xy_halt, self.max_speed, self.max_accel)
|
||||
# Print time tracking
|
||||
def update_move_time(self, movetime):
|
||||
self.print_time += movetime
|
||||
flush_to_time = self.print_time - self.move_flush_time
|
||||
self.printer.mcu.flush_moves(flush_to_time)
|
||||
def get_next_move_time(self):
|
||||
if not self.print_time:
|
||||
self.print_time = self.buffer_time_low + STALL_TIME
|
||||
curtime = time.time()
|
||||
if self.synch_print_time:
|
||||
curtime = self.reactor.monotonic()
|
||||
if self.print_time:
|
||||
buffer_time = self.printer.mcu.get_print_buffer_time(
|
||||
curtime, self.print_time)
|
||||
self.print_time += max(self.buffer_time_start - buffer_time, 0.)
|
||||
if self.forced_synch:
|
||||
self.print_stall += 1
|
||||
self.forced_synch = False
|
||||
else:
|
||||
self.printer.mcu.set_print_start_time(curtime)
|
||||
self.print_time = self.buffer_time_start
|
||||
self._reset_motor_off()
|
||||
self.reactor.update_timer(self.flush_timer, self.reactor.NOW)
|
||||
self.synch_print_time = False
|
||||
return self.print_time
|
||||
def get_last_move_time(self):
|
||||
self.move_queue.flush()
|
||||
return self.get_next_move_time()
|
||||
def reset_motor_off_time(self, eventtime):
|
||||
self.motor_off_time = eventtime + self.motor_off_delay
|
||||
def reset_print_time(self):
|
||||
def _flush_lookahead(self, must_synch=False):
|
||||
synch_print_time = self.synch_print_time
|
||||
self.move_queue.flush()
|
||||
if synch_print_time or must_synch:
|
||||
self.synch_print_time = True
|
||||
self.move_queue.set_flush_time(self.buffer_time_high)
|
||||
self.printer.mcu.flush_moves(self.print_time)
|
||||
def get_last_move_time(self):
|
||||
self._flush_lookahead()
|
||||
return self.get_next_move_time()
|
||||
def reset_print_time(self):
|
||||
self._flush_lookahead(must_synch=True)
|
||||
self.print_time = 0.
|
||||
self.last_print_end_time = self.reactor.monotonic()
|
||||
self.need_check_stall = -1.
|
||||
self.reset_motor_off_time(time.time())
|
||||
self.reactor.update_timer(self.flush_timer, self.motor_off_time)
|
||||
self.forced_synch = False
|
||||
self._reset_motor_off()
|
||||
def _check_stall(self):
|
||||
eventtime = self.reactor.monotonic()
|
||||
if not self.print_time:
|
||||
# XXX - find better way to flush initial move_queue items
|
||||
if self.move_queue.queue:
|
||||
self.reactor.update_timer(self.flush_timer, time.time() + 0.100)
|
||||
# Building initial queue - make sure to flush on idle input
|
||||
self.reactor.update_timer(self.flush_timer, eventtime + 0.100)
|
||||
return
|
||||
eventtime = time.time()
|
||||
# Check if there are lots of queued moves and stall if so
|
||||
while 1:
|
||||
buffer_time = self.printer.mcu.get_print_buffer_time(
|
||||
eventtime, self.print_time)
|
||||
@@ -224,47 +283,58 @@ class ToolHead:
|
||||
def _flush_handler(self, eventtime):
|
||||
try:
|
||||
if not self.print_time:
|
||||
self.move_queue.flush()
|
||||
# Input idled before filling lookahead queue - flush it
|
||||
self._flush_lookahead()
|
||||
if not self.print_time:
|
||||
if eventtime >= self.motor_off_time:
|
||||
self.motor_off()
|
||||
self.reset_print_time()
|
||||
self.motor_off_time = self.reactor.NEVER
|
||||
return self.motor_off_time
|
||||
return self.reactor.NEVER
|
||||
print_time = self.print_time
|
||||
buffer_time = self.printer.mcu.get_print_buffer_time(
|
||||
eventtime, print_time)
|
||||
if buffer_time > self.buffer_time_low:
|
||||
# Running normally - reschedule check
|
||||
return eventtime + buffer_time - self.buffer_time_low
|
||||
self.move_queue.flush()
|
||||
# Under ran low buffer mark - flush lookahead queue
|
||||
self._flush_lookahead(must_synch=True)
|
||||
if print_time != self.print_time:
|
||||
self.print_time_stall += 1
|
||||
self.dwell(self.buffer_time_low + STALL_TIME)
|
||||
# Flushed something - retry
|
||||
self.forced_synch = True
|
||||
return self.reactor.NOW
|
||||
if buffer_time > 0.:
|
||||
# Wait for buffer to fully empty
|
||||
return eventtime + buffer_time
|
||||
self.reset_print_time()
|
||||
return self.motor_off_time
|
||||
except:
|
||||
logging.exception("Exception in flush_handler")
|
||||
self.force_shutdown()
|
||||
def stats(self, eventtime):
|
||||
buffer_time = 0.
|
||||
if self.print_time:
|
||||
buffer_time = self.printer.mcu.get_print_buffer_time(
|
||||
eventtime, self.print_time)
|
||||
return "print_time=%.3f buffer_time=%.3f print_time_stall=%d" % (
|
||||
self.print_time, buffer_time, self.print_time_stall)
|
||||
return self.reactor.NEVER
|
||||
# Motor off timer
|
||||
def _reset_motor_off(self):
|
||||
if not self.print_time:
|
||||
waketime = self.reactor.monotonic() + self.motor_off_time
|
||||
else:
|
||||
waketime = self.reactor.NEVER
|
||||
self.reactor.update_timer(self.motor_off_timer, waketime)
|
||||
def _motor_off_handler(self, eventtime):
|
||||
try:
|
||||
self.motor_off()
|
||||
self.reset_print_time()
|
||||
except:
|
||||
logging.exception("Exception in motor_off_handler")
|
||||
self.force_shutdown()
|
||||
return self.reactor.NEVER
|
||||
# Movement commands
|
||||
def get_position(self):
|
||||
return list(self.commanded_pos)
|
||||
def set_position(self, newpos):
|
||||
self.move_queue.flush()
|
||||
self._flush_lookahead()
|
||||
self.commanded_pos[:] = newpos
|
||||
self.kin.set_position(newpos)
|
||||
def move(self, newpos, speed):
|
||||
speed = min(speed, self.max_speed)
|
||||
move = Move(self, self.commanded_pos, newpos, speed, self.max_accel)
|
||||
move = Move(self, self.commanded_pos, newpos, speed)
|
||||
if not move.move_d:
|
||||
return
|
||||
if move.is_kinematic_move:
|
||||
self.kin.check_move(move)
|
||||
if move.axes_d[3]:
|
||||
self.extruder.check_move(move)
|
||||
@@ -285,9 +355,30 @@ class ToolHead:
|
||||
self.extruder.motor_off(last_move_time)
|
||||
self.dwell(STALL_TIME)
|
||||
logging.debug('; Max time of %f' % (last_move_time,))
|
||||
def wait_moves(self):
|
||||
self._flush_lookahead()
|
||||
eventtime = self.reactor.monotonic()
|
||||
while self.print_time:
|
||||
eventtime = self.reactor.pause(eventtime + 0.100)
|
||||
def query_endstops(self):
|
||||
last_move_time = self.get_last_move_time()
|
||||
return self.kin.query_endstops(last_move_time)
|
||||
# Misc commands
|
||||
def stats(self, eventtime):
|
||||
buffer_time = 0.
|
||||
print_time = self.print_time
|
||||
if print_time:
|
||||
is_active = True
|
||||
buffer_time = max(0., self.printer.mcu.get_print_buffer_time(
|
||||
eventtime, print_time))
|
||||
else:
|
||||
is_active = eventtime < self.last_print_end_time + 60.
|
||||
return is_active, "print_time=%.3f buffer_time=%.3f print_stall=%d" % (
|
||||
print_time, buffer_time, self.print_stall)
|
||||
def force_shutdown(self):
|
||||
try:
|
||||
self.printer.mcu.force_shutdown()
|
||||
self.move_queue.reset()
|
||||
self.reset_print_time()
|
||||
except:
|
||||
logging.exception("Exception in force_shutdown")
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
import os, pty, fcntl, termios, signal
|
||||
import sys, os, pty, fcntl, termios, signal, logging
|
||||
import subprocess, traceback, shlex
|
||||
|
||||
# Return the SIGINT interrupt handler back to the OS default
|
||||
def fix_sigint():
|
||||
@@ -36,3 +36,34 @@ def create_pty(ptyname):
|
||||
old[3] = old[3] & ~termios.ECHO
|
||||
termios.tcsetattr(mfd, termios.TCSADRAIN, old)
|
||||
return mfd
|
||||
|
||||
def get_cpu_info():
|
||||
try:
|
||||
f = open('/proc/cpuinfo', 'rb')
|
||||
data = f.read()
|
||||
f.close()
|
||||
except OSError:
|
||||
logging.debug("Exception on read /proc/cpuinfo: %s" % (
|
||||
traceback.format_exc(),))
|
||||
return "?"
|
||||
lines = [l.split(':', 1) for l in data.split('\n')]
|
||||
lines = [(l[0].strip(), l[1].strip()) for l in lines if len(l) == 2]
|
||||
core_count = [k for k, v in lines].count("processor")
|
||||
model_name = dict(lines).get("model name", "?")
|
||||
return "%d core %s" % (core_count, model_name)
|
||||
|
||||
def get_git_version():
|
||||
# Obtain version info from "git" program
|
||||
gitdir = os.path.join(sys.path[0], '..', '.git')
|
||||
if not os.path.exists(gitdir):
|
||||
logging.debug("No '.git' file/directory found")
|
||||
return "?"
|
||||
prog = "git --git-dir=%s describe --tags --long --dirty" % (gitdir,)
|
||||
try:
|
||||
process = subprocess.Popen(shlex.split(prog), stdout=subprocess.PIPE)
|
||||
output = process.communicate()[0]
|
||||
retcode = process.poll()
|
||||
except OSError:
|
||||
logging.debug("Exception on run: %s" % (traceback.format_exc(),))
|
||||
return "?"
|
||||
return output.strip()
|
||||
|
||||
@@ -10,3 +10,7 @@ The cmsis-sam3x8e directory contains code from the Arduino project:
|
||||
version 1.5.1 (extracted on 20160608). It has been modified to
|
||||
compile with gcc's LTO feature. See cmsis-sam3x8e.patch for the
|
||||
modifications.
|
||||
|
||||
The hub-ctrl directory contains code from:
|
||||
https://github.com/codazoda/hub-ctrl.c/
|
||||
revision 42095e522859059e8a5f4ec05c1e3def01a870a9.
|
||||
|
||||
412
lib/hub-ctrl/hub-ctrl.c
Normal file
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
* Copyright (C) 2006 Free Software Initiative of Japan
|
||||
*
|
||||
* Author: NIIBE Yutaka <gniibe at fsij.org>
|
||||
*
|
||||
* This file can be distributed under the terms and conditions of the
|
||||
* GNU General Public License version 2 (or later).
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <usb.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define USB_RT_HUB (USB_TYPE_CLASS | USB_RECIP_DEVICE)
|
||||
#define USB_RT_PORT (USB_TYPE_CLASS | USB_RECIP_OTHER)
|
||||
#define USB_PORT_FEAT_POWER 8
|
||||
#define USB_PORT_FEAT_INDICATOR 22
|
||||
#define USB_DIR_IN 0x80 /* to host */
|
||||
|
||||
#define COMMAND_SET_NONE 0
|
||||
#define COMMAND_SET_LED 1
|
||||
#define COMMAND_SET_POWER 2
|
||||
#define HUB_LED_GREEN 2
|
||||
|
||||
static void
|
||||
usage (const char *progname)
|
||||
{
|
||||
fprintf (stderr,
|
||||
"Usage: %s [{-h HUBNUM | -b BUSNUM -d DEVNUM}] \\\n"
|
||||
" [-P PORT] [{-p [VALUE]|-l [VALUE]}]\n", progname);
|
||||
}
|
||||
|
||||
static void
|
||||
exit_with_usage (const char *progname)
|
||||
{
|
||||
usage (progname);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
#define HUB_CHAR_LPSM 0x0003
|
||||
#define HUB_CHAR_PORTIND 0x0080
|
||||
|
||||
struct usb_hub_descriptor {
|
||||
unsigned char bDescLength;
|
||||
unsigned char bDescriptorType;
|
||||
unsigned char bNbrPorts;
|
||||
unsigned char wHubCharacteristics[2];
|
||||
unsigned char bPwrOn2PwrGood;
|
||||
unsigned char bHubContrCurrent;
|
||||
unsigned char data[0];
|
||||
};
|
||||
|
||||
#define CTRL_TIMEOUT 1000
|
||||
#define USB_STATUS_SIZE 4
|
||||
|
||||
#define MAX_HUBS 128
|
||||
struct hub_info {
|
||||
int busnum, devnum;
|
||||
struct usb_device *dev;
|
||||
int nport;
|
||||
int indicator_support;
|
||||
};
|
||||
|
||||
static struct hub_info hubs[MAX_HUBS];
|
||||
static int number_of_hubs_with_feature;
|
||||
|
||||
static void
|
||||
hub_port_status (usb_dev_handle *uh, int nport)
|
||||
{
|
||||
int i;
|
||||
|
||||
printf(" Hub Port Status:\n");
|
||||
for (i = 0; i < nport; i++)
|
||||
{
|
||||
char buf[USB_STATUS_SIZE];
|
||||
int ret;
|
||||
|
||||
ret = usb_control_msg (uh,
|
||||
USB_ENDPOINT_IN | USB_TYPE_CLASS | USB_RECIP_OTHER,
|
||||
USB_REQ_GET_STATUS,
|
||||
0, i + 1,
|
||||
buf, USB_STATUS_SIZE,
|
||||
CTRL_TIMEOUT);
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf (stderr,
|
||||
"cannot read port %d status, %s (%d)\n",
|
||||
i + 1, strerror(errno), errno);
|
||||
break;
|
||||
}
|
||||
|
||||
printf(" Port %d: %02x%02x.%02x%02x", i + 1,
|
||||
buf[3], buf [2],
|
||||
buf[1], buf [0]);
|
||||
|
||||
printf("%s%s%s%s%s",
|
||||
(buf[2] & 0x10) ? " C_RESET" : "",
|
||||
(buf[2] & 0x08) ? " C_OC" : "",
|
||||
(buf[2] & 0x04) ? " C_SUSPEND" : "",
|
||||
(buf[2] & 0x02) ? " C_ENABLE" : "",
|
||||
(buf[2] & 0x01) ? " C_CONNECT" : "");
|
||||
|
||||
printf("%s%s%s%s%s%s%s%s%s%s\n",
|
||||
(buf[1] & 0x10) ? " indicator" : "",
|
||||
(buf[1] & 0x08) ? " test" : "",
|
||||
(buf[1] & 0x04) ? " highspeed" : "",
|
||||
(buf[1] & 0x02) ? " lowspeed" : "",
|
||||
(buf[1] & 0x01) ? " power" : "",
|
||||
(buf[0] & 0x10) ? " RESET" : "",
|
||||
(buf[0] & 0x08) ? " oc" : "",
|
||||
(buf[0] & 0x04) ? " suspend" : "",
|
||||
(buf[0] & 0x02) ? " enable" : "",
|
||||
(buf[0] & 0x01) ? " connect" : "");
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
usb_find_hubs (int listing, int verbose, int busnum, int devnum, int hub)
|
||||
{
|
||||
struct usb_bus *busses;
|
||||
struct usb_bus *bus;
|
||||
|
||||
number_of_hubs_with_feature = 0;
|
||||
busses = usb_get_busses();
|
||||
if (busses == NULL)
|
||||
{
|
||||
perror ("failed to access USB");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (bus = busses; bus; bus = bus->next)
|
||||
{
|
||||
struct usb_device *dev;
|
||||
|
||||
for (dev = bus->devices; dev; dev = dev->next)
|
||||
{
|
||||
usb_dev_handle *uh;
|
||||
int print = 0;
|
||||
|
||||
if (dev->descriptor.bDeviceClass != USB_CLASS_HUB)
|
||||
continue;
|
||||
|
||||
if (listing
|
||||
|| (verbose
|
||||
&& ((atoi (bus->dirname) == busnum && dev->devnum == devnum)
|
||||
|| hub == number_of_hubs_with_feature)))
|
||||
print = 1;
|
||||
|
||||
uh = usb_open (dev);
|
||||
|
||||
if (uh != NULL)
|
||||
{
|
||||
char buf[1024];
|
||||
int len;
|
||||
int nport;
|
||||
struct usb_hub_descriptor *uhd = (struct usb_hub_descriptor *)buf;
|
||||
if ((len = usb_control_msg (uh, USB_DIR_IN | USB_RT_HUB,
|
||||
USB_REQ_GET_DESCRIPTOR,
|
||||
USB_DT_HUB << 8, 0,
|
||||
buf, sizeof (buf), CTRL_TIMEOUT))
|
||||
> sizeof (struct usb_hub_descriptor))
|
||||
{
|
||||
if (!(uhd->wHubCharacteristics[0] & HUB_CHAR_PORTIND)
|
||||
&& (uhd->wHubCharacteristics[0] & HUB_CHAR_LPSM) >= 2)
|
||||
continue;
|
||||
|
||||
if (print)
|
||||
printf ("Hub #%d at %s:%03d\n",
|
||||
number_of_hubs_with_feature,
|
||||
bus->dirname, dev->devnum);
|
||||
|
||||
switch ((uhd->wHubCharacteristics[0] & HUB_CHAR_LPSM))
|
||||
{
|
||||
case 0:
|
||||
if (print)
|
||||
fprintf (stderr, " INFO: ganged switching.\n");
|
||||
break;
|
||||
case 1:
|
||||
if (print)
|
||||
fprintf (stderr, " INFO: individual power switching.\n");
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
if (print)
|
||||
fprintf (stderr, " WARN: No power switching.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (print
|
||||
&& !(uhd->wHubCharacteristics[0] & HUB_CHAR_PORTIND))
|
||||
fprintf (stderr, " WARN: Port indicators are NOT supported.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
perror ("Can't get hub descriptor");
|
||||
usb_close (uh);
|
||||
continue;
|
||||
}
|
||||
|
||||
nport = buf[2];
|
||||
hubs[number_of_hubs_with_feature].busnum = atoi (bus->dirname);
|
||||
hubs[number_of_hubs_with_feature].devnum = dev->devnum;
|
||||
hubs[number_of_hubs_with_feature].dev = dev;
|
||||
hubs[number_of_hubs_with_feature].indicator_support =
|
||||
(uhd->wHubCharacteristics[0] & HUB_CHAR_PORTIND)? 1 : 0;
|
||||
hubs[number_of_hubs_with_feature].nport = nport;
|
||||
|
||||
number_of_hubs_with_feature++;
|
||||
|
||||
if (verbose)
|
||||
hub_port_status (uh, nport);
|
||||
|
||||
usb_close (uh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return number_of_hubs_with_feature;
|
||||
}
|
||||
|
||||
int
|
||||
get_hub (int busnum, int devnum)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < number_of_hubs_with_feature; i++)
|
||||
if (hubs[i].busnum == busnum && hubs[i].devnum == devnum)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* HUB-CTRL - program to control port power/led of USB hub
|
||||
*
|
||||
* # hub-ctrl // List hubs available
|
||||
* # hub-ctrl -P 1 // Power off at port 1
|
||||
* # hub-ctrl -P 1 -p 1 // Power on at port 1
|
||||
* # hub-ctrl -P 2 -l // LED on at port 1
|
||||
*
|
||||
* Requirement: USB hub which implements port power control / indicator control
|
||||
*
|
||||
* Work fine:
|
||||
* Elecom's U2H-G4S: www.elecom.co.jp (indicator depends on power)
|
||||
* 04b4:6560
|
||||
*
|
||||
* Sanwa Supply's USB-HUB14GPH: www.sanwa.co.jp (indicators don't)
|
||||
*
|
||||
* Targus, Inc.'s PAUH212: www.targus.com (indicators don't)
|
||||
* 04cc:1521
|
||||
*
|
||||
* Hawking Technology's UH214: hawkingtech.com (indicators don't)
|
||||
*
|
||||
*/
|
||||
|
||||
int
|
||||
main (int argc, const char *argv[])
|
||||
{
|
||||
int busnum = 0, devnum = 0;
|
||||
int cmd = COMMAND_SET_NONE;
|
||||
int port = 1;
|
||||
int value = 0;
|
||||
int request, feature, index;
|
||||
int result = 0;
|
||||
int listing = 0;
|
||||
int verbose = 0;
|
||||
int hub = -1;
|
||||
usb_dev_handle *uh = NULL;
|
||||
int i;
|
||||
|
||||
if (argc == 1)
|
||||
listing = 1;
|
||||
|
||||
for (i = 1; i < argc; i++)
|
||||
if (argv[i][0] == '-')
|
||||
switch (argv[i][1])
|
||||
{
|
||||
case 'h':
|
||||
if (++i >= argc || busnum > 0 || devnum > 0)
|
||||
exit_with_usage (argv[0]);
|
||||
hub = atoi (argv[i]);
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
if (++i >= argc || hub >= 0)
|
||||
exit_with_usage (argv[0]);
|
||||
busnum = atoi (argv[i]);
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
if (++i >= argc || hub >= 0)
|
||||
exit_with_usage (argv[0]);
|
||||
devnum = atoi (argv[i]);
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
if (++i >= argc)
|
||||
exit_with_usage (argv[0]);
|
||||
port = atoi (argv[i]);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
if (cmd != COMMAND_SET_NONE)
|
||||
exit_with_usage (argv[0]);
|
||||
if (++i < argc)
|
||||
value = atoi (argv[i]);
|
||||
else
|
||||
value = HUB_LED_GREEN;
|
||||
cmd = COMMAND_SET_LED;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
if (cmd != COMMAND_SET_NONE)
|
||||
exit_with_usage (argv[0]);
|
||||
if (++i < argc)
|
||||
value = atoi (argv[i]);
|
||||
else
|
||||
value= 0;
|
||||
cmd = COMMAND_SET_POWER;
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
verbose = 1;
|
||||
if (argc == 2)
|
||||
listing = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
exit_with_usage (argv[0]);
|
||||
}
|
||||
else
|
||||
exit_with_usage (argv[0]);
|
||||
|
||||
if ((busnum > 0 && devnum <= 0) || (busnum <= 0 && devnum > 0))
|
||||
/* BUS is specified, but DEV is'nt, or ... */
|
||||
exit_with_usage (argv[0]);
|
||||
|
||||
/* Default is the hub #0 */
|
||||
if (hub < 0 && busnum == 0)
|
||||
hub = 0;
|
||||
|
||||
/* Default is POWER */
|
||||
if (cmd == COMMAND_SET_NONE)
|
||||
cmd = COMMAND_SET_POWER;
|
||||
|
||||
usb_init ();
|
||||
usb_find_busses ();
|
||||
usb_find_devices ();
|
||||
|
||||
if (usb_find_hubs (listing, verbose, busnum, devnum, hub) <= 0)
|
||||
{
|
||||
fprintf (stderr, "No hubs found.\n");
|
||||
exit (1);
|
||||
}
|
||||
|
||||
if (listing)
|
||||
exit (0);
|
||||
|
||||
if (hub < 0)
|
||||
hub = get_hub (busnum, devnum);
|
||||
|
||||
if (hub >= 0 && hub < number_of_hubs_with_feature)
|
||||
uh = usb_open (hubs[hub].dev);
|
||||
|
||||
if (uh == NULL)
|
||||
{
|
||||
fprintf (stderr, "Device not found.\n");
|
||||
result = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cmd == COMMAND_SET_POWER)
|
||||
if (value)
|
||||
{
|
||||
request = USB_REQ_SET_FEATURE;
|
||||
feature = USB_PORT_FEAT_POWER;
|
||||
index = port;
|
||||
}
|
||||
else
|
||||
{
|
||||
request = USB_REQ_CLEAR_FEATURE;
|
||||
feature = USB_PORT_FEAT_POWER;
|
||||
index = port;
|
||||
}
|
||||
else
|
||||
{
|
||||
request = USB_REQ_SET_FEATURE;
|
||||
feature = USB_PORT_FEAT_INDICATOR;
|
||||
index = (value << 8) | port;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
printf ("Send control message (REQUEST=%d, FEATURE=%d, INDEX=%d)\n",
|
||||
request, feature, index);
|
||||
|
||||
if (usb_control_msg (uh, USB_RT_PORT, request, feature, index,
|
||||
NULL, 0, CTRL_TIMEOUT) < 0)
|
||||
{
|
||||
perror ("failed to control.\n");
|
||||
result = 1;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
hub_port_status (uh, hubs[hub].nport);
|
||||
|
||||
usb_close (uh);
|
||||
}
|
||||
|
||||
exit (result);
|
||||
}
|
||||
@@ -5,21 +5,22 @@
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
import sys, optparse, os, pty, select, fcntl, termios, traceback, errno
|
||||
import sys, optparse, time, os, pty, fcntl, termios, errno
|
||||
import pysimulavr
|
||||
|
||||
SERIALBITS = 10 # 8N1 = 1 start, 8 data, 1 stop
|
||||
SIMULAVR_FREQ = 10**9
|
||||
|
||||
# Class to read serial data from AVR serial transmit pin.
|
||||
class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
||||
def __init__(self, baud):
|
||||
def __init__(self, baud, terminal):
|
||||
pysimulavr.Pin.__init__(self)
|
||||
pysimulavr.PySimulationMember.__init__(self)
|
||||
self.terminal = terminal
|
||||
self.sc = pysimulavr.SystemClock.Instance()
|
||||
self.delay = 10**9 / baud
|
||||
self.delay = SIMULAVR_FREQ / baud
|
||||
self.current = 0
|
||||
self.pos = -1
|
||||
self.queue = ""
|
||||
def SetInState(self, pin):
|
||||
pysimulavr.Pin.SetInState(self, pin)
|
||||
self.state = pin.outState
|
||||
@@ -33,32 +34,33 @@ class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
||||
if self.pos == 1:
|
||||
return int(self.delay * 1.5)
|
||||
if self.pos >= SERIALBITS:
|
||||
self.queue += chr((self.current >> 1) & 0xff)
|
||||
data = chr((self.current >> 1) & 0xff)
|
||||
self.terminal.write(data)
|
||||
self.pos = -1
|
||||
self.current = 0
|
||||
return -1
|
||||
return self.delay
|
||||
def popChars(self):
|
||||
d = self.queue
|
||||
self.queue = ""
|
||||
return d
|
||||
|
||||
# Class to send serial data to AVR serial receive pin.
|
||||
class SerialTxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
||||
MAX_QUEUE = 64
|
||||
def __init__(self, baud):
|
||||
def __init__(self, baud, terminal):
|
||||
pysimulavr.Pin.__init__(self)
|
||||
pysimulavr.PySimulationMember.__init__(self)
|
||||
self.terminal = terminal
|
||||
self.SetPin('H')
|
||||
self.sc = pysimulavr.SystemClock.Instance()
|
||||
self.delay = 10**9 / baud
|
||||
self.delay = SIMULAVR_FREQ / baud
|
||||
self.current = 0
|
||||
self.pos = 0
|
||||
self.queue = ""
|
||||
self.sc.Add(self)
|
||||
def DoStep(self, trueHwStep):
|
||||
if not self.pos:
|
||||
if not self.queue:
|
||||
return -1
|
||||
data = self.terminal.read()
|
||||
if not data:
|
||||
return self.delay * 100
|
||||
self.queue += data
|
||||
self.current = (ord(self.queue[0]) << 1) | 0x200
|
||||
self.queue = self.queue[1:]
|
||||
newstate = 'L'
|
||||
@@ -69,15 +71,6 @@ class SerialTxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
||||
if self.pos >= SERIALBITS:
|
||||
self.pos = 0
|
||||
return self.delay
|
||||
def needChars(self):
|
||||
if len(self.queue) > self.MAX_QUEUE / 2:
|
||||
return 0
|
||||
return self.MAX_QUEUE - len(self.queue)
|
||||
def pushChars(self, c):
|
||||
queueEmpty = not self.queue
|
||||
self.queue += c
|
||||
if queueEmpty:
|
||||
self.sc.Add(self)
|
||||
|
||||
# Support for creating VCD trace files
|
||||
class Tracing:
|
||||
@@ -108,6 +101,47 @@ class Tracing:
|
||||
if self.dman is not None:
|
||||
self.dman.stopApplication()
|
||||
|
||||
# Pace the simulation scaled to real time
|
||||
class Pacing(pysimulavr.PySimulationMember):
|
||||
def __init__(self, rate):
|
||||
pysimulavr.PySimulationMember.__init__(self)
|
||||
self.sc = pysimulavr.SystemClock.Instance()
|
||||
self.pacing_rate = 1. / (rate * SIMULAVR_FREQ)
|
||||
self.rel_time = self.next_rel_time = time.time()
|
||||
self.rel_clock = self.next_rel_clock = self.sc.GetCurrentTime()
|
||||
self.delay = SIMULAVR_FREQ / 10000
|
||||
self.sc.Add(self)
|
||||
def DoStep(self, trueHwStep):
|
||||
curtime = time.time()
|
||||
clock = self.sc.GetCurrentTime()
|
||||
clock_diff = clock - self.rel_clock
|
||||
time_diff = curtime - self.rel_time
|
||||
offset = clock_diff * self.pacing_rate - time_diff
|
||||
if offset > 0.000050:
|
||||
time.sleep(offset)
|
||||
if clock_diff > self.delay * 20:
|
||||
self.rel_clock = self.next_rel_clock
|
||||
self.rel_time = self.next_rel_time
|
||||
self.next_rel_clock = clock
|
||||
self.next_rel_time = curtime
|
||||
return self.delay
|
||||
|
||||
# Forward data from a terminal device to the serial port pins
|
||||
class TerminalIO:
|
||||
def __init__(self):
|
||||
self.fd = -1
|
||||
def run(self, fd):
|
||||
self.fd = fd
|
||||
def write(self, data):
|
||||
os.write(self.fd, data)
|
||||
def read(self):
|
||||
try:
|
||||
return os.read(self.fd, 64)
|
||||
except os.error, e:
|
||||
if e.errno not in (errno.EAGAIN, errno.EWOULDBLOCK):
|
||||
pysimulavr.SystemClock.Instance().stop()
|
||||
return ""
|
||||
|
||||
# Support for creating a pseudo-tty for emulating a serial port
|
||||
def create_pty(ptyname):
|
||||
mfd, sfd = pty.openpty()
|
||||
@@ -130,6 +164,8 @@ def main():
|
||||
default="atmega644", help="type of AVR machine to simulate")
|
||||
opts.add_option("-s", "--speed", type="int", dest="speed", default=8000000,
|
||||
help="machine speed")
|
||||
opts.add_option("-r", "--rate", type="float", dest="pacing_rate", default=0.,
|
||||
help="real-time pacing rate")
|
||||
opts.add_option("-b", "--baud", type="int", dest="baud", default=38400,
|
||||
help="baud rate of the emulated serial port")
|
||||
opts.add_option("-t", "--trace", type="string", dest="trace",
|
||||
@@ -154,18 +190,26 @@ def main():
|
||||
trace = Tracing(options.tracefile, options.trace)
|
||||
dev = pysimulavr.AvrFactory.instance().makeDevice(proc)
|
||||
dev.Load(elffile)
|
||||
dev.SetClockFreq(10**9 / speed)
|
||||
dev.SetClockFreq(SIMULAVR_FREQ / speed)
|
||||
sc.Add(dev)
|
||||
pysimulavr.cvar.sysConHandler.SetUseExit(False)
|
||||
trace.load_options()
|
||||
|
||||
# Do optional real-time pacing
|
||||
if options.pacing_rate:
|
||||
pacing = Pacing(options.pacing_rate)
|
||||
|
||||
# Setup terminal
|
||||
io = TerminalIO()
|
||||
|
||||
# Setup rx pin
|
||||
rxpin = SerialRxPin(baud)
|
||||
rxpin = SerialRxPin(baud, io)
|
||||
net = pysimulavr.Net()
|
||||
net.Add(rxpin)
|
||||
net.Add(dev.GetPin("D1"))
|
||||
|
||||
# Setup tx pin
|
||||
txpin = SerialTxPin(baud)
|
||||
txpin = SerialTxPin(baud, io)
|
||||
net2 = pysimulavr.Net()
|
||||
net2.Add(dev.GetPin("D0"))
|
||||
net2.Add(txpin)
|
||||
@@ -183,27 +227,9 @@ def main():
|
||||
|
||||
# Run loop
|
||||
try:
|
||||
io.run(fd)
|
||||
trace.start()
|
||||
while 1:
|
||||
starttime = sc.GetCurrentTime()
|
||||
r = sc.RunTimeRange(speed/1000)
|
||||
endtime = sc.GetCurrentTime()
|
||||
if starttime == endtime:
|
||||
break
|
||||
d = rxpin.popChars()
|
||||
if d:
|
||||
os.write(fd, d)
|
||||
txsize = txpin.needChars()
|
||||
if txsize:
|
||||
res = select.select([fd], [], [], 0)
|
||||
if res[0]:
|
||||
try:
|
||||
d = os.read(fd, txsize)
|
||||
except os.error, e:
|
||||
if e.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
|
||||
continue
|
||||
break
|
||||
txpin.pushChars(d)
|
||||
sc.RunTimeRange(0x7fff0000ffff0000)
|
||||
trace.finish()
|
||||
finally:
|
||||
os.unlink(ptyname)
|
||||
|
||||
@@ -172,6 +172,9 @@ def main():
|
||||
if '+' in ref:
|
||||
# Inter-function jump.
|
||||
pass
|
||||
elif insn.startswith('ld') or insn.startswith('st'):
|
||||
# memory access
|
||||
pass
|
||||
elif insn in ('rjmp', 'jmp', 'brne', 'brcs'):
|
||||
# Tail call
|
||||
cur.noteCall(insnaddr, calladdr, 0)
|
||||
@@ -190,7 +193,7 @@ def main():
|
||||
funcsbyname[funcnameroot] = info
|
||||
mainfunc = funcsbyname.get('sched_main')
|
||||
cmdfunc = funcsbyname.get('command_task')
|
||||
eventfunc = funcsbyname.get('__vector_13')
|
||||
eventfunc = funcsbyname.get('__vector_13', funcsbyname.get('__vector_17'))
|
||||
for funcnameroot, info in funcsbyname.items():
|
||||
if (funcnameroot.startswith('_DECL_taskfuncs_')
|
||||
or funcnameroot.startswith('_DECL_initfuncs_')
|
||||
@@ -204,7 +207,7 @@ def main():
|
||||
numparams = int(datalines[info.funcaddr][2], 16)
|
||||
stackusage = cmdfunc.basic_stack_usage + 2 + numparams * 4
|
||||
cmdfunc.noteCall(0, f.funcaddr, stackusage)
|
||||
if funcnameroot.endswith('_event'):
|
||||
if funcnameroot.endswith('_event') and eventfunc is not None:
|
||||
eventfunc.noteCall(0, info.funcaddr, eventfunc.basic_stack_usage + 2)
|
||||
|
||||
# Calculate maxstackusage
|
||||
|
||||
@@ -8,7 +8,7 @@ import optparse, datetime
|
||||
import matplotlib.pyplot as plt, matplotlib.dates as mdates
|
||||
|
||||
MAXBANDWIDTH=25000.
|
||||
MAXBUFFER=5.
|
||||
MAXBUFFER=2.
|
||||
|
||||
def parse_log(logname):
|
||||
f = open(logname, 'rb')
|
||||
@@ -27,10 +27,32 @@ def parse_log(logname):
|
||||
f.close()
|
||||
return out
|
||||
|
||||
def find_print_restarts(data):
|
||||
last_print_time = 0.
|
||||
print_resets = []
|
||||
for d in data:
|
||||
print_time = float(d.get('print_time', last_print_time))
|
||||
if print_time < last_print_time:
|
||||
print_resets.append(d['#sampletime'])
|
||||
last_print_time = 0.
|
||||
else:
|
||||
last_print_time = print_time
|
||||
sample_resets = {}
|
||||
for d in data:
|
||||
st = d['#sampletime']
|
||||
while print_resets and st > print_resets[0]:
|
||||
print_resets.pop(0)
|
||||
if not print_resets:
|
||||
break
|
||||
if st + 2. * MAXBUFFER > print_resets[0]:
|
||||
sample_resets[st] = 1
|
||||
return sample_resets
|
||||
|
||||
def plot_mcu(data, maxbw, outname):
|
||||
# Generate data for plot
|
||||
basetime = lasttime = data[0]['#sampletime']
|
||||
lastbw = float(data[0]['bytes_write']) + float(data[0]['bytes_retransmit'])
|
||||
sample_resets = find_print_restarts(data)
|
||||
times = []
|
||||
bwdeltas = []
|
||||
loads = []
|
||||
@@ -49,7 +71,7 @@ def plot_mcu(data, maxbw, outname):
|
||||
load = 0.
|
||||
pt = float(d['print_time'])
|
||||
hb = float(d['buffer_time'])
|
||||
if pt <= 2*MAXBUFFER or hb >= MAXBUFFER:
|
||||
if pt <= 2. * MAXBUFFER or hb >= MAXBUFFER or st in sample_resets:
|
||||
hb = 0.
|
||||
else:
|
||||
hb = 100. * (MAXBUFFER - hb) / MAXBUFFER
|
||||
@@ -63,12 +85,12 @@ def plot_mcu(data, maxbw, outname):
|
||||
# Build plot
|
||||
fig, ax1 = plt.subplots()
|
||||
ax1.set_title("MCU bandwidth and load utilization")
|
||||
ax1.set_xlabel('Time (UTC)')
|
||||
ax1.set_xlabel('Time')
|
||||
ax1.set_ylabel('Usage (%)')
|
||||
ax1.plot_date(times, bwdeltas, 'g', label='Bandwidth')
|
||||
ax1.plot_date(times, loads, 'r', label='MCU load')
|
||||
#ax1.plot_date(times, hostbuffers, 'c', label='Host buffer')
|
||||
ax1.legend()
|
||||
ax1.plot_date(times, hostbuffers, 'c', label='Host buffer')
|
||||
ax1.legend(loc='best')
|
||||
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
|
||||
#plt.gcf().autofmt_xdate()
|
||||
ax1.grid(True)
|
||||
|
||||
102
scripts/install-octopi.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
# This script installs Klipper on a Raspberry Pi machine running the
|
||||
# OctoPi distribution.
|
||||
|
||||
PYTHONDIR="${HOME}/klippy-env"
|
||||
|
||||
# Step 1: Install system packages
|
||||
install_packages()
|
||||
{
|
||||
# Packages for python cffi
|
||||
PKGLIST="libffi-dev"
|
||||
# kconfig requirements
|
||||
PKGLIST="${PKGLIST} libncurses-dev"
|
||||
# hub-ctrl
|
||||
PKGLIST="${PKGLIST} libusb-dev"
|
||||
# AVR chip installation and building
|
||||
PKGLIST="${PKGLIST} avrdude gcc-avr binutils-avr avr-libc"
|
||||
# ARM chip installation and building
|
||||
PKGLIST="${PKGLIST} bossa-cli libnewlib-arm-none-eabi"
|
||||
|
||||
# Update system package info
|
||||
report_status "Running apt-get update..."
|
||||
sudo apt-get update
|
||||
|
||||
# Install desired packages
|
||||
report_status "Installing packages..."
|
||||
sudo apt-get install --yes ${PKGLIST}
|
||||
}
|
||||
|
||||
# Step 2: Create python virtual environment
|
||||
create_virtualenv()
|
||||
{
|
||||
report_status "Updating python virtual environment..."
|
||||
|
||||
# Create virtualenv if it doesn't already exist
|
||||
[ ! -d ${PYTHONDIR} ] && virtualenv ${PYTHONDIR}
|
||||
|
||||
# Install/update dependencies
|
||||
${PYTHONDIR}/bin/pip install cffi==1.6.0 pyserial==3.2.1 greenlet==0.4.10
|
||||
}
|
||||
|
||||
# Step 3: Install startup script
|
||||
install_script()
|
||||
{
|
||||
report_status "Installing system start script..."
|
||||
sudo cp "${SRCDIR}/scripts/klipper-start.sh" /etc/init.d/klipper
|
||||
sudo update-rc.d klipper defaults
|
||||
}
|
||||
|
||||
# Step 4: Install startup script config
|
||||
install_config()
|
||||
{
|
||||
DEFAULTS_FILE=/etc/default/klipper
|
||||
[ -f $DEFAULTS_FILE ] && return
|
||||
|
||||
report_status "Installing system start configuration..."
|
||||
sudo /bin/sh -c "cat > $DEFAULTS_FILE" <<EOF
|
||||
# Configuration for /etc/init.d/klipper
|
||||
|
||||
KLIPPY_USER=$USER
|
||||
|
||||
KLIPPY_EXEC=${PYTHONDIR}/bin/python
|
||||
|
||||
KLIPPY_ARGS="${SRCDIR}/klippy/klippy.py ${HOME}/printer.cfg -l /tmp/klippy.log"
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Step 5: Start host software
|
||||
start_software()
|
||||
{
|
||||
report_status "Launching Klipper host software..."
|
||||
sudo /etc/init.d/klipper restart
|
||||
}
|
||||
|
||||
# Helper functions
|
||||
report_status()
|
||||
{
|
||||
echo -e "\n\n###### $1"
|
||||
}
|
||||
|
||||
verify_ready()
|
||||
{
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
echo "This script must not run as root"
|
||||
exit -1
|
||||
fi
|
||||
}
|
||||
|
||||
# Force script to exit if an error occurs
|
||||
set -e
|
||||
|
||||
# Find SRCDIR from the pathname of this script
|
||||
SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
|
||||
# Run installation steps defined above
|
||||
verify_ready
|
||||
install_packages
|
||||
create_virtualenv
|
||||
install_script
|
||||
install_config
|
||||
start_software
|
||||
54
scripts/klipper-start.sh
Executable file
@@ -0,0 +1,54 @@
|
||||
#!/bin/sh
|
||||
# System startup script for Klipper 3d-printer host code
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: klipper
|
||||
# Required-Start: $local_fs
|
||||
# Required-Stop:
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Klipper daemon
|
||||
# Description: Starts the Klipper daemon.
|
||||
### END INIT INFO
|
||||
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
DESC="klipper daemon"
|
||||
NAME="klipper"
|
||||
DEFAULTS_FILE=/etc/default/klipper
|
||||
PIDFILE=/var/run/klipper.pid
|
||||
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
# Read defaults file
|
||||
[ -r $DEFAULTS_FILE ] && . $DEFAULTS_FILE
|
||||
|
||||
case "$1" in
|
||||
start) log_daemon_msg "Starting klipper" $NAME
|
||||
start-stop-daemon --start --quiet --exec $KLIPPY_EXEC \
|
||||
--background --pidfile $PIDFILE --make-pidfile \
|
||||
--chuid $KLIPPY_USER --user $KLIPPY_USER \
|
||||
-- $KLIPPY_ARGS
|
||||
log_end_msg $?
|
||||
;;
|
||||
stop) log_daemon_msg "Stopping klipper" $NAME
|
||||
killproc -p $PIDFILE $KLIPPY_EXEC
|
||||
RETVAL=$?
|
||||
[ $RETVAL -eq 0 ] && [ -e "$PIDFILE" ] && rm -f $PIDFILE
|
||||
log_end_msg $RETVAL
|
||||
;;
|
||||
restart) log_daemon_msg "Restarting klipper" $NAME
|
||||
$0 stop
|
||||
$0 start
|
||||
;;
|
||||
reload|force-reload)
|
||||
log_daemon_msg "Reloading configuration not supported" $NAME
|
||||
log_end_msg 1
|
||||
;;
|
||||
status)
|
||||
status_of_proc -p $PIDFILE $KLIPPY_EXEC $NAME && exit 0 || exit $?
|
||||
;;
|
||||
*) log_action_msg "Usage: /etc/init.d/klipper {start|stop|status|restart|reload|force-reload}"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
exit 0
|
||||
@@ -4,7 +4,7 @@
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
#include "basecmd.h" // alloc_oid
|
||||
#include "basecmd.h" // oid_alloc
|
||||
#include "board/gpio.h" // struct gpio_adc
|
||||
#include "board/irq.h" // irq_disable
|
||||
#include "command.h" // DECL_COMMAND
|
||||
@@ -50,7 +50,7 @@ analog_in_event(struct timer *timer)
|
||||
void
|
||||
command_config_analog_in(uint32_t *args)
|
||||
{
|
||||
struct analog_in *a = alloc_oid(
|
||||
struct analog_in *a = oid_alloc(
|
||||
args[0], command_config_analog_in, sizeof(*a));
|
||||
a->timer.func = analog_in_event;
|
||||
a->pin = gpio_adc_setup(args[1]);
|
||||
@@ -61,7 +61,7 @@ DECL_COMMAND(command_config_analog_in, "config_analog_in oid=%c pin=%u");
|
||||
void
|
||||
command_query_analog_in(uint32_t *args)
|
||||
{
|
||||
struct analog_in *a = lookup_oid(args[0], command_config_analog_in);
|
||||
struct analog_in *a = oid_lookup(args[0], command_config_analog_in);
|
||||
sched_del_timer(&a->timer);
|
||||
gpio_adc_cancel_sample(a->pin);
|
||||
a->next_begin_time = args[1];
|
||||
@@ -74,7 +74,7 @@ command_query_analog_in(uint32_t *args)
|
||||
a->max_value = args[6];
|
||||
if (! a->sample_count)
|
||||
return;
|
||||
sched_timer(&a->timer);
|
||||
sched_add_timer(&a->timer);
|
||||
}
|
||||
DECL_COMMAND(command_query_analog_in,
|
||||
"query_analog_in oid=%c clock=%u sample_ticks=%u sample_count=%c"
|
||||
|
||||
@@ -5,8 +5,7 @@ CROSS_PREFIX=avr-
|
||||
|
||||
dirs-y += src/avr lib/pjrc_usb_serial
|
||||
|
||||
CFLAGS-y += -Os -mmcu=$(CONFIG_MCU) -DF_CPU=$(CONFIG_CLOCK_FREQ)
|
||||
LDFLAGS-y += -Wl,--relax
|
||||
CFLAGS-y += -mmcu=$(CONFIG_MCU)
|
||||
|
||||
# Add avr source files
|
||||
src-y += avr/main.c avr/timer.c avr/gpio.c avr/misc.c
|
||||
@@ -14,9 +13,16 @@ src-$(CONFIG_AVR_WATCHDOG) += avr/watchdog.c
|
||||
src-$(CONFIG_AVR_USBSERIAL) += avr/usbserial.c ../lib/pjrc_usb_serial/usb_serial.c
|
||||
src-$(CONFIG_AVR_SERIAL) += avr/serial.c
|
||||
|
||||
# Suppress broken "misspelled signal handler" warnings on gcc 4.8.1
|
||||
CFLAGS_klipper.o := $(if $(filter 4.8.1, $(shell $(CC) -dumpversion)), -w)
|
||||
|
||||
# Build the additional hex output file
|
||||
target-y += $(OUT)klipper.elf.hex
|
||||
|
||||
$(OUT)klipper.elf.hex: $(OUT)klipper.elf
|
||||
@echo " Creating hex file $@"
|
||||
$(Q)$(OBJCOPY) -j .text -j .data -O ihex $< $@
|
||||
|
||||
flash: $(OUT)klipper.elf.hex
|
||||
@echo " Flashing $(FLASH_DEVICE) via avrdude"
|
||||
$(Q)avrdude -p$(CONFIG_MCU) -cwiring -P"$(FLASH_DEVICE)" -D -U"flash:w:$(OUT)klipper.elf.hex:i"
|
||||
|
||||
248
src/avr/gpio.c
@@ -1,6 +1,6 @@
|
||||
// GPIO functions on AVR.
|
||||
//
|
||||
// Copyright (C) 2016 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.
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* AVR chip definitions
|
||||
* General Purpose Input Output (GPIO) pins
|
||||
****************************************************************/
|
||||
|
||||
#define GPIO(PORT, NUM) (((PORT)-'A') * 8 + (NUM))
|
||||
@@ -44,6 +44,67 @@ struct gpio_digital_regs {
|
||||
#define GPIO2REGS(pin) \
|
||||
((struct gpio_digital_regs*)READP(digital_regs[GPIO2PORT(pin)]))
|
||||
|
||||
struct gpio_out
|
||||
gpio_out_setup(uint8_t pin, uint8_t val)
|
||||
{
|
||||
if (GPIO2PORT(pin) >= ARRAY_SIZE(digital_regs))
|
||||
goto fail;
|
||||
struct gpio_digital_regs *regs = GPIO2REGS(pin);
|
||||
if (! regs)
|
||||
goto fail;
|
||||
uint8_t bit = GPIO2BIT(pin);
|
||||
irqstatus_t flag = irq_save();
|
||||
regs->out = val ? (regs->out | bit) : (regs->out & ~bit);
|
||||
regs->mode |= bit;
|
||||
irq_restore(flag);
|
||||
return (struct gpio_out){ .regs=regs, .bit=bit };
|
||||
fail:
|
||||
shutdown("Not an output pin");
|
||||
}
|
||||
|
||||
void
|
||||
gpio_out_toggle(struct gpio_out g)
|
||||
{
|
||||
g.regs->in = g.bit;
|
||||
}
|
||||
|
||||
void
|
||||
gpio_out_write(struct gpio_out g, uint8_t val)
|
||||
{
|
||||
irqstatus_t flag = irq_save();
|
||||
g.regs->out = val ? (g.regs->out | g.bit) : (g.regs->out & ~g.bit);
|
||||
irq_restore(flag);
|
||||
}
|
||||
|
||||
struct gpio_in
|
||||
gpio_in_setup(uint8_t pin, int8_t pull_up)
|
||||
{
|
||||
if (GPIO2PORT(pin) >= ARRAY_SIZE(digital_regs))
|
||||
goto fail;
|
||||
struct gpio_digital_regs *regs = GPIO2REGS(pin);
|
||||
if (! regs)
|
||||
goto fail;
|
||||
uint8_t bit = GPIO2BIT(pin);
|
||||
irqstatus_t flag = irq_save();
|
||||
regs->out = pull_up > 0 ? (regs->out | bit) : (regs->out & ~bit);
|
||||
regs->mode &= ~bit;
|
||||
irq_restore(flag);
|
||||
return (struct gpio_in){ .regs=regs, .bit=bit };
|
||||
fail:
|
||||
shutdown("Not an input pin");
|
||||
}
|
||||
|
||||
uint8_t
|
||||
gpio_in_read(struct gpio_in g)
|
||||
{
|
||||
return !!(g.regs->in & g.bit);
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* Hardware Pulse Width Modulation (PWM) pins
|
||||
****************************************************************/
|
||||
|
||||
struct gpio_pwm_info {
|
||||
volatile void *ocr;
|
||||
volatile uint8_t *rega, *regb;
|
||||
@@ -101,116 +162,21 @@ static const uint8_t pwm_pins[ARRAY_SIZE(pwm_regs)] PROGMEM = {
|
||||
#endif
|
||||
};
|
||||
|
||||
static const uint8_t adc_pins[] PROGMEM = {
|
||||
#if CONFIG_MACH_atmega168
|
||||
GPIO('C', 0), GPIO('C', 1), GPIO('C', 2), GPIO('C', 3),
|
||||
GPIO('C', 4), GPIO('C', 5), GPIO('E', 0), GPIO('E', 1),
|
||||
#elif CONFIG_MACH_atmega644p
|
||||
GPIO('A', 0), GPIO('A', 1), GPIO('A', 2), GPIO('A', 3),
|
||||
GPIO('A', 4), GPIO('A', 5), GPIO('A', 6), GPIO('A', 7),
|
||||
#elif CONFIG_MACH_at90usb1286
|
||||
GPIO('F', 0), GPIO('F', 1), GPIO('F', 2), GPIO('F', 3),
|
||||
GPIO('F', 4), GPIO('F', 5), GPIO('F', 6), GPIO('F', 7),
|
||||
#elif CONFIG_MACH_atmega1280 || CONFIG_MACH_atmega2560
|
||||
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('K', 0), GPIO('K', 1), GPIO('K', 2), GPIO('K', 3),
|
||||
GPIO('K', 4), GPIO('K', 5), GPIO('K', 6), GPIO('K', 7),
|
||||
#endif
|
||||
};
|
||||
|
||||
#if CONFIG_MACH_atmega168
|
||||
static const uint8_t SS = GPIO('B', 2), SCK = GPIO('B', 5), MOSI = GPIO('B', 3);
|
||||
#elif CONFIG_MACH_atmega644p
|
||||
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
|
||||
static const uint8_t SS = GPIO('B', 0), SCK = GPIO('B', 1), MOSI = GPIO('B', 2);
|
||||
#endif
|
||||
|
||||
static const uint8_t ADMUX_DEFAULT = 0x40;
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* gpio functions
|
||||
****************************************************************/
|
||||
|
||||
struct gpio_out
|
||||
gpio_out_setup(uint8_t pin, uint8_t val)
|
||||
{
|
||||
if (GPIO2PORT(pin) >= ARRAY_SIZE(digital_regs))
|
||||
goto fail;
|
||||
struct gpio_digital_regs *regs = GPIO2REGS(pin);
|
||||
if (! regs)
|
||||
goto fail;
|
||||
uint8_t bit = GPIO2BIT(pin);
|
||||
irqstatus_t flag = irq_save();
|
||||
regs->out = val ? (regs->out | bit) : (regs->out & ~bit);
|
||||
regs->mode |= bit;
|
||||
irq_restore(flag);
|
||||
return (struct gpio_out){ .regs=regs, .bit=bit };
|
||||
fail:
|
||||
shutdown("Not an output pin");
|
||||
}
|
||||
|
||||
void gpio_out_toggle(struct gpio_out g)
|
||||
{
|
||||
g.regs->in = g.bit;
|
||||
}
|
||||
|
||||
void
|
||||
gpio_out_write(struct gpio_out g, uint8_t val)
|
||||
{
|
||||
irqstatus_t flag = irq_save();
|
||||
g.regs->out = val ? (g.regs->out | g.bit) : (g.regs->out & ~g.bit);
|
||||
irq_restore(flag);
|
||||
}
|
||||
|
||||
struct gpio_in
|
||||
gpio_in_setup(uint8_t pin, int8_t pull_up)
|
||||
{
|
||||
if (GPIO2PORT(pin) >= ARRAY_SIZE(digital_regs))
|
||||
goto fail;
|
||||
struct gpio_digital_regs *regs = GPIO2REGS(pin);
|
||||
if (! regs)
|
||||
goto fail;
|
||||
uint8_t bit = GPIO2BIT(pin);
|
||||
irqstatus_t flag = irq_save();
|
||||
regs->out = pull_up > 0 ? (regs->out | bit) : (regs->out & ~bit);
|
||||
regs->mode &= ~bit;
|
||||
irq_restore(flag);
|
||||
return (struct gpio_in){ .regs=regs, .bit=bit };
|
||||
fail:
|
||||
shutdown("Not an input pin");
|
||||
}
|
||||
|
||||
uint8_t
|
||||
gpio_in_read(struct gpio_in g)
|
||||
{
|
||||
return !!(g.regs->in & g.bit);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
gpio_pwm_write(struct gpio_pwm g, uint8_t val)
|
||||
{
|
||||
if (g.size8) {
|
||||
*(volatile uint8_t*)g.reg = val;
|
||||
} else {
|
||||
irqstatus_t flag = irq_save();
|
||||
*(volatile uint16_t*)g.reg = val;
|
||||
irq_restore(flag);
|
||||
}
|
||||
}
|
||||
|
||||
struct gpio_pwm
|
||||
gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val)
|
||||
{
|
||||
// Find pin in pwm_pins table
|
||||
uint8_t chan;
|
||||
for (chan=0; chan<ARRAY_SIZE(pwm_regs); chan++) {
|
||||
if (READP(pwm_pins[chan]) != pin)
|
||||
continue;
|
||||
for (chan=0; ; chan++) {
|
||||
if (chan >= ARRAY_SIZE(pwm_pins))
|
||||
shutdown("Not a valid PWM pin");
|
||||
if (READP(pwm_pins[chan]) == pin)
|
||||
break;
|
||||
}
|
||||
|
||||
// Map cycle_time to pwm clock divisor
|
||||
const struct gpio_pwm_info *p = &pwm_regs[chan];
|
||||
irqstatus_t flags = READP(p->flags), cs;
|
||||
uint8_t flags = READP(p->flags), cs;
|
||||
if (flags & GP_AFMT) {
|
||||
switch (cycle_time) {
|
||||
case 0 ... 8*510L - 1: cs = 1; break;
|
||||
@@ -232,12 +198,12 @@ gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val)
|
||||
}
|
||||
volatile uint8_t *rega = READP(p->rega), *regb = READP(p->regb);
|
||||
uint8_t en_bit = READP(p->en_bit);
|
||||
struct gpio_digital_regs *regs = GPIO2REGS(pin);
|
||||
uint8_t bit = GPIO2BIT(pin);
|
||||
struct gpio_digital_regs *gpio_regs = GPIO2REGS(pin);
|
||||
uint8_t gpio_bit = GPIO2BIT(pin);
|
||||
struct gpio_pwm g = (struct gpio_pwm) {
|
||||
(void*)READP(p->ocr), flags & GP_8BIT };
|
||||
if (rega == &TCCR1A)
|
||||
shutdown("Can not user timer1 for PWM; timer1 is used for timers");
|
||||
shutdown("Can not use timer1 for PWM; timer1 is used for timers");
|
||||
|
||||
// Setup PWM timer
|
||||
irqstatus_t flag = irq_save();
|
||||
@@ -249,24 +215,62 @@ gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val)
|
||||
// Set default value and enable output
|
||||
gpio_pwm_write(g, val);
|
||||
*rega |= (1<<WGM00) | en_bit;
|
||||
regs->mode |= bit;
|
||||
gpio_regs->mode |= gpio_bit;
|
||||
irq_restore(flag);
|
||||
|
||||
return g;
|
||||
}
|
||||
shutdown("Not a valid PWM pin");
|
||||
|
||||
void
|
||||
gpio_pwm_write(struct gpio_pwm g, uint8_t val)
|
||||
{
|
||||
if (g.size8) {
|
||||
*(volatile uint8_t*)g.reg = val;
|
||||
} else {
|
||||
irqstatus_t flag = irq_save();
|
||||
*(volatile uint16_t*)g.reg = val;
|
||||
irq_restore(flag);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* Analog to Digital Converter (ADC) pins
|
||||
****************************************************************/
|
||||
|
||||
static const uint8_t adc_pins[] PROGMEM = {
|
||||
#if CONFIG_MACH_atmega168
|
||||
GPIO('C', 0), GPIO('C', 1), GPIO('C', 2), GPIO('C', 3),
|
||||
GPIO('C', 4), GPIO('C', 5), GPIO('E', 0), GPIO('E', 1),
|
||||
#elif CONFIG_MACH_atmega644p
|
||||
GPIO('A', 0), GPIO('A', 1), GPIO('A', 2), GPIO('A', 3),
|
||||
GPIO('A', 4), GPIO('A', 5), GPIO('A', 6), GPIO('A', 7),
|
||||
#elif CONFIG_MACH_at90usb1286
|
||||
GPIO('F', 0), GPIO('F', 1), GPIO('F', 2), GPIO('F', 3),
|
||||
GPIO('F', 4), GPIO('F', 5), GPIO('F', 6), GPIO('F', 7),
|
||||
#elif CONFIG_MACH_atmega1280 || CONFIG_MACH_atmega2560
|
||||
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('K', 0), GPIO('K', 1), GPIO('K', 2), GPIO('K', 3),
|
||||
GPIO('K', 4), GPIO('K', 5), GPIO('K', 6), GPIO('K', 7),
|
||||
#endif
|
||||
};
|
||||
|
||||
static const uint8_t ADMUX_DEFAULT = 0x40;
|
||||
|
||||
DECL_CONSTANT(ADC_MAX, 1024);
|
||||
|
||||
struct gpio_adc
|
||||
gpio_adc_setup(uint8_t pin)
|
||||
{
|
||||
// Find pin in adc_pins table
|
||||
uint8_t chan;
|
||||
for (chan=0; chan<ARRAY_SIZE(adc_pins); chan++) {
|
||||
if (READP(adc_pins[chan]) != pin)
|
||||
continue;
|
||||
for (chan=0; ; chan++) {
|
||||
if (chan >= ARRAY_SIZE(adc_pins))
|
||||
shutdown("Not a valid ADC pin");
|
||||
if (READP(adc_pins[chan]) == pin)
|
||||
break;
|
||||
}
|
||||
|
||||
// Enable ADC
|
||||
ADCSRA |= (1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2)|(1<<ADEN);
|
||||
@@ -281,8 +285,6 @@ gpio_adc_setup(uint8_t pin)
|
||||
|
||||
return (struct gpio_adc){ chan };
|
||||
}
|
||||
shutdown("Not a valid ADC pin");
|
||||
}
|
||||
|
||||
enum { ADC_DUMMY=0xff };
|
||||
static uint8_t last_analog_read = ADC_DUMMY;
|
||||
@@ -335,6 +337,18 @@ gpio_adc_cancel_sample(struct gpio_adc g)
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* Serial Peripheral Interface (SPI) hardware
|
||||
****************************************************************/
|
||||
|
||||
#if CONFIG_MACH_atmega168
|
||||
static const uint8_t SS = GPIO('B', 2), SCK = GPIO('B', 5), MOSI = GPIO('B', 3);
|
||||
#elif CONFIG_MACH_atmega644p
|
||||
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
|
||||
static const uint8_t SS = GPIO('B', 0), SCK = GPIO('B', 1), MOSI = GPIO('B', 2);
|
||||
#endif
|
||||
|
||||
void
|
||||
spi_config(void)
|
||||
{
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
#include "autoconf.h" // CONFIG_MCU
|
||||
#include "command.h" // DECL_CONSTANT
|
||||
#include "irq.h" // irq_enable
|
||||
#include "sched.h" // sched_main
|
||||
|
||||
DECL_CONSTANT(MCU, CONFIG_MCU);
|
||||
|
||||
// Main entry point for avr code.
|
||||
int
|
||||
main(void)
|
||||
|
||||
@@ -30,10 +30,12 @@ serial_init(void)
|
||||
{
|
||||
if (CONFIG_SERIAL_BAUD_U2X) {
|
||||
UCSR0A = 1<<U2X0;
|
||||
UBRR0 = DIV_ROUND_CLOSEST(F_CPU, 8UL * CONFIG_SERIAL_BAUD) - 1UL;
|
||||
UBRR0 = DIV_ROUND_CLOSEST(
|
||||
CONFIG_CLOCK_FREQ, 8UL * CONFIG_SERIAL_BAUD) - 1UL;
|
||||
} else {
|
||||
UCSR0A = 0;
|
||||
UBRR0 = DIV_ROUND_CLOSEST(F_CPU, 16UL * CONFIG_SERIAL_BAUD) - 1UL;
|
||||
UBRR0 = DIV_ROUND_CLOSEST(
|
||||
CONFIG_CLOCK_FREQ, 16UL * CONFIG_SERIAL_BAUD) - 1UL;
|
||||
}
|
||||
|
||||
UCSR0C = (1<<UCSZ01) | (1<<UCSZ00);
|
||||
@@ -115,7 +117,7 @@ char *
|
||||
console_get_output(uint8_t len)
|
||||
{
|
||||
uint8_t tpos = readb(&transmit_pos), tmax = readb(&transmit_max);
|
||||
if (tpos == tmax) {
|
||||
if (tpos >= tmax) {
|
||||
tpos = tmax = 0;
|
||||
writeb(&transmit_max, 0);
|
||||
writeb(&transmit_pos, 0);
|
||||
@@ -126,12 +128,10 @@ console_get_output(uint8_t len)
|
||||
return NULL;
|
||||
// Disable TX irq and move buffer
|
||||
writeb(&transmit_max, 0);
|
||||
barrier();
|
||||
tpos = readb(&transmit_pos);
|
||||
tmax -= tpos;
|
||||
memmove(&transmit_buf[0], &transmit_buf[tpos], tmax);
|
||||
writeb(&transmit_pos, 0);
|
||||
barrier();
|
||||
writeb(&transmit_max, tmax);
|
||||
enable_tx_irq();
|
||||
return &transmit_buf[tmax];
|
||||
|
||||
167
src/avr/timer.c
@@ -16,14 +16,38 @@
|
||||
* Low level timer code
|
||||
****************************************************************/
|
||||
|
||||
DECL_CONSTANT(CLOCK_FREQ, F_CPU);
|
||||
DECL_CONSTANT(MCU, CONFIG_MCU);
|
||||
DECL_CONSTANT(CLOCK_FREQ, CONFIG_CLOCK_FREQ);
|
||||
|
||||
// Return the number of clock ticks for a given number of microseconds
|
||||
uint32_t
|
||||
timer_from_us(uint32_t us)
|
||||
{
|
||||
return us * (F_CPU / 1000000);
|
||||
return us * (CONFIG_CLOCK_FREQ / 1000000);
|
||||
}
|
||||
|
||||
union u32_u {
|
||||
struct { uint8_t b0, b1, b2, b3; };
|
||||
struct { uint16_t lo, hi; };
|
||||
uint32_t val;
|
||||
};
|
||||
|
||||
// Return true if time1 is before time2. Always use this function to
|
||||
// compare times as regular C comparisons can fail if the counter
|
||||
// rolls over.
|
||||
uint8_t __always_inline
|
||||
timer_is_before(uint32_t time1, uint32_t time2)
|
||||
{
|
||||
// This asm is equivalent to:
|
||||
// return (int32_t)(time1 - time2) < 0;
|
||||
// But gcc doesn't do a good job with the above, so it's hand coded.
|
||||
union u32_u utime1 = { .val = time1 };
|
||||
uint8_t f = utime1.b3;
|
||||
asm(" cp %A1, %A2\n"
|
||||
" cpc %B1, %B2\n"
|
||||
" cpc %C1, %C2\n"
|
||||
" sbc %0, %D2"
|
||||
: "+r"(f) : "r"(time1), "r"(time2));
|
||||
return (int8_t)f < 0;
|
||||
}
|
||||
|
||||
static inline uint16_t
|
||||
@@ -38,13 +62,6 @@ timer_set(uint16_t next)
|
||||
OCR1A = next;
|
||||
}
|
||||
|
||||
static inline void
|
||||
timer_set_clear(uint16_t next)
|
||||
{
|
||||
OCR1A = next;
|
||||
TIFR1 = 1<<OCF1A;
|
||||
}
|
||||
|
||||
static inline void
|
||||
timer_repeat_set(uint16_t next)
|
||||
{
|
||||
@@ -53,10 +70,16 @@ timer_repeat_set(uint16_t next)
|
||||
TIFR1 = 1<<OCF1B;
|
||||
}
|
||||
|
||||
ISR(TIMER1_COMPA_vect)
|
||||
// Reset the timer - clear settings and dispatch next timer immediately
|
||||
static void
|
||||
timer_reset(void)
|
||||
{
|
||||
sched_timer_kick();
|
||||
uint16_t now = timer_get();
|
||||
timer_set(now + 50);
|
||||
TIFR1 = 1<<OCF1A;
|
||||
timer_repeat_set(now + 50);
|
||||
}
|
||||
DECL_SHUTDOWN(timer_reset);
|
||||
|
||||
static void
|
||||
timer_init(void)
|
||||
@@ -73,8 +96,13 @@ timer_init(void)
|
||||
TCCR1A = 0;
|
||||
// Normal Mode
|
||||
TCCR1B = 1<<CS10;
|
||||
// Setup for first irq
|
||||
irqstatus_t flag = irq_save();
|
||||
timer_reset();
|
||||
TIFR1 = 1<<TOV1;
|
||||
// enable interrupt
|
||||
TIMSK1 = 1<<OCIE1A;
|
||||
irq_restore(flag);
|
||||
}
|
||||
DECL_INIT(timer_init);
|
||||
|
||||
@@ -83,20 +111,19 @@ DECL_INIT(timer_init);
|
||||
* 32bit timer wrappers
|
||||
****************************************************************/
|
||||
|
||||
static uint32_t timer_last;
|
||||
static uint16_t timer_high;
|
||||
|
||||
// Return the 32bit current time given the 16bit current time.
|
||||
static __always_inline uint32_t
|
||||
calc_time(uint32_t last, uint16_t cur)
|
||||
// Return the current time (in absolute clock ticks).
|
||||
uint32_t
|
||||
timer_read_time(void)
|
||||
{
|
||||
union u32_u16_u {
|
||||
struct { uint16_t lo, hi; };
|
||||
uint32_t val;
|
||||
} calc;
|
||||
calc.val = last;
|
||||
if (cur < calc.lo)
|
||||
irqstatus_t flag = irq_save();
|
||||
union u32_u calc;
|
||||
calc.val = timer_get();
|
||||
calc.hi = timer_high;
|
||||
if (TIFR1 & (1<<TOV1) && calc.lo < 0x8000)
|
||||
calc.hi++;
|
||||
calc.lo = cur;
|
||||
irq_restore(flag);
|
||||
return calc.val;
|
||||
}
|
||||
|
||||
@@ -104,39 +131,11 @@ calc_time(uint32_t last, uint16_t cur)
|
||||
void
|
||||
timer_periodic(void)
|
||||
{
|
||||
timer_last = calc_time(timer_last, timer_get());
|
||||
if (TIFR1 & (1<<TOV1)) {
|
||||
// Hardware timer has overflowed - update overflow counter
|
||||
TIFR1 = 1<<TOV1;
|
||||
timer_high++;
|
||||
}
|
||||
|
||||
// Return the current time (in absolute clock ticks).
|
||||
uint32_t
|
||||
timer_read_time(void)
|
||||
{
|
||||
irqstatus_t flag = irq_save();
|
||||
uint16_t cur = timer_get();
|
||||
uint32_t last = timer_last;
|
||||
irq_restore(flag);
|
||||
return calc_time(last, cur);
|
||||
}
|
||||
|
||||
#define TIMER_MIN_TICKS 100
|
||||
|
||||
// Set the next timer wake time (in absolute clock ticks). Caller
|
||||
// must disable irqs. The caller should not schedule a time more than
|
||||
// a few milliseconds in the future.
|
||||
uint8_t
|
||||
timer_set_next(uint32_t next)
|
||||
{
|
||||
uint16_t cur = timer_get();
|
||||
if ((int16_t)(OCR1A - cur) < 0 && !(TIFR1 & (1<<OCF1A)))
|
||||
// Already processing timer irqs
|
||||
try_shutdown("timer_set_next called during timer dispatch");
|
||||
uint32_t mintime = calc_time(timer_last, cur + TIMER_MIN_TICKS);
|
||||
if (sched_is_before(mintime, next)) {
|
||||
timer_set_clear(next);
|
||||
return 0;
|
||||
}
|
||||
timer_set_clear(mintime);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define TIMER_IDLE_REPEAT_TICKS 8000
|
||||
@@ -145,45 +144,51 @@ timer_set_next(uint32_t next)
|
||||
#define TIMER_MIN_TRY_TICKS 60 // 40 ticks to exit irq; 20 ticks of progress
|
||||
#define TIMER_DEFER_REPEAT_TICKS 200
|
||||
|
||||
// Similar to timer_set_next(), but wait for the given time if it is
|
||||
// in the near future.
|
||||
uint8_t
|
||||
timer_try_set_next(uint32_t target)
|
||||
// Hardware timer IRQ handler - dispatch software timers
|
||||
ISR(TIMER1_COMPA_vect)
|
||||
{
|
||||
uint16_t next = target, now = timer_get();
|
||||
int16_t diff = next - now;
|
||||
if (diff > TIMER_MIN_TRY_TICKS)
|
||||
// Schedule next timer normally.
|
||||
uint16_t next;
|
||||
for (;;) {
|
||||
// Run the next software timer
|
||||
next = sched_timer_dispatch();
|
||||
|
||||
int16_t diff = timer_get() - next;
|
||||
if (likely(diff >= 0)) {
|
||||
// Another timer is pending - briefly allow irqs to fire
|
||||
irq_enable();
|
||||
if (unlikely(TIFR1 & (1<<OCF1B)))
|
||||
// Too many repeat timers - must exit irq handler
|
||||
goto force_defer;
|
||||
irq_disable();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (likely(diff <= -TIMER_MIN_TRY_TICKS))
|
||||
// Schedule next timer normally
|
||||
goto done;
|
||||
|
||||
// Next timer is in the past or near future - can't reschedule to it
|
||||
if (!(TIFR1 & (1<<OCF1B))) {
|
||||
// Can run more timers from this irq; briefly allow irqs
|
||||
// Next timer in very near future - wait for it to be ready
|
||||
do {
|
||||
irq_enable();
|
||||
asm("nop");
|
||||
irq_disable();
|
||||
|
||||
while (diff >= 0) {
|
||||
// Next timer is in the near future - wait for time to occur
|
||||
now = timer_get();
|
||||
irq_enable();
|
||||
diff = next - now;
|
||||
if (unlikely(TIFR1 & (1<<OCF1B)))
|
||||
goto force_defer;
|
||||
irq_disable();
|
||||
diff = timer_get() - next;
|
||||
} while (diff < 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (diff < (int16_t)(-timer_from_us(1000)))
|
||||
goto fail;
|
||||
|
||||
force_defer:
|
||||
// Too many repeat timers - force a pause so tasks aren't starved
|
||||
irq_disable();
|
||||
uint16_t now = timer_get();
|
||||
if ((int16_t)(next - now) < (int16_t)(-timer_from_us(1000)))
|
||||
shutdown("Rescheduled timer in the past");
|
||||
timer_repeat_set(now + TIMER_REPEAT_TICKS);
|
||||
next = now + TIMER_DEFER_REPEAT_TICKS;
|
||||
|
||||
done:
|
||||
timer_set(next);
|
||||
return 1;
|
||||
fail:
|
||||
shutdown("Rescheduled timer in the past");
|
||||
return;
|
||||
}
|
||||
|
||||
// Periodic background task that temporarily boosts priority of
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <avr/interrupt.h> // WDT_vect
|
||||
#include <avr/wdt.h> // wdt_enable
|
||||
#include "command.h" // shutdown
|
||||
#include "irq.h" // irq_disable
|
||||
#include "sched.h" // DECL_TASK
|
||||
|
||||
static uint8_t watchdog_shutdown;
|
||||
@@ -22,7 +23,7 @@ watchdog_reset(void)
|
||||
{
|
||||
wdt_reset();
|
||||
if (watchdog_shutdown) {
|
||||
WDTCSR |= 1<<WDIE;
|
||||
WDTCSR = 1<<WDIE;
|
||||
watchdog_shutdown = 0;
|
||||
}
|
||||
}
|
||||
@@ -33,6 +34,25 @@ watchdog_init(void)
|
||||
{
|
||||
// 0.5s timeout, interrupt and system reset
|
||||
wdt_enable(WDTO_500MS);
|
||||
WDTCSR |= 1<<WDIE;
|
||||
WDTCSR = 1<<WDIE;
|
||||
}
|
||||
DECL_INIT(watchdog_init);
|
||||
|
||||
// Very early reset of the watchdog
|
||||
void __attribute__((naked)) __visible __section(".init3")
|
||||
watchdog_early_init(void)
|
||||
{
|
||||
MCUSR = 0;
|
||||
wdt_disable();
|
||||
}
|
||||
|
||||
// Support reset on AVR via the watchdog timer
|
||||
void
|
||||
command_reset(uint32_t *args)
|
||||
{
|
||||
irq_disable();
|
||||
wdt_enable(WDTO_15MS);
|
||||
for (;;)
|
||||
;
|
||||
}
|
||||
DECL_COMMAND_FLAGS(command_reset, HF_IN_SHUTDOWN, "reset");
|
||||
|
||||
180
src/basecmd.c
@@ -5,8 +5,8 @@
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
#include <stdlib.h> // malloc
|
||||
#include <string.h> // memcpy
|
||||
#include "basecmd.h" // lookup_oid
|
||||
#include <string.h> // memset
|
||||
#include "basecmd.h" // oid_lookup
|
||||
#include "board/irq.h" // irq_save
|
||||
#include "board/misc.h" // alloc_maxsize
|
||||
#include "command.h" // DECL_COMMAND
|
||||
@@ -17,26 +17,53 @@
|
||||
* Move queue
|
||||
****************************************************************/
|
||||
|
||||
static struct move *move_list, *move_free_list;
|
||||
static uint16_t move_count;
|
||||
struct move_freed {
|
||||
struct move_freed *next;
|
||||
};
|
||||
|
||||
void
|
||||
move_free(struct move *m)
|
||||
static struct move_freed *move_free_list;
|
||||
static void *move_list;
|
||||
static uint16_t move_count;
|
||||
static uint8_t move_item_size;
|
||||
|
||||
// Is the config and move queue finalized?
|
||||
static int
|
||||
is_finalized(void)
|
||||
{
|
||||
m->next = move_free_list;
|
||||
move_free_list = m;
|
||||
return !!move_count;
|
||||
}
|
||||
|
||||
struct move *
|
||||
// Free previously allocated storage from move_alloc(). Caller must
|
||||
// disable irqs.
|
||||
void
|
||||
move_free(void *m)
|
||||
{
|
||||
struct move_freed *mf = m;
|
||||
mf->next = move_free_list;
|
||||
move_free_list = mf;
|
||||
}
|
||||
|
||||
// Allocate runtime storage
|
||||
void *
|
||||
move_alloc(void)
|
||||
{
|
||||
irqstatus_t flag = irq_save();
|
||||
struct move *m = move_free_list;
|
||||
if (!m)
|
||||
struct move_freed *mf = move_free_list;
|
||||
if (!mf)
|
||||
shutdown("Move queue empty");
|
||||
move_free_list = m->next;
|
||||
move_free_list = mf->next;
|
||||
irq_restore(flag);
|
||||
return m;
|
||||
return mf;
|
||||
}
|
||||
|
||||
// Request minimum size of runtime allocations returned by move_alloc()
|
||||
void
|
||||
move_request_size(int size)
|
||||
{
|
||||
if (size > UINT8_MAX || is_finalized())
|
||||
shutdown("Invalid move request size");
|
||||
if (size > move_item_size)
|
||||
move_item_size = size;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -46,13 +73,28 @@ move_reset(void)
|
||||
return;
|
||||
// Add everything in move_list to the free list.
|
||||
uint32_t i;
|
||||
for (i=0; i<move_count-1; i++)
|
||||
move_list[i].next = &move_list[i+1];
|
||||
move_list[move_count-1].next = NULL;
|
||||
move_free_list = &move_list[0];
|
||||
for (i=0; i<move_count-1; i++) {
|
||||
struct move_freed *mf = move_list + i*move_item_size;
|
||||
mf->next = move_list + (i + 1)*move_item_size;
|
||||
}
|
||||
struct move_freed *mf = move_list + (move_count - 1)*move_item_size;
|
||||
mf->next = NULL;
|
||||
move_free_list = move_list;
|
||||
}
|
||||
DECL_SHUTDOWN(move_reset);
|
||||
|
||||
static void
|
||||
move_finalize(void)
|
||||
{
|
||||
move_request_size(sizeof(*move_free_list));
|
||||
uint16_t count = alloc_maxsize(move_item_size*1024) / move_item_size;
|
||||
move_list = malloc(count * move_item_size);
|
||||
if (!count || !move_list)
|
||||
shutdown("move queue malloc failed");
|
||||
move_count = count;
|
||||
move_reset();
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* Generic object ids (oid)
|
||||
@@ -63,45 +105,43 @@ struct oid_s {
|
||||
};
|
||||
|
||||
static struct oid_s *oids;
|
||||
static uint8_t num_oid;
|
||||
static uint32_t config_crc;
|
||||
static uint8_t config_finalized;
|
||||
static uint8_t oid_count;
|
||||
|
||||
void *
|
||||
lookup_oid(uint8_t oid, void *type)
|
||||
oid_lookup(uint8_t oid, void *type)
|
||||
{
|
||||
if (oid >= num_oid || type != oids[oid].type)
|
||||
if (oid >= oid_count || type != oids[oid].type)
|
||||
shutdown("Invalid oid type");
|
||||
return oids[oid].data;
|
||||
}
|
||||
|
||||
static void
|
||||
assign_oid(uint8_t oid, void *type, void *data)
|
||||
oid_assign(uint8_t oid, void *type, void *data)
|
||||
{
|
||||
if (oid >= num_oid || oids[oid].type || config_finalized)
|
||||
if (oid >= oid_count || oids[oid].type || is_finalized())
|
||||
shutdown("Can't assign oid");
|
||||
oids[oid].type = type;
|
||||
oids[oid].data = data;
|
||||
}
|
||||
|
||||
void *
|
||||
alloc_oid(uint8_t oid, void *type, uint16_t size)
|
||||
oid_alloc(uint8_t oid, void *type, uint16_t size)
|
||||
{
|
||||
void *data = malloc(size);
|
||||
if (!data)
|
||||
shutdown("malloc failed");
|
||||
memset(data, 0, size);
|
||||
assign_oid(oid, type, data);
|
||||
oid_assign(oid, type, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
void *
|
||||
next_oid(uint8_t *i, void *type)
|
||||
oid_next(uint8_t *i, void *type)
|
||||
{
|
||||
uint8_t oid = *i;
|
||||
for (;;) {
|
||||
oid++;
|
||||
if (oid >= num_oid)
|
||||
if (oid >= oid_count)
|
||||
return NULL;
|
||||
if (oids[oid].type == type) {
|
||||
*i = oid;
|
||||
@@ -120,31 +160,32 @@ command_allocate_oids(uint32_t *args)
|
||||
if (!oids)
|
||||
shutdown("malloc failed");
|
||||
memset(oids, 0, sizeof(oids[0]) * count);
|
||||
num_oid = count;
|
||||
oid_count = count;
|
||||
}
|
||||
DECL_COMMAND(command_allocate_oids, "allocate_oids count=%c");
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* Config CRC
|
||||
****************************************************************/
|
||||
|
||||
static uint32_t config_crc;
|
||||
|
||||
void
|
||||
command_get_config(uint32_t *args)
|
||||
{
|
||||
sendf("config is_config=%c crc=%u move_count=%hu"
|
||||
, config_finalized, config_crc, move_count);
|
||||
, is_finalized(), config_crc, move_count);
|
||||
}
|
||||
DECL_COMMAND_FLAGS(command_get_config, HF_IN_SHUTDOWN, "get_config");
|
||||
|
||||
void
|
||||
command_finalize_config(uint32_t *args)
|
||||
{
|
||||
if (!oids || config_finalized)
|
||||
if (!oids || is_finalized())
|
||||
shutdown("Can't finalize");
|
||||
uint16_t count = alloc_maxsize(sizeof(*move_list)*1024) / sizeof(*move_list);
|
||||
move_list = malloc(count * sizeof(*move_list));
|
||||
if (!count || !move_list)
|
||||
shutdown("malloc failed");
|
||||
move_count = count;
|
||||
move_reset();
|
||||
move_finalize();
|
||||
config_crc = args[0];
|
||||
config_finalized = 1;
|
||||
command_get_config(NULL);
|
||||
}
|
||||
DECL_COMMAND(command_finalize_config, "finalize_config crc=%u");
|
||||
@@ -168,7 +209,7 @@ command_start_group(uint32_t *args)
|
||||
sched_del_timer(&group_timer);
|
||||
group_timer.func = group_end_event;
|
||||
group_timer.waketime = args[0];
|
||||
sched_timer(&group_timer);
|
||||
sched_add_timer(&group_timer);
|
||||
}
|
||||
DECL_COMMAND(command_start_group, "start_group clock=%u");
|
||||
|
||||
@@ -187,28 +228,51 @@ DECL_COMMAND(command_end_group, "end_group");
|
||||
void
|
||||
command_get_status(uint32_t *args)
|
||||
{
|
||||
sendf("status clock=%u status=%c", sched_read_time(), sched_is_shutdown());
|
||||
sendf("status clock=%u status=%c", timer_read_time(), sched_is_shutdown());
|
||||
}
|
||||
DECL_COMMAND_FLAGS(command_get_status, HF_IN_SHUTDOWN, "get_status");
|
||||
|
||||
static uint32_t stats_send_time, stats_send_time_high;
|
||||
|
||||
void
|
||||
command_get_uptime(uint32_t *args)
|
||||
{
|
||||
uint32_t cur = timer_read_time();
|
||||
uint32_t high = stats_send_time_high + (cur < stats_send_time);
|
||||
sendf("uptime high=%u clock=%u", high, cur);
|
||||
}
|
||||
DECL_COMMAND_FLAGS(command_get_uptime, HF_IN_SHUTDOWN, "get_uptime");
|
||||
|
||||
#define SUMSQ_BASE 256
|
||||
DECL_CONSTANT(STATS_SUMSQ_BASE, SUMSQ_BASE);
|
||||
|
||||
static void
|
||||
stats_task(void)
|
||||
{
|
||||
static uint32_t last, count, sumsq;
|
||||
uint32_t cur = sched_read_time();
|
||||
uint32_t diff = (cur - last) >> 8;
|
||||
uint32_t cur = timer_read_time();
|
||||
uint32_t diff = cur - last;
|
||||
last = cur;
|
||||
count++;
|
||||
uint32_t nextsumsq = sumsq + diff*diff;
|
||||
// Calculate sum of diff^2 - be careful of integer overflow
|
||||
uint32_t nextsumsq;
|
||||
if (diff <= 0xffff) {
|
||||
nextsumsq = sumsq + DIV_ROUND_UP(diff * diff, SUMSQ_BASE);
|
||||
} else if (diff <= 0xfffff) {
|
||||
nextsumsq = sumsq + DIV_ROUND_UP(diff, SUMSQ_BASE) * diff;
|
||||
} else {
|
||||
nextsumsq = 0xffffffff;
|
||||
}
|
||||
if (nextsumsq < sumsq)
|
||||
nextsumsq = 0xffffffff;
|
||||
sumsq = nextsumsq;
|
||||
|
||||
static uint32_t prev;
|
||||
if (sched_is_before(cur, prev + sched_from_us(5000000)))
|
||||
if (timer_is_before(cur, stats_send_time + timer_from_us(5000000)))
|
||||
return;
|
||||
sendf("stats count=%u sum=%u sumsq=%u", count, cur - prev, sumsq);
|
||||
prev = cur;
|
||||
sendf("stats count=%u sum=%u sumsq=%u", count, cur - stats_send_time, sumsq);
|
||||
if (cur < stats_send_time)
|
||||
stats_send_time_high++;
|
||||
stats_send_time = cur;
|
||||
count = sumsq = 0;
|
||||
}
|
||||
DECL_TASK(stats_task);
|
||||
@@ -258,18 +322,26 @@ command_debug_write16(uint32_t *args)
|
||||
DECL_COMMAND_FLAGS(command_debug_write16, HF_IN_SHUTDOWN,
|
||||
"debug_write16 addr=%u val=%u");
|
||||
|
||||
void
|
||||
command_debug_ping(uint32_t *args)
|
||||
{
|
||||
uint8_t len = args[0];
|
||||
char *data = (void*)(size_t)args[1];
|
||||
sendf("pong data=%*s", len, data);
|
||||
}
|
||||
DECL_COMMAND_FLAGS(command_debug_ping, HF_IN_SHUTDOWN, "debug_ping data=%*s");
|
||||
|
||||
void
|
||||
command_debug_nop(uint32_t *args)
|
||||
{
|
||||
}
|
||||
DECL_COMMAND_FLAGS(command_debug_nop, HF_IN_SHUTDOWN, "debug_nop data=%*s");
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* Misc commands
|
||||
****************************************************************/
|
||||
|
||||
void
|
||||
command_reset(uint32_t *args)
|
||||
{
|
||||
// XXX - implement reset
|
||||
}
|
||||
DECL_COMMAND_FLAGS(command_reset, HF_IN_SHUTDOWN, "msg_reset");
|
||||
|
||||
void
|
||||
command_emergency_stop(uint32_t *args)
|
||||
{
|
||||
|
||||
@@ -3,21 +3,14 @@
|
||||
|
||||
#include <stdint.h> // uint8_t
|
||||
|
||||
struct move {
|
||||
uint32_t interval;
|
||||
int16_t add;
|
||||
uint16_t count;
|
||||
struct move *next;
|
||||
uint8_t flags;
|
||||
};
|
||||
|
||||
void move_free(struct move *m);
|
||||
struct move *move_alloc(void);
|
||||
void *lookup_oid(uint8_t oid, void *type);
|
||||
void *alloc_oid(uint8_t oid, void *type, uint16_t size);
|
||||
void *next_oid(uint8_t *i, void *type);
|
||||
void move_free(void *m);
|
||||
void *move_alloc(void);
|
||||
void move_request_size(int size);
|
||||
void *oid_lookup(uint8_t oid, void *type);
|
||||
void *oid_alloc(uint8_t oid, void *type, uint16_t size);
|
||||
void *oid_next(uint8_t *i, void *type);
|
||||
|
||||
#define foreach_oid(pos,data,oidtype) \
|
||||
for (pos=-1; (data=next_oid(&pos, oidtype)); )
|
||||
for (pos=-1; (data=oid_next(&pos, oidtype)); )
|
||||
|
||||
#endif // basecmd.h
|
||||
|
||||
@@ -4,12 +4,9 @@
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
#include <ctype.h> // isspace
|
||||
#include <stdarg.h> // va_start
|
||||
#include <stdio.h> // vsnprintf
|
||||
#include <stdlib.h> // strtod
|
||||
#include <string.h> // strcasecmp
|
||||
#include "board/irq.h" // irq_disable
|
||||
#include <string.h> // memcpy
|
||||
#include "board/io.h" // readb
|
||||
#include "board/misc.h" // crc16_ccitt
|
||||
#include "board/pgm.h" // READP
|
||||
#include "command.h" // output_P
|
||||
@@ -110,15 +107,23 @@ error:
|
||||
shutdown("Command parser error");
|
||||
}
|
||||
|
||||
static uint8_t in_sendf;
|
||||
|
||||
// Encode a message and transmit it
|
||||
void
|
||||
_sendf(uint8_t parserid, ...)
|
||||
{
|
||||
if (readb(&in_sendf))
|
||||
// This sendf call was made from an irq handler while the main
|
||||
// code was already in sendf - just drop this sendf request.
|
||||
return;
|
||||
writeb(&in_sendf, 1);
|
||||
|
||||
const struct command_encoder *cp = &command_encoders[parserid];
|
||||
uint8_t max_size = READP(cp->max_size);
|
||||
char *buf = console_get_output(max_size + MESSAGE_MIN);
|
||||
if (!buf)
|
||||
return;
|
||||
goto done;
|
||||
char *p = &buf[MESSAGE_HEADER_SIZE];
|
||||
if (max_size) {
|
||||
char *maxend = &p[max_size];
|
||||
@@ -140,7 +145,10 @@ _sendf(uint8_t parserid, ...)
|
||||
case PT_int16:
|
||||
case PT_byte:
|
||||
if (t >= PT_uint16)
|
||||
v = va_arg(args, int) & 0xffff;
|
||||
if (t == PT_int16)
|
||||
v = (int32_t)va_arg(args, int);
|
||||
else
|
||||
v = va_arg(args, unsigned int);
|
||||
else
|
||||
v = va_arg(args, uint32_t);
|
||||
p = encode_int(p, v);
|
||||
@@ -182,11 +190,20 @@ _sendf(uint8_t parserid, ...)
|
||||
*p++ = crc;
|
||||
*p++ = MESSAGE_SYNC;
|
||||
console_push_output(msglen);
|
||||
done:
|
||||
writeb(&in_sendf, 0);
|
||||
return;
|
||||
error:
|
||||
shutdown("Message encode error");
|
||||
}
|
||||
|
||||
static void
|
||||
sendf_shutdown(void)
|
||||
{
|
||||
writeb(&in_sendf, 0);
|
||||
}
|
||||
DECL_SHUTDOWN(sendf_shutdown);
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* Command routing
|
||||
@@ -293,6 +310,5 @@ command_task(void)
|
||||
func(args);
|
||||
}
|
||||
console_pop_input(msglen);
|
||||
return;
|
||||
}
|
||||
DECL_TASK(command_task);
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
#include <stddef.h> // offsetof
|
||||
#include "basecmd.h" // alloc_oid
|
||||
#include "basecmd.h" // oid_alloc
|
||||
#include "board/gpio.h" // struct gpio
|
||||
#include "board/irq.h" // irq_disable
|
||||
#include "command.h" // DECL_COMMAND
|
||||
@@ -15,12 +14,22 @@
|
||||
struct end_stop {
|
||||
struct timer time;
|
||||
uint32_t rest_time;
|
||||
struct stepper *stepper;
|
||||
struct gpio_in pin;
|
||||
uint8_t pin_value, flags;
|
||||
uint8_t flags, stepper_count;
|
||||
struct stepper *steppers[0];
|
||||
};
|
||||
|
||||
enum { ESF_HOMING=1, ESF_REPORT=2 };
|
||||
enum { ESF_PIN_HIGH=1<<0, ESF_HOMING=1<<1, ESF_REPORT=1<<2 };
|
||||
|
||||
static void noinline
|
||||
stop_steppers(struct end_stop *e)
|
||||
{
|
||||
e->flags = ESF_REPORT;
|
||||
uint8_t count = e->stepper_count;
|
||||
while (count--)
|
||||
if (e->steppers[count])
|
||||
stepper_stop(e->steppers[count]);
|
||||
}
|
||||
|
||||
// Timer callback for an end stop
|
||||
static uint_fast8_t
|
||||
@@ -28,33 +37,46 @@ end_stop_event(struct timer *t)
|
||||
{
|
||||
struct end_stop *e = container_of(t, struct end_stop, time);
|
||||
uint8_t val = gpio_in_read(e->pin);
|
||||
if (val != e->pin_value) {
|
||||
if ((val ? ~e->flags : e->flags) & ESF_PIN_HIGH) {
|
||||
// No match - reschedule for the next attempt
|
||||
e->time.waketime += e->rest_time;
|
||||
return SF_RESCHEDULE;
|
||||
}
|
||||
// Stop stepper
|
||||
e->flags = ESF_REPORT;
|
||||
stepper_stop(e->stepper);
|
||||
stop_steppers(e);
|
||||
return SF_DONE;
|
||||
}
|
||||
|
||||
void
|
||||
command_config_end_stop(uint32_t *args)
|
||||
{
|
||||
struct end_stop *e = alloc_oid(args[0], command_config_end_stop, sizeof(*e));
|
||||
struct stepper *s = lookup_oid(args[3], command_config_stepper);
|
||||
uint8_t stepper_count = args[3];
|
||||
struct end_stop *e = oid_alloc(
|
||||
args[0], command_config_end_stop
|
||||
, sizeof(*e) + sizeof(e->steppers[0]) * stepper_count);
|
||||
e->time.func = end_stop_event;
|
||||
e->stepper = s;
|
||||
e->pin = gpio_in_setup(args[1], args[2]);
|
||||
e->stepper_count = stepper_count;
|
||||
}
|
||||
DECL_COMMAND(command_config_end_stop,
|
||||
"config_end_stop oid=%c pin=%c pull_up=%c stepper_oid=%c");
|
||||
"config_end_stop oid=%c pin=%c pull_up=%c stepper_count=%c");
|
||||
|
||||
void
|
||||
command_end_stop_set_stepper(uint32_t *args)
|
||||
{
|
||||
struct end_stop *e = oid_lookup(args[0], command_config_end_stop);
|
||||
uint8_t pos = args[1];
|
||||
if (pos >= e->stepper_count)
|
||||
shutdown("Set stepper past maximum stepper count");
|
||||
e->steppers[pos] = stepper_oid_lookup(args[2]);
|
||||
}
|
||||
DECL_COMMAND(command_end_stop_set_stepper,
|
||||
"end_stop_set_stepper oid=%c pos=%c stepper_oid=%c");
|
||||
|
||||
// Home an axis
|
||||
void
|
||||
command_end_stop_home(uint32_t *args)
|
||||
{
|
||||
struct end_stop *e = lookup_oid(args[0], command_config_end_stop);
|
||||
struct end_stop *e = oid_lookup(args[0], command_config_end_stop);
|
||||
sched_del_timer(&e->time);
|
||||
e->time.waketime = args[1];
|
||||
e->rest_time = args[2];
|
||||
@@ -63,9 +85,8 @@ command_end_stop_home(uint32_t *args)
|
||||
e->flags = 0;
|
||||
return;
|
||||
}
|
||||
e->pin_value = args[3];
|
||||
e->flags = ESF_HOMING;
|
||||
sched_timer(&e->time);
|
||||
e->flags = ESF_HOMING | (args[3] ? ESF_PIN_HIGH : 0);
|
||||
sched_add_timer(&e->time);
|
||||
}
|
||||
DECL_COMMAND(command_end_stop_home,
|
||||
"end_stop_home oid=%c clock=%u rest_ticks=%u pin_value=%c");
|
||||
@@ -74,21 +95,19 @@ static void
|
||||
end_stop_report(uint8_t oid, struct end_stop *e)
|
||||
{
|
||||
irq_disable();
|
||||
uint32_t position = stepper_get_position(e->stepper);
|
||||
uint8_t eflags = e->flags;
|
||||
e->flags &= ~ESF_REPORT;
|
||||
irq_enable();
|
||||
|
||||
sendf("end_stop_state oid=%c homing=%c pin=%c pos=%i"
|
||||
, oid, !!(eflags & ESF_HOMING), gpio_in_read(e->pin)
|
||||
, position - STEPPER_POSITION_BIAS);
|
||||
sendf("end_stop_state oid=%c homing=%c pin=%c"
|
||||
, oid, !!(eflags & ESF_HOMING), gpio_in_read(e->pin));
|
||||
}
|
||||
|
||||
void
|
||||
command_end_stop_query(uint32_t *args)
|
||||
{
|
||||
uint8_t oid = args[0];
|
||||
struct end_stop *e = lookup_oid(oid, command_config_end_stop);
|
||||
struct end_stop *e = oid_lookup(oid, command_config_end_stop);
|
||||
end_stop_report(oid, e);
|
||||
}
|
||||
DECL_COMMAND(command_end_stop_query, "end_stop_query oid=%c");
|
||||
|
||||
58
src/generic/armcm_irq.c
Normal file
@@ -0,0 +1,58 @@
|
||||
// Definitions for irq enable/disable on ARM Cortex-M processors
|
||||
//
|
||||
// Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
#include "irq.h" // irqstatus_t
|
||||
#include "sched.h" // DECL_SHUTDOWN
|
||||
|
||||
void
|
||||
irq_disable(void)
|
||||
{
|
||||
asm volatile("cpsid i" ::: "memory");
|
||||
}
|
||||
|
||||
void
|
||||
irq_enable(void)
|
||||
{
|
||||
asm volatile("cpsie i" ::: "memory");
|
||||
}
|
||||
|
||||
irqstatus_t
|
||||
irq_save(void)
|
||||
{
|
||||
irqstatus_t flag;
|
||||
asm volatile("mrs %0, primask" : "=r" (flag) :: "memory");
|
||||
irq_disable();
|
||||
return flag;
|
||||
}
|
||||
|
||||
void
|
||||
irq_restore(irqstatus_t flag)
|
||||
{
|
||||
asm volatile("msr primask, %0" :: "r" (flag) : "memory");
|
||||
}
|
||||
|
||||
// Clear the active irq if a shutdown happened in an irq handler
|
||||
static void
|
||||
clear_active_irq(void)
|
||||
{
|
||||
uint32_t psr;
|
||||
asm volatile("mrs %0, psr" : "=r" (psr));
|
||||
if (!(psr & 0x1ff))
|
||||
// Shutdown did not occur in an irq - nothing to do.
|
||||
return;
|
||||
// Clear active irq status
|
||||
psr = 1<<24; // T-bit
|
||||
uint32_t temp;
|
||||
asm volatile(
|
||||
" push { %1 }\n"
|
||||
" adr %0, 1f\n"
|
||||
" push { %0 }\n"
|
||||
" push { r0, r1, r2, r3, r12, lr }\n"
|
||||
" bx %2\n"
|
||||
"1:\n"
|
||||
: "=&r"(temp) : "r"(psr), "r"(0xfffffff9) : "cc");
|
||||
}
|
||||
DECL_SHUTDOWN(clear_active_irq);
|
||||
@@ -2,24 +2,34 @@
|
||||
#define __GENERIC_IO_H
|
||||
|
||||
#include <stdint.h> // uint32_t
|
||||
#include "compiler.h" // barrier
|
||||
|
||||
static inline void writel(void *addr, uint32_t val) {
|
||||
barrier();
|
||||
*(volatile uint32_t *)addr = val;
|
||||
}
|
||||
static inline void writew(void *addr, uint16_t val) {
|
||||
barrier();
|
||||
*(volatile uint16_t *)addr = val;
|
||||
}
|
||||
static inline void writeb(void *addr, uint8_t val) {
|
||||
barrier();
|
||||
*(volatile uint8_t *)addr = val;
|
||||
}
|
||||
static inline uint32_t readl(const void *addr) {
|
||||
return *(volatile const uint32_t *)addr;
|
||||
uint32_t val = *(volatile const uint32_t *)addr;
|
||||
barrier();
|
||||
return val;
|
||||
}
|
||||
static inline uint16_t readw(const void *addr) {
|
||||
return *(volatile const uint16_t *)addr;
|
||||
uint16_t val = *(volatile const uint16_t *)addr;
|
||||
barrier();
|
||||
return val;
|
||||
}
|
||||
static inline uint8_t readb(const void *addr) {
|
||||
return *(volatile const uint8_t *)addr;
|
||||
uint8_t val = *(volatile const uint8_t *)addr;
|
||||
barrier();
|
||||
return val;
|
||||
}
|
||||
|
||||
#endif // io.h
|
||||
|
||||
@@ -10,10 +10,9 @@ char *console_get_output(uint8_t len);
|
||||
void console_push_output(uint8_t len);
|
||||
|
||||
uint32_t timer_from_us(uint32_t us);
|
||||
void timer_periodic(void);
|
||||
uint8_t timer_is_before(uint32_t time1, uint32_t time2);
|
||||
uint32_t timer_read_time(void);
|
||||
uint8_t timer_set_next(uint32_t next);
|
||||
uint8_t timer_try_set_next(uint32_t next);
|
||||
void timer_periodic(void);
|
||||
|
||||
size_t alloc_maxsize(size_t reqsize);
|
||||
|
||||
|
||||
99
src/generic/timer_irq.c
Normal file
@@ -0,0 +1,99 @@
|
||||
// Generic interrupt based timer helper functions
|
||||
//
|
||||
// Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
#include "autoconf.h" // CONFIG_CLOCK_FREQ
|
||||
#include "board/irq.h" // irq_disable
|
||||
#include "board/misc.h" // timer_from_us
|
||||
#include "board/timer_irq.h" // timer_dispatch_many
|
||||
#include "command.h" // shutdown
|
||||
#include "sched.h" // sched_timer_kick
|
||||
|
||||
DECL_CONSTANT(CLOCK_FREQ, CONFIG_CLOCK_FREQ);
|
||||
|
||||
// Return the number of clock ticks for a given number of microseconds
|
||||
uint32_t
|
||||
timer_from_us(uint32_t us)
|
||||
{
|
||||
return us * (CONFIG_CLOCK_FREQ / 1000000);
|
||||
}
|
||||
|
||||
// Return true if time1 is before time2. Always use this function to
|
||||
// compare times as regular C comparisons can fail if the counter
|
||||
// rolls over.
|
||||
uint8_t
|
||||
timer_is_before(uint32_t time1, uint32_t time2)
|
||||
{
|
||||
return (int32_t)(time1 - time2) < 0;
|
||||
}
|
||||
|
||||
// Called by main code once every millisecond. (IRQs disabled.)
|
||||
void
|
||||
timer_periodic(void)
|
||||
{
|
||||
}
|
||||
|
||||
static uint32_t timer_repeat_until;
|
||||
#define TIMER_IDLE_REPEAT_TICKS timer_from_us(500)
|
||||
#define TIMER_REPEAT_TICKS timer_from_us(100)
|
||||
|
||||
#define TIMER_MIN_TRY_TICKS timer_from_us(1)
|
||||
#define TIMER_DEFER_REPEAT_TICKS timer_from_us(5)
|
||||
|
||||
// Reschedule timers after a brief pause to prevent task starvation
|
||||
static uint32_t noinline
|
||||
force_defer(uint32_t next)
|
||||
{
|
||||
uint32_t now = timer_read_time();
|
||||
if (timer_is_before(next + timer_from_us(1000), now))
|
||||
shutdown("Rescheduled timer in the past");
|
||||
timer_repeat_until = now + TIMER_REPEAT_TICKS;
|
||||
return now + TIMER_DEFER_REPEAT_TICKS;
|
||||
}
|
||||
|
||||
// Invoke timers - called from board irq code.
|
||||
uint32_t
|
||||
timer_dispatch_many(void)
|
||||
{
|
||||
uint32_t tru = timer_repeat_until;
|
||||
for (;;) {
|
||||
// Run the next software timer
|
||||
uint32_t next = sched_timer_dispatch();
|
||||
|
||||
uint32_t now = timer_read_time();
|
||||
int32_t diff = next - now;
|
||||
if (diff > (int32_t)TIMER_MIN_TRY_TICKS)
|
||||
// Schedule next timer normally.
|
||||
return next;
|
||||
|
||||
if (unlikely(timer_is_before(tru, now)))
|
||||
// Too many repeat timers from a single interrupt - force a pause
|
||||
return force_defer(next);
|
||||
|
||||
// Next timer in the past or near future - wait for it to be ready
|
||||
irq_enable();
|
||||
while (unlikely(diff > 0))
|
||||
diff = next - timer_read_time();
|
||||
irq_disable();
|
||||
}
|
||||
}
|
||||
|
||||
// Periodic background task that temporarily boosts priority of
|
||||
// timers. This helps prioritize timers when tasks are idling.
|
||||
static void
|
||||
timer_task(void)
|
||||
{
|
||||
irq_disable();
|
||||
timer_repeat_until = timer_read_time() + TIMER_IDLE_REPEAT_TICKS;
|
||||
irq_enable();
|
||||
}
|
||||
DECL_TASK(timer_task);
|
||||
|
||||
static void
|
||||
timer_irq_shutdown(void)
|
||||
{
|
||||
timer_repeat_until = timer_read_time() + TIMER_IDLE_REPEAT_TICKS;
|
||||
}
|
||||
DECL_SHUTDOWN(timer_irq_shutdown);
|
||||
6
src/generic/timer_irq.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef __GENERIC_TIMER_IRQ_H
|
||||
#define __GENERIC_TIMER_IRQ_H
|
||||
|
||||
uint32_t timer_dispatch_many(void);
|
||||
|
||||
#endif // timer_irq.h
|
||||
@@ -4,11 +4,12 @@
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
#include "basecmd.h" // alloc_oid
|
||||
#include "basecmd.h" // oid_alloc
|
||||
#include "board/gpio.h" // struct gpio_out
|
||||
#include "board/irq.h" // irq_disable
|
||||
#include "board/misc.h" // timer_is_before
|
||||
#include "command.h" // DECL_COMMAND
|
||||
#include "sched.h" // sched_timer
|
||||
#include "sched.h" // sched_add_timer
|
||||
|
||||
|
||||
/****************************************************************
|
||||
@@ -43,7 +44,7 @@ digital_out_event(struct timer *timer)
|
||||
void
|
||||
command_config_digital_out(uint32_t *args)
|
||||
{
|
||||
struct digital_out_s *d = alloc_oid(args[0], command_config_digital_out
|
||||
struct digital_out_s *d = oid_alloc(args[0], command_config_digital_out
|
||||
, sizeof(*d));
|
||||
d->default_value = args[2];
|
||||
d->pin = gpio_out_setup(args[1], d->default_value);
|
||||
@@ -56,12 +57,12 @@ DECL_COMMAND(command_config_digital_out,
|
||||
void
|
||||
command_schedule_digital_out(uint32_t *args)
|
||||
{
|
||||
struct digital_out_s *d = lookup_oid(args[0], command_config_digital_out);
|
||||
struct digital_out_s *d = oid_lookup(args[0], command_config_digital_out);
|
||||
sched_del_timer(&d->timer);
|
||||
d->timer.func = digital_out_event;
|
||||
d->timer.waketime = args[1];
|
||||
d->value = args[2];
|
||||
sched_timer(&d->timer);
|
||||
sched_add_timer(&d->timer);
|
||||
}
|
||||
DECL_COMMAND(command_schedule_digital_out,
|
||||
"schedule_digital_out oid=%c clock=%u value=%c");
|
||||
@@ -117,7 +118,7 @@ soft_pwm_toggle_event(struct timer *timer)
|
||||
waketime += s->on_duration;
|
||||
else
|
||||
waketime += s->off_duration;
|
||||
if (s->flags & SPF_CHECK_END && !sched_is_before(waketime, s->end_time)) {
|
||||
if (s->flags & SPF_CHECK_END && !timer_is_before(waketime, s->end_time)) {
|
||||
// End of normal pulsing - next event loads new pwm settings
|
||||
s->timer.func = soft_pwm_load_event;
|
||||
waketime = s->end_time;
|
||||
@@ -155,7 +156,7 @@ soft_pwm_load_event(struct timer *timer)
|
||||
void
|
||||
command_config_soft_pwm_out(uint32_t *args)
|
||||
{
|
||||
struct soft_pwm_s *s = alloc_oid(args[0], command_config_soft_pwm_out
|
||||
struct soft_pwm_s *s = oid_alloc(args[0], command_config_soft_pwm_out
|
||||
, sizeof(*s));
|
||||
s->cycle_time = args[2];
|
||||
s->pulse_time = s->cycle_time / 255;
|
||||
@@ -171,7 +172,7 @@ DECL_COMMAND(command_config_soft_pwm_out,
|
||||
void
|
||||
command_schedule_soft_pwm_out(uint32_t *args)
|
||||
{
|
||||
struct soft_pwm_s *s = lookup_oid(args[0], command_config_soft_pwm_out);
|
||||
struct soft_pwm_s *s = oid_lookup(args[0], command_config_soft_pwm_out);
|
||||
uint32_t time = args[1];
|
||||
uint8_t value = args[2];
|
||||
uint8_t next_flags = SPF_CHECK_END | SPF_HAVE_NEXT;
|
||||
@@ -189,20 +190,20 @@ command_schedule_soft_pwm_out(uint32_t *args)
|
||||
next_flags |= SPF_NEXT_CHECK_END;
|
||||
}
|
||||
irq_disable();
|
||||
if (s->flags & SPF_CHECK_END && sched_is_before(s->end_time, time))
|
||||
if (s->flags & SPF_CHECK_END && timer_is_before(s->end_time, time))
|
||||
shutdown("next soft pwm extends existing pwm");
|
||||
s->end_time = time;
|
||||
s->next_on_duration = next_on_duration;
|
||||
s->next_off_duration = next_off_duration;
|
||||
s->flags |= next_flags;
|
||||
if (s->flags & SPF_TOGGLING && sched_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
|
||||
} else {
|
||||
// Schedule the loading of the pwm parameters at the requested time
|
||||
sched_del_timer(&s->timer);
|
||||
s->timer.waketime = time;
|
||||
s->timer.func = soft_pwm_load_event;
|
||||
sched_timer(&s->timer);
|
||||
sched_add_timer(&s->timer);
|
||||
}
|
||||
irq_enable();
|
||||
}
|
||||
|
||||