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
|
$(OUT)klipper.o: $(patsubst %.c, $(OUT)src/%.o,$(src-y)) $(OUT)declfunc.lds
|
||||||
@echo " Linking $@"
|
@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
|
$(OUT)compile_time_request.o: $(OUT)klipper.o ./scripts/buildcommands.py
|
||||||
@echo " Building $@"
|
@echo " Building $@"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Support for internal testing with the "simulavr" program. To use
|
# Support for internal testing with the "simulavr" program. To use
|
||||||
# this config, compile the firmware for an AVR atmega644p, disable the
|
# this config, compile the firmware for an AVR atmega644p, disable the
|
||||||
# AVR watchdog timer, set the MCU frequency to 20000000, and set 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]
|
[stepper_x]
|
||||||
# Pins: PA5, PA4, PA1
|
# Pins: PA5, PA4, PA1
|
||||||
@@ -42,11 +42,11 @@ step_pin: ar19
|
|||||||
dir_pin: ar18
|
dir_pin: ar18
|
||||||
enable_pin: ar25
|
enable_pin: ar25
|
||||||
step_distance: .004242
|
step_distance: .004242
|
||||||
max_velocity: 200000
|
nozzle_diameter: 0.500
|
||||||
max_accel: 3000
|
filament_diameter: 3.500
|
||||||
heater_pin: ar4
|
heater_pin: ar4
|
||||||
thermistor_pin: analog1
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
thermistor_type: EPCOS 100K B57560G104F
|
sensor_pin: analog1
|
||||||
control: pid
|
control: pid
|
||||||
pid_Kp: 22.2
|
pid_Kp: 22.2
|
||||||
pid_Ki: 1.08
|
pid_Ki: 1.08
|
||||||
@@ -57,19 +57,17 @@ max_temp: 210
|
|||||||
|
|
||||||
[heater_bed]
|
[heater_bed]
|
||||||
heater_pin: ar3
|
heater_pin: ar3
|
||||||
thermistor_pin: analog0
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
thermistor_type: EPCOS 100K B57560G104F
|
sensor_pin: analog0
|
||||||
control: watermark
|
control: watermark
|
||||||
min_temp: 0
|
min_temp: 0
|
||||||
max_temp: 110
|
max_temp: 110
|
||||||
|
|
||||||
[fan]
|
[fan]
|
||||||
pin: ar14
|
pin: ar14
|
||||||
hard_pwm: 1
|
|
||||||
|
|
||||||
[mcu]
|
[mcu]
|
||||||
serial: /tmp/pseudoserial
|
serial: /tmp/pseudoserial
|
||||||
baud: 250000
|
|
||||||
pin_map: arduino
|
pin_map: arduino
|
||||||
|
|
||||||
[printer]
|
[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
|
# The stepper_a section describes the stepper controlling the front
|
||||||
# left tower (at 210 degrees). This section also controls the homing
|
# left tower (at 210 degrees). This section also controls the homing
|
||||||
# parameters (homing_speed, homing_retract_dist) and maximum tower
|
# parameters (homing_speed, homing_retract_dist) for all towers.
|
||||||
# length (position_max) for all towers.
|
|
||||||
[stepper_a]
|
[stepper_a]
|
||||||
step_pin: ar54
|
step_pin: ar54
|
||||||
dir_pin: ar55
|
dir_pin: ar55
|
||||||
@@ -19,7 +18,6 @@ step_distance: .01
|
|||||||
endstop_pin: ^ar2
|
endstop_pin: ^ar2
|
||||||
homing_speed: 50.0
|
homing_speed: 50.0
|
||||||
position_endstop: 297.05
|
position_endstop: 297.05
|
||||||
position_max: 297.55
|
|
||||||
|
|
||||||
# The stepper_b section describes the stepper controlling the front
|
# The stepper_b section describes the stepper controlling the front
|
||||||
# right tower (at 330 degrees)
|
# right tower (at 330 degrees)
|
||||||
@@ -46,11 +44,11 @@ step_pin: ar26
|
|||||||
dir_pin: ar28
|
dir_pin: ar28
|
||||||
enable_pin: !ar24
|
enable_pin: !ar24
|
||||||
step_distance: .0022
|
step_distance: .0022
|
||||||
max_velocity: 200
|
nozzle_diameter: 0.400
|
||||||
max_accel: 3000
|
filament_diameter: 1.750
|
||||||
heater_pin: ar10
|
heater_pin: ar10
|
||||||
thermistor_pin: analog13
|
sensor_type: ATC Semitec 104GT-2
|
||||||
thermistor_type: ATC Semitec 104GT-2
|
sensor_pin: analog13
|
||||||
control: pid
|
control: pid
|
||||||
pid_Kp: 22.2
|
pid_Kp: 22.2
|
||||||
pid_Ki: 1.08
|
pid_Ki: 1.08
|
||||||
@@ -60,8 +58,8 @@ max_temp: 250
|
|||||||
|
|
||||||
[heater_bed]
|
[heater_bed]
|
||||||
heater_pin: ar8
|
heater_pin: ar8
|
||||||
thermistor_pin: analog14
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
thermistor_type: EPCOS 100K B57560G104F
|
sensor_pin: analog14
|
||||||
control: watermark
|
control: watermark
|
||||||
min_temp: 0
|
min_temp: 0
|
||||||
max_temp: 130
|
max_temp: 130
|
||||||
@@ -69,27 +67,37 @@ max_temp: 130
|
|||||||
# Extruder print fan (omit section if fan not present)
|
# Extruder print fan (omit section if fan not present)
|
||||||
#[fan]
|
#[fan]
|
||||||
#pin: ar9
|
#pin: ar9
|
||||||
#hard_pwm: 1
|
|
||||||
|
|
||||||
[mcu]
|
[mcu]
|
||||||
serial: /dev/ttyACM0
|
serial: /dev/ttyACM0
|
||||||
baud: 250000
|
|
||||||
pin_map: arduino
|
pin_map: arduino
|
||||||
|
|
||||||
[printer]
|
[printer]
|
||||||
kinematics: delta
|
kinematics: delta
|
||||||
# This option must be "delta" for linear delta printers
|
# This option must be "delta" for linear delta printers.
|
||||||
max_velocity: 300
|
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
|
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
|
max_z_velocity: 200
|
||||||
# For delta printers this limits the maximum velocity (in mm/s) of
|
# 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
|
# 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
|
# 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
|
delta_arm_length: 333.0
|
||||||
# Length (in mm) of the diagonal rods that connect the linear axes
|
# 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
|
delta_radius: 174.75
|
||||||
# Radius (in mm) of the horizontal circle formed by the three linear
|
# Radius (in mm) of the horizontal circle formed by the three linear
|
||||||
# axis towers. This parameter may also be calculated as:
|
# axis towers. This parameter may also be calculated as:
|
||||||
# delta_radius = smooth_rod_offset - effector_offset - carriage_offset
|
# delta_radius = smooth_rod_offset - effector_offset - carriage_offset
|
||||||
|
# This parameter must be provided.
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# This file serves as documentation for config parameters. One may
|
# This file serves as documentation for config parameters. One may
|
||||||
# copy and edit this file to configure a new cartesian style
|
# 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
|
# DO NOT COPY THIS FILE WITHOUT CAREFULLY READING AND UPDATING IT
|
||||||
# FIRST. Incorrectly configured parameters may cause damage.
|
# FIRST. Incorrectly configured parameters may cause damage.
|
||||||
@@ -19,27 +21,35 @@
|
|||||||
# the X axis in a cartesian robot
|
# the X axis in a cartesian robot
|
||||||
[stepper_x]
|
[stepper_x]
|
||||||
step_pin: ar29
|
step_pin: ar29
|
||||||
# Step GPIO pin (triggered high)
|
# Step GPIO pin (triggered high). This parameter must be provided.
|
||||||
dir_pin: !ar28
|
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: !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
|
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_pin: ^ar0
|
||||||
# Endstop switch detection pin
|
# Endstop switch detection pin. This parameter must be provided for
|
||||||
homing_speed: 50.0
|
# the X, Y, and Z steppers on cartesian style printers.
|
||||||
# Maximum velocity (in mm/s) of the stepper when homing
|
#homing_speed: 5.0
|
||||||
homing_retract_dist: 5.0
|
# Maximum velocity (in mm/s) of the stepper when homing. The default
|
||||||
# Distance to backoff (in mm) before homing a second time during homing
|
# is 5mm/s.
|
||||||
homing_positive_dir: False
|
#homing_retract_dist: 5.0
|
||||||
# If true, homes in a positive direction (away from zero)
|
# Distance to backoff (in mm) before homing a second time during
|
||||||
homing_stepper_phases: 0
|
# 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
|
# One may optionally set this to the number of phases of the stepper
|
||||||
# motor driver (which is the number of micro-steps multiplied by
|
# motor driver (which is the number of micro-steps multiplied by
|
||||||
# four). When set, the phase of the stepper driver will be used
|
# four). When set, the phase of the stepper driver will be used
|
||||||
# during homing to improve the accuracy of the endstop switch.
|
# 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
|
# Sets the expected accuracy (in mm) of the endstop. This represents
|
||||||
# the maximum error distance the endstop may trigger (eg, if an
|
# the maximum error distance the endstop may trigger (eg, if an
|
||||||
# endstop may occasionally trigger 100um early or up to 100um late
|
# 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.
|
# phase will be used on all subsequent homes.
|
||||||
position_min: -0.25
|
position_min: -0.25
|
||||||
# Minimum valid distance (in mm) the user may command the stepper to
|
# Minimum valid distance (in mm) the user may command the stepper to
|
||||||
# move to
|
# move to. The default is 0mm.
|
||||||
position_endstop: 0
|
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
|
position_max: 200
|
||||||
# Maximum valid distance (in mm) the user may command the stepper to
|
# 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 stepper_y section is used to describe the stepper controlling
|
||||||
# the Y axis in a cartesian robot. It has the same settings as the
|
# the Y axis in a cartesian robot. It has the same settings as the
|
||||||
@@ -99,84 +111,148 @@ step_pin: ar19
|
|||||||
dir_pin: ar18
|
dir_pin: ar18
|
||||||
enable_pin: !ar25
|
enable_pin: !ar25
|
||||||
step_distance: .004242
|
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
|
# Maximum velocity (in mm/s) of the extruder motor for extrude only
|
||||||
# moves.
|
# moves. If this is not specified then it is calculated to match the
|
||||||
max_accel: 3000
|
# 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
|
# Maximum acceleration (in mm/s^2) of the extruder motor for extrude
|
||||||
# only moves.
|
# only moves. If this is not specified then it is calculated to
|
||||||
#
|
# match the limit an XY printing move with a
|
||||||
# The remaining variables describe the extruder heater
|
# max_extrude_cross_section extrusion would have.
|
||||||
pressure_advance: 0.0
|
#pressure_advance: 0.0
|
||||||
# The amount of raw filament to push into the extruder during
|
# The amount of raw filament to push into the extruder during
|
||||||
# extruder acceleration. An equal amount of filament is retracted
|
# extruder acceleration. An equal amount of filament is retracted
|
||||||
# during deceleration. It is measured in millimeters per
|
# 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
|
heater_pin: ar4
|
||||||
# PWM output pin controlling the heater
|
# PWM output pin controlling the heater. This parameter must be
|
||||||
thermistor_pin: analog1
|
# provided.
|
||||||
# Analog input pin connected to thermistor
|
#max_power: 1.0
|
||||||
thermistor_type: EPCOS 100K B57560G104F
|
# The maximum power (expressed as a value from 0.0 to 1.0) that the
|
||||||
# Type of thermistor (see klippy/heater.py for available types)
|
# heater_pin may be set to. The value 1.0 allows the pin to be set
|
||||||
pullup_resistor: 4700
|
# fully enabled for extended periods, while a value of 0.5 would
|
||||||
# The resistance (in ohms) of the pullup attached to the thermistor
|
# 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: pid
|
||||||
# Control algorithm (either pid or watermark)
|
# Control algorithm (either pid or watermark). This parameter must
|
||||||
|
# be provided.
|
||||||
pid_Kp: 22.2
|
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
|
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
|
pid_Kd: 114
|
||||||
# Kd is the "derivative" constant for the pid
|
# Kd is the "derivative" constant for the pid. This parameter must
|
||||||
pid_deriv_time: 2.0
|
# be provided for PID heaters.
|
||||||
|
#pid_deriv_time: 2.0
|
||||||
# A time value (in seconds) over which the derivative in the pid
|
# A time value (in seconds) over which the derivative in the pid
|
||||||
# will be smoothed to reduce the impact of measurement noise
|
# will be smoothed to reduce the impact of measurement noise. The
|
||||||
pid_integral_max: 255
|
# default is 2 seconds.
|
||||||
# The maximum "windup" the integral term may accumulate
|
#pid_integral_max:
|
||||||
min_extrude_temp: 170
|
# 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
|
# 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
|
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
|
max_temp: 210
|
||||||
# Maximum temperature (mcu will shutdown if temperature is above
|
# 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
|
# The heater_bed section describes a heated bed (if present - omit
|
||||||
# section if not present).
|
# section if not present).
|
||||||
[heater_bed]
|
[heater_bed]
|
||||||
heater_pin: ar3
|
heater_pin: ar3
|
||||||
thermistor_pin: analog0
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
thermistor_type: EPCOS 100K B57560G104F
|
sensor_pin: analog0
|
||||||
control: watermark
|
control: watermark
|
||||||
max_delta: 2.0
|
#max_delta: 2.0
|
||||||
# The number of degrees in Celsius above the target temperature
|
# On 'watermark' controlled heaters this is the number of degrees in
|
||||||
# before disabling the heater as well as the number of degrees below
|
# Celsius above the target temperature before disabling the heater
|
||||||
# the target before re-enabling 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
|
min_temp: 0
|
||||||
max_temp: 110
|
max_temp: 110
|
||||||
|
|
||||||
# Extruder print fan (omit section if fan not present)
|
# Extruder print fan (omit section if fan not present)
|
||||||
[fan]
|
[fan]
|
||||||
pin: ar14
|
pin: ar14
|
||||||
# PWM output pin controlling the heater
|
# PWM output pin controlling the fan. This parameter must be
|
||||||
hard_pwm: 1
|
# provided.
|
||||||
|
#hard_pwm: 0
|
||||||
# Set this value to force hardware PWM instead of software PWM. Set
|
# 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
|
# 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
|
# number to force hardware PWM with the given cycle time in clock
|
||||||
# in clock ticks.
|
# ticks. The default is 0 which enables software PWM with a cycle
|
||||||
kick_start_time: 0.100
|
# time of 10ms.
|
||||||
|
#kick_start_time: 0.100
|
||||||
# Time (in seconds) to run the fan at full speed when first enabling
|
# Time (in seconds) to run the fan at full speed when first enabling
|
||||||
# it (helps get the fan spinning)
|
# it (helps get the fan spinning). The default is 0.100 seconds.
|
||||||
|
|
||||||
# Micro-controller information
|
# Micro-controller information
|
||||||
[mcu]
|
[mcu]
|
||||||
serial: /dev/ttyACM0
|
serial: /dev/ttyACM0
|
||||||
# The serial port to connect to the MCU
|
# The serial port to connect to the MCU. The default is /dev/ttyS0
|
||||||
baud: 250000
|
#baud: 250000
|
||||||
# The baud rate to use
|
# The baud rate to use. The default is 250000.
|
||||||
pin_map: arduino
|
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:
|
custom:
|
||||||
# This option may be used to specify a set of custom
|
# This option may be used to specify a set of custom
|
||||||
# micro-controller commands to be sent at the start of the
|
# micro-controller commands to be sent at the start of the
|
||||||
@@ -187,25 +263,34 @@ custom:
|
|||||||
# The printer section controls high level printer settings
|
# The printer section controls high level printer settings
|
||||||
[printer]
|
[printer]
|
||||||
kinematics: cartesian
|
kinematics: cartesian
|
||||||
# This option must be "cartesian" for cartesian printers
|
# This option must be "cartesian" for cartesian printers.
|
||||||
max_velocity: 500
|
max_velocity: 500
|
||||||
# Maximum velocity (in mm/s) of the toolhead (relative to the
|
# Maximum velocity (in mm/s) of the toolhead (relative to the
|
||||||
# print)
|
# print). This parameter must be specified.
|
||||||
max_accel: 3000
|
max_accel: 3000
|
||||||
# Maximum acceleration (in mm/s^2) of the toolhead (relative to the
|
# Maximum acceleration (in mm/s^2) of the toolhead (relative to the
|
||||||
# print)
|
# print). This parameter must be specified.
|
||||||
max_z_velocity: 250
|
#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
|
# For cartesian printers this sets the maximum velocity (in mm/s) of
|
||||||
# movement along the z axis. This setting can be used to restrict
|
# 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
|
max_z_accel: 30
|
||||||
# For cartesian printers this sets the maximum acceleration (in
|
# For cartesian printers this sets the maximum acceleration (in
|
||||||
# mm/s^2) of movement along the z axis. It limits the acceleration
|
# mm/s^2) of movement along the z axis. It limits the acceleration
|
||||||
# of the z stepper motor on cartesian printers.
|
# of the z stepper motor on cartesian printers. The default is to
|
||||||
motor_off_time: 60
|
# use max_accel for max_z_accel.
|
||||||
|
#motor_off_time: 600
|
||||||
# Time (in seconds) of idle time before the printer will try to
|
# Time (in seconds) of idle time before the printer will try to
|
||||||
# disable active motors.
|
# disable active motors. The default is 600 seconds.
|
||||||
junction_deviation: 0.02
|
#junction_deviation: 0.02
|
||||||
# Distance (in mm) used to control the internal approximated
|
# Distance (in mm) used to control the internal approximated
|
||||||
# centripetal velocity cornering algorithm. A larger number will
|
# centripetal velocity cornering algorithm. A larger number will
|
||||||
# permit higher "cornering speeds" at the junction of two moves.
|
# 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
|
dir_pin: PL6
|
||||||
enable_pin: !PA4
|
enable_pin: !PA4
|
||||||
step_distance: .004242
|
step_distance: .004242
|
||||||
max_velocity: 200000
|
nozzle_diameter: 0.350
|
||||||
max_accel: 3000
|
filament_diameter: 1.750
|
||||||
pressure_advance: 0.07
|
pressure_advance: 0.07
|
||||||
heater_pin: PH6
|
heater_pin: PH6
|
||||||
thermistor_pin: PF0
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
thermistor_type: EPCOS 100K B57560G104F
|
sensor_pin: PF0
|
||||||
control: pid
|
control: pid
|
||||||
pid_Kp: 7.0
|
pid_Kp: 7.0
|
||||||
pid_Ki: 0.1
|
pid_Ki: 0.1
|
||||||
@@ -63,19 +63,17 @@ max_temp: 210
|
|||||||
|
|
||||||
[heater_bed]
|
[heater_bed]
|
||||||
heater_pin: PE5
|
heater_pin: PE5
|
||||||
thermistor_pin: PF2
|
sensor_type: EPCOS 100K B57560G104F
|
||||||
thermistor_type: EPCOS 100K B57560G104F
|
sensor_pin: PF2
|
||||||
control: watermark
|
control: watermark
|
||||||
min_temp: 0
|
min_temp: 0
|
||||||
max_temp: 100
|
max_temp: 100
|
||||||
|
|
||||||
[fan]
|
[fan]
|
||||||
pin: PH5
|
pin: PH5
|
||||||
hard_pwm: 1
|
|
||||||
|
|
||||||
[mcu]
|
[mcu]
|
||||||
serial: /dev/ttyACM0
|
serial: /dev/ttyACM0
|
||||||
baud: 250000
|
|
||||||
custom:
|
custom:
|
||||||
# Nozzle fan
|
# Nozzle fan
|
||||||
set_pwm_out pin=PH3 cycle_ticks=1 value=155
|
set_pwm_out pin=PH3 cycle_ticks=1 value=155
|
||||||
@@ -101,6 +99,5 @@ custom:
|
|||||||
kinematics: cartesian
|
kinematics: cartesian
|
||||||
max_velocity: 500
|
max_velocity: 500
|
||||||
max_accel: 3000
|
max_accel: 3000
|
||||||
max_z_velocity: 250
|
max_z_velocity: 25
|
||||||
max_z_accel: 30
|
max_z_accel: 30
|
||||||
motor_off_time: 600
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ host architectures. The build arranges for includes of
|
|||||||
src/generic/somefile.h).
|
src/generic/somefile.h).
|
||||||
|
|
||||||
The **klippy/** directory contains the C and Python source for the
|
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
|
The **lib/** directory contains external 3rd-party library code that
|
||||||
is necessary to build some targets.
|
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
|
than a few micro-seconds. These functions schedule work at specific
|
||||||
times by scheduling timers.
|
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
|
**src/sched.c**). The scheduler code will arrange for the given
|
||||||
function to be called at the requested clock time. Timer interrupts
|
function to be called at the requested clock time. Timer interrupts
|
||||||
are initially handled in an architecture specific interrupt handler
|
are initially handled in an architecture specific interrupt handler
|
||||||
(eg, **src/avr/timer.c**), but this just calls sched_timer_kick()
|
(eg, **src/avr/timer.c**) which calls sched_timer_dispatch() located
|
||||||
located in **src/sched.c**. The timer interrupt leads to execution of
|
in **src/sched.c**. The timer interrupt leads to execution of schedule
|
||||||
schedule timer functions. Timer functions always run with interrupts
|
timer functions. Timer functions always run with interrupts
|
||||||
disabled. The timer functions should always complete within a few
|
disabled. The timer functions should always complete within a few
|
||||||
micro-seconds. At completion of the timer event, the function may
|
micro-seconds. At completion of the timer event, the function may
|
||||||
choose to reschedule itself.
|
choose to reschedule itself.
|
||||||
@@ -92,8 +92,8 @@ some functionality in C code.
|
|||||||
Initial execution starts in **klippy/klippy.py**. This reads the
|
Initial execution starts in **klippy/klippy.py**. This reads the
|
||||||
command-line arguments, opens the printer config file, instantiates
|
command-line arguments, opens the printer config file, instantiates
|
||||||
the main printer objects, and starts the serial connection. The main
|
the main printer objects, and starts the serial connection. The main
|
||||||
execution of gcode commands is in the process_commands() method in
|
execution of G-code commands is in the process_commands() method in
|
||||||
**klippy/gcode.py**. This code translates the gcode commands into
|
**klippy/gcode.py**. This code translates the G-code commands into
|
||||||
printer object calls, which frequently translate the actions to
|
printer object calls, which frequently translate the actions to
|
||||||
commands to be executed on the micro-controller (as declared via the
|
commands to be executed on the micro-controller (as declared via the
|
||||||
DECL_COMMAND macro in the micro-controller code).
|
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
|
**klippy/serialhdl.py**). The fourth thread writes debug messages to
|
||||||
the log (see **klippy/queuelogger.py**) so that the other threads
|
the log (see **klippy/queuelogger.py**) so that the other threads
|
||||||
never block on log writes.
|
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
|
The Klippy host code can run in a batch mode to produce the low-level
|
||||||
firmware commands associated with a gcode file. Inspecting these
|
micro-controller commands associated with a gcode file. Inspecting
|
||||||
low-level firmware commands is useful when trying to understand the
|
these low-level commands is useful when trying to understand the
|
||||||
actions of the low-level hardware. It can also be useful to compare
|
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 run Klippy in this batch mode, there is a one time step necessary
|
||||||
to generate the firmware "data dictionary". This is done by compiling
|
to generate the micro-controller "data dictionary". This is done by
|
||||||
the firmware code to obtain the **out/klipper.dict** file:
|
compiling the micro-controller code to obtain the **out/klipper.dict**
|
||||||
|
file:
|
||||||
|
|
||||||
```
|
```
|
||||||
make menuconfig
|
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
|
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
|
The batch mode disables certain response / request commands in order
|
||||||
to function. As a result, there will be some differences between
|
to function. As a result, there will be some differences between
|
||||||
actual firmware commands and the above output. The generated data is
|
actual commands and the above output. The generated data is useful for
|
||||||
useful for testing and inspection; it is not useful for sending to a
|
testing and inspection; it is not useful for sending to a real
|
||||||
real micro-controller.
|
micro-controller.
|
||||||
|
|
||||||
Testing with simulavr
|
Testing with simulavr
|
||||||
=====================
|
=====================
|
||||||
@@ -74,25 +75,20 @@ cd /patch/to/klipper
|
|||||||
make menuconfig
|
make menuconfig
|
||||||
```
|
```
|
||||||
|
|
||||||
and compile the firmware for an AVR atmega644p, disable the AVR
|
and compile the micro-controller software for an AVR atmega644p,
|
||||||
watchdog timer, and set the MCU frequency to 20000000. Then one can
|
disable the AVR watchdog timer, and set the MCU frequency
|
||||||
compile Klipper (run `make`) and then start the simulation with:
|
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
|
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
|
Then, with simulavr running in another window, one can run the
|
||||||
following to read gcode from a file (eg, "test.gcode"), process it
|
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
|
~/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
|
~/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
|
Klipper has several compelling features:
|
||||||
compelling features:
|
|
||||||
|
|
||||||
* High precision stepper movement. Klipper utilizes an application
|
* High precision stepper movement. Klipper utilizes an application
|
||||||
processor (such as a low-cost Raspberry Pi) when calculating printer
|
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
|
stepper motor, it compresses those events, transmits them to the
|
||||||
micro-controller, and then the micro-controller executes each event
|
micro-controller, and then the micro-controller executes each event
|
||||||
at the requested time. Each stepper event is scheduled with a
|
at the requested time. Each stepper event is scheduled with a
|
||||||
precision of no less than 50 micro-seconds. The software does not
|
precision of 25 micro-seconds or better. The software does not use
|
||||||
use kinematic estimations (such as the Bresenham algorithm) -
|
kinematic estimations (such as the Bresenham algorithm) - instead it
|
||||||
instead it calculates precise step times based on the physics of
|
calculates precise step times based on the physics of acceleration
|
||||||
acceleration and the physics of the machine kinematics. More precise
|
and the physics of the machine kinematics. More precise stepper
|
||||||
stepper movement translates to quieter and more stable printer
|
movement translates to quieter and more stable printer operation.
|
||||||
operation.
|
|
||||||
|
|
||||||
* Best in class performance. Klipper is able to achieve high stepping
|
* Best in class performance. Klipper is able to achieve high stepping
|
||||||
rates on both new and old micro-controllers. Even an old 8bit AVR
|
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
|
micro-controller can obtain rates over 175K steps per second. On
|
||||||
more recent ARM micro-controllers, rates over 350K steps per second
|
more recent ARM micro-controllers, rates over 450K steps per second
|
||||||
are possible. Higher stepper rates enable higher print
|
are possible. Higher stepper rates enable higher print
|
||||||
velocities. The stepper event timing remains precise even at high
|
velocities. The stepper event timing remains precise even at high
|
||||||
speeds which improves overall stability.
|
speeds which improves overall stability.
|
||||||
@@ -34,16 +32,21 @@ compelling features:
|
|||||||
micro-controller architectures as well.
|
micro-controller architectures as well.
|
||||||
|
|
||||||
* Simpler code. Klipper uses a very high level language (Python) for
|
* Simpler code. Klipper uses a very high level language (Python) for
|
||||||
most code. The kinematics algorithms, the gcode parsing, the heating
|
most code. The kinematics algorithms, the G-code parsing, the
|
||||||
and thermistor algorithms, etc. are all written in Python. This
|
heating and thermistor algorithms, etc. are all written in
|
||||||
makes it easier to develop new functionality.
|
Python. This makes it easier to develop new functionality.
|
||||||
|
|
||||||
* Advanced features. Klipper implements the "pressure advance"
|
* Advanced features:
|
||||||
algorithm for extruders. When properly tuned, pressure advance
|
* Klipper implements the "pressure advance" algorithm for
|
||||||
reduces extruder ooze. Klipper also implements a novel "stepper
|
extruders. When properly tuned, pressure advance reduces extruder
|
||||||
phase endstop" algorithm that can dramatically improve the accuracy
|
ooze.
|
||||||
of typical endstop switches. When properly tuned it can improve a
|
* Klipper also implements a novel "stepper phase endstop" algorithm
|
||||||
print's first layer bed adhesion.
|
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)
|
To get started with Klipper, read the [installation](Installation.md)
|
||||||
guide.
|
guide.
|
||||||
@@ -65,12 +68,12 @@ Klipper supports many standard 3d printer features:
|
|||||||
gradually accelerate from standstill to cruising speed and then
|
gradually accelerate from standstill to cruising speed and then
|
||||||
decelerate back to a standstill.
|
decelerate back to a standstill.
|
||||||
|
|
||||||
* "Lookahead" support. The incoming stream of G-Code movement commands
|
* "Look-ahead" support. The incoming stream of G-Code movement
|
||||||
are queued and analyzed - the acceleration between movements in a
|
commands are queued and analyzed - the acceleration between
|
||||||
similar direction will be optimized to reduce print stalls and
|
movements in a similar direction will be optimized to reduce print
|
||||||
improve overall print time.
|
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
|
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 |
|
| Micro-controller | 1 stepper active | 3 steppers active |
|
||||||
| ----------------- | ---------------- | ----------------- |
|
| ----------------- | ---------------- | ----------------- |
|
||||||
| 20Mhz AVR | 158.7K | 103K |
|
| 20Mhz AVR | 177K | 117K |
|
||||||
| 16Mhz AVR | 126.9K | 82K |
|
| 16Mhz AVR | 140K | 93K |
|
||||||
| Arduino Due (ARM) | 352.9K | 288K |
|
| 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
|
These instructions assume the software will run on a Raspberry Pi
|
||||||
assume the software will run on a Raspberry Pi computer in conjunction
|
computer in conjunction with OctoPrint. It is recommended that a
|
||||||
with OctoPrint. Klipper supports Atmel ATmega based micro-controllers
|
Raspberry Pi 2 or Raspberry Pi 3 computer be used as the host
|
||||||
and Arduino Due (Atmel SAM3x8e ARM micro-controllers) printers.
|
machine.
|
||||||
|
|
||||||
It is recommended that a Raspberry Pi 2 or Raspberry Pi 3 computer be
|
It should be possible to run the Klipper host software on any computer
|
||||||
used as the host. The software will run on a first generation
|
running a recent Linux distribution, but doing so will require Linux
|
||||||
Raspberry Pi, but the combined load of OctoPrint, Klipper, and a web
|
admin knowledge to translate these installation instructions to the
|
||||||
cam (if applicable) can overwhelm its CPU leading to print stalls.
|
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
|
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
|
[octopi releases](https://github.com/guysoft/OctoPi/releases) for
|
||||||
release information. One should verify that OctoPi boots and that the
|
release information. One should verify that OctoPi boots and that the
|
||||||
OctoPrint web server works. After connecting to the OctoPrint web
|
OctoPrint web server works. After connecting to the OctoPrint web
|
||||||
page, follow the prompt to upgrade OctoPrint to v1.3.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
|
After installing OctoPi and upgrading OctoPrint, ssh into the target
|
||||||
machine (ssh pi@octopi -- password is "raspberry") and run the
|
machine (ssh pi@octopi -- password is "raspberry") and run the
|
||||||
following commands:
|
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
|
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:
|
To compile the micro-controller code, start by configuring it:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
cd ~/klipper/
|
||||||
make menuconfig
|
make menuconfig
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -60,54 +52,39 @@ configured, run:
|
|||||||
make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
Ignore any warnings you may see about "misspelled signal handler" (it
|
Finally, for common micro-controllers, the code can be flashed with:
|
||||||
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:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
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
|
The Klipper configuration is stored in a text file on the Raspberry
|
||||||
not work when connected to the application USB port). The programming
|
Pi. Take a look at the example config files in the
|
||||||
port is the USB port closest to the power supply. To flash Klipper to
|
[config directory](../config/). The
|
||||||
the Due connect it to the host machine and run:
|
[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
|
||||||
stty -F /dev/ttyACM0 1200
|
may be a more concise starting point. The next step is to copy and
|
||||||
bossac -i -p ttyACM0 -R -e -w -v -b ~/klipper/out/klipper.bin
|
edit one of these config files - for example:
|
||||||
```
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
cp ~/klipper/config/example.cfg ~/printer.cfg
|
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.
|
the hardware.
|
||||||
|
|
||||||
Configuring OctoPrint to use Klippy
|
Configuring OctoPrint to use Klipper
|
||||||
===================================
|
====================================
|
||||||
|
|
||||||
The OctoPrint web server needs to be configured to communicate with
|
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
|
web page, and navigate to the Settings tab. Then configure the
|
||||||
following items:
|
following items:
|
||||||
|
|
||||||
@@ -115,40 +92,30 @@ Under "Serial Connection" in "Additional serial ports" add
|
|||||||
"/tmp/printer". Then click "Save".
|
"/tmp/printer". Then click "Save".
|
||||||
|
|
||||||
Enter the Settings tab again and under "Serial Connection" change the
|
Enter the Settings tab again and under "Serial Connection" change the
|
||||||
"Serial Port" setting to "/tmp/printer". Change the Baudrate field to
|
"Serial Port" setting to "/tmp/printer". Unselect the "Not only cancel
|
||||||
250000 (this buad rate field is not related to the firmware baudrate
|
ongoing prints but also disconnect..." checkbox. Click "Save".
|
||||||
and may be safely left at 250000). Unselect the "Not only cancel
|
|
||||||
ongoing prints but also disconnect..." checkbox.
|
|
||||||
|
|
||||||
Under the "Features" tab, unselect "Enable SD support". Then click
|
From the main page, under the "Connection" section (at the top left of
|
||||||
"Save".
|
the page) make sure the "Serial Port" is set to "/tmp/printer" and
|
||||||
|
|
||||||
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
|
|
||||||
click "Connect". (If "/tmp/printer" is not an available selection then
|
click "Connect". (If "/tmp/printer" is not an available selection then
|
||||||
try reloading the page.)
|
try reloading the page.)
|
||||||
|
|
||||||
Once connected, navigate to the "Terminal" tab and type "status"
|
Once connected, navigate to the "Terminal" tab and type "status"
|
||||||
(without the quotes) into the command entry box and click "Send". If
|
(without the quotes) into the command entry box and click "Send". The
|
||||||
the Klippy config file was successfully read, and the micro-controller
|
terminal window will likely report there is an error opening the
|
||||||
was successfully found and configured, then this command will report
|
config file - issue a "restart" command in the OctoPrint terminal to
|
||||||
that the printer is ready. Klippy reports error messages via this
|
load the config. A "status" command will report the printer is ready
|
||||||
terminal tab. The "status" command can be used to re-report error
|
if the Klipper config file is successfully read and the
|
||||||
messages.
|
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
|
Klipper reports error messages via the OctoPrint terminal tab. The
|
||||||
commands - "status" is an example of one of these commands. Use the
|
"status" command can be used to re-report error messages. The default
|
||||||
"help" command to get a list of other extended commands. In
|
Klipper startup script also places a log in **/tmp/klippy.log** which
|
||||||
particular, note the "restart" command - use this command to reload
|
provides more detailed information.
|
||||||
the Klippy config file after any changes.
|
|
||||||
|
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,
|
Welcome to the Klipper documentation. There are two parts to Klipper -
|
||||||
installing, and running Klipper. Read [features](Features.md) for a
|
code that runs on a micro-controller and code that runs on a "host"
|
||||||
high-level description of useful capabilities. The history of releases
|
machine. The host code is intended to run on a low-cost
|
||||||
is available at [releases](Releases.md).
|
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
|
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
|
See [protocol](Protocol.md) for information on the low-level messaging
|
||||||
and layout of the Klipper code.
|
protocol between host and micro-controller. See also
|
||||||
|
[MCU commands](MCU_Commands.md) for a description of low-level
|
||||||
See [protocol](Protocol.md) for information on the messaging protocol
|
commands implemented in the micro-controller software.
|
||||||
between host and firmware. See also
|
|
||||||
[firmware commands](Firmware_Commands.md) for a high-level description
|
|
||||||
of common commands implemented in the firmware.
|
|
||||||
|
|
||||||
See [debugging](Debugging.md) for information on how to test and debug
|
See [debugging](Debugging.md) for information on how to test and debug
|
||||||
Klipper.
|
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,
|
The Klipper messaging protocol is used for low-level communication
|
||||||
as a series of command and response strings that are compressed,
|
between the Klipper host software and the Klipper micro-controller
|
||||||
transmitted over a serial line, and then processed at the receiving
|
software. At a high level the protocol can be thought of as a series
|
||||||
side. An example series of commands in uncompressed human-readable
|
of command and response strings that are compressed, transmitted, and
|
||||||
format might look like:
|
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
|
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
|
queue_step oid=7 interval=11717 count=4 add=1281
|
||||||
```
|
```
|
||||||
|
|
||||||
See the [firmware commands](Firmware_Commands.md) document for
|
See the [mcu commands](MCU_Commands.md) document for information on
|
||||||
information on available commands. See the [debugging](Debugging.md)
|
available commands. See the [debugging](Debugging.md) document for
|
||||||
document for information on how to translate a G-Code file into its
|
information on how to translate a G-Code file into its corresponding
|
||||||
corresponding human-readable firmware commands.
|
human-readable micro-controller commands.
|
||||||
|
|
||||||
This page provides a high-level description of the Klipper
|
This page provides a high-level description of the Klipper messaging
|
||||||
transmission protocol itself. It describes how messages are declared,
|
protocol itself. It describes how messages are declared, encoded in
|
||||||
encoded in binary format (the "compression" scheme), and transmitted.
|
binary format (the "compression" scheme), and transmitted.
|
||||||
|
|
||||||
The goal of the protocol is to enable an error-free communication
|
The goal of the protocol is to enable an error-free communication
|
||||||
channel between the host and firmware that is low-latency,
|
channel between the host and micro-controller that is low-latency,
|
||||||
low-bandwidth, and low-complexity for the firmware.
|
low-bandwidth, and low-complexity for the micro-controller.
|
||||||
|
|
||||||
Firmware Interface
|
Micro-controller Interface
|
||||||
==================
|
==========================
|
||||||
|
|
||||||
The Klipper transmission protocol can be thought of as a
|
The Klipper transmission protocol can be thought of as a
|
||||||
[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) mechanism
|
[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) mechanism
|
||||||
between firmware and host. The firmware declares the commands that the
|
between micro-controller and host. The micro-controller software
|
||||||
host may invoke along with the response messages that it can
|
declares the commands that the host may invoke along with the response
|
||||||
generate. The host uses that information to command the firmware to
|
messages that it can generate. The host uses that information to
|
||||||
perform actions and to interpret the results.
|
command the micro-controller to perform actions and to interpret the
|
||||||
|
results.
|
||||||
|
|
||||||
Declaring commands
|
Declaring commands
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
The firmware declares a "command" by using the DECL_COMMAND() macro in
|
The micro-controller software declares a "command" by using the
|
||||||
the C code. For example:
|
DECL_COMMAND() macro in the C code. For example:
|
||||||
|
|
||||||
```
|
```
|
||||||
DECL_COMMAND(command_set_digital_out, "set_digital_out pin=%u value=%c");
|
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
|
The above declares a command named "set_digital_out". This allows the
|
||||||
host to "invoke" this command which would cause the
|
host to "invoke" this command which would cause the
|
||||||
command_set_digital_out() C function to be executed in the
|
command_set_digital_out() C function to be executed in the
|
||||||
firmware. The above also indicates that the command takes two integer
|
micro-controller. The above also indicates that the command takes two
|
||||||
parameters. When the command_set_digital_out() C code is executed, it
|
integer parameters. When the command_set_digital_out() C code is
|
||||||
will be passed an array containing these two integers - the first
|
executed, it will be passed an array containing these two integers -
|
||||||
corresponding to the 'pin' and the second corresponding to the
|
the first corresponding to the 'pin' and the second corresponding to
|
||||||
'value'.
|
the 'value'.
|
||||||
|
|
||||||
In general, the parameters are described with printf() style syntax
|
In general, the parameters are described with printf() style syntax
|
||||||
(eg, "%u"). The formatting directly corresponds to the human-readable
|
(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
|
to indicate the expected integer is 1 byte in size (the declared
|
||||||
integer size does not impact the parsing or encoding).
|
integer size does not impact the parsing or encoding).
|
||||||
|
|
||||||
At firmware compile time, the build will collect all commands declared
|
The micro-controller build will collect all commands declared with
|
||||||
with DECL_COMMAND(), determine their parameters, and arrange for them
|
DECL_COMMAND(), determine their parameters, and arrange for them to be
|
||||||
to be callable.
|
callable.
|
||||||
|
|
||||||
Declaring responses
|
Declaring responses
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
To send information from the firmware to the host a "response" is
|
To send information from the micro-controller to the host a "response"
|
||||||
generated. These are both declared and transmitted using the sendf() C
|
is generated. These are both declared and transmitted using the
|
||||||
macro. For example:
|
sendf() C macro. For example:
|
||||||
|
|
||||||
```
|
```
|
||||||
sendf("status clock=%u status=%c", sched_read_time(), sched_is_shutdown());
|
sendf("status clock=%u status=%c", sched_read_time(), sched_is_shutdown());
|
||||||
```
|
```
|
||||||
|
|
||||||
The above transmits a "status" response message that contains two
|
The above transmits a "status" response message that contains two
|
||||||
integer parameters ("clock" and "status"). At firmware compile time
|
integer parameters ("clock" and "status"). The micro-controller build
|
||||||
the build automatically finds all sendf() calls and generates encoders
|
automatically finds all sendf() calls and generates encoders for
|
||||||
for them. The first parameter of the sendf() function describes the
|
them. The first parameter of the sendf() function describes the
|
||||||
response and it is in the same format as command declarations.
|
response and it is in the same format as command declarations.
|
||||||
|
|
||||||
The host can arrange to register a callback function for each
|
The host can arrange to register a callback function for each
|
||||||
response. So, in effect, commands allow the host to invoke C functions
|
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
|
in the micro-controller and responses allow the micro-controller
|
||||||
host.
|
software to invoke code in the host.
|
||||||
|
|
||||||
The firmware should only invoke sendf() from command or task handlers,
|
The sendf() macro should only be invoked from command or task
|
||||||
and it should not be invoked from interrupts or timers. The firmware
|
handlers, and it should not be invoked from interrupts or timers. The
|
||||||
does not need to issue a sendf() in response to a received command, it
|
code does not need to issue a sendf() in response to a received
|
||||||
is not limited in the number of times sendf() may be invoked, and it
|
command, it is not limited in the number of times sendf() may be
|
||||||
may invoke sendf() at any time from a task handler.
|
invoked, and it may invoke sendf() at any time from a task handler.
|
||||||
|
|
||||||
### Output responses
|
### Output responses
|
||||||
|
|
||||||
To simplify debugging, the firmware also has an output() C
|
To simplify debugging, there is also has an output() C function. For
|
||||||
function. For example:
|
example:
|
||||||
|
|
||||||
```
|
```
|
||||||
output("The value of %u is %s with size %u.", x, buf, buf_len);
|
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
|
The output() function is similar in usage to printf() - it is intended
|
||||||
to generate and format arbitrary messages for human consumption. It is
|
to generate and format arbitrary messages for human consumption.
|
||||||
a wrapper around sendf() and as with sendf() it should not be called
|
|
||||||
from interrupts or timers.
|
|
||||||
|
|
||||||
Declaring constants
|
Declaring constants
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
The firmware can also define constants to be exported. For example,
|
Constants can also be exported. For example, the following:
|
||||||
the following:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
DECL_CONSTANT(SERIAL_BAUD, 250000);
|
DECL_CONSTANT(SERIAL_BAUD, 250000);
|
||||||
```
|
```
|
||||||
|
|
||||||
would export a constant named "SERIAL_BAUD" with a value of 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
|
Low-level message encoding
|
||||||
==========================
|
==========================
|
||||||
@@ -132,9 +131,9 @@ the transmission system.
|
|||||||
Message Blocks
|
Message Blocks
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
All data sent from host to firmware and vice-versa are contained in
|
All data sent from host to micro-controller and vice-versa are
|
||||||
"message blocks". A message block has a two byte header and a three
|
contained in "message blocks". A message block has a two byte header
|
||||||
byte trailer. The format of a message block is:
|
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>
|
<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
|
Message Block Contents
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
Each message block sent from host to firmware contains a series of
|
Each message block sent from host to micro-controller contains a
|
||||||
zero or more message commands in its contents. Each command starts
|
series of zero or more message commands in its contents. Each command
|
||||||
with a Variable Length Quantity (VLQ) encoded integer command-id
|
starts with a [Variable Length Quantity](#variable-length-quantities)
|
||||||
followed by zero or more VLQ parameters for the given command.
|
(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
|
As an example, the following four commands might be placed in a single
|
||||||
message block:
|
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
|
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
|
micro-controller must agree on the command ids and the number of
|
||||||
each command has. So, in the above example, both the host and firmware
|
parameters each command has. So, in the above example, both the host
|
||||||
would know that "id_set_digital_out" is always followed by two
|
and micro-controller would know that "id_set_digital_out" is always
|
||||||
parameters, and "id_get_config" and "id_get_status" have zero
|
followed by two parameters, and "id_get_config" and "id_get_status"
|
||||||
parameters. The host and firmware share a "data dictionary" that maps
|
have zero parameters. The host and micro-controller share a "data
|
||||||
the command descriptions (eg, "set_digital_out pin=%u value=%c") to
|
dictionary" that maps the command descriptions (eg, "set_digital_out
|
||||||
their integer command-ids. When processing the data, the parser will
|
pin=%u value=%c") to their integer command-ids. When processing the
|
||||||
know to expect a specific number of VLQ encoded parameters following a
|
data, the parser will know to expect a specific number of VLQ encoded
|
||||||
given command id.
|
parameters following a given command id.
|
||||||
|
|
||||||
The message contents for blocks sent from firmware to host follow the
|
The message contents for blocks sent from micro-controller to host
|
||||||
same format. The identifiers in these messages are "response ids", but
|
follow the same format. The identifiers in these messages are
|
||||||
they serve the same purpose and follow the same encoding rules. In
|
"response ids", but they serve the same purpose and follow the same
|
||||||
practice, message blocks sent from the firmware to the host never
|
encoding rules. In practice, message blocks sent from the
|
||||||
contain more than one response in the message block contents.
|
micro-controller to the host never contain more than one response in
|
||||||
|
the message block contents.
|
||||||
|
|
||||||
### Variable Length Quantities
|
### 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
|
The command descriptions found in the data dictionary allow both the
|
||||||
host and firmware to know which command parameters use simple VLQ
|
host and micro-controller to know which command parameters use simple
|
||||||
encoding and which parameters use string encoding.
|
VLQ encoding and which parameters use string encoding.
|
||||||
|
|
||||||
Data Dictionary
|
Data Dictionary
|
||||||
===============
|
===============
|
||||||
|
|
||||||
In order for meaningful communications to be established between
|
In order for meaningful communications to be established between
|
||||||
firmware and host, both sides must agree on a "data dictionary". This
|
micro-controller and host, both sides must agree on a "data
|
||||||
data dictionary contains the integer identifiers for commands and
|
dictionary". This data dictionary contains the integer identifiers for
|
||||||
responses along with their descriptions.
|
commands and responses along with their descriptions.
|
||||||
|
|
||||||
At compile time the firmware build uses the contents of DECL_COMMAND()
|
The micro-controller build uses the contents of DECL_COMMAND() and
|
||||||
and sendf() macros to generate the data dictionary. The build
|
sendf() macros to generate the data dictionary. The build
|
||||||
automatically assigns unique identifiers to each command and
|
automatically assigns unique identifiers to each command and
|
||||||
response. This system allows both the host and firmware code to
|
response. This system allows both the host and micro-controller code
|
||||||
seamlessly use descriptive human-readable names while still using
|
to seamlessly use descriptive human-readable names while still using
|
||||||
minimal bandwidth.
|
minimal bandwidth.
|
||||||
|
|
||||||
The host queries the data dictionary when it first connects to the
|
The host queries the data dictionary when it first connects to the
|
||||||
firmware. Once the host downloads the data dictionary from the
|
micro-controller. Once the host downloads the data dictionary from the
|
||||||
firmware, it uses that data dictionary to encode all commands and to
|
micro-controller, it uses that data dictionary to encode all commands
|
||||||
parse all responses from the firmware. The host must therefore handle
|
and to parse all responses from the micro-controller. The host must
|
||||||
a dynamic data dictionary. However, to keep the firmware simple, the
|
therefore handle a dynamic data dictionary. However, to keep the
|
||||||
firmware always uses its static (compiled in) data dictionary.
|
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
|
The data dictionary is queried by sending "identify" commands to the
|
||||||
firmware. The firmware will respond to each identify command with an
|
micro-controller. The micro-controller will respond to each identify
|
||||||
"identify_response" message. Since these two commands are needed prior
|
command with an "identify_response" message. Since these two commands
|
||||||
to obtaining the data dictionary, their integer ids and parameter
|
are needed prior to obtaining the data dictionary, their integer ids
|
||||||
types are hard-coded in both the firmware and the host. The
|
and parameter types are hard-coded in both the micro-controller and
|
||||||
"identify_response" response id is 0, the "identify" command id
|
the host. The "identify_response" response id is 0, the "identify"
|
||||||
is 1. Other than having hard-coded ids the identify command and its
|
command id is 1. Other than having hard-coded ids the identify command
|
||||||
response are declared and transmitted the same way as other commands
|
and its response are declared and transmitted the same way as other
|
||||||
and responses. No other command or response is hard-coded.
|
commands and responses. No other command or response is hard-coded.
|
||||||
|
|
||||||
The format of the transmitted data dictionary itself is a zlib
|
The format of the transmitted data dictionary itself is a zlib
|
||||||
compressed JSON string. The firmware compile process generates the
|
compressed JSON string. The micro-controller build process generates
|
||||||
string, compresses it, and stores it in the text section of the
|
the string, compresses it, and stores it in the text section of the
|
||||||
firmware. The data dictionary can be much larger than the maximum
|
micro-controller flash. The data dictionary can be much larger than
|
||||||
message block size - the host downloads it by sending multiple
|
the maximum message block size - the host downloads it by sending
|
||||||
identify commands requesting progressive chunks of the data
|
multiple identify commands requesting progressive chunks of the data
|
||||||
dictionary. Once all chunks are obtained the host will assemble the
|
dictionary. Once all chunks are obtained the host will assemble the
|
||||||
chunks, uncompress the data, and parse the contents.
|
chunks, uncompress the data, and parse the contents.
|
||||||
|
|
||||||
In addition to information on the communication protocol, the data
|
In addition to information on the communication protocol, the data
|
||||||
dictionary also contains firmware version, constants (as defined by
|
dictionary also contains the software version, constants (as defined
|
||||||
DECL_CONSTANT), and static strings.
|
by DECL_CONSTANT), and static strings.
|
||||||
|
|
||||||
Static Strings
|
Static Strings
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
To reduce bandwidth the data dictionary also contains a set of static
|
To reduce bandwidth the data dictionary also contains a set of static
|
||||||
strings known to the firmware. This is useful when sending messages
|
strings known to the micro-controller. This is useful when sending
|
||||||
from firmware to host. For example, if the firmware were to run:
|
messages from micro-controller to host. For example, if the
|
||||||
|
micro-controller were to run:
|
||||||
|
|
||||||
```
|
```
|
||||||
shutdown("Unable to handle command");
|
shutdown("Unable to handle command");
|
||||||
@@ -296,22 +299,22 @@ to their associated human-readable strings.
|
|||||||
Message flow
|
Message flow
|
||||||
============
|
============
|
||||||
|
|
||||||
Message commands sent from host to firmware are intended to be
|
Message commands sent from host to micro-controller are intended to be
|
||||||
error-free. The firmware will check the CRC and sequence numbers in
|
error-free. The micro-controller will check the CRC and sequence
|
||||||
each message block to ensure the commands are accurate and
|
numbers in each message block to ensure the commands are accurate and
|
||||||
in-order. The firmware always processes message blocks in-order -
|
in-order. The micro-controller always processes message blocks
|
||||||
should it receive a block out-of-order it will discard it and any
|
in-order - should it receive a block out-of-order it will discard it
|
||||||
other out-of-order blocks until it receives blocks with the correct
|
and any other out-of-order blocks until it receives blocks with the
|
||||||
sequencing.
|
correct sequencing.
|
||||||
|
|
||||||
The low-level host code implements an automatic retransmission system
|
The low-level host code implements an automatic retransmission system
|
||||||
for lost and corrupt message blocks sent to the firmware. To
|
for lost and corrupt message blocks sent to the micro-controller. To
|
||||||
facilitate this, the firmware transmits an "ack message block" after
|
facilitate this, the micro-controller transmits an "ack message block"
|
||||||
each successfully received message block. The host schedules a timeout
|
after each successfully received message block. The host schedules a
|
||||||
after sending each block and it will retransmit should the timeout
|
timeout after sending each block and it will retransmit should the
|
||||||
expire without receiving a corresponding "ack". In addition, if the
|
timeout expire without receiving a corresponding "ack". In addition,
|
||||||
firmware detects a corrupt or out-of-order block it may transmit a
|
if the micro-controller detects a corrupt or out-of-order block it may
|
||||||
"nak message block" to facilitate fast retransmission.
|
transmit a "nak message block" to facilitate fast retransmission.
|
||||||
|
|
||||||
An "ack" is a message block with empty content (ie, a 5 byte message
|
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
|
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
|
windowing, and ack mechanism are inspired by similar mechanisms in
|
||||||
[TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol).
|
[TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol).
|
||||||
|
|
||||||
In the other direction, message blocks sent from firmware to host are
|
In the other direction, message blocks sent from micro-controller to
|
||||||
designed to be error-free, but they do not have assured
|
host are designed to be error-free, but they do not have assured
|
||||||
transmission. (Responses should not be corrupt, but they may go
|
transmission. (Responses should not be corrupt, but they may go
|
||||||
missing.) This is done to keep the implementation in the firmware
|
missing.) This is done to keep the implementation in the
|
||||||
simple. There is no automatic retransmission system for responses -
|
micro-controller simple. There is no automatic retransmission system
|
||||||
the high-level code is expected to be capable of handling an
|
for responses - the high-level code is expected to be capable of
|
||||||
occasional missing response (usually by re-requesting the content or
|
handling an occasional missing response (usually by re-requesting the
|
||||||
setting up a recurring schedule of response transmission). The
|
content or setting up a recurring schedule of response
|
||||||
sequence number field in message blocks sent to the host is always one
|
transmission). The sequence number field in message blocks sent to the
|
||||||
greater than the last received sequence number of message blocks
|
host is always one greater than the last received sequence number of
|
||||||
received from the host. It is not used to track sequences of response
|
message blocks received from the host. It is not used to track
|
||||||
message blocks.
|
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
|
History of Klipper releases. Please see
|
||||||
[installation](Installation.md) for information on installing Klipper.
|
[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
|
Klipper 0.3.0
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|||||||
54
docs/Todo.md
@@ -1,5 +1,5 @@
|
|||||||
Klipper is currently in an experimental state. There are several
|
There are several features still to be implemented in Klipper. In no
|
||||||
features still to be implemented. In no particular order:
|
particular order:
|
||||||
|
|
||||||
Host user interaction
|
Host user interaction
|
||||||
=====================
|
=====================
|
||||||
@@ -9,13 +9,6 @@ Host user interaction
|
|||||||
find the error) and errors written to the log can be non-obvious to
|
find the error) and errors written to the log can be non-obvious to
|
||||||
a user.
|
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:
|
* Improve gcode interface:
|
||||||
|
|
||||||
* Provide a better way to handle print nozzle z offsets. The M206
|
* Provide a better way to handle print nozzle z offsets. The M206
|
||||||
@@ -28,12 +21,6 @@ Host user interaction
|
|||||||
|
|
||||||
* Improve logging:
|
* 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
|
* Possibly collate and report the statistics messages in the log in a
|
||||||
more friendly way.
|
more friendly way.
|
||||||
|
|
||||||
@@ -44,12 +31,12 @@ Host user interaction
|
|||||||
Safety features
|
Safety features
|
||||||
===============
|
===============
|
||||||
|
|
||||||
* Support loading a valid step range into the firmware after
|
* Support loading a valid step range into the micro-controller
|
||||||
homing. This would provide a sanity check in the firmware that would
|
software after homing. This would provide a sanity check in the
|
||||||
reduce the risk of the host commanding a stepper motor past its
|
micro-controller that would reduce the risk of the host commanding a
|
||||||
valid step range. To maintain high efficiency in the firmware, the
|
stepper motor past its valid step range. To maintain high
|
||||||
firmware would only need to check periodically (eg, every 100ms)
|
efficiency, the micro-controller would only need to check
|
||||||
that the stepper is in range.
|
periodically (eg, every 100ms) that the stepper is in range.
|
||||||
|
|
||||||
* Possibly support periodically querying the endstop switches and use
|
* Possibly support periodically querying the endstop switches and use
|
||||||
multiple step ranges depending on the switch state. This would
|
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
|
can be useful to detect a sensor failure (eg, thermistor short) that
|
||||||
could otherwise cause the PID to command excessive heating.
|
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
|
Testing features
|
||||||
================
|
================
|
||||||
|
|
||||||
* Complete the host based simulator. It's possible to compile the
|
* Complete the host based simulator. It's possible to compile the
|
||||||
firmware for a "host simulator", but that simulator doesn't do
|
micro-controller for a "host simulator", but that simulator doesn't
|
||||||
anything currently. It would be useful to expand the code to support
|
do anything currently. It would be useful to expand the code to
|
||||||
more error checks, kinematic simulations, and improved logging.
|
support more error checks, kinematic simulations, and improved
|
||||||
|
logging.
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
=============
|
=============
|
||||||
@@ -81,20 +65,15 @@ Documentation
|
|||||||
* Add documentation describing how to perform bed-leveling accurately
|
* Add documentation describing how to perform bed-leveling accurately
|
||||||
in Klipper. Improve description of stepper phase based bed leveling.
|
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
|
Hardware features
|
||||||
=================
|
=================
|
||||||
|
|
||||||
* Port firmware to more architectures:
|
* Port to additional micro-controller architectures:
|
||||||
* Beagle Bone Black PRU
|
* Beagle Bone Black PRU
|
||||||
* Smoothieboard / NXP LPC1769 (ARM cortex-M3)
|
* Smoothieboard / NXP LPC1769 (ARM cortex-M3)
|
||||||
* Unix based scheduling; Unix based real-time scheduling
|
* 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.
|
* Support shared motor enable GPIO lines.
|
||||||
|
|
||||||
@@ -108,11 +87,6 @@ Hardware features
|
|||||||
it would also be useful to handle panels already hardwired to the
|
it would also be useful to handle panels already hardwired to the
|
||||||
micro-controller.)
|
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.
|
* Possibly support printers using multiple micro-controllers.
|
||||||
|
|
||||||
Misc features
|
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(
|
self.steppers = [stepper.PrinterStepper(
|
||||||
printer, config.getsection('stepper_' + n), n)
|
printer, config.getsection('stepper_' + n), n)
|
||||||
for n in ['x', 'y', 'z']]
|
for n in ['x', 'y', 'z']]
|
||||||
self.max_z_velocity = config.getfloat('max_z_velocity', 9999999.9)
|
self.max_z_velocity = config.getfloat(
|
||||||
self.max_z_accel = config.getfloat('max_z_accel', 9999999.9)
|
'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.need_motor_enable = True
|
||||||
self.limits = [(1.0, -1.0)] * 3
|
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[0].set_max_jerk(max_xy_halt_velocity, max_accel)
|
||||||
self.steppers[1].set_max_jerk(max_xy_halt_velocity, max_accel)
|
self.steppers[1].set_max_jerk(max_xy_halt_velocity, max_accel)
|
||||||
self.steppers[2].set_max_jerk(0., self.max_z_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):
|
def set_position(self, newpos):
|
||||||
for i in StepList:
|
for i in StepList:
|
||||||
s = self.steppers[i]
|
self.steppers[i].mcu_stepper.set_position(newpos[i])
|
||||||
s.mcu_stepper.set_position(int(newpos[i]*s.inv_step_dist + 0.5))
|
|
||||||
def home(self, homing_state):
|
def home(self, homing_state):
|
||||||
# Each axis is homed independently and in order
|
# Each axis is homed independently and in order
|
||||||
for axis in homing_state.get_axes():
|
for axis in homing_state.get_axes():
|
||||||
@@ -58,7 +56,7 @@ class CartKinematics:
|
|||||||
homing_state.home(
|
homing_state.home(
|
||||||
list(coord), homepos, [s], s.homing_speed/2.0, second_home=True)
|
list(coord), homepos, [s], s.homing_speed/2.0, second_home=True)
|
||||||
# Set final homed position
|
# 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)
|
homing_state.set_homed_position(coord)
|
||||||
def motor_off(self, move_time):
|
def motor_off(self, move_time):
|
||||||
self.limits = [(1.0, -1.0)] * 3
|
self.limits = [(1.0, -1.0)] * 3
|
||||||
@@ -102,46 +100,33 @@ class CartKinematics:
|
|||||||
def move(self, move_time, move):
|
def move(self, move_time, move):
|
||||||
if self.need_motor_enable:
|
if self.need_motor_enable:
|
||||||
self._check_motor_enable(move_time, move)
|
self._check_motor_enable(move_time, move)
|
||||||
inv_accel = 1. / move.accel
|
|
||||||
inv_cruise_v = 1. / move.cruise_v
|
|
||||||
for i in StepList:
|
for i in StepList:
|
||||||
if not move.axes_d[i]:
|
axis_d = move.axes_d[i]
|
||||||
|
if not axis_d:
|
||||||
continue
|
continue
|
||||||
mcu_stepper = self.steppers[i].mcu_stepper
|
mcu_stepper = self.steppers[i].mcu_stepper
|
||||||
mcu_time = mcu_stepper.print_to_mcu_time(move_time)
|
mcu_time = mcu_stepper.print_to_mcu_time(move_time)
|
||||||
step_pos = mcu_stepper.commanded_position
|
start_pos = move.start_pos[i]
|
||||||
inv_step_dist = self.steppers[i].inv_step_dist
|
axis_r = abs(axis_d) / move.move_d
|
||||||
step_offset = step_pos - move.start_pos[i] * inv_step_dist
|
accel = move.accel * axis_r
|
||||||
steps = move.axes_d[i] * inv_step_dist
|
cruise_v = move.cruise_v * axis_r
|
||||||
move_step_d = move.move_d / abs(steps)
|
|
||||||
|
|
||||||
# Acceleration steps
|
# Acceleration steps
|
||||||
accel_multiplier = 2.0 * move_step_d * inv_accel
|
|
||||||
if move.accel_r:
|
if move.accel_r:
|
||||||
#t = sqrt(2*pos/accel + (start_v/accel)**2) - start_v/accel
|
accel_d = move.accel_r * axis_d
|
||||||
accel_time_offset = move.start_v * inv_accel
|
mcu_stepper.step_const(
|
||||||
accel_sqrt_offset = accel_time_offset**2
|
mcu_time, start_pos, accel_d, move.start_v * axis_r, accel)
|
||||||
accel_steps = move.accel_r * steps
|
start_pos += accel_d
|
||||||
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_time += move.accel_t
|
mcu_time += move.accel_t
|
||||||
# Cruising steps
|
# Cruising steps
|
||||||
if move.cruise_r:
|
if move.cruise_r:
|
||||||
#t = pos/cruise_v
|
cruise_d = move.cruise_r * axis_d
|
||||||
cruise_multiplier = move_step_d * inv_cruise_v
|
mcu_stepper.step_const(
|
||||||
cruise_steps = move.cruise_r * steps
|
mcu_time, start_pos, cruise_d, cruise_v, 0.)
|
||||||
count = mcu_stepper.step_factor(
|
start_pos += cruise_d
|
||||||
mcu_time, cruise_steps, step_offset, cruise_multiplier)
|
|
||||||
step_offset += count - cruise_steps
|
|
||||||
mcu_time += move.cruise_t
|
mcu_time += move.cruise_t
|
||||||
# Deceleration steps
|
# Deceleration steps
|
||||||
if move.decel_r:
|
if move.decel_r:
|
||||||
#t = cruise_v/accel - sqrt((cruise_v/accel)**2 - 2*pos/accel)
|
decel_d = move.decel_r * axis_d
|
||||||
decel_time_offset = move.cruise_v * inv_accel
|
mcu_stepper.step_const(
|
||||||
decel_sqrt_offset = decel_time_offset**2
|
mcu_time, start_pos, decel_d, cruise_v, -accel)
|
||||||
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)
|
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
# Wrapper around C helper code
|
# 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.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import os, logging
|
import os, logging
|
||||||
import cffi
|
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']
|
SOURCE_FILES = ['stepcompress.c', 'serialqueue.c', 'pyhelper.c']
|
||||||
DEST_LIB = "c_helper.so"
|
DEST_LIB = "c_helper.so"
|
||||||
OTHER_FILES = ['list.h', 'serialqueue.h', 'pyhelper.h']
|
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 queue_step_msgid, uint32_t set_next_step_dir_msgid
|
||||||
, uint32_t invert_sdir, uint32_t oid);
|
, uint32_t invert_sdir, uint32_t oid);
|
||||||
void stepcompress_free(struct stepcompress *sc);
|
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 sdir);
|
||||||
int32_t stepcompress_push_factor(struct stepcompress *sc
|
int32_t stepcompress_push_const(struct stepcompress *sc, double clock_offset
|
||||||
, double steps, double step_offset
|
, double step_offset, double steps, double start_sv, double accel);
|
||||||
, double clock_offset, double factor);
|
int32_t stepcompress_push_delta(struct stepcompress *sc
|
||||||
int32_t stepcompress_push_sqrt(struct stepcompress *sc
|
, double clock_offset, double move_sd, double start_sv, double accel
|
||||||
, double steps, double step_offset
|
, double height, double startxy_sd, double arm_d, double movez_r);
|
||||||
, 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);
|
|
||||||
|
|
||||||
struct steppersync *steppersync_alloc(struct serialqueue *sq
|
struct steppersync *steppersync_alloc(struct serialqueue *sq
|
||||||
, struct stepcompress **sc_list, int sc_num, int move_num);
|
, struct stepcompress **sc_list, int sc_num, int move_num);
|
||||||
void steppersync_free(struct steppersync *ss);
|
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 = """
|
defs_serialqueue = """
|
||||||
@@ -65,7 +61,6 @@ defs_serialqueue = """
|
|||||||
void serialqueue_set_baud_adjust(struct serialqueue *sq, double baud_adjust);
|
void serialqueue_set_baud_adjust(struct serialqueue *sq, double baud_adjust);
|
||||||
void serialqueue_set_clock_est(struct serialqueue *sq, double est_clock
|
void serialqueue_set_clock_est(struct serialqueue *sq, double est_clock
|
||||||
, double last_ack_time, uint64_t last_ack_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);
|
void serialqueue_get_stats(struct serialqueue *sq, char *buf, int len);
|
||||||
int serialqueue_extract_old(struct serialqueue *sq, int sentq
|
int serialqueue_extract_old(struct serialqueue *sq, int sentq
|
||||||
, struct pull_queue_message *q, int max);
|
, struct pull_queue_message *q, int max);
|
||||||
@@ -73,6 +68,7 @@ defs_serialqueue = """
|
|||||||
|
|
||||||
defs_pyhelper = """
|
defs_pyhelper = """
|
||||||
void set_python_logging_callback(void (*func)(const char *));
|
void set_python_logging_callback(void (*func)(const char *));
|
||||||
|
double get_monotonic(void);
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Return the list of file modification times
|
# Return the list of file modification times
|
||||||
@@ -88,14 +84,14 @@ def get_mtimes(srcdir, filelist):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
# Check if the code needs to be compiled
|
# Check if the code needs to be compiled
|
||||||
def check_build_code(srcdir):
|
def check_build_code(srcdir, target, sources, cmd, other_files=[]):
|
||||||
src_times = get_mtimes(srcdir, SOURCE_FILES + OTHER_FILES)
|
src_times = get_mtimes(srcdir, sources + other_files)
|
||||||
obj_times = get_mtimes(srcdir, [DEST_LIB])
|
obj_times = get_mtimes(srcdir, [target])
|
||||||
if not obj_times or max(src_times) > min(obj_times):
|
if not obj_times or max(src_times) > min(obj_times):
|
||||||
logging.info("Building C code module")
|
logging.info("Building C code module %s" % (target,))
|
||||||
srcfiles = [os.path.join(srcdir, fname) for fname in SOURCE_FILES]
|
srcfiles = [os.path.join(srcdir, fname) for fname in sources]
|
||||||
destlib = os.path.join(srcdir, DEST_LIB)
|
destlib = os.path.join(srcdir, target)
|
||||||
os.system(COMPILE_CMD % (destlib, ' '.join(srcfiles)))
|
os.system(cmd % (destlib, ' '.join(srcfiles)))
|
||||||
|
|
||||||
FFI_main = None
|
FFI_main = None
|
||||||
FFI_lib = None
|
FFI_lib = None
|
||||||
@@ -106,7 +102,8 @@ def get_ffi():
|
|||||||
global FFI_main, FFI_lib, pyhelper_logging_callback
|
global FFI_main, FFI_lib, pyhelper_logging_callback
|
||||||
if FFI_lib is None:
|
if FFI_lib is None:
|
||||||
srcdir = os.path.dirname(os.path.realpath(__file__))
|
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 = cffi.FFI()
|
||||||
FFI_main.cdef(defs_stepcompress)
|
FFI_main.cdef(defs_stepcompress)
|
||||||
FFI_main.cdef(defs_serialqueue)
|
FFI_main.cdef(defs_serialqueue)
|
||||||
@@ -119,3 +116,20 @@ def get_ffi():
|
|||||||
"void(const char *)", logging_callback)
|
"void(const char *)", logging_callback)
|
||||||
FFI_lib.set_python_logging_callback(pyhelper_logging_callback)
|
FFI_lib.set_python_logging_callback(pyhelper_logging_callback)
|
||||||
return FFI_main, FFI_lib
|
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
|
#!/usr/bin/env python
|
||||||
# Script to implement a test console with firmware over serial port
|
# 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.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import sys, optparse, os, re, logging
|
import sys, optparse, os, re, logging
|
||||||
@@ -16,6 +16,7 @@ class KeyboardReader:
|
|||||||
self.reactor = reactor
|
self.reactor = reactor
|
||||||
self.fd = sys.stdin.fileno()
|
self.fd = sys.stdin.fileno()
|
||||||
util.set_nonblock(self.fd)
|
util.set_nonblock(self.fd)
|
||||||
|
self.mcu_freq = 0
|
||||||
self.pins = None
|
self.pins = None
|
||||||
self.data = ""
|
self.data = ""
|
||||||
reactor.register_fd(self.fd, self.process_kbd)
|
reactor.register_fd(self.fd, self.process_kbd)
|
||||||
@@ -24,49 +25,51 @@ class KeyboardReader:
|
|||||||
self.eval_globals = {}
|
self.eval_globals = {}
|
||||||
def connect(self, eventtime):
|
def connect(self, eventtime):
|
||||||
self.ser.connect()
|
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)
|
self.reactor.unregister_timer(self.connect_timer)
|
||||||
return self.reactor.NEVER
|
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):
|
def update_evals(self, eventtime):
|
||||||
f = int(self.ser.msgparser.config.get('CLOCK_FREQ', 1))
|
self.eval_globals['freq'] = self.mcu_freq
|
||||||
c = self.ser.get_clock(eventtime)
|
self.eval_globals['clock'] = self.ser.get_clock(eventtime)
|
||||||
self.eval_globals['freq'] = f
|
|
||||||
self.eval_globals['clock'] = int(c)
|
|
||||||
def set_pin_map(self, parts):
|
def set_pin_map(self, parts):
|
||||||
mcu = self.ser.msgparser.config['MCU']
|
mcu = self.ser.msgparser.get_constant('MCU')
|
||||||
self.pins = pins.map_pins(parts[1], mcu)
|
self.pins = pins.get_pin_map(mcu, parts[1])
|
||||||
def set_var(self, parts):
|
def set_var(self, parts):
|
||||||
val = parts[2]
|
val = parts[2]
|
||||||
try:
|
|
||||||
val = int(val)
|
|
||||||
except ValueError:
|
|
||||||
try:
|
try:
|
||||||
val = float(val)
|
val = float(val)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
self.eval_globals[parts[1]] = val
|
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):
|
def translate(self, line, eventtime):
|
||||||
evalparts = re_eval.split(line)
|
evalparts = re_eval.split(line)
|
||||||
if len(evalparts) > 1:
|
if len(evalparts) > 1:
|
||||||
self.update_evals(eventtime)
|
self.update_evals(eventtime)
|
||||||
try:
|
try:
|
||||||
for i in range(1, len(evalparts), 2):
|
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:
|
except:
|
||||||
print "Unable to evaluate: ", line
|
self.output("Unable to evaluate: %s" % (line,))
|
||||||
return None
|
return None
|
||||||
line = ''.join(evalparts)
|
line = ''.join(evalparts)
|
||||||
print "Eval:", line
|
self.output("Eval: %s" % (line,))
|
||||||
if self.pins is None and self.ser.msgparser.config:
|
|
||||||
self.pins = pins.mcu_to_pins(self.ser.msgparser.config['MCU'])
|
|
||||||
if self.pins is not None:
|
if self.pins is not None:
|
||||||
try:
|
try:
|
||||||
line = pins.update_command(line, self.pins).strip()
|
line = pins.update_command(
|
||||||
|
line, self.mcu_freq, self.pins).strip()
|
||||||
except:
|
except:
|
||||||
print "Unable to map pin: ", line
|
self.output("Unable to map pin: %s" % (line,))
|
||||||
return None
|
return None
|
||||||
if line:
|
if line:
|
||||||
parts = line.split()
|
parts = line.split()
|
||||||
@@ -76,7 +79,7 @@ class KeyboardReader:
|
|||||||
try:
|
try:
|
||||||
msg = self.ser.msgparser.create_command(line)
|
msg = self.ser.msgparser.create_command(line)
|
||||||
except msgproto.error, e:
|
except msgproto.error, e:
|
||||||
print "Error:", e
|
self.output("Error: %s" % (str(e),))
|
||||||
return None
|
return None
|
||||||
return msg
|
return msg
|
||||||
def process_kbd(self, eventtime):
|
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
|
# 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.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import math, logging
|
import math, logging
|
||||||
@@ -8,40 +8,61 @@ import stepper, homing
|
|||||||
|
|
||||||
StepList = (0, 1, 2)
|
StepList = (0, 1, 2)
|
||||||
|
|
||||||
|
# Slow moves once the ratio of tower to XY movement exceeds SLOW_RATIO
|
||||||
|
SLOW_RATIO = 3.
|
||||||
|
|
||||||
class DeltaKinematics:
|
class DeltaKinematics:
|
||||||
def __init__(self, printer, config):
|
def __init__(self, printer, config):
|
||||||
|
self.config = config
|
||||||
self.steppers = [stepper.PrinterStepper(
|
self.steppers = [stepper.PrinterStepper(
|
||||||
printer, config.getsection('stepper_' + n), n)
|
printer, config.getsection('stepper_' + n), n)
|
||||||
for n in ['a', 'b', 'c']]
|
for n in ['a', 'b', 'c']]
|
||||||
self.need_motor_enable = True
|
self.need_motor_enable = self.need_home = True
|
||||||
self.max_z_velocity = config.getfloat('max_z_velocity', 9999999.9)
|
self.max_velocity = self.max_z_velocity = self.max_accel = 0.
|
||||||
radius = config.getfloat('delta_radius')
|
radius = config.getfloat('delta_radius', above=0.)
|
||||||
arm_length = config.getfloat('delta_arm_length')
|
arm_length = config.getfloat('delta_arm_length', above=radius)
|
||||||
self.arm_length2 = arm_length**2
|
self.arm_length2 = arm_length**2
|
||||||
self.max_xy2 = min(radius, arm_length - radius)**2
|
|
||||||
self.limit_xy2 = -1.
|
self.limit_xy2 = -1.
|
||||||
tower_height_at_zeros = math.sqrt(self.arm_length2 - radius**2)
|
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)
|
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))
|
sin = lambda angle: math.sin(math.radians(angle))
|
||||||
cos = lambda angle: math.cos(math.radians(angle))
|
cos = lambda angle: math.cos(math.radians(angle))
|
||||||
self.towers = [
|
self.towers = [
|
||||||
(cos(210.)*radius, sin(210.)*radius),
|
(cos(210.)*radius, sin(210.)*radius),
|
||||||
(cos(330.)*radius, sin(330.)*radius),
|
(cos(330.)*radius, sin(330.)*radius),
|
||||||
(cos(90.)*radius, sin(90.)*radius)]
|
(cos(90.)*radius, sin(90.)*radius)]
|
||||||
def set_max_jerk(self, max_xy_halt_velocity, max_accel):
|
# Find the point where an XY move could result in excessive
|
||||||
# XXX - this sets conservative values
|
# 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:
|
for stepper in self.steppers:
|
||||||
stepper.set_max_jerk(max_xy_halt_velocity, max_accel)
|
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):
|
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][0] - coord[0])**2
|
||||||
- (self.towers[i][1] - coord[1])**2) + coord[2])
|
- (self.towers[i][1] - coord[1])**2) + coord[2]
|
||||||
* self.steppers[i].inv_step_dist + 0.5)
|
|
||||||
for i in StepList]
|
for i in StepList]
|
||||||
def _actuator_to_cartesian(self, pos):
|
def _actuator_to_cartesian(self, pos):
|
||||||
# Based on code from Smoothieware
|
# Based on code from Smoothieware
|
||||||
@@ -78,13 +99,14 @@ class DeltaKinematics:
|
|||||||
pos = self._cartesian_to_actuator(newpos)
|
pos = self._cartesian_to_actuator(newpos)
|
||||||
for i in StepList:
|
for i in StepList:
|
||||||
self.steppers[i].mcu_stepper.set_position(pos[i])
|
self.steppers[i].mcu_stepper.set_position(pos[i])
|
||||||
|
self.limit_xy2 = -1.
|
||||||
def home(self, homing_state):
|
def home(self, homing_state):
|
||||||
# All axes are homed simultaneously
|
# All axes are homed simultaneously
|
||||||
homing_state.set_axes([0, 1, 2])
|
homing_state.set_axes([0, 1, 2])
|
||||||
s = self.steppers[0] # Assume homing parameters same for all steppers
|
s = self.steppers[0] # Assume homing speed same for all steppers
|
||||||
self.limit_xy2 = self.max_xy2
|
self.need_home = False
|
||||||
# Initial homing
|
# Initial homing
|
||||||
homepos = [0., 0., s.position_endstop, None]
|
homepos = [0., 0., self.max_z, None]
|
||||||
coord = list(homepos)
|
coord = list(homepos)
|
||||||
coord[2] = -1.5 * math.sqrt(self.arm_length2-self.max_xy2)
|
coord[2] = -1.5 * math.sqrt(self.arm_length2-self.max_xy2)
|
||||||
homing_state.home(list(coord), homepos, self.steppers, s.homing_speed)
|
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
|
homing_state.home(list(coord), homepos, self.steppers
|
||||||
, s.homing_speed/2.0, second_home=True)
|
, s.homing_speed/2.0, second_home=True)
|
||||||
# Set final homed position
|
# Set final homed position
|
||||||
coord = [(s.mcu_stepper.commanded_position + s.get_homed_offset())
|
coord = [s.mcu_stepper.get_commanded_position() + s.get_homed_offset()
|
||||||
* s.step_dist
|
|
||||||
for s in self.steppers]
|
for s in self.steppers]
|
||||||
homing_state.set_homed_position(self._actuator_to_cartesian(coord))
|
homing_state.set_homed_position(self._actuator_to_cartesian(coord))
|
||||||
def motor_off(self, move_time):
|
def motor_off(self, move_time):
|
||||||
self.limit_xy2 = -1.
|
self.limit_xy2 = -1.
|
||||||
for stepper in self.steppers:
|
for stepper in self.steppers:
|
||||||
stepper.motor_enable(move_time, 0)
|
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):
|
def _check_motor_enable(self, move_time):
|
||||||
for i in StepList:
|
for i in StepList:
|
||||||
self.steppers[i].motor_enable(move_time, 1)
|
self.steppers[i].motor_enable(move_time, 1)
|
||||||
@@ -115,127 +136,90 @@ class DeltaKinematics:
|
|||||||
def check_move(self, move):
|
def check_move(self, move):
|
||||||
end_pos = move.end_pos
|
end_pos = move.end_pos
|
||||||
xy2 = end_pos[0]**2 + end_pos[1]**2
|
xy2 = end_pos[0]**2 + end_pos[1]**2
|
||||||
if xy2 > self.limit_xy2 or end_pos[2] < 0.:
|
if xy2 <= self.limit_xy2 and not move.axes_d[2]:
|
||||||
if self.limit_xy2 < 0.:
|
# Normal XY move
|
||||||
|
return
|
||||||
|
if self.need_home:
|
||||||
raise homing.EndstopMoveError(end_pos, "Must home first")
|
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.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)
|
raise homing.EndstopMoveError(end_pos)
|
||||||
if move.axes_d[2]:
|
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):
|
def move(self, move_time, move):
|
||||||
|
if self.need_motor_enable:
|
||||||
|
self._check_motor_enable(move_time)
|
||||||
axes_d = move.axes_d
|
axes_d = move.axes_d
|
||||||
move_d = movexy_d = move.move_d
|
move_d = move.move_d
|
||||||
movexy_r = 1.
|
movexy_r = 1.
|
||||||
movez_r = 0.
|
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[0] and not axes_d[1]:
|
||||||
if not axes_d[2]:
|
# Z only move
|
||||||
return
|
|
||||||
movez_r = axes_d[2] * inv_movexy_d
|
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]:
|
elif axes_d[2]:
|
||||||
|
# XY+Z move
|
||||||
movexy_d = math.sqrt(axes_d[0]**2 + axes_d[1]**2)
|
movexy_d = math.sqrt(axes_d[0]**2 + axes_d[1]**2)
|
||||||
movexy_r = movexy_d * inv_movexy_d
|
movexy_r = movexy_d * inv_movexy_d
|
||||||
movez_r = axes_d[2] * inv_movexy_d
|
movez_r = axes_d[2] * inv_movexy_d
|
||||||
inv_movexy_d = 1. / 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]
|
origx, origy, origz = move.start_pos[:3]
|
||||||
|
|
||||||
accel_t = move.accel_t
|
accel = move.accel
|
||||||
cruise_end_t = accel_t + move.cruise_t
|
cruise_v = move.cruise_v
|
||||||
accel_d = move.accel_r * move_d
|
accel_d = move.accel_r * move_d
|
||||||
cruise_end_d = accel_d + move.cruise_r * move_d
|
cruise_d = move.cruise_r * move_d
|
||||||
|
decel_d = move.decel_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
|
|
||||||
|
|
||||||
for i in StepList:
|
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
|
towerx_d = self.towers[i][0] - origx
|
||||||
towery_d = self.towers[i][1] - origy
|
towery_d = self.towers[i][1] - origy
|
||||||
closestxy_d = (towerx_d*axes_d[0] + towery_d*axes_d[1])*inv_movexy_d
|
vt_startxy_d = (towerx_d*axes_d[0] + towery_d*axes_d[1])*inv_movexy_d
|
||||||
tangentxy_d2 = towerx_d**2 + towery_d**2 - closestxy_d**2
|
tangentxy_d2 = towerx_d**2 + towery_d**2 - vt_startxy_d**2
|
||||||
closest_height2 = self.arm_length2 - tangentxy_d2
|
vt_arm_d = math.sqrt(self.arm_length2 - tangentxy_d2)
|
||||||
|
vt_startz = origz
|
||||||
# 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
|
|
||||||
|
|
||||||
# Generate steps
|
# Generate steps
|
||||||
mcu_stepper = self.steppers[i].mcu_stepper
|
mcu_stepper = self.steppers[i].mcu_stepper
|
||||||
mcu_time = mcu_stepper.print_to_mcu_time(move_time)
|
mcu_time = mcu_stepper.print_to_mcu_time(move_time)
|
||||||
step_pos = mcu_stepper.commanded_position
|
if accel_d:
|
||||||
step_dist = self.steppers[i].step_dist
|
mcu_stepper.step_delta(
|
||||||
height = step_pos*step_dist - origz
|
mcu_time, accel_d, move.start_v, accel,
|
||||||
if accel_up_d > 0.:
|
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
|
||||||
count = mcu_stepper.step_delta_accel(
|
vt_startz += accel_d * movez_r
|
||||||
mcu_time - accel_time_offset, accel_up_d,
|
vt_startxy_d -= accel_d * movexy_r
|
||||||
accel_offset, accel_multiplier, step_dist,
|
mcu_time += move.accel_t
|
||||||
height, closestxy_d, closest_height2, movez_r)
|
if cruise_d:
|
||||||
height += count * step_dist
|
mcu_stepper.step_delta(
|
||||||
if cruise_up_d > 0.:
|
mcu_time, cruise_d, cruise_v, 0.,
|
||||||
count = mcu_stepper.step_delta_const(
|
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
|
||||||
mcu_time + accel_t, cruise_up_d,
|
vt_startz += cruise_d * movez_r
|
||||||
-accel_d, inv_cruise_v, step_dist,
|
vt_startxy_d -= cruise_d * movexy_r
|
||||||
height, closestxy_d, closest_height2, movez_r)
|
mcu_time += move.cruise_t
|
||||||
height += count * step_dist
|
if decel_d:
|
||||||
if decel_up_d > 0.:
|
mcu_stepper.step_delta(
|
||||||
count = mcu_stepper.step_delta_accel(
|
mcu_time, decel_d, cruise_v, -accel,
|
||||||
mcu_time + decel_time_offset, decel_up_d,
|
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
|
||||||
-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)
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|||||||
@@ -3,45 +3,127 @@
|
|||||||
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging
|
import math, logging
|
||||||
import stepper, heater, homing
|
import stepper, heater, homing
|
||||||
|
|
||||||
|
EXTRUDE_DIFF_IGNORE = 1.02
|
||||||
|
|
||||||
class PrinterExtruder:
|
class PrinterExtruder:
|
||||||
def __init__(self, printer, config):
|
def __init__(self, printer, config):
|
||||||
|
self.config = config
|
||||||
self.heater = heater.PrinterHeater(printer, config)
|
self.heater = heater.PrinterHeater(printer, config)
|
||||||
self.stepper = stepper.PrinterStepper(printer, config, 'extruder')
|
self.stepper = stepper.PrinterStepper(printer, config, 'extruder')
|
||||||
self.pressure_advance = config.getfloat('pressure_advance', 0.)
|
self.nozzle_diameter = config.getfloat('nozzle_diameter', above=0.)
|
||||||
self.max_e_velocity = config.getfloat('max_velocity')
|
filament_diameter = config.getfloat(
|
||||||
self.max_e_accel = config.getfloat('max_accel')
|
'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.need_motor_enable = True
|
||||||
self.extrude_pos = 0.
|
self.extrude_pos = 0.
|
||||||
def build_config(self):
|
def set_max_jerk(self, max_xy_halt_velocity, max_velocity, max_accel):
|
||||||
self.heater.build_config()
|
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.set_max_jerk(9999999.9, 9999999.9)
|
||||||
self.stepper.build_config()
|
|
||||||
def motor_off(self, move_time):
|
def motor_off(self, move_time):
|
||||||
self.stepper.motor_enable(move_time, 0)
|
self.stepper.motor_enable(move_time, 0)
|
||||||
self.need_motor_enable = True
|
self.need_motor_enable = True
|
||||||
def check_move(self, move):
|
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:
|
if not self.heater.can_extrude:
|
||||||
raise homing.EndstopMoveError(
|
raise homing.EndstopMoveError(
|
||||||
move.end_pos, "Extrude below minimum temp")
|
move.end_pos, "Extrude below minimum temp")
|
||||||
if (not move.do_calc_junction
|
if not move.is_kinematic_move or move.extrude_r < 0.:
|
||||||
and not move.axes_d[0] and not move.axes_d[1]
|
# Extrude only move (or retraction move) - limit accel and velocity
|
||||||
and not move.axes_d[2]):
|
if abs(move.axes_d[3]) > self.max_e_dist:
|
||||||
# Extrude only move - limit accel and velocity
|
raise homing.EndstopMoveError(
|
||||||
move.limit_speed(self.max_e_velocity, self.max_e_accel)
|
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):
|
def move(self, move_time, move):
|
||||||
if self.need_motor_enable:
|
if self.need_motor_enable:
|
||||||
self.stepper.motor_enable(move_time, 1)
|
self.stepper.motor_enable(move_time, 1)
|
||||||
self.need_motor_enable = False
|
self.need_motor_enable = False
|
||||||
axis_d = move.axes_d[3]
|
axis_d = move.axes_d[3]
|
||||||
extrude_r = abs(axis_d) / move.move_d
|
axis_r = abs(axis_d) / move.move_d
|
||||||
inv_accel = 1. / (move.accel * extrude_r)
|
accel = move.accel * axis_r
|
||||||
|
start_v = move.start_v * axis_r
|
||||||
start_v = move.start_v * extrude_r
|
cruise_v = move.cruise_v * axis_r
|
||||||
cruise_v = move.cruise_v * extrude_r
|
end_v = move.end_v * axis_r
|
||||||
end_v = move.end_v * extrude_r
|
|
||||||
accel_t, cruise_t, decel_t = move.accel_t, move.cruise_t, move.decel_t
|
accel_t, cruise_t, decel_t = move.accel_t, move.cruise_t, move.decel_t
|
||||||
accel_d = move.accel_r * axis_d
|
accel_d = move.accel_r * axis_d
|
||||||
cruise_d = move.cruise_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])
|
if (axis_d >= 0. and (move.axes_d[0] or move.axes_d[1])
|
||||||
and self.pressure_advance):
|
and self.pressure_advance):
|
||||||
# Increase accel_d and start_v when accelerating
|
# 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]
|
prev_pressure_d = start_pos - move.start_pos[3]
|
||||||
if accel_t:
|
if accel_d:
|
||||||
npd = move.cruise_v * move_extrude_r * self.pressure_advance
|
npd = move.cruise_v * pressure_advance
|
||||||
extra_accel_d = npd - prev_pressure_d
|
extra_accel_d = npd - prev_pressure_d
|
||||||
if extra_accel_d > 0.:
|
if extra_accel_d > 0.:
|
||||||
accel_d += extra_accel_d
|
accel_d += extra_accel_d
|
||||||
start_v += extra_accel_d / accel_t
|
start_v += extra_accel_d / accel_t
|
||||||
prev_pressure_d += extra_accel_d
|
prev_pressure_d += extra_accel_d
|
||||||
# Update decel and retract parameters when decelerating
|
# Update decel and retract parameters when decelerating
|
||||||
if decel_t:
|
emcv = move.extrude_max_corner_v
|
||||||
if move.corner_min:
|
if decel_d and emcv < move.cruise_v:
|
||||||
npd = move.corner_max*move_extrude_r * self.pressure_advance
|
npd = max(emcv, move.end_v) * 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
|
|
||||||
extra_decel_d = prev_pressure_d - npd
|
extra_decel_d = prev_pressure_d - npd
|
||||||
if extra_decel_d > 0.:
|
if extra_decel_d > 0.:
|
||||||
extra_decel_v = extra_decel_d / decel_t
|
extra_decel_v = extra_decel_d / decel_t
|
||||||
@@ -87,7 +163,7 @@ class PrinterExtruder:
|
|||||||
decel_t = decel_d = 0.
|
decel_t = decel_d = 0.
|
||||||
elif end_v < 0.:
|
elif end_v < 0.:
|
||||||
# Split decel phase into decel and retraction
|
# 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
|
retract_d = -end_v * 0.5 * retract_t
|
||||||
decel_t -= retract_t
|
decel_t -= retract_t
|
||||||
decel_d = decel_v * 0.5 * decel_t
|
decel_d = decel_v * 0.5 * decel_t
|
||||||
@@ -96,53 +172,41 @@ class PrinterExtruder:
|
|||||||
decel_d -= extra_decel_d
|
decel_d -= extra_decel_d
|
||||||
|
|
||||||
# Prepare for steps
|
# Prepare for steps
|
||||||
inv_step_dist = self.stepper.inv_step_dist
|
|
||||||
step_dist = self.stepper.step_dist
|
|
||||||
mcu_stepper = self.stepper.mcu_stepper
|
mcu_stepper = self.stepper.mcu_stepper
|
||||||
mcu_time = mcu_stepper.print_to_mcu_time(move_time)
|
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
|
# Acceleration steps
|
||||||
accel_multiplier = 2.0 * step_dist * inv_accel
|
|
||||||
if accel_d:
|
if accel_d:
|
||||||
#t = sqrt(2*pos/accel + (start_v/accel)**2) - start_v/accel
|
mcu_stepper.step_const(mcu_time, start_pos, accel_d, start_v, accel)
|
||||||
accel_time_offset = start_v * inv_accel
|
start_pos += accel_d
|
||||||
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_time += accel_t
|
mcu_time += accel_t
|
||||||
# Cruising steps
|
# Cruising steps
|
||||||
if cruise_d:
|
if cruise_d:
|
||||||
#t = pos/cruise_v
|
mcu_stepper.step_const(mcu_time, start_pos, cruise_d, cruise_v, 0.)
|
||||||
cruise_multiplier = step_dist / cruise_v
|
start_pos += cruise_d
|
||||||
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_time += cruise_t
|
mcu_time += cruise_t
|
||||||
# Deceleration steps
|
# Deceleration steps
|
||||||
if decel_d:
|
if decel_d:
|
||||||
#t = cruise_v/accel - sqrt((cruise_v/accel)**2 - 2*pos/accel)
|
mcu_stepper.step_const(mcu_time, start_pos, decel_d, decel_v, -accel)
|
||||||
decel_time_offset = decel_v * inv_accel
|
start_pos += decel_d
|
||||||
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_time += decel_t
|
mcu_time += decel_t
|
||||||
# Retraction steps
|
# Retraction steps
|
||||||
if retract_d:
|
if retract_d:
|
||||||
#t = sqrt(2*pos/accel + (start_v/accel)**2) - start_v/accel
|
mcu_stepper.step_const(
|
||||||
accel_time_offset = retract_v * inv_accel
|
mcu_time, start_pos, -retract_d, retract_v, accel)
|
||||||
accel_sqrt_offset = accel_time_offset**2
|
start_pos -= retract_d
|
||||||
accel_steps = -retract_d * inv_step_dist
|
self.extrude_pos = start_pos
|
||||||
count = mcu_stepper.step_sqrt(
|
|
||||||
mcu_time - accel_time_offset, accel_steps, step_offset
|
|
||||||
, accel_sqrt_offset, accel_multiplier)
|
|
||||||
|
|
||||||
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.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
FAN_MIN_TIME = 0.1
|
FAN_MIN_TIME = 0.1
|
||||||
|
PWM_CYCLE_TIME = 0.010
|
||||||
|
|
||||||
class PrinterFan:
|
class PrinterFan:
|
||||||
def __init__(self, printer, config):
|
def __init__(self, printer, config):
|
||||||
self.printer = printer
|
self.last_fan_value = 0.
|
||||||
self.config = config
|
|
||||||
self.mcu_fan = None
|
|
||||||
self.last_fan_value = 0
|
|
||||||
self.last_fan_time = 0.
|
self.last_fan_time = 0.
|
||||||
self.kick_start_time = config.getfloat('kick_start_time', 0.1)
|
self.kick_start_time = config.getfloat('kick_start_time', 0.1, minval=0.)
|
||||||
def build_config(self):
|
pin = config.get('pin')
|
||||||
pin = self.config.get('pin')
|
hard_pwm = config.getint('hard_pwm', 0)
|
||||||
hard_pwm = self.config.getint('hard_pwm', 128)
|
self.mcu_fan = printer.mcu.create_pwm(pin, PWM_CYCLE_TIME, hard_pwm, 0.)
|
||||||
self.mcu_fan = self.printer.mcu.create_pwm(pin, hard_pwm, 0)
|
|
||||||
# External commands
|
# External commands
|
||||||
def set_speed(self, print_time, value):
|
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:
|
if value == self.last_fan_value:
|
||||||
return
|
return
|
||||||
mcu_time = self.mcu_fan.print_to_mcu_time(print_time)
|
mcu_time = self.mcu_fan.print_to_mcu_time(print_time)
|
||||||
mcu_time = max(self.last_fan_time + FAN_MIN_TIME, mcu_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):
|
and not self.last_fan_value and self.kick_start_time):
|
||||||
# Run fan at full speed for specified 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
|
mcu_time += self.kick_start_time
|
||||||
self.mcu_fan.set_pwm(mcu_time, value)
|
self.mcu_fan.set_pwm(mcu_time, value)
|
||||||
self.last_fan_time = mcu_time
|
self.last_fan_time = mcu_time
|
||||||
|
|||||||
213
klippy/gcode.py
@@ -3,7 +3,7 @@
|
|||||||
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import os, re, logging, collections, time
|
import os, re, logging, collections
|
||||||
import homing
|
import homing
|
||||||
|
|
||||||
# Parse out incoming GCode and find and translate head movements
|
# Parse out incoming GCode and find and translate head movements
|
||||||
@@ -23,7 +23,7 @@ class GCodeParser:
|
|||||||
self.bytes_read = 0
|
self.bytes_read = 0
|
||||||
self.input_log = collections.deque([], 50)
|
self.input_log = collections.deque([], 50)
|
||||||
# Command handling
|
# Command handling
|
||||||
self.gcode_handlers = {}
|
self.gcode_handlers = self.build_handlers(False)
|
||||||
self.is_printer_ready = False
|
self.is_printer_ready = False
|
||||||
self.need_ack = False
|
self.need_ack = False
|
||||||
self.toolhead = self.heater_nozzle = self.heater_bed = self.fan = None
|
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.last_position = [0.0, 0.0, 0.0, 0.0]
|
||||||
self.homing_add = [0.0, 0.0, 0.0, 0.0]
|
self.homing_add = [0.0, 0.0, 0.0, 0.0]
|
||||||
self.axis2pos = {'X': 0, 'Y': 1, 'Z': 2, 'E': 3}
|
self.axis2pos = {'X': 0, 'Y': 1, 'Z': 2, 'E': 3}
|
||||||
self.build_handlers()
|
def build_handlers(self, is_ready):
|
||||||
def build_config(self):
|
handlers = self.all_handlers
|
||||||
self.toolhead = self.printer.objects['toolhead']
|
if not is_ready:
|
||||||
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:
|
|
||||||
handlers = [h for h in handlers
|
handlers = [h for h in handlers
|
||||||
if getattr(self, 'cmd_'+h+'_when_not_ready', False)]
|
if getattr(self, 'cmd_'+h+'_when_not_ready', False)]
|
||||||
self.gcode_handlers = dict((h, getattr(self, 'cmd_'+h))
|
gcode_handlers = dict((h, getattr(self, 'cmd_'+h)) for h in handlers)
|
||||||
for h in handlers)
|
for h, f in gcode_handlers.items():
|
||||||
for h, f in self.gcode_handlers.items():
|
|
||||||
aliases = getattr(self, 'cmd_'+h+'_aliases', [])
|
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):
|
def stats(self, eventtime):
|
||||||
return "gcodein=%d" % (self.bytes_read,)
|
return "gcodein=%d" % (self.bytes_read,)
|
||||||
def set_printer_ready(self, is_ready):
|
def set_printer_ready(self, is_ready):
|
||||||
if self.is_printer_ready == is_ready:
|
if self.is_printer_ready == is_ready:
|
||||||
return
|
return
|
||||||
self.is_printer_ready = is_ready
|
self.is_printer_ready = is_ready
|
||||||
self.build_handlers()
|
self.gcode_handlers = self.build_handlers(is_ready)
|
||||||
if is_ready and self.is_fileinput and self.fd_handle is None:
|
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)
|
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
|
||||||
def motor_heater_off(self):
|
def motor_heater_off(self):
|
||||||
if self.toolhead is not None:
|
if self.toolhead is None:
|
||||||
|
return
|
||||||
self.toolhead.motor_off()
|
self.toolhead.motor_off()
|
||||||
|
print_time = self.toolhead.get_last_move_time()
|
||||||
if self.heater_nozzle is not None:
|
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:
|
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):
|
def dump_debug(self):
|
||||||
logging.info("Dumping gcode input %d blocks" % (
|
logging.info("Dumping gcode input %d blocks" % (
|
||||||
len(self.input_log),))
|
len(self.input_log),))
|
||||||
@@ -109,10 +106,15 @@ class GCodeParser:
|
|||||||
handler = self.gcode_handlers.get(cmd, self.cmd_default)
|
handler = self.gcode_handlers.get(cmd, self.cmd_default)
|
||||||
try:
|
try:
|
||||||
handler(params)
|
handler(params)
|
||||||
|
except error, e:
|
||||||
|
self.respond_error(str(e))
|
||||||
except:
|
except:
|
||||||
logging.exception("Exception in command handler")
|
logging.exception("Exception in command handler")
|
||||||
self.toolhead.force_shutdown()
|
self.toolhead.force_shutdown()
|
||||||
self.respond_error('Internal error on command:"%s"' % (cmd,))
|
self.respond_error('Internal error on command:"%s"' % (cmd,))
|
||||||
|
if self.is_fileinput:
|
||||||
|
self.printer.request_exit('exit_eof')
|
||||||
|
break
|
||||||
self.ack()
|
self.ack()
|
||||||
def process_data(self, eventtime):
|
def process_data(self, eventtime):
|
||||||
data = os.read(self.fd, 4096)
|
data = os.read(self.fd, 4096)
|
||||||
@@ -136,7 +138,7 @@ class GCodeParser:
|
|||||||
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
|
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
|
||||||
if not data and self.is_fileinput:
|
if not data and self.is_fileinput:
|
||||||
self.motor_heater_off()
|
self.motor_heater_off()
|
||||||
self.printer.request_exit_eof()
|
self.printer.request_exit('exit_eof')
|
||||||
# Response handling
|
# Response handling
|
||||||
def ack(self, msg=None):
|
def ack(self, msg=None):
|
||||||
if not self.need_ack or self.is_fileinput:
|
if not self.need_ack or self.is_fileinput:
|
||||||
@@ -159,6 +161,27 @@ class GCodeParser:
|
|||||||
if len(lines) > 1:
|
if len(lines) > 1:
|
||||||
self.respond_info("\n".join(lines[:-1]))
|
self.respond_info("\n".join(lines[:-1]))
|
||||||
self.respond('!! %s' % (lines[-1].strip(),))
|
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
|
# Temperature wrappers
|
||||||
def get_temp(self):
|
def get_temp(self):
|
||||||
if not self.is_printer_ready:
|
if not self.is_printer_ready:
|
||||||
@@ -175,17 +198,32 @@ class GCodeParser:
|
|||||||
def bg_temp(self, heater):
|
def bg_temp(self, heater):
|
||||||
if self.is_fileinput:
|
if self.is_fileinput:
|
||||||
return
|
return
|
||||||
eventtime = time.time()
|
eventtime = self.reactor.monotonic()
|
||||||
while self.is_printer_ready and heater.check_busy(eventtime):
|
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())
|
self.respond(self.get_temp())
|
||||||
eventtime = self.reactor.pause(eventtime + 1.)
|
eventtime = self.reactor.pause(eventtime + 1.)
|
||||||
def set_temp(self, heater, params, wait=False):
|
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()
|
print_time = self.toolhead.get_last_move_time()
|
||||||
temp = float(params.get('S', '0'))
|
try:
|
||||||
heater.set_temp(print_time, temp)
|
heater.set_temp(print_time, temp)
|
||||||
|
except heater.error, e:
|
||||||
|
self.respond_error(str(e))
|
||||||
|
return
|
||||||
if wait:
|
if wait:
|
||||||
self.bg_temp(heater)
|
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
|
# Individual command handlers
|
||||||
def cmd_default(self, params):
|
def cmd_default(self, params):
|
||||||
if not self.is_printer_ready:
|
if not self.is_printer_ready:
|
||||||
@@ -196,20 +234,34 @@ class GCodeParser:
|
|||||||
logging.debug(params['#original'])
|
logging.debug(params['#original'])
|
||||||
return
|
return
|
||||||
self.respond('echo:Unknown command:"%s"' % (cmd,))
|
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']
|
cmd_G1_aliases = ['G0']
|
||||||
def cmd_G1(self, params):
|
def cmd_G1(self, params):
|
||||||
# Move
|
# Move
|
||||||
|
try:
|
||||||
for a, p in self.axis2pos.items():
|
for a, p in self.axis2pos.items():
|
||||||
if a in params:
|
if a in params:
|
||||||
v = float(params[a])
|
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
|
# value relative to position of last move
|
||||||
self.last_position[p] += v
|
self.last_position[p] += v
|
||||||
else:
|
else:
|
||||||
# value relative to base coordinate position
|
# value relative to base coordinate position
|
||||||
self.last_position[p] = v + self.base_position[p]
|
self.last_position[p] = v + self.base_position[p]
|
||||||
if 'F' in params:
|
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:
|
try:
|
||||||
self.toolhead.move(self.last_position, self.speed)
|
self.toolhead.move(self.last_position, self.speed)
|
||||||
except homing.EndstopError, e:
|
except homing.EndstopError, e:
|
||||||
@@ -218,16 +270,13 @@ class GCodeParser:
|
|||||||
def cmd_G4(self, params):
|
def cmd_G4(self, params):
|
||||||
# Dwell
|
# Dwell
|
||||||
if 'S' in params:
|
if 'S' in params:
|
||||||
delay = float(params['S'])
|
delay = self.get_float('S', params)
|
||||||
else:
|
else:
|
||||||
delay = float(params.get('P', '0')) / 1000.
|
delay = self.get_float('P', params, 0.) / 1000.
|
||||||
self.toolhead.dwell(delay)
|
self.toolhead.dwell(delay)
|
||||||
def cmd_G20(self, params):
|
def cmd_G20(self, params):
|
||||||
# Set units to inches
|
# Set units to inches
|
||||||
self.respond_error('Machine does not support G20 (inches) command')
|
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):
|
def cmd_G28(self, params):
|
||||||
# Move to origin
|
# Move to origin
|
||||||
axes = []
|
axes = []
|
||||||
@@ -257,12 +306,11 @@ class GCodeParser:
|
|||||||
self.absolutecoord = False
|
self.absolutecoord = False
|
||||||
def cmd_G92(self, params):
|
def cmd_G92(self, params):
|
||||||
# Set position
|
# Set position
|
||||||
mcount = 0
|
offsets = { p: self.get_float(a, params)
|
||||||
for a, p in self.axis2pos.items():
|
for a, p in self.axis2pos.items() if a in params }
|
||||||
if a in params:
|
for p, offset in offsets.items():
|
||||||
self.base_position[p] = self.last_position[p] - float(params[a])
|
self.base_position[p] = self.last_position[p] - offset
|
||||||
mcount += 1
|
if not offsets:
|
||||||
if not mcount:
|
|
||||||
self.base_position = list(self.last_position)
|
self.base_position = list(self.last_position)
|
||||||
def cmd_M82(self, params):
|
def cmd_M82(self, params):
|
||||||
# Use absolute distances for extrusion
|
# Use absolute distances for extrusion
|
||||||
@@ -284,10 +332,6 @@ class GCodeParser:
|
|||||||
def cmd_M109(self, params):
|
def cmd_M109(self, params):
|
||||||
# Set Extruder Temperature and Wait
|
# Set Extruder Temperature and Wait
|
||||||
self.set_temp(self.heater_nozzle, params, wait=True)
|
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):
|
def cmd_M112(self, params):
|
||||||
# Emergency Stop
|
# Emergency Stop
|
||||||
self.toolhead.force_shutdown()
|
self.toolhead.force_shutdown()
|
||||||
@@ -302,6 +346,12 @@ class GCodeParser:
|
|||||||
self.last_position[0], self.last_position[1],
|
self.last_position[0], self.last_position[1],
|
||||||
self.last_position[2], self.last_position[3],
|
self.last_position[2], self.last_position[3],
|
||||||
kinpos[0], kinpos[1], kinpos[2]))
|
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):
|
def cmd_M140(self, params):
|
||||||
# Set Bed Temperature
|
# Set Bed Temperature
|
||||||
self.set_temp(self.heater_bed, params)
|
self.set_temp(self.heater_bed, params)
|
||||||
@@ -310,19 +360,25 @@ class GCodeParser:
|
|||||||
self.set_temp(self.heater_bed, params, wait=True)
|
self.set_temp(self.heater_bed, params, wait=True)
|
||||||
def cmd_M106(self, params):
|
def cmd_M106(self, params):
|
||||||
# Set fan speed
|
# Set fan speed
|
||||||
print_time = self.toolhead.get_last_move_time()
|
self.set_fan_speed(self.get_float('S', params, 255.) / 255.)
|
||||||
self.fan.set_speed(print_time, float(params.get('S', '255')) / 255.)
|
|
||||||
def cmd_M107(self, params):
|
def cmd_M107(self, params):
|
||||||
# Turn fan off
|
# Turn fan off
|
||||||
print_time = self.toolhead.get_last_move_time()
|
self.set_fan_speed(0.)
|
||||||
self.fan.set_speed(print_time, 0)
|
|
||||||
def cmd_M206(self, params):
|
def cmd_M206(self, params):
|
||||||
# Set home offset
|
# Set home offset
|
||||||
for a, p in self.axis2pos.items():
|
offsets = { p: self.get_float(a, params)
|
||||||
if a in params:
|
for a, p in self.axis2pos.items() if a in params }
|
||||||
v = float(params[a])
|
for p, offset in offsets.items():
|
||||||
self.base_position[p] += self.homing_add[p] - v
|
self.base_position[p] += self.homing_add[p] - offset
|
||||||
self.homing_add[p] = v
|
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_help = "Report on the status of each endstop"
|
||||||
cmd_QUERY_ENDSTOPS_aliases = ["M119"]
|
cmd_QUERY_ENDSTOPS_aliases = ["M119"]
|
||||||
def cmd_QUERY_ENDSTOPS(self, params):
|
def cmd_QUERY_ENDSTOPS(self, params):
|
||||||
@@ -340,23 +396,29 @@ class GCodeParser:
|
|||||||
cmd_PID_TUNE_aliases = ["M303"]
|
cmd_PID_TUNE_aliases = ["M303"]
|
||||||
def cmd_PID_TUNE(self, params):
|
def cmd_PID_TUNE(self, params):
|
||||||
# Run PID tuning
|
# 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]
|
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)
|
heater.start_auto_tune(temp)
|
||||||
self.bg_temp(heater)
|
self.bg_temp(heater)
|
||||||
cmd_CLEAR_SHUTDOWN_when_not_ready = True
|
def prep_restart(self):
|
||||||
cmd_CLEAR_SHUTDOWN_help = "Clear a firmware shutdown and restart"
|
if self.is_printer_ready:
|
||||||
def cmd_CLEAR_SHUTDOWN(self, params):
|
self.respond_info("Preparing to restart...")
|
||||||
if self.toolhead is None:
|
self.motor_heater_off()
|
||||||
self.cmd_default(params)
|
self.toolhead.dwell(0.500)
|
||||||
return
|
self.toolhead.wait_moves()
|
||||||
self.printer.mcu.clear_shutdown()
|
|
||||||
self.printer.request_restart()
|
|
||||||
cmd_RESTART_when_not_ready = True
|
cmd_RESTART_when_not_ready = True
|
||||||
cmd_RESTART_help = "Reload config file and restart host software"
|
cmd_RESTART_help = "Reload config file and restart host software"
|
||||||
def cmd_RESTART(self, params):
|
def cmd_RESTART(self, params):
|
||||||
self.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_when_not_ready = True
|
||||||
cmd_STATUS_help = "Report the printer status"
|
cmd_STATUS_help = "Report the printer status"
|
||||||
def cmd_STATUS(self, params):
|
def cmd_STATUS(self, params):
|
||||||
@@ -371,8 +433,11 @@ class GCodeParser:
|
|||||||
if not self.is_printer_ready:
|
if not self.is_printer_ready:
|
||||||
cmdhelp.append("Printer is not ready - not all commands available.")
|
cmdhelp.append("Printer is not ready - not all commands available.")
|
||||||
cmdhelp.append("Available extended commands:")
|
cmdhelp.append("Available extended commands:")
|
||||||
for cmd in self.gcode_handlers:
|
for cmd in sorted(self.gcode_handlers):
|
||||||
desc = getattr(self, 'cmd_'+cmd+'_help', None)
|
desc = getattr(self, 'cmd_'+cmd+'_help', None)
|
||||||
if desc is not None:
|
if desc is not None:
|
||||||
cmdhelp.append("%-10s: %s" % (cmd, desc))
|
cmdhelp.append("%-10s: %s" % (cmd, desc))
|
||||||
self.respond_info("\n".join(cmdhelp))
|
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.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import math, logging, threading
|
import math, logging, threading
|
||||||
|
|
||||||
# Mapping from name to Steinhart-Hart coefficients
|
# Available sensors
|
||||||
Thermistors = {
|
Sensors = {
|
||||||
|
# Common thermistors and their Steinhart-Hart coefficients
|
||||||
"EPCOS 100K B57560G104F": (
|
"EPCOS 100K B57560G104F": (
|
||||||
|
"thermistor",
|
||||||
0.000722136308968056, 0.000216766566488498, 8.92935804531095e-08),
|
0.000722136308968056, 0.000216766566488498, 8.92935804531095e-08),
|
||||||
"ATC Semitec 104GT-2": (
|
"ATC Semitec 104GT-2": (
|
||||||
|
"thermistor",
|
||||||
0.000809651054275124, 0.000211636030735685, 7.07420883993973e-08),
|
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_TIME = 0.001
|
||||||
SAMPLE_COUNT = 8
|
SAMPLE_COUNT = 8
|
||||||
REPORT_TIME = 0.300
|
REPORT_TIME = 0.300
|
||||||
|
PWM_CYCLE_TIME = 0.100
|
||||||
KELVIN_TO_CELCIUS = -273.15
|
KELVIN_TO_CELCIUS = -273.15
|
||||||
MAX_HEAT_TIME = 5.0
|
MAX_HEAT_TIME = 5.0
|
||||||
AMBIENT_TEMP = 25.
|
AMBIENT_TEMP = 25.
|
||||||
PWM_MAX = 255
|
PID_PARAM_BASE = 255.
|
||||||
|
|
||||||
|
class error(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class PrinterHeater:
|
class PrinterHeater:
|
||||||
|
error = error
|
||||||
def __init__(self, printer, config):
|
def __init__(self, printer, config):
|
||||||
self.printer = printer
|
self.name = config.section
|
||||||
self.config = config
|
sensor_params = config.getchoice('sensor_type', Sensors)
|
||||||
self.mcu_pwm = self.mcu_adc = None
|
self.is_linear_sensor = (sensor_params[0] == 'linear')
|
||||||
self.thermistor_c = config.getchoice('thermistor_type', Thermistors)
|
if self.is_linear_sensor:
|
||||||
self.pullup_r = config.getfloat('pullup_resistor', 4700.)
|
adc_voltage = config.getfloat('adc_voltage', 5., above=0.)
|
||||||
self.min_extrude_temp = config.getfloat('min_extrude_temp', 170.)
|
self.sensor_coef = sensor_params[1] * adc_voltage, sensor_params[2]
|
||||||
self.can_extrude = (self.min_extrude_temp <= 0.)
|
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.lock = threading.Lock()
|
||||||
self.last_temp = 0.
|
self.last_temp = 0.
|
||||||
self.last_temp_time = 0.
|
self.last_temp_time = 0.
|
||||||
self.target_temp = 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
|
# pwm caching
|
||||||
self.next_pwm_time = 0.
|
self.next_pwm_time = 0.
|
||||||
self.last_pwm_value = 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):
|
def set_pwm(self, read_time, value):
|
||||||
if value:
|
|
||||||
if self.target_temp <= 0.:
|
if self.target_temp <= 0.:
|
||||||
return
|
value = 0.
|
||||||
if (read_time < self.next_pwm_time
|
if ((read_time < self.next_pwm_time or not self.last_pwm_value)
|
||||||
and abs(value - self.last_pwm_value) < 15):
|
and abs(value - self.last_pwm_value) < 0.05):
|
||||||
return
|
# No significant change in value - can suppress update
|
||||||
elif not self.last_pwm_value:
|
|
||||||
return
|
return
|
||||||
pwm_time = read_time + REPORT_TIME + SAMPLE_TIME*SAMPLE_COUNT
|
pwm_time = read_time + REPORT_TIME + SAMPLE_TIME*SAMPLE_COUNT
|
||||||
self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME
|
self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME
|
||||||
self.last_pwm_value = value
|
self.last_pwm_value = value
|
||||||
logging.debug("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)
|
self.mcu_pwm.set_pwm(pwm_time, value)
|
||||||
# Temperature calculation
|
# Temperature calculation
|
||||||
def calc_temp(self, adc):
|
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)
|
ln_r = math.log(r)
|
||||||
c1, c2, c3 = self.thermistor_c
|
|
||||||
temp_inv = c1 + c2*ln_r + c3*math.pow(ln_r, 3)
|
temp_inv = c1 + c2*ln_r + c3*math.pow(ln_r, 3)
|
||||||
return 1.0/temp_inv + KELVIN_TO_CELCIUS
|
return 1.0/temp_inv + KELVIN_TO_CELCIUS
|
||||||
def calc_adc(self, temp):
|
def calc_adc(self, temp):
|
||||||
if temp is None:
|
if temp is None:
|
||||||
return 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 -= KELVIN_TO_CELCIUS
|
||||||
temp_inv = 1./temp
|
temp_inv = 1./temp
|
||||||
y = (c1 - temp_inv) / (2*c3)
|
y = (c1 - temp_inv) / (2*c3)
|
||||||
x = math.sqrt(math.pow(c2 / (3.*c3), 3.) + math.pow(y, 2.))
|
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.))
|
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):
|
def adc_callback(self, read_time, read_value):
|
||||||
temp = self.calc_temp(read_value)
|
temp = self.calc_temp(read_value)
|
||||||
with self.lock:
|
with self.lock:
|
||||||
@@ -93,6 +119,9 @@ class PrinterHeater:
|
|||||||
#logging.debug("temp: %.3f %f = %f" % (read_time, read_value, temp))
|
#logging.debug("temp: %.3f %f = %f" % (read_time, read_value, temp))
|
||||||
# External commands
|
# External commands
|
||||||
def set_temp(self, print_time, degrees):
|
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:
|
with self.lock:
|
||||||
self.target_temp = degrees
|
self.target_temp = degrees
|
||||||
def get_temp(self):
|
def get_temp(self):
|
||||||
@@ -113,7 +142,7 @@ class PrinterHeater:
|
|||||||
class ControlBangBang:
|
class ControlBangBang:
|
||||||
def __init__(self, heater, config):
|
def __init__(self, heater, config):
|
||||||
self.heater = heater
|
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
|
self.heating = False
|
||||||
def adc_callback(self, read_time, temp):
|
def adc_callback(self, read_time, temp):
|
||||||
if self.heating and temp >= self.heater.target_temp+self.max_delta:
|
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:
|
elif not self.heating and temp <= self.heater.target_temp-self.max_delta:
|
||||||
self.heating = True
|
self.heating = True
|
||||||
if self.heating:
|
if self.heating:
|
||||||
self.heater.set_pwm(read_time, PWM_MAX)
|
self.heater.set_pwm(read_time, self.heater.max_power)
|
||||||
else:
|
else:
|
||||||
self.heater.set_pwm(read_time, 0)
|
self.heater.set_pwm(read_time, 0.)
|
||||||
def check_busy(self, eventtime):
|
def check_busy(self, eventtime):
|
||||||
return self.heater.last_temp < self.heater.target_temp-self.max_delta
|
return self.heater.last_temp < self.heater.target_temp-self.max_delta
|
||||||
|
|
||||||
@@ -135,11 +164,11 @@ class ControlBangBang:
|
|||||||
class ControlPID:
|
class ControlPID:
|
||||||
def __init__(self, heater, config):
|
def __init__(self, heater, config):
|
||||||
self.heater = heater
|
self.heater = heater
|
||||||
self.Kp = config.getfloat('pid_Kp')
|
self.Kp = config.getfloat('pid_Kp') / PID_PARAM_BASE
|
||||||
self.Ki = config.getfloat('pid_Ki')
|
self.Ki = config.getfloat('pid_Ki') / PID_PARAM_BASE
|
||||||
self.Kd = config.getfloat('pid_Kd')
|
self.Kd = config.getfloat('pid_Kd') / PID_PARAM_BASE
|
||||||
self.min_deriv_time = config.getfloat('pid_deriv_time', 2.)
|
self.min_deriv_time = config.getfloat('pid_deriv_time', 2., above=0.)
|
||||||
imax = config.getint('pid_integral_max', PWM_MAX)
|
imax = config.getfloat('pid_integral_max', heater.max_power, minval=0.)
|
||||||
self.temp_integ_max = imax / self.Ki
|
self.temp_integ_max = imax / self.Ki
|
||||||
self.prev_temp = AMBIENT_TEMP
|
self.prev_temp = AMBIENT_TEMP
|
||||||
self.prev_temp_time = 0.
|
self.prev_temp_time = 0.
|
||||||
@@ -159,10 +188,10 @@ class ControlPID:
|
|||||||
temp_integ = self.prev_temp_integ + temp_err * time_diff
|
temp_integ = self.prev_temp_integ + temp_err * time_diff
|
||||||
temp_integ = max(0., min(self.temp_integ_max, temp_integ))
|
temp_integ = max(0., min(self.temp_integ_max, temp_integ))
|
||||||
# Calculate output
|
# 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" % (
|
#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))
|
# 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)
|
self.heater.set_pwm(read_time, bounded_co)
|
||||||
# Store state for next measurement
|
# Store state for next measurement
|
||||||
self.prev_temp = temp
|
self.prev_temp = temp
|
||||||
@@ -198,12 +227,12 @@ class ControlAutoTune:
|
|||||||
self.heating = True
|
self.heating = True
|
||||||
self.check_peaks()
|
self.check_peaks()
|
||||||
if self.heating:
|
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:
|
if temp < self.peak:
|
||||||
self.peak = temp
|
self.peak = temp
|
||||||
self.peak_time = read_time
|
self.peak_time = read_time
|
||||||
else:
|
else:
|
||||||
self.heater.set_pwm(read_time, 0)
|
self.heater.set_pwm(read_time, 0.)
|
||||||
if temp > self.peak:
|
if temp > self.peak:
|
||||||
self.peak = temp
|
self.peak = temp
|
||||||
self.peak_time = read_time
|
self.peak_time = read_time
|
||||||
@@ -217,8 +246,8 @@ class ControlAutoTune:
|
|||||||
return
|
return
|
||||||
temp_diff = self.peaks[-1][0] - self.peaks[-2][0]
|
temp_diff = self.peaks[-1][0] - self.peaks[-2][0]
|
||||||
time_diff = self.peaks[-1][1] - self.peaks[-3][1]
|
time_diff = self.peaks[-1][1] - self.peaks[-3][1]
|
||||||
pwm_diff = PWM_MAX - 0
|
max_power = self.heater.max_power
|
||||||
Ku = 4. * (2. * pwm_diff) / (abs(temp_diff) * math.pi)
|
Ku = 4. * (2. * max_power) / (abs(temp_diff) * math.pi)
|
||||||
Tu = time_diff
|
Tu = time_diff
|
||||||
|
|
||||||
Kp = 0.6 * Ku
|
Kp = 0.6 * Ku
|
||||||
@@ -226,8 +255,9 @@ class ControlAutoTune:
|
|||||||
Td = 0.125 * Tu
|
Td = 0.125 * Tu
|
||||||
Ki = Kp / Ti
|
Ki = Kp / Ti
|
||||||
Kd = Kp * Td
|
Kd = Kp * Td
|
||||||
logging.info("Autotune: raw=%f/%d Ku=%f Tu=%f Kp=%f Ki=%f Kd=%f" % (
|
logging.info("Autotune: raw=%f/%f Ku=%f Tu=%f Kp=%f Ki=%f Kd=%f" % (
|
||||||
temp_diff, pwm_diff, Ku, Tu, Kp, Ki, Kd))
|
temp_diff, max_power, Ku, Tu,
|
||||||
|
Kp * PID_PARAM_BASE, Ki * PID_PARAM_BASE, Kd * PID_PARAM_BASE))
|
||||||
def check_busy(self, eventtime):
|
def check_busy(self, eventtime):
|
||||||
if self.heating or len(self.peaks) < 12:
|
if self.heating or len(self.peaks) < 12:
|
||||||
return True
|
return True
|
||||||
@@ -253,17 +283,17 @@ class ControlBumpTest:
|
|||||||
def adc_callback(self, read_time, temp):
|
def adc_callback(self, read_time, temp):
|
||||||
self.temp_samples[read_time] = temp
|
self.temp_samples[read_time] = temp
|
||||||
if not self.state:
|
if not self.state:
|
||||||
self.set_pwm(read_time, 0)
|
self.set_pwm(read_time, 0.)
|
||||||
if len(self.temp_samples) >= 20:
|
if len(self.temp_samples) >= 20:
|
||||||
self.state += 1
|
self.state += 1
|
||||||
elif self.state == 1:
|
elif self.state == 1:
|
||||||
if temp < self.target_temp:
|
if temp < self.target_temp:
|
||||||
self.set_pwm(read_time, PWM_MAX)
|
self.set_pwm(read_time, self.heater.max_power)
|
||||||
return
|
return
|
||||||
self.set_pwm(read_time, 0)
|
self.set_pwm(read_time, 0.)
|
||||||
self.state += 1
|
self.state += 1
|
||||||
elif self.state == 2:
|
elif self.state == 2:
|
||||||
self.set_pwm(read_time, 0)
|
self.set_pwm(read_time, 0.)
|
||||||
if temp <= (self.target_temp + AMBIENT_TEMP) / 2.:
|
if temp <= (self.target_temp + AMBIENT_TEMP) / 2.:
|
||||||
self.dump_stats()
|
self.dump_stats()
|
||||||
self.state += 1
|
self.state += 1
|
||||||
|
|||||||
163
klippy/klippy.py
@@ -6,6 +6,9 @@
|
|||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import sys, optparse, ConfigParser, logging, time, threading
|
import sys, optparse, ConfigParser, logging, time, threading
|
||||||
import gcode, toolhead, util, mcu, fan, heater, extruder, reactor, queuelogger
|
import gcode, toolhead, util, mcu, fan, heater, extruder, reactor, queuelogger
|
||||||
|
import msgproto
|
||||||
|
|
||||||
|
message_ready = "Printer is ready"
|
||||||
|
|
||||||
message_startup = """
|
message_startup = """
|
||||||
The klippy host software is attempting to connect. Please
|
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
|
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 = """
|
message_mcu_connect_error = """
|
||||||
This is an unrecoverable error. Please manually restart the
|
Once the underlying issue is corrected, use the
|
||||||
micro-controller and then issue the "RESTART" command to
|
"FIRMWARE_RESTART" command to reset the firmware, reload the
|
||||||
restart the host software.
|
config, and restart the host software.
|
||||||
Error configuring printer
|
Error configuring printer
|
||||||
"""
|
"""
|
||||||
|
|
||||||
message_shutdown = """
|
message_shutdown = """
|
||||||
Once the underlying issue is corrected, the "CLEAR_SHUTDOWN"
|
Once the underlying issue is corrected, use the
|
||||||
command can be used to clear the firmware flag and restart
|
"FIRMWARE_RESTART" command to reset the firmware, reload the
|
||||||
the host software.
|
config, and restart the host software.
|
||||||
Printer is shutdown
|
Printer is shutdown
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -40,26 +52,47 @@ class ConfigWrapper:
|
|||||||
def __init__(self, printer, section):
|
def __init__(self, printer, section):
|
||||||
self.printer = printer
|
self.printer = printer
|
||||||
self.section = section
|
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
|
if (default is not self.sentinel
|
||||||
and not self.printer.fileconfig.has_option(self.section, option)):
|
and not self.printer.fileconfig.has_option(self.section, option)):
|
||||||
return default
|
return default
|
||||||
self.printer.all_config_options[
|
self.printer.all_config_options[
|
||||||
(self.section.lower(), option.lower())] = 1
|
(self.section.lower(), option.lower())] = 1
|
||||||
try:
|
try:
|
||||||
return parser(self.section, option)
|
v = parser(self.section, option)
|
||||||
except self.error, e:
|
except self.error, e:
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
raise self.error("Unable to parse option '%s' in section '%s'" % (
|
raise self.error("Unable to parse option '%s' in section '%s'" % (
|
||||||
option, self.section))
|
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):
|
def get(self, option, default=sentinel):
|
||||||
return self.get_wrapper(self.printer.fileconfig.get, option, default)
|
return self.get_wrapper(self.printer.fileconfig.get, option, default)
|
||||||
def getint(self, option, default=sentinel):
|
def getint(self, option, default=sentinel, minval=None, maxval=None):
|
||||||
return self.get_wrapper(self.printer.fileconfig.getint, option, default)
|
|
||||||
def getfloat(self, option, default=sentinel):
|
|
||||||
return self.get_wrapper(
|
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):
|
def getboolean(self, option, default=sentinel):
|
||||||
return self.get_wrapper(
|
return self.get_wrapper(
|
||||||
self.printer.fileconfig.getboolean, option, default)
|
self.printer.fileconfig.getboolean, option, default)
|
||||||
@@ -73,10 +106,28 @@ class ConfigWrapper:
|
|||||||
def getsection(self, section):
|
def getsection(self, section):
|
||||||
return ConfigWrapper(self.printer, 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:
|
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.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.reactor = reactor.Reactor()
|
||||||
|
self.objects = {}
|
||||||
self.gcode = gcode.GCodeParser(self, input_fd, is_fileinput)
|
self.gcode = gcode.GCodeParser(self, input_fd, is_fileinput)
|
||||||
self.stats_timer = self.reactor.register_timer(self.stats)
|
self.stats_timer = self.reactor.register_timer(self.stats)
|
||||||
self.connect_timer = self.reactor.register_timer(
|
self.connect_timer = self.reactor.register_timer(
|
||||||
@@ -88,23 +139,25 @@ class Printer:
|
|||||||
self.run_result = None
|
self.run_result = None
|
||||||
self.fileconfig = None
|
self.fileconfig = None
|
||||||
self.mcu = None
|
self.mcu = None
|
||||||
self.objects = {}
|
|
||||||
def set_fileoutput(self, debugoutput, dictionary):
|
def set_fileoutput(self, debugoutput, dictionary):
|
||||||
self.debugoutput = debugoutput
|
self.debugoutput = debugoutput
|
||||||
self.dictionary = dictionary
|
self.dictionary = dictionary
|
||||||
def stats(self, eventtime):
|
def stats(self, eventtime, force_output=False):
|
||||||
if self.need_dump_debug:
|
if self.need_dump_debug:
|
||||||
# Call dump_debug here so it is executed in the main thread
|
# Call dump_debug here so it is executed in the main thread
|
||||||
self.gcode.dump_debug()
|
self.gcode.dump_debug()
|
||||||
self.need_dump_debug = False
|
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 = []
|
||||||
out.append(self.gcode.stats(eventtime))
|
out.append(self.gcode.stats(eventtime))
|
||||||
toolhead = self.objects.get('toolhead')
|
out.append(thstats)
|
||||||
if toolhead is not None:
|
|
||||||
out.append(toolhead.stats(eventtime))
|
|
||||||
if self.mcu is not None:
|
|
||||||
out.append(self.mcu.stats(eventtime))
|
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.
|
return eventtime + 1.
|
||||||
def load_config(self):
|
def load_config(self):
|
||||||
self.fileconfig = ConfigParser.RawConfigParser()
|
self.fileconfig = ConfigParser.RawConfigParser()
|
||||||
@@ -112,24 +165,23 @@ class Printer:
|
|||||||
if not res:
|
if not res:
|
||||||
raise ConfigParser.Error("Unable to open config file %s" % (
|
raise ConfigParser.Error("Unable to open config file %s" % (
|
||||||
self.conffile,))
|
self.conffile,))
|
||||||
|
if self.bglogger is not None:
|
||||||
|
ConfigLogger(self.fileconfig, self.bglogger)
|
||||||
self.mcu = mcu.MCU(self, ConfigWrapper(self, 'mcu'))
|
self.mcu = mcu.MCU(self, ConfigWrapper(self, 'mcu'))
|
||||||
if self.fileconfig.has_section('fan'):
|
if self.debugoutput is not None:
|
||||||
self.objects['fan'] = fan.PrinterFan(
|
self.mcu.connect_file(self.debugoutput, self.dictionary)
|
||||||
self, ConfigWrapper(self, 'fan'))
|
|
||||||
if self.fileconfig.has_section('extruder'):
|
if self.fileconfig.has_section('extruder'):
|
||||||
self.objects['extruder'] = extruder.PrinterExtruder(
|
self.objects['extruder'] = extruder.PrinterExtruder(
|
||||||
self, ConfigWrapper(self, 'extruder'))
|
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'):
|
if self.fileconfig.has_section('heater_bed'):
|
||||||
self.objects['heater_bed'] = heater.PrinterHeater(
|
self.objects['heater_bed'] = heater.PrinterHeater(
|
||||||
self, ConfigWrapper(self, 'heater_bed'))
|
self, ConfigWrapper(self, 'heater_bed'))
|
||||||
self.objects['toolhead'] = toolhead.ToolHead(
|
self.objects['toolhead'] = toolhead.ToolHead(
|
||||||
self, ConfigWrapper(self, 'printer'))
|
self, ConfigWrapper(self, 'printer'))
|
||||||
def build_config(self):
|
# Validate that there are no undefined parameters in the config file
|
||||||
for oname in sorted(self.objects.keys()):
|
|
||||||
self.objects[oname].build_config()
|
|
||||||
self.gcode.build_config()
|
|
||||||
self.mcu.build_config()
|
|
||||||
def validate_config(self):
|
|
||||||
valid_sections = dict([(s, 1) for s, o in self.all_config_options])
|
valid_sections = dict([(s, 1) for s, o in self.all_config_options])
|
||||||
for section in self.fileconfig.sections():
|
for section in self.fileconfig.sections():
|
||||||
section = section.lower()
|
section = section.lower()
|
||||||
@@ -147,17 +199,17 @@ class Printer:
|
|||||||
self.load_config()
|
self.load_config()
|
||||||
if self.debugoutput is None:
|
if self.debugoutput is None:
|
||||||
self.reactor.update_timer(self.stats_timer, self.reactor.NOW)
|
self.reactor.update_timer(self.stats_timer, self.reactor.NOW)
|
||||||
else:
|
|
||||||
self.mcu.connect_file(self.debugoutput, self.dictionary)
|
|
||||||
self.mcu.connect()
|
self.mcu.connect()
|
||||||
self.build_config()
|
|
||||||
self.validate_config()
|
|
||||||
self.gcode.set_printer_ready(True)
|
self.gcode.set_printer_ready(True)
|
||||||
self.state_message = "Printer is ready"
|
self.state_message = message_ready
|
||||||
except ConfigParser.Error, e:
|
except ConfigParser.Error, e:
|
||||||
logging.exception("Config error")
|
logging.exception("Config error")
|
||||||
self.state_message = "%s%s" % (str(e), message_restart)
|
self.state_message = "%s%s" % (str(e), message_restart)
|
||||||
self.reactor.update_timer(self.stats_timer, self.reactor.NEVER)
|
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:
|
except mcu.error, e:
|
||||||
logging.exception("MCU error during connect")
|
logging.exception("MCU error during connect")
|
||||||
self.state_message = "%s%s" % (str(e), message_mcu_connect_error)
|
self.state_message = "%s%s" % (str(e), message_mcu_connect_error)
|
||||||
@@ -170,6 +222,10 @@ class Printer:
|
|||||||
self.reactor.unregister_timer(self.connect_timer)
|
self.reactor.unregister_timer(self.connect_timer)
|
||||||
return self.reactor.NEVER
|
return self.reactor.NEVER
|
||||||
def run(self):
|
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:
|
try:
|
||||||
self.reactor.run()
|
self.reactor.run()
|
||||||
except:
|
except:
|
||||||
@@ -179,7 +235,7 @@ class Printer:
|
|||||||
def get_state_message(self):
|
def get_state_message(self):
|
||||||
return self.state_message
|
return self.state_message
|
||||||
def note_shutdown(self, msg):
|
def note_shutdown(self, msg):
|
||||||
if self.state_message == 'Running':
|
if self.state_message == message_ready:
|
||||||
self.need_dump_debug = True
|
self.need_dump_debug = True
|
||||||
self.state_message = "Firmware shutdown: %s%s" % (
|
self.state_message = "Firmware shutdown: %s%s" % (
|
||||||
msg, message_shutdown)
|
msg, message_shutdown)
|
||||||
@@ -191,15 +247,22 @@ class Printer:
|
|||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
try:
|
try:
|
||||||
if self.mcu is not None:
|
if self.mcu is not None:
|
||||||
self.stats(time.time())
|
self.stats(self.reactor.monotonic(), force_output=True)
|
||||||
self.mcu.disconnect()
|
self.mcu.disconnect()
|
||||||
except:
|
except:
|
||||||
logging.exception("Unhandled exception during disconnect")
|
logging.exception("Unhandled exception during disconnect")
|
||||||
def request_restart(self):
|
def firmware_restart(self):
|
||||||
self.run_result = "restart"
|
try:
|
||||||
self.reactor.end()
|
if self.mcu is not None:
|
||||||
def request_exit_eof(self):
|
self.stats(self.reactor.monotonic(), force_output=True)
|
||||||
self.run_result = "exit_eof"
|
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()
|
self.reactor.end()
|
||||||
|
|
||||||
|
|
||||||
@@ -250,11 +313,22 @@ def main():
|
|||||||
else:
|
else:
|
||||||
logging.basicConfig(level=debuglevel)
|
logging.basicConfig(level=debuglevel)
|
||||||
logging.info("Starting Klippy...")
|
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
|
# Start firmware
|
||||||
|
res = 'startup'
|
||||||
while 1:
|
while 1:
|
||||||
is_fileinput = debuginput is not None
|
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:
|
if debugoutput:
|
||||||
proto_dict = read_dictionary(options.read_dictionary)
|
proto_dict = read_dictionary(options.read_dictionary)
|
||||||
printer.set_fileoutput(debugoutput, proto_dict)
|
printer.set_fileoutput(debugoutput, proto_dict)
|
||||||
@@ -264,6 +338,11 @@ def main():
|
|||||||
time.sleep(1.)
|
time.sleep(1.)
|
||||||
logging.info("Restarting printer")
|
logging.info("Restarting printer")
|
||||||
continue
|
continue
|
||||||
|
elif res == 'firmware_restart':
|
||||||
|
printer.firmware_restart()
|
||||||
|
time.sleep(1.)
|
||||||
|
logging.info("Restarting printer")
|
||||||
|
continue
|
||||||
elif res == 'exit_eof':
|
elif res == 'exit_eof':
|
||||||
printer.disconnect()
|
printer.disconnect()
|
||||||
break
|
break
|
||||||
|
|||||||
556
klippy/mcu.py
@@ -1,9 +1,9 @@
|
|||||||
# Multi-processor safe interface to micro-controller
|
# 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.
|
# 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
|
import serialhdl, pins, chelper
|
||||||
|
|
||||||
class error(Exception):
|
class error(Exception):
|
||||||
@@ -19,139 +19,181 @@ def parse_pin_extras(pin, can_pullup=False):
|
|||||||
pin = pin[1:].strip()
|
pin = pin[1:].strip()
|
||||||
return pin, pullup, invert
|
return pin, pullup, invert
|
||||||
|
|
||||||
|
STEPCOMPRESS_ERROR_RET = -989898989
|
||||||
|
|
||||||
class MCU_stepper:
|
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._mcu = mcu
|
||||||
self._oid = mcu.create_oid()
|
self._oid = mcu.create_oid(self)
|
||||||
step_pin, pullup, invert_step = parse_pin_extras(step_pin)
|
self._step_pin, pullup, self._invert_step = parse_pin_extras(step_pin)
|
||||||
dir_pin, pullup, self._invert_dir = parse_pin_extras(dir_pin)
|
self._dir_pin, pullup, self._invert_dir = parse_pin_extras(dir_pin)
|
||||||
self._mcu_freq = mcu.get_mcu_freq()
|
self._commanded_pos = 0
|
||||||
min_stop_interval = int(min_stop_interval * self._mcu_freq)
|
self._step_dist = self._inv_step_dist = 1.
|
||||||
max_error = int(max_error * self._mcu_freq)
|
self._velocity_factor = self._accel_factor = 0.
|
||||||
self.commanded_position = 0
|
|
||||||
self._mcu_position_offset = 0
|
self._mcu_position_offset = 0
|
||||||
mcu.add_config_cmd(
|
self._mcu_freq = self._min_stop_interval = 0.
|
||||||
"config_stepper oid=%d step_pin=%s dir_pin=%s"
|
self._reset_cmd = self._get_position_cmd = None
|
||||||
" min_stop_interval=%d invert_step=%d" % (
|
self._ffi_lib = self._stepqueue = None
|
||||||
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.print_to_mcu_time = mcu.print_to_mcu_time
|
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):
|
def get_oid(self):
|
||||||
return self._oid
|
return self._oid
|
||||||
def get_invert_dir(self):
|
|
||||||
return self._invert_dir
|
|
||||||
def set_position(self, pos):
|
def set_position(self, pos):
|
||||||
self._mcu_position_offset += self.commanded_position - pos
|
if pos >= 0.:
|
||||||
self.commanded_position = pos
|
steppos = int(pos * self._inv_step_dist + 0.5)
|
||||||
def set_mcu_position(self, pos):
|
else:
|
||||||
self._mcu_position_offset = pos - self.commanded_position
|
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):
|
def get_mcu_position(self):
|
||||||
return self.commanded_position + self._mcu_position_offset
|
return self._commanded_pos + self._mcu_position_offset
|
||||||
def note_stepper_stop(self):
|
def note_homing_start(self, homing_clock):
|
||||||
self.ffi_lib.stepcompress_reset(self._stepqueue, 0)
|
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):
|
def reset_step_clock(self, mcu_time):
|
||||||
clock = int(mcu_time * self._mcu_freq)
|
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)
|
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):
|
def step(self, mcu_time, sdir):
|
||||||
clock = mcu_time * self._mcu_freq
|
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:
|
if sdir:
|
||||||
self.commanded_position += 1
|
self._commanded_pos += 1
|
||||||
else:
|
else:
|
||||||
self.commanded_position -= 1
|
self._commanded_pos -= 1
|
||||||
def step_sqrt(self, mcu_time, steps, step_offset, sqrt_offset, factor):
|
def step_const(self, mcu_time, start_pos, dist, start_v, accel):
|
||||||
clock = mcu_time * self._mcu_freq
|
inv_step_dist = self._inv_step_dist
|
||||||
mcu_freq2 = self._mcu_freq**2
|
step_offset = self._commanded_pos - start_pos * inv_step_dist
|
||||||
count = self.ffi_lib.stepcompress_push_sqrt(
|
count = self._ffi_lib.stepcompress_push_const(
|
||||||
self._stepqueue, steps, step_offset, clock
|
self._stepqueue, mcu_time * self._mcu_freq, step_offset,
|
||||||
, sqrt_offset * mcu_freq2, factor * mcu_freq2)
|
dist * inv_step_dist, start_v * self._velocity_factor,
|
||||||
self.commanded_position += count
|
accel * self._accel_factor)
|
||||||
return count
|
if count == STEPCOMPRESS_ERROR_RET:
|
||||||
def step_factor(self, mcu_time, steps, step_offset, factor):
|
raise error("Internal error in stepcompress")
|
||||||
clock = mcu_time * self._mcu_freq
|
self._commanded_pos += count
|
||||||
count = self.ffi_lib.stepcompress_push_factor(
|
def step_delta(self, mcu_time, dist, start_v, accel
|
||||||
self._stepqueue, steps, step_offset, clock, factor * self._mcu_freq)
|
, height_base, startxy_d, arm_d, movez_r):
|
||||||
self.commanded_position += count
|
inv_step_dist = self._inv_step_dist
|
||||||
return count
|
height = self._commanded_pos - height_base * inv_step_dist
|
||||||
def step_delta_const(self, mcu_time, dist, start_pos
|
count = self._ffi_lib.stepcompress_push_delta(
|
||||||
, inv_velocity, step_dist
|
self._stepqueue, mcu_time * self._mcu_freq, dist * inv_step_dist,
|
||||||
, height, closestxy_d, closest_height2, movez_r):
|
start_v * self._velocity_factor, accel * self._accel_factor,
|
||||||
clock = mcu_time * self._mcu_freq
|
height, startxy_d * inv_step_dist, arm_d * inv_step_dist, movez_r)
|
||||||
count = self.ffi_lib.stepcompress_push_delta_const(
|
if count == STEPCOMPRESS_ERROR_RET:
|
||||||
self._stepqueue, clock, dist, start_pos
|
raise error("Internal error in stepcompress")
|
||||||
, inv_velocity * self._mcu_freq, step_dist
|
self._commanded_pos += count
|
||||||
, 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)
|
|
||||||
|
|
||||||
class MCU_endstop:
|
class MCU_endstop:
|
||||||
error = error
|
error = error
|
||||||
RETRY_QUERY = 1.000
|
RETRY_QUERY = 1.000
|
||||||
def __init__(self, mcu, pin, stepper):
|
def __init__(self, mcu, pin):
|
||||||
self._mcu = mcu
|
self._mcu = mcu
|
||||||
self._oid = mcu.create_oid()
|
self._oid = mcu.create_oid(self)
|
||||||
self._stepper = stepper
|
self._steppers = []
|
||||||
stepper_oid = stepper.get_oid()
|
self._pin, self._pullup, self._invert = parse_pin_extras(
|
||||||
pin, pullup, self._invert = parse_pin_extras(pin, can_pullup=True)
|
pin, can_pullup=True)
|
||||||
self._cmd_queue = mcu.alloc_command_queue()
|
self._cmd_queue = mcu.alloc_command_queue()
|
||||||
mcu.add_config_cmd(
|
self._home_cmd = self._query_cmd = None
|
||||||
"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._homing = False
|
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._next_query_clock = self._home_timeout_clock = 0
|
||||||
self._mcu_freq = mcu.get_mcu_freq()
|
self._retry_query_ticks = 0
|
||||||
self._retry_query_ticks = int(self._mcu_freq * self.RETRY_QUERY)
|
|
||||||
self._last_state = {}
|
self._last_state = {}
|
||||||
|
mcu.add_init_callback(self._init_callback)
|
||||||
self.print_to_mcu_time = mcu.print_to_mcu_time
|
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):
|
def home_start(self, mcu_time, rest_time):
|
||||||
clock = int(mcu_time * self._mcu_freq)
|
clock = int(mcu_time * self._mcu_freq)
|
||||||
rest_ticks = int(rest_time * self._mcu_freq)
|
rest_ticks = int(rest_time * self._mcu_freq)
|
||||||
self._homing = True
|
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
|
self._next_query_clock = clock + self._retry_query_ticks
|
||||||
msg = self._home_cmd.encode(
|
msg = self._home_cmd.encode(
|
||||||
self._oid, clock, rest_ticks, 1 ^ self._invert)
|
self._oid, clock, rest_ticks, 1 ^ self._invert)
|
||||||
self._mcu.send(msg, reqclock=clock, cq=self._cmd_queue)
|
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):
|
def home_finalize(self, mcu_time):
|
||||||
# XXX - this flushes the serial port of messages ready to be
|
for s in self._steppers:
|
||||||
# sent, but doesn't flush messages if they had an unmet minclock
|
s.note_homing_finalized()
|
||||||
self._mcu.serial.send_flush()
|
|
||||||
self._stepper.note_stepper_stop()
|
|
||||||
self._home_timeout_clock = int(mcu_time * self._mcu_freq)
|
self._home_timeout_clock = int(mcu_time * self._mcu_freq)
|
||||||
def home_wait(self):
|
def home_wait(self):
|
||||||
eventtime = time.time()
|
eventtime = self._mcu.monotonic()
|
||||||
while self._check_busy(eventtime):
|
while self._check_busy(eventtime):
|
||||||
eventtime = self._mcu.pause(eventtime + 0.1)
|
eventtime = self._mcu.pause(eventtime + 0.1)
|
||||||
def _handle_end_stop_state(self, params):
|
def _handle_end_stop_state(self, params):
|
||||||
@@ -166,10 +208,8 @@ class MCU_endstop:
|
|||||||
if not self._homing:
|
if not self._homing:
|
||||||
return False
|
return False
|
||||||
if not self._last_state.get('homing', 0):
|
if not self._last_state.get('homing', 0):
|
||||||
pos = self._last_state.get('pos', 0)
|
for s in self._steppers:
|
||||||
if self._stepper.get_invert_dir():
|
s.note_homing_triggered()
|
||||||
pos = -pos
|
|
||||||
self._stepper.set_mcu_position(pos)
|
|
||||||
self._homing = False
|
self._homing = False
|
||||||
return False
|
return False
|
||||||
if (self._mcu.serial.get_clock(last_sent_time)
|
if (self._mcu.serial.get_clock(last_sent_time)
|
||||||
@@ -189,10 +229,10 @@ class MCU_endstop:
|
|||||||
def query_endstop(self, mcu_time):
|
def query_endstop(self, mcu_time):
|
||||||
clock = int(mcu_time * self._mcu_freq)
|
clock = int(mcu_time * self._mcu_freq)
|
||||||
self._homing = False
|
self._homing = False
|
||||||
self._min_query_time = time.time()
|
self._min_query_time = self._mcu.monotonic()
|
||||||
self._next_query_clock = clock
|
self._next_query_clock = clock
|
||||||
def query_endstop_wait(self):
|
def query_endstop_wait(self):
|
||||||
eventtime = time.time()
|
eventtime = self._mcu.monotonic()
|
||||||
while self._check_busy(eventtime):
|
while self._check_busy(eventtime):
|
||||||
eventtime = self._mcu.pause(eventtime + 0.1)
|
eventtime = self._mcu.pause(eventtime + 0.1)
|
||||||
return self._last_state.get('pin', self._invert) ^ self._invert
|
return self._last_state.get('pin', self._invert) ^ self._invert
|
||||||
@@ -200,18 +240,22 @@ class MCU_endstop:
|
|||||||
class MCU_digital_out:
|
class MCU_digital_out:
|
||||||
def __init__(self, mcu, pin, max_duration):
|
def __init__(self, mcu, pin, max_duration):
|
||||||
self._mcu = mcu
|
self._mcu = mcu
|
||||||
self._oid = mcu.create_oid()
|
self._oid = mcu.create_oid(self)
|
||||||
pin, pullup, self._invert = parse_pin_extras(pin)
|
pin, pullup, self._invert = parse_pin_extras(pin)
|
||||||
self._last_clock = 0
|
self._last_clock = 0
|
||||||
self._last_value = None
|
self._last_value = None
|
||||||
self._mcu_freq = mcu.get_mcu_freq()
|
self._mcu_freq = 0.
|
||||||
self._cmd_queue = mcu.alloc_command_queue()
|
self._cmd_queue = mcu.alloc_command_queue()
|
||||||
mcu.add_config_cmd(
|
mcu.add_config_cmd(
|
||||||
"config_digital_out oid=%d pin=%s default_value=%d"
|
"config_digital_out oid=%d pin=%s default_value=%d"
|
||||||
" max_duration=%d" % (self._oid, pin, self._invert, max_duration))
|
" max_duration=TICKS(%f)" % (
|
||||||
self._set_cmd = mcu.lookup_command(
|
self._oid, pin, self._invert, max_duration))
|
||||||
"schedule_digital_out oid=%c clock=%u value=%c")
|
self._set_cmd = None
|
||||||
self.print_to_mcu_time = mcu.print_to_mcu_time
|
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):
|
def set_digital(self, mcu_time, value):
|
||||||
clock = int(mcu_time * self._mcu_freq)
|
clock = int(mcu_time * self._mcu_freq)
|
||||||
msg = self._set_cmd.encode(self._oid, clock, value ^ self._invert)
|
msg = self._set_cmd.encode(self._oid, clock, value ^ self._invert)
|
||||||
@@ -223,33 +267,46 @@ class MCU_digital_out:
|
|||||||
return self._last_value
|
return self._last_value
|
||||||
def set_pwm(self, mcu_time, value):
|
def set_pwm(self, mcu_time, value):
|
||||||
dval = 0
|
dval = 0
|
||||||
if value > 127:
|
if value >= 0.5:
|
||||||
dval = 1
|
dval = 1
|
||||||
self.set_digital(mcu_time, dval)
|
self.set_digital(mcu_time, dval)
|
||||||
|
|
||||||
class MCU_pwm:
|
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._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._last_clock = 0
|
||||||
self._mcu_freq = mcu.get_mcu_freq()
|
self._mcu_freq = 0.
|
||||||
self._cmd_queue = mcu.alloc_command_queue()
|
self._cmd_queue = mcu.alloc_command_queue()
|
||||||
if hard_pwm:
|
if hard_cycle_ticks:
|
||||||
mcu.add_config_cmd(
|
mcu.add_config_cmd(
|
||||||
"config_pwm_out oid=%d pin=%s cycle_ticks=%d default_value=0"
|
"config_pwm_out oid=%d pin=%s cycle_ticks=%d default_value=%d"
|
||||||
" max_duration=%d" % (self._oid, pin, cycle_ticks, max_duration))
|
" max_duration=TICKS(%f)" % (
|
||||||
self._set_cmd = mcu.lookup_command(
|
self._oid, pin, hard_cycle_ticks, self._invert,
|
||||||
"schedule_pwm_out oid=%c clock=%u value=%c")
|
max_duration))
|
||||||
else:
|
else:
|
||||||
mcu.add_config_cmd(
|
mcu.add_config_cmd(
|
||||||
"config_soft_pwm_out oid=%d pin=%s cycle_ticks=%d"
|
"config_soft_pwm_out oid=%d pin=%s cycle_ticks=TICKS(%f)"
|
||||||
" default_value=0 max_duration=%d" % (
|
" default_value=%d max_duration=TICKS(%f)" % (
|
||||||
self._oid, pin, cycle_ticks, max_duration))
|
self._oid, pin, cycle_time, self._invert, max_duration))
|
||||||
self._set_cmd = mcu.lookup_command(
|
self._set_cmd = None
|
||||||
"schedule_soft_pwm_out oid=%c clock=%u value=%c")
|
|
||||||
self.print_to_mcu_time = mcu.print_to_mcu_time
|
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):
|
def set_pwm(self, mcu_time, value):
|
||||||
clock = int(mcu_time * self._mcu_freq)
|
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)
|
msg = self._set_cmd.encode(self._oid, clock, value)
|
||||||
self._mcu.send(msg, minclock=self._last_clock, reqclock=clock
|
self._mcu.send(msg, minclock=self._last_clock, reqclock=clock
|
||||||
, cq=self._cmd_queue)
|
, cq=self._cmd_queue)
|
||||||
@@ -258,41 +315,46 @@ class MCU_pwm:
|
|||||||
class MCU_adc:
|
class MCU_adc:
|
||||||
def __init__(self, mcu, pin):
|
def __init__(self, mcu, pin):
|
||||||
self._mcu = mcu
|
self._mcu = mcu
|
||||||
self._oid = mcu.create_oid()
|
self._oid = mcu.create_oid(self)
|
||||||
self._min_sample = 0
|
self._min_sample = self._max_sample = 0.
|
||||||
self._max_sample = 0xffff
|
self._sample_time = self._report_time = 0.
|
||||||
self._sample_ticks = 0
|
self._sample_count = 0
|
||||||
self._sample_count = 1
|
|
||||||
self._report_clock = 0
|
self._report_clock = 0
|
||||||
self._callback = None
|
self._callback = None
|
||||||
self._inv_max_adc = 0.
|
self._inv_max_adc = 0.
|
||||||
self._mcu_freq = mcu.get_mcu_freq()
|
self._mcu_freq = 0.
|
||||||
self._cmd_queue = mcu.alloc_command_queue()
|
self._cmd_queue = mcu.alloc_command_queue()
|
||||||
mcu.add_config_cmd("config_analog_in oid=%d pin=%s" % (self._oid, pin))
|
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.add_init_callback(self._init_callback)
|
||||||
mcu.register_msg(self._handle_analog_in_state, "analog_in_state"
|
self._query_cmd = None
|
||||||
, self._oid)
|
def build_config(self):
|
||||||
self._query_cmd = mcu.lookup_command(
|
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"
|
"query_analog_in oid=%c clock=%u sample_ticks=%u sample_count=%c"
|
||||||
" rest_ticks=%u min_value=%hu max_value=%hu")
|
" rest_ticks=%u min_value=%hu max_value=%hu")
|
||||||
def set_minmax(self, sample_time, sample_count, minval=None, maxval=None):
|
def set_minmax(self, sample_time, sample_count, minval=0., maxval=1.):
|
||||||
self._sample_ticks = int(sample_time * self._mcu_freq)
|
self._sample_time = sample_time
|
||||||
self._sample_count = sample_count
|
self._sample_count = sample_count
|
||||||
if minval is None:
|
self._min_sample = minval
|
||||||
minval = 0
|
self._max_sample = maxval
|
||||||
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
|
|
||||||
def _init_callback(self):
|
def _init_callback(self):
|
||||||
|
if not self._sample_count:
|
||||||
|
return
|
||||||
last_clock, last_clock_time = self._mcu.get_last_clock()
|
last_clock, last_clock_time = self._mcu.get_last_clock()
|
||||||
clock = last_clock + int(self._mcu_freq * (1.0 + self._oid * 0.01)) # XXX
|
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(
|
msg = self._query_cmd.encode(
|
||||||
self._oid, clock, self._sample_ticks, self._sample_count
|
self._oid, clock, sample_ticks, self._sample_count
|
||||||
, self._report_clock, self._min_sample, self._max_sample)
|
, self._report_clock, min_sample, max_sample)
|
||||||
self._mcu.send(msg, reqclock=clock, cq=self._cmd_queue)
|
self._mcu.send(msg, reqclock=clock, cq=self._cmd_queue)
|
||||||
def _handle_analog_in_state(self, params):
|
def _handle_analog_in_state(self, params):
|
||||||
last_value = params['value'] * self._inv_max_adc
|
last_value = params['value'] * self._inv_max_adc
|
||||||
@@ -301,7 +363,7 @@ class MCU_adc:
|
|||||||
if self._callback is not None:
|
if self._callback is not None:
|
||||||
self._callback(last_read_time, last_value)
|
self._callback(last_read_time, last_value)
|
||||||
def set_adc_callback(self, report_time, callback):
|
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
|
self._callback = callback
|
||||||
|
|
||||||
class MCU:
|
class MCU:
|
||||||
@@ -309,60 +371,91 @@ class MCU:
|
|||||||
COMM_TIMEOUT = 3.5
|
COMM_TIMEOUT = 3.5
|
||||||
def __init__(self, printer, config):
|
def __init__(self, printer, config):
|
||||||
self._printer = printer
|
self._printer = printer
|
||||||
self._config = config
|
|
||||||
# Serial port
|
# Serial port
|
||||||
baud = config.getint('baud', 115200)
|
baud = config.getint('baud', 250000)
|
||||||
serialport = config.get('serial', '/dev/ttyS0')
|
self._serialport = config.get('serial', '/dev/ttyS0')
|
||||||
self.serial = serialhdl.SerialReader(printer.reactor, serialport, baud)
|
self.serial = serialhdl.SerialReader(
|
||||||
|
printer.reactor, self._serialport, baud)
|
||||||
self.is_shutdown = False
|
self.is_shutdown = False
|
||||||
|
self._shutdown_msg = ""
|
||||||
self._is_fileoutput = False
|
self._is_fileoutput = False
|
||||||
self._timeout_timer = printer.reactor.register_timer(
|
self._timeout_timer = printer.reactor.register_timer(
|
||||||
self.timeout_handler)
|
self.timeout_handler)
|
||||||
|
rmethods = {m: m for m in ['arduino', 'command', 'rpi_usb']}
|
||||||
|
self._restart_method = config.getchoice(
|
||||||
|
'restart_method', rmethods, 'arduino')
|
||||||
# Config building
|
# Config building
|
||||||
self._emergency_stop_cmd = self._clear_shutdown_cmd = None
|
if printer.bglogger is not None:
|
||||||
self._num_oids = 0
|
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_cmds = []
|
||||||
self._config_crc = None
|
self._config_crc = None
|
||||||
self._init_callbacks = []
|
self._init_callbacks = []
|
||||||
|
self._pin_map = config.get('pin_map', None)
|
||||||
|
self._custom = config.get('custom', '')
|
||||||
# Move command queuing
|
# 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._steppers = []
|
||||||
self._steppersync = None
|
self._steppersync = None
|
||||||
# Print time to clock epoch calculations
|
# Print time to clock epoch calculations
|
||||||
self._print_start_time = 0.
|
self._print_start_time = 0.
|
||||||
self._mcu_freq = 0.
|
self._mcu_freq = 0.
|
||||||
# Stats
|
# Stats
|
||||||
|
self._stats_sumsq_base = 0.
|
||||||
self._mcu_tick_avg = 0.
|
self._mcu_tick_avg = 0.
|
||||||
self._mcu_tick_stddev = 0.
|
self._mcu_tick_stddev = 0.
|
||||||
def handle_mcu_stats(self, params):
|
def handle_mcu_stats(self, params):
|
||||||
logging.debug("mcu stats: %s" % (params,))
|
|
||||||
count = params['count']
|
count = params['count']
|
||||||
tick_sum = params['sum']
|
tick_sum = params['sum']
|
||||||
c = 1.0 / (count * self._mcu_freq)
|
c = 1.0 / (count * self._mcu_freq)
|
||||||
self._mcu_tick_avg = tick_sum * c
|
self._mcu_tick_avg = tick_sum * c
|
||||||
tick_sumsq = params['sumsq']
|
tick_sumsq = params['sumsq'] * self._stats_sumsq_base
|
||||||
tick_sumavgsq = ((tick_sum // (256*count)) * count)**2
|
self._mcu_tick_stddev = c * math.sqrt(count*tick_sumsq - tick_sum**2)
|
||||||
self._mcu_tick_stddev = c * 256. * math.sqrt(
|
|
||||||
count * tick_sumsq - tick_sumavgsq)
|
|
||||||
def handle_shutdown(self, params):
|
def handle_shutdown(self, params):
|
||||||
if self.is_shutdown:
|
if self.is_shutdown:
|
||||||
return
|
return
|
||||||
self.is_shutdown = True
|
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.serial.dump_debug()
|
||||||
self._printer.note_shutdown(params['#msg'])
|
self._printer.note_shutdown(self._shutdown_msg)
|
||||||
# Connection phase
|
# 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):
|
def connect(self):
|
||||||
if not self._is_fileoutput:
|
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.serial.connect()
|
||||||
self._printer.reactor.update_timer(
|
self._printer.reactor.update_timer(
|
||||||
self._timeout_timer, time.time() + self.COMM_TIMEOUT)
|
self._timeout_timer, self.monotonic() + self.COMM_TIMEOUT)
|
||||||
self._mcu_freq = float(self.serial.msgparser.config['CLOCK_FREQ'])
|
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._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, 'shutdown')
|
||||||
self.register_msg(self.handle_shutdown, 'is_shutdown')
|
self.register_msg(self.handle_shutdown, 'is_shutdown')
|
||||||
self.register_msg(self.handle_mcu_stats, 'stats')
|
self.register_msg(self.handle_mcu_stats, 'stats')
|
||||||
|
self._build_config()
|
||||||
|
self._send_config()
|
||||||
def connect_file(self, debugoutput, dictionary, pace=False):
|
def connect_file(self, debugoutput, dictionary, pace=False):
|
||||||
self._is_fileoutput = True
|
self._is_fileoutput = True
|
||||||
self.serial.connect_file(debugoutput, dictionary)
|
self.serial.connect_file(debugoutput, dictionary)
|
||||||
@@ -370,7 +463,7 @@ class MCU:
|
|||||||
def dummy_set_print_start_time(eventtime):
|
def dummy_set_print_start_time(eventtime):
|
||||||
pass
|
pass
|
||||||
def dummy_get_print_buffer_time(eventtime, last_move_end):
|
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.set_print_start_time = dummy_set_print_start_time
|
||||||
self.get_print_buffer_time = dummy_get_print_buffer_time
|
self.get_print_buffer_time = dummy_get_print_buffer_time
|
||||||
def timeout_handler(self, eventtime):
|
def timeout_handler(self, eventtime):
|
||||||
@@ -385,29 +478,45 @@ class MCU:
|
|||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
self.serial.disconnect()
|
self.serial.disconnect()
|
||||||
if self._steppersync is not None:
|
if self._steppersync is not None:
|
||||||
self.ffi_lib.steppersync_free(self._steppersync)
|
self._ffi_lib.steppersync_free(self._steppersync)
|
||||||
self._steppersync = None
|
self._steppersync = None
|
||||||
def stats(self, eventtime):
|
def stats(self, eventtime):
|
||||||
stats = self.serial.stats(eventtime)
|
return "%s mcu_task_avg=%.06f mcu_task_stddev=%.06f" % (
|
||||||
stats += " mcu_task_avg=%.06f mcu_task_stddev=%.06f" % (
|
self.serial.stats(eventtime),
|
||||||
self._mcu_tick_avg, self._mcu_tick_stddev)
|
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):
|
def force_shutdown(self):
|
||||||
self.send(self._emergency_stop_cmd.encode())
|
self.send(self._emergency_stop_cmd.encode())
|
||||||
def clear_shutdown(self):
|
def microcontroller_restart(self):
|
||||||
logging.info("Sending clear_shutdown command")
|
reactor = self._printer.reactor
|
||||||
self.send(self._clear_shutdown_cmd.encode())
|
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):
|
def is_fileoutput(self):
|
||||||
return self._is_fileoutput
|
return self._is_fileoutput
|
||||||
# Configuration phase
|
# Configuration phase
|
||||||
def _add_custom(self):
|
def _add_custom(self):
|
||||||
data = self._config.get('custom', '')
|
for line in self._custom.split('\n'):
|
||||||
for line in data.split('\n'):
|
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
cpos = line.find('#')
|
cpos = line.find('#')
|
||||||
if cpos >= 0:
|
if cpos >= 0:
|
||||||
@@ -415,33 +524,30 @@ class MCU:
|
|||||||
if not line:
|
if not line:
|
||||||
continue
|
continue
|
||||||
self.add_config_cmd(line)
|
self.add_config_cmd(line)
|
||||||
def build_config(self):
|
def _build_config(self):
|
||||||
# Build config commands
|
# Build config commands
|
||||||
|
for oid in self._oids:
|
||||||
|
oid.build_config()
|
||||||
self._add_custom()
|
self._add_custom()
|
||||||
self._config_cmds.insert(0, "allocate_oids count=%d" % (
|
self._config_cmds.insert(0, "allocate_oids count=%d" % (
|
||||||
self._num_oids,))
|
len(self._oids),))
|
||||||
|
|
||||||
# Resolve pin names
|
# Resolve pin names
|
||||||
mcu = self.serial.msgparser.config['MCU']
|
mcu = self.serial.msgparser.get_constant('MCU')
|
||||||
pin_map = self._config.get('pin_map', None)
|
pnames = pins.get_pin_map(mcu, self._pin_map)
|
||||||
if pin_map is None:
|
|
||||||
pnames = pins.mcu_to_pins(mcu)
|
|
||||||
else:
|
|
||||||
pnames = pins.map_pins(pin_map, mcu)
|
|
||||||
updated_cmds = []
|
updated_cmds = []
|
||||||
for cmd in self._config_cmds:
|
for cmd in self._config_cmds:
|
||||||
try:
|
try:
|
||||||
updated_cmds.append(pins.update_command(cmd, pnames))
|
updated_cmds.append(pins.update_command(
|
||||||
|
cmd, self._mcu_freq, pnames))
|
||||||
except:
|
except:
|
||||||
raise self._config.error("Unable to translate pin name: %s" % (
|
raise self._config_error("Unable to translate pin name: %s" % (
|
||||||
cmd,))
|
cmd,))
|
||||||
self._config_cmds = updated_cmds
|
self._config_cmds = updated_cmds
|
||||||
|
|
||||||
# Calculate config CRC
|
# Calculate config CRC
|
||||||
self._config_crc = zlib.crc32('\n'.join(self._config_cmds)) & 0xffffffff
|
self._config_crc = zlib.crc32('\n'.join(self._config_cmds)) & 0xffffffff
|
||||||
self.add_config_cmd("finalize_config crc=%d" % (self._config_crc,))
|
self.add_config_cmd("finalize_config crc=%d" % (self._config_crc,))
|
||||||
|
|
||||||
self._send_config()
|
|
||||||
def _send_config(self):
|
def _send_config(self):
|
||||||
msg = self.create_command("get_config")
|
msg = self.create_command("get_config")
|
||||||
if self._is_fileoutput:
|
if self._is_fileoutput:
|
||||||
@@ -450,25 +556,45 @@ class MCU:
|
|||||||
else:
|
else:
|
||||||
config_params = self.serial.send_with_response(msg, 'config')
|
config_params = self.serial.send_with_response(msg, 'config')
|
||||||
if not config_params['is_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
|
# Send config commands
|
||||||
|
logging.info("Sending printer configuration...")
|
||||||
for c in self._config_cmds:
|
for c in self._config_cmds:
|
||||||
self.send(self.create_command(c))
|
self.send(self.create_command(c))
|
||||||
if not self._is_fileoutput:
|
if not self._is_fileoutput:
|
||||||
config_params = self.serial.send_with_response(msg, 'config')
|
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']:
|
if self._config_crc != config_params['crc']:
|
||||||
|
self._check_restart("CRC mismatch")
|
||||||
raise error("Printer CRC does not match config")
|
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)
|
stepqueues = tuple(s._stepqueue for s in self._steppers)
|
||||||
self._steppersync = self.ffi_lib.steppersync_alloc(
|
self._steppersync = self._ffi_lib.steppersync_alloc(
|
||||||
self.serial.serialqueue, stepqueues, len(stepqueues),
|
self.serial.serialqueue, stepqueues, len(stepqueues), move_count)
|
||||||
config_params['move_count'])
|
|
||||||
for cb in self._init_callbacks:
|
for cb in self._init_callbacks:
|
||||||
cb()
|
cb()
|
||||||
# Config creation helpers
|
# Config creation helpers
|
||||||
def create_oid(self):
|
def create_oid(self, oid):
|
||||||
oid = self._num_oids
|
self._oids.append(oid)
|
||||||
self._num_oids += 1
|
return len(self._oids) - 1
|
||||||
return oid
|
|
||||||
def add_config_cmd(self, cmd):
|
def add_config_cmd(self, cmd):
|
||||||
self._config_cmds.append(cmd)
|
self._config_cmds.append(cmd)
|
||||||
def add_init_callback(self, callback):
|
def add_init_callback(self, callback):
|
||||||
@@ -484,26 +610,24 @@ class MCU:
|
|||||||
def create_command(self, msg):
|
def create_command(self, msg):
|
||||||
return self.serial.msgparser.create_command(msg)
|
return self.serial.msgparser.create_command(msg)
|
||||||
# Wrappers for mcu object creation
|
# Wrappers for mcu object creation
|
||||||
def create_stepper(self, step_pin, dir_pin, min_stop_interval, max_error):
|
def create_stepper(self, step_pin, dir_pin):
|
||||||
return MCU_stepper(self, step_pin, dir_pin, min_stop_interval, max_error)
|
return MCU_stepper(self, step_pin, dir_pin)
|
||||||
def create_endstop(self, pin, stepper):
|
def create_endstop(self, pin):
|
||||||
return MCU_endstop(self, pin, stepper)
|
return MCU_endstop(self, pin)
|
||||||
def create_digital_out(self, pin, max_duration=2.):
|
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)
|
return MCU_digital_out(self, pin, max_duration)
|
||||||
def create_pwm(self, pin, hard_cycle_ticks, max_duration=2.):
|
def create_pwm(self, pin, cycle_time, hard_cycle_ticks=0, 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)
|
|
||||||
if hard_cycle_ticks < 0:
|
if hard_cycle_ticks < 0:
|
||||||
return MCU_digital_out(self, pin, max_duration)
|
return MCU_digital_out(self, pin, max_duration)
|
||||||
cycle_ticks = int(self._mcu_freq / 10.)
|
return MCU_pwm(self, pin, cycle_time, hard_cycle_ticks, max_duration)
|
||||||
return MCU_pwm(self, pin, cycle_ticks, max_duration, hard_pwm=False)
|
|
||||||
def create_adc(self, pin):
|
def create_adc(self, pin):
|
||||||
return MCU_adc(self, pin)
|
return MCU_adc(self, pin)
|
||||||
# Clock syncing
|
# Clock syncing
|
||||||
def set_print_start_time(self, eventtime):
|
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
|
self._print_start_time = est_mcu_time
|
||||||
def get_print_buffer_time(self, eventtime, print_time):
|
def get_print_buffer_time(self, eventtime, print_time):
|
||||||
if self.is_shutdown:
|
if self.is_shutdown:
|
||||||
@@ -517,14 +641,22 @@ class MCU:
|
|||||||
return self._mcu_freq
|
return self._mcu_freq
|
||||||
def get_last_clock(self):
|
def get_last_clock(self):
|
||||||
return self.serial.get_last_clock()
|
return self.serial.get_last_clock()
|
||||||
|
def get_max_stepper_error(self):
|
||||||
|
return self._max_stepper_error
|
||||||
# Move command queuing
|
# Move command queuing
|
||||||
def send(self, cmd, minclock=0, reqclock=0, cq=None):
|
def send(self, cmd, minclock=0, reqclock=0, cq=None):
|
||||||
self.serial.send(cmd, minclock, reqclock, cq=cq)
|
self.serial.send(cmd, minclock, reqclock, cq=cq)
|
||||||
def flush_moves(self, print_time):
|
def flush_moves(self, print_time):
|
||||||
|
if self._steppersync is None:
|
||||||
|
return
|
||||||
mcu_time = print_time + self._print_start_time
|
mcu_time = print_time + self._print_start_time
|
||||||
clock = int(mcu_time * self._mcu_freq)
|
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):
|
def pause(self, waketime):
|
||||||
return self._printer.reactor.pause(waketime)
|
return self._printer.reactor.pause(waketime)
|
||||||
|
def monotonic(self):
|
||||||
|
return self._printer.reactor.monotonic()
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Protocol definitions for firmware communication
|
# 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.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import json, zlib, logging
|
import json, zlib, logging
|
||||||
@@ -106,8 +106,8 @@ class MessageFormat:
|
|||||||
self.name = parts[0]
|
self.name = parts[0]
|
||||||
argparts = [arg.split('=') for arg in parts[1:]]
|
argparts = [arg.split('=') for arg in parts[1:]]
|
||||||
self.param_types = [MessageTypes[fmt] for name, fmt in argparts]
|
self.param_types = [MessageTypes[fmt] for name, fmt in argparts]
|
||||||
self.param_names = [name for name, fmt in argparts]
|
self.param_names = [(name, MessageTypes[fmt]) for name, fmt in argparts]
|
||||||
self.name_to_type = dict(zip(self.param_names, self.param_types))
|
self.name_to_type = dict(self.param_names)
|
||||||
def encode(self, *params):
|
def encode(self, *params):
|
||||||
out = []
|
out = []
|
||||||
out.append(self.msgid)
|
out.append(self.msgid)
|
||||||
@@ -117,26 +117,24 @@ class MessageFormat:
|
|||||||
def encode_by_name(self, **params):
|
def encode_by_name(self, **params):
|
||||||
out = []
|
out = []
|
||||||
out.append(self.msgid)
|
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])
|
t.encode(out, params[name])
|
||||||
return out
|
return out
|
||||||
def parse(self, s, pos):
|
def parse(self, s, pos):
|
||||||
pos += 1
|
pos += 1
|
||||||
out = {}
|
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)
|
v, pos = t.parse(s, pos)
|
||||||
out[name] = v
|
out[name] = v
|
||||||
return out, pos
|
return out, pos
|
||||||
def dump(self, s, pos):
|
def format_params(self, params):
|
||||||
pos += 1
|
|
||||||
out = []
|
out = []
|
||||||
for t in self.param_types:
|
for name, t in self.param_names:
|
||||||
v, pos = t.parse(s, pos)
|
v = params[name]
|
||||||
if not t.is_int:
|
if not t.is_int:
|
||||||
v = repr(v)
|
v = repr(v)
|
||||||
out.append(v)
|
out.append(v)
|
||||||
outmsg = self.debugformat % tuple(out)
|
return self.debugformat % tuple(out)
|
||||||
return outmsg, pos
|
|
||||||
|
|
||||||
class OutputFormat:
|
class OutputFormat:
|
||||||
name = '#output'
|
name = '#output'
|
||||||
@@ -164,17 +162,13 @@ class OutputFormat:
|
|||||||
out = []
|
out = []
|
||||||
for t in self.param_types:
|
for t in self.param_types:
|
||||||
v, pos = t.parse(s, pos)
|
v, pos = t.parse(s, pos)
|
||||||
|
if not t.is_int:
|
||||||
|
v = repr(v)
|
||||||
out.append(v)
|
out.append(v)
|
||||||
outmsg = self.debugformat % tuple(out)
|
outmsg = self.debugformat % tuple(out)
|
||||||
return {'#msg': outmsg}, pos
|
return {'#msg': outmsg}, pos
|
||||||
def dump(self, s, pos):
|
def format_params(self, params):
|
||||||
pos += 1
|
return "#output %s" % (params['#msg'],)
|
||||||
out = []
|
|
||||||
for t in self.param_types:
|
|
||||||
v, pos = t.parse(s, pos)
|
|
||||||
out.append(v)
|
|
||||||
outmsg = self.debugformat % tuple(out)
|
|
||||||
return outmsg, pos
|
|
||||||
|
|
||||||
class UnknownFormat:
|
class UnknownFormat:
|
||||||
name = '#unknown'
|
name = '#unknown'
|
||||||
@@ -182,8 +176,11 @@ class UnknownFormat:
|
|||||||
msgid = s[pos]
|
msgid = s[pos]
|
||||||
msg = str(bytearray(s))
|
msg = str(bytearray(s))
|
||||||
return {'#msgid': msgid, '#msg': msg}, len(s)-MESSAGE_TRAILER_SIZE
|
return {'#msgid': msgid, '#msg': msg}, len(s)-MESSAGE_TRAILER_SIZE
|
||||||
|
def format_params(self, params):
|
||||||
|
return "#unknown %s" % (repr(params['#msg']),)
|
||||||
|
|
||||||
class MessageParser:
|
class MessageParser:
|
||||||
|
error = error
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.unknown = UnknownFormat()
|
self.unknown = UnknownFormat()
|
||||||
self.messages_by_id = {}
|
self.messages_by_id = {}
|
||||||
@@ -220,11 +217,20 @@ class MessageParser:
|
|||||||
while 1:
|
while 1:
|
||||||
msgid = s[pos]
|
msgid = s[pos]
|
||||||
mid = self.messages_by_id.get(msgid, self.unknown)
|
mid = self.messages_by_id.get(msgid, self.unknown)
|
||||||
params, pos = mid.dump(s, pos)
|
params, pos = mid.parse(s, pos)
|
||||||
out.append("%s" % (params,))
|
out.append(mid.format_params(params))
|
||||||
if pos >= len(s)-MESSAGE_TRAILER_SIZE:
|
if pos >= len(s)-MESSAGE_TRAILER_SIZE:
|
||||||
break
|
break
|
||||||
return out
|
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):
|
def parse(self, s):
|
||||||
msgid = s[MESSAGE_HEADER_SIZE]
|
msgid = s[MESSAGE_HEADER_SIZE]
|
||||||
mid = self.messages_by_id.get(msgid, self.unknown)
|
mid = self.messages_by_id.get(msgid, self.unknown)
|
||||||
@@ -308,3 +314,15 @@ class MessageParser:
|
|||||||
self.static_strings = data.get('static_strings', [])
|
self.static_strings = data.get('static_strings', [])
|
||||||
self.config.update(data.get('config', {}))
|
self.config.update(data.get('config', {}))
|
||||||
self.version = data.get('version', '')
|
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
|
pins['P%c%d' % (portchr, portbit)] = port * bit_count + portbit
|
||||||
return pins
|
return pins
|
||||||
|
|
||||||
PINS_atmega164 = port_pins(4)
|
|
||||||
PINS_atmega1280 = port_pins(12)
|
|
||||||
|
|
||||||
MCU_PINS = {
|
MCU_PINS = {
|
||||||
"atmega168": PINS_atmega164, "atmega644p": PINS_atmega164,
|
"atmega168": port_pins(4), "atmega644p": port_pins(4),
|
||||||
"at90usb1286": port_pins(5),
|
"at90usb1286": port_pins(5),
|
||||||
"atmega1280": PINS_atmega1280, "atmega2560": PINS_atmega1280,
|
"atmega1280": port_pins(12), "atmega2560": port_pins(12),
|
||||||
"sam3x8e": port_pins(4, 32)
|
"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
|
# Arduino mappings
|
||||||
@@ -96,12 +84,28 @@ Arduino_from_mcu = {
|
|||||||
"sam3x8e": (Arduino_Due, Arduino_Due_analog),
|
"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, {})
|
pins = MCU_PINS.get(mcu, {})
|
||||||
if name == 'arduino':
|
if mapping_name == 'arduino':
|
||||||
dpins, apins = Arduino_from_mcu.get(mcu, [])
|
dpins, apins = Arduino_from_mcu.get(mcu, [])
|
||||||
for i in range(len(dpins)):
|
for i in range(len(dpins)):
|
||||||
pins['ar' + str(i)] = pins[dpins[i]]
|
pins['ar' + str(i)] = pins[dpins[i]]
|
||||||
for i in range(len(apins)):
|
for i in range(len(apins)):
|
||||||
pins['analog%d' % (i,)] = pins[apins[i]]
|
pins['analog%d' % (i,)] = pins[apins[i]]
|
||||||
return pins
|
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 <stdint.h> // uint8_t
|
||||||
#include <stdio.h> // fprintf
|
#include <stdio.h> // fprintf
|
||||||
#include <string.h> // strerror
|
#include <string.h> // strerror
|
||||||
#include <sys/time.h> // gettimeofday
|
|
||||||
#include <time.h> // struct timespec
|
#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
|
double
|
||||||
get_time(void)
|
get_monotonic(void)
|
||||||
{
|
{
|
||||||
struct timeval tv;
|
struct timespec ts;
|
||||||
gettimeofday(&tv, NULL);
|
int ret = clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.;
|
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
|
// Fill a 'struct timespec' with a system time stored in a double
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
#ifndef PYHELPER_H
|
#ifndef PYHELPER_H
|
||||||
#define 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);
|
struct timespec fill_time(double time);
|
||||||
void set_python_logging_callback(void (*func)(const char *));
|
void set_python_logging_callback(void (*func)(const char *));
|
||||||
void errorf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
|
void errorf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Code to implement asynchronous logging from a background thread
|
# 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.
|
# 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 to forward all messages through a queue to a background thread
|
||||||
class QueueHandler(logging.Handler):
|
class QueueHandler(logging.Handler):
|
||||||
@@ -21,27 +21,39 @@ class QueueHandler(logging.Handler):
|
|||||||
self.handleError(record)
|
self.handleError(record)
|
||||||
|
|
||||||
# Class to poll a queue in a background thread and log each message
|
# Class to poll a queue in a background thread and log each message
|
||||||
class QueueListener(object):
|
class QueueListener(logging.handlers.TimedRotatingFileHandler):
|
||||||
def __init__(self, handler):
|
def __init__(self, filename):
|
||||||
self.handler = handler
|
logging.handlers.TimedRotatingFileHandler.__init__(
|
||||||
self.queue = Queue.Queue()
|
self, filename, when='midnight', backupCount=5)
|
||||||
self.thread = threading.Thread(target=self._bg_thread)
|
self.bg_queue = Queue.Queue()
|
||||||
self.thread.start()
|
self.bg_thread = threading.Thread(target=self._bg_thread)
|
||||||
|
self.bg_thread.start()
|
||||||
|
self.rollover_info = {}
|
||||||
def _bg_thread(self):
|
def _bg_thread(self):
|
||||||
while 1:
|
while 1:
|
||||||
record = self.queue.get(True)
|
record = self.bg_queue.get(True)
|
||||||
if record is None:
|
if record is None:
|
||||||
break
|
break
|
||||||
self.handler.handle(record)
|
self.handle(record)
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.queue.put_nowait(None)
|
self.bg_queue.put_nowait(None)
|
||||||
self.thread.join()
|
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):
|
def setup_bg_logging(filename, debuglevel):
|
||||||
logoutput = open(filename, 'wb')
|
ql = QueueListener(filename)
|
||||||
handler = logging.StreamHandler(logoutput)
|
qh = QueueHandler(ql.bg_queue)
|
||||||
ql = QueueListener(handler)
|
|
||||||
qh = QueueHandler(ql.queue)
|
|
||||||
root = logging.getLogger()
|
root = logging.getLogger()
|
||||||
root.addHandler(qh)
|
root.addHandler(qh)
|
||||||
root.setLevel(debuglevel)
|
root.setLevel(debuglevel)
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# File descriptor and timer event helper
|
# 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.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import select, time, math
|
import select, math, time
|
||||||
import greenlet
|
import greenlet
|
||||||
|
import chelper
|
||||||
|
|
||||||
class ReactorTimer:
|
class ReactorTimer:
|
||||||
def __init__(self, callback, waketime):
|
def __init__(self, callback, waketime):
|
||||||
@@ -33,6 +34,7 @@ class SelectReactor:
|
|||||||
self._process = False
|
self._process = False
|
||||||
self._g_dispatch = None
|
self._g_dispatch = None
|
||||||
self._greenlets = []
|
self._greenlets = []
|
||||||
|
self.monotonic = chelper.get_ffi()[1].get_monotonic
|
||||||
# Timers
|
# Timers
|
||||||
def _note_time(self, t):
|
def _note_time(self, t):
|
||||||
nexttime = t.waketime
|
nexttime = t.waketime
|
||||||
@@ -67,11 +69,19 @@ class SelectReactor:
|
|||||||
self._note_time(t)
|
self._note_time(t)
|
||||||
if eventtime >= self._next_timer:
|
if eventtime >= self._next_timer:
|
||||||
return 0.
|
return 0.
|
||||||
return min(1., max(.001, self._next_timer - time.time()))
|
return min(1., max(.001, self._next_timer - self.monotonic()))
|
||||||
# Greenlets
|
# 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):
|
def pause(self, waketime):
|
||||||
g = greenlet.getcurrent()
|
g = greenlet.getcurrent()
|
||||||
if g is not self._g_dispatch:
|
if g is not self._g_dispatch:
|
||||||
|
if self._g_dispatch is None:
|
||||||
|
return self._sys_pause(waketime)
|
||||||
return self._g_dispatch.switch(waketime)
|
return self._g_dispatch.switch(waketime)
|
||||||
if self._greenlets:
|
if self._greenlets:
|
||||||
g_next = self._greenlets.pop()
|
g_next = self._greenlets.pop()
|
||||||
@@ -95,20 +105,21 @@ class SelectReactor:
|
|||||||
self._fds.pop(self._fds.index(handler))
|
self._fds.pop(self._fds.index(handler))
|
||||||
# Main loop
|
# Main loop
|
||||||
def _dispatch_loop(self):
|
def _dispatch_loop(self):
|
||||||
self._process = True
|
|
||||||
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
||||||
eventtime = time.time()
|
eventtime = self.monotonic()
|
||||||
while self._process:
|
while self._process:
|
||||||
timeout = self._check_timers(eventtime)
|
timeout = self._check_timers(eventtime)
|
||||||
res = select.select(self._fds, [], [], timeout)
|
res = select.select(self._fds, [], [], timeout)
|
||||||
eventtime = time.time()
|
eventtime = self.monotonic()
|
||||||
for fd in res[0]:
|
for fd in res[0]:
|
||||||
fd.callback(eventtime)
|
fd.callback(eventtime)
|
||||||
if g_dispatch is not self._g_dispatch:
|
if g_dispatch is not self._g_dispatch:
|
||||||
self._end_greenlet(g_dispatch)
|
self._end_greenlet(g_dispatch)
|
||||||
|
eventtime = self.monotonic()
|
||||||
break
|
break
|
||||||
self._g_dispatch = None
|
self._g_dispatch = None
|
||||||
def run(self):
|
def run(self):
|
||||||
|
self._process = True
|
||||||
g_next = ReactorGreenlet(run=self._dispatch_loop)
|
g_next = ReactorGreenlet(run=self._dispatch_loop)
|
||||||
g_next.switch()
|
g_next.switch()
|
||||||
def end(self):
|
def end(self):
|
||||||
@@ -134,17 +145,17 @@ class PollReactor(SelectReactor):
|
|||||||
self._fds = fds
|
self._fds = fds
|
||||||
# Main loop
|
# Main loop
|
||||||
def _dispatch_loop(self):
|
def _dispatch_loop(self):
|
||||||
self._process = True
|
|
||||||
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
||||||
eventtime = time.time()
|
eventtime = self.monotonic()
|
||||||
while self._process:
|
while self._process:
|
||||||
timeout = int(math.ceil(self._check_timers(eventtime) * 1000.))
|
timeout = self._check_timers(eventtime)
|
||||||
res = self._poll.poll(timeout)
|
res = self._poll.poll(int(math.ceil(timeout * 1000.)))
|
||||||
eventtime = time.time()
|
eventtime = self.monotonic()
|
||||||
for fd, event in res:
|
for fd, event in res:
|
||||||
self._fds[fd](eventtime)
|
self._fds[fd](eventtime)
|
||||||
if g_dispatch is not self._g_dispatch:
|
if g_dispatch is not self._g_dispatch:
|
||||||
self._end_greenlet(g_dispatch)
|
self._end_greenlet(g_dispatch)
|
||||||
|
eventtime = self.monotonic()
|
||||||
break
|
break
|
||||||
self._g_dispatch = None
|
self._g_dispatch = None
|
||||||
|
|
||||||
@@ -168,17 +179,17 @@ class EPollReactor(SelectReactor):
|
|||||||
self._fds = fds
|
self._fds = fds
|
||||||
# Main loop
|
# Main loop
|
||||||
def _dispatch_loop(self):
|
def _dispatch_loop(self):
|
||||||
self._process = True
|
|
||||||
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
||||||
eventtime = time.time()
|
eventtime = self.monotonic()
|
||||||
while self._process:
|
while self._process:
|
||||||
timeout = self._check_timers(eventtime)
|
timeout = self._check_timers(eventtime)
|
||||||
res = self._epoll.poll(timeout)
|
res = self._epoll.poll(timeout)
|
||||||
eventtime = time.time()
|
eventtime = self.monotonic()
|
||||||
for fd, event in res:
|
for fd, event in res:
|
||||||
self._fds[fd](eventtime)
|
self._fds[fd](eventtime)
|
||||||
if g_dispatch is not self._g_dispatch:
|
if g_dispatch is not self._g_dispatch:
|
||||||
self._end_greenlet(g_dispatch)
|
self._end_greenlet(g_dispatch)
|
||||||
|
eventtime = self.monotonic()
|
||||||
break
|
break
|
||||||
self._g_dispatch = None
|
self._g_dispatch = None
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,14 @@
|
|||||||
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import time, logging, threading
|
import logging, threading
|
||||||
import serial
|
import serial
|
||||||
|
|
||||||
import msgproto, chelper, util
|
import msgproto, chelper, util
|
||||||
|
|
||||||
|
class error(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class SerialReader:
|
class SerialReader:
|
||||||
BITS_PER_BYTE = 10.
|
BITS_PER_BYTE = 10.
|
||||||
def __init__(self, reactor, serialport, baud):
|
def __init__(self, reactor, serialport, baud):
|
||||||
@@ -30,8 +33,7 @@ class SerialReader:
|
|||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
self.background_thread = None
|
self.background_thread = None
|
||||||
# Message handlers
|
# Message handlers
|
||||||
self.status_timer = self.reactor.register_timer(
|
self.status_timer = self.reactor.register_timer(self._status_event)
|
||||||
self._status_event, self.reactor.NOW)
|
|
||||||
self.status_cmd = None
|
self.status_cmd = None
|
||||||
handlers = {
|
handlers = {
|
||||||
'#unknown': self.handle_unknown,
|
'#unknown': self.handle_unknown,
|
||||||
@@ -60,10 +62,10 @@ class SerialReader:
|
|||||||
# Initial connection
|
# Initial connection
|
||||||
logging.info("Starting serial connect")
|
logging.info("Starting serial connect")
|
||||||
while 1:
|
while 1:
|
||||||
starttime = time.time()
|
starttime = self.reactor.monotonic()
|
||||||
try:
|
try:
|
||||||
self.ser = serial.Serial(self.serialport, self.baud, timeout=0)
|
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,))
|
logging.warn("Unable to open port: %s" % (e,))
|
||||||
self.reactor.pause(starttime + 5.)
|
self.reactor.pause(starttime + 5.)
|
||||||
continue
|
continue
|
||||||
@@ -78,8 +80,6 @@ class SerialReader:
|
|||||||
if identify_data is None:
|
if identify_data is None:
|
||||||
logging.warn("Timeout on serial connect")
|
logging.warn("Timeout on serial connect")
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
self.ser.close()
|
|
||||||
self.ser = None
|
|
||||||
continue
|
continue
|
||||||
break
|
break
|
||||||
msgparser = msgproto.MessageParser()
|
msgparser = msgproto.MessageParser()
|
||||||
@@ -88,14 +88,29 @@ class SerialReader:
|
|||||||
self.register_callback(self.handle_unknown, '#unknown')
|
self.register_callback(self.handle_unknown, '#unknown')
|
||||||
logging.info("Loaded %d commands (%s)" % (
|
logging.info("Loaded %d commands (%s)" % (
|
||||||
len(msgparser.messages_by_id), msgparser.version))
|
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.))
|
mcu_baud = float(msgparser.config.get('SERIAL_BAUD', 0.))
|
||||||
if mcu_baud:
|
if mcu_baud:
|
||||||
baud_adjust = self.BITS_PER_BYTE / mcu_baud
|
baud_adjust = self.BITS_PER_BYTE / mcu_baud
|
||||||
self.ffi_lib.serialqueue_set_baud_adjust(
|
self.ffi_lib.serialqueue_set_baud_adjust(
|
||||||
self.serialqueue, baud_adjust)
|
self.serialqueue, baud_adjust)
|
||||||
|
# Enable periodic get_status timer
|
||||||
get_status = msgparser.lookup_command('get_status')
|
get_status = msgparser.lookup_command('get_status')
|
||||||
self.status_cmd = get_status.encode()
|
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):
|
def connect_file(self, debugoutput, dictionary, pace=False):
|
||||||
self.ser = debugoutput
|
self.ser = debugoutput
|
||||||
self.msgparser.process_identify(dictionary, decompress=False)
|
self.msgparser.process_identify(dictionary, decompress=False)
|
||||||
@@ -104,19 +119,21 @@ class SerialReader:
|
|||||||
est_clock = float(self.msgparser.config['CLOCK_FREQ'])
|
est_clock = float(self.msgparser.config['CLOCK_FREQ'])
|
||||||
self.serialqueue = self.ffi_lib.serialqueue_alloc(self.ser.fileno(), 1)
|
self.serialqueue = self.ffi_lib.serialqueue_alloc(self.ser.fileno(), 1)
|
||||||
self.est_clock = est_clock
|
self.est_clock = est_clock
|
||||||
self.last_ack_time = time.time()
|
self.last_ack_time = self.reactor.monotonic()
|
||||||
self.last_ack_clock = 0
|
self.last_ack_clock = 0
|
||||||
self.ffi_lib.serialqueue_set_clock_est(
|
self.ffi_lib.serialqueue_set_clock_est(
|
||||||
self.serialqueue, self.est_clock, self.last_ack_time
|
self.serialqueue, self.est_clock, self.last_ack_time
|
||||||
, self.last_ack_clock)
|
, self.last_ack_clock)
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
if self.serialqueue is None:
|
if self.serialqueue is not None:
|
||||||
return
|
|
||||||
self.ffi_lib.serialqueue_exit(self.serialqueue)
|
self.ffi_lib.serialqueue_exit(self.serialqueue)
|
||||||
if self.background_thread is not None:
|
if self.background_thread is not None:
|
||||||
self.background_thread.join()
|
self.background_thread.join()
|
||||||
self.ffi_lib.serialqueue_free(self.serialqueue)
|
self.ffi_lib.serialqueue_free(self.serialqueue)
|
||||||
self.background_thread = self.serialqueue = None
|
self.background_thread = self.serialqueue = None
|
||||||
|
if self.ser is not None:
|
||||||
|
self.ser.close()
|
||||||
|
self.ser = None
|
||||||
def stats(self, eventtime):
|
def stats(self, eventtime):
|
||||||
if self.serialqueue is None:
|
if self.serialqueue is None:
|
||||||
return ""
|
return ""
|
||||||
@@ -127,8 +144,6 @@ class SerialReader:
|
|||||||
self.est_clock, self.last_ack_time, self.last_ack_clock)
|
self.est_clock, self.last_ack_time, self.last_ack_clock)
|
||||||
return sqstats + tstats
|
return sqstats + tstats
|
||||||
def _status_event(self, eventtime):
|
def _status_event(self, eventtime):
|
||||||
if self.status_cmd is None:
|
|
||||||
return eventtime + 0.1
|
|
||||||
self.send(self.status_cmd)
|
self.send(self.status_cmd)
|
||||||
return eventtime + 1.0
|
return eventtime + 1.0
|
||||||
# Serial response callbacks
|
# Serial response callbacks
|
||||||
@@ -162,11 +177,9 @@ class SerialReader:
|
|||||||
def encode_and_send(self, data, minclock, reqclock, cq):
|
def encode_and_send(self, data, minclock, reqclock, cq):
|
||||||
self.ffi_lib.serialqueue_encode_and_send(
|
self.ffi_lib.serialqueue_encode_and_send(
|
||||||
self.serialqueue, cq, data, len(data), minclock, reqclock)
|
self.serialqueue, cq, data, len(data), minclock, reqclock)
|
||||||
def send_with_response(self, cmd, name):
|
def send_with_response(self, cmd, name, oid=None):
|
||||||
src = SerialRetryCommand(self, cmd, name)
|
src = SerialRetryCommand(self, cmd, name, oid)
|
||||||
return src.get_response()
|
return src.get_response()
|
||||||
def send_flush(self):
|
|
||||||
self.ffi_lib.serialqueue_flush_ready(self.serialqueue)
|
|
||||||
def alloc_command_queue(self):
|
def alloc_command_queue(self):
|
||||||
return self.ffi_main.gc(self.ffi_lib.serialqueue_alloc_commandqueue(),
|
return self.ffi_main.gc(self.ffi_lib.serialqueue_alloc_commandqueue(),
|
||||||
self.ffi_lib.serialqueue_free_commandqueue)
|
self.ffi_lib.serialqueue_free_commandqueue)
|
||||||
@@ -226,16 +239,21 @@ class SerialReader:
|
|||||||
|
|
||||||
# Class to retry sending of a query command until a given response is received
|
# Class to retry sending of a query command until a given response is received
|
||||||
class SerialRetryCommand:
|
class SerialRetryCommand:
|
||||||
|
TIMEOUT_TIME = 5.0
|
||||||
RETRY_TIME = 0.500
|
RETRY_TIME = 0.500
|
||||||
def __init__(self, serial, cmd, name):
|
def __init__(self, serial, cmd, name, oid=None):
|
||||||
self.serial = serial
|
self.serial = serial
|
||||||
self.cmd = cmd
|
self.cmd = cmd
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.oid = oid
|
||||||
self.response = None
|
self.response = None
|
||||||
self.min_query_time = time.time()
|
self.min_query_time = self.serial.reactor.monotonic()
|
||||||
self.serial.register_callback(self.handle_callback, self.name)
|
self.serial.register_callback(self.handle_callback, self.name, self.oid)
|
||||||
self.send_timer = self.serial.reactor.register_timer(
|
self.send_timer = self.serial.reactor.register_timer(
|
||||||
self.send_event, self.serial.reactor.NOW)
|
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):
|
def send_event(self, eventtime):
|
||||||
if self.response is not None:
|
if self.response is not None:
|
||||||
return self.serial.reactor.NEVER
|
return self.serial.reactor.NEVER
|
||||||
@@ -246,11 +264,13 @@ class SerialRetryCommand:
|
|||||||
if last_sent_time >= self.min_query_time:
|
if last_sent_time >= self.min_query_time:
|
||||||
self.response = params
|
self.response = params
|
||||||
def get_response(self):
|
def get_response(self):
|
||||||
eventtime = time.time()
|
eventtime = self.serial.reactor.monotonic()
|
||||||
while self.response is None:
|
while self.response is None:
|
||||||
eventtime = self.serial.reactor.pause(eventtime + 0.05)
|
eventtime = self.serial.reactor.pause(eventtime + 0.05)
|
||||||
self.serial.unregister_callback(self.name)
|
if eventtime > self.min_query_time + self.TIMEOUT_TIME:
|
||||||
self.serial.reactor.unregister_timer(self.send_timer)
|
self.unregister()
|
||||||
|
raise error("Timeout on wait for '%s' response" % (self.name,))
|
||||||
|
self.unregister()
|
||||||
return self.response
|
return self.response
|
||||||
|
|
||||||
# Code to start communication and download message type dictionary
|
# 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_timer = self.serial.reactor.register_timer(
|
||||||
self.send_event, self.serial.reactor.NOW)
|
self.send_event, self.serial.reactor.NOW)
|
||||||
def get_identify_data(self, timeout):
|
def get_identify_data(self, timeout):
|
||||||
eventtime = time.time()
|
eventtime = self.serial.reactor.monotonic()
|
||||||
while not self.is_done and eventtime <= timeout:
|
while not self.is_done and eventtime <= timeout:
|
||||||
eventtime = self.serial.reactor.pause(eventtime + 0.05)
|
eventtime = self.serial.reactor.pause(eventtime + 0.05)
|
||||||
self.serial.unregister_callback('identify_response')
|
self.serial.unregister_callback('identify_response')
|
||||||
@@ -305,10 +325,23 @@ def stk500v2_leave(ser, reactor):
|
|||||||
ser.read(1)
|
ser.read(1)
|
||||||
# Send stk500v2 leave programmer sequence
|
# Send stk500v2 leave programmer sequence
|
||||||
ser.baudrate = 115200
|
ser.baudrate = 115200
|
||||||
reactor.pause(time.time() + 0.100)
|
reactor.pause(reactor.monotonic() + 0.100)
|
||||||
ser.read(4096)
|
ser.read(4096)
|
||||||
ser.write('\x1b\x01\x00\x01\x0e\x11\x04')
|
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)
|
res = ser.read(4096)
|
||||||
logging.debug("Got %s from stk500v2" % (repr(res),))
|
logging.debug("Got %s from stk500v2" % (repr(res),))
|
||||||
ser.baudrate = origbaud
|
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 <termios.h> // tcflush
|
||||||
#include <unistd.h> // pipe
|
#include <unistd.h> // pipe
|
||||||
#include "list.h" // list_add_tail
|
#include "list.h" // list_add_tail
|
||||||
#include "pyhelper.h" // get_time
|
#include "pyhelper.h" // get_monotonic
|
||||||
#include "serialqueue.h" // struct queue_message
|
#include "serialqueue.h" // struct queue_message
|
||||||
|
|
||||||
|
|
||||||
@@ -149,11 +149,11 @@ static void
|
|||||||
pollreactor_run(struct pollreactor *pr)
|
pollreactor_run(struct pollreactor *pr)
|
||||||
{
|
{
|
||||||
pr->must_exit = 0;
|
pr->must_exit = 0;
|
||||||
double eventtime = get_time();
|
double eventtime = get_monotonic();
|
||||||
while (! pr->must_exit) {
|
while (! pr->must_exit) {
|
||||||
int timeout = pollreactor_check_timers(pr, eventtime);
|
int timeout = pollreactor_check_timers(pr, eventtime);
|
||||||
int ret = poll(pr->fds, pr->num_fds, timeout);
|
int ret = poll(pr->fds, pr->num_fds, timeout);
|
||||||
eventtime = get_time();
|
eventtime = get_monotonic();
|
||||||
if (ret > 0) {
|
if (ret > 0) {
|
||||||
int i;
|
int i;
|
||||||
for (i=0; i<pr->num_fds; i++)
|
for (i=0; i<pr->num_fds; i++)
|
||||||
@@ -356,7 +356,6 @@ struct serialqueue {
|
|||||||
struct list_head pending_queues;
|
struct list_head pending_queues;
|
||||||
int ready_bytes, stalled_bytes;
|
int ready_bytes, stalled_bytes;
|
||||||
uint64_t need_kick_clock;
|
uint64_t need_kick_clock;
|
||||||
int can_delay_writes;
|
|
||||||
// Received messages
|
// Received messages
|
||||||
struct list_head receive_queue;
|
struct list_head receive_queue;
|
||||||
// Debugging
|
// Debugging
|
||||||
@@ -488,7 +487,8 @@ handle_message(struct serialqueue *sq, double eventtime, int len)
|
|||||||
if (rseq != sq->receive_seq)
|
if (rseq != sq->receive_seq)
|
||||||
// New sequence number
|
// New sequence number
|
||||||
update_receive_seq(sq, eventtime, rseq);
|
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
|
// Duplicate sequence number in an empty message is a nak
|
||||||
pollreactor_update_timer(&sq->pr, SQPT_RETRANSMIT, PR_NOW);
|
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
|
// Add message to receive queue
|
||||||
struct queue_message *qm = message_fill(sq->input_buf, len);
|
struct queue_message *qm = message_fill(sq->input_buf, len);
|
||||||
qm->sent_time = sq->last_receive_sent_time;
|
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);
|
list_add_tail(&qm->node, &sq->receive_queue);
|
||||||
check_wake_receive(sq);
|
check_wake_receive(sq);
|
||||||
}
|
}
|
||||||
@@ -695,11 +695,9 @@ check_send_command(struct serialqueue *sq, double eventtime)
|
|||||||
// Check for messages to send
|
// Check for messages to send
|
||||||
if (sq->ready_bytes >= MESSAGE_PAYLOAD_MAX)
|
if (sq->ready_bytes >= MESSAGE_PAYLOAD_MAX)
|
||||||
return PR_NOW;
|
return PR_NOW;
|
||||||
if (! sq->can_delay_writes) {
|
if (! sq->est_clock) {
|
||||||
if (sq->ready_bytes)
|
if (sq->ready_bytes)
|
||||||
return PR_NOW;
|
return PR_NOW;
|
||||||
if (sq->est_clock)
|
|
||||||
sq->can_delay_writes = 1;
|
|
||||||
sq->need_kick_clock = MAX_CLOCK;
|
sq->need_kick_clock = MAX_CLOCK;
|
||||||
return PR_NEVER;
|
return PR_NEVER;
|
||||||
}
|
}
|
||||||
@@ -986,16 +984,6 @@ serialqueue_set_clock_est(struct serialqueue *sq, double est_clock
|
|||||||
pthread_mutex_unlock(&sq->lock);
|
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
|
// Return a string buffer containing statistics for the serial port
|
||||||
void
|
void
|
||||||
serialqueue_get_stats(struct serialqueue *sq, char *buf, int len)
|
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_baud_adjust(struct serialqueue *sq, double baud_adjust);
|
||||||
void serialqueue_set_clock_est(struct serialqueue *sq, double est_clock
|
void serialqueue_set_clock_est(struct serialqueue *sq, double est_clock
|
||||||
, double last_ack_time, uint64_t last_ack_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);
|
void serialqueue_get_stats(struct serialqueue *sq, char *buf, int len);
|
||||||
int serialqueue_extract_old(struct serialqueue *sq, int sentq
|
int serialqueue_extract_old(struct serialqueue *sq, int sentq
|
||||||
, struct pull_queue_message *q, int max);
|
, struct pull_queue_message *q, int max);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Stepper pulse schedule compression
|
// 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.
|
// 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;
|
uint64_t *queue, *queue_end, *queue_pos, *queue_next;
|
||||||
// Internal tracking
|
// Internal tracking
|
||||||
uint32_t max_error;
|
uint32_t max_error;
|
||||||
// Error checking
|
|
||||||
uint32_t errors;
|
|
||||||
// Message generation
|
// Message generation
|
||||||
uint64_t last_step_clock;
|
uint64_t last_step_clock, homing_clock;
|
||||||
struct list_head msg_queue;
|
struct list_head msg_queue;
|
||||||
uint32_t queue_step_msgid, set_next_step_dir_msgid, oid;
|
uint32_t queue_step_msgid, set_next_step_dir_msgid, oid;
|
||||||
int sdir, invert_sdir;
|
int sdir, invert_sdir;
|
||||||
@@ -174,6 +172,9 @@ compress_bisect_add(struct stepcompress *sc)
|
|||||||
zerointerval = interval;
|
zerointerval = interval;
|
||||||
zerocount = count;
|
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
|
// Check if a greater or lesser add could extend the sequence
|
||||||
@@ -221,27 +222,30 @@ compress_bisect_add(struct stepcompress *sc)
|
|||||||
* Step compress checking
|
* Step compress checking
|
||||||
****************************************************************/
|
****************************************************************/
|
||||||
|
|
||||||
|
#define ERROR_RET -989898989
|
||||||
|
|
||||||
// Verify that a given 'step_move' matches the actual step times
|
// Verify that a given 'step_move' matches the actual step times
|
||||||
static void
|
static int
|
||||||
check_line(struct stepcompress *sc, struct step_move move)
|
check_line(struct stepcompress *sc, struct step_move move)
|
||||||
{
|
{
|
||||||
if (!CHECK_LINES)
|
if (!CHECK_LINES)
|
||||||
return;
|
return 0;
|
||||||
if (move.count == 1) {
|
if (move.count == 1) {
|
||||||
if (move.interval != (uint32_t)(*sc->queue_pos - sc->last_step_clock)
|
if (move.interval != (uint32_t)(*sc->queue_pos - sc->last_step_clock)
|
||||||
|| *sc->queue_pos < sc->last_step_clock) {
|
|| *sc->queue_pos < sc->last_step_clock) {
|
||||||
errorf("Count 1 point out of range: %d %d %d"
|
errorf("stepcompress o=%d i=%d c=%d a=%d:"
|
||||||
, move.interval, move.count, move.add);
|
" Count 1 point out of range (%lld)"
|
||||||
sc->errors++;
|
, 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)
|
if (!move.count || (!move.interval && !move.add)
|
||||||
|| move.interval >= 0x80000000) {
|
|| move.interval >= 0x80000000) {
|
||||||
errorf("Point out of range: %d %d %d"
|
errorf("stepcompress o=%d i=%d c=%d a=%d: Invalid sequence"
|
||||||
, move.interval, move.count, move.add);
|
, sc->oid, move.interval, move.count, move.add);
|
||||||
err++;
|
return ERROR_RET;
|
||||||
}
|
}
|
||||||
uint32_t interval = move.interval, p = 0;
|
uint32_t interval = move.interval, p = 0;
|
||||||
uint16_t i;
|
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);
|
struct points point = minmax_point(sc, sc->queue_pos + i);
|
||||||
p += interval;
|
p += interval;
|
||||||
if (p < point.minp || p > point.maxp) {
|
if (p < point.minp || p > point.maxp) {
|
||||||
errorf("Point %d of %d: %d not in %d:%d"
|
errorf("stepcompress o=%d i=%d c=%d a=%d: Point %d: %d not in %d:%d"
|
||||||
, i+1, move.count, p, point.minp, point.maxp);
|
, sc->oid, move.interval, move.count, move.add
|
||||||
err++;
|
, i+1, p, point.minp, point.maxp);
|
||||||
|
return ERROR_RET;
|
||||||
}
|
}
|
||||||
if (interval >= 0x80000000) {
|
if (interval >= 0x80000000) {
|
||||||
errorf("Point %d of %d: interval overflow %d"
|
errorf("stepcompress o=%d i=%d c=%d a=%d:"
|
||||||
, i+1, move.count, interval);
|
" Point %d: interval overflow %d"
|
||||||
err++;
|
, sc->oid, move.interval, move.count, move.add
|
||||||
|
, i+1, interval);
|
||||||
|
return ERROR_RET;
|
||||||
}
|
}
|
||||||
interval += move.add;
|
interval += move.add;
|
||||||
}
|
}
|
||||||
sc->errors += err;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -268,22 +275,6 @@ check_line(struct stepcompress *sc, struct step_move move)
|
|||||||
* Step compress interface
|
* 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
|
// Allocate a new 'stepcompress' object
|
||||||
struct stepcompress *
|
struct stepcompress *
|
||||||
stepcompress_alloc(uint32_t max_error, uint32_t queue_step_msgid
|
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
|
// Convert previously scheduled steps into commands for the mcu
|
||||||
static void
|
static int
|
||||||
stepcompress_flush(struct stepcompress *sc, uint64_t move_clock)
|
stepcompress_flush(struct stepcompress *sc, uint64_t move_clock)
|
||||||
{
|
{
|
||||||
if (sc->queue_pos >= sc->queue_next)
|
if (sc->queue_pos >= sc->queue_next)
|
||||||
return;
|
return 0;
|
||||||
while (move_clock > sc->last_step_clock) {
|
while (move_clock > sc->last_step_clock) {
|
||||||
struct step_move move = compress_bisect_add(sc);
|
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] = {
|
uint32_t msg[5] = {
|
||||||
sc->queue_step_msgid, sc->oid, move.interval, move.count, move.add
|
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
|
// Be careful with 32bit overflow
|
||||||
sc->last_step_clock = qm->req_clock = *sc->queue_pos;
|
sc->last_step_clock = qm->req_clock = *sc->queue_pos;
|
||||||
} else {
|
} 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;
|
uint32_t ticks = move.add*addfactor + move.interval*move.count;
|
||||||
sc->last_step_clock += ticks;
|
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);
|
list_add_tail(&qm->node, &sc->msg_queue);
|
||||||
|
|
||||||
if (sc->queue_pos + move.count >= sc->queue_next) {
|
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;
|
sc->queue_pos += move.count;
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the set_next_step_dir command
|
// Send the set_next_step_dir command
|
||||||
static void
|
static int
|
||||||
set_next_step_dir(struct stepcompress *sc, int sdir)
|
set_next_step_dir(struct stepcompress *sc, int sdir)
|
||||||
{
|
{
|
||||||
|
if (sc->sdir == sdir)
|
||||||
|
return 0;
|
||||||
sc->sdir = sdir;
|
sc->sdir = sdir;
|
||||||
stepcompress_flush(sc, UINT64_MAX);
|
int ret = stepcompress_flush(sc, UINT64_MAX);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
uint32_t msg[3] = {
|
uint32_t msg[3] = {
|
||||||
sc->set_next_step_dir_msgid, sc->oid, sdir ^ sc->invert_sdir
|
sc->set_next_step_dir_msgid, sc->oid, sdir ^ sc->invert_sdir
|
||||||
};
|
};
|
||||||
struct queue_message *qm = message_alloc_and_encode(msg, 3);
|
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);
|
list_add_tail(&qm->node, &sc->msg_queue);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the internal queue needs to be expanded, and expand if so
|
// Check if the internal queue needs to be expanded, and expand if so
|
||||||
static inline void
|
static int
|
||||||
check_expand(struct stepcompress *sc, int sdir, int count)
|
_check_push(struct stepcompress *sc)
|
||||||
{
|
{
|
||||||
if (sdir != sc->sdir)
|
if (sc->queue_next - sc->queue_pos > 65535 + 2000) {
|
||||||
set_next_step_dir(sc, sdir);
|
// No point in keeping more than 64K steps in memory
|
||||||
if (sc->queue_next + count > sc->queue_end)
|
int ret = stepcompress_flush(sc, *(sc->queue_next - 65535));
|
||||||
expand_queue(sc, count);
|
if (ret)
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
expand_queue(sc, 1);
|
||||||
// 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);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
check_expand(sc, sdir, count);
|
static inline int
|
||||||
|
check_push(struct stepcompress *sc, uint64_t **pqnext, uint64_t **pqend
|
||||||
// Calculate each step time
|
, uint64_t c)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
// Calculate number of steps to take
|
if (unlikely(*pqnext >= *pqend)) {
|
||||||
int sdir = 1;
|
sc->queue_next = *pqnext;
|
||||||
if (steps < 0) {
|
int ret = _check_push(sc);
|
||||||
sdir = 0;
|
if (ret)
|
||||||
steps = -steps;
|
return ret;
|
||||||
step_offset = -step_offset;
|
*pqnext = sc->queue_next;
|
||||||
|
*pqend = sc->queue_end;
|
||||||
}
|
}
|
||||||
int count = steps + .5 - step_offset;
|
*(*pqnext)++ = c;
|
||||||
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);
|
|
||||||
return 0;
|
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
|
// Reset the internal state of the stepcompress object
|
||||||
void
|
int
|
||||||
stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock)
|
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->last_step_clock = last_step_clock;
|
||||||
sc->sdir = -1;
|
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
|
// 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_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);
|
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);
|
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'
|
// Find and transmit any scheduled steps prior to the given 'move_clock'
|
||||||
void
|
int
|
||||||
steppersync_flush(struct steppersync *ss, uint64_t move_clock)
|
steppersync_flush(struct steppersync *ss, uint64_t move_clock)
|
||||||
{
|
{
|
||||||
// Flush each stepcompress to the specified move_clock
|
// Flush each stepcompress to the specified move_clock
|
||||||
int i;
|
int i;
|
||||||
for (i=0; i<ss->sc_num; i++)
|
for (i=0; i<ss->sc_num; i++) {
|
||||||
stepcompress_flush(ss->sc_list[i], move_clock);
|
int ret = stepcompress_flush(ss->sc_list[i], move_clock);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
// Order commands by the reqclock of each pending command
|
// Order commands by the reqclock of each pending command
|
||||||
struct list_head msgs;
|
struct list_head msgs;
|
||||||
@@ -701,4 +793,5 @@ steppersync_flush(struct steppersync *ss, uint64_t move_clock)
|
|||||||
// Transmit commands
|
// Transmit commands
|
||||||
if (!list_empty(&msgs))
|
if (!list_empty(&msgs))
|
||||||
serialqueue_send_batch(ss->sq, ss->cq, &msgs);
|
serialqueue_send_batch(ss->sq, ss->cq, &msgs);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,24 +8,26 @@ import homing
|
|||||||
|
|
||||||
class PrinterStepper:
|
class PrinterStepper:
|
||||||
def __init__(self, printer, config, name):
|
def __init__(self, printer, config, name):
|
||||||
self.printer = printer
|
|
||||||
self.config = config
|
|
||||||
self.name = name
|
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.inv_step_dist = 1. / self.step_dist
|
||||||
self.min_stop_interval = 0.
|
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(
|
self.homing_positive_dir = config.getboolean(
|
||||||
'homing_positive_dir', False)
|
'homing_positive_dir', False)
|
||||||
self.homing_retract_dist = config.getfloat('homing_retract_dist', 5.)
|
self.homing_retract_dist = config.getfloat(
|
||||||
self.homing_stepper_phases = config.getint('homing_stepper_phases', None)
|
'homing_retract_dist', 5., above=0.)
|
||||||
self.homing_endstop_phase = config.getint('homing_endstop_phase', None)
|
self.homing_stepper_phases = config.getint(
|
||||||
endstop_accuracy = config.getfloat('homing_endstop_accuracy', None)
|
'homing_stepper_phases', None, minval=0)
|
||||||
self.homing_endstop_accuracy = None
|
endstop_accuracy = config.getfloat(
|
||||||
|
'homing_endstop_accuracy', None, above=0.)
|
||||||
|
self.homing_endstop_accuracy = self.homing_endstop_phase = None
|
||||||
if self.homing_stepper_phases:
|
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:
|
if endstop_accuracy is None:
|
||||||
self.homing_endstop_accuracy = self.homing_stepper_phases//2 - 1
|
self.homing_endstop_accuracy = self.homing_stepper_phases//2 - 1
|
||||||
elif self.homing_endstop_phase is not None:
|
elif self.homing_endstop_phase is not None:
|
||||||
@@ -36,34 +38,40 @@ class PrinterStepper:
|
|||||||
endstop_accuracy * self.inv_step_dist))
|
endstop_accuracy * self.inv_step_dist))
|
||||||
if self.homing_endstop_accuracy >= self.homing_stepper_phases/2:
|
if self.homing_endstop_accuracy >= self.homing_stepper_phases/2:
|
||||||
logging.info("Endstop for %s is not accurate enough for stepper"
|
logging.info("Endstop for %s is not accurate enough for stepper"
|
||||||
" phase adjustment" % (self.config.section,))
|
" phase adjustment" % (name,))
|
||||||
self.homing_stepper_phases = None
|
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
|
self.position_min = self.position_endstop = self.position_max = None
|
||||||
if config.get('endstop_pin', None) is not None:
|
endstop_pin = config.get('endstop_pin', None)
|
||||||
self.position_min = config.getfloat('position_min', 0.)
|
step_pin = config.get('step_pin')
|
||||||
self.position_endstop = config.getfloat('position_endstop')
|
dir_pin = config.get('dir_pin')
|
||||||
self.position_max = config.getfloat('position_max', 0.)
|
mcu = printer.mcu
|
||||||
|
self.mcu_stepper = mcu.create_stepper(step_pin, dir_pin)
|
||||||
self.need_motor_enable = True
|
self.mcu_stepper.set_step_distance(self.step_dist)
|
||||||
def set_max_jerk(self, max_halt_velocity, max_accel):
|
enable_pin = config.get('enable_pin', None)
|
||||||
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)
|
|
||||||
if enable_pin is not None:
|
if enable_pin is not None:
|
||||||
self.mcu_enable = mcu.create_digital_out(enable_pin, 0)
|
self.mcu_enable = mcu.create_digital_out(enable_pin, 0)
|
||||||
endstop_pin = self.config.get('endstop_pin', None)
|
|
||||||
if endstop_pin is not 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):
|
def motor_enable(self, move_time, enable=0):
|
||||||
if enable and self.need_motor_enable:
|
if enable and self.need_motor_enable:
|
||||||
mcu_time = self.mcu_stepper.print_to_mcu_time(move_time)
|
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.mcu_stepper.get_mcu_position()
|
||||||
pos %= self.homing_stepper_phases
|
pos %= self.homing_stepper_phases
|
||||||
if self.homing_endstop_phase is None:
|
if self.homing_endstop_phase is None:
|
||||||
logging.info("Setting %s endstop phase to %d" % (
|
logging.info("Setting %s endstop phase to %d" % (self.name, pos))
|
||||||
self.config.section, pos))
|
|
||||||
self.homing_endstop_phase = pos
|
self.homing_endstop_phase = pos
|
||||||
return 0
|
return 0
|
||||||
delta = (pos - self.homing_endstop_phase) % self.homing_stepper_phases
|
delta = (pos - self.homing_endstop_phase) % self.homing_stepper_phases
|
||||||
@@ -98,4 +105,4 @@ class PrinterStepper:
|
|||||||
raise homing.EndstopError(
|
raise homing.EndstopError(
|
||||||
"Endstop %s incorrect phase (got %d vs %d)" % (
|
"Endstop %s incorrect phase (got %d vs %d)" % (
|
||||||
self.name, pos, self.homing_endstop_phase))
|
self.name, pos, self.homing_endstop_phase))
|
||||||
return delta
|
return delta * self.step_dist
|
||||||
|
|||||||
@@ -3,148 +3,171 @@
|
|||||||
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import math, logging, time
|
import math, logging
|
||||||
import cartesian, delta
|
import cartesian, corexy, delta, extruder
|
||||||
|
|
||||||
EXTRUDE_DIFF_IGNORE = 1.02
|
|
||||||
|
|
||||||
# Common suffixes: _d is distance (in mm), _v is velocity (in
|
# Common suffixes: _d is distance (in mm), _v is velocity (in
|
||||||
# mm/second), _t is time (in seconds), _r is ratio (scalar between
|
# mm/second), _v2 is velocity squared (mm^2/s^2), _t is time (in
|
||||||
# 0.0 and 1.0)
|
# seconds), _r is ratio (scalar between 0.0 and 1.0)
|
||||||
|
|
||||||
# Class to track each move request
|
# Class to track each move request
|
||||||
class Move:
|
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.toolhead = toolhead
|
||||||
self.start_pos = tuple(start_pos)
|
self.start_pos = tuple(start_pos)
|
||||||
self.end_pos = tuple(end_pos)
|
self.end_pos = tuple(end_pos)
|
||||||
self.accel = accel
|
self.accel = toolhead.max_accel
|
||||||
self.do_calc_junction = True
|
self.is_kinematic_move = True
|
||||||
self.axes_d = axes_d = [end_pos[i] - start_pos[i] for i in (0, 1, 2, 3)]
|
self.axes_d = axes_d = [end_pos[i] - start_pos[i] for i in (0, 1, 2, 3)]
|
||||||
if axes_d[2]:
|
self.move_d = move_d = math.sqrt(sum([d*d for d in axes_d[:3]]))
|
||||||
# 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)
|
|
||||||
if not move_d:
|
if not move_d:
|
||||||
# Extrude only move
|
# Extrude only move
|
||||||
move_d = abs(axes_d[3])
|
self.move_d = move_d = abs(axes_d[3])
|
||||||
if not move_d:
|
self.is_kinematic_move = False
|
||||||
# No move
|
self.min_move_t = move_d / speed
|
||||||
self.move_d = 0.
|
# Junction speeds are tracked in velocity squared. The
|
||||||
return
|
# delta_v2 is the maximum amount of this squared-velocity that
|
||||||
self.do_calc_junction = False
|
# can change in this move.
|
||||||
self.move_d = move_d
|
self.max_start_v2 = 0.
|
||||||
self.extrude_r = axes_d[3] / move_d
|
self.max_cruise_v2 = speed**2
|
||||||
# Junction speeds are velocities squared. The junction_delta
|
self.delta_v2 = 2.0 * move_d * self.accel
|
||||||
# is the maximum amount of this squared-velocity that can
|
self.max_smoothed_v2 = 0.
|
||||||
# change in this move.
|
self.smooth_delta_v2 = 2.0 * move_d * toolhead.max_accel_to_decel
|
||||||
self.junction_max = speed**2
|
|
||||||
self.junction_delta = 2.0 * move_d * accel
|
|
||||||
self.junction_start_max = 0.
|
|
||||||
def limit_speed(self, speed, accel):
|
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.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):
|
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
|
return
|
||||||
# Find max junction_start_velocity between two moves
|
# Allow extruder to calculate its maximum junction
|
||||||
if (self.extrude_r > prev_move.extrude_r * EXTRUDE_DIFF_IGNORE
|
extruder_v2 = self.toolhead.extruder.calc_junction(prev_move, self)
|
||||||
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
|
|
||||||
# Find max velocity using approximated centripetal velocity as
|
# Find max velocity using approximated centripetal velocity as
|
||||||
# described at:
|
# described at:
|
||||||
# https://onehossshay.wordpress.com/2011/09/24/improving_grbl_cornering_algorithm/
|
# https://onehossshay.wordpress.com/2011/09/24/improving_grbl_cornering_algorithm/
|
||||||
junction_cos_theta = -((self.axes_d[0] * prev_move.axes_d[0]
|
junction_cos_theta = -((axes_d[0] * prev_axes_d[0]
|
||||||
+ self.axes_d[1] * prev_move.axes_d[1])
|
+ axes_d[1] * prev_axes_d[1])
|
||||||
/ (self.move_d * prev_move.move_d))
|
/ (self.move_d * prev_move.move_d))
|
||||||
if junction_cos_theta > 0.999999:
|
if junction_cos_theta > 0.999999:
|
||||||
return
|
return
|
||||||
junction_cos_theta = max(junction_cos_theta, -0.999999)
|
junction_cos_theta = max(junction_cos_theta, -0.999999)
|
||||||
sin_theta_d2 = math.sqrt(0.5*(1.0-junction_cos_theta))
|
sin_theta_d2 = math.sqrt(0.5*(1.0-junction_cos_theta))
|
||||||
R = self.toolhead.junction_deviation * sin_theta_d2 / (1. - sin_theta_d2)
|
R = self.toolhead.junction_deviation * sin_theta_d2 / (1. - sin_theta_d2)
|
||||||
self.junction_start_max = min(
|
self.max_start_v2 = min(
|
||||||
R * self.accel, self.junction_max, prev_move.junction_max
|
R * self.accel, self.max_cruise_v2, prev_move.max_cruise_v2
|
||||||
, prev_move.junction_start_max + prev_move.junction_delta)
|
, extruder_v2, prev_move.max_start_v2 + prev_move.delta_v2)
|
||||||
def process(self, junction_start, junction_cruise, junction_end
|
self.max_smoothed_v2 = min(
|
||||||
, cornering_min, cornering_max):
|
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
|
# Determine accel, cruise, and decel portions of the move distance
|
||||||
inv_junction_delta = 1. / self.junction_delta
|
inv_delta_v2 = 1. / self.delta_v2
|
||||||
accel_r = (junction_cruise-junction_start) * inv_junction_delta
|
self.accel_r = accel_r = (cruise_v2 - start_v2) * inv_delta_v2
|
||||||
decel_r = (junction_cruise-junction_end) * inv_junction_delta
|
self.decel_r = decel_r = (cruise_v2 - end_v2) * inv_delta_v2
|
||||||
cruise_r = 1. - accel_r - decel_r
|
self.cruise_r = cruise_r = 1. - accel_r - decel_r
|
||||||
self.accel_r, self.cruise_r, self.decel_r = accel_r, cruise_r, decel_r
|
|
||||||
# Determine move velocities
|
# Determine move velocities
|
||||||
start_v = math.sqrt(junction_start)
|
self.start_v = start_v = math.sqrt(start_v2)
|
||||||
cruise_v = math.sqrt(junction_cruise)
|
self.cruise_v = cruise_v = math.sqrt(cruise_v2)
|
||||||
end_v = math.sqrt(junction_end)
|
self.end_v = end_v = math.sqrt(end_v2)
|
||||||
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)
|
|
||||||
# Determine time spent in each portion of move (time is the
|
# Determine time spent in each portion of move (time is the
|
||||||
# distance divided by average velocity)
|
# distance divided by average velocity)
|
||||||
accel_t = accel_r * self.move_d / ((start_v + cruise_v) * 0.5)
|
self.accel_t = accel_r * self.move_d / ((start_v + cruise_v) * 0.5)
|
||||||
cruise_t = cruise_r * self.move_d / cruise_v
|
self.cruise_t = cruise_r * self.move_d / cruise_v
|
||||||
decel_t = decel_r * self.move_d / ((end_v + cruise_v) * 0.5)
|
self.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
|
def move(self):
|
||||||
# Generate step times for the move
|
# Generate step times for the move
|
||||||
next_move_time = self.toolhead.get_next_move_time()
|
next_move_time = self.toolhead.get_next_move_time()
|
||||||
|
if self.is_kinematic_move:
|
||||||
self.toolhead.kin.move(next_move_time, self)
|
self.toolhead.kin.move(next_move_time, self)
|
||||||
if self.axes_d[3]:
|
if self.axes_d[3]:
|
||||||
self.toolhead.extruder.move(next_move_time, self)
|
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
|
# Class to track a list of pending move requests and to facilitate
|
||||||
# "look-ahead" across moves to reduce acceleration between moves.
|
# "look-ahead" across moves to reduce acceleration between moves.
|
||||||
class MoveQueue:
|
class MoveQueue:
|
||||||
def __init__(self):
|
def __init__(self, extruder_lookahead):
|
||||||
|
self.extruder_lookahead = extruder_lookahead
|
||||||
self.queue = []
|
self.queue = []
|
||||||
self.junction_flush = 0.
|
self.leftover = 0
|
||||||
|
self.junction_flush = LOOKAHEAD_FLUSH_TIME
|
||||||
def reset(self):
|
def reset(self):
|
||||||
del self.queue[:]
|
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):
|
def flush(self, lazy=False):
|
||||||
flush_count = len(self.queue)
|
self.junction_flush = LOOKAHEAD_FLUSH_TIME
|
||||||
move_info = [None] * flush_count
|
update_flush_count = lazy
|
||||||
|
queue = self.queue
|
||||||
|
flush_count = len(queue)
|
||||||
# Traverse queue from last to first move and determine maximum
|
# Traverse queue from last to first move and determine maximum
|
||||||
# junction speed assuming the robot comes to a complete stop
|
# junction speed assuming the robot comes to a complete stop
|
||||||
# after the last move.
|
# after the last move.
|
||||||
next_junction_end = cornering_min = cornering_max = 0.
|
delayed = []
|
||||||
for i in range(flush_count-1, -1, -1):
|
next_end_v2 = next_smoothed_v2 = peak_cruise_v2 = 0.
|
||||||
move = self.queue[i]
|
for i in range(flush_count-1, self.leftover-1, -1):
|
||||||
reachable_start = next_junction_end + move.junction_delta
|
move = queue[i]
|
||||||
junction_start = min(move.junction_start_max, reachable_start)
|
reachable_start_v2 = next_end_v2 + move.delta_v2
|
||||||
junction_cruise = min((junction_start + reachable_start) * .5
|
start_v2 = min(move.max_start_v2, reachable_start_v2)
|
||||||
, move.junction_max)
|
reachable_smoothed_v2 = next_smoothed_v2 + move.smooth_delta_v2
|
||||||
move_info[i] = (junction_start, junction_cruise, next_junction_end
|
smoothed_v2 = min(move.max_smoothed_v2, reachable_smoothed_v2)
|
||||||
, cornering_min, cornering_max)
|
if smoothed_v2 < reachable_smoothed_v2:
|
||||||
if reachable_start > junction_start:
|
# It's possible for this move to accelerate
|
||||||
cornering_min = junction_start
|
if (smoothed_v2 + move.smooth_delta_v2 > next_smoothed_v2
|
||||||
if junction_start + move.junction_delta > next_junction_end:
|
or delayed):
|
||||||
cornering_max = junction_cruise
|
# This move can decelerate or this is a full accel
|
||||||
if lazy:
|
# move after a full decel move
|
||||||
|
if update_flush_count and peak_cruise_v2:
|
||||||
flush_count = i
|
flush_count = i
|
||||||
lazy = False
|
update_flush_count = False
|
||||||
next_junction_end = junction_start
|
peak_cruise_v2 = min(move.max_cruise_v2, (
|
||||||
if lazy:
|
smoothed_v2 + reachable_smoothed_v2) * .5)
|
||||||
flush_count = 0
|
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
|
# Generate step times for all moves ready to be flushed
|
||||||
for i in range(flush_count):
|
for move in queue[:move_count]:
|
||||||
self.queue[i].process(*move_info[i])
|
move.move()
|
||||||
# Remove processed moves from the queue
|
# Remove processed moves from the queue
|
||||||
del self.queue[:flush_count]
|
self.leftover = flush_count - move_count
|
||||||
if self.queue:
|
del queue[:move_count]
|
||||||
self.junction_flush = 2. * self.queue[-1].junction_max
|
|
||||||
def add_move(self, move):
|
def add_move(self, move):
|
||||||
self.queue.append(move)
|
self.queue.append(move)
|
||||||
if len(self.queue) == 1:
|
if len(self.queue) == 1:
|
||||||
self.junction_flush = 2. * move.junction_max
|
|
||||||
return
|
return
|
||||||
move.calc_junction(self.queue[-2])
|
move.calc_junction(self.queue[-2])
|
||||||
self.junction_flush -= move.junction_delta
|
self.junction_flush -= move.min_move_t
|
||||||
if self.junction_flush <= 0.:
|
if self.junction_flush <= 0.:
|
||||||
# There are enough queued moves to return to zero velocity
|
# There are enough queued moves to return to zero velocity
|
||||||
# from the first move's maximum possible velocity, so at
|
# from the first move's maximum possible velocity, so at
|
||||||
@@ -159,58 +182,94 @@ class ToolHead:
|
|||||||
self.printer = printer
|
self.printer = printer
|
||||||
self.reactor = printer.reactor
|
self.reactor = printer.reactor
|
||||||
self.extruder = printer.objects.get('extruder')
|
self.extruder = printer.objects.get('extruder')
|
||||||
|
if self.extruder is None:
|
||||||
|
self.extruder = extruder.DummyExtruder()
|
||||||
kintypes = {'cartesian': cartesian.CartKinematics,
|
kintypes = {'cartesian': cartesian.CartKinematics,
|
||||||
|
'corexy': corexy.CoreXYKinematics,
|
||||||
'delta': delta.DeltaKinematics}
|
'delta': delta.DeltaKinematics}
|
||||||
self.kin = config.getchoice('kinematics', kintypes)(printer, config)
|
self.kin = config.getchoice('kinematics', kintypes)(printer, config)
|
||||||
self.max_speed = config.getfloat('max_velocity')
|
self.max_speed = config.getfloat('max_velocity', above=0.)
|
||||||
self.max_accel = config.getfloat('max_accel')
|
self.max_accel = config.getfloat('max_accel', above=0.)
|
||||||
self.junction_deviation = config.getfloat('junction_deviation', 0.02)
|
self.max_accel_to_decel = config.getfloat(
|
||||||
self.move_queue = MoveQueue()
|
'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.]
|
self.commanded_pos = [0., 0., 0., 0.]
|
||||||
# Print time tracking
|
# Print time tracking
|
||||||
self.buffer_time_high = config.getfloat('buffer_time_high', 5.000)
|
self.buffer_time_low = config.getfloat(
|
||||||
self.buffer_time_low = config.getfloat('buffer_time_low', 0.150)
|
'buffer_time_low', 1.000, above=0.)
|
||||||
self.move_flush_time = config.getfloat('move_flush_time', 0.050)
|
self.buffer_time_high = config.getfloat(
|
||||||
self.motor_off_delay = config.getfloat('motor_off_time', 60.000)
|
'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.print_time = 0.
|
||||||
|
self.last_print_end_time = self.reactor.monotonic()
|
||||||
self.need_check_stall = -1.
|
self.need_check_stall = -1.
|
||||||
self.print_time_stall = 0
|
self.print_stall = 0
|
||||||
self.motor_off_time = self.reactor.NEVER
|
self.synch_print_time = True
|
||||||
|
self.forced_synch = False
|
||||||
self.flush_timer = self.reactor.register_timer(self._flush_handler)
|
self.flush_timer = self.reactor.register_timer(self._flush_handler)
|
||||||
def build_config(self):
|
self.move_queue.set_flush_time(self.buffer_time_high)
|
||||||
self.kin.set_max_jerk(0.005 * self.max_accel, self.max_accel) # XXX
|
# Motor off tracking
|
||||||
self.kin.build_config()
|
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
|
# Print time tracking
|
||||||
def update_move_time(self, movetime):
|
def update_move_time(self, movetime):
|
||||||
self.print_time += movetime
|
self.print_time += movetime
|
||||||
flush_to_time = self.print_time - self.move_flush_time
|
flush_to_time = self.print_time - self.move_flush_time
|
||||||
self.printer.mcu.flush_moves(flush_to_time)
|
self.printer.mcu.flush_moves(flush_to_time)
|
||||||
def get_next_move_time(self):
|
def get_next_move_time(self):
|
||||||
if not self.print_time:
|
if self.synch_print_time:
|
||||||
self.print_time = self.buffer_time_low + STALL_TIME
|
curtime = self.reactor.monotonic()
|
||||||
curtime = time.time()
|
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.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.reactor.update_timer(self.flush_timer, self.reactor.NOW)
|
||||||
|
self.synch_print_time = False
|
||||||
return self.print_time
|
return self.print_time
|
||||||
def get_last_move_time(self):
|
def _flush_lookahead(self, must_synch=False):
|
||||||
self.move_queue.flush()
|
synch_print_time = self.synch_print_time
|
||||||
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):
|
|
||||||
self.move_queue.flush()
|
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)
|
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.print_time = 0.
|
||||||
|
self.last_print_end_time = self.reactor.monotonic()
|
||||||
self.need_check_stall = -1.
|
self.need_check_stall = -1.
|
||||||
self.reset_motor_off_time(time.time())
|
self.forced_synch = False
|
||||||
self.reactor.update_timer(self.flush_timer, self.motor_off_time)
|
self._reset_motor_off()
|
||||||
def _check_stall(self):
|
def _check_stall(self):
|
||||||
|
eventtime = self.reactor.monotonic()
|
||||||
if not self.print_time:
|
if not self.print_time:
|
||||||
# XXX - find better way to flush initial move_queue items
|
# Building initial queue - make sure to flush on idle input
|
||||||
if self.move_queue.queue:
|
self.reactor.update_timer(self.flush_timer, eventtime + 0.100)
|
||||||
self.reactor.update_timer(self.flush_timer, time.time() + 0.100)
|
|
||||||
return
|
return
|
||||||
eventtime = time.time()
|
# Check if there are lots of queued moves and stall if so
|
||||||
while 1:
|
while 1:
|
||||||
buffer_time = self.printer.mcu.get_print_buffer_time(
|
buffer_time = self.printer.mcu.get_print_buffer_time(
|
||||||
eventtime, self.print_time)
|
eventtime, self.print_time)
|
||||||
@@ -224,47 +283,58 @@ class ToolHead:
|
|||||||
def _flush_handler(self, eventtime):
|
def _flush_handler(self, eventtime):
|
||||||
try:
|
try:
|
||||||
if not self.print_time:
|
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 not self.print_time:
|
||||||
if eventtime >= self.motor_off_time:
|
return self.reactor.NEVER
|
||||||
self.motor_off()
|
|
||||||
self.reset_print_time()
|
|
||||||
self.motor_off_time = self.reactor.NEVER
|
|
||||||
return self.motor_off_time
|
|
||||||
print_time = self.print_time
|
print_time = self.print_time
|
||||||
buffer_time = self.printer.mcu.get_print_buffer_time(
|
buffer_time = self.printer.mcu.get_print_buffer_time(
|
||||||
eventtime, print_time)
|
eventtime, print_time)
|
||||||
if buffer_time > self.buffer_time_low:
|
if buffer_time > self.buffer_time_low:
|
||||||
|
# Running normally - reschedule check
|
||||||
return eventtime + buffer_time - self.buffer_time_low
|
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:
|
if print_time != self.print_time:
|
||||||
self.print_time_stall += 1
|
# Flushed something - retry
|
||||||
self.dwell(self.buffer_time_low + STALL_TIME)
|
self.forced_synch = True
|
||||||
return self.reactor.NOW
|
return self.reactor.NOW
|
||||||
|
if buffer_time > 0.:
|
||||||
|
# Wait for buffer to fully empty
|
||||||
|
return eventtime + buffer_time
|
||||||
self.reset_print_time()
|
self.reset_print_time()
|
||||||
return self.motor_off_time
|
|
||||||
except:
|
except:
|
||||||
logging.exception("Exception in flush_handler")
|
logging.exception("Exception in flush_handler")
|
||||||
self.force_shutdown()
|
self.force_shutdown()
|
||||||
def stats(self, eventtime):
|
return self.reactor.NEVER
|
||||||
buffer_time = 0.
|
# Motor off timer
|
||||||
if self.print_time:
|
def _reset_motor_off(self):
|
||||||
buffer_time = self.printer.mcu.get_print_buffer_time(
|
if not self.print_time:
|
||||||
eventtime, self.print_time)
|
waketime = self.reactor.monotonic() + self.motor_off_time
|
||||||
return "print_time=%.3f buffer_time=%.3f print_time_stall=%d" % (
|
else:
|
||||||
self.print_time, buffer_time, self.print_time_stall)
|
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
|
# Movement commands
|
||||||
def get_position(self):
|
def get_position(self):
|
||||||
return list(self.commanded_pos)
|
return list(self.commanded_pos)
|
||||||
def set_position(self, newpos):
|
def set_position(self, newpos):
|
||||||
self.move_queue.flush()
|
self._flush_lookahead()
|
||||||
self.commanded_pos[:] = newpos
|
self.commanded_pos[:] = newpos
|
||||||
self.kin.set_position(newpos)
|
self.kin.set_position(newpos)
|
||||||
def move(self, newpos, speed):
|
def move(self, newpos, speed):
|
||||||
speed = min(speed, self.max_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:
|
if not move.move_d:
|
||||||
return
|
return
|
||||||
|
if move.is_kinematic_move:
|
||||||
self.kin.check_move(move)
|
self.kin.check_move(move)
|
||||||
if move.axes_d[3]:
|
if move.axes_d[3]:
|
||||||
self.extruder.check_move(move)
|
self.extruder.check_move(move)
|
||||||
@@ -285,9 +355,30 @@ class ToolHead:
|
|||||||
self.extruder.motor_off(last_move_time)
|
self.extruder.motor_off(last_move_time)
|
||||||
self.dwell(STALL_TIME)
|
self.dwell(STALL_TIME)
|
||||||
logging.debug('; Max time of %f' % (last_move_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):
|
def query_endstops(self):
|
||||||
last_move_time = self.get_last_move_time()
|
last_move_time = self.get_last_move_time()
|
||||||
return self.kin.query_endstops(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):
|
def force_shutdown(self):
|
||||||
|
try:
|
||||||
self.printer.mcu.force_shutdown()
|
self.printer.mcu.force_shutdown()
|
||||||
self.move_queue.reset()
|
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>
|
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
import sys, os, pty, fcntl, termios, signal, logging
|
||||||
import os, pty, fcntl, termios, signal
|
import subprocess, traceback, shlex
|
||||||
|
|
||||||
# Return the SIGINT interrupt handler back to the OS default
|
# Return the SIGINT interrupt handler back to the OS default
|
||||||
def fix_sigint():
|
def fix_sigint():
|
||||||
@@ -36,3 +36,34 @@ def create_pty(ptyname):
|
|||||||
old[3] = old[3] & ~termios.ECHO
|
old[3] = old[3] & ~termios.ECHO
|
||||||
termios.tcsetattr(mfd, termios.TCSADRAIN, old)
|
termios.tcsetattr(mfd, termios.TCSADRAIN, old)
|
||||||
return mfd
|
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
|
version 1.5.1 (extracted on 20160608). It has been modified to
|
||||||
compile with gcc's LTO feature. See cmsis-sam3x8e.patch for the
|
compile with gcc's LTO feature. See cmsis-sam3x8e.patch for the
|
||||||
modifications.
|
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.
|
# 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
|
import pysimulavr
|
||||||
|
|
||||||
SERIALBITS = 10 # 8N1 = 1 start, 8 data, 1 stop
|
SERIALBITS = 10 # 8N1 = 1 start, 8 data, 1 stop
|
||||||
|
SIMULAVR_FREQ = 10**9
|
||||||
|
|
||||||
# Class to read serial data from AVR serial transmit pin.
|
# Class to read serial data from AVR serial transmit pin.
|
||||||
class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
||||||
def __init__(self, baud):
|
def __init__(self, baud, terminal):
|
||||||
pysimulavr.Pin.__init__(self)
|
pysimulavr.Pin.__init__(self)
|
||||||
pysimulavr.PySimulationMember.__init__(self)
|
pysimulavr.PySimulationMember.__init__(self)
|
||||||
|
self.terminal = terminal
|
||||||
self.sc = pysimulavr.SystemClock.Instance()
|
self.sc = pysimulavr.SystemClock.Instance()
|
||||||
self.delay = 10**9 / baud
|
self.delay = SIMULAVR_FREQ / baud
|
||||||
self.current = 0
|
self.current = 0
|
||||||
self.pos = -1
|
self.pos = -1
|
||||||
self.queue = ""
|
|
||||||
def SetInState(self, pin):
|
def SetInState(self, pin):
|
||||||
pysimulavr.Pin.SetInState(self, pin)
|
pysimulavr.Pin.SetInState(self, pin)
|
||||||
self.state = pin.outState
|
self.state = pin.outState
|
||||||
@@ -33,32 +34,33 @@ class SerialRxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
|||||||
if self.pos == 1:
|
if self.pos == 1:
|
||||||
return int(self.delay * 1.5)
|
return int(self.delay * 1.5)
|
||||||
if self.pos >= SERIALBITS:
|
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.pos = -1
|
||||||
self.current = 0
|
self.current = 0
|
||||||
return -1
|
return -1
|
||||||
return self.delay
|
return self.delay
|
||||||
def popChars(self):
|
|
||||||
d = self.queue
|
|
||||||
self.queue = ""
|
|
||||||
return d
|
|
||||||
|
|
||||||
# Class to send serial data to AVR serial receive pin.
|
# Class to send serial data to AVR serial receive pin.
|
||||||
class SerialTxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
class SerialTxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
||||||
MAX_QUEUE = 64
|
def __init__(self, baud, terminal):
|
||||||
def __init__(self, baud):
|
|
||||||
pysimulavr.Pin.__init__(self)
|
pysimulavr.Pin.__init__(self)
|
||||||
pysimulavr.PySimulationMember.__init__(self)
|
pysimulavr.PySimulationMember.__init__(self)
|
||||||
|
self.terminal = terminal
|
||||||
self.SetPin('H')
|
self.SetPin('H')
|
||||||
self.sc = pysimulavr.SystemClock.Instance()
|
self.sc = pysimulavr.SystemClock.Instance()
|
||||||
self.delay = 10**9 / baud
|
self.delay = SIMULAVR_FREQ / baud
|
||||||
self.current = 0
|
self.current = 0
|
||||||
self.pos = 0
|
self.pos = 0
|
||||||
self.queue = ""
|
self.queue = ""
|
||||||
|
self.sc.Add(self)
|
||||||
def DoStep(self, trueHwStep):
|
def DoStep(self, trueHwStep):
|
||||||
if not self.pos:
|
if not self.pos:
|
||||||
if not self.queue:
|
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.current = (ord(self.queue[0]) << 1) | 0x200
|
||||||
self.queue = self.queue[1:]
|
self.queue = self.queue[1:]
|
||||||
newstate = 'L'
|
newstate = 'L'
|
||||||
@@ -69,15 +71,6 @@ class SerialTxPin(pysimulavr.PySimulationMember, pysimulavr.Pin):
|
|||||||
if self.pos >= SERIALBITS:
|
if self.pos >= SERIALBITS:
|
||||||
self.pos = 0
|
self.pos = 0
|
||||||
return self.delay
|
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
|
# Support for creating VCD trace files
|
||||||
class Tracing:
|
class Tracing:
|
||||||
@@ -108,6 +101,47 @@ class Tracing:
|
|||||||
if self.dman is not None:
|
if self.dman is not None:
|
||||||
self.dman.stopApplication()
|
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
|
# Support for creating a pseudo-tty for emulating a serial port
|
||||||
def create_pty(ptyname):
|
def create_pty(ptyname):
|
||||||
mfd, sfd = pty.openpty()
|
mfd, sfd = pty.openpty()
|
||||||
@@ -130,6 +164,8 @@ def main():
|
|||||||
default="atmega644", help="type of AVR machine to simulate")
|
default="atmega644", help="type of AVR machine to simulate")
|
||||||
opts.add_option("-s", "--speed", type="int", dest="speed", default=8000000,
|
opts.add_option("-s", "--speed", type="int", dest="speed", default=8000000,
|
||||||
help="machine speed")
|
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,
|
opts.add_option("-b", "--baud", type="int", dest="baud", default=38400,
|
||||||
help="baud rate of the emulated serial port")
|
help="baud rate of the emulated serial port")
|
||||||
opts.add_option("-t", "--trace", type="string", dest="trace",
|
opts.add_option("-t", "--trace", type="string", dest="trace",
|
||||||
@@ -154,18 +190,26 @@ def main():
|
|||||||
trace = Tracing(options.tracefile, options.trace)
|
trace = Tracing(options.tracefile, options.trace)
|
||||||
dev = pysimulavr.AvrFactory.instance().makeDevice(proc)
|
dev = pysimulavr.AvrFactory.instance().makeDevice(proc)
|
||||||
dev.Load(elffile)
|
dev.Load(elffile)
|
||||||
dev.SetClockFreq(10**9 / speed)
|
dev.SetClockFreq(SIMULAVR_FREQ / speed)
|
||||||
sc.Add(dev)
|
sc.Add(dev)
|
||||||
|
pysimulavr.cvar.sysConHandler.SetUseExit(False)
|
||||||
trace.load_options()
|
trace.load_options()
|
||||||
|
|
||||||
|
# Do optional real-time pacing
|
||||||
|
if options.pacing_rate:
|
||||||
|
pacing = Pacing(options.pacing_rate)
|
||||||
|
|
||||||
|
# Setup terminal
|
||||||
|
io = TerminalIO()
|
||||||
|
|
||||||
# Setup rx pin
|
# Setup rx pin
|
||||||
rxpin = SerialRxPin(baud)
|
rxpin = SerialRxPin(baud, io)
|
||||||
net = pysimulavr.Net()
|
net = pysimulavr.Net()
|
||||||
net.Add(rxpin)
|
net.Add(rxpin)
|
||||||
net.Add(dev.GetPin("D1"))
|
net.Add(dev.GetPin("D1"))
|
||||||
|
|
||||||
# Setup tx pin
|
# Setup tx pin
|
||||||
txpin = SerialTxPin(baud)
|
txpin = SerialTxPin(baud, io)
|
||||||
net2 = pysimulavr.Net()
|
net2 = pysimulavr.Net()
|
||||||
net2.Add(dev.GetPin("D0"))
|
net2.Add(dev.GetPin("D0"))
|
||||||
net2.Add(txpin)
|
net2.Add(txpin)
|
||||||
@@ -183,27 +227,9 @@ def main():
|
|||||||
|
|
||||||
# Run loop
|
# Run loop
|
||||||
try:
|
try:
|
||||||
|
io.run(fd)
|
||||||
trace.start()
|
trace.start()
|
||||||
while 1:
|
sc.RunTimeRange(0x7fff0000ffff0000)
|
||||||
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)
|
|
||||||
trace.finish()
|
trace.finish()
|
||||||
finally:
|
finally:
|
||||||
os.unlink(ptyname)
|
os.unlink(ptyname)
|
||||||
|
|||||||
@@ -172,6 +172,9 @@ def main():
|
|||||||
if '+' in ref:
|
if '+' in ref:
|
||||||
# Inter-function jump.
|
# Inter-function jump.
|
||||||
pass
|
pass
|
||||||
|
elif insn.startswith('ld') or insn.startswith('st'):
|
||||||
|
# memory access
|
||||||
|
pass
|
||||||
elif insn in ('rjmp', 'jmp', 'brne', 'brcs'):
|
elif insn in ('rjmp', 'jmp', 'brne', 'brcs'):
|
||||||
# Tail call
|
# Tail call
|
||||||
cur.noteCall(insnaddr, calladdr, 0)
|
cur.noteCall(insnaddr, calladdr, 0)
|
||||||
@@ -190,7 +193,7 @@ def main():
|
|||||||
funcsbyname[funcnameroot] = info
|
funcsbyname[funcnameroot] = info
|
||||||
mainfunc = funcsbyname.get('sched_main')
|
mainfunc = funcsbyname.get('sched_main')
|
||||||
cmdfunc = funcsbyname.get('command_task')
|
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():
|
for funcnameroot, info in funcsbyname.items():
|
||||||
if (funcnameroot.startswith('_DECL_taskfuncs_')
|
if (funcnameroot.startswith('_DECL_taskfuncs_')
|
||||||
or funcnameroot.startswith('_DECL_initfuncs_')
|
or funcnameroot.startswith('_DECL_initfuncs_')
|
||||||
@@ -204,7 +207,7 @@ def main():
|
|||||||
numparams = int(datalines[info.funcaddr][2], 16)
|
numparams = int(datalines[info.funcaddr][2], 16)
|
||||||
stackusage = cmdfunc.basic_stack_usage + 2 + numparams * 4
|
stackusage = cmdfunc.basic_stack_usage + 2 + numparams * 4
|
||||||
cmdfunc.noteCall(0, f.funcaddr, stackusage)
|
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)
|
eventfunc.noteCall(0, info.funcaddr, eventfunc.basic_stack_usage + 2)
|
||||||
|
|
||||||
# Calculate maxstackusage
|
# Calculate maxstackusage
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import optparse, datetime
|
|||||||
import matplotlib.pyplot as plt, matplotlib.dates as mdates
|
import matplotlib.pyplot as plt, matplotlib.dates as mdates
|
||||||
|
|
||||||
MAXBANDWIDTH=25000.
|
MAXBANDWIDTH=25000.
|
||||||
MAXBUFFER=5.
|
MAXBUFFER=2.
|
||||||
|
|
||||||
def parse_log(logname):
|
def parse_log(logname):
|
||||||
f = open(logname, 'rb')
|
f = open(logname, 'rb')
|
||||||
@@ -27,10 +27,32 @@ def parse_log(logname):
|
|||||||
f.close()
|
f.close()
|
||||||
return out
|
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):
|
def plot_mcu(data, maxbw, outname):
|
||||||
# Generate data for plot
|
# Generate data for plot
|
||||||
basetime = lasttime = data[0]['#sampletime']
|
basetime = lasttime = data[0]['#sampletime']
|
||||||
lastbw = float(data[0]['bytes_write']) + float(data[0]['bytes_retransmit'])
|
lastbw = float(data[0]['bytes_write']) + float(data[0]['bytes_retransmit'])
|
||||||
|
sample_resets = find_print_restarts(data)
|
||||||
times = []
|
times = []
|
||||||
bwdeltas = []
|
bwdeltas = []
|
||||||
loads = []
|
loads = []
|
||||||
@@ -49,7 +71,7 @@ def plot_mcu(data, maxbw, outname):
|
|||||||
load = 0.
|
load = 0.
|
||||||
pt = float(d['print_time'])
|
pt = float(d['print_time'])
|
||||||
hb = float(d['buffer_time'])
|
hb = float(d['buffer_time'])
|
||||||
if pt <= 2*MAXBUFFER or hb >= MAXBUFFER:
|
if pt <= 2. * MAXBUFFER or hb >= MAXBUFFER or st in sample_resets:
|
||||||
hb = 0.
|
hb = 0.
|
||||||
else:
|
else:
|
||||||
hb = 100. * (MAXBUFFER - hb) / MAXBUFFER
|
hb = 100. * (MAXBUFFER - hb) / MAXBUFFER
|
||||||
@@ -63,12 +85,12 @@ def plot_mcu(data, maxbw, outname):
|
|||||||
# Build plot
|
# Build plot
|
||||||
fig, ax1 = plt.subplots()
|
fig, ax1 = plt.subplots()
|
||||||
ax1.set_title("MCU bandwidth and load utilization")
|
ax1.set_title("MCU bandwidth and load utilization")
|
||||||
ax1.set_xlabel('Time (UTC)')
|
ax1.set_xlabel('Time')
|
||||||
ax1.set_ylabel('Usage (%)')
|
ax1.set_ylabel('Usage (%)')
|
||||||
ax1.plot_date(times, bwdeltas, 'g', label='Bandwidth')
|
ax1.plot_date(times, bwdeltas, 'g', label='Bandwidth')
|
||||||
ax1.plot_date(times, loads, 'r', label='MCU load')
|
ax1.plot_date(times, loads, 'r', label='MCU load')
|
||||||
#ax1.plot_date(times, hostbuffers, 'c', label='Host buffer')
|
ax1.plot_date(times, hostbuffers, 'c', label='Host buffer')
|
||||||
ax1.legend()
|
ax1.legend(loc='best')
|
||||||
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
|
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
|
||||||
#plt.gcf().autofmt_xdate()
|
#plt.gcf().autofmt_xdate()
|
||||||
ax1.grid(True)
|
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.
|
// 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/gpio.h" // struct gpio_adc
|
||||||
#include "board/irq.h" // irq_disable
|
#include "board/irq.h" // irq_disable
|
||||||
#include "command.h" // DECL_COMMAND
|
#include "command.h" // DECL_COMMAND
|
||||||
@@ -50,7 +50,7 @@ analog_in_event(struct timer *timer)
|
|||||||
void
|
void
|
||||||
command_config_analog_in(uint32_t *args)
|
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));
|
args[0], command_config_analog_in, sizeof(*a));
|
||||||
a->timer.func = analog_in_event;
|
a->timer.func = analog_in_event;
|
||||||
a->pin = gpio_adc_setup(args[1]);
|
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
|
void
|
||||||
command_query_analog_in(uint32_t *args)
|
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);
|
sched_del_timer(&a->timer);
|
||||||
gpio_adc_cancel_sample(a->pin);
|
gpio_adc_cancel_sample(a->pin);
|
||||||
a->next_begin_time = args[1];
|
a->next_begin_time = args[1];
|
||||||
@@ -74,7 +74,7 @@ command_query_analog_in(uint32_t *args)
|
|||||||
a->max_value = args[6];
|
a->max_value = args[6];
|
||||||
if (! a->sample_count)
|
if (! a->sample_count)
|
||||||
return;
|
return;
|
||||||
sched_timer(&a->timer);
|
sched_add_timer(&a->timer);
|
||||||
}
|
}
|
||||||
DECL_COMMAND(command_query_analog_in,
|
DECL_COMMAND(command_query_analog_in,
|
||||||
"query_analog_in oid=%c clock=%u sample_ticks=%u sample_count=%c"
|
"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
|
dirs-y += src/avr lib/pjrc_usb_serial
|
||||||
|
|
||||||
CFLAGS-y += -Os -mmcu=$(CONFIG_MCU) -DF_CPU=$(CONFIG_CLOCK_FREQ)
|
CFLAGS-y += -mmcu=$(CONFIG_MCU)
|
||||||
LDFLAGS-y += -Wl,--relax
|
|
||||||
|
|
||||||
# Add avr source files
|
# Add avr source files
|
||||||
src-y += avr/main.c avr/timer.c avr/gpio.c avr/misc.c
|
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_USBSERIAL) += avr/usbserial.c ../lib/pjrc_usb_serial/usb_serial.c
|
||||||
src-$(CONFIG_AVR_SERIAL) += avr/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
|
# Build the additional hex output file
|
||||||
target-y += $(OUT)klipper.elf.hex
|
target-y += $(OUT)klipper.elf.hex
|
||||||
|
|
||||||
$(OUT)klipper.elf.hex: $(OUT)klipper.elf
|
$(OUT)klipper.elf.hex: $(OUT)klipper.elf
|
||||||
@echo " Creating hex file $@"
|
@echo " Creating hex file $@"
|
||||||
$(Q)$(OBJCOPY) -j .text -j .data -O ihex $< $@
|
$(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.
|
// 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.
|
// 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))
|
#define GPIO(PORT, NUM) (((PORT)-'A') * 8 + (NUM))
|
||||||
@@ -44,6 +44,67 @@ struct gpio_digital_regs {
|
|||||||
#define GPIO2REGS(pin) \
|
#define GPIO2REGS(pin) \
|
||||||
((struct gpio_digital_regs*)READP(digital_regs[GPIO2PORT(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 {
|
struct gpio_pwm_info {
|
||||||
volatile void *ocr;
|
volatile void *ocr;
|
||||||
volatile uint8_t *rega, *regb;
|
volatile uint8_t *rega, *regb;
|
||||||
@@ -101,116 +162,21 @@ static const uint8_t pwm_pins[ARRAY_SIZE(pwm_regs)] PROGMEM = {
|
|||||||
#endif
|
#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
|
struct gpio_pwm
|
||||||
gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val)
|
gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val)
|
||||||
{
|
{
|
||||||
|
// Find pin in pwm_pins table
|
||||||
uint8_t chan;
|
uint8_t chan;
|
||||||
for (chan=0; chan<ARRAY_SIZE(pwm_regs); chan++) {
|
for (chan=0; ; chan++) {
|
||||||
if (READP(pwm_pins[chan]) != pin)
|
if (chan >= ARRAY_SIZE(pwm_pins))
|
||||||
continue;
|
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];
|
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) {
|
if (flags & GP_AFMT) {
|
||||||
switch (cycle_time) {
|
switch (cycle_time) {
|
||||||
case 0 ... 8*510L - 1: cs = 1; break;
|
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);
|
volatile uint8_t *rega = READP(p->rega), *regb = READP(p->regb);
|
||||||
uint8_t en_bit = READP(p->en_bit);
|
uint8_t en_bit = READP(p->en_bit);
|
||||||
struct gpio_digital_regs *regs = GPIO2REGS(pin);
|
struct gpio_digital_regs *gpio_regs = GPIO2REGS(pin);
|
||||||
uint8_t bit = GPIO2BIT(pin);
|
uint8_t gpio_bit = GPIO2BIT(pin);
|
||||||
struct gpio_pwm g = (struct gpio_pwm) {
|
struct gpio_pwm g = (struct gpio_pwm) {
|
||||||
(void*)READP(p->ocr), flags & GP_8BIT };
|
(void*)READP(p->ocr), flags & GP_8BIT };
|
||||||
if (rega == &TCCR1A)
|
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
|
// Setup PWM timer
|
||||||
irqstatus_t flag = irq_save();
|
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
|
// Set default value and enable output
|
||||||
gpio_pwm_write(g, val);
|
gpio_pwm_write(g, val);
|
||||||
*rega |= (1<<WGM00) | en_bit;
|
*rega |= (1<<WGM00) | en_bit;
|
||||||
regs->mode |= bit;
|
gpio_regs->mode |= gpio_bit;
|
||||||
irq_restore(flag);
|
irq_restore(flag);
|
||||||
|
|
||||||
return g;
|
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);
|
DECL_CONSTANT(ADC_MAX, 1024);
|
||||||
|
|
||||||
struct gpio_adc
|
struct gpio_adc
|
||||||
gpio_adc_setup(uint8_t pin)
|
gpio_adc_setup(uint8_t pin)
|
||||||
{
|
{
|
||||||
|
// Find pin in adc_pins table
|
||||||
uint8_t chan;
|
uint8_t chan;
|
||||||
for (chan=0; chan<ARRAY_SIZE(adc_pins); chan++) {
|
for (chan=0; ; chan++) {
|
||||||
if (READP(adc_pins[chan]) != pin)
|
if (chan >= ARRAY_SIZE(adc_pins))
|
||||||
continue;
|
shutdown("Not a valid ADC pin");
|
||||||
|
if (READP(adc_pins[chan]) == pin)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Enable ADC
|
// Enable ADC
|
||||||
ADCSRA |= (1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2)|(1<<ADEN);
|
ADCSRA |= (1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2)|(1<<ADEN);
|
||||||
@@ -281,8 +285,6 @@ gpio_adc_setup(uint8_t pin)
|
|||||||
|
|
||||||
return (struct gpio_adc){ chan };
|
return (struct gpio_adc){ chan };
|
||||||
}
|
}
|
||||||
shutdown("Not a valid ADC pin");
|
|
||||||
}
|
|
||||||
|
|
||||||
enum { ADC_DUMMY=0xff };
|
enum { ADC_DUMMY=0xff };
|
||||||
static uint8_t last_analog_read = ADC_DUMMY;
|
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
|
void
|
||||||
spi_config(void)
|
spi_config(void)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,9 +4,13 @@
|
|||||||
//
|
//
|
||||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
|
#include "autoconf.h" // CONFIG_MCU
|
||||||
|
#include "command.h" // DECL_CONSTANT
|
||||||
#include "irq.h" // irq_enable
|
#include "irq.h" // irq_enable
|
||||||
#include "sched.h" // sched_main
|
#include "sched.h" // sched_main
|
||||||
|
|
||||||
|
DECL_CONSTANT(MCU, CONFIG_MCU);
|
||||||
|
|
||||||
// Main entry point for avr code.
|
// Main entry point for avr code.
|
||||||
int
|
int
|
||||||
main(void)
|
main(void)
|
||||||
|
|||||||
@@ -30,10 +30,12 @@ serial_init(void)
|
|||||||
{
|
{
|
||||||
if (CONFIG_SERIAL_BAUD_U2X) {
|
if (CONFIG_SERIAL_BAUD_U2X) {
|
||||||
UCSR0A = 1<<U2X0;
|
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 {
|
} else {
|
||||||
UCSR0A = 0;
|
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);
|
UCSR0C = (1<<UCSZ01) | (1<<UCSZ00);
|
||||||
@@ -115,7 +117,7 @@ char *
|
|||||||
console_get_output(uint8_t len)
|
console_get_output(uint8_t len)
|
||||||
{
|
{
|
||||||
uint8_t tpos = readb(&transmit_pos), tmax = readb(&transmit_max);
|
uint8_t tpos = readb(&transmit_pos), tmax = readb(&transmit_max);
|
||||||
if (tpos == tmax) {
|
if (tpos >= tmax) {
|
||||||
tpos = tmax = 0;
|
tpos = tmax = 0;
|
||||||
writeb(&transmit_max, 0);
|
writeb(&transmit_max, 0);
|
||||||
writeb(&transmit_pos, 0);
|
writeb(&transmit_pos, 0);
|
||||||
@@ -126,12 +128,10 @@ console_get_output(uint8_t len)
|
|||||||
return NULL;
|
return NULL;
|
||||||
// Disable TX irq and move buffer
|
// Disable TX irq and move buffer
|
||||||
writeb(&transmit_max, 0);
|
writeb(&transmit_max, 0);
|
||||||
barrier();
|
|
||||||
tpos = readb(&transmit_pos);
|
tpos = readb(&transmit_pos);
|
||||||
tmax -= tpos;
|
tmax -= tpos;
|
||||||
memmove(&transmit_buf[0], &transmit_buf[tpos], tmax);
|
memmove(&transmit_buf[0], &transmit_buf[tpos], tmax);
|
||||||
writeb(&transmit_pos, 0);
|
writeb(&transmit_pos, 0);
|
||||||
barrier();
|
|
||||||
writeb(&transmit_max, tmax);
|
writeb(&transmit_max, tmax);
|
||||||
enable_tx_irq();
|
enable_tx_irq();
|
||||||
return &transmit_buf[tmax];
|
return &transmit_buf[tmax];
|
||||||
|
|||||||
167
src/avr/timer.c
@@ -16,14 +16,38 @@
|
|||||||
* Low level timer code
|
* Low level timer code
|
||||||
****************************************************************/
|
****************************************************************/
|
||||||
|
|
||||||
DECL_CONSTANT(CLOCK_FREQ, F_CPU);
|
DECL_CONSTANT(CLOCK_FREQ, CONFIG_CLOCK_FREQ);
|
||||||
DECL_CONSTANT(MCU, CONFIG_MCU);
|
|
||||||
|
|
||||||
// Return the number of clock ticks for a given number of microseconds
|
// Return the number of clock ticks for a given number of microseconds
|
||||||
uint32_t
|
uint32_t
|
||||||
timer_from_us(uint32_t us)
|
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
|
static inline uint16_t
|
||||||
@@ -38,13 +62,6 @@ timer_set(uint16_t next)
|
|||||||
OCR1A = next;
|
OCR1A = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
|
||||||
timer_set_clear(uint16_t next)
|
|
||||||
{
|
|
||||||
OCR1A = next;
|
|
||||||
TIFR1 = 1<<OCF1A;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
timer_repeat_set(uint16_t next)
|
timer_repeat_set(uint16_t next)
|
||||||
{
|
{
|
||||||
@@ -53,10 +70,16 @@ timer_repeat_set(uint16_t next)
|
|||||||
TIFR1 = 1<<OCF1B;
|
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
|
static void
|
||||||
timer_init(void)
|
timer_init(void)
|
||||||
@@ -73,8 +96,13 @@ timer_init(void)
|
|||||||
TCCR1A = 0;
|
TCCR1A = 0;
|
||||||
// Normal Mode
|
// Normal Mode
|
||||||
TCCR1B = 1<<CS10;
|
TCCR1B = 1<<CS10;
|
||||||
|
// Setup for first irq
|
||||||
|
irqstatus_t flag = irq_save();
|
||||||
|
timer_reset();
|
||||||
|
TIFR1 = 1<<TOV1;
|
||||||
// enable interrupt
|
// enable interrupt
|
||||||
TIMSK1 = 1<<OCIE1A;
|
TIMSK1 = 1<<OCIE1A;
|
||||||
|
irq_restore(flag);
|
||||||
}
|
}
|
||||||
DECL_INIT(timer_init);
|
DECL_INIT(timer_init);
|
||||||
|
|
||||||
@@ -83,20 +111,19 @@ DECL_INIT(timer_init);
|
|||||||
* 32bit timer wrappers
|
* 32bit timer wrappers
|
||||||
****************************************************************/
|
****************************************************************/
|
||||||
|
|
||||||
static uint32_t timer_last;
|
static uint16_t timer_high;
|
||||||
|
|
||||||
// Return the 32bit current time given the 16bit current time.
|
// Return the current time (in absolute clock ticks).
|
||||||
static __always_inline uint32_t
|
uint32_t
|
||||||
calc_time(uint32_t last, uint16_t cur)
|
timer_read_time(void)
|
||||||
{
|
{
|
||||||
union u32_u16_u {
|
irqstatus_t flag = irq_save();
|
||||||
struct { uint16_t lo, hi; };
|
union u32_u calc;
|
||||||
uint32_t val;
|
calc.val = timer_get();
|
||||||
} calc;
|
calc.hi = timer_high;
|
||||||
calc.val = last;
|
if (TIFR1 & (1<<TOV1) && calc.lo < 0x8000)
|
||||||
if (cur < calc.lo)
|
|
||||||
calc.hi++;
|
calc.hi++;
|
||||||
calc.lo = cur;
|
irq_restore(flag);
|
||||||
return calc.val;
|
return calc.val;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,39 +131,11 @@ calc_time(uint32_t last, uint16_t cur)
|
|||||||
void
|
void
|
||||||
timer_periodic(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
|
#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_MIN_TRY_TICKS 60 // 40 ticks to exit irq; 20 ticks of progress
|
||||||
#define TIMER_DEFER_REPEAT_TICKS 200
|
#define TIMER_DEFER_REPEAT_TICKS 200
|
||||||
|
|
||||||
// Similar to timer_set_next(), but wait for the given time if it is
|
// Hardware timer IRQ handler - dispatch software timers
|
||||||
// in the near future.
|
ISR(TIMER1_COMPA_vect)
|
||||||
uint8_t
|
|
||||||
timer_try_set_next(uint32_t target)
|
|
||||||
{
|
{
|
||||||
uint16_t next = target, now = timer_get();
|
uint16_t next;
|
||||||
int16_t diff = next - now;
|
for (;;) {
|
||||||
if (diff > TIMER_MIN_TRY_TICKS)
|
// Run the next software timer
|
||||||
// Schedule next timer normally.
|
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;
|
goto done;
|
||||||
|
|
||||||
// Next timer is in the past or near future - can't reschedule to it
|
// Next timer in very near future - wait for it to be ready
|
||||||
if (!(TIFR1 & (1<<OCF1B))) {
|
do {
|
||||||
// Can run more timers from this irq; briefly allow irqs
|
|
||||||
irq_enable();
|
irq_enable();
|
||||||
asm("nop");
|
if (unlikely(TIFR1 & (1<<OCF1B)))
|
||||||
irq_disable();
|
goto force_defer;
|
||||||
|
|
||||||
while (diff >= 0) {
|
|
||||||
// Next timer is in the near future - wait for time to occur
|
|
||||||
now = timer_get();
|
|
||||||
irq_enable();
|
|
||||||
diff = next - now;
|
|
||||||
irq_disable();
|
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
|
// 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);
|
timer_repeat_set(now + TIMER_REPEAT_TICKS);
|
||||||
next = now + TIMER_DEFER_REPEAT_TICKS;
|
next = now + TIMER_DEFER_REPEAT_TICKS;
|
||||||
|
|
||||||
done:
|
done:
|
||||||
timer_set(next);
|
timer_set(next);
|
||||||
return 1;
|
return;
|
||||||
fail:
|
|
||||||
shutdown("Rescheduled timer in the past");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Periodic background task that temporarily boosts priority of
|
// Periodic background task that temporarily boosts priority of
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <avr/interrupt.h> // WDT_vect
|
#include <avr/interrupt.h> // WDT_vect
|
||||||
#include <avr/wdt.h> // wdt_enable
|
#include <avr/wdt.h> // wdt_enable
|
||||||
#include "command.h" // shutdown
|
#include "command.h" // shutdown
|
||||||
|
#include "irq.h" // irq_disable
|
||||||
#include "sched.h" // DECL_TASK
|
#include "sched.h" // DECL_TASK
|
||||||
|
|
||||||
static uint8_t watchdog_shutdown;
|
static uint8_t watchdog_shutdown;
|
||||||
@@ -22,7 +23,7 @@ watchdog_reset(void)
|
|||||||
{
|
{
|
||||||
wdt_reset();
|
wdt_reset();
|
||||||
if (watchdog_shutdown) {
|
if (watchdog_shutdown) {
|
||||||
WDTCSR |= 1<<WDIE;
|
WDTCSR = 1<<WDIE;
|
||||||
watchdog_shutdown = 0;
|
watchdog_shutdown = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,6 +34,25 @@ watchdog_init(void)
|
|||||||
{
|
{
|
||||||
// 0.5s timeout, interrupt and system reset
|
// 0.5s timeout, interrupt and system reset
|
||||||
wdt_enable(WDTO_500MS);
|
wdt_enable(WDTO_500MS);
|
||||||
WDTCSR |= 1<<WDIE;
|
WDTCSR = 1<<WDIE;
|
||||||
}
|
}
|
||||||
DECL_INIT(watchdog_init);
|
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.
|
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
#include <stdlib.h> // malloc
|
#include <stdlib.h> // malloc
|
||||||
#include <string.h> // memcpy
|
#include <string.h> // memset
|
||||||
#include "basecmd.h" // lookup_oid
|
#include "basecmd.h" // oid_lookup
|
||||||
#include "board/irq.h" // irq_save
|
#include "board/irq.h" // irq_save
|
||||||
#include "board/misc.h" // alloc_maxsize
|
#include "board/misc.h" // alloc_maxsize
|
||||||
#include "command.h" // DECL_COMMAND
|
#include "command.h" // DECL_COMMAND
|
||||||
@@ -17,26 +17,53 @@
|
|||||||
* Move queue
|
* Move queue
|
||||||
****************************************************************/
|
****************************************************************/
|
||||||
|
|
||||||
static struct move *move_list, *move_free_list;
|
struct move_freed {
|
||||||
static uint16_t move_count;
|
struct move_freed *next;
|
||||||
|
};
|
||||||
|
|
||||||
void
|
static struct move_freed *move_free_list;
|
||||||
move_free(struct move *m)
|
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;
|
return !!move_count;
|
||||||
move_free_list = m;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
move_alloc(void)
|
||||||
{
|
{
|
||||||
irqstatus_t flag = irq_save();
|
irqstatus_t flag = irq_save();
|
||||||
struct move *m = move_free_list;
|
struct move_freed *mf = move_free_list;
|
||||||
if (!m)
|
if (!mf)
|
||||||
shutdown("Move queue empty");
|
shutdown("Move queue empty");
|
||||||
move_free_list = m->next;
|
move_free_list = mf->next;
|
||||||
irq_restore(flag);
|
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
|
static void
|
||||||
@@ -46,13 +73,28 @@ move_reset(void)
|
|||||||
return;
|
return;
|
||||||
// Add everything in move_list to the free list.
|
// Add everything in move_list to the free list.
|
||||||
uint32_t i;
|
uint32_t i;
|
||||||
for (i=0; i<move_count-1; i++)
|
for (i=0; i<move_count-1; i++) {
|
||||||
move_list[i].next = &move_list[i+1];
|
struct move_freed *mf = move_list + i*move_item_size;
|
||||||
move_list[move_count-1].next = NULL;
|
mf->next = move_list + (i + 1)*move_item_size;
|
||||||
move_free_list = &move_list[0];
|
}
|
||||||
|
struct move_freed *mf = move_list + (move_count - 1)*move_item_size;
|
||||||
|
mf->next = NULL;
|
||||||
|
move_free_list = move_list;
|
||||||
}
|
}
|
||||||
DECL_SHUTDOWN(move_reset);
|
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)
|
* Generic object ids (oid)
|
||||||
@@ -63,45 +105,43 @@ struct oid_s {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static struct oid_s *oids;
|
static struct oid_s *oids;
|
||||||
static uint8_t num_oid;
|
static uint8_t oid_count;
|
||||||
static uint32_t config_crc;
|
|
||||||
static uint8_t config_finalized;
|
|
||||||
|
|
||||||
void *
|
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");
|
shutdown("Invalid oid type");
|
||||||
return oids[oid].data;
|
return oids[oid].data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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");
|
shutdown("Can't assign oid");
|
||||||
oids[oid].type = type;
|
oids[oid].type = type;
|
||||||
oids[oid].data = data;
|
oids[oid].data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *
|
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);
|
void *data = malloc(size);
|
||||||
if (!data)
|
if (!data)
|
||||||
shutdown("malloc failed");
|
shutdown("malloc failed");
|
||||||
memset(data, 0, size);
|
memset(data, 0, size);
|
||||||
assign_oid(oid, type, data);
|
oid_assign(oid, type, data);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *
|
void *
|
||||||
next_oid(uint8_t *i, void *type)
|
oid_next(uint8_t *i, void *type)
|
||||||
{
|
{
|
||||||
uint8_t oid = *i;
|
uint8_t oid = *i;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
oid++;
|
oid++;
|
||||||
if (oid >= num_oid)
|
if (oid >= oid_count)
|
||||||
return NULL;
|
return NULL;
|
||||||
if (oids[oid].type == type) {
|
if (oids[oid].type == type) {
|
||||||
*i = oid;
|
*i = oid;
|
||||||
@@ -120,31 +160,32 @@ command_allocate_oids(uint32_t *args)
|
|||||||
if (!oids)
|
if (!oids)
|
||||||
shutdown("malloc failed");
|
shutdown("malloc failed");
|
||||||
memset(oids, 0, sizeof(oids[0]) * count);
|
memset(oids, 0, sizeof(oids[0]) * count);
|
||||||
num_oid = count;
|
oid_count = count;
|
||||||
}
|
}
|
||||||
DECL_COMMAND(command_allocate_oids, "allocate_oids count=%c");
|
DECL_COMMAND(command_allocate_oids, "allocate_oids count=%c");
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
* Config CRC
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
static uint32_t config_crc;
|
||||||
|
|
||||||
void
|
void
|
||||||
command_get_config(uint32_t *args)
|
command_get_config(uint32_t *args)
|
||||||
{
|
{
|
||||||
sendf("config is_config=%c crc=%u move_count=%hu"
|
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");
|
DECL_COMMAND_FLAGS(command_get_config, HF_IN_SHUTDOWN, "get_config");
|
||||||
|
|
||||||
void
|
void
|
||||||
command_finalize_config(uint32_t *args)
|
command_finalize_config(uint32_t *args)
|
||||||
{
|
{
|
||||||
if (!oids || config_finalized)
|
if (!oids || is_finalized())
|
||||||
shutdown("Can't finalize");
|
shutdown("Can't finalize");
|
||||||
uint16_t count = alloc_maxsize(sizeof(*move_list)*1024) / sizeof(*move_list);
|
move_finalize();
|
||||||
move_list = malloc(count * sizeof(*move_list));
|
|
||||||
if (!count || !move_list)
|
|
||||||
shutdown("malloc failed");
|
|
||||||
move_count = count;
|
|
||||||
move_reset();
|
|
||||||
config_crc = args[0];
|
config_crc = args[0];
|
||||||
config_finalized = 1;
|
|
||||||
command_get_config(NULL);
|
command_get_config(NULL);
|
||||||
}
|
}
|
||||||
DECL_COMMAND(command_finalize_config, "finalize_config crc=%u");
|
DECL_COMMAND(command_finalize_config, "finalize_config crc=%u");
|
||||||
@@ -168,7 +209,7 @@ command_start_group(uint32_t *args)
|
|||||||
sched_del_timer(&group_timer);
|
sched_del_timer(&group_timer);
|
||||||
group_timer.func = group_end_event;
|
group_timer.func = group_end_event;
|
||||||
group_timer.waketime = args[0];
|
group_timer.waketime = args[0];
|
||||||
sched_timer(&group_timer);
|
sched_add_timer(&group_timer);
|
||||||
}
|
}
|
||||||
DECL_COMMAND(command_start_group, "start_group clock=%u");
|
DECL_COMMAND(command_start_group, "start_group clock=%u");
|
||||||
|
|
||||||
@@ -187,28 +228,51 @@ DECL_COMMAND(command_end_group, "end_group");
|
|||||||
void
|
void
|
||||||
command_get_status(uint32_t *args)
|
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");
|
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
|
static void
|
||||||
stats_task(void)
|
stats_task(void)
|
||||||
{
|
{
|
||||||
static uint32_t last, count, sumsq;
|
static uint32_t last, count, sumsq;
|
||||||
uint32_t cur = sched_read_time();
|
uint32_t cur = timer_read_time();
|
||||||
uint32_t diff = (cur - last) >> 8;
|
uint32_t diff = cur - last;
|
||||||
last = cur;
|
last = cur;
|
||||||
count++;
|
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)
|
if (nextsumsq < sumsq)
|
||||||
nextsumsq = 0xffffffff;
|
nextsumsq = 0xffffffff;
|
||||||
sumsq = nextsumsq;
|
sumsq = nextsumsq;
|
||||||
|
|
||||||
static uint32_t prev;
|
if (timer_is_before(cur, stats_send_time + timer_from_us(5000000)))
|
||||||
if (sched_is_before(cur, prev + sched_from_us(5000000)))
|
|
||||||
return;
|
return;
|
||||||
sendf("stats count=%u sum=%u sumsq=%u", count, cur - prev, sumsq);
|
sendf("stats count=%u sum=%u sumsq=%u", count, cur - stats_send_time, sumsq);
|
||||||
prev = cur;
|
if (cur < stats_send_time)
|
||||||
|
stats_send_time_high++;
|
||||||
|
stats_send_time = cur;
|
||||||
count = sumsq = 0;
|
count = sumsq = 0;
|
||||||
}
|
}
|
||||||
DECL_TASK(stats_task);
|
DECL_TASK(stats_task);
|
||||||
@@ -258,18 +322,26 @@ command_debug_write16(uint32_t *args)
|
|||||||
DECL_COMMAND_FLAGS(command_debug_write16, HF_IN_SHUTDOWN,
|
DECL_COMMAND_FLAGS(command_debug_write16, HF_IN_SHUTDOWN,
|
||||||
"debug_write16 addr=%u val=%u");
|
"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
|
* Misc commands
|
||||||
****************************************************************/
|
****************************************************************/
|
||||||
|
|
||||||
void
|
|
||||||
command_reset(uint32_t *args)
|
|
||||||
{
|
|
||||||
// XXX - implement reset
|
|
||||||
}
|
|
||||||
DECL_COMMAND_FLAGS(command_reset, HF_IN_SHUTDOWN, "msg_reset");
|
|
||||||
|
|
||||||
void
|
void
|
||||||
command_emergency_stop(uint32_t *args)
|
command_emergency_stop(uint32_t *args)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,21 +3,14 @@
|
|||||||
|
|
||||||
#include <stdint.h> // uint8_t
|
#include <stdint.h> // uint8_t
|
||||||
|
|
||||||
struct move {
|
void move_free(void *m);
|
||||||
uint32_t interval;
|
void *move_alloc(void);
|
||||||
int16_t add;
|
void move_request_size(int size);
|
||||||
uint16_t count;
|
void *oid_lookup(uint8_t oid, void *type);
|
||||||
struct move *next;
|
void *oid_alloc(uint8_t oid, void *type, uint16_t size);
|
||||||
uint8_t flags;
|
void *oid_next(uint8_t *i, void *type);
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
#define foreach_oid(pos,data,oidtype) \
|
#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
|
#endif // basecmd.h
|
||||||
|
|||||||
@@ -4,12 +4,9 @@
|
|||||||
//
|
//
|
||||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
#include <ctype.h> // isspace
|
|
||||||
#include <stdarg.h> // va_start
|
#include <stdarg.h> // va_start
|
||||||
#include <stdio.h> // vsnprintf
|
#include <string.h> // memcpy
|
||||||
#include <stdlib.h> // strtod
|
#include "board/io.h" // readb
|
||||||
#include <string.h> // strcasecmp
|
|
||||||
#include "board/irq.h" // irq_disable
|
|
||||||
#include "board/misc.h" // crc16_ccitt
|
#include "board/misc.h" // crc16_ccitt
|
||||||
#include "board/pgm.h" // READP
|
#include "board/pgm.h" // READP
|
||||||
#include "command.h" // output_P
|
#include "command.h" // output_P
|
||||||
@@ -110,15 +107,23 @@ error:
|
|||||||
shutdown("Command parser error");
|
shutdown("Command parser error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint8_t in_sendf;
|
||||||
|
|
||||||
// Encode a message and transmit it
|
// Encode a message and transmit it
|
||||||
void
|
void
|
||||||
_sendf(uint8_t parserid, ...)
|
_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];
|
const struct command_encoder *cp = &command_encoders[parserid];
|
||||||
uint8_t max_size = READP(cp->max_size);
|
uint8_t max_size = READP(cp->max_size);
|
||||||
char *buf = console_get_output(max_size + MESSAGE_MIN);
|
char *buf = console_get_output(max_size + MESSAGE_MIN);
|
||||||
if (!buf)
|
if (!buf)
|
||||||
return;
|
goto done;
|
||||||
char *p = &buf[MESSAGE_HEADER_SIZE];
|
char *p = &buf[MESSAGE_HEADER_SIZE];
|
||||||
if (max_size) {
|
if (max_size) {
|
||||||
char *maxend = &p[max_size];
|
char *maxend = &p[max_size];
|
||||||
@@ -140,7 +145,10 @@ _sendf(uint8_t parserid, ...)
|
|||||||
case PT_int16:
|
case PT_int16:
|
||||||
case PT_byte:
|
case PT_byte:
|
||||||
if (t >= PT_uint16)
|
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
|
else
|
||||||
v = va_arg(args, uint32_t);
|
v = va_arg(args, uint32_t);
|
||||||
p = encode_int(p, v);
|
p = encode_int(p, v);
|
||||||
@@ -182,11 +190,20 @@ _sendf(uint8_t parserid, ...)
|
|||||||
*p++ = crc;
|
*p++ = crc;
|
||||||
*p++ = MESSAGE_SYNC;
|
*p++ = MESSAGE_SYNC;
|
||||||
console_push_output(msglen);
|
console_push_output(msglen);
|
||||||
|
done:
|
||||||
|
writeb(&in_sendf, 0);
|
||||||
return;
|
return;
|
||||||
error:
|
error:
|
||||||
shutdown("Message encode error");
|
shutdown("Message encode error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sendf_shutdown(void)
|
||||||
|
{
|
||||||
|
writeb(&in_sendf, 0);
|
||||||
|
}
|
||||||
|
DECL_SHUTDOWN(sendf_shutdown);
|
||||||
|
|
||||||
|
|
||||||
/****************************************************************
|
/****************************************************************
|
||||||
* Command routing
|
* Command routing
|
||||||
@@ -293,6 +310,5 @@ command_task(void)
|
|||||||
func(args);
|
func(args);
|
||||||
}
|
}
|
||||||
console_pop_input(msglen);
|
console_pop_input(msglen);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
DECL_TASK(command_task);
|
DECL_TASK(command_task);
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
//
|
//
|
||||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
|
|
||||||
#include <stddef.h> // offsetof
|
#include "basecmd.h" // oid_alloc
|
||||||
#include "basecmd.h" // alloc_oid
|
|
||||||
#include "board/gpio.h" // struct gpio
|
#include "board/gpio.h" // struct gpio
|
||||||
#include "board/irq.h" // irq_disable
|
#include "board/irq.h" // irq_disable
|
||||||
#include "command.h" // DECL_COMMAND
|
#include "command.h" // DECL_COMMAND
|
||||||
@@ -15,12 +14,22 @@
|
|||||||
struct end_stop {
|
struct end_stop {
|
||||||
struct timer time;
|
struct timer time;
|
||||||
uint32_t rest_time;
|
uint32_t rest_time;
|
||||||
struct stepper *stepper;
|
|
||||||
struct gpio_in pin;
|
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
|
// Timer callback for an end stop
|
||||||
static uint_fast8_t
|
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);
|
struct end_stop *e = container_of(t, struct end_stop, time);
|
||||||
uint8_t val = gpio_in_read(e->pin);
|
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;
|
e->time.waketime += e->rest_time;
|
||||||
return SF_RESCHEDULE;
|
return SF_RESCHEDULE;
|
||||||
}
|
}
|
||||||
// Stop stepper
|
stop_steppers(e);
|
||||||
e->flags = ESF_REPORT;
|
|
||||||
stepper_stop(e->stepper);
|
|
||||||
return SF_DONE;
|
return SF_DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
command_config_end_stop(uint32_t *args)
|
command_config_end_stop(uint32_t *args)
|
||||||
{
|
{
|
||||||
struct end_stop *e = alloc_oid(args[0], command_config_end_stop, sizeof(*e));
|
uint8_t stepper_count = args[3];
|
||||||
struct stepper *s = lookup_oid(args[3], command_config_stepper);
|
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->time.func = end_stop_event;
|
||||||
e->stepper = s;
|
|
||||||
e->pin = gpio_in_setup(args[1], args[2]);
|
e->pin = gpio_in_setup(args[1], args[2]);
|
||||||
|
e->stepper_count = stepper_count;
|
||||||
}
|
}
|
||||||
DECL_COMMAND(command_config_end_stop,
|
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
|
// Home an axis
|
||||||
void
|
void
|
||||||
command_end_stop_home(uint32_t *args)
|
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);
|
sched_del_timer(&e->time);
|
||||||
e->time.waketime = args[1];
|
e->time.waketime = args[1];
|
||||||
e->rest_time = args[2];
|
e->rest_time = args[2];
|
||||||
@@ -63,9 +85,8 @@ command_end_stop_home(uint32_t *args)
|
|||||||
e->flags = 0;
|
e->flags = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
e->pin_value = args[3];
|
e->flags = ESF_HOMING | (args[3] ? ESF_PIN_HIGH : 0);
|
||||||
e->flags = ESF_HOMING;
|
sched_add_timer(&e->time);
|
||||||
sched_timer(&e->time);
|
|
||||||
}
|
}
|
||||||
DECL_COMMAND(command_end_stop_home,
|
DECL_COMMAND(command_end_stop_home,
|
||||||
"end_stop_home oid=%c clock=%u rest_ticks=%u pin_value=%c");
|
"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)
|
end_stop_report(uint8_t oid, struct end_stop *e)
|
||||||
{
|
{
|
||||||
irq_disable();
|
irq_disable();
|
||||||
uint32_t position = stepper_get_position(e->stepper);
|
|
||||||
uint8_t eflags = e->flags;
|
uint8_t eflags = e->flags;
|
||||||
e->flags &= ~ESF_REPORT;
|
e->flags &= ~ESF_REPORT;
|
||||||
irq_enable();
|
irq_enable();
|
||||||
|
|
||||||
sendf("end_stop_state oid=%c homing=%c pin=%c pos=%i"
|
sendf("end_stop_state oid=%c homing=%c pin=%c"
|
||||||
, oid, !!(eflags & ESF_HOMING), gpio_in_read(e->pin)
|
, oid, !!(eflags & ESF_HOMING), gpio_in_read(e->pin));
|
||||||
, position - STEPPER_POSITION_BIAS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
command_end_stop_query(uint32_t *args)
|
command_end_stop_query(uint32_t *args)
|
||||||
{
|
{
|
||||||
uint8_t oid = args[0];
|
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);
|
end_stop_report(oid, e);
|
||||||
}
|
}
|
||||||
DECL_COMMAND(command_end_stop_query, "end_stop_query oid=%c");
|
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
|
#define __GENERIC_IO_H
|
||||||
|
|
||||||
#include <stdint.h> // uint32_t
|
#include <stdint.h> // uint32_t
|
||||||
|
#include "compiler.h" // barrier
|
||||||
|
|
||||||
static inline void writel(void *addr, uint32_t val) {
|
static inline void writel(void *addr, uint32_t val) {
|
||||||
|
barrier();
|
||||||
*(volatile uint32_t *)addr = val;
|
*(volatile uint32_t *)addr = val;
|
||||||
}
|
}
|
||||||
static inline void writew(void *addr, uint16_t val) {
|
static inline void writew(void *addr, uint16_t val) {
|
||||||
|
barrier();
|
||||||
*(volatile uint16_t *)addr = val;
|
*(volatile uint16_t *)addr = val;
|
||||||
}
|
}
|
||||||
static inline void writeb(void *addr, uint8_t val) {
|
static inline void writeb(void *addr, uint8_t val) {
|
||||||
|
barrier();
|
||||||
*(volatile uint8_t *)addr = val;
|
*(volatile uint8_t *)addr = val;
|
||||||
}
|
}
|
||||||
static inline uint32_t readl(const void *addr) {
|
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) {
|
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) {
|
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
|
#endif // io.h
|
||||||
|
|||||||
@@ -10,10 +10,9 @@ char *console_get_output(uint8_t len);
|
|||||||
void console_push_output(uint8_t len);
|
void console_push_output(uint8_t len);
|
||||||
|
|
||||||
uint32_t timer_from_us(uint32_t us);
|
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);
|
uint32_t timer_read_time(void);
|
||||||
uint8_t timer_set_next(uint32_t next);
|
void timer_periodic(void);
|
||||||
uint8_t timer_try_set_next(uint32_t next);
|
|
||||||
|
|
||||||
size_t alloc_maxsize(size_t reqsize);
|
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.
|
// 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/gpio.h" // struct gpio_out
|
||||||
#include "board/irq.h" // irq_disable
|
#include "board/irq.h" // irq_disable
|
||||||
|
#include "board/misc.h" // timer_is_before
|
||||||
#include "command.h" // DECL_COMMAND
|
#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
|
void
|
||||||
command_config_digital_out(uint32_t *args)
|
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));
|
, sizeof(*d));
|
||||||
d->default_value = args[2];
|
d->default_value = args[2];
|
||||||
d->pin = gpio_out_setup(args[1], d->default_value);
|
d->pin = gpio_out_setup(args[1], d->default_value);
|
||||||
@@ -56,12 +57,12 @@ DECL_COMMAND(command_config_digital_out,
|
|||||||
void
|
void
|
||||||
command_schedule_digital_out(uint32_t *args)
|
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);
|
sched_del_timer(&d->timer);
|
||||||
d->timer.func = digital_out_event;
|
d->timer.func = digital_out_event;
|
||||||
d->timer.waketime = args[1];
|
d->timer.waketime = args[1];
|
||||||
d->value = args[2];
|
d->value = args[2];
|
||||||
sched_timer(&d->timer);
|
sched_add_timer(&d->timer);
|
||||||
}
|
}
|
||||||
DECL_COMMAND(command_schedule_digital_out,
|
DECL_COMMAND(command_schedule_digital_out,
|
||||||
"schedule_digital_out oid=%c clock=%u value=%c");
|
"schedule_digital_out oid=%c clock=%u value=%c");
|
||||||
@@ -117,7 +118,7 @@ soft_pwm_toggle_event(struct timer *timer)
|
|||||||
waketime += s->on_duration;
|
waketime += s->on_duration;
|
||||||
else
|
else
|
||||||
waketime += s->off_duration;
|
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
|
// End of normal pulsing - next event loads new pwm settings
|
||||||
s->timer.func = soft_pwm_load_event;
|
s->timer.func = soft_pwm_load_event;
|
||||||
waketime = s->end_time;
|
waketime = s->end_time;
|
||||||
@@ -155,7 +156,7 @@ soft_pwm_load_event(struct timer *timer)
|
|||||||
void
|
void
|
||||||
command_config_soft_pwm_out(uint32_t *args)
|
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));
|
, sizeof(*s));
|
||||||
s->cycle_time = args[2];
|
s->cycle_time = args[2];
|
||||||
s->pulse_time = s->cycle_time / 255;
|
s->pulse_time = s->cycle_time / 255;
|
||||||
@@ -171,7 +172,7 @@ DECL_COMMAND(command_config_soft_pwm_out,
|
|||||||
void
|
void
|
||||||
command_schedule_soft_pwm_out(uint32_t *args)
|
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];
|
uint32_t time = args[1];
|
||||||
uint8_t value = args[2];
|
uint8_t value = args[2];
|
||||||
uint8_t next_flags = SPF_CHECK_END | SPF_HAVE_NEXT;
|
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;
|
next_flags |= SPF_NEXT_CHECK_END;
|
||||||
}
|
}
|
||||||
irq_disable();
|
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");
|
shutdown("next soft pwm extends existing pwm");
|
||||||
s->end_time = time;
|
s->end_time = time;
|
||||||
s->next_on_duration = next_on_duration;
|
s->next_on_duration = next_on_duration;
|
||||||
s->next_off_duration = next_off_duration;
|
s->next_off_duration = next_off_duration;
|
||||||
s->flags |= next_flags;
|
s->flags |= 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
|
// soft_pwm_toggle_event() will schedule a load event when ready
|
||||||
} else {
|
} else {
|
||||||
// Schedule the loading of the pwm parameters at the requested time
|
// Schedule the loading of the pwm parameters at the requested time
|
||||||
sched_del_timer(&s->timer);
|
sched_del_timer(&s->timer);
|
||||||
s->timer.waketime = time;
|
s->timer.waketime = time;
|
||||||
s->timer.func = soft_pwm_load_event;
|
s->timer.func = soft_pwm_load_event;
|
||||||
sched_timer(&s->timer);
|
sched_add_timer(&s->timer);
|
||||||
}
|
}
|
||||||
irq_enable();
|
irq_enable();
|
||||||
}
|
}
|
||||||
|
|||||||