Compare commits
229 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3aadda6fb3 | ||
|
|
159b71e51e | ||
|
|
718be7c6a3 | ||
|
|
eb7bdf18ad | ||
|
|
fe44dd8baa | ||
|
|
ae010215e7 | ||
|
|
eec81683eb | ||
|
|
1965298ab0 | ||
|
|
9a1ac45d19 | ||
|
|
b817848567 | ||
|
|
3a11645afe | ||
|
|
7ed7791723 | ||
|
|
3b68769ea5 | ||
|
|
2ddfa32dd8 | ||
|
|
371647109f | ||
|
|
91b5e8e942 | ||
|
|
d34d3b05b8 | ||
|
|
78462cff4c | ||
|
|
edbfc6f856 | ||
|
|
d6d8587289 | ||
|
|
2919f37343 | ||
|
|
dd4cc8eb4c | ||
|
|
c520bf981d | ||
|
|
c454e88d9a | ||
|
|
b5e573957c | ||
|
|
6d59279438 | ||
|
|
1d569a6631 | ||
|
|
7b25d1c06f | ||
|
|
864c78f24a | ||
|
|
c09ca4cf5a | ||
|
|
6f685e9e01 | ||
|
|
128226fe8a | ||
|
|
5cbe7d83e8 | ||
|
|
9399e738bc | ||
|
|
126275d1f4 | ||
|
|
f8da8099d5 | ||
|
|
bcd4510958 | ||
|
|
3ef760c18f | ||
|
|
cfc58d3ce7 | ||
|
|
5eb07966b5 | ||
|
|
e1ba7c17ce | ||
|
|
0df40b43e8 | ||
|
|
17ce45d212 | ||
|
|
39d01158ba | ||
|
|
73c6674306 | ||
|
|
c78dd6a00a | ||
|
|
d5c031bc13 | ||
|
|
2cbb895978 | ||
|
|
e1176e4dfb | ||
|
|
6773ab074b | ||
|
|
4a567c8d10 | ||
|
|
60879fd298 | ||
|
|
ef4c76fe94 | ||
|
|
116b304541 | ||
|
|
3219712c17 | ||
|
|
b761b8c654 | ||
|
|
a209d4db5b | ||
|
|
354b1e666b | ||
|
|
4691243179 | ||
|
|
4e4a5c6336 | ||
|
|
9323a5dfe2 | ||
|
|
b724b3a348 | ||
|
|
317f8c94c8 | ||
|
|
9c0d0f6a72 | ||
|
|
5923a2e3a1 | ||
|
|
8d67e1a4e9 | ||
|
|
33bd67f9b7 | ||
|
|
993cec0891 | ||
|
|
697c6e8d28 | ||
|
|
2585accfeb | ||
|
|
37ddab223f | ||
|
|
119d007058 | ||
|
|
1931b11001 | ||
|
|
c01e6eee1d | ||
|
|
42fbf8256f | ||
|
|
9346ad1914 | ||
|
|
0e52f03b5b | ||
|
|
f54b7b9376 | ||
|
|
5666b88c69 | ||
|
|
889be5b275 | ||
|
|
607d0b4237 | ||
|
|
d120a313b7 | ||
|
|
4d4b9684a5 | ||
|
|
14cbb8dd2d | ||
|
|
aa3388cc59 | ||
|
|
d6902240dd | ||
|
|
105ce35e1b | ||
|
|
c0ca4c5cc7 | ||
|
|
cfa48fe39f | ||
|
|
2dd73d0431 | ||
|
|
d25602e88d | ||
|
|
1f3b4cc749 | ||
|
|
8e58f8fb39 | ||
|
|
f4130aa948 | ||
|
|
de182b1d14 | ||
|
|
f5956b5395 | ||
|
|
8d7e487149 | ||
|
|
eb43b20e3b | ||
|
|
388fe1b23f | ||
|
|
f6d878a898 | ||
|
|
b3e894f241 | ||
|
|
3dbac01e1d | ||
|
|
69507a0354 | ||
|
|
42c9031c81 | ||
|
|
cb0c38f7d8 | ||
|
|
0181023954 | ||
|
|
07b3726d31 | ||
|
|
28a4baf95c | ||
|
|
14685bf77f | ||
|
|
b1011e3fb1 | ||
|
|
17b8ce4c6b | ||
|
|
9090377bbc | ||
|
|
2d4589949c | ||
|
|
8c01be8c75 | ||
|
|
3a015cd00d | ||
|
|
841a9ca2f7 | ||
|
|
fe9eff8ce3 | ||
|
|
82f540bb73 | ||
|
|
ed36041b67 | ||
|
|
1af219fad6 | ||
|
|
6c1d5d912a | ||
|
|
ee0bc3d697 | ||
|
|
64e01f03a2 | ||
|
|
7201f41664 | ||
|
|
d40fd2190d | ||
|
|
4c21e1d00f | ||
|
|
2082300309 | ||
|
|
53acdfd0a5 | ||
|
|
a537ae0ceb | ||
|
|
307c03e480 | ||
|
|
64d6f110a9 | ||
|
|
447908ce0c | ||
|
|
bb281834b0 | ||
|
|
9dbfc76d9d | ||
|
|
ae536b4786 | ||
|
|
8928c394cf | ||
|
|
01422da951 | ||
|
|
f06eeb5c7a | ||
|
|
ca83c13f37 | ||
|
|
8627c94d6a | ||
|
|
6f87a4e685 | ||
|
|
b5aea5b774 | ||
|
|
fd55dd9e9d | ||
|
|
885f63cff0 | ||
|
|
efabe63357 | ||
|
|
1dc9aa8e19 | ||
|
|
9aba1a8536 | ||
|
|
81277154d2 | ||
|
|
d444289111 | ||
|
|
89ffbbed4c | ||
|
|
cc6736c3e3 | ||
|
|
1cc6398074 | ||
|
|
1e045e8ee0 | ||
|
|
f7e33df99d | ||
|
|
4504c0333f | ||
|
|
554ae78d8c | ||
|
|
ee79d0e307 | ||
|
|
7b697105b3 | ||
|
|
3cf8899a5a | ||
|
|
b7c243db19 | ||
|
|
5b2f8104c7 | ||
|
|
cf3bedfbdc | ||
|
|
7f4f696f10 | ||
|
|
9c37a918db | ||
|
|
f2b68fef73 | ||
|
|
c352617c30 | ||
|
|
5d1f773ffb | ||
|
|
da8e0a6e50 | ||
|
|
42faa962fc | ||
|
|
0f94f6c8e3 | ||
|
|
c917bd893d | ||
|
|
d57bc253c5 | ||
|
|
0dce120a20 | ||
|
|
ab61b0a435 | ||
|
|
cc919a5f8d | ||
|
|
8e107b2280 | ||
|
|
f1e0730701 | ||
|
|
2e82fc4790 | ||
|
|
bfda326c24 | ||
|
|
f2b27d17b7 | ||
|
|
5001983d34 | ||
|
|
73e27aee4f | ||
|
|
0d27195fd4 | ||
|
|
1f5783a250 | ||
|
|
37952e8686 | ||
|
|
ab9b9e8584 | ||
|
|
3fb1191cad | ||
|
|
f3a1c914a4 | ||
|
|
b2e36e5d98 | ||
|
|
ff0ffedd17 | ||
|
|
1e87d26707 | ||
|
|
f8de9ae080 | ||
|
|
6a87c5e9f5 | ||
|
|
db7a9cf071 | ||
|
|
765de72f9e | ||
|
|
6202a0f3bc | ||
|
|
413ff19ea8 | ||
|
|
4e7fcc2704 | ||
|
|
871637d3f2 | ||
|
|
0fbcc156c5 | ||
|
|
56d3f4e64c | ||
|
|
cb6828ec34 | ||
|
|
3656006a30 | ||
|
|
7a9b06ad86 | ||
|
|
acd96047de | ||
|
|
516ef1d361 | ||
|
|
b9757c9b69 | ||
|
|
a9b04e8536 | ||
|
|
841adcfff7 | ||
|
|
8ab12c86bf | ||
|
|
abc76ee963 | ||
|
|
b826844b34 | ||
|
|
017371b744 | ||
|
|
4aa2250221 | ||
|
|
49d9ed22bf | ||
|
|
51311948be | ||
|
|
61bbd455cf | ||
|
|
d93645a750 | ||
|
|
8c67adc164 | ||
|
|
04e7eb20fd | ||
|
|
868760f5b1 | ||
|
|
b0fa36e221 | ||
|
|
6356e3d35c | ||
|
|
6e9b5b309c | ||
|
|
d98abfc5db | ||
|
|
c3c64adc32 | ||
|
|
efc2d9b364 | ||
|
|
d96bb6ca82 | ||
|
|
8087200ffe |
2
.github/workflows/build-test.yaml
vendored
2
.github/workflows/build-test.yaml
vendored
@@ -4,7 +4,7 @@ on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
|
||||
138
config/example-generic-caretesian.cfg
Normal file
138
config/example-generic-caretesian.cfg
Normal file
@@ -0,0 +1,138 @@
|
||||
# This file is an example config file for cartesian style printers.
|
||||
# One may copy and edit this file to configure a new printer with
|
||||
# a generic cartesian kinematics.
|
||||
|
||||
# DO NOT COPY THIS FILE WITHOUT CAREFULLY READING AND UPDATING IT
|
||||
# FIRST. Incorrectly configured parameters may cause damage.
|
||||
|
||||
# See docs/Config_Reference.md for a description of parameters.
|
||||
|
||||
[carriage x]
|
||||
position_endstop: 0
|
||||
position_max: 300
|
||||
homing_speed: 50
|
||||
endstop_pin: ^PE5
|
||||
|
||||
[carriage y]
|
||||
position_endstop: 0
|
||||
position_max: 200
|
||||
homing_speed: 50
|
||||
endstop_pin: ^PJ1
|
||||
|
||||
[extra_carriage y1]
|
||||
primary_carriage: y
|
||||
endstop_pin: ^PB6
|
||||
|
||||
[carriage z]
|
||||
position_endstop: 0.5
|
||||
position_max: 100
|
||||
endstop_pin: ^PD3
|
||||
|
||||
[dual_carriage u]
|
||||
primary_carriage: x
|
||||
position_endstop: 300
|
||||
position_max: 300
|
||||
homing_speed: 50
|
||||
endstop_pin: ^PE4
|
||||
|
||||
[stepper my_stepper_x]
|
||||
carriages: x+y
|
||||
step_pin: PF0
|
||||
dir_pin: PF1
|
||||
enable_pin: !PD7
|
||||
microsteps: 16
|
||||
rotation_distance: 40
|
||||
|
||||
[stepper my_stepper_u]
|
||||
carriages: u-y1
|
||||
step_pin: PH1
|
||||
dir_pin: PH0
|
||||
enable_pin: !PA1
|
||||
microsteps: 16
|
||||
rotation_distance: 40
|
||||
|
||||
[stepper my_stepper_y0]
|
||||
carriages: y
|
||||
step_pin: PF6
|
||||
dir_pin: !PF7
|
||||
enable_pin: !PF2
|
||||
microsteps: 16
|
||||
rotation_distance: 40
|
||||
|
||||
[stepper my_stepper_y1]
|
||||
carriages: y1
|
||||
step_pin: PE3
|
||||
dir_pin: !PH6
|
||||
enable_pin: !PG5
|
||||
microsteps: 16
|
||||
rotation_distance: 40
|
||||
|
||||
[stepper my_stepper_z0]
|
||||
carriages: z
|
||||
step_pin: PL3
|
||||
dir_pin: PL1
|
||||
enable_pin: !PK0
|
||||
microsteps: 16
|
||||
rotation_distance: 8
|
||||
|
||||
[stepper my_stepper_z1]
|
||||
carriages: z
|
||||
step_pin: PG1
|
||||
dir_pin: PG0
|
||||
enable_pin: !PH3
|
||||
microsteps: 16
|
||||
rotation_distance: 8
|
||||
|
||||
[extruder]
|
||||
step_pin: PA4
|
||||
dir_pin: PA6
|
||||
enable_pin: !PA2
|
||||
microsteps: 16
|
||||
rotation_distance: 33.5
|
||||
nozzle_diameter: 0.400
|
||||
filament_diameter: 1.750
|
||||
heater_pin: PB4
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
sensor_pin: PK5
|
||||
control: pid
|
||||
pid_Kp: 22.2
|
||||
pid_Ki: 1.08
|
||||
pid_Kd: 114
|
||||
min_temp: 0
|
||||
max_temp: 250
|
||||
|
||||
[extruder1]
|
||||
step_pin: PC1
|
||||
dir_pin: PC3
|
||||
enable_pin: !PC7
|
||||
microsteps: 16
|
||||
rotation_distance: 33.5
|
||||
nozzle_diameter: 0.400
|
||||
filament_diameter: 1.750
|
||||
heater_pin: PB5
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
sensor_pin: PK7
|
||||
control: pid
|
||||
pid_Kp: 22.2
|
||||
pid_Ki: 1.08
|
||||
pid_Kd: 114
|
||||
min_temp: 0
|
||||
max_temp: 250
|
||||
|
||||
[heater_bed]
|
||||
heater_pin: PH5
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
sensor_pin: PK6
|
||||
control: watermark
|
||||
min_temp: 0
|
||||
max_temp: 110
|
||||
|
||||
[mcu]
|
||||
serial: /dev/ttyACM0
|
||||
|
||||
[printer]
|
||||
kinematics: generic_cartesian
|
||||
max_velocity: 500
|
||||
max_accel: 3000
|
||||
max_z_velocity: 20
|
||||
max_z_accel: 100
|
||||
@@ -39,7 +39,7 @@ position_max: 270
|
||||
# Motor4
|
||||
# The M8P only has 4 heater outputs which leaves an extra stepper
|
||||
# This can be used for a second Z stepper, dual_carriage, extruder co-stepper,
|
||||
# or other accesory such as an MMU
|
||||
# or other accessory such as an MMU
|
||||
#[stepper_]
|
||||
#step_pin: PD3
|
||||
#dir_pin: PD2
|
||||
|
||||
@@ -40,7 +40,7 @@ position_max: 270
|
||||
# Motor4
|
||||
# The M8P only has 4 heater outputs which leaves an extra stepper
|
||||
# This can be used for a second Z stepper, dual_carriage, extruder co-stepper,
|
||||
# or other accesory such as an MMU
|
||||
# or other accessory such as an MMU
|
||||
#[stepper_]
|
||||
#step_pin: PD3
|
||||
#dir_pin: PD2
|
||||
|
||||
@@ -43,7 +43,7 @@ position_max: 200
|
||||
# Motor-4
|
||||
# The Octopus only has 4 heater outputs which leaves an extra stepper
|
||||
# This can be used for a second Z stepper, dual_carriage, extruder co-stepper,
|
||||
# or other accesory such as an MMU
|
||||
# or other accessory such as an MMU
|
||||
#[stepper_]
|
||||
#step_pin: PB8
|
||||
#dir_pin: PB9
|
||||
|
||||
@@ -52,7 +52,7 @@ position_max: 200
|
||||
# Driver3
|
||||
# The Octopus only has 4 heater outputs which leaves an extra stepper
|
||||
# This can be used for a second Z stepper, dual_carriage, extruder co-stepper,
|
||||
# or other accesory such as an MMU
|
||||
# or other accessory such as an MMU
|
||||
#[stepper_]
|
||||
#step_pin: PG4
|
||||
#dir_pin: PC1
|
||||
|
||||
@@ -153,3 +153,48 @@ aliases:
|
||||
#uart_pin: PD12
|
||||
#run_current: 0.600
|
||||
#diag_pin:
|
||||
|
||||
########################################
|
||||
# TMC2130 configuration
|
||||
########################################
|
||||
|
||||
#[tmc2130 stepper_x]
|
||||
#cs_pin: PE0
|
||||
#spi_software_miso_pin: PA14
|
||||
#spi_software_mosi_pin: PE14
|
||||
#spi_software_sclk_pin: PE15
|
||||
#run_current: 0.800
|
||||
#diag1_pin: PC1
|
||||
|
||||
#[tmc2130 stepper_y]
|
||||
#cs_pin: PD3
|
||||
#spi_software_miso_pin: PA14
|
||||
#spi_software_mosi_pin: PE14
|
||||
#spi_software_sclk_pin: PE15
|
||||
#run_current: 0.800
|
||||
#diag1_pin: PC3
|
||||
|
||||
#[tmc2130 stepper_z]
|
||||
#cs_pin: PD0
|
||||
#spi_software_miso_pin: PA14
|
||||
#spi_software_mosi_pin: PE14
|
||||
#spi_software_sclk_pin: PE15
|
||||
#run_current: 0.800
|
||||
#diag1_pin: PC0
|
||||
|
||||
#[tmc2130 extruder]
|
||||
#cs_pin: PC6
|
||||
#spi_software_miso_pin: PA14
|
||||
#spi_software_mosi_pin: PE14
|
||||
#spi_software_sclk_pin: PE15
|
||||
#run_current: 0.600
|
||||
#diag1_pin: PC2
|
||||
|
||||
#[tmc2130 extruder1]
|
||||
#cs_pin: PD12
|
||||
#spi_software_miso_pin: PA14
|
||||
#spi_software_mosi_pin: PE14
|
||||
#spi_software_sclk_pin: PE15
|
||||
#run_current: 0.600
|
||||
#stealthchop_threshold: 999999
|
||||
#diag1_pin: PA0
|
||||
|
||||
@@ -89,32 +89,32 @@ max_z_velocity: 5
|
||||
max_z_accel: 100
|
||||
|
||||
[mcp4018 x_axis_pot]
|
||||
scl_pin: PJ5
|
||||
sda_pin: PF3
|
||||
i2c_software_scl_pin: PJ5
|
||||
i2c_software_sda_pin: PF3
|
||||
wiper: 0.50
|
||||
scale: 0.773
|
||||
|
||||
[mcp4018 y_axis_pot]
|
||||
scl_pin: PJ5
|
||||
sda_pin: PF7
|
||||
i2c_software_scl_pin: PJ5
|
||||
i2c_software_sda_pin: PF7
|
||||
wiper: 0.50
|
||||
scale: 0.773
|
||||
|
||||
[mcp4018 z_axis_pot]
|
||||
scl_pin: PJ5
|
||||
sda_pin: PK3
|
||||
i2c_software_scl_pin: PJ5
|
||||
i2c_software_sda_pin: PK3
|
||||
wiper: 0.50
|
||||
scale: 0.773
|
||||
|
||||
[mcp4018 a_axis_pot]
|
||||
scl_pin: PJ5
|
||||
sda_pin: PA5
|
||||
i2c_software_scl_pin: PJ5
|
||||
i2c_software_sda_pin: PA5
|
||||
wiper: 0.50
|
||||
scale: 0.773
|
||||
|
||||
[mcp4018 b_axis_pot]
|
||||
scl_pin: PJ5
|
||||
sda_pin: PJ6
|
||||
i2c_software_scl_pin: PJ5
|
||||
i2c_software_sda_pin: PJ6
|
||||
wiper: 0.50
|
||||
scale: 0.773
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# FSR switch (z endstop) location [homing_override] section
|
||||
# FSR switch (z endstop) offset for Z0 [stepper_z] section
|
||||
# Probe points [quad_gantry_level] section
|
||||
# Min & Max gantry corner postions [quad_gantry_level] section
|
||||
# Min & Max gantry corner positions [quad_gantry_level] section
|
||||
# PID tune [extruder] and [heater_bed] sections
|
||||
# Fine tune E steps [extruder] section
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# FSR switch (z endstop) location [homing_override] section
|
||||
# FSR switch (z endstop) offset for Z0 [stepper_z] section
|
||||
# Probe points [quad_gantry_level] section
|
||||
# Min & Max gantry corner postions [quad_gantry_level] section
|
||||
# Min & Max gantry corner positions [quad_gantry_level] section
|
||||
# PID tune [extruder] and [heater_bed] sections
|
||||
# Fine tune E steps [extruder] section
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ endstop_pin: ^PE4
|
||||
homing_speed: 60
|
||||
# The next parameter needs to be adjusted for
|
||||
# your printer. You may want to start with 280
|
||||
# and meassure the distance from nozzle to bed.
|
||||
# and measure the distance from nozzle to bed.
|
||||
# This value then needs to be added.
|
||||
position_endstop: 273.0
|
||||
arm_length: 229.4
|
||||
|
||||
@@ -43,7 +43,7 @@ position_max: 400
|
||||
#Uncomment if you have a BL-Touch:
|
||||
#position_min: -4
|
||||
#endstop_pin: probe:z_virtual_endstop
|
||||
#and comment the follwing lines:
|
||||
#and comment the following lines:
|
||||
position_endstop: 0.0
|
||||
endstop_pin: ^PD3 #ar18
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# This file contains pin mappings for the stock 2020 Creality CR6-SE.
|
||||
# This file contains pin mappings for the stock 2020 Creality CR6-SE
|
||||
# with the early 4.5.2 board only.
|
||||
# To use this config, during "make menuconfig" select the STM32F103
|
||||
# with a "28KiB bootloader" and serial (on USART1 PA10/PA9)
|
||||
# communication.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# This file contains pin mappings for the Creality CR6-SE with Rev. 4.5.3 Motherboard (Late 2020/2021) as the heater pins changed.
|
||||
# This file contains pin mappings for the Creality CR6-SE
|
||||
# with Rev. 4.5.3 Motherboard (Late 2020/2021) as the heater pins changed.
|
||||
# This config also works for the CR-ERA_V1.1.0.3
|
||||
# To use this config, during "make menuconfig" select the STM32F103
|
||||
# with a "28KiB bootloader" and serial (on USART1 PA10/PA9)
|
||||
# communication.
|
||||
|
||||
@@ -81,7 +81,7 @@ pin: PA0
|
||||
kick_start_time: 0.5
|
||||
|
||||
# Hotend fan
|
||||
# set fan runnig when extruder temperature is over 60
|
||||
# set fan running when extruder temperature is over 60
|
||||
[heater_fan heatbreak_fan]
|
||||
pin: PC0
|
||||
heater:extruder
|
||||
|
||||
@@ -127,32 +127,32 @@ max_z_velocity: 5
|
||||
max_z_accel: 100
|
||||
|
||||
[mcp4018 x_axis_pot]
|
||||
scl_pin: PJ5
|
||||
sda_pin: PF3
|
||||
i2c_software_scl_pin: PJ5
|
||||
i2c_software_sda_pin: PF3
|
||||
wiper: 118
|
||||
scale: 127
|
||||
|
||||
[mcp4018 y_axis_pot]
|
||||
scl_pin: PJ5
|
||||
sda_pin: PF7
|
||||
i2c_software_scl_pin: PJ5
|
||||
i2c_software_sda_pin: PF7
|
||||
wiper: 118
|
||||
scale: 127
|
||||
|
||||
[mcp4018 z_axis_pot]
|
||||
scl_pin: PJ5
|
||||
sda_pin: PK3
|
||||
i2c_software_scl_pin: PJ5
|
||||
i2c_software_sda_pin: PK3
|
||||
wiper: 40
|
||||
scale: 127
|
||||
|
||||
[mcp4018 a_axis_pot]
|
||||
scl_pin: PJ5
|
||||
sda_pin: PA5
|
||||
i2c_software_scl_pin: PJ5
|
||||
i2c_software_sda_pin: PA5
|
||||
wiper: 118
|
||||
scale: 127
|
||||
|
||||
[mcp4018 b_axis_pot]
|
||||
scl_pin: PJ5
|
||||
sda_pin: PJ6
|
||||
i2c_software_scl_pin: PJ5
|
||||
i2c_software_sda_pin: PJ6
|
||||
wiper: 118
|
||||
scale: 127
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ samples_tolerance: 0.200
|
||||
samples_tolerance_retries: 2
|
||||
|
||||
[bed_tilt]
|
||||
# Enable bed tilt measurments using the probe we defined above
|
||||
# Enable bed tilt measurements using the probe we defined above
|
||||
# Probe points using X0 Y0 offsets @ 0.01mm/step
|
||||
points: -2, -6
|
||||
156, -6
|
||||
|
||||
@@ -183,7 +183,7 @@ samples: 2
|
||||
samples_tolerance: 0.100
|
||||
|
||||
[bed_tilt]
|
||||
#Enable bed tilt measurments using the probe we defined above
|
||||
#Enable bed tilt measurements using the probe we defined above
|
||||
#Probe points using X0 Y0 offsets @ 0.01mm/step
|
||||
points: -3, -6
|
||||
282, -6
|
||||
|
||||
@@ -37,7 +37,7 @@ microsteps: 16
|
||||
rotation_distance: 4
|
||||
# Required if not using probe for the virtual endstop
|
||||
# endstop_pin: ^PD3
|
||||
# position_endstop: 250 # Will need ajustment
|
||||
# position_endstop: 250 # Will need adjustment
|
||||
endstop_pin: probe:z_virtual_endstop
|
||||
homing_speed: 10.0
|
||||
position_max: 250
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file constains the pin mappings for the SeeMeCNC Rostock Max
|
||||
# This file contains the pin mappings for the SeeMeCNC Rostock Max
|
||||
# (version 2) delta printer from 2015. To use this config, the
|
||||
# firmware should be compiled for the AVR atmega2560.
|
||||
|
||||
|
||||
177
config/sample-corexyuv.cfg
Normal file
177
config/sample-corexyuv.cfg
Normal file
@@ -0,0 +1,177 @@
|
||||
# This file contains a configuration snippet for a CoreXYUV
|
||||
# printer with an independent dual extruder moving over X and Y axes.
|
||||
|
||||
# See docs/Config_Reference.md for a description of parameters.
|
||||
|
||||
[carriage x]
|
||||
position_endstop: 0
|
||||
position_max: 300
|
||||
homing_speed: 50
|
||||
endstop_pin: ^PE5
|
||||
|
||||
[carriage y]
|
||||
position_endstop: 0
|
||||
position_max: 200
|
||||
homing_speed: 50
|
||||
endstop_pin: ^PJ1
|
||||
|
||||
[dual_carriage u]
|
||||
primary_carriage: x
|
||||
safe_distance: 70
|
||||
position_endstop: 300
|
||||
position_max: 300
|
||||
homing_speed: 50
|
||||
endstop_pin: ^PE4
|
||||
|
||||
[dual_carriage v]
|
||||
primary_carriage: y
|
||||
safe_distance: 50
|
||||
position_endstop: 200
|
||||
position_max: 200
|
||||
homing_speed: 50
|
||||
endstop_pin: ^PD4
|
||||
|
||||
[stepper a]
|
||||
carriages: x+y
|
||||
step_pin: PF0
|
||||
dir_pin: PF1
|
||||
enable_pin: !PD7
|
||||
microsteps: 16
|
||||
rotation_distance: 40
|
||||
|
||||
[stepper b]
|
||||
carriages: u-v
|
||||
step_pin: PH1
|
||||
dir_pin: PH0
|
||||
enable_pin: !PA1
|
||||
microsteps: 16
|
||||
rotation_distance: 40
|
||||
|
||||
[stepper c]
|
||||
carriages: x-y
|
||||
step_pin: PF6
|
||||
dir_pin: !PF7
|
||||
enable_pin: !PF2
|
||||
microsteps: 16
|
||||
rotation_distance: 40
|
||||
|
||||
[stepper d]
|
||||
carriages: u+v
|
||||
step_pin: PE3
|
||||
dir_pin: !PH6
|
||||
enable_pin: !PG5
|
||||
microsteps: 16
|
||||
rotation_distance: 40
|
||||
|
||||
[extruder]
|
||||
step_pin: PA4
|
||||
dir_pin: PA6
|
||||
enable_pin: !PA2
|
||||
microsteps: 16
|
||||
rotation_distance: 33.5
|
||||
nozzle_diameter: 0.400
|
||||
filament_diameter: 1.750
|
||||
heater_pin: PB4
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
sensor_pin: PK5
|
||||
control: pid
|
||||
pid_Kp: 22.2
|
||||
pid_Ki: 1.08
|
||||
pid_Kd: 114
|
||||
min_temp: 0
|
||||
max_temp: 250
|
||||
|
||||
[gcode_macro PARK_extruder]
|
||||
gcode:
|
||||
SET_DUAL_CARRIAGE CARRIAGE=x
|
||||
SET_DUAL_CARRIAGE CARRIAGE=y
|
||||
G90
|
||||
G1 X0 Y0
|
||||
|
||||
[gcode_macro T0]
|
||||
gcode:
|
||||
PARK_{printer.toolhead.extruder}
|
||||
ACTIVATE_EXTRUDER EXTRUDER=extruder
|
||||
SET_DUAL_CARRIAGE CARRIAGE=x
|
||||
SET_DUAL_CARRIAGE CARRIAGE=y
|
||||
|
||||
[extruder1]
|
||||
step_pin: PC1
|
||||
dir_pin: PC3
|
||||
enable_pin: !PC7
|
||||
microsteps: 16
|
||||
rotation_distance: 33.5
|
||||
nozzle_diameter: 0.400
|
||||
filament_diameter: 1.750
|
||||
heater_pin: PB5
|
||||
sensor_type: EPCOS 100K B57560G104F
|
||||
sensor_pin: PK7
|
||||
control: pid
|
||||
pid_Kp: 22.2
|
||||
pid_Ki: 1.08
|
||||
pid_Kd: 114
|
||||
min_temp: 0
|
||||
max_temp: 250
|
||||
|
||||
[gcode_macro PARK_extruder1]
|
||||
gcode:
|
||||
SET_DUAL_CARRIAGE CARRIAGE=u
|
||||
SET_DUAL_CARRIAGE CARRIAGE=v
|
||||
G90
|
||||
G1 X300 Y200
|
||||
|
||||
[gcode_macro T1]
|
||||
gcode:
|
||||
PARK_{printer.toolhead.extruder}
|
||||
ACTIVATE_EXTRUDER EXTRUDER=extruder1
|
||||
SET_DUAL_CARRIAGE CARRIAGE=u
|
||||
SET_DUAL_CARRIAGE CARRIAGE=v
|
||||
|
||||
# A helper script to activate copy mode
|
||||
[gcode_macro ACTIVATE_COPY_MODE]
|
||||
gcode:
|
||||
SET_DUAL_CARRIAGE CARRIAGE=x MODE=PRIMARY
|
||||
SET_DUAL_CARRIAGE CARRIAGE=y MODE=PRIMARY
|
||||
G1 X0 Y0
|
||||
ACTIVATE_EXTRUDER EXTRUDER=extruder
|
||||
SET_DUAL_CARRIAGE CARRIAGE=u MODE=PRIMARY
|
||||
SET_DUAL_CARRIAGE CARRIAGE=v MODE=PRIMARY
|
||||
G1 X150 Y100
|
||||
SET_DUAL_CARRIAGE CARRIAGE=u MODE=COPY
|
||||
SET_DUAL_CARRIAGE CARRIAGE=v MODE=COPY
|
||||
SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder
|
||||
|
||||
# A helper script to activate mirror mode
|
||||
[gcode_macro ACTIVATE_MIRROR_MODE]
|
||||
gcode:
|
||||
SET_DUAL_CARRIAGE CARRIAGE=x MODE=PRIMARY
|
||||
SET_DUAL_CARRIAGE CARRIAGE=y MODE=PRIMARY
|
||||
G1 X0 Y0
|
||||
ACTIVATE_EXTRUDER EXTRUDER=extruder
|
||||
SET_DUAL_CARRIAGE CARRIAGE=u MODE=PRIMARY
|
||||
SET_DUAL_CARRIAGE CARRIAGE=v MODE=PRIMARY
|
||||
G1 X300 Y100
|
||||
SET_DUAL_CARRIAGE CARRIAGE=u MODE=MIRROR
|
||||
SET_DUAL_CARRIAGE CARRIAGE=v MODE=COPY
|
||||
SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder
|
||||
|
||||
[printer]
|
||||
kinematics: generic_cartesian
|
||||
max_velocity: 300
|
||||
max_accel: 3000
|
||||
max_z_velocity: 5
|
||||
max_z_accel: 100
|
||||
|
||||
## An optional input shaper support
|
||||
#[input_shaper]
|
||||
## The section is intentionally empty
|
||||
#
|
||||
#[delayed_gcode init_shaper]
|
||||
#initial_duration: 0.1
|
||||
#gcode:
|
||||
# SET_DUAL_CARRIAGE CARRIAGE=u
|
||||
# SET_DUAL_CARRIAGE CARRIAGE=v
|
||||
# SET_INPUT_SHAPER SHAPER_TYPE_X=<dual_carriage_x_shaper> SHAPER_FREQ_X=<dual_carriage_x_freq> SHAPER_TYPE_Y=<dual_carriage_y_shaper> SHAPER_FREQ_Y=<dual_carriage_y_freq>
|
||||
# SET_DUAL_CARRIAGE CARRIAGE=x MODE=PRIMARY
|
||||
# SET_DUAL_CARRIAGE CARRIAGE=y MODE=PRIMARY
|
||||
# SET_INPUT_SHAPER SHAPER_TYPE_X=<primary_carriage_x_shaper> SHAPER_FREQ_X=<primary_carriage_x_freq> SHAPER_TYPE_Y=<primary_carriage_y_shaper> SHAPER_FREQ_Y=<primary_carriage_y_freq>
|
||||
@@ -6,7 +6,7 @@
|
||||
# Communication interface of "CAN bus (on PA25/PA24)"
|
||||
|
||||
# To flash the board use a debugger, or use a raspberry pi and follow
|
||||
# the instructions at docs/Bootloaders.md fot the SAMC21. You may
|
||||
# the instructions at docs/Bootloaders.md for the SAMC21. You may
|
||||
# supply power to the 1LC by connecting the 3.3v rail on the Pi to the
|
||||
# 5v input of the SWD header on the 1LC.
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ switch_pin: !P1.28 # P1.28 for X-max
|
||||
# variable_pause_z : z lift when MMU2S need intervention and the printer is paused
|
||||
# variable_min_temp_extruder : minimal required heater temperature to load/unload filament from the extruder gear to the nozzle
|
||||
# variable_extruder_eject_temp : heater temperature used to eject filament during home if the filament is already loaded
|
||||
# variable_enable_5in1 : pass from MMU2S standart (0) to MMU2S-5in1 mode with splitter
|
||||
# variable_enable_5in1 : pass from MMU2S standard (0) to MMU2S-5in1 mode with splitter
|
||||
#
|
||||
################################
|
||||
[gcode_macro VAR_MMU2S]
|
||||
@@ -394,7 +394,7 @@ gcode:
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
# Retry unload, try correct misalignement of bondtech gear
|
||||
# Retry unload, try correct misalignment of bondtech gear
|
||||
[gcode_macro RETRY_UNLOAD_FILAMENT_IN_EXTRUDER]
|
||||
gcode:
|
||||
{% if printer["filament_switch_sensor ir_sensor"].filament_detected == True %}
|
||||
@@ -444,7 +444,7 @@ gcode:
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
# Ramming process for standart PLA, code extracted from slic3r gcode
|
||||
# Ramming process for standard PLA, code extracted from slic3r gcode
|
||||
[gcode_macro RAMMING_SLICER]
|
||||
gcode:
|
||||
G91
|
||||
|
||||
@@ -380,6 +380,27 @@ and might later produce asynchronous messages such as:
|
||||
The "header" field in the initial query response is used to describe
|
||||
the fields found in later "data" responses.
|
||||
|
||||
### load_cell_probe/dump_taps
|
||||
|
||||
This endpoint is used to subscribe to details of probing "tap" events.
|
||||
Using this endpoint may increase Klipper's system load.
|
||||
|
||||
A request may look like:
|
||||
`{"id": 123, "method":"load_cell/dump_force",
|
||||
"params": {"sensor": "load_cell", "response_template": {}}}`
|
||||
and might return:
|
||||
`{"id": 123,"result":{"header":["probe_tap_event"]}}`
|
||||
and might later produce asynchronous messages such as:
|
||||
```
|
||||
{"params":{"tap":'{
|
||||
"time": [118032.28039, 118032.2834, ...],
|
||||
"force": [-459.4213119680034, -458.1640702543264, ...],
|
||||
}}}
|
||||
```
|
||||
|
||||
This data can be used to render:
|
||||
* The time/force graph
|
||||
|
||||
### pause_resume/cancel
|
||||
|
||||
This endpoint is similar to running the "PRINT_CANCEL" G-Code command.
|
||||
|
||||
@@ -267,7 +267,7 @@ by heat or interference. This can make calculating the probe's z-offset
|
||||
challenging, particularly at different bed temperatures. As such, some
|
||||
printers use an endstop for homing the Z axis and a probe for calibrating the
|
||||
mesh. In this configuration it is possible offset the mesh so that the (X, Y)
|
||||
`reference position` applies zero adjustment. The `reference postion` should
|
||||
`reference position` applies zero adjustment. The `reference position` should
|
||||
be the location on the bed where a
|
||||
[Z_ENDSTOP_CALIBRATE](./Manual_Level.md#calibrating-a-z-endstop)
|
||||
paper test is performed. The bed_mesh module provides the
|
||||
@@ -292,33 +292,6 @@ probe_count: 5, 3
|
||||
z-offset. Note that this coordinate must NOT be in a location specified as
|
||||
a `faulty_region` if a probe is necessary.
|
||||
|
||||
#### The deprecated relative_reference_index
|
||||
|
||||
Existing configurations using the `relative_reference_index` option must be
|
||||
updated to use the `zero_reference_position`. The response to the
|
||||
[BED_MESH_OUTPUT PGP=1](#output) gcode command will include the (X, Y)
|
||||
coordinate associated with the index; this position may be used as the value for
|
||||
the `zero_reference_position`. The output will look similar to the following:
|
||||
|
||||
```
|
||||
// bed_mesh: generated points
|
||||
// Index | Tool Adjusted | Probe
|
||||
// 0 | (1.0, 1.0) | (24.0, 6.0)
|
||||
// 1 | (36.7, 1.0) | (59.7, 6.0)
|
||||
// 2 | (72.3, 1.0) | (95.3, 6.0)
|
||||
// 3 | (108.0, 1.0) | (131.0, 6.0)
|
||||
... (additional generated points)
|
||||
// bed_mesh: relative_reference_index 24 is (131.5, 108.0)
|
||||
```
|
||||
|
||||
_Note: The above output is also printed in `klippy.log` during initialization._
|
||||
|
||||
Using the example above we see that the `relative_reference_index` is
|
||||
printed along with its coordinate. Thus the `zero_reference_position`
|
||||
is `131.5, 108`.
|
||||
|
||||
|
||||
|
||||
### Faulty Regions
|
||||
|
||||
It is possible for some areas of a bed to report inaccurate results when
|
||||
@@ -497,7 +470,8 @@ _Default Adaptive Margin: 0_
|
||||
|
||||
Initiates the probing procedure for Bed Mesh Calibration.
|
||||
|
||||
The mesh will be saved into a profile specified by the `PROFILE` parameter,
|
||||
The mesh will be immediately ready to use when the command completes and saved
|
||||
into a profile specified by the `PROFILE` parameter,
|
||||
or `default` if unspecified. The `METHOD` parameter takes one of the following
|
||||
values:
|
||||
|
||||
@@ -561,6 +535,10 @@ load the `default` profile it is recommended to add
|
||||
`BED_MESH_PROFILE LOAD=default` to either their `START_PRINT` macro or their
|
||||
slicer's "Start G-Code" configuration, whichever is applicable.
|
||||
|
||||
Note that this is not required if a new mesh is generated with
|
||||
`BED_MESH_CALIBRATE` in the `START_PRINT` macro or the slicer's "Start G-Code"
|
||||
and may produce unexpected results, especially with adaptive meshing.
|
||||
|
||||
Alternatively the old behavior of loading a profile at startup can be
|
||||
restored with a `[delayed_gcode]`:
|
||||
|
||||
|
||||
@@ -250,23 +250,22 @@ results were obtained by running an STM32F407 binary on an STM32F446
|
||||
|
||||
### STM32H7 step rate benchmark
|
||||
|
||||
The following configuration sequence is used on a STM32H743VIT6:
|
||||
The following configuration sequence is used on STM32H723:
|
||||
```
|
||||
allocate_oids count=3
|
||||
config_stepper oid=0 step_pin=PD4 dir_pin=PD3 invert_step=-1 step_pulse_ticks=0
|
||||
config_stepper oid=1 step_pin=PA15 dir_pin=PA8 invert_step=-1 step_pulse_ticks=0
|
||||
config_stepper oid=2 step_pin=PE2 dir_pin=PE3 invert_step=-1 step_pulse_ticks=0
|
||||
config_stepper oid=0 step_pin=PA13 dir_pin=PB5 invert_step=-1 step_pulse_ticks=52
|
||||
config_stepper oid=1 step_pin=PB2 dir_pin=PB6 invert_step=-1 step_pulse_ticks=52
|
||||
config_stepper oid=2 step_pin=PB3 dir_pin=PB7 invert_step=-1 step_pulse_ticks=52
|
||||
finalize_config crc=0
|
||||
```
|
||||
|
||||
The test was last run on commit `00191b5c` with gcc version
|
||||
`arm-none-eabi-gcc (15:8-2019-q3-1+b1) 8.3.1 20190703 (release)
|
||||
[gcc-8-branch revision 273027]`.
|
||||
The test was last run on commit `554ae78d` with gcc version
|
||||
`arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0`.
|
||||
|
||||
| stm32h7 | ticks |
|
||||
| stm32h723 | ticks |
|
||||
| -------------------- | ----- |
|
||||
| 1 stepper | 44 |
|
||||
| 3 stepper | 198 |
|
||||
| 1 stepper | 70 |
|
||||
| 3 stepper | 181 |
|
||||
|
||||
### STM32G0B1 step rate benchmark
|
||||
|
||||
@@ -287,6 +286,25 @@ The test was last run on commit `247cd753` with gcc version
|
||||
| 1 stepper | 58 |
|
||||
| 3 stepper | 243 |
|
||||
|
||||
### STM32G4 step rate benchmark
|
||||
|
||||
The following configuration sequence is used on the STM32G431:
|
||||
```
|
||||
allocate_oids count=3
|
||||
config_stepper oid=0 step_pin=PA0 dir_pin=PB5 invert_step=-1 step_pulse_ticks=17
|
||||
config_stepper oid=1 step_pin=PB2 dir_pin=PB6 invert_step=-1 step_pulse_ticks=17
|
||||
config_stepper oid=2 step_pin=PB3 dir_pin=PB7 invert_step=-1 step_pulse_ticks=17
|
||||
finalize_config crc=0
|
||||
```
|
||||
|
||||
The test was last run on commit `cfa48fe3` with gcc version
|
||||
`arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0`.
|
||||
|
||||
| stm32g431 | ticks |
|
||||
| ---------------- | ----- |
|
||||
| 1 stepper | 47 |
|
||||
| 3 stepper | 208 |
|
||||
|
||||
### LPC176x step rate benchmark
|
||||
|
||||
The following configuration sequence is used on the LPC176x:
|
||||
@@ -423,7 +441,7 @@ Pico and Pico 2 boards.
|
||||
|
||||
(*) Note that the reported rp2040 ticks are relative to a 12Mhz
|
||||
scheduling timer and do not correspond to its 200Mhz internal ARM
|
||||
processing rate. It is expected that 5 scheduling ticks corresponds to
|
||||
processing rate. It is expected that 3 scheduling ticks corresponds to
|
||||
~42 ARM core cycles and 14 scheduling ticks corresponds to ~225 ARM
|
||||
core cycles.
|
||||
|
||||
@@ -464,18 +482,23 @@ When the test completes, determine the difference between the clocks
|
||||
reported in the two "uptime" response messages. The total number of
|
||||
commands per second is then `100000 * mcu_frequency / clock_diff`.
|
||||
|
||||
Note that this test may saturate the USB/CPU capacity of a Raspberry
|
||||
Pi. If running on a Raspberry Pi, Beaglebone, or similar host computer
|
||||
then increase the delay (eg, `DELAY {clock + 20*freq} get_uptime`).
|
||||
Where applicable, the benchmarks below are with console.py running on
|
||||
a desktop class machine with the device connected via a high-speed
|
||||
hub.
|
||||
The USB tests may exceed the CPU capacity of a Raspberry Pi. If
|
||||
running on a Raspberry Pi, Beaglebone, or similar host computer then
|
||||
increase the delay (eg, `DELAY {clock + 20*freq} get_uptime`). Where
|
||||
applicable, the benchmarks below are with console.py running on a
|
||||
desktop class machine with the device connected via a super-speed hub.
|
||||
|
||||
The CAN bus tests may saturate the USB host controller of a Raspberry
|
||||
Pi (when testing via a standard gs_usb USB to CAN bus adapter). Where
|
||||
applicable, the CAN bus benchmarks below are with console.py running
|
||||
on a desktop class machine with a USB to CAN bus adapter connected via
|
||||
a super-speed USB hub.
|
||||
|
||||
| MCU | Rate | Build | Build compiler |
|
||||
| ------------------- | ---- | -------- | ------------------- |
|
||||
| stm32f042 (CAN) | 18K | c105adc8 | arm-none-eabi-gcc (GNU Tools 7-2018-q3-update) 7.3.1 |
|
||||
| atmega2560 (serial) | 23K | b161a69e | avr-gcc (GCC) 4.8.1 |
|
||||
| sam3x8e (serial) | 23K | b161a69e | arm-none-eabi-gcc (Fedora 7.1.0-5.fc27) 7.1.0 |
|
||||
| rp2350 (CAN) | 59K | 17b8ce4c | arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0 |
|
||||
| at90usb1286 (USB) | 75K | 01d2183f | avr-gcc (GCC) 5.4.0 |
|
||||
| ar100 (serial) | 138K | 08d037c6 | or1k-linux-musl-gcc 9.3.0 |
|
||||
| samd21 (USB) | 223K | 01d2183f | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 |
|
||||
|
||||
@@ -194,7 +194,7 @@ Alternatively, one can use a
|
||||
|
||||
When using OpenOCD with the SAMC21, extra steps must be taken to first
|
||||
put the chip into Cold Plugging mode if the board makes use of the
|
||||
SWD pins for other purposes. If using OpenOCD on a Rasberry Pi, this
|
||||
SWD pins for other purposes. If using OpenOCD on a Raspberry Pi, this
|
||||
can be done by running the following commands before invoking OpenOCD.
|
||||
```
|
||||
SWCLK=25
|
||||
|
||||
@@ -125,10 +125,14 @@ iface can0 can static
|
||||
frequency. As a result, it is recommended to use a CAN bus frequency
|
||||
of 1000000 when using "USB to CAN bus bridge mode".
|
||||
|
||||
Even at a CAN bus frequency of 1000000, there may not be sufficient
|
||||
bandwidth to run a `SHAPER_CALIBRATE` test if both the XY steppers
|
||||
and the accelerometer all communicate via a single "USB to CAN bus"
|
||||
interface.
|
||||
* It is only valid to use USB to CAN bridge mode if there is a
|
||||
functioning CAN bus with at least one other node available (in
|
||||
addition to the bridge node itself). Use a standard USB
|
||||
configuration if the goal is to communicate only with the single USB
|
||||
device. Using USB to CAN bridge mode without a fully functioning CAN
|
||||
bus (including terminating resistors and an additional node) may
|
||||
result in sporadic errors even when communicating with the bridge
|
||||
node.
|
||||
|
||||
* A USB to CAN bridge board will not appear as a USB serial device, it
|
||||
will not show up when running `ls /dev/serial/by-id`, and it can not
|
||||
|
||||
@@ -118,6 +118,23 @@ necessary to increase the `txqueuelen` above the recommended value
|
||||
of 128. However, as above, care should be taken when selecting a new
|
||||
value to avoid excessive round-trip-time latency.
|
||||
|
||||
## Use `canbus_query.py` only to identify nodes never previously seen
|
||||
|
||||
It is only valid to use the
|
||||
[`canbus_query.py` tool](CANBUS.md#finding-the-canbus_uuid-for-new-micro-controllers)
|
||||
to identify micro-controllers that have never been previously
|
||||
identified. Once all nodes on a bus are identified, record the
|
||||
resulting uuids in the printer.cfg, and avoid running the tool
|
||||
unnecessarily.
|
||||
|
||||
The tool is implemented using a low-level mechanism that can cause
|
||||
nodes to internally observe bus errors. These internal errors may
|
||||
result in communication interruptions and may result is some nodes
|
||||
disconnecting from the bus.
|
||||
|
||||
It is not valid to use the tool to "ping" if a node is connected. Do
|
||||
not run the tool during an active print.
|
||||
|
||||
## Obtaining candump logs
|
||||
|
||||
The CAN bus messages sent to and from the micro-controller are handled
|
||||
|
||||
@@ -323,7 +323,7 @@ a month without updates.
|
||||
|
||||
Once the requirements are met, you need to:
|
||||
|
||||
1. update klipper-tranlations repository
|
||||
1. update klipper-translations repository
|
||||
[active_translations](https://github.com/Klipper3d/klipper-translations/blob/translations/active_translations)
|
||||
2. Optional: add a manual-index.md file in klipper-translations repository's
|
||||
`docs\locals\<lang>` folder to replace the language specific index.md (generated
|
||||
|
||||
@@ -286,6 +286,11 @@ The following may also be useful:
|
||||
during the `load_config()` or "connect event" phases. Use either
|
||||
`raise config.error("my error")` or `raise printer.config_error("my
|
||||
error")` to report the error.
|
||||
* Do not store a reference to the `config` object in a class member
|
||||
variable (nor in any similar location that may persist past initial
|
||||
module loading). The `config` object is a reference to a "config
|
||||
loading phase" class and it is not valid to invoke its methods after
|
||||
the "config loading phase" has completed.
|
||||
* Use the "pins" module to configure a pin on a micro-controller. This
|
||||
is typically done with something similar to
|
||||
`printer.lookup_object("pins").setup_pin("pwm",
|
||||
|
||||
@@ -8,6 +8,34 @@ All dates in this document are approximate.
|
||||
|
||||
## Changes
|
||||
|
||||
20250811: Support for the `max_accel_to_decel` parameter in the
|
||||
`[printer]` config section has been removed and support for the
|
||||
`ACCEL_TO_DECEL` parameter in the `SET_VELOCITY_LIMIT` command has
|
||||
been removed. These capabilities were deprecated on 20240313.
|
||||
|
||||
20250721: The `[pca9632]` and `[mcp4018]` modules no longer accept the
|
||||
`scl_pin` and `sda_pin` options. Use `i2c_software_scl_pin` and
|
||||
`i2c_software_sda_pin` instead.
|
||||
|
||||
20250428: The maximum `cycle_time` for pwm `[output_pin]`,
|
||||
`[pwm_cycle_time]`, `[pwm_tool]`, and similar config sections is now 3
|
||||
seconds (reduced from 5 seconds). The `maximum_mcu_duration` in
|
||||
`[pwm_tool]` is now also 3 seconds.
|
||||
|
||||
20250418: The manual_stepper `STOP_ON_ENDSTOP` feature may now take
|
||||
less time to complete. Previously, the command would wait the entire
|
||||
time the move could possibly take even if the endstop triggered
|
||||
earlier. Now, the command finishes shortly after the endstop trigger.
|
||||
|
||||
20250417: SPI devices using "software SPI" are now rate limited.
|
||||
Previously, the `spi_speed` in the config was ignored and the
|
||||
transmission speed was only limited by the processing speed of the
|
||||
micro-controller. Now, speeds are limited by the `spi_speed` config
|
||||
parameter (actual hardware speeds are likely to be lower than the
|
||||
configured value due to software overhead).
|
||||
|
||||
20250411: Klipper v0.13.0 released.
|
||||
|
||||
20250308: The `AUTO` parameter of the
|
||||
`AXIS_TWIST_COMPENSATION_CALIBRATE` command has been removed.
|
||||
|
||||
@@ -39,7 +67,7 @@ object were issued faster than the minimum scheduling time (typically
|
||||
100ms) then actual updates could be queued far into the future. Now if
|
||||
many updates are issued in rapid succession then it is possible that
|
||||
only the latest request will be applied. If the previous behavior is
|
||||
requried then consider adding explicit `G4` delay commands between
|
||||
required then consider adding explicit `G4` delay commands between
|
||||
updates.
|
||||
|
||||
20240912: Support for `maximum_mcu_duration` and `static_value`
|
||||
@@ -112,7 +140,7 @@ carriage are exported as `printer.dual_carriage.carriage_0` and
|
||||
`printer.dual_carriage.carriage_1`.
|
||||
|
||||
20230619: The `relative_reference_index` option has been deprecated
|
||||
and superceded by the `zero_reference_position` option. Refer to the
|
||||
and superseded by the `zero_reference_position` option. Refer to the
|
||||
[Bed Mesh Documentation](./Bed_Mesh.md#the-deprecated-relative_reference_index)
|
||||
for details on how to update the configuration. With this deprecation
|
||||
the `RELATIVE_REFERENCE_INDEX` is no longer available as a parameter
|
||||
@@ -346,7 +374,7 @@ endstop phases by running the ENDSTOP_PHASE_CALIBRATE command.
|
||||
`gear_ratio` for their rotary steppers, and they may no longer specify
|
||||
a `step_distance` parameter. See the
|
||||
[config reference](Config_Reference.md#stepper) for the format of the
|
||||
new gear_ratio paramter.
|
||||
new gear_ratio parameter.
|
||||
|
||||
20201213: It is not valid to specify a Z "position_endstop" when using
|
||||
"probe:z_virtual_endstop". An error will now be raised if a Z
|
||||
|
||||
@@ -84,8 +84,9 @@ The printer section controls high level printer settings.
|
||||
[printer]
|
||||
kinematics:
|
||||
# The type of printer in use. This option may be one of: cartesian,
|
||||
# corexy, corexz, hybrid_corexy, hybrid_corexz, rotary_delta, delta,
|
||||
# deltesian, polar, winch, or none. This parameter must be specified.
|
||||
# corexy, corexz, hybrid_corexy, hybrid_corexz, generic_cartesian,
|
||||
# rotary_delta, delta, deltesian, polar, winch, or none.
|
||||
# This parameter must be specified.
|
||||
max_velocity:
|
||||
# Maximum velocity (in mm/s) of the toolhead (relative to the
|
||||
# print). This value may be changed at runtime using the
|
||||
@@ -125,8 +126,6 @@ max_accel:
|
||||
# decelerate to zero at each corner. The value specified here may be
|
||||
# changed at runtime using the SET_VELOCITY_LIMIT command. The
|
||||
# default is 5mm/s.
|
||||
#max_accel_to_decel:
|
||||
# This parameter is deprecated and should no longer be used.
|
||||
```
|
||||
|
||||
### [stepper]
|
||||
@@ -712,6 +711,171 @@ anchor_z:
|
||||
# These parameters must be provided.
|
||||
```
|
||||
|
||||
### Generic Cartesian Kinematics
|
||||
|
||||
See [example-generic-cartesian.cfg](../config/example-generic-caretesian.cfg)
|
||||
for an example generic Cartesian kinematics config file.
|
||||
|
||||
This printer kinematic class allows a user to define in a pretty flexible
|
||||
manner an arbitrary Cartesian-style kinematics. In principle, the regular
|
||||
cartesian, corexy, hybrid_corexy can be defined this way too. However,
|
||||
more importantly, various otherwise unsupported kinematics such as
|
||||
inverted hybrid_corexy or corexyuv can be defined using this kinematic.
|
||||
|
||||
Notably, the definition of a generic Cartesian kinematic deviates
|
||||
significantly from the other kinematic types. It follows the following
|
||||
convention: a user defines a set of carriages with certain range of motion
|
||||
that can move independently from each other (they should move over the
|
||||
Cartesian axes X, Y, and Z, hence the name of the kinematic) and
|
||||
corresponding endstops that allow the firmware to determine the position
|
||||
of carriages during homing, as well as a set of steppers that move those
|
||||
carriages. The `[printer]` section must specify the kinematic and
|
||||
other printer-level settings same as the regular Cartesian kinematic:
|
||||
```
|
||||
[printer]
|
||||
kinematics: generic_cartesian
|
||||
max_velocity:
|
||||
max_accel:
|
||||
#minimum_cruise_ratio:
|
||||
#square_corner_velocity:
|
||||
#max_z_velocity:
|
||||
#max_z_accel:
|
||||
|
||||
```
|
||||
|
||||
Then a user must define the following three carriages: `[carriage x]`,
|
||||
`[carriage y]`, and `[carriage z]`, e.g.
|
||||
```
|
||||
[carriage x]
|
||||
endstop_pin:
|
||||
# Endstop switch detection pin. If this endstop pin is on a
|
||||
# different mcu than the stepper motor(s) moving this carriage,
|
||||
# then it enables "multi-mcu homing". This parameter must be provided.
|
||||
#position_min: 0
|
||||
# Minimum valid distance (in mm) the user may command the carriage to
|
||||
# move to. The default is 0mm.
|
||||
position_endstop:
|
||||
# Location of the endstop (in mm). This parameter must be provided.
|
||||
position_max:
|
||||
# Maximum valid distance (in mm) the user may command the stepper to
|
||||
# move to. This parameter must be provided.
|
||||
#homing_speed: 5.0
|
||||
# Maximum velocity (in mm/s) of the carriage when homing. The default
|
||||
# is 5mm/s.
|
||||
#homing_retract_dist: 5.0
|
||||
# Distance to backoff (in mm) before homing a second time during
|
||||
# homing. Set this to zero to disable the second home. The default
|
||||
# is 5mm.
|
||||
#homing_retract_speed:
|
||||
# Speed to use on the retract move after homing in case this should
|
||||
# be different from the homing speed, which is the default for this
|
||||
# parameter
|
||||
#second_homing_speed:
|
||||
# Velocity (in mm/s) of the carriage when performing the second home.
|
||||
# The default is homing_speed/2.
|
||||
#homing_positive_dir:
|
||||
# If true, homing will cause the carriage to move in a positive
|
||||
# direction (away from zero); if false, home towards zero. It is
|
||||
# better to use the default than to specify this parameter. The
|
||||
# default is true if position_endstop is near position_max and false
|
||||
# if near position_min.
|
||||
```
|
||||
|
||||
Afterwards, a user specifies the stepper motors that move these carriages,
|
||||
for instance
|
||||
```
|
||||
[stepper my_stepper]
|
||||
carriages:
|
||||
# A string describing the carriages the stepper moves. All defined
|
||||
# carriages can be specified here, as well as their linear combinations,
|
||||
# e.g. x, x+y, y-0.5*z, x-z, etc. This parameter must be provided.
|
||||
step_pin:
|
||||
dir_pin:
|
||||
enable_pin:
|
||||
rotation_distance:
|
||||
microsteps:
|
||||
#full_steps_per_rotation: 200
|
||||
#gear_ratio:
|
||||
#step_pulse_duration:
|
||||
```
|
||||
See [stepper](#stepper) section for more information on the regular
|
||||
stepper parameters. The `carriages` parameter defines how the stepper
|
||||
affects the motion of the carriages. For example, `x+y` indicates that
|
||||
the motion of the stepper in the positive direction by the distance `d`
|
||||
moves the carriages `x` and `y` by the same distance `d` in the positive
|
||||
direction, while `x-0.5*y` means the motion of the stepper in the positive
|
||||
direction by the distance `d` moves the carriage `x` by the distance `d`
|
||||
in the positive direction, but the carriage `y` will travel distance `d/2`
|
||||
in the negative direction.
|
||||
|
||||
More than a single stepper motor can be defined to drive the same axis
|
||||
or belt. For example, on a CoreXY AWD setups two motors driving the same
|
||||
belt can be defined as
|
||||
```
|
||||
[carriage x]
|
||||
endstop_pin: ...
|
||||
...
|
||||
|
||||
[carriage y]
|
||||
endstop_pin: ...
|
||||
...
|
||||
|
||||
[stepper a0]
|
||||
carriages: x-y
|
||||
step_pin: ...
|
||||
dir_pin: ...
|
||||
enable_pin: ...
|
||||
rotation_distance: ...
|
||||
...
|
||||
|
||||
[stepper a1]
|
||||
carriages: x-y
|
||||
step_pin: ...
|
||||
dir_pin: ...
|
||||
enable_pin: ...
|
||||
rotation_distance: ...
|
||||
...
|
||||
```
|
||||
with `a0` and `a1` steppers having their own control pins, but
|
||||
sharing the same `carriages` and corresponding endstops.
|
||||
|
||||
There are situations when a user wants to have more than one endstop
|
||||
per axis. Examples of such configurations include Y axis driven by
|
||||
two independent stepper motors with belts attached to both ends of the
|
||||
X beam, with effectively two carriages on Y axis each having an
|
||||
independent endstop, and multi-stepper Z axis with each stepper having
|
||||
its own endstop (not to be confused with the configurations with
|
||||
multiple Z motors but only a single endstop). These configurations
|
||||
can be declared by specifying additional carriage(s) with their endstops:
|
||||
|
||||
```
|
||||
[extra_carriage my_carriage]
|
||||
primary_carriage:
|
||||
# The name of the primary carriage this carriage corresponds to.
|
||||
# It also effectively defines the axis the carriage moves over.
|
||||
# This parameter must be provided.
|
||||
endstop_pin:
|
||||
# Endstop switch detection pin. This parameter must be provided.
|
||||
```
|
||||
|
||||
and the corresponding stepper motors, for example:
|
||||
```
|
||||
[extra_carriage y1]
|
||||
primary_carriage: y
|
||||
endstop_pin: ...
|
||||
|
||||
[stepper sy1]
|
||||
carriages: y1
|
||||
...
|
||||
```
|
||||
Notably, an `[extra_carriage]` does not define parameters such as
|
||||
`position_min`, `position_max`, and `position_endstop`, but instead
|
||||
inherits them from the specified `primary_carriage`, thus sharing
|
||||
the same range of motion with the primary carriage.
|
||||
|
||||
For the references on how to configure IDEX setups, see the
|
||||
[dual carriage](#dual-carriage) section.
|
||||
|
||||
### None Kinematics
|
||||
|
||||
It is possible to define a special "none" kinematics to disable
|
||||
@@ -2207,8 +2371,8 @@ for an example configuration.
|
||||
|
||||
### [dual_carriage]
|
||||
|
||||
Support for cartesian and hybrid_corexy/z printers with dual carriages
|
||||
on a single axis. The carriage mode can be set via the
|
||||
Support for cartesian, generic_cartesian and hybrid_corexy/z printers with
|
||||
dual carriages on a single axis. The carriage mode can be set via the
|
||||
SET_DUAL_CARRIAGE extended g-code command. For example,
|
||||
"SET_DUAL_CARRIAGE CARRIAGE=1" command will activate the carriage defined
|
||||
in this section (CARRIAGE=0 will return activation to the primary carriage).
|
||||
@@ -2235,7 +2399,7 @@ typically be achieved with
|
||||
or a similar command.
|
||||
|
||||
See [sample-idex.cfg](../config/sample-idex.cfg) for an example
|
||||
configuration.
|
||||
configuration with a regular Cartesian kinematic.
|
||||
|
||||
```
|
||||
[dual_carriage]
|
||||
@@ -2249,7 +2413,7 @@ axis:
|
||||
# error. If safe_distance is not provided, it will be inferred from
|
||||
# position_min and position_max for the dual and primary carriages. If set
|
||||
# to 0 (or safe_distance is unset and position_min and position_max are
|
||||
# identical for the primary and dual carraiges), the carriages proximity
|
||||
# identical for the primary and dual carriages), the carriages proximity
|
||||
# checks will be disabled.
|
||||
#step_pin:
|
||||
#dir_pin:
|
||||
@@ -2263,6 +2427,83 @@ axis:
|
||||
# See the "stepper" section for the definition of the above parameters.
|
||||
```
|
||||
|
||||
For an example of dual carriage configuration with `generic_cartesian`
|
||||
kinematic, see the following configuration
|
||||
[sample](../config/example-generic-caretesian.cfg).
|
||||
Please note that in this case the `[dual_carriage]` configuration deviates
|
||||
from the configuration described above:
|
||||
```
|
||||
[dual_carriage my_dc_carriage]
|
||||
primary_carriage:
|
||||
# Defines the matching primary carriage of this dual carriage and
|
||||
# the corresponding IDEX axis. Valid choices are x, y, z.
|
||||
# This parameter must be provided.
|
||||
#safe_distance:
|
||||
# The minimum distance (in mm) to enforce between the dual and the primary
|
||||
# carriages. If a G-Code command is executed that will bring the carriages
|
||||
# closer than the specified limit, such a command will be rejected with an
|
||||
# error. If safe_distance is not provided, it will be inferred from
|
||||
# position_min and position_max for the dual and primary carriages. If set
|
||||
# to 0 (or safe_distance is unset and position_min and position_max are
|
||||
# identical for the primary and dual carriages), the carriages proximity
|
||||
# checks will be disabled.
|
||||
endstop_pin:
|
||||
#position_min:
|
||||
position_endstop:
|
||||
position_max:
|
||||
#homing_speed:
|
||||
#homing_retract_dist:
|
||||
#homing_retract_speed:
|
||||
#second_homing_speed:
|
||||
#homing_positive_dir:
|
||||
...
|
||||
```
|
||||
Refer to [generic cartesian](#generic-cartesian) section for more information
|
||||
on the regular `carriage` parameters.
|
||||
|
||||
Then a user must define one or more stepper motors moving the dual carriage
|
||||
(and other carriages as appropriate), for instance
|
||||
```
|
||||
[carriage x]
|
||||
...
|
||||
|
||||
[carriage y]
|
||||
...
|
||||
|
||||
[dual_carriage u]
|
||||
primary_carriage: x
|
||||
...
|
||||
|
||||
[stepper dc_stepper]
|
||||
carriages: u-y
|
||||
...
|
||||
```
|
||||
|
||||
`[dual_carriage]` requires special configuration for the input shaper.
|
||||
In general, it is necessary to run input shaper calibration twice -
|
||||
for the `dual_carriage` and its `primary_carriage` for the axis they
|
||||
share. Then the input shaper can be configured as follows, assuming the
|
||||
example above:
|
||||
```
|
||||
[input_shaper]
|
||||
# Intentionally empty
|
||||
|
||||
[delayed_gcode init_shaper]
|
||||
initial_duration: 0.1
|
||||
gcode:
|
||||
SET_DUAL_CARRIAGE CARRIAGE=u
|
||||
SET_INPUT_SHAPER SHAPER_TYPE_X=<dual_carriage_x_shaper> SHAPER_FREQ_X=<dual_carriage_x_freq> SHAPER_TYPE_Y=<y_shaper> SHAPER_FREQ_Y=<y_freq>
|
||||
SET_DUAL_CARRIAGE CARRIAGE=x
|
||||
SET_INPUT_SHAPER SHAPER_TYPE_X=<primary_carriage_x_shaper> SHAPER_FREQ_X=<primary_carriage_x_freq> SHAPER_TYPE_Y=<y_shaper> SHAPER_FREQ_Y=<y_freq>
|
||||
```
|
||||
Note that `SHAPER_TYPE_Y` and `SHAPER_FREQ_Y` must be the same in both
|
||||
commands in this case, since the same motors drive Y axis when either
|
||||
of the `x` and `u` carriages are active.
|
||||
|
||||
It is worth noting that `generic_cartesian` kinematic can support two
|
||||
dual carriages for X and Y axes. For reference, see for instance a
|
||||
[sample](../config/sample-corexyuv.cfg) of CoreXYUV configuration.
|
||||
|
||||
### [extruder_stepper]
|
||||
|
||||
Support for additional steppers synchronized to the movement of an
|
||||
@@ -2317,6 +2558,13 @@ printer kinematics.
|
||||
# Endstop switch detection pin. If specified, then one may perform
|
||||
# "homing moves" by adding a STOP_ON_ENDSTOP parameter to
|
||||
# MANUAL_STEPPER movement commands.
|
||||
#position_min:
|
||||
#position_max:
|
||||
# The minimum and maximum position the stepper can be commanded to
|
||||
# move to. If specified then one may not command the stepper to move
|
||||
# past the given position. Note that these limits do not prevent
|
||||
# setting an arbitrary position with the `MANUAL_STEPPER
|
||||
# SET_POSITION=x` command. The default is to not enforce a limit.
|
||||
```
|
||||
|
||||
## Custom heaters and sensors
|
||||
@@ -3226,11 +3474,6 @@ PCA9632 LED support. The PCA9632 is used on the FlashForge Dreamer.
|
||||
#i2c_speed:
|
||||
# See the "common I2C settings" section for a description of the
|
||||
# above parameters.
|
||||
#scl_pin:
|
||||
#sda_pin:
|
||||
# Alternatively, if the pca9632 is not connected to a hardware I2C
|
||||
# bus, then one may specify the "clock" (scl_pin) and "data"
|
||||
# (sda_pin) pins. The default is to use hardware I2C.
|
||||
#color_order: RGBW
|
||||
# Set the pixel order of the LED (using a string containing the
|
||||
# letters R, G, B, W). The default is RGBW.
|
||||
@@ -3533,6 +3776,7 @@ run_current:
|
||||
#driver_PWM_FREQ: 1
|
||||
#driver_PWM_GRAD: 4
|
||||
#driver_PWM_AMPL: 128
|
||||
#driver_FREEWHEEL: 0
|
||||
#driver_SGT: 0
|
||||
#driver_SEMIN: 0
|
||||
#driver_SEUP: 0
|
||||
@@ -3613,6 +3857,7 @@ run_current:
|
||||
#driver_PWM_FREQ: 1
|
||||
#driver_PWM_GRAD: 14
|
||||
#driver_PWM_OFS: 36
|
||||
#driver_FREEWHEEL: 0
|
||||
# Set the given register during the configuration of the TMC2208
|
||||
# chip. This may be used to set custom motor parameters. The
|
||||
# defaults for each parameter are next to the parameter name in the
|
||||
@@ -3662,6 +3907,7 @@ run_current:
|
||||
#driver_PWM_FREQ: 1
|
||||
#driver_PWM_GRAD: 14
|
||||
#driver_PWM_OFS: 36
|
||||
#driver_FREEWHEEL: 0
|
||||
#driver_SGTHRS: 0
|
||||
#driver_SEMIN: 0
|
||||
#driver_SEUP: 0
|
||||
@@ -4143,16 +4389,21 @@ prefix).
|
||||
|
||||
### [mcp4018]
|
||||
|
||||
Statically configured MCP4018 digipot connected via two gpio "bit
|
||||
banging" pins (one may define any number of sections with an "mcp4018"
|
||||
prefix).
|
||||
Statically configured MCP4018 digipot connected via i2c (one may
|
||||
define any number of sections with an "mcp4018" prefix).
|
||||
|
||||
```
|
||||
[mcp4018 my_digipot]
|
||||
scl_pin:
|
||||
# The SCL "clock" pin. This parameter must be provided.
|
||||
sda_pin:
|
||||
# The SDA "data" pin. This parameter must be provided.
|
||||
#i2c_address: 47
|
||||
# The i2c address that the chip is using on the i2c bus. The default
|
||||
# is 47.
|
||||
#i2c_mcu:
|
||||
#i2c_bus:
|
||||
#i2c_software_scl_pin:
|
||||
#i2c_software_sda_pin:
|
||||
#i2c_speed:
|
||||
# See the "common I2C settings" section for a description of the
|
||||
# above parameters.
|
||||
wiper:
|
||||
# The value to statically set the given MCP4018 "wiper" to. This is
|
||||
# typically set to a number between 0.0 and 1.0 with 1.0 being the
|
||||
@@ -4895,6 +5146,65 @@ data_ready_pin:
|
||||
# and 'analog_supply'. Default is 'internal'.
|
||||
```
|
||||
|
||||
### [load_cell_probe]
|
||||
Load Cell Probe. This combines the functionality of a [probe] and a [load_cell].
|
||||
|
||||
```
|
||||
[load_cell_probe]
|
||||
sensor_type:
|
||||
# This must be one of the supported bulk ADC sensor types and support
|
||||
# load cell endstops on the mcu.
|
||||
#counts_per_gram:
|
||||
#reference_tare_counts:
|
||||
#sensor_orientation:
|
||||
# These parameters must be configured before the probe will operate.
|
||||
# See the [load_cell] section for further details.
|
||||
#force_safety_limit: 2000
|
||||
# The safe limit for probing force relative to the reference_tare_counts on
|
||||
# the load_cell. The default is +/-2Kg.
|
||||
#trigger_force: 75.0
|
||||
# The force that the probe will trigger at. 75g is the default.
|
||||
#drift_filter_cutoff_frequency: 0.8
|
||||
# Enable optional continuous taring while homing & probing to reject drift.
|
||||
# The value is a frequency, in Hz, below which drift will be ignored. This
|
||||
# option requires the SciPy library. Default: None
|
||||
#drift_filter_delay: 2
|
||||
# The delay, or 'order', of the drift filter. This controls the number of
|
||||
# samples required to make a trigger detection. Can be 1 or 2, the default
|
||||
# is 2.
|
||||
#buzz_filter_cutoff_frequency: 100.0
|
||||
# The value is a frequency, in Hz, above which high frequency noise in the
|
||||
# load cell will be igfiltered outnored. This option requires the SciPy
|
||||
# library. Default: None
|
||||
#buzz_filter_delay: 2
|
||||
# The delay, or 'order', of the buzz filter. This controls the number of
|
||||
# samples required to make a trigger detection. Can be 1 or 2, the default
|
||||
# is 2.
|
||||
#notch_filter_frequencies: 50, 60
|
||||
# 1 or 2 frequencies, in Hz, to filter out of the load cell data. This is
|
||||
# intended to reject power line noise. This option requires the SciPy
|
||||
# library. Default: None
|
||||
#notch_filter_quality: 2.0
|
||||
# Controls how narrow the range of frequencies are that the notch filter
|
||||
# removes. Larger numbers produce a narrower filter. Minimum value is 0.5 and
|
||||
# maximum is 3.0. Default: 2.0
|
||||
#tare_time:
|
||||
# The rime in seconds used for taring the load_cell before each probe. The
|
||||
# default value is: 4 / 60 = 0.066. This collects samples from 4 cycles of
|
||||
# 60Hz mains power to cancel power line noise.
|
||||
#z_offset:
|
||||
#speed:
|
||||
#samples:
|
||||
#sample_retract_dist:
|
||||
#lift_speed:
|
||||
#samples_result:
|
||||
#samples_tolerance:
|
||||
#samples_tolerance_retries:
|
||||
#activate_gcode:
|
||||
#deactivate_gcode:
|
||||
# See the "[probe]" section for a description of the above parameters.
|
||||
```
|
||||
|
||||
## Board specific hardware support
|
||||
|
||||
### [sx1509]
|
||||
@@ -5001,7 +5311,7 @@ chip: ADS1115
|
||||
# scales all values read from the ADC. Options are: 6.144V, 4.096V, 2.048V,
|
||||
# 1.024V, 0.512V, 0.256V
|
||||
#adc_voltage: 3.3
|
||||
# The suppy voltage for the device. This allows additional software scaling
|
||||
# The supply voltage for the device. This allows additional software scaling
|
||||
# for all values read from the ADC.
|
||||
i2c_mcu: host
|
||||
i2c_bus: i2c.1
|
||||
@@ -5020,7 +5330,7 @@ sensor_pin: my_ads1x1x:AIN0
|
||||
# A combination of the name of the ads1x1x chip and the pin. Possible
|
||||
# pin values are AIN0, AIN1, AIN2 and AIN3 for single ended lines and
|
||||
# DIFF01, DIFF03, DIFF13 and DIFF23 for differential between their
|
||||
# correspoding lines. For example
|
||||
# corresponding lines. For example
|
||||
# DIFF03 measures the differential between line 0 and 3. Only specific
|
||||
# combinations for the differentials are allowed.
|
||||
```
|
||||
|
||||
@@ -190,12 +190,13 @@ represent total number of steps per second on the micro-controller.
|
||||
| SAM4E8E | 2500K | 1674K |
|
||||
| SAMD51 | 3077K | 1885K |
|
||||
| AR100 | 3529K | 2507K |
|
||||
| STM32G431 | 3617K | 2452K |
|
||||
| STM32F407 | 3652K | 2459K |
|
||||
| STM32F446 | 3913K | 2634K |
|
||||
| RP2040 | 4000K | 2571K |
|
||||
| RP2350 | 4167K | 2663K |
|
||||
| SAME70 | 6667K | 4737K |
|
||||
| STM32H743 | 9091K | 6061K |
|
||||
| STM32H723 | 7429K | 8619K |
|
||||
|
||||
If unsure of the micro-controller on a particular board, find the
|
||||
appropriate [config file](../config/), and look for the
|
||||
|
||||
124
docs/G-Codes.md
124
docs/G-Codes.md
@@ -174,8 +174,10 @@ The following commands are available when the
|
||||
[ADAPTIVE_MARGIN=<value>]`: This command probes the bed using generated points
|
||||
specified by the parameters in the config. After probing, a mesh is generated
|
||||
and z-movement is adjusted according to the mesh.
|
||||
The mesh is immediately active after successful completion of `BED_MESH_CALIBRATE`.
|
||||
The mesh will be saved into a profile specified by the `PROFILE` parameter,
|
||||
or `default` if unspecified.
|
||||
or `default` if unspecified. If ADAPTIVE=1 is specified then the profile
|
||||
name will begin with `adaptive-` and should not be saved for reuse.
|
||||
See the PROBE command for details on the optional probe parameters. If
|
||||
METHOD=manual is specified then the manual probing tool is activated - see the
|
||||
MANUAL_PROBE command above for details on the additional commands available
|
||||
@@ -341,15 +343,18 @@ The following command is available when the
|
||||
enabled.
|
||||
|
||||
#### SET_DUAL_CARRIAGE
|
||||
`SET_DUAL_CARRIAGE CARRIAGE=[0|1] [MODE=[PRIMARY|COPY|MIRROR]]`:
|
||||
`SET_DUAL_CARRIAGE CARRIAGE=<carriage> [MODE=[PRIMARY|COPY|MIRROR]]`:
|
||||
This command will change the mode of the specified carriage.
|
||||
If no `MODE` is provided it defaults to `PRIMARY`. Setting the mode
|
||||
to `PRIMARY` deactivates the other carriage and makes the specified
|
||||
carriage execute subsequent G-Code commands as-is. `COPY` and `MIRROR`
|
||||
modes are supported only for `CARRIAGE=1`. When set to either of these
|
||||
modes, carriage 1 will then track the subsequent moves of the carriage 0
|
||||
and either copy relative movements of it (in `COPY` mode) or execute them
|
||||
in the opposite (mirror) direction (in `MIRROR` mode).
|
||||
If no `MODE` is provided it defaults to `PRIMARY`. `<carriage>` must
|
||||
reference a defined primary or dual carriage for `generic_cartesian`
|
||||
kinematics or be 0 (for primary carriage) or 1 (for dual carriage)
|
||||
for all other kinematics supporting IDEX. Setting the mode to `PRIMARY`
|
||||
deactivates the other carriage and makes the specified carriage execute
|
||||
subsequent G-Code commands as-is. `COPY` and `MIRROR` modes are supported
|
||||
only for dual carriages. When set to either of these modes, dual carriage
|
||||
will then track the subsequent moves of its primary carriage and either
|
||||
copy relative movements of it (in `COPY` mode) or execute them in the
|
||||
opposite (mirror) direction (in `MIRROR` mode).
|
||||
|
||||
#### SAVE_DUAL_CARRIAGE_STATE
|
||||
`SAVE_DUAL_CARRIAGE_STATE [NAME=<state_name>]`: Save the current positions
|
||||
@@ -367,7 +372,7 @@ restored and "MOVE_SPEED" is specified, then the toolhead moves will be
|
||||
performed with the given speed (in mm/s); otherwise the toolhead move will
|
||||
use the rail homing speed. Note that the carriages restore their positions
|
||||
only over their own axis, which may be necessary to correctly restore COPY
|
||||
and MIRROR mode of the dual carraige.
|
||||
and MIRROR mode of the dual carriage.
|
||||
|
||||
### [endstop_phase]
|
||||
|
||||
@@ -715,6 +720,46 @@ is specified then the toolhead move will be performed with the given
|
||||
speed (in mm/s); otherwise the toolhead move will use the restored
|
||||
g-code speed.
|
||||
|
||||
### [generic_cartesian]
|
||||
The commands in this section become automatically available when
|
||||
`kinematics: generic_cartesian` is specified as the printer kinematics.
|
||||
|
||||
#### SET_STEPPER_CARRIAGES
|
||||
`SET_STEPPER_CARRIAGES STEPPER=<stepper_name> CARRIAGES=<carriages>
|
||||
[DISABLE_CHECKS=[0|1]]`: Set or update the stepper carriages.
|
||||
`<stepper_name>` must reference an existing stepper defined in `printer.cfg`,
|
||||
and `<carriages>` describes the carriages the stepper moves. See
|
||||
[Generic Cartesian Kinematics](Config_Reference.md#generic-cartesian-kinematics)
|
||||
for a more detailed overview of the `carriages` parameter in the
|
||||
stepper configuration section. Note that it is only possible
|
||||
to change the coefficients or signs of the carriages with this
|
||||
command, but a user cannot add or remove the carriages that the stepper
|
||||
controls.
|
||||
|
||||
`SET_STEPPER_CARRIAGES` is an advanced tool, and the user is advised
|
||||
to exercise an extreme caution using it, since specifying incorrect
|
||||
configuration may physically damage the printer.
|
||||
|
||||
Note that `SET_STEPPER_CARRIAGES` performs certain internal validations
|
||||
of the new printer kinematics after the change. Keep in mind that if it
|
||||
detects an issue, it may leave printer kinematics in an invalid state.
|
||||
This means that if `SET_STEPPER_CARRIAGES` reports an error, it is unsafe
|
||||
to issue other GCode commands, and the user must inspect the error message
|
||||
and either fix the problem, or manually restore the previous stepper(s)
|
||||
configuration.
|
||||
|
||||
Since `SET_STEPPER_CARRIAGES` can update a configuration of a single
|
||||
stepper at a time, some sequences of changes can lead to invalid
|
||||
intermediate kinematic configurations, even if the final configuration
|
||||
is valid. In such cases a user can pass `DISABLE_CHECKS=1` parameters to
|
||||
all but the last command to disable intermediate checks. For example,
|
||||
if `stepper a` and `stepper b` initially have `x-y` and `x+y` carriages
|
||||
correspondingly, then the following sequence of commands will let a user
|
||||
effectively swap the carriage controls:
|
||||
`SET_STEPPER_CARRIAGES STEPPER=a CARRIAGES=x+y DISABLE_CHECKS=1`
|
||||
and `SET_STEPPER_CARRIAGES STEPPER=b CARRIAGES=x-y`, while
|
||||
still validating the final kinematics state.
|
||||
|
||||
### [hall_filament_width_sensor]
|
||||
|
||||
The following commands are available when the
|
||||
@@ -851,9 +896,9 @@ uncalibrated load cells.
|
||||
`LOAD_CELL_CALIBRATE [LOAD_CELL=<config_name>]`: Start the guided calibration
|
||||
utility. Calibration is a 3 step process:
|
||||
1. First you remove all load from the load cell and run the `TARE` command
|
||||
1. Next you apply a known load to the load cell and run the
|
||||
2. Next you apply a known load to the load cell and run the
|
||||
`CALIBRATE GRAMS=nnn` command
|
||||
1. Finally use the `ACCEPT` command to save the results
|
||||
3. Finally use the `ACCEPT` command to save the results
|
||||
|
||||
You can cancel the calibration process at any time with `ABORT`.
|
||||
|
||||
@@ -861,7 +906,8 @@ You can cancel the calibration process at any time with `ABORT`.
|
||||
`LOAD_CELL_TARE [LOAD_CELL=<config_name>]`: This works just like the tare button
|
||||
on digital scale. It sets the current raw reading of the load cell to be the
|
||||
zero point reference value. The response is the percentage of the sensors range
|
||||
that was read and the raw value in counts.
|
||||
that was read and the raw value in counts. If the load cell is calibrated a
|
||||
force in grams is also reported.
|
||||
|
||||
### LOAD_CELL_READ load_cell="name"
|
||||
`LOAD_CELL_READ [LOAD_CELL=<config_name>]`:
|
||||
@@ -869,6 +915,39 @@ This command takes a reading from the load cell. The response is the percentage
|
||||
of the sensors range that was read and the raw value in counts. If the load cell
|
||||
is calibrated a force in grams is also reported.
|
||||
|
||||
### [load_cell_probe]
|
||||
|
||||
The following commands are enabled if a
|
||||
[load_cell config section](Config_Reference.md#load_cell_probe) has been
|
||||
enabled.
|
||||
|
||||
### LOAD_CELL_TEST_TAP
|
||||
`LOAD_CELL_TEST_TAP [TAPS=<taps>] [TIMEOUT=<timeout>]`: Run a testing routine
|
||||
that reports taps on the load cell. The toolhead will not move but the load cell
|
||||
probe will sense taps just as if it was probing. This can be used as a
|
||||
sanity check to make sure that the probe works. This tool replaces
|
||||
QUERY_ENDSTOPS and QUERY_PROBE for load cell probes.
|
||||
- `TAPS`: the number of taps the tool expects
|
||||
- `TIMEOOUT`: the time, in seconds, that the tool waits for each tab before
|
||||
aborting.
|
||||
|
||||
### Load Cell Command Extensions
|
||||
Commands that perform probes, such as [`PROBE`](#probe),
|
||||
[`PROBE_ACCURACY`](#probe_accuracy),
|
||||
[`BED_MESH_CALIBRATE`](#bed_mesh_calibrate) etc. will accept additional
|
||||
parameters if a `[load_cell_probe]` is defined. The parameters override the
|
||||
corresponding settings from the
|
||||
[`[load_cell_probe]`](./Config_Reference.md#load_cell_probe) configuration:
|
||||
- `FORCE_SAFETY_LIMIT=<grams>`
|
||||
- `TRIGGER_FORCE=<grams>`
|
||||
- `DRIFT_FILTER_CUTOFF_FREQUENCY=<frequency_hz>`
|
||||
- `DRIFT_FILTER_DELAY=<1|2>`
|
||||
- `BUZZ_FILTER_CUTOFF_FREQUENCY=<frequency_hz>`
|
||||
- `BUZZ_FILTER_DELAY=<1|2>`
|
||||
- `NOTCH_FILTER_FREQUENCIES=<list of frequency_hz>`
|
||||
- `NOTCH_FILTER_QUALITY=<quality>`
|
||||
- `TARE_TIME=<seconds>`
|
||||
|
||||
### [manual_probe]
|
||||
|
||||
The manual_probe module is automatically loaded.
|
||||
@@ -925,6 +1004,25 @@ scheduled to run after the stepper move completes, however if a manual
|
||||
stepper move uses SYNC=0 then future G-Code movement commands may run
|
||||
in parallel with the stepper movement.
|
||||
|
||||
`MANUAL_STEPPER STEPPER=config_name GCODE_AXIS=[A-Z]
|
||||
[LIMIT_VELOCITY=<velocity>] [LIMIT_ACCEL=<accel>]
|
||||
[INSTANTANEOUS_CORNER_VELOCITY=<velocity>]`: If the `GCODE_AXIS`
|
||||
parameter is specified then it configures the stepper motor as an
|
||||
extra axis on `G1` move commands. For example, if one were to issue a
|
||||
`MANUAL_STEPPER ... GCODE_AXIS=R` command then one could issue
|
||||
commands like `G1 X10 Y20 R30` to move the stepper motor. The
|
||||
resulting moves will occur synchronously with the associated toolhead
|
||||
xyz movements. If the motor is associated with a `GCODE_AXIS` then
|
||||
one may no longer issue movements using the above `MANUAL_STEPPER`
|
||||
command - one may unregister the stepper with a `MANUAL_STEPPER
|
||||
... GCODE_AXIS=` command to resume manual control of the motor. The
|
||||
`LIMIT_VELOCITY` and `LIMIT_ACCEL` parameters allow one to reduce the
|
||||
speed of `G1` moves if those moves would result in a velocity or
|
||||
acceleration above the specified limits. The
|
||||
`INSTANTANEOUS_CORNER_VELOCITY` specifies the maximum instantaneous
|
||||
velocity change (in mm/s) of the motor during the junction of two
|
||||
moves (the default is 1mm/s).
|
||||
|
||||
### [mcp4018]
|
||||
|
||||
The following command is available when a
|
||||
|
||||
@@ -139,6 +139,25 @@ print board is not connected with USB to the host, due to some boards
|
||||
being able to feed power back to the board and stopping a flash from
|
||||
occurring.
|
||||
|
||||
Please note, that most print boards that use SD cards for flash will
|
||||
implement some kind of flash loop protection for when the sd card is left
|
||||
in place. There are two common methods:
|
||||
|
||||
Filename Change Required (usually "stock" print boards):
|
||||
|
||||
These boards require the firmware file to have a different name each
|
||||
time you flash (for example, firmware1.bin, firmware2.bin, etc.).
|
||||
If you reuse the same filename, the board may ignore it and not update.
|
||||
|
||||
Automatic File Renaming (usually aftermarket print boards:
|
||||
|
||||
Other boards allow using the same filename, commonly firmware.bin,
|
||||
but after flashing, the board renames the file to firmware.cur.
|
||||
This helps indicate the firmware was successfully flashed and prevents
|
||||
it from flashing again on the next startup.
|
||||
|
||||
Before flashing, make sure to check which behavior your board follows.
|
||||
|
||||
For common micro-controllers using Atmega chips, for example the 2560,
|
||||
the code can be flashed with something
|
||||
similar to:
|
||||
|
||||
@@ -27,26 +27,28 @@ $ LOAD_CELL_DIAGNOSTIC
|
||||
```
|
||||
|
||||
Things you can check with this data:
|
||||
|
||||
* The configured sample rate of the sensor should be close to the 'Measured
|
||||
samples per second' value. If it is not you may have a configuration or wiring
|
||||
issue.
|
||||
samples per second' value. If it is not you may have a configuration or wiring
|
||||
issue.
|
||||
* 'Saturated samples' should be 0. If you have saturated samples it means the
|
||||
load sell is seeing more force than it can measure.
|
||||
load sell is seeing more force than it can measure.
|
||||
* 'Unique values' should be a large percentage of the 'Samples
|
||||
Collected' value. If 'Unique values' is 1 it is very likely a wiring issue.
|
||||
Collected' value. If 'Unique values' is 1 it is very likely a wiring issue.
|
||||
* Tap or push on the sensor while `LOAD_CELL_DIAGNOSTIC` runs. If
|
||||
things are working correctly ths should increase the 'Sample range'.
|
||||
things are working correctly this should increase the 'Sample range'.
|
||||
|
||||
## Calibrating a Load Cell
|
||||
|
||||
Load cells are calibrated using the `LOAD_CELL_CALIBRATE` command. This is an
|
||||
interactive calibration utility that walks you though a 3 step process:
|
||||
|
||||
1. First use the `TARE` command to establish the zero force value. This is the
|
||||
`reference_tare_counts` config value.
|
||||
`reference_tare_counts` config value.
|
||||
2. Next you apply a known load or force to the load cell and run the
|
||||
`CALIBRATE GRAMS=nnn` command. From this the `counts_per_gram` value is
|
||||
calculated. See [the next section](#applying-a-known-force-or-load) for some
|
||||
suggestions on how to do this.
|
||||
`CALIBRATE GRAMS=nnn` command. From this the `counts_per_gram` value is
|
||||
calculated. See [the next section](#applying-a-known-force-or-load) for some
|
||||
suggestions on how to do this.
|
||||
3. Finally, use the `ACCEPT` command to save the results.
|
||||
|
||||
You can cancel the calibration process at any time with `ABORT`.
|
||||
@@ -73,11 +75,13 @@ printer bed or toolhead can tolerate without damage. Do try to use at least 1Kg
|
||||
of force, most printers should tolerate this without issue.
|
||||
|
||||
When calibrating make careful note of the values reported:
|
||||
|
||||
```
|
||||
$ CALIBRATE GRAMS=555
|
||||
// Calibration value: -2.78% (-59803108), Counts/gram: 73039.78739,
|
||||
Total capacity: +/- 29.14Kg
|
||||
```
|
||||
|
||||
The `Total capacity` should be close to the theoretical rating of the load cell
|
||||
based on the sensor's capacity. If it is much larger you could have used a
|
||||
higher gain setting in the sensor or a more sensitive load cell. This isn't as
|
||||
@@ -120,3 +124,366 @@ a macro:
|
||||
```
|
||||
{% set tare_counts = printer.load_cell.tare_counts %}
|
||||
```
|
||||
|
||||
# Load Cell Probes
|
||||
|
||||
## Related Documentation
|
||||
|
||||
* [load_cell_probe Config Reference](Config_Reference.md#load_cell_probe)
|
||||
* [load_cell_probe G-Code Commands](G-Codes.md#load_cell_probe)
|
||||
* [load_cell_probe Statuc Reference](Status_Reference.md#load_cell_probe)
|
||||
|
||||
## Load Cell Probe Safety
|
||||
|
||||
Because load cells are a direct nozzle contact probe there is a risk of
|
||||
damage to your printer if too much force is used. The load cell probing system
|
||||
includes a number of safety checks that try to keep your machine safe from
|
||||
excessive force to the toolhead. It's important to understand what they are
|
||||
and how they work as you can defeat most of them with poorly chosen config
|
||||
values.
|
||||
|
||||
#### Calibration Check
|
||||
Every time a homing move starts, load_cell_probe checks
|
||||
that the load_cell is calibrated. If not it will stop the move with an error:
|
||||
`!! Load Cell not calibrated`.
|
||||
|
||||
#### `counts_per_gram`
|
||||
This setting is used to convert raw sensor counts into grams. All the safety
|
||||
limits are in gram units for your convenience. If the `counts_per_gram`
|
||||
setting is not accurate you can easily exceed the safe force on the toolhead.
|
||||
You should never guess this value. Use `LOAD_CELL_CALIBRATE` to find your load
|
||||
cells actual `counts_per_gram`.
|
||||
|
||||
#### `trigger_force`
|
||||
This is the force in grams that triggers the endstop to halt the homing move.
|
||||
When a homing move starts the endstop tares itself with the current reading
|
||||
from the load cell. `trigger_force` is measured from that tare value. There is
|
||||
always some overshoot of this value when the probe collides with the bed,
|
||||
so be conservative. e.g. a setting of 100g could result in 350g of peak force
|
||||
before the toolhead stops. This overshoot will increase with faster probing
|
||||
`speed`, a low ADC sample rate or [multi MCU homing](Multi_MCU_Homing.md).
|
||||
|
||||
#### `reference_tare_counts`
|
||||
This is the baseline tare value that is set by `LOAD_CELL_CALIBRATE`.
|
||||
This value works with `force_safety_limit` to limit the maximum force on the
|
||||
toolhead.
|
||||
|
||||
#### `force_safety_limit`
|
||||
This is the maximum absolute force, relative to `reference_tare_counts`,
|
||||
that the probe will allow while homing or probing. If the MCU sees this
|
||||
force exceeded it will shut down the printer with the error `!! Load cell
|
||||
endstop: too much force!`. There are a number of ways this can be triggered:
|
||||
|
||||
The first risk this protects against is picking too large of a value for
|
||||
`drift_filter_cutoff_frequency`. This can cause the drift filter to filter out
|
||||
a probe event and continue the homing move. If this happens the
|
||||
`force_safety_limit` acts as a backup protection.
|
||||
|
||||
The second problem is probing repeatedly in one place. Klipper does not retract
|
||||
the probe when doing a single `PROBE` command. This can result
|
||||
in force applied to the toolhead at the end of a probing cycle. Because
|
||||
external forces can vary greatly between probing locations,
|
||||
`load_cell_probe` performs a tare before beginning each probe. If you repeat
|
||||
the `PROBE` command, load_cell_probe will tare the endstop at the current force.
|
||||
Multiple cycles of this will result in ever-increasing force on the toolhead.
|
||||
`force_safety_limit` stops this cycle from running out of control.
|
||||
|
||||
Another way this run-away can happen is damage to a strain gauge. If the metal
|
||||
part is permanently bent it will change the `reference_tare_counts` of the
|
||||
device. This puts the starting tare value much closer to the limit making it
|
||||
more likely to be violated. You want to be notified if this is happening
|
||||
because your hardware has been permanently damaged.
|
||||
|
||||
The final way this can be triggered is due to temperature changes. If your
|
||||
strain gauges are heated their `reference_tare_counts` may be very different
|
||||
at ambient temperature vs operating temperature. In this case you may need
|
||||
to increase the `force_safety_limit` to allow for thermal changes.
|
||||
|
||||
#### Load Cell Endstop Watchdog Task
|
||||
When homing the load_cell_endstop starts a task on the MCU to trac
|
||||
measurements arriving from the sensor. If the sensor fails to send
|
||||
measurements for 2 sample periods the watchdog will shut down the printer
|
||||
with an error `!! LoadCell Endstop timed out waiting on ADC data`.
|
||||
|
||||
If this happens, the most likely cause is a fault from the ADC. Inadequate
|
||||
grounding of your printer can be the root cause. The frame, power supply
|
||||
case and pint bed should all be connected to ground. You may need to ground
|
||||
the frame in multiple places. Anodized aluminum extrusions do not conduct
|
||||
electricity well. You might need to sand the area where the grounding wire
|
||||
is attached to make good electrical contact.
|
||||
|
||||
## Load Cell Probe Setup
|
||||
|
||||
This section covers the process for commissioning a load cell probe.
|
||||
|
||||
### Verify the Load Cell First
|
||||
|
||||
A `[load_cell_probe]` is also a `[load_cell]` and G-code commands related to
|
||||
`[load_cell]` work with `[load_cell_probe]`. Before attempting to use a load
|
||||
cell probe, follow the directions for
|
||||
[calibrating the load cell](Load_Cell.md#calibrating-a-load-cell) with
|
||||
`CALIBRATE_LOAD_CELL` and checking its operation with `LOAD_CELL_DIAGNOSTIC`.
|
||||
|
||||
### Verify Probe Operation With LOAD_CELL_TEST_TAP
|
||||
|
||||
Use the command `LOAD_CELL_TEST_TAP` to test the operation of the load cell
|
||||
probe before actually trying to probe with it. This command detects taps,
|
||||
just like the PROBE command, but it does not move the z axis. By default, it
|
||||
listens for 3 taps before ending the test. You have 30 seconds to do each
|
||||
tap, if no taps are detected the command will time out.
|
||||
|
||||
If this test fails, check your configuration and `LOAD_CELL_DIAGNOSTIC`
|
||||
carefully to look for issues.
|
||||
|
||||
Load cell probes don't support the `QUERY_ENDSTOPS` or `QUERY_PROBE`
|
||||
commands. Use `LOAD_CELL_TEST_TAP` for testing functionality before probing.
|
||||
|
||||
### Homing Macros
|
||||
|
||||
Load cell probe is not an endstop and doesn't support `endstop:
|
||||
prove:z_virtual_endstop`. For the time being you'll need to configure your z
|
||||
axis with an MCU pin as its endstop. You won't actually be using the pin but
|
||||
for the time being you have to configure something.
|
||||
|
||||
To home the axis with just the probe you need to set up a custom homing
|
||||
macro. This requires setting up
|
||||
[homing_override](Config_Reference.md#homing_override).
|
||||
|
||||
Here is a simple macro that can accomplish this. Note that the
|
||||
`_HOME_Z_FROM_LAST_PROBE` macro has to be separate because of the way macros
|
||||
work. The sub-call is needed so that the `_HOME_Z_FROM_LAST_PROBE` macro can
|
||||
see the result of the probe in `printer.probe.last_z_result`.
|
||||
|
||||
```gcode
|
||||
[gcode_macro _HOME_Z_FROM_LAST_PROBE]
|
||||
gcode:
|
||||
{% set z_probed = printer.probe.last_z_result %}
|
||||
{% set z_position = printer.toolhead.position[2] %}
|
||||
{% set z_actual = z_position - z_probed %}
|
||||
SET_KINEMATIC_POSITION Z={z_actual}
|
||||
|
||||
[gcode_macro _HOME_Z]
|
||||
gcode:
|
||||
SET_GCODE_OFFSET Z=0 # load cell probes dont need a Z offset
|
||||
# position toolhead for homing Z, edit for your printers size
|
||||
#G90 # absolute move
|
||||
#G1 Y50 X50 F{5 * 60} # move to X/Y position for homing
|
||||
|
||||
# soft home the z axis to its limit so it can be moved:
|
||||
SET_KINEMATIC_POSITION Z={printer.toolhead.axis_maximum[2]}
|
||||
|
||||
# Fast approach and tap
|
||||
PROBE PROBE_SPEED={5 * 60} # override the speed for faster homing
|
||||
_HOME_Z_FROM_LAST_PROBE
|
||||
|
||||
# lift z to 2mm
|
||||
G91 # relative move
|
||||
G1 Z2 F{5 * 60}
|
||||
|
||||
# probe at standard speed
|
||||
PROBE
|
||||
_HOME_Z_FROM_LAST_PROBE
|
||||
|
||||
# lift z to 10mm for clearance
|
||||
G91 # relative move
|
||||
G1 Z10 F{5 * 60}
|
||||
```
|
||||
|
||||
### Suggested Probing Temperature
|
||||
|
||||
Currently, we suggest keeping the nozzle temperature below the level that causes
|
||||
the filament to ooze while homing and probing. 140C is a good starting
|
||||
point. This temperature is also low enough not to scar PEI build surfaces.
|
||||
|
||||
Fouling of the nozzle and the print bed due to oozing filament is the #1 source
|
||||
of probing error with the load cell probe. Klipper does not yet have a universal
|
||||
way to detect poor quality taps due to filament ooze. The existing code may
|
||||
decide that a tap is valid when it is of poor quality. Classifying these poor
|
||||
quality taps is an area of active research.
|
||||
|
||||
Klipper also lacks support for re-locating a probe point if the
|
||||
location has become fouled by filament ooze. Modules like `quad_gantry_level`
|
||||
will repeatedly probe the same coordinates even if a probe previously failed
|
||||
there.
|
||||
|
||||
Give the above it is strongly suggested not to probe at printing temperatures.
|
||||
|
||||
### Hot Nozzle Protection
|
||||
|
||||
The Voron project has a great macro for protecting your print surface from the
|
||||
hot nozzle. See [Voron Tap's
|
||||
`activate_gcode`](https://github.com/VoronDesign/Voron-Tap/blob/main/config/tap_klipper_instructions.md)
|
||||
|
||||
It is highly suggested to add something like this to your config.
|
||||
|
||||
### Nozzle Cleaning
|
||||
|
||||
Before probing the nozzle should be clean. You could do this manually before
|
||||
every print. You can also implement a nozzle scrubber and automate the process.
|
||||
Here is a suggested sequence:
|
||||
|
||||
1. Wait for the nozzle to heat up to probing temp (e.g. `M109 S140`)
|
||||
1. Home the machine (`G28`)
|
||||
1. Scrub the nozzle on a brush
|
||||
1. Heat soak the print bed
|
||||
1. Perform probing tasks: QGL, bed mesh etc.
|
||||
|
||||
### Temperature Compensation for Nozzle Growth
|
||||
|
||||
If you are probing at a safe temperature, the nozzle will expand after
|
||||
heating to printing temperatures. This will cause the nozzle to get longer
|
||||
and closer to the print surface. You can compensate for this with
|
||||
[[z_thermal_adjust]](Config_Reference.md#z_thermal_adjust). This adjustment will
|
||||
work across a range of printing
|
||||
temperatures from PLA to PC.
|
||||
|
||||
#### Calculating the `temp_coeff` for `[z_thermal_adjust]`
|
||||
|
||||
The easiest way to do this is to measure at 2 different temperatures.
|
||||
Ideally these should be the upper and lower limits of the printing
|
||||
temperature range. E.g. 180C and 290C. You can perform a `PROBE_ACCURACY` at
|
||||
both temperatures and then calculate the difference of the `average z` at both.
|
||||
|
||||
The adjustment value is the change in nozzle length divided by the change in
|
||||
temperature. e.g.
|
||||
|
||||
```
|
||||
temp_coeff = -0.05 / (290 - 180) = -0.00045455
|
||||
```
|
||||
|
||||
The expected result is a negative number. Positive values for `temp_coeff` move
|
||||
the nozzle closer to the bed and negative values move it further away.
|
||||
Expect to have to move the nozzle further away as it gets longer when hot.
|
||||
|
||||
#### Configure `[z_thermal_adjust]`
|
||||
Set up z_thermal_adjust to reference the `extruder` as the source of temperature
|
||||
data. E.g.:
|
||||
|
||||
```
|
||||
[z_thermal_adjust nozzle]
|
||||
temp_coeff=-0.00045455
|
||||
sensor_type: temperature_combined
|
||||
sensor_list: extruder
|
||||
combination_method: max
|
||||
min_temp: 0
|
||||
max_temp: 400
|
||||
max_z_adjustment: 0.1
|
||||
```
|
||||
|
||||
## Continuous Tare Filters for Toolhead Load Cells
|
||||
|
||||
Klipper implements a configurable IIR filter on the MCU to provide continuous
|
||||
tareing of the load cell while probing. Continuous taring means the 0 value
|
||||
moves with drift caused by external factors like bowden tubes and thermal
|
||||
changes. This is aimed at toolhead sensors and moving beds that experience lots
|
||||
of external forces that change while probing.
|
||||
|
||||
### Installing SciPy
|
||||
|
||||
The filtering code uses the excellent [SciPy](https://scipy.org/) library to
|
||||
compute the filter coefficients based on the values your enter into the config.
|
||||
|
||||
Pre-compiled SciPi builds are available for Python 3 on 32 bit Raspberry Pi
|
||||
systems. 32 bit + Python 3 is strongly recommended because it will streamline
|
||||
your installation experience. It does work with Python 2 but installation can
|
||||
take 30+ minutes and require installing additional tools.
|
||||
|
||||
```bash
|
||||
~/klippy-env/bin/pip install scipy
|
||||
```
|
||||
|
||||
### Filter Workbench
|
||||
|
||||
The filter parameters should be selected based on drift seen on the printer
|
||||
during normal operation. A Jupyter notebook is provided in scripts,
|
||||
[filter_workbench.ipynb](../scripts/filter_workbench.ipynb), to perform a
|
||||
detailed investigation with real captured data and FFTs.
|
||||
|
||||
### Filtering Suggestions
|
||||
|
||||
For those just trying to get a filter working follow these suggestions:
|
||||
|
||||
* The only essential option is `drift_filter_cutoff_frequency`. A conservative
|
||||
starting value is `0.5`Hz. Prusa shipped the MK4 with a setting of `0.8`Hz and
|
||||
the XL with `11.2`Hz. This is probably a safe range to experiment with. This
|
||||
value should be increased only until normal drift due to bowden tube force is
|
||||
eliminated. Setting this value too high will result in slow triggering and
|
||||
excess force going through the toolhead.
|
||||
* Keep `trigger_force` low. The default is `75`g. The drift filter keeps the
|
||||
internal grams value very close to 0 so a large trigger force is not needed.
|
||||
* Keep `force_safety_limit` to a conservative value. The default value is 2Kg
|
||||
and should keep your toolhead safe while experimenting. If you hit this limit
|
||||
the `drift_filter_cutoff_frequency` value may be too high.
|
||||
|
||||
## Suggestions for Load Cell Tool Boards
|
||||
|
||||
This section covers suggestions for those developing toolhead boards that want
|
||||
to support [load_cell_probe]
|
||||
|
||||
### ADC Sensor Selection & Board Development Hints
|
||||
|
||||
Ideally a sensor would meet these criteria:
|
||||
|
||||
* At least 24 bits wide
|
||||
* Use SPI communications
|
||||
* Has a pin can be used to indicate sample ready without SPI communications.
|
||||
This is often called the "data ready" or "DRDY" pin. Checking a pin is much
|
||||
faster than running an SPI query.
|
||||
* Has a programmable gain amplifier gain setting of 128. This should eliminate
|
||||
the need for a separate amplifier.
|
||||
* Indicates via SPI if the sensor has been reset. Detecting resets avoids
|
||||
timing errors in homing and using noisy data at startup. It can also help
|
||||
users
|
||||
track down wiring and grounding issues.
|
||||
* A selectable sample rate between 350Hz and 2Khz. Very high sample rates don't
|
||||
turn out to be beneficial in our 3D printers because they produce so much
|
||||
noise
|
||||
when moving fast. Sample rates below 250Hz will require slower probing speeds.
|
||||
They also increase the force on the toolhead due to longer delays between
|
||||
measurements. E.g. a 500Hz sensor moving at 5mm/s has the same safety factor
|
||||
as
|
||||
a 100Hz sensor moving at only 1mm/s.
|
||||
* If designing for under-bed applications, and you want to sense multiple load
|
||||
cells, use a chip that can sample all of its inputs simultaneously. Multiplex
|
||||
ADCs that require switching channels have a settling of several samples after
|
||||
each channel switch making them unsuitable for probing applications.
|
||||
|
||||
Implementing support for a new sensor chip is not particularly difficult with
|
||||
Klipper's `bulk_sensor` and `load_cell_endstop` infrastructure.
|
||||
|
||||
### 5V Power Filtering
|
||||
|
||||
It is strongly suggested to use larger capacitors than specified by the ADC chip
|
||||
manufacturer. ADC chips are usually targeted at low noise environments, like
|
||||
battery powered devices. Sensor manufacturers suggested application notes
|
||||
generally assume a quiet power supply. Treat their suggested capacitor values as
|
||||
minimums.
|
||||
|
||||
3D printers put huge amounts of noise onto the 5V bus and this can ruin the
|
||||
sensor's accuracy. Test the sensor on the board with a typical 3D printer power
|
||||
supply and active stepper drivers before deciding on smoothing capacitor sizes.
|
||||
|
||||
### Grounding & Ground Planes
|
||||
|
||||
Analog ADC chips contain components that are very vulnerable to noise and
|
||||
ESD. A large ground plane on the first board layer under the chip can help with
|
||||
noise. Keep the chip away from power sections and DC to DC converters. The board
|
||||
should have proper grounding back to the DC supply.
|
||||
|
||||
### HX711 and HX717 Notes
|
||||
|
||||
This sensor is popular because of its low cost and availability in the
|
||||
supply chain. However, this is a sensor with some drawbacks:
|
||||
|
||||
* The HX71x sensors use bit-bang communication which has a high overhead on the
|
||||
MCU. Using a sensor that communicates via SPI would save resources on the tool
|
||||
board's CPU.
|
||||
* The HX71x lacks a way to communicate reset events to the MCU. Klipper detects
|
||||
resets with a timing heuristic but this is not ideal. Resets indicate a
|
||||
problem with wiring or grounding.
|
||||
* For probing applications the HX717 version is strongly preferred because
|
||||
of its higher sample rate (320 vs 80). Probing speed on the HX711 should be
|
||||
limited to less than 2mm/s.
|
||||
* The sample rate on the HX71x cannot be set from klipper's config. If you have
|
||||
the 10SPS version of the sensor (which is widely distributed) it needs to
|
||||
be physically re-wired to run at 80SPS.
|
||||
|
||||
@@ -152,7 +152,7 @@ Recommended connection scheme for I2C on the Raspberry Pi:
|
||||
| SDA | 03 | GPIO02 (SDA1) |
|
||||
| SCL | 05 | GPIO03 (SCL1) |
|
||||
|
||||
The RPi has buit-in 1.8K pull-ups on both SCL and SDA.
|
||||
The RPi has built-in 1.8K pull-ups on both SCL and SDA.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ AD do not include the flats on the corners that some test objects provide.
|
||||
## Configure your skew
|
||||
|
||||
Make sure `[skew_correction]` is in printer.cfg. You may now use the `SET_SKEW`
|
||||
gcode to configure skew_correcton. For example, if your measured lengths
|
||||
gcode to configure skew_correction. For example, if your measured lengths
|
||||
along XY are as follows:
|
||||
|
||||
```
|
||||
|
||||
@@ -121,5 +121,5 @@ M104 S0
|
||||
before the macro call. Also note that SuperSlicer has a
|
||||
"custom gcode only" button option, which achieves the same outcome.
|
||||
|
||||
An example of a START_PRINT macro using these paramaters can
|
||||
An example of a START_PRINT macro using these parameters can
|
||||
be found in config/sample-macros.cfg
|
||||
|
||||
@@ -242,6 +242,8 @@ The following information is available in the `gcode_move` object
|
||||
The following information is available in the
|
||||
[hall_filament_width_sensor](Config_Reference.md#hall_filament_width_sensor)
|
||||
object:
|
||||
- all items from
|
||||
[filament_switch_sensor](Status_Reference.md#filament_switch_sensor)
|
||||
- `is_active`: Returns True if the sensor is currently active.
|
||||
- `Diameter`: The last reading from the sensor in mm.
|
||||
- `Raw`: The last raw ADC reading from the sensor.
|
||||
@@ -289,6 +291,9 @@ is always available):
|
||||
- `printing_time`: The amount of time (in seconds) the printer has
|
||||
been in the "Printing" state (as tracked by the idle_timeout
|
||||
module).
|
||||
- `idle_timeout`: The current 'timeout' (in seconds)
|
||||
to wait for the gcode to be triggered.
|
||||
(as set by [SET_IDLE_TIMEOUT](G-Codes.md#set_idle_timeout))
|
||||
|
||||
## led
|
||||
|
||||
@@ -298,7 +303,7 @@ The following information is available for each `[led led_name]`,
|
||||
- `color_data`: A list of color lists containing the RGBW values for a
|
||||
led in the chain. Each value is represented as a float from 0.0 to
|
||||
1.0. Each color list contains 4 items (red, green, blue, white) even
|
||||
if the underyling LED supports fewer color channels. For example,
|
||||
if the underlying LED supports fewer color channels. For example,
|
||||
the blue value (3rd item in color list) of the second neopixel in a
|
||||
chain could be accessed at
|
||||
`printer["neopixel <config_name>"].color_data[1][2]`.
|
||||
@@ -314,6 +319,15 @@ The following information is available for each `[load_cell name]`:
|
||||
- 'min_force_g': The minimum force in grams, over the last polling period.
|
||||
- 'max_force_g': The maximum force in grams, over the last polling period.
|
||||
|
||||
## load_cell_probe
|
||||
|
||||
The following information is available for `[load_cell_probe]`:
|
||||
- all items from [load_cell](Status_Reference.md#load_cell)
|
||||
- all items from [probe](Status_Reference.md#probe)
|
||||
- 'endstop_tare_counts': the load cell probe keeps a tare value independent of
|
||||
the load cell. This re-set at the start of each probe.
|
||||
- 'last_trigger_time': timestamp of the last homing trigger
|
||||
|
||||
## manual_probe
|
||||
|
||||
The following information is available in the
|
||||
@@ -568,6 +582,12 @@ on a cartesian, hybrid_corexy or hybrid_corexz robot
|
||||
- `carriage_1`: The mode of the carriage 1. Possible values are:
|
||||
"INACTIVE", "PRIMARY", "COPY", and "MIRROR".
|
||||
|
||||
On a `generic_cartesian` kinematic, the following information is
|
||||
available in `dual_carriage`:
|
||||
- `carriages["<carriage>"]`: The mode of the carriage `<carriage>`. Possible
|
||||
values are "INACTIVE" and "PRIMARY" for the primary carriage and "INACTIVE",
|
||||
"PRIMARY", "COPY", and "MIRROR" for the dual carriage.
|
||||
|
||||
## virtual_sdcard
|
||||
|
||||
The following information is available in the
|
||||
|
||||
@@ -8,14 +8,12 @@ title: Welcome
|
||||
|
||||
The Klipper firmware controls 3d-Printers. It combines the power of a
|
||||
general purpose computer with one or more micro-controllers. See the
|
||||
[features document](https://www.klipper3d.org/Features.html) for more
|
||||
information on why you should use the Klipper software.
|
||||
[features document](Features.md) for more information on why you
|
||||
should use the Klipper software.
|
||||
|
||||
Start by [installing Klipper software](https://www.klipper3d.org/Installation.html).
|
||||
Start by [installing Klipper software](Installation.md).
|
||||
|
||||
Klipper software is Free Software. Read the
|
||||
[documentation](https://www.klipper3d.org/Overview.html), see the
|
||||
[license](COPYING), or
|
||||
[documentation](Overview.md), see the [license](../COPYING), or
|
||||
[download](https://github.com/Klipper3d/Klipper) the software. We
|
||||
depend on the generous support from our
|
||||
[sponsors](https://www.klipper3d.org/Sponsors.html).
|
||||
depend on the generous support from our [sponsors](Sponsors.md).
|
||||
|
||||
@@ -17,16 +17,16 @@ COMPILE_ARGS = ("-Wall -g -O2 -shared -fPIC"
|
||||
" -o %s %s")
|
||||
SSE_FLAGS = "-mfpmath=sse -msse2"
|
||||
SOURCE_FILES = [
|
||||
'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'itersolve.c', 'trapq.c',
|
||||
'pollreactor.c', 'msgblock.c', 'trdispatch.c',
|
||||
'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'steppersync.c',
|
||||
'itersolve.c', 'trapq.c', 'pollreactor.c', 'msgblock.c', 'trdispatch.c',
|
||||
'kin_cartesian.c', 'kin_corexy.c', 'kin_corexz.c', 'kin_delta.c',
|
||||
'kin_deltesian.c', 'kin_polar.c', 'kin_rotary_delta.c', 'kin_winch.c',
|
||||
'kin_extruder.c', 'kin_shaper.c', 'kin_idex.c',
|
||||
'kin_extruder.c', 'kin_shaper.c', 'kin_idex.c', 'kin_generic.c'
|
||||
]
|
||||
DEST_LIB = "c_helper.so"
|
||||
OTHER_FILES = [
|
||||
'list.h', 'serialqueue.h', 'stepcompress.h', 'itersolve.h', 'pyhelper.h',
|
||||
'trapq.h', 'pollreactor.h', 'msgblock.h'
|
||||
'list.h', 'serialqueue.h', 'stepcompress.h', 'steppersync.h',
|
||||
'itersolve.h', 'pyhelper.h', 'trapq.h', 'pollreactor.h', 'msgblock.h'
|
||||
]
|
||||
|
||||
defs_stepcompress = """
|
||||
@@ -54,25 +54,28 @@ defs_stepcompress = """
|
||||
int stepcompress_extract_old(struct stepcompress *sc
|
||||
, struct pull_history_steps *p, int max
|
||||
, uint64_t start_clock, uint64_t end_clock);
|
||||
void stepcompress_set_stepper_kinematics(struct stepcompress *sc
|
||||
, struct stepper_kinematics *sk);
|
||||
"""
|
||||
|
||||
defs_steppersync = """
|
||||
struct steppersync *steppersync_alloc(struct serialqueue *sq
|
||||
, struct stepcompress **sc_list, int sc_num, int move_num);
|
||||
void steppersync_free(struct steppersync *ss);
|
||||
void steppersync_set_time(struct steppersync *ss
|
||||
, double time_offset, double mcu_freq);
|
||||
int steppersync_flush(struct steppersync *ss, uint64_t move_clock
|
||||
, uint64_t clear_history_clock);
|
||||
int32_t steppersync_generate_steps(struct steppersync *ss
|
||||
, double gen_steps_time, uint64_t flush_clock);
|
||||
void steppersync_history_expire(struct steppersync *ss, uint64_t end_clock);
|
||||
int steppersync_flush(struct steppersync *ss, uint64_t move_clock);
|
||||
"""
|
||||
|
||||
defs_itersolve = """
|
||||
int32_t itersolve_generate_steps(struct stepper_kinematics *sk
|
||||
, double flush_time);
|
||||
double itersolve_check_active(struct stepper_kinematics *sk
|
||||
, double flush_time);
|
||||
int32_t itersolve_is_active_axis(struct stepper_kinematics *sk, char axis);
|
||||
void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq);
|
||||
void itersolve_set_stepcompress(struct stepper_kinematics *sk
|
||||
, struct stepcompress *sc, double step_dist);
|
||||
void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq
|
||||
, double step_dist);
|
||||
double itersolve_calc_position_from_coord(struct stepper_kinematics *sk
|
||||
, double x, double y, double z);
|
||||
void itersolve_set_position(struct stepper_kinematics *sk
|
||||
@@ -106,6 +109,12 @@ defs_trapq = """
|
||||
defs_kin_cartesian = """
|
||||
struct stepper_kinematics *cartesian_stepper_alloc(char axis);
|
||||
"""
|
||||
defs_kin_generic_cartesian = """
|
||||
struct stepper_kinematics *generic_cartesian_stepper_alloc(double a_x
|
||||
, double a_y, double a_z);
|
||||
void generic_cartesian_stepper_set_coeffs(struct stepper_kinematics *sk
|
||||
, double a_x, double a_y, double a_z);
|
||||
"""
|
||||
|
||||
defs_kin_corexy = """
|
||||
struct stepper_kinematics *corexy_stepper_alloc(char type);
|
||||
@@ -154,6 +163,7 @@ defs_kin_shaper = """
|
||||
, int n, double a[], double t[]);
|
||||
int input_shaper_set_sk(struct stepper_kinematics *sk
|
||||
, struct stepper_kinematics *orig_sk);
|
||||
void input_shaper_update_sk(struct stepper_kinematics *sk);
|
||||
struct stepper_kinematics * input_shaper_alloc(void);
|
||||
"""
|
||||
|
||||
@@ -175,7 +185,7 @@ defs_serialqueue = """
|
||||
};
|
||||
|
||||
struct serialqueue *serialqueue_alloc(int serial_fd, char serial_fd_type
|
||||
, int client_id);
|
||||
, int client_id, char name[16]);
|
||||
void serialqueue_exit(struct serialqueue *sq);
|
||||
void serialqueue_free(struct serialqueue *sq);
|
||||
struct command_queue *serialqueue_alloc_commandqueue(void);
|
||||
@@ -212,6 +222,7 @@ defs_trdispatch = """
|
||||
defs_pyhelper = """
|
||||
void set_python_logging_callback(void (*func)(const char *));
|
||||
double get_monotonic(void);
|
||||
int set_thread_name(char name[16]);
|
||||
"""
|
||||
|
||||
defs_std = """
|
||||
@@ -220,10 +231,11 @@ defs_std = """
|
||||
|
||||
defs_all = [
|
||||
defs_pyhelper, defs_serialqueue, defs_std, defs_stepcompress,
|
||||
defs_itersolve, defs_trapq, defs_trdispatch,
|
||||
defs_steppersync, defs_itersolve, defs_trapq, defs_trdispatch,
|
||||
defs_kin_cartesian, defs_kin_corexy, defs_kin_corexz, defs_kin_delta,
|
||||
defs_kin_deltesian, defs_kin_polar, defs_kin_rotary_delta, defs_kin_winch,
|
||||
defs_kin_extruder, defs_kin_shaper, defs_kin_idex,
|
||||
defs_kin_generic_cartesian,
|
||||
]
|
||||
|
||||
# Update filenames to an absolute path
|
||||
@@ -262,11 +274,33 @@ def do_build_code(cmd):
|
||||
logging.error(msg)
|
||||
raise Exception(msg)
|
||||
|
||||
# Build the main c_helper.so c code library
|
||||
def check_build_c_library():
|
||||
srcdir = os.path.dirname(os.path.realpath(__file__))
|
||||
srcfiles = get_abs_files(srcdir, SOURCE_FILES)
|
||||
ofiles = get_abs_files(srcdir, OTHER_FILES)
|
||||
destlib = get_abs_files(srcdir, [DEST_LIB])[0]
|
||||
if not check_build_code(srcfiles+ofiles+[__file__], destlib):
|
||||
# Code already built
|
||||
return destlib
|
||||
# Select command line options
|
||||
if check_gcc_option(SSE_FLAGS):
|
||||
cmd = "%s %s %s" % (GCC_CMD, SSE_FLAGS, COMPILE_ARGS)
|
||||
else:
|
||||
cmd = "%s %s" % (GCC_CMD, COMPILE_ARGS)
|
||||
# Invoke compiler
|
||||
logging.info("Building C code module %s", DEST_LIB)
|
||||
tempdestlib = get_abs_files(srcdir, ["_temp_" + DEST_LIB])[0]
|
||||
do_build_code(cmd % (tempdestlib, ' '.join(srcfiles)))
|
||||
# Rename from temporary file to final file name
|
||||
os.rename(tempdestlib, destlib)
|
||||
return destlib
|
||||
|
||||
FFI_main = None
|
||||
FFI_lib = None
|
||||
pyhelper_logging_callback = None
|
||||
|
||||
# Hepler invoked from C errorf() code to log errors
|
||||
# Helper invoked from C errorf() code to log errors
|
||||
def logging_callback(msg):
|
||||
logging.error(FFI_main.string(msg))
|
||||
|
||||
@@ -274,17 +308,9 @@ def logging_callback(msg):
|
||||
def get_ffi():
|
||||
global FFI_main, FFI_lib, pyhelper_logging_callback
|
||||
if FFI_lib is None:
|
||||
srcdir = os.path.dirname(os.path.realpath(__file__))
|
||||
srcfiles = get_abs_files(srcdir, SOURCE_FILES)
|
||||
ofiles = get_abs_files(srcdir, OTHER_FILES)
|
||||
destlib = get_abs_files(srcdir, [DEST_LIB])[0]
|
||||
if check_build_code(srcfiles+ofiles+[__file__], destlib):
|
||||
if check_gcc_option(SSE_FLAGS):
|
||||
cmd = "%s %s %s" % (GCC_CMD, SSE_FLAGS, COMPILE_ARGS)
|
||||
else:
|
||||
cmd = "%s %s" % (GCC_CMD, COMPILE_ARGS)
|
||||
logging.info("Building C code module %s", DEST_LIB)
|
||||
do_build_code(cmd % (destlib, ' '.join(srcfiles)))
|
||||
# Check if library needs to be built, and build if so
|
||||
destlib = check_build_c_library()
|
||||
# Open library
|
||||
FFI_main = cffi.FFI()
|
||||
for d in defs_all:
|
||||
FFI_main.cdef(d)
|
||||
|
||||
@@ -26,8 +26,8 @@ struct timepos {
|
||||
|
||||
// Generate step times for a portion of a move
|
||||
static int32_t
|
||||
itersolve_gen_steps_range(struct stepper_kinematics *sk, struct move *m
|
||||
, double abs_start, double abs_end)
|
||||
itersolve_gen_steps_range(struct stepper_kinematics *sk, struct stepcompress *sc
|
||||
, struct move *m, double abs_start, double abs_end)
|
||||
{
|
||||
sk_calc_callback calc_position_cb = sk->calc_position_cb;
|
||||
double half_step = .5 * sk->step_dist;
|
||||
@@ -37,7 +37,7 @@ itersolve_gen_steps_range(struct stepper_kinematics *sk, struct move *m
|
||||
if (end > m->move_t)
|
||||
end = m->move_t;
|
||||
struct timepos old_guess = {start, sk->commanded_pos}, guess = old_guess;
|
||||
int sdir = stepcompress_get_step_dir(sk->sc);
|
||||
int sdir = stepcompress_get_step_dir(sc);
|
||||
int is_dir_change = 0, have_bracket = 0, check_oscillate = 0;
|
||||
double target = sk->commanded_pos + (sdir ? half_step : -half_step);
|
||||
double last_time=start, low_time=start, high_time=start + SEEK_TIME_RESET;
|
||||
@@ -99,13 +99,13 @@ itersolve_gen_steps_range(struct stepper_kinematics *sk, struct move *m
|
||||
if (!have_bracket || high_time - low_time > .000000001) {
|
||||
if (!is_dir_change && rel_dist >= -half_step)
|
||||
// Avoid rollback if stepper fully reaches step position
|
||||
stepcompress_commit(sk->sc);
|
||||
stepcompress_commit(sc);
|
||||
// Guess is not close enough - guess again with new time
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Found next step - submit it
|
||||
int ret = stepcompress_append(sk->sc, sdir, m->print_time, guess.time);
|
||||
int ret = stepcompress_append(sc, sdir, m->print_time, guess.time);
|
||||
if (ret)
|
||||
return ret;
|
||||
target = sdir ? target+half_step+half_step : target-half_step-half_step;
|
||||
@@ -143,8 +143,9 @@ check_active(struct stepper_kinematics *sk, struct move *m)
|
||||
}
|
||||
|
||||
// Generate step times for a range of moves on the trapq
|
||||
int32_t __visible
|
||||
itersolve_generate_steps(struct stepper_kinematics *sk, double flush_time)
|
||||
int32_t
|
||||
itersolve_generate_steps(struct stepper_kinematics *sk, struct stepcompress *sc
|
||||
, double flush_time)
|
||||
{
|
||||
double last_flush_time = sk->last_flush_time;
|
||||
sk->last_flush_time = flush_time;
|
||||
@@ -170,15 +171,15 @@ itersolve_generate_steps(struct stepper_kinematics *sk, double flush_time)
|
||||
while (--skip_count && pm->print_time > abs_start)
|
||||
pm = list_prev_entry(pm, node);
|
||||
do {
|
||||
int32_t ret = itersolve_gen_steps_range(sk, pm, abs_start
|
||||
, flush_time);
|
||||
int32_t ret = itersolve_gen_steps_range(
|
||||
sk, sc, pm, abs_start, flush_time);
|
||||
if (ret)
|
||||
return ret;
|
||||
pm = list_next_entry(pm, node);
|
||||
} while (pm != m);
|
||||
}
|
||||
// Generate steps for this move
|
||||
int32_t ret = itersolve_gen_steps_range(sk, m, last_flush_time
|
||||
int32_t ret = itersolve_gen_steps_range(sk, sc, m, last_flush_time
|
||||
, flush_time);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -195,8 +196,8 @@ itersolve_generate_steps(struct stepper_kinematics *sk, double flush_time)
|
||||
double abs_end = force_steps_time;
|
||||
if (abs_end > flush_time)
|
||||
abs_end = flush_time;
|
||||
int32_t ret = itersolve_gen_steps_range(sk, m, last_flush_time
|
||||
, abs_end);
|
||||
int32_t ret = itersolve_gen_steps_range(
|
||||
sk, sc, m, last_flush_time, abs_end);
|
||||
if (ret)
|
||||
return ret;
|
||||
skip_count = 1;
|
||||
@@ -240,16 +241,10 @@ itersolve_is_active_axis(struct stepper_kinematics *sk, char axis)
|
||||
}
|
||||
|
||||
void __visible
|
||||
itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq)
|
||||
itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq
|
||||
, double step_dist)
|
||||
{
|
||||
sk->tq = tq;
|
||||
}
|
||||
|
||||
void __visible
|
||||
itersolve_set_stepcompress(struct stepper_kinematics *sk
|
||||
, struct stepcompress *sc, double step_dist)
|
||||
{
|
||||
sk->sc = sc;
|
||||
sk->step_dist = step_dist;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,12 +26,11 @@ struct stepper_kinematics {
|
||||
};
|
||||
|
||||
int32_t itersolve_generate_steps(struct stepper_kinematics *sk
|
||||
, double flush_time);
|
||||
, struct stepcompress *sc, double flush_time);
|
||||
double itersolve_check_active(struct stepper_kinematics *sk, double flush_time);
|
||||
int32_t itersolve_is_active_axis(struct stepper_kinematics *sk, char axis);
|
||||
void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq);
|
||||
void itersolve_set_stepcompress(struct stepper_kinematics *sk
|
||||
, struct stepcompress *sc, double step_dist);
|
||||
void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq
|
||||
, double step_dist);
|
||||
double itersolve_calc_position_from_coord(struct stepper_kinematics *sk
|
||||
, double x, double y, double z);
|
||||
void itersolve_set_position(struct stepper_kinematics *sk
|
||||
|
||||
52
klippy/chelper/kin_generic.c
Normal file
52
klippy/chelper/kin_generic.c
Normal file
@@ -0,0 +1,52 @@
|
||||
// Generic cartesian kinematics stepper position calculation
|
||||
//
|
||||
// Copyright (C) 2024 Dmitry Butyugin <dmbutyugin@google.com>
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
#include <stddef.h> // offsetof
|
||||
#include <stdlib.h> // malloc
|
||||
#include <string.h> // memset
|
||||
#include "compiler.h" // __visible
|
||||
#include "itersolve.h" // struct stepper_kinematics
|
||||
#include "trapq.h" // move_get_coord
|
||||
|
||||
struct generic_cartesian_stepper {
|
||||
struct stepper_kinematics sk;
|
||||
struct coord a;
|
||||
};
|
||||
|
||||
static double
|
||||
generic_cartesian_stepper_calc_position(struct stepper_kinematics *sk
|
||||
, struct move *m, double move_time)
|
||||
{
|
||||
struct generic_cartesian_stepper *cs = container_of(
|
||||
sk, struct generic_cartesian_stepper, sk);
|
||||
struct coord c = move_get_coord(m, move_time);
|
||||
return cs->a.x * c.x + cs->a.y * c.y + cs->a.z * c.z;
|
||||
}
|
||||
|
||||
void __visible
|
||||
generic_cartesian_stepper_set_coeffs(struct stepper_kinematics *sk
|
||||
, double a_x, double a_y, double a_z)
|
||||
{
|
||||
struct generic_cartesian_stepper *cs = container_of(
|
||||
sk, struct generic_cartesian_stepper, sk);
|
||||
cs->a.x = a_x;
|
||||
cs->a.y = a_y;
|
||||
cs->a.z = a_z;
|
||||
cs->sk.active_flags = 0;
|
||||
if (a_x) cs->sk.active_flags |= AF_X;
|
||||
if (a_y) cs->sk.active_flags |= AF_Y;
|
||||
if (a_z) cs->sk.active_flags |= AF_Z;
|
||||
}
|
||||
|
||||
struct stepper_kinematics * __visible
|
||||
generic_cartesian_stepper_alloc(double a_x, double a_y, double a_z)
|
||||
{
|
||||
struct generic_cartesian_stepper *cs = malloc(sizeof(*cs));
|
||||
memset(cs, 0, sizeof(*cs));
|
||||
cs->sk.calc_position_cb = generic_cartesian_stepper_calc_position;
|
||||
generic_cartesian_stepper_set_coeffs(&cs->sk, a_x, a_y, a_z);
|
||||
return &cs->sk;
|
||||
}
|
||||
@@ -77,5 +77,6 @@ dual_carriage_alloc(void)
|
||||
struct dual_carriage_stepper *dc = malloc(sizeof(*dc));
|
||||
memset(dc, 0, sizeof(*dc));
|
||||
dc->m.move_t = 2. * DUMMY_T;
|
||||
dc->x_scale = dc->y_scale = 1.0;
|
||||
return &dc->sk;
|
||||
}
|
||||
|
||||
@@ -166,6 +166,38 @@ shaper_commanded_pos_post_fixup(struct stepper_kinematics *sk)
|
||||
sk->commanded_pos = is->orig_sk->commanded_pos;
|
||||
}
|
||||
|
||||
static void
|
||||
shaper_note_generation_time(struct input_shaper *is)
|
||||
{
|
||||
double pre_active = 0., post_active = 0.;
|
||||
if ((is->sk.active_flags & AF_X) && is->sx.num_pulses) {
|
||||
pre_active = is->sx.pulses[is->sx.num_pulses-1].t;
|
||||
post_active = -is->sx.pulses[0].t;
|
||||
}
|
||||
if ((is->sk.active_flags & AF_Y) && is->sy.num_pulses) {
|
||||
pre_active = is->sy.pulses[is->sy.num_pulses-1].t > pre_active
|
||||
? is->sy.pulses[is->sy.num_pulses-1].t : pre_active;
|
||||
post_active = -is->sy.pulses[0].t > post_active
|
||||
? -is->sy.pulses[0].t : post_active;
|
||||
}
|
||||
is->sk.gen_steps_pre_active = pre_active;
|
||||
is->sk.gen_steps_post_active = post_active;
|
||||
}
|
||||
|
||||
void __visible
|
||||
input_shaper_update_sk(struct stepper_kinematics *sk)
|
||||
{
|
||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||
if ((is->orig_sk->active_flags & (AF_X | AF_Y)) == (AF_X | AF_Y))
|
||||
is->sk.calc_position_cb = shaper_xy_calc_position;
|
||||
else if (is->orig_sk->active_flags & AF_X)
|
||||
is->sk.calc_position_cb = shaper_x_calc_position;
|
||||
else if (is->orig_sk->active_flags & AF_Y)
|
||||
is->sk.calc_position_cb = shaper_y_calc_position;
|
||||
is->sk.active_flags = is->orig_sk->active_flags;
|
||||
shaper_note_generation_time(is);
|
||||
}
|
||||
|
||||
int __visible
|
||||
input_shaper_set_sk(struct stepper_kinematics *sk
|
||||
, struct stepper_kinematics *orig_sk)
|
||||
@@ -190,24 +222,6 @@ input_shaper_set_sk(struct stepper_kinematics *sk
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
shaper_note_generation_time(struct input_shaper *is)
|
||||
{
|
||||
double pre_active = 0., post_active = 0.;
|
||||
if ((is->sk.active_flags & AF_X) && is->sx.num_pulses) {
|
||||
pre_active = is->sx.pulses[is->sx.num_pulses-1].t;
|
||||
post_active = -is->sx.pulses[0].t;
|
||||
}
|
||||
if ((is->sk.active_flags & AF_Y) && is->sy.num_pulses) {
|
||||
pre_active = is->sy.pulses[is->sy.num_pulses-1].t > pre_active
|
||||
? is->sy.pulses[is->sy.num_pulses-1].t : pre_active;
|
||||
post_active = -is->sy.pulses[0].t > post_active
|
||||
? -is->sy.pulses[0].t : post_active;
|
||||
}
|
||||
is->sk.gen_steps_pre_active = pre_active;
|
||||
is->sk.gen_steps_post_active = post_active;
|
||||
}
|
||||
|
||||
int __visible
|
||||
input_shaper_set_shaper_params(struct stepper_kinematics *sk, char axis
|
||||
, int n, double a[], double t[])
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <stdio.h> // fprintf
|
||||
#include <string.h> // strerror
|
||||
#include <time.h> // struct timespec
|
||||
#include <linux/prctl.h> // PR_SET_NAME
|
||||
#include <sys/prctl.h> // prctl
|
||||
#include "compiler.h" // __visible
|
||||
#include "pyhelper.h" // get_monotonic
|
||||
|
||||
@@ -92,3 +94,10 @@ dump_string(char *outbuf, int outbuf_size, char *inbuf, int inbuf_size)
|
||||
*o = '\0';
|
||||
return outbuf;
|
||||
}
|
||||
|
||||
// Set custom thread names
|
||||
int __visible
|
||||
set_thread_name(char name[16])
|
||||
{
|
||||
return prctl(PR_SET_NAME, name);
|
||||
}
|
||||
|
||||
@@ -7,5 +7,6 @@ void set_python_logging_callback(void (*func)(const char *));
|
||||
void errorf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
|
||||
void report_errno(char *where, int rc);
|
||||
char *dump_string(char *outbuf, int outbuf_size, char *inbuf, int inbuf_size);
|
||||
int set_thread_name(char name[16]);
|
||||
|
||||
#endif // pyhelper.h
|
||||
|
||||
@@ -43,6 +43,7 @@ struct serialqueue {
|
||||
uint8_t need_sync;
|
||||
int input_pos;
|
||||
// Threading
|
||||
char name[16];
|
||||
pthread_t tid;
|
||||
pthread_mutex_t lock; // protects variables below
|
||||
pthread_cond_t cond;
|
||||
@@ -612,6 +613,7 @@ static void *
|
||||
background_thread(void *data)
|
||||
{
|
||||
struct serialqueue *sq = data;
|
||||
set_thread_name(sq->name);
|
||||
pollreactor_run(sq->pr);
|
||||
|
||||
pthread_mutex_lock(&sq->lock);
|
||||
@@ -623,13 +625,16 @@ background_thread(void *data)
|
||||
|
||||
// Create a new 'struct serialqueue' object
|
||||
struct serialqueue * __visible
|
||||
serialqueue_alloc(int serial_fd, char serial_fd_type, int client_id)
|
||||
serialqueue_alloc(int serial_fd, char serial_fd_type, int client_id
|
||||
, char name[16])
|
||||
{
|
||||
struct serialqueue *sq = malloc(sizeof(*sq));
|
||||
memset(sq, 0, sizeof(*sq));
|
||||
sq->serial_fd = serial_fd;
|
||||
sq->serial_fd_type = serial_fd_type;
|
||||
sq->client_id = client_id;
|
||||
strncpy(sq->name, name, sizeof(sq->name));
|
||||
sq->name[sizeof(sq->name)-1] = '\0';
|
||||
|
||||
int ret = pipe(sq->pipe_fds);
|
||||
if (ret)
|
||||
|
||||
@@ -27,7 +27,7 @@ struct pull_queue_message {
|
||||
|
||||
struct serialqueue;
|
||||
struct serialqueue *serialqueue_alloc(int serial_fd, char serial_fd_type
|
||||
, int client_id);
|
||||
, int client_id, char name[16]);
|
||||
void serialqueue_exit(struct serialqueue *sq);
|
||||
void serialqueue_free(struct serialqueue *sq);
|
||||
struct command_queue *serialqueue_alloc_commandqueue(void);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Stepper pulse schedule compression
|
||||
//
|
||||
// Copyright (C) 2016-2021 Kevin O'Connor <kevin@koconnor.net>
|
||||
// Copyright (C) 2016-2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <stdlib.h> // malloc
|
||||
#include <string.h> // memset
|
||||
#include "compiler.h" // DIV_ROUND_UP
|
||||
#include "itersolve.h" // itersolve_generate_steps
|
||||
#include "pyhelper.h" // errorf
|
||||
#include "serialqueue.h" // struct queue_message
|
||||
#include "stepcompress.h" // stepcompress_alloc
|
||||
@@ -46,6 +47,8 @@ struct stepcompress {
|
||||
// History tracking
|
||||
int64_t last_position;
|
||||
struct list_head history_list;
|
||||
// Itersolve reference
|
||||
struct stepper_kinematics *sk;
|
||||
};
|
||||
|
||||
struct step_move {
|
||||
@@ -276,9 +279,9 @@ stepcompress_set_invert_sdir(struct stepcompress *sc, uint32_t invert_sdir)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to free items from the history_list
|
||||
static void
|
||||
free_history(struct stepcompress *sc, uint64_t end_clock)
|
||||
// Expire the stepcompress history older than the given clock
|
||||
void
|
||||
stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock)
|
||||
{
|
||||
while (!list_empty(&sc->history_list)) {
|
||||
struct history_steps *hs = list_last_entry(
|
||||
@@ -290,13 +293,6 @@ free_history(struct stepcompress *sc, uint64_t end_clock)
|
||||
}
|
||||
}
|
||||
|
||||
// Expire the stepcompress history older than the given clock
|
||||
static void
|
||||
stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock)
|
||||
{
|
||||
free_history(sc, end_clock);
|
||||
}
|
||||
|
||||
// Free memory associated with a 'stepcompress' object
|
||||
void __visible
|
||||
stepcompress_free(struct stepcompress *sc)
|
||||
@@ -305,7 +301,7 @@ stepcompress_free(struct stepcompress *sc)
|
||||
return;
|
||||
free(sc->queue);
|
||||
message_queue_free(&sc->msg_queue);
|
||||
free_history(sc, UINT64_MAX);
|
||||
stepcompress_history_expire(sc, UINT64_MAX);
|
||||
free(sc);
|
||||
}
|
||||
|
||||
@@ -321,6 +317,12 @@ stepcompress_get_step_dir(struct stepcompress *sc)
|
||||
return sc->next_step_dir;
|
||||
}
|
||||
|
||||
struct list_head *
|
||||
stepcompress_get_msg_queue(struct stepcompress *sc)
|
||||
{
|
||||
return &sc->msg_queue;
|
||||
}
|
||||
|
||||
// Determine the "print time" of the last_step_clock
|
||||
static void
|
||||
calc_last_step_print_time(struct stepcompress *sc)
|
||||
@@ -330,7 +332,7 @@ calc_last_step_print_time(struct stepcompress *sc)
|
||||
}
|
||||
|
||||
// Set the conversion rate of 'print_time' to mcu clock
|
||||
static void
|
||||
void
|
||||
stepcompress_set_time(struct stepcompress *sc
|
||||
, double time_offset, double mcu_freq)
|
||||
{
|
||||
@@ -664,164 +666,25 @@ stepcompress_extract_old(struct stepcompress *sc, struct pull_history_steps *p
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* Step compress synchronization
|
||||
****************************************************************/
|
||||
|
||||
// The steppersync object is used to synchronize the output of mcu
|
||||
// step commands. The mcu can only queue a limited number of step
|
||||
// commands - this code tracks when items on the mcu step queue become
|
||||
// free so that new commands can be transmitted. It also ensures the
|
||||
// mcu step queue is ordered between steppers so that no stepper
|
||||
// starves the other steppers of space in the mcu step queue.
|
||||
|
||||
struct steppersync {
|
||||
// Serial port
|
||||
struct serialqueue *sq;
|
||||
struct command_queue *cq;
|
||||
// Storage for associated stepcompress objects
|
||||
struct stepcompress **sc_list;
|
||||
int sc_num;
|
||||
// Storage for list of pending move clocks
|
||||
uint64_t *move_clocks;
|
||||
int num_move_clocks;
|
||||
};
|
||||
|
||||
// Allocate a new 'steppersync' object
|
||||
struct steppersync * __visible
|
||||
steppersync_alloc(struct serialqueue *sq, struct stepcompress **sc_list
|
||||
, int sc_num, int move_num)
|
||||
{
|
||||
struct steppersync *ss = malloc(sizeof(*ss));
|
||||
memset(ss, 0, sizeof(*ss));
|
||||
ss->sq = sq;
|
||||
ss->cq = serialqueue_alloc_commandqueue();
|
||||
|
||||
ss->sc_list = malloc(sizeof(*sc_list)*sc_num);
|
||||
memcpy(ss->sc_list, sc_list, sizeof(*sc_list)*sc_num);
|
||||
ss->sc_num = sc_num;
|
||||
|
||||
ss->move_clocks = malloc(sizeof(*ss->move_clocks)*move_num);
|
||||
memset(ss->move_clocks, 0, sizeof(*ss->move_clocks)*move_num);
|
||||
ss->num_move_clocks = move_num;
|
||||
|
||||
return ss;
|
||||
}
|
||||
|
||||
// Free memory associated with a 'steppersync' object
|
||||
// Store a reference to stepper_kinematics
|
||||
void __visible
|
||||
steppersync_free(struct steppersync *ss)
|
||||
stepcompress_set_stepper_kinematics(struct stepcompress *sc
|
||||
, struct stepper_kinematics *sk)
|
||||
{
|
||||
if (!ss)
|
||||
return;
|
||||
free(ss->sc_list);
|
||||
free(ss->move_clocks);
|
||||
serialqueue_free_commandqueue(ss->cq);
|
||||
free(ss);
|
||||
sc->sk = sk;
|
||||
}
|
||||
|
||||
// Set the conversion rate of 'print_time' to mcu clock
|
||||
void __visible
|
||||
steppersync_set_time(struct steppersync *ss, double time_offset
|
||||
, double mcu_freq)
|
||||
// Generate steps (via itersolve) and flush
|
||||
int32_t
|
||||
stepcompress_generate_steps(struct stepcompress *sc, double gen_steps_time
|
||||
, uint64_t flush_clock)
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<ss->sc_num; i++) {
|
||||
struct stepcompress *sc = ss->sc_list[i];
|
||||
stepcompress_set_time(sc, time_offset, mcu_freq);
|
||||
}
|
||||
}
|
||||
|
||||
// Expire the stepcompress history before the given clock time
|
||||
static void
|
||||
steppersync_history_expire(struct steppersync *ss, uint64_t end_clock)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < ss->sc_num; i++)
|
||||
{
|
||||
struct stepcompress *sc = ss->sc_list[i];
|
||||
stepcompress_history_expire(sc, end_clock);
|
||||
}
|
||||
}
|
||||
|
||||
// Implement a binary heap algorithm to track when the next available
|
||||
// 'struct move' in the mcu will be available
|
||||
static void
|
||||
heap_replace(struct steppersync *ss, uint64_t req_clock)
|
||||
{
|
||||
uint64_t *mc = ss->move_clocks;
|
||||
int nmc = ss->num_move_clocks, pos = 0;
|
||||
for (;;) {
|
||||
int child1_pos = 2*pos+1, child2_pos = 2*pos+2;
|
||||
uint64_t child2_clock = child2_pos < nmc ? mc[child2_pos] : UINT64_MAX;
|
||||
uint64_t child1_clock = child1_pos < nmc ? mc[child1_pos] : UINT64_MAX;
|
||||
if (req_clock <= child1_clock && req_clock <= child2_clock) {
|
||||
mc[pos] = req_clock;
|
||||
break;
|
||||
}
|
||||
if (child1_clock < child2_clock) {
|
||||
mc[pos] = child1_clock;
|
||||
pos = child1_pos;
|
||||
} else {
|
||||
mc[pos] = child2_clock;
|
||||
pos = child2_pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find and transmit any scheduled steps prior to the given 'move_clock'
|
||||
int __visible
|
||||
steppersync_flush(struct steppersync *ss, uint64_t move_clock
|
||||
, uint64_t clear_history_clock)
|
||||
{
|
||||
// Flush each stepcompress to the specified move_clock
|
||||
int i;
|
||||
for (i=0; i<ss->sc_num; i++) {
|
||||
int ret = stepcompress_flush(ss->sc_list[i], move_clock);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Order commands by the reqclock of each pending command
|
||||
struct list_head msgs;
|
||||
list_init(&msgs);
|
||||
for (;;) {
|
||||
// Find message with lowest reqclock
|
||||
uint64_t req_clock = MAX_CLOCK;
|
||||
struct queue_message *qm = NULL;
|
||||
for (i=0; i<ss->sc_num; i++) {
|
||||
struct stepcompress *sc = ss->sc_list[i];
|
||||
if (!list_empty(&sc->msg_queue)) {
|
||||
struct queue_message *m = list_first_entry(
|
||||
&sc->msg_queue, struct queue_message, node);
|
||||
if (m->req_clock < req_clock) {
|
||||
qm = m;
|
||||
req_clock = m->req_clock;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!qm || (qm->min_clock && req_clock > move_clock))
|
||||
break;
|
||||
|
||||
uint64_t next_avail = ss->move_clocks[0];
|
||||
if (qm->min_clock)
|
||||
// The qm->min_clock field is overloaded to indicate that
|
||||
// the command uses the 'move queue' and to store the time
|
||||
// that move queue item becomes available.
|
||||
heap_replace(ss, qm->min_clock);
|
||||
// Reset the min_clock to its normal meaning (minimum transmit time)
|
||||
qm->min_clock = next_avail;
|
||||
|
||||
// Batch this command
|
||||
list_del(&qm->node);
|
||||
list_add_tail(&qm->node, &msgs);
|
||||
}
|
||||
|
||||
// Transmit commands
|
||||
if (!list_empty(&msgs))
|
||||
serialqueue_send_batch(ss->sq, ss->cq, &msgs);
|
||||
|
||||
steppersync_history_expire(ss, clear_history_clock);
|
||||
return 0;
|
||||
if (!sc->sk)
|
||||
return 0;
|
||||
// Generate steps
|
||||
int32_t ret = itersolve_generate_steps(sc->sk, sc, gen_steps_time);
|
||||
if (ret)
|
||||
return ret;
|
||||
// Flush steps
|
||||
return stepcompress_flush(sc, flush_clock);
|
||||
}
|
||||
|
||||
@@ -17,9 +17,13 @@ void stepcompress_fill(struct stepcompress *sc, uint32_t max_error
|
||||
, int32_t set_next_step_dir_msgtag);
|
||||
void stepcompress_set_invert_sdir(struct stepcompress *sc
|
||||
, uint32_t invert_sdir);
|
||||
void stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock);
|
||||
void stepcompress_free(struct stepcompress *sc);
|
||||
uint32_t stepcompress_get_oid(struct stepcompress *sc);
|
||||
int stepcompress_get_step_dir(struct stepcompress *sc);
|
||||
struct list_head *stepcompress_get_msg_queue(struct stepcompress *sc);
|
||||
void stepcompress_set_time(struct stepcompress *sc
|
||||
, double time_offset, double mcu_freq);
|
||||
int stepcompress_append(struct stepcompress *sc, int sdir
|
||||
, double print_time, double step_time);
|
||||
int stepcompress_commit(struct stepcompress *sc);
|
||||
@@ -34,15 +38,11 @@ int stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock
|
||||
int stepcompress_extract_old(struct stepcompress *sc
|
||||
, struct pull_history_steps *p, int max
|
||||
, uint64_t start_clock, uint64_t end_clock);
|
||||
|
||||
struct serialqueue;
|
||||
struct steppersync *steppersync_alloc(
|
||||
struct serialqueue *sq, struct stepcompress **sc_list, int sc_num
|
||||
, int move_num);
|
||||
void steppersync_free(struct steppersync *ss);
|
||||
void steppersync_set_time(struct steppersync *ss, double time_offset
|
||||
, double mcu_freq);
|
||||
int steppersync_flush(struct steppersync *ss, uint64_t move_clock
|
||||
, uint64_t clear_history_clock);
|
||||
struct stepper_kinematics;
|
||||
void stepcompress_set_stepper_kinematics(struct stepcompress *sc
|
||||
, struct stepper_kinematics *sk);
|
||||
int32_t stepcompress_generate_steps(struct stepcompress *sc
|
||||
, double gen_steps_time
|
||||
, uint64_t flush_clock);
|
||||
|
||||
#endif // stepcompress.h
|
||||
|
||||
177
klippy/chelper/steppersync.c
Normal file
177
klippy/chelper/steppersync.c
Normal file
@@ -0,0 +1,177 @@
|
||||
// Stepper step transmit synchronization
|
||||
//
|
||||
// Copyright (C) 2016-2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
// The steppersync object is used to synchronize the output of mcu
|
||||
// step commands. The mcu can only queue a limited number of step
|
||||
// commands - this code tracks when items on the mcu step queue become
|
||||
// free so that new commands can be transmitted. It also ensures the
|
||||
// mcu step queue is ordered between steppers so that no stepper
|
||||
// starves the other steppers of space in the mcu step queue.
|
||||
|
||||
#include <stddef.h> // offsetof
|
||||
#include <stdlib.h> // malloc
|
||||
#include <string.h> // memset
|
||||
#include "compiler.h" // __visible
|
||||
#include "serialqueue.h" // struct queue_message
|
||||
#include "stepcompress.h" // stepcompress_flush
|
||||
#include "steppersync.h" // steppersync_alloc
|
||||
|
||||
struct steppersync {
|
||||
// Serial port
|
||||
struct serialqueue *sq;
|
||||
struct command_queue *cq;
|
||||
// Storage for associated stepcompress objects
|
||||
struct stepcompress **sc_list;
|
||||
int sc_num;
|
||||
// Storage for list of pending move clocks
|
||||
uint64_t *move_clocks;
|
||||
int num_move_clocks;
|
||||
};
|
||||
|
||||
// Allocate a new 'steppersync' object
|
||||
struct steppersync * __visible
|
||||
steppersync_alloc(struct serialqueue *sq, struct stepcompress **sc_list
|
||||
, int sc_num, int move_num)
|
||||
{
|
||||
struct steppersync *ss = malloc(sizeof(*ss));
|
||||
memset(ss, 0, sizeof(*ss));
|
||||
ss->sq = sq;
|
||||
ss->cq = serialqueue_alloc_commandqueue();
|
||||
|
||||
ss->sc_list = malloc(sizeof(*sc_list)*sc_num);
|
||||
memcpy(ss->sc_list, sc_list, sizeof(*sc_list)*sc_num);
|
||||
ss->sc_num = sc_num;
|
||||
|
||||
ss->move_clocks = malloc(sizeof(*ss->move_clocks)*move_num);
|
||||
memset(ss->move_clocks, 0, sizeof(*ss->move_clocks)*move_num);
|
||||
ss->num_move_clocks = move_num;
|
||||
|
||||
return ss;
|
||||
}
|
||||
|
||||
// Free memory associated with a 'steppersync' object
|
||||
void __visible
|
||||
steppersync_free(struct steppersync *ss)
|
||||
{
|
||||
if (!ss)
|
||||
return;
|
||||
free(ss->sc_list);
|
||||
free(ss->move_clocks);
|
||||
serialqueue_free_commandqueue(ss->cq);
|
||||
free(ss);
|
||||
}
|
||||
|
||||
// Set the conversion rate of 'print_time' to mcu clock
|
||||
void __visible
|
||||
steppersync_set_time(struct steppersync *ss, double time_offset
|
||||
, double mcu_freq)
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<ss->sc_num; i++) {
|
||||
struct stepcompress *sc = ss->sc_list[i];
|
||||
stepcompress_set_time(sc, time_offset, mcu_freq);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate steps and flush stepcompress objects
|
||||
int32_t __visible
|
||||
steppersync_generate_steps(struct steppersync *ss, double gen_steps_time
|
||||
, uint64_t flush_clock)
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<ss->sc_num; i++) {
|
||||
struct stepcompress *sc = ss->sc_list[i];
|
||||
int32_t ret = stepcompress_generate_steps(sc, gen_steps_time
|
||||
, flush_clock);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Expire the stepcompress history before the given clock time
|
||||
void __visible
|
||||
steppersync_history_expire(struct steppersync *ss, uint64_t end_clock)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < ss->sc_num; i++) {
|
||||
struct stepcompress *sc = ss->sc_list[i];
|
||||
stepcompress_history_expire(sc, end_clock);
|
||||
}
|
||||
}
|
||||
|
||||
// Implement a binary heap algorithm to track when the next available
|
||||
// 'struct move' in the mcu will be available
|
||||
static void
|
||||
heap_replace(struct steppersync *ss, uint64_t req_clock)
|
||||
{
|
||||
uint64_t *mc = ss->move_clocks;
|
||||
int nmc = ss->num_move_clocks, pos = 0;
|
||||
for (;;) {
|
||||
int child1_pos = 2*pos+1, child2_pos = 2*pos+2;
|
||||
uint64_t child2_clock = child2_pos < nmc ? mc[child2_pos] : UINT64_MAX;
|
||||
uint64_t child1_clock = child1_pos < nmc ? mc[child1_pos] : UINT64_MAX;
|
||||
if (req_clock <= child1_clock && req_clock <= child2_clock) {
|
||||
mc[pos] = req_clock;
|
||||
break;
|
||||
}
|
||||
if (child1_clock < child2_clock) {
|
||||
mc[pos] = child1_clock;
|
||||
pos = child1_pos;
|
||||
} else {
|
||||
mc[pos] = child2_clock;
|
||||
pos = child2_pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find and transmit any scheduled steps prior to the given 'move_clock'
|
||||
int __visible
|
||||
steppersync_flush(struct steppersync *ss, uint64_t move_clock)
|
||||
{
|
||||
// Order commands by the reqclock of each pending command
|
||||
struct list_head msgs;
|
||||
list_init(&msgs);
|
||||
for (;;) {
|
||||
// Find message with lowest reqclock
|
||||
uint64_t req_clock = MAX_CLOCK;
|
||||
struct queue_message *qm = NULL;
|
||||
int i;
|
||||
for (i=0; i<ss->sc_num; i++) {
|
||||
struct stepcompress *sc = ss->sc_list[i];
|
||||
struct list_head *sc_mq = stepcompress_get_msg_queue(sc);
|
||||
if (!list_empty(sc_mq)) {
|
||||
struct queue_message *m = list_first_entry(
|
||||
sc_mq, struct queue_message, node);
|
||||
if (m->req_clock < req_clock) {
|
||||
qm = m;
|
||||
req_clock = m->req_clock;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!qm || (qm->min_clock && req_clock > move_clock))
|
||||
break;
|
||||
|
||||
uint64_t next_avail = ss->move_clocks[0];
|
||||
if (qm->min_clock)
|
||||
// The qm->min_clock field is overloaded to indicate that
|
||||
// the command uses the 'move queue' and to store the time
|
||||
// that move queue item becomes available.
|
||||
heap_replace(ss, qm->min_clock);
|
||||
// Reset the min_clock to its normal meaning (minimum transmit time)
|
||||
qm->min_clock = next_avail;
|
||||
|
||||
// Batch this command
|
||||
list_del(&qm->node);
|
||||
list_add_tail(&qm->node, &msgs);
|
||||
}
|
||||
|
||||
// Transmit commands
|
||||
if (!list_empty(&msgs))
|
||||
serialqueue_send_batch(ss->sq, ss->cq, &msgs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
18
klippy/chelper/steppersync.h
Normal file
18
klippy/chelper/steppersync.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef STEPPERSYNC_H
|
||||
#define STEPPERSYNC_H
|
||||
|
||||
#include <stdint.h> // uint64_t
|
||||
|
||||
struct serialqueue;
|
||||
struct steppersync *steppersync_alloc(
|
||||
struct serialqueue *sq, struct stepcompress **sc_list, int sc_num
|
||||
, int move_num);
|
||||
void steppersync_free(struct steppersync *ss);
|
||||
void steppersync_set_time(struct steppersync *ss, double time_offset
|
||||
, double mcu_freq);
|
||||
int32_t steppersync_generate_steps(struct steppersync *ss, double gen_steps_time
|
||||
, uint64_t flush_clock);
|
||||
void steppersync_history_expire(struct steppersync *ss, uint64_t end_clock);
|
||||
int steppersync_flush(struct steppersync *ss, uint64_t move_clock);
|
||||
|
||||
#endif // steppersync.h
|
||||
@@ -96,6 +96,7 @@ class ADS1220:
|
||||
self.printer, self._process_batch, self._start_measurements,
|
||||
self._finish_measurements, UPDATE_INTERVAL)
|
||||
# Command Configuration
|
||||
self.attach_probe_cmd = None
|
||||
mcu.add_config_cmd(
|
||||
"config_ads1220 oid=%d spi_oid=%d data_ready_pin=%s"
|
||||
% (self.oid, self.spi.get_oid(), self.data_ready_pin))
|
||||
@@ -108,6 +109,8 @@ class ADS1220:
|
||||
cmdqueue = self.spi.get_command_queue()
|
||||
self.query_ads1220_cmd = self.mcu.lookup_command(
|
||||
"query_ads1220 oid=%c rest_ticks=%u", cq=cmdqueue)
|
||||
self.attach_probe_cmd = self.mcu.lookup_command(
|
||||
"ads1220_attach_load_cell_probe oid=%c load_cell_probe_oid=%c")
|
||||
self.ffreader.setup_query_command("query_ads1220_status oid=%c",
|
||||
oid=self.oid, cq=cmdqueue)
|
||||
|
||||
@@ -126,6 +129,9 @@ class ADS1220:
|
||||
def add_client(self, callback):
|
||||
self.batch_bulk.add_client(callback)
|
||||
|
||||
def attach_load_cell_probe(self, load_cell_probe_oid):
|
||||
self.attach_probe_cmd.send([self.oid, load_cell_probe_oid])
|
||||
|
||||
# Measurement decoding
|
||||
def _convert_samples(self, samples):
|
||||
adc_factor = 1. / (1 << 23)
|
||||
|
||||
@@ -210,28 +210,28 @@ class ADS1X1X_chip:
|
||||
raise pins.error('ADS1x1x pin %s is not valid' % \
|
||||
pin_params['pin'])
|
||||
|
||||
config = 0
|
||||
config |= (ADS1X1X_OS['OS_SINGLE'] & \
|
||||
ADS1X1X_REG_CONFIG['OS_MASK'])
|
||||
config |= (ADS1X1X_MUX[pin_params['pin']] & \
|
||||
ADS1X1X_REG_CONFIG['MULTIPLEXER_MASK'])
|
||||
config |= (self.pga & ADS1X1X_REG_CONFIG['PGA_MASK'])
|
||||
pcfg = 0
|
||||
pcfg |= (ADS1X1X_OS['OS_SINGLE'] & \
|
||||
ADS1X1X_REG_CONFIG['OS_MASK'])
|
||||
pcfg |= (ADS1X1X_MUX[pin_params['pin']] & \
|
||||
ADS1X1X_REG_CONFIG['MULTIPLEXER_MASK'])
|
||||
pcfg |= (self.pga & ADS1X1X_REG_CONFIG['PGA_MASK'])
|
||||
# Have to use single mode, because in continuous, it never reaches
|
||||
# idle state, which we use to determine if the sampling is done.
|
||||
config |= (ADS1X1X_MODE['single'] & \
|
||||
pcfg |= (ADS1X1X_MODE['single'] & \
|
||||
ADS1X1X_REG_CONFIG['MODE_MASK'])
|
||||
# lowest sample rate per default, until report time has been set in
|
||||
# setup_adc_sample
|
||||
config |= (self.comp_mode \
|
||||
& ADS1X1X_REG_CONFIG['COMPARATOR_MODE_MASK'])
|
||||
config |= (self.comp_polarity \
|
||||
& ADS1X1X_REG_CONFIG['COMPARATOR_POLARITY_MASK'])
|
||||
config |= (self.comp_latching \
|
||||
& ADS1X1X_REG_CONFIG['COMPARATOR_LATCHING_MASK'])
|
||||
config |= (self.comp_queue \
|
||||
& ADS1X1X_REG_CONFIG['COMPARATOR_QUEUE_MASK'])
|
||||
pcfg |= (self.comp_mode \
|
||||
& ADS1X1X_REG_CONFIG['COMPARATOR_MODE_MASK'])
|
||||
pcfg |= (self.comp_polarity \
|
||||
& ADS1X1X_REG_CONFIG['COMPARATOR_POLARITY_MASK'])
|
||||
pcfg |= (self.comp_latching \
|
||||
& ADS1X1X_REG_CONFIG['COMPARATOR_LATCHING_MASK'])
|
||||
pcfg |= (self.comp_queue \
|
||||
& ADS1X1X_REG_CONFIG['COMPARATOR_QUEUE_MASK'])
|
||||
|
||||
pin_obj = ADS1X1X_pin(self, config)
|
||||
pin_obj = ADS1X1X_pin(self, pcfg)
|
||||
if pin in self._pins:
|
||||
raise pins.error(
|
||||
'pin %s for chip %s is used multiple times' \
|
||||
@@ -250,8 +250,8 @@ class ADS1X1X_chip:
|
||||
logging.exception("ADS1X1X: error while resetting device")
|
||||
|
||||
def is_ready(self):
|
||||
config = self._read_register(ADS1X1X_REG_POINTER['CONFIG'])
|
||||
return bool((config & ADS1X1X_REG_CONFIG['OS_MASK']) == \
|
||||
cfg = self._read_register(ADS1X1X_REG_POINTER['CONFIG'])
|
||||
return bool((cfg & ADS1X1X_REG_CONFIG['OS_MASK']) == \
|
||||
ADS1X1X_OS['OS_IDLE'])
|
||||
|
||||
def calculate_sample_rate(self):
|
||||
@@ -281,7 +281,7 @@ class ADS1X1X_chip:
|
||||
(sample_rate, sample_rate_bits) = self.calculate_sample_rate()
|
||||
|
||||
for pin in self._pins.values():
|
||||
pin.config = (pin.config & ~ADS1X1X_REG_CONFIG['DATA_RATE_MASK']) \
|
||||
pin.pcfg = (pin.pcfg & ~ADS1X1X_REG_CONFIG['DATA_RATE_MASK']) \
|
||||
| (sample_rate_bits & ADS1X1X_REG_CONFIG['DATA_RATE_MASK'])
|
||||
|
||||
self.delay = 1 / float(sample_rate)
|
||||
@@ -289,7 +289,7 @@ class ADS1X1X_chip:
|
||||
def sample(self, pin):
|
||||
with self._mutex:
|
||||
try:
|
||||
self._write_register(ADS1X1X_REG_POINTER['CONFIG'], pin.config)
|
||||
self._write_register(ADS1X1X_REG_POINTER['CONFIG'], pin.pcfg)
|
||||
self._reactor.pause(self._reactor.monotonic() + self.delay)
|
||||
start_time = self._reactor.monotonic()
|
||||
while not self.is_ready():
|
||||
@@ -318,10 +318,10 @@ class ADS1X1X_chip:
|
||||
self._i2c.i2c_write(data)
|
||||
|
||||
class ADS1X1X_pin:
|
||||
def __init__(self, chip, config):
|
||||
def __init__(self, chip, pcfg):
|
||||
self.mcu = chip.mcu
|
||||
self.chip = chip
|
||||
self.config = config
|
||||
self.pcfg = pcfg
|
||||
|
||||
self.invalid_count = 0
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ class AngleCalibration:
|
||||
return None
|
||||
return self.mcu_stepper.mcu_to_commanded_position(self.mcu_pos_offset)
|
||||
def load_calibration(self, angles):
|
||||
# Calculate linear intepolation calibration buckets by solving
|
||||
# Calculate linear interpolation calibration buckets by solving
|
||||
# linear equations
|
||||
angle_max = 1 << ANGLE_BITS
|
||||
calibration_count = 1 << CALIBRATION_BITS
|
||||
|
||||
@@ -34,7 +34,7 @@ def constrain(val, min_val, max_val):
|
||||
def lerp(t, v0, v1):
|
||||
return (1. - t) * v0 + t * v1
|
||||
|
||||
# retreive commma separated pair from config
|
||||
# retrieve comma separated pair from config
|
||||
def parse_config_pair(config, option, default, minval=None, maxval=None):
|
||||
pair = config.getintlist(option, (default, default))
|
||||
if len(pair) != 2:
|
||||
@@ -54,7 +54,7 @@ def parse_config_pair(config, option, default, minval=None, maxval=None):
|
||||
% (option, str(maxval)))
|
||||
return pair
|
||||
|
||||
# retreive commma separated pair from a g-code command
|
||||
# retrieve comma separated pair from a g-code command
|
||||
def parse_gcmd_pair(gcmd, name, minval=None, maxval=None):
|
||||
try:
|
||||
pair = [int(v.strip()) for v in gcmd.get(name).split(',')]
|
||||
@@ -74,7 +74,7 @@ def parse_gcmd_pair(gcmd, name, minval=None, maxval=None):
|
||||
% (name, maxval))
|
||||
return pair
|
||||
|
||||
# retreive commma separated coordinate from a g-code command
|
||||
# retrieve comma separated coordinate from a g-code command
|
||||
def parse_gcmd_coord(gcmd, name):
|
||||
try:
|
||||
v1, v2 = [float(v.strip()) for v in gcmd.get(name).split(',')]
|
||||
@@ -186,7 +186,8 @@ class BedMesh:
|
||||
self.last_position[2] -= self.fade_target
|
||||
else:
|
||||
# return current position minus the current z-adjustment
|
||||
x, y, z, e = self.toolhead.get_position()
|
||||
cur_pos = self.toolhead.get_position()
|
||||
x, y, z = cur_pos[:3]
|
||||
max_adj = self.z_mesh.calc_z(x, y)
|
||||
factor = 1.
|
||||
z_adj = max_adj - self.fade_target
|
||||
@@ -202,19 +203,19 @@ class BedMesh:
|
||||
(self.fade_dist - z_adj))
|
||||
factor = constrain(factor, 0., 1.)
|
||||
final_z_adj = factor * z_adj + self.fade_target
|
||||
self.last_position[:] = [x, y, z - final_z_adj, e]
|
||||
self.last_position[:] = [x, y, z - final_z_adj] + cur_pos[3:]
|
||||
return list(self.last_position)
|
||||
def move(self, newpos, speed):
|
||||
factor = self.get_z_factor(newpos[2])
|
||||
if self.z_mesh is None or not factor:
|
||||
# No mesh calibrated, or mesh leveling phased out.
|
||||
x, y, z, e = newpos
|
||||
x, y, z = newpos[:3]
|
||||
if self.log_fade_complete:
|
||||
self.log_fade_complete = False
|
||||
logging.info(
|
||||
"bed_mesh fade complete: Current Z: %.4f fade_target: %.4f "
|
||||
% (z, self.fade_target))
|
||||
self.toolhead.move([x, y, z + self.fade_target, e], speed)
|
||||
self.toolhead.move([x, y, z + self.fade_target] + newpos[3:], speed)
|
||||
else:
|
||||
self.splitter.build_move(self.last_position, newpos, factor)
|
||||
while not self.splitter.traverse_complete:
|
||||
@@ -913,7 +914,7 @@ class ProbeManager:
|
||||
for i in range(y_cnt):
|
||||
for j in range(x_cnt):
|
||||
if not i % 2:
|
||||
# move in positive directon
|
||||
# move in positive direction
|
||||
pos_x = min_x + j * x_dist
|
||||
else:
|
||||
# move in negative direction
|
||||
@@ -1163,7 +1164,7 @@ class ProbeManager:
|
||||
|
||||
def _gen_arc(self, origin, radius, start, step, count):
|
||||
end = start + step * count
|
||||
# create a segent for every 3 degress of travel
|
||||
# create a segent for every 3 degrees of travel
|
||||
for angle in range(start, end, step):
|
||||
rad = math.radians(angle % 360)
|
||||
opp = math.sin(rad) * radius
|
||||
@@ -1273,7 +1274,7 @@ class MoveSplitter:
|
||||
self.z_offset = self._calc_z_offset(prev_pos)
|
||||
self.traverse_complete = False
|
||||
self.distance_checked = 0.
|
||||
axes_d = [self.next_pos[i] - self.prev_pos[i] for i in range(4)]
|
||||
axes_d = [np - pp for np, pp in zip(self.next_pos, self.prev_pos)]
|
||||
self.total_move_length = math.sqrt(sum([d*d for d in axes_d[:3]]))
|
||||
self.axis_move = [not isclose(d, 0., abs_tol=1e-10) for d in axes_d]
|
||||
def _calc_z_offset(self, pos):
|
||||
@@ -1286,7 +1287,7 @@ class MoveSplitter:
|
||||
raise self.gcode.error(
|
||||
"bed_mesh: Slice distance is negative "
|
||||
"or greater than entire move length")
|
||||
for i in range(4):
|
||||
for i in range(len(self.next_pos)):
|
||||
if self.axis_move[i]:
|
||||
self.current_pos[i] = lerp(
|
||||
t, self.prev_pos[i], self.next_pos[i])
|
||||
@@ -1301,9 +1302,9 @@ class MoveSplitter:
|
||||
next_z = self._calc_z_offset(self.current_pos)
|
||||
if abs(next_z - self.z_offset) >= self.split_delta_z:
|
||||
self.z_offset = next_z
|
||||
return self.current_pos[0], self.current_pos[1], \
|
||||
self.current_pos[2] + self.z_offset, \
|
||||
self.current_pos[3]
|
||||
newpos = list(self.current_pos)
|
||||
newpos[2] += self.z_offset
|
||||
return newpos
|
||||
# end of move reached
|
||||
self.current_pos[:] = self.next_pos
|
||||
self.z_offset = self._calc_z_offset(self.current_pos)
|
||||
|
||||
@@ -24,12 +24,14 @@ class BedTilt:
|
||||
def handle_connect(self):
|
||||
self.toolhead = self.printer.lookup_object('toolhead')
|
||||
def get_position(self):
|
||||
x, y, z, e = self.toolhead.get_position()
|
||||
return [x, y, z - x*self.x_adjust - y*self.y_adjust - self.z_adjust, e]
|
||||
pos = self.toolhead.get_position()
|
||||
x, y, z = pos[:3]
|
||||
z -= x*self.x_adjust + y*self.y_adjust + self.z_adjust
|
||||
return [x, y, z] + pos[3:]
|
||||
def move(self, newpos, speed):
|
||||
x, y, z, e = newpos
|
||||
self.toolhead.move([x, y, z + x*self.x_adjust + y*self.y_adjust
|
||||
+ self.z_adjust, e], speed)
|
||||
x, y, z = newpos[:3]
|
||||
z += x*self.x_adjust + y*self.y_adjust + self.z_adjust
|
||||
self.toolhead.move([x, y, z] + newpos[3:], speed)
|
||||
def update_adjust(self, x_adjust, y_adjust, z_adjust):
|
||||
self.x_adjust = x_adjust
|
||||
self.y_adjust = y_adjust
|
||||
|
||||
@@ -64,7 +64,11 @@ class BLTouchProbe:
|
||||
self.cmd_helper = probe.ProbeCommandHelper(
|
||||
config, self, self.mcu_endstop.query_endstop)
|
||||
self.probe_offsets = probe.ProbeOffsetsHelper(config)
|
||||
self.probe_session = probe.ProbeSessionHelper(config, self)
|
||||
self.param_helper = probe.ProbeParameterHelper(config)
|
||||
self.homing_helper = probe.HomingViaProbeHelper(config, self,
|
||||
self.param_helper)
|
||||
self.probe_session = probe.ProbeSessionHelper(
|
||||
config, self.param_helper, self.homing_helper.start_probe_session)
|
||||
# Register BLTOUCH_DEBUG command
|
||||
self.gcode = self.printer.lookup_object('gcode')
|
||||
self.gcode.register_command("BLTOUCH_DEBUG", self.cmd_BLTOUCH_DEBUG,
|
||||
@@ -75,7 +79,7 @@ class BLTouchProbe:
|
||||
self.printer.register_event_handler("klippy:connect",
|
||||
self.handle_connect)
|
||||
def get_probe_params(self, gcmd=None):
|
||||
return self.probe_session.get_probe_params(gcmd)
|
||||
return self.param_helper.get_probe_params(gcmd)
|
||||
def get_offsets(self):
|
||||
return self.probe_offsets.get_offsets()
|
||||
def get_status(self, eventtime):
|
||||
@@ -191,9 +195,6 @@ class BLTouchProbe:
|
||||
self.verify_raise_probe()
|
||||
self.sync_print_time()
|
||||
self.multi = 'OFF'
|
||||
def probing_move(self, pos, speed):
|
||||
phoming = self.printer.lookup_object('homing')
|
||||
return phoming.probing_move(self, pos, speed)
|
||||
def probe_prepare(self, hmove):
|
||||
if self.multi == 'OFF' or self.multi == 'FIRST':
|
||||
self.lower_probe()
|
||||
|
||||
@@ -284,7 +284,7 @@ class BME280:
|
||||
self.chip_type, self.i2c.i2c_address))
|
||||
|
||||
# Reset chip
|
||||
self.write_register('RESET', [RESET_CHIP_VALUE], wait=True)
|
||||
self.write_register('RESET', [RESET_CHIP_VALUE])
|
||||
self.reactor.pause(self.reactor.monotonic() + .5)
|
||||
|
||||
# Make sure non-volatile memory has been copied to registers
|
||||
@@ -394,7 +394,7 @@ class BME280:
|
||||
self.write_register('CTRL_HUM', self.os_hum)
|
||||
# Enter normal (periodic) mode
|
||||
meas = self.os_temp << 5 | self.os_pres << 2 | MODE_PERIODIC
|
||||
self.write_register('CTRL_MEAS', meas, wait=True)
|
||||
self.write_register('CTRL_MEAS', meas)
|
||||
|
||||
if self.chip_type == 'BME680':
|
||||
self.write_register('CONFIG', self.iir_filter << 2)
|
||||
@@ -528,7 +528,7 @@ class BME280:
|
||||
|
||||
# Enter forced mode
|
||||
meas = self.os_temp << 5 | self.os_pres << 2 | MODE
|
||||
self.write_register('CTRL_MEAS', meas, wait=True)
|
||||
self.write_register('CTRL_MEAS', meas)
|
||||
max_sample_time = self.max_sample_time
|
||||
if run_gas:
|
||||
max_sample_time += self.gas_heat_duration / 1000
|
||||
@@ -776,15 +776,12 @@ class BME280:
|
||||
params = self.i2c.i2c_read(regs, read_len)
|
||||
return bytearray(params['response'])
|
||||
|
||||
def write_register(self, reg_name, data, wait = False):
|
||||
def write_register(self, reg_name, data):
|
||||
if type(data) is not list:
|
||||
data = [data]
|
||||
reg = self.chip_registers[reg_name]
|
||||
data.insert(0, reg)
|
||||
if not wait:
|
||||
self.i2c.i2c_write(data)
|
||||
else:
|
||||
self.i2c.i2c_write_wait_ack(data)
|
||||
self.i2c.i2c_write(data)
|
||||
|
||||
def get_status(self, eventtime):
|
||||
data = {
|
||||
|
||||
@@ -43,6 +43,7 @@ class MCU_SPI:
|
||||
cs_active_high=False):
|
||||
self.mcu = mcu
|
||||
self.bus = bus
|
||||
self.speed = speed
|
||||
# Config SPI object (set all CS pins high before spi_set_bus commands)
|
||||
self.oid = mcu.create_oid()
|
||||
if pin is None:
|
||||
@@ -51,11 +52,17 @@ class MCU_SPI:
|
||||
mcu.add_config_cmd("config_spi oid=%d pin=%s cs_active_high=%d"
|
||||
% (self.oid, pin, cs_active_high))
|
||||
# Generate SPI bus config message
|
||||
self.config_fmt_ticks = None
|
||||
if sw_pins is not None:
|
||||
self.config_fmt = (
|
||||
"spi_set_software_bus oid=%d"
|
||||
" miso_pin=%s mosi_pin=%s sclk_pin=%s mode=%d rate=%d"
|
||||
% (self.oid, sw_pins[0], sw_pins[1], sw_pins[2], mode, speed))
|
||||
self.config_fmt_ticks = (
|
||||
"spi_set_sw_bus oid=%d"
|
||||
" miso_pin=%s mosi_pin=%s sclk_pin=%s mode=%d pulse_ticks=%%d"
|
||||
% (self.oid, sw_pins[0], sw_pins[1],
|
||||
sw_pins[2], mode))
|
||||
else:
|
||||
self.config_fmt = (
|
||||
"spi_set_bus oid=%d spi_bus=%%s mode=%d rate=%d"
|
||||
@@ -78,6 +85,12 @@ class MCU_SPI:
|
||||
if '%' in self.config_fmt:
|
||||
bus = resolve_bus_name(self.mcu, "spi_bus", self.bus)
|
||||
self.config_fmt = self.config_fmt % (bus,)
|
||||
if self.config_fmt_ticks:
|
||||
if self.mcu.try_lookup_command("spi_set_sw_bus oid=%c miso_pin=%u "
|
||||
"mosi_pin=%u sclk_pin=%u "
|
||||
"mode=%u pulse_ticks=%u"):
|
||||
pulse_ticks = self.mcu.seconds_to_clock(1./self.speed)
|
||||
self.config_fmt = self.config_fmt_ticks % (pulse_ticks,)
|
||||
self.mcu.add_config_cmd(self.config_fmt)
|
||||
self.spi_send_cmd = self.mcu.lookup_command(
|
||||
"spi_send oid=%c data=%*s", cq=self.cmd_queue)
|
||||
@@ -147,6 +160,8 @@ class MCU_I2C:
|
||||
self.bus = bus
|
||||
self.i2c_address = addr
|
||||
self.oid = self.mcu.create_oid()
|
||||
self.speed = speed
|
||||
self.config_fmt_ticks = None
|
||||
mcu.add_config_cmd("config_i2c oid=%d" % (self.oid,))
|
||||
# Generate I2C bus config message
|
||||
if sw_pins is not None:
|
||||
@@ -154,6 +169,10 @@ class MCU_I2C:
|
||||
"i2c_set_software_bus oid=%d"
|
||||
" scl_pin=%s sda_pin=%s rate=%d address=%d"
|
||||
% (self.oid, sw_pins[0], sw_pins[1], speed, addr))
|
||||
self.config_fmt_ticks = (
|
||||
"i2c_set_sw_bus oid=%d"
|
||||
" scl_pin=%s sda_pin=%s pulse_ticks=%%d address=%d"
|
||||
% (self.oid, sw_pins[0], sw_pins[1], addr))
|
||||
else:
|
||||
self.config_fmt = (
|
||||
"i2c_set_bus oid=%d i2c_bus=%%s rate=%d address=%d"
|
||||
@@ -161,6 +180,13 @@ class MCU_I2C:
|
||||
self.cmd_queue = self.mcu.alloc_command_queue()
|
||||
self.mcu.register_config_callback(self.build_config)
|
||||
self.i2c_write_cmd = self.i2c_read_cmd = None
|
||||
printer = self.mcu.get_printer()
|
||||
printer.register_event_handler("klippy:connect", self._handle_connect)
|
||||
# backward support i2c_write inside the init section
|
||||
self._to_write = []
|
||||
def _handle_connect(self):
|
||||
for data in self._to_write:
|
||||
self.i2c_write(data)
|
||||
def get_oid(self):
|
||||
return self.oid
|
||||
def get_mcu(self):
|
||||
@@ -173,6 +199,12 @@ class MCU_I2C:
|
||||
if '%' in self.config_fmt:
|
||||
bus = resolve_bus_name(self.mcu, "i2c_bus", self.bus)
|
||||
self.config_fmt = self.config_fmt % (bus,)
|
||||
if self.config_fmt_ticks:
|
||||
if self.mcu.try_lookup_command("i2c_set_sw_bus oid=%c"
|
||||
" scl_pin=%u sda_pin=%u"
|
||||
" pulse_ticks=%u address=%u"):
|
||||
pulse_ticks = self.mcu.seconds_to_clock(1./self.speed/2)
|
||||
self.config_fmt = self.config_fmt_ticks % (pulse_ticks,)
|
||||
self.mcu.add_config_cmd(self.config_fmt)
|
||||
self.i2c_write_cmd = self.mcu.lookup_command(
|
||||
"i2c_write oid=%c data=%*s", cq=self.cmd_queue)
|
||||
@@ -182,18 +214,12 @@ class MCU_I2C:
|
||||
cq=self.cmd_queue)
|
||||
def i2c_write(self, data, minclock=0, reqclock=0):
|
||||
if self.i2c_write_cmd is None:
|
||||
# Send setup message via mcu initialization
|
||||
data_msg = "".join(["%02x" % (x,) for x in data])
|
||||
self.mcu.add_config_cmd("i2c_write oid=%d data=%s" % (
|
||||
self.oid, data_msg), is_init=True)
|
||||
self._to_write.append(data)
|
||||
return
|
||||
self.i2c_write_cmd.send([self.oid, data],
|
||||
minclock=minclock, reqclock=reqclock)
|
||||
def i2c_write_wait_ack(self, data, minclock=0, reqclock=0):
|
||||
self.i2c_write_cmd.send_wait_ack([self.oid, data],
|
||||
minclock=minclock, reqclock=reqclock)
|
||||
def i2c_read(self, write, read_len):
|
||||
return self.i2c_read_cmd.send([self.oid, write, read_len])
|
||||
minclock=minclock, reqclock=reqclock)
|
||||
def i2c_read(self, write, read_len, retry=True):
|
||||
return self.i2c_read_cmd.send([self.oid, write, read_len], retry)
|
||||
|
||||
def MCU_I2C_from_config(config, default_addr=None, default_speed=100000):
|
||||
# Load bus parameters
|
||||
|
||||
@@ -12,7 +12,7 @@ def load_config_prefix(config):
|
||||
if not config.has_section('display'):
|
||||
raise config.error(
|
||||
"A primary [display] section must be defined in printer.cfg "
|
||||
"to use auxilary displays")
|
||||
"to use auxiliary displays")
|
||||
name = config.get_name().split()[-1]
|
||||
if name == "display":
|
||||
raise config.error(
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# ftp://ftp.simtel.net/pub/simtelnet/msdos/screen/fntcol16.zip
|
||||
# (c) Joseph Gil
|
||||
#
|
||||
# Indivdual fonts are public domain
|
||||
# Individual fonts are public domain
|
||||
######################################################################
|
||||
|
||||
VGA_FONT = [
|
||||
|
||||
@@ -52,7 +52,7 @@ class PhaseCalc:
|
||||
class EndstopPhase:
|
||||
def __init__(self, config):
|
||||
self.printer = config.get_printer()
|
||||
self.name = config.get_name().split()[1]
|
||||
self.name = " ".join(config.get_name().split()[1:])
|
||||
# Obtain step_distance and microsteps from stepper config section
|
||||
sconfig = config.getsection(self.name)
|
||||
rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig)
|
||||
@@ -118,7 +118,7 @@ class EndstopPhase:
|
||||
return delta * self.step_dist
|
||||
def handle_home_rails_end(self, homing_state, rails):
|
||||
for rail in rails:
|
||||
stepper = rail.get_steppers()[0]
|
||||
stepper = rail.get_endstops()[0][0].get_steppers()[0]
|
||||
if stepper.get_name() == self.name:
|
||||
trig_mcu_pos = homing_state.get_trigger_position(self.name)
|
||||
align = self.align_endstop(rail)
|
||||
|
||||
@@ -82,24 +82,25 @@ class ExcludeObject:
|
||||
self._reset_state()
|
||||
self._unregister_transform()
|
||||
|
||||
def _get_extrusion_offsets(self):
|
||||
offset = self.extrusion_offsets.get(
|
||||
self.toolhead.get_extruder().get_name())
|
||||
def _get_extrusion_offsets(self, num_coord):
|
||||
ename = self.toolhead.get_extruder().get_name()
|
||||
offset = self.extrusion_offsets.get(ename)
|
||||
if offset is None:
|
||||
offset = [0., 0., 0., 0.]
|
||||
self.extrusion_offsets[self.toolhead.get_extruder().get_name()] = \
|
||||
offset
|
||||
offset = [0.] * num_coord
|
||||
self.extrusion_offsets[ename] = offset
|
||||
if len(offset) < num_coord:
|
||||
offset.extend([0.] * (len(num_coord) - len(offset)))
|
||||
return offset
|
||||
|
||||
def get_position(self):
|
||||
offset = self._get_extrusion_offsets()
|
||||
pos = self.next_transform.get_position()
|
||||
for i in range(4):
|
||||
offset = self._get_extrusion_offsets(len(pos))
|
||||
for i in range(len(pos)):
|
||||
self.last_position[i] = pos[i] + offset[i]
|
||||
return list(self.last_position)
|
||||
|
||||
def _normal_move(self, newpos, speed):
|
||||
offset = self._get_extrusion_offsets()
|
||||
offset = self._get_extrusion_offsets(len(newpos))
|
||||
|
||||
if self.initial_extrusion_moves > 0 and \
|
||||
self.last_position[3] != newpos[3]:
|
||||
@@ -122,9 +123,9 @@ class ExcludeObject:
|
||||
if (offset[0] != 0 or offset[1] != 0) and \
|
||||
(newpos[0] != self.last_position_excluded[0] or \
|
||||
newpos[1] != self.last_position_excluded[1]):
|
||||
offset[0] = 0
|
||||
offset[1] = 0
|
||||
offset[2] = 0
|
||||
for i in range(len(newpos)):
|
||||
if i != 3:
|
||||
offset[i] = 0
|
||||
offset[3] += self.extruder_adj
|
||||
self.extruder_adj = 0
|
||||
|
||||
@@ -137,17 +138,18 @@ class ExcludeObject:
|
||||
self.extruder_adj = 0
|
||||
|
||||
tx_pos = newpos[:]
|
||||
for i in range(4):
|
||||
for i in range(len(newpos)):
|
||||
tx_pos[i] = newpos[i] - offset[i]
|
||||
self.next_transform.move(tx_pos, speed)
|
||||
|
||||
def _ignore_move(self, newpos, speed):
|
||||
offset = self._get_extrusion_offsets()
|
||||
for i in range(3):
|
||||
offset[i] = newpos[i] - self.last_position_extruded[i]
|
||||
offset = self._get_extrusion_offsets(len(newpos))
|
||||
for i in range(len(newpos)):
|
||||
if i != 3:
|
||||
offset[i] = newpos[i] - self.last_position_extruded[i]
|
||||
offset[3] = offset[3] + newpos[3] - self.last_position[3]
|
||||
self.last_position[:] = newpos
|
||||
self.last_position_excluded[:] =self.last_position
|
||||
self.last_position_excluded[:] = self.last_position
|
||||
self.max_position_excluded = max(self.max_position_excluded, newpos[3])
|
||||
|
||||
def _move_into_excluded_region(self, newpos, speed):
|
||||
|
||||
@@ -43,7 +43,7 @@ class FirmwareRetraction:
|
||||
self.unretract_length = (self.retract_length
|
||||
+ self.unretract_extra_length)
|
||||
self.is_retracted = False
|
||||
cmd_GET_RETRACTION_help = ("Report firmware retraction paramters")
|
||||
cmd_GET_RETRACTION_help = ("Report firmware retraction parameters")
|
||||
def cmd_GET_RETRACTION(self, gcmd):
|
||||
gcmd.respond_info("RETRACT_LENGTH=%.5f RETRACT_SPEED=%.5f"
|
||||
" UNRETRACT_EXTRA_LENGTH=%.5f UNRETRACT_SPEED=%.5f"
|
||||
|
||||
@@ -33,10 +33,10 @@ class ForceMove:
|
||||
self.printer = config.get_printer()
|
||||
self.steppers = {}
|
||||
# Setup iterative solver
|
||||
self.motion_queuing = self.printer.load_object(config, 'motion_queuing')
|
||||
self.trapq = self.motion_queuing.allocate_trapq()
|
||||
self.trapq_append = self.motion_queuing.lookup_trapq_append()
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free)
|
||||
self.trapq_append = ffi_lib.trapq_append
|
||||
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
|
||||
self.stepper_kinematics = ffi_main.gc(
|
||||
ffi_lib.cartesian_stepper_alloc(b'x'), ffi_lib.free)
|
||||
# Register commands
|
||||
@@ -85,14 +85,12 @@ class ForceMove:
|
||||
self.trapq_append(self.trapq, print_time, accel_t, cruise_t, accel_t,
|
||||
0., 0., 0., axis_r, 0., 0., 0., cruise_v, accel)
|
||||
print_time = print_time + accel_t + cruise_t + accel_t
|
||||
stepper.generate_steps(print_time)
|
||||
self.trapq_finalize_moves(self.trapq, print_time + 99999.9,
|
||||
print_time + 99999.9)
|
||||
stepper.set_trapq(prev_trapq)
|
||||
stepper.set_stepper_kinematics(prev_sk)
|
||||
toolhead.note_mcu_movequeue_activity(print_time)
|
||||
toolhead.dwell(accel_t + cruise_t + accel_t)
|
||||
toolhead.flush_step_generation()
|
||||
stepper.set_trapq(prev_trapq)
|
||||
stepper.set_stepper_kinematics(prev_sk)
|
||||
self.motion_queuing.wipe_trapq(self.trapq)
|
||||
def _lookup_stepper(self, gcmd):
|
||||
name = gcmd.get('STEPPER')
|
||||
if name not in self.steppers:
|
||||
@@ -142,7 +140,7 @@ class ForceMove:
|
||||
logging.info("SET_KINEMATIC_POSITION pos=%.3f,%.3f,%.3f"
|
||||
" set_homed=%s clear_homed=%s",
|
||||
x, y, z, set_homed_axes, clear_homed_axes)
|
||||
toolhead.set_position([x, y, z, curpos[3]], homing_axes=set_homed_axes)
|
||||
toolhead.set_position([x, y, z], homing_axes=set_homed_axes)
|
||||
toolhead.get_kinematics().clear_homing_state(clear_homed_axes)
|
||||
|
||||
def load_config(config):
|
||||
|
||||
@@ -178,8 +178,8 @@ class GCodeMacro:
|
||||
literal = ast.literal_eval(value)
|
||||
json.dumps(literal, separators=(',', ':'))
|
||||
except (SyntaxError, TypeError, ValueError) as e:
|
||||
raise gcmd.error("Unable to parse '%s' as a literal: %s" %
|
||||
(value, e))
|
||||
raise gcmd.error("Unable to parse '%s' as a literal: %s in '%s'" %
|
||||
(value, e, gcmd.get_commandline()))
|
||||
v = dict(self.variables)
|
||||
v[variable] = literal
|
||||
self.variables = v
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# G-Code G1 movement commands (and associated coordinate manipulation)
|
||||
#
|
||||
# Copyright (C) 2016-2021 Kevin O'Connor <kevin@koconnor.net>
|
||||
# Copyright (C) 2016-2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging
|
||||
@@ -14,6 +14,8 @@ class GCodeMove:
|
||||
self.reset_last_position)
|
||||
printer.register_event_handler("toolhead:manual_move",
|
||||
self.reset_last_position)
|
||||
printer.register_event_handler("toolhead:update_extra_axes",
|
||||
self._update_extra_axes)
|
||||
printer.register_event_handler("gcode:command_error",
|
||||
self.reset_last_position)
|
||||
printer.register_event_handler("extruder:activate_extruder",
|
||||
@@ -42,6 +44,7 @@ class GCodeMove:
|
||||
self.base_position = [0.0, 0.0, 0.0, 0.0]
|
||||
self.last_position = [0.0, 0.0, 0.0, 0.0]
|
||||
self.homing_position = [0.0, 0.0, 0.0, 0.0]
|
||||
self.axis_map = {'X':0, 'Y': 1, 'Z': 2, 'E': 3}
|
||||
self.speed = 25.
|
||||
self.speed_factor = 1. / 60.
|
||||
self.extrude_factor = 1.
|
||||
@@ -102,35 +105,46 @@ class GCodeMove:
|
||||
'extrude_factor': self.extrude_factor,
|
||||
'absolute_coordinates': self.absolute_coord,
|
||||
'absolute_extrude': self.absolute_extrude,
|
||||
'homing_origin': self.Coord(*self.homing_position),
|
||||
'position': self.Coord(*self.last_position),
|
||||
'gcode_position': self.Coord(*move_position),
|
||||
'homing_origin': self.Coord(*self.homing_position[:4]),
|
||||
'position': self.Coord(*self.last_position[:4]),
|
||||
'gcode_position': self.Coord(*move_position[:4]),
|
||||
}
|
||||
def reset_last_position(self):
|
||||
if self.is_printer_ready:
|
||||
self.last_position = self.position_with_transform()
|
||||
def _update_extra_axes(self):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
axis_map = {'X':0, 'Y': 1, 'Z': 2, 'E': 3}
|
||||
extra_axes = toolhead.get_extra_axes()
|
||||
for index, ea in enumerate(extra_axes):
|
||||
if ea is None:
|
||||
continue
|
||||
gcode_id = ea.get_axis_gcode_id()
|
||||
if gcode_id is None or gcode_id in axis_map or gcode_id in "FN":
|
||||
continue
|
||||
axis_map[gcode_id] = index
|
||||
self.axis_map = axis_map
|
||||
self.base_position[4:] = [0.] * (len(extra_axes) - 4)
|
||||
self.reset_last_position()
|
||||
# G-Code movement commands
|
||||
def cmd_G1(self, gcmd):
|
||||
# Move
|
||||
params = gcmd.get_command_parameters()
|
||||
try:
|
||||
for pos, axis in enumerate('XYZ'):
|
||||
for axis, pos in self.axis_map.items():
|
||||
if axis in params:
|
||||
v = float(params[axis])
|
||||
if not self.absolute_coord:
|
||||
absolute_coord = self.absolute_coord
|
||||
if axis == 'E':
|
||||
v *= self.extrude_factor
|
||||
if not self.absolute_extrude:
|
||||
absolute_coord = False
|
||||
if not absolute_coord:
|
||||
# value relative to position of last move
|
||||
self.last_position[pos] += v
|
||||
else:
|
||||
# value relative to base coordinate position
|
||||
self.last_position[pos] = v + self.base_position[pos]
|
||||
if 'E' in params:
|
||||
v = float(params['E']) * self.extrude_factor
|
||||
if not self.absolute_coord or not self.absolute_extrude:
|
||||
# value relative to position of last move
|
||||
self.last_position[3] += v
|
||||
else:
|
||||
# value relative to base coordinate position
|
||||
self.last_position[3] = v + self.base_position[3]
|
||||
if 'F' in params:
|
||||
gcode_speed = float(params['F'])
|
||||
if gcode_speed <= 0.:
|
||||
@@ -169,7 +183,7 @@ class GCodeMove:
|
||||
offset *= self.extrude_factor
|
||||
self.base_position[i] = self.last_position[i] - offset
|
||||
if offsets == [None, None, None, None]:
|
||||
self.base_position = list(self.last_position)
|
||||
self.base_position[:4] = self.last_position[:4]
|
||||
def cmd_M114(self, gcmd):
|
||||
# Get Current Position
|
||||
p = self._get_gcode_position()
|
||||
@@ -227,7 +241,7 @@ class GCodeMove:
|
||||
# Restore state
|
||||
self.absolute_coord = state['absolute_coord']
|
||||
self.absolute_extrude = state['absolute_extrude']
|
||||
self.base_position = list(state['base_position'])
|
||||
self.base_position[:4] = state['base_position'][:4]
|
||||
self.homing_position = list(state['homing_position'])
|
||||
self.speed = state['speed']
|
||||
self.speed_factor = state['speed_factor']
|
||||
@@ -255,7 +269,7 @@ class GCodeMove:
|
||||
kinfo = zip("XYZ", kin.calc_position(dict(cinfo)))
|
||||
kin_pos = " ".join(["%s:%.6f" % (a, v) for a, v in kinfo])
|
||||
toolhead_pos = " ".join(["%s:%.6f" % (a, v) for a, v in zip(
|
||||
"XYZE", toolhead.get_position())])
|
||||
"XYZE", toolhead.get_position()[:4])])
|
||||
gcode_pos = " ".join(["%s:%.6f" % (a, v)
|
||||
for a, v in zip("XYZE", self.last_position)])
|
||||
base_pos = " ".join(["%s:%.6f" % (a, v)
|
||||
|
||||
@@ -209,10 +209,12 @@ class HallFilamentWidthSensor:
|
||||
+self.lastFilamentWidthReading2))
|
||||
gcmd.respond_info(response)
|
||||
def get_status(self, eventtime):
|
||||
return {'Diameter': self.diameter,
|
||||
status = self.runout_helper.get_status(eventtime)
|
||||
status.update({'Diameter': self.diameter,
|
||||
'Raw':(self.lastFilamentWidthReading+
|
||||
self.lastFilamentWidthReading2),
|
||||
'is_active':self.is_active}
|
||||
'is_active':self.is_active})
|
||||
return status
|
||||
def cmd_log_enable(self, gcmd):
|
||||
self.is_log = True
|
||||
gcmd.respond_info("Filament width logging Turned On")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Tracking of PWM controlled heaters and their temperature control
|
||||
#
|
||||
# Copyright (C) 2016-2020 Kevin O'Connor <kevin@koconnor.net>
|
||||
# Copyright (C) 2016-2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import os, logging, threading
|
||||
@@ -11,10 +11,11 @@ import os, logging, threading
|
||||
######################################################################
|
||||
|
||||
KELVIN_TO_CELSIUS = -273.15
|
||||
MAX_HEAT_TIME = 5.0
|
||||
MAX_HEAT_TIME = 3.0
|
||||
AMBIENT_TEMP = 25.
|
||||
PID_PARAM_BASE = 255.
|
||||
MAX_MAINTHREAD_TIME = 5.0
|
||||
QUELL_STALE_TIME = 7.0
|
||||
|
||||
class Heater:
|
||||
def __init__(self, config, sensor):
|
||||
@@ -74,7 +75,8 @@ class Heater:
|
||||
# No significant change in value - can suppress update
|
||||
return
|
||||
pwm_time = read_time + self.pwm_delay
|
||||
self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME
|
||||
self.next_pwm_time = (pwm_time + MAX_HEAT_TIME
|
||||
- (3. * self.pwm_delay + 0.001))
|
||||
self.last_pwm_value = value
|
||||
self.mcu_pwm.set_pwm(pwm_time, value)
|
||||
#logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])",
|
||||
@@ -110,9 +112,10 @@ class Heater:
|
||||
with self.lock:
|
||||
self.target_temp = degrees
|
||||
def get_temp(self, eventtime):
|
||||
print_time = self.mcu_pwm.get_mcu().estimated_print_time(eventtime) - 5.
|
||||
est_print_time = self.mcu_pwm.get_mcu().estimated_print_time(eventtime)
|
||||
quell_time = est_print_time - QUELL_STALE_TIME
|
||||
with self.lock:
|
||||
if self.last_temp_time < print_time:
|
||||
if self.last_temp_time < quell_time:
|
||||
return 0., self.target_temp
|
||||
return self.smoothed_temp, self.target_temp
|
||||
def check_busy(self, eventtime):
|
||||
|
||||
@@ -45,7 +45,7 @@ class StepperPosition:
|
||||
class HomingMove:
|
||||
def __init__(self, printer, endstops, toolhead=None):
|
||||
self.printer = printer
|
||||
self.endstops = endstops
|
||||
self.endstops = [es for es in endstops if es[0].get_steppers()]
|
||||
if toolhead is None:
|
||||
toolhead = printer.lookup_object('toolhead')
|
||||
self.toolhead = toolhead
|
||||
@@ -71,7 +71,9 @@ class HomingMove:
|
||||
sname = stepper.get_name()
|
||||
kin_spos[sname] += offsets.get(sname, 0) * stepper.get_step_dist()
|
||||
thpos = self.toolhead.get_position()
|
||||
return list(kin.calc_position(kin_spos))[:3] + thpos[3:]
|
||||
cpos = kin.calc_position(kin_spos)
|
||||
return [cp if cp is not None else tp
|
||||
for cp, tp in zip(cpos, thpos[:3])] + thpos[3:]
|
||||
def homing_move(self, movepos, speed, probe_pos=False,
|
||||
triggered=True, check_triggered=True):
|
||||
# Notify start of homing/probing move
|
||||
@@ -233,6 +235,10 @@ class Homing:
|
||||
for s in kin.get_steppers()}
|
||||
newpos = kin.calc_position(kin_spos)
|
||||
for axis in force_axes:
|
||||
if newpos[axis] is None:
|
||||
raise self.printer.command_error(
|
||||
"Cannot determine position of toolhead on "
|
||||
"axis %s after homing" % "xyz"[axis])
|
||||
homepos[axis] = newpos[axis]
|
||||
self.toolhead.set_position(homepos)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ from . import bus
|
||||
# Si7013 - Untested
|
||||
# Si7020 - Untested
|
||||
# Si7021 - Tested on Pico MCU
|
||||
# SHT21 - Untested
|
||||
# SHT21 - Tested on Linux MCU.
|
||||
#
|
||||
######################################################################
|
||||
|
||||
@@ -34,7 +34,7 @@ HTU21D_COMMANDS = {
|
||||
|
||||
}
|
||||
|
||||
HTU21D_RESOLUTION_MASK = 0x7E;
|
||||
HTU21D_RESOLUTION_MASK = 0x7E
|
||||
HTU21D_RESOLUTIONS = {
|
||||
'TEMP14_HUM12':int('00000000',2),
|
||||
'TEMP13_HUM10':int('10000000',2),
|
||||
@@ -42,31 +42,40 @@ HTU21D_RESOLUTIONS = {
|
||||
'TEMP11_HUM11':int('10000001',2)
|
||||
}
|
||||
|
||||
ID_MAP = {
|
||||
0x0D: 'SI7013',
|
||||
0x14: 'SI7020',
|
||||
0x15: 'SI7021',
|
||||
0x31: 'SHT21',
|
||||
0x01: 'SHT21',
|
||||
0x32: 'HTU21D',
|
||||
}
|
||||
|
||||
# Device with conversion time for tmp/resolution bit
|
||||
# The format is:
|
||||
# <CHIPNAME>:{id:<ID>, ..<RESOlUTION>:[<temp time>,<humidity time>].. }
|
||||
HTU21D_DEVICES = {
|
||||
'SI7013':{'id':0x0D,
|
||||
'SI7013':{
|
||||
'TEMP14_HUM12':[.11,.12],
|
||||
'TEMP13_HUM10':[ .7, .5],
|
||||
'TEMP12_HUM08':[ .4, .4],
|
||||
'TEMP11_HUM11':[ .3, .7]},
|
||||
'SI7020':{'id':0x14,
|
||||
'SI7020':{
|
||||
'TEMP14_HUM12':[.11,.12],
|
||||
'TEMP13_HUM10':[ .7, .5],
|
||||
'TEMP12_HUM08':[ .4, .4],
|
||||
'TEMP11_HUM11':[ .3, .7]},
|
||||
'SI7021':{'id':0x15,
|
||||
'SI7021':{
|
||||
'TEMP14_HUM12':[.11,.12],
|
||||
'TEMP13_HUM10':[ .7, .5],
|
||||
'TEMP12_HUM08':[ .4, .4],
|
||||
'TEMP11_HUM11':[ .3, .7]},
|
||||
'SHT21': {'id':0x31,
|
||||
'SHT21': {
|
||||
'TEMP14_HUM12':[.85,.29],
|
||||
'TEMP13_HUM10':[.43, .9],
|
||||
'TEMP12_HUM08':[.22, .4],
|
||||
'TEMP11_HUM11':[.11,.15]},
|
||||
'HTU21D':{'id':0x32,
|
||||
'HTU21D':{
|
||||
'TEMP14_HUM12':[.50,.16],
|
||||
'TEMP13_HUM10':[.25, .5],
|
||||
'TEMP12_HUM08':[.13, .3],
|
||||
@@ -128,19 +137,16 @@ class HTU21D:
|
||||
if self._chekCRC8(rdevId) != checksum:
|
||||
logging.warning("htu21d: Reading deviceId !Checksum error!")
|
||||
rdevId = rdevId >> 8
|
||||
deviceId_list = list(
|
||||
filter(
|
||||
lambda elem: HTU21D_DEVICES[elem]['id'] == rdevId,HTU21D_DEVICES)
|
||||
)
|
||||
if len(deviceId_list) != 0:
|
||||
logging.info("htu21d: Found Device Type %s" % deviceId_list[0])
|
||||
guess_dev = ID_MAP.get(rdevId, "Unknown")
|
||||
if guess_dev == self.deviceId:
|
||||
logging.info("htu21d: Found Device Type %s" % guess_dev)
|
||||
else:
|
||||
logging.warning("htu21d: Unknown Device ID %#x " % rdevId)
|
||||
|
||||
if self.deviceId != deviceId_list[0]:
|
||||
if self.deviceId != guess_dev:
|
||||
logging.warning(
|
||||
"htu21d: Found device %s. Forcing to type %s as config.",
|
||||
deviceId_list[0],self.deviceId)
|
||||
"htu21d: Found device %s. Forcing to type %s as config." %
|
||||
(guess_dev, self.deviceId))
|
||||
|
||||
# Set Resolution
|
||||
params = self.i2c.i2c_read([HTU21D_COMMANDS['READ']], 1)
|
||||
@@ -152,7 +158,7 @@ class HTU21D:
|
||||
|
||||
def _sample_htu21d(self, eventtime):
|
||||
try:
|
||||
# Read Temeprature
|
||||
# Read Temperature
|
||||
if self.hold_master_mode:
|
||||
params = self.i2c.i2c_write([HTU21D_COMMANDS['HTU21D_TEMP']])
|
||||
else:
|
||||
|
||||
@@ -53,6 +53,7 @@ class HX71xBase:
|
||||
self._finish_measurements, UPDATE_INTERVAL)
|
||||
# Command Configuration
|
||||
self.query_hx71x_cmd = None
|
||||
self.attach_probe_cmd = None
|
||||
mcu.add_config_cmd(
|
||||
"config_hx71x oid=%d gain_channel=%d dout_pin=%s sclk_pin=%s"
|
||||
% (self.oid, self.gain_channel, self.dout_pin, self.sclk_pin))
|
||||
@@ -64,10 +65,13 @@ class HX71xBase:
|
||||
def _build_config(self):
|
||||
self.query_hx71x_cmd = self.mcu.lookup_command(
|
||||
"query_hx71x oid=%c rest_ticks=%u")
|
||||
self.attach_probe_cmd = self.mcu.lookup_command(
|
||||
"hx71x_attach_load_cell_probe oid=%c load_cell_probe_oid=%c")
|
||||
self.ffreader.setup_query_command("query_hx71x_status oid=%c",
|
||||
oid=self.oid,
|
||||
cq=self.mcu.alloc_command_queue())
|
||||
|
||||
|
||||
def get_mcu(self):
|
||||
return self.mcu
|
||||
|
||||
@@ -83,6 +87,9 @@ class HX71xBase:
|
||||
def add_client(self, callback):
|
||||
self.batch_bulk.add_client(callback)
|
||||
|
||||
def attach_load_cell_probe(self, load_cell_probe_oid):
|
||||
self.attach_probe_cmd.send([self.oid, load_cell_probe_oid])
|
||||
|
||||
# Measurement decoding
|
||||
def _convert_samples(self, samples):
|
||||
adc_factor = 1. / (1 << 23)
|
||||
|
||||
@@ -35,7 +35,9 @@ class IdleTimeout:
|
||||
printing_time = 0.
|
||||
if self.state == "Printing":
|
||||
printing_time = eventtime - self.last_print_start_systime
|
||||
return { "state": self.state, "printing_time": printing_time }
|
||||
return {"state": self.state,
|
||||
"printing_time": printing_time,
|
||||
"idle_timeout": self.idle_timeout}
|
||||
def handle_ready(self):
|
||||
self.toolhead = self.printer.lookup_object('toolhead')
|
||||
self.timeout_timer = self.reactor.register_timer(self.timeout_handler)
|
||||
|
||||
@@ -69,6 +69,8 @@ class AxisInputShaper:
|
||||
ffi_lib.input_shaper_set_shaper_params(
|
||||
sk, self.axis.encode(), self.n, self.A, self.T)
|
||||
return success
|
||||
def is_enabled(self):
|
||||
return self.n > 0
|
||||
def disable_shaping(self):
|
||||
if self.saved is None and self.n:
|
||||
self.saved = (self.n, self.A, self.T)
|
||||
@@ -89,6 +91,8 @@ class InputShaper:
|
||||
def __init__(self, config):
|
||||
self.printer = config.get_printer()
|
||||
self.printer.register_event_handler("klippy:connect", self.connect)
|
||||
self.printer.register_event_handler("dual_carriage:update_kinematics",
|
||||
self._update_kinematics)
|
||||
self.toolhead = None
|
||||
self.shapers = [AxisInputShaper('x', config),
|
||||
AxisInputShaper('y', config)]
|
||||
@@ -103,17 +107,23 @@ class InputShaper:
|
||||
return self.shapers
|
||||
def connect(self):
|
||||
self.toolhead = self.printer.lookup_object("toolhead")
|
||||
dual_carriage = self.printer.lookup_object('dual_carriage', None)
|
||||
if dual_carriage is not None:
|
||||
for shaper in self.shapers:
|
||||
if shaper.is_enabled():
|
||||
raise self.printer.config_error(
|
||||
'Input shaper parameters cannot be configured via'
|
||||
' [input_shaper] section with dual_carriage(s) '
|
||||
' enabled. Refer to Klipper documentation on how '
|
||||
' to configure input shaper for dual_carriage(s).')
|
||||
return
|
||||
# Configure initial values
|
||||
self._update_input_shaping(error=self.printer.config_error)
|
||||
def _get_input_shaper_stepper_kinematics(self, stepper):
|
||||
# Lookup stepper kinematics
|
||||
sk = stepper.get_stepper_kinematics()
|
||||
if sk in self.orig_stepper_kinematics:
|
||||
# Already processed this stepper kinematics unsuccessfully
|
||||
return None
|
||||
if sk in self.input_shaper_stepper_kinematics:
|
||||
return sk
|
||||
self.orig_stepper_kinematics.append(sk)
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
is_sk = ffi_main.gc(ffi_lib.input_shaper_alloc(), ffi_lib.free)
|
||||
stepper.set_stepper_kinematics(is_sk)
|
||||
@@ -121,8 +131,27 @@ class InputShaper:
|
||||
if res < 0:
|
||||
stepper.set_stepper_kinematics(sk)
|
||||
return None
|
||||
self.orig_stepper_kinematics.append(sk)
|
||||
self.input_shaper_stepper_kinematics.append(is_sk)
|
||||
return is_sk
|
||||
def _update_kinematics(self):
|
||||
if self.toolhead is None:
|
||||
# Klipper initialization is not yet completed
|
||||
return
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
kin = self.toolhead.get_kinematics()
|
||||
for s in kin.get_steppers():
|
||||
if s.get_trapq() is None:
|
||||
continue
|
||||
is_sk = self._get_input_shaper_stepper_kinematics(s)
|
||||
if is_sk is None:
|
||||
continue
|
||||
old_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk)
|
||||
ffi_lib.input_shaper_update_sk(is_sk)
|
||||
new_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk)
|
||||
if old_delay != new_delay:
|
||||
self.toolhead.note_step_generation_scan_time(new_delay,
|
||||
old_delay)
|
||||
def _update_input_shaping(self, error=None):
|
||||
self.toolhead.flush_step_generation()
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Support for PWM driven LEDs
|
||||
#
|
||||
# Copyright (C) 2019-2024 Kevin O'Connor <kevin@koconnor.net>
|
||||
# Copyright (C) 2019-2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging
|
||||
@@ -97,17 +97,15 @@ class LEDHelper:
|
||||
for i in range(self.led_count):
|
||||
set_template(gcmd, self.tcallbacks[i], self._check_transmit)
|
||||
|
||||
PIN_MIN_TIME = 0.100
|
||||
MAX_SCHEDULE_TIME = 5.0
|
||||
|
||||
# Handler for PWM controlled LEDs
|
||||
class PrinterPWMLED:
|
||||
def __init__(self, config):
|
||||
self.printer = printer = config.get_printer()
|
||||
# Configure pwm pins
|
||||
ppins = printer.lookup_object('pins')
|
||||
max_duration = printer.lookup_object('mcu').max_nominal_duration()
|
||||
cycle_time = config.getfloat('cycle_time', 0.010, above=0.,
|
||||
maxval=MAX_SCHEDULE_TIME)
|
||||
maxval=max_duration)
|
||||
hardware_pwm = config.getboolean('hardware_pwm', False)
|
||||
self.pins = []
|
||||
for i, name in enumerate(("red", "green", "blue", "white")):
|
||||
@@ -128,11 +126,12 @@ class PrinterPWMLED:
|
||||
for idx, mcu_pin in self.pins:
|
||||
mcu_pin.setup_start_value(color[idx], 0.)
|
||||
def update_leds(self, led_state, print_time):
|
||||
mcu = self.pins[0][1].get_mcu()
|
||||
min_sched_time = mcu.min_schedule_time()
|
||||
if print_time is None:
|
||||
eventtime = self.printer.get_reactor().monotonic()
|
||||
mcu = self.pins[0][1].get_mcu()
|
||||
print_time = mcu.estimated_print_time(eventtime) + PIN_MIN_TIME
|
||||
print_time = max(print_time, self.last_print_time + PIN_MIN_TIME)
|
||||
print_time = mcu.estimated_print_time(eventtime) + min_sched_time
|
||||
print_time = max(print_time, self.last_print_time + min_sched_time)
|
||||
color = led_state[0]
|
||||
for idx, mcu_pin in self.pins:
|
||||
if self.prev_color[idx] != color[idx]:
|
||||
|
||||
@@ -34,7 +34,7 @@ LIS_I2C_ADDR = 0x19
|
||||
# Right shift for left justified registers.
|
||||
FREEFALL_ACCEL = 9.80665
|
||||
LIS2DW_SCALE = FREEFALL_ACCEL * 1.952 / 4
|
||||
LIS3DH_SCALE = FREEFALL_ACCEL * 3.906 / 16
|
||||
LIS3DH_SCALE = FREEFALL_ACCEL * 11.718 / 16
|
||||
|
||||
BATCH_UPDATES = 0.100
|
||||
|
||||
@@ -145,6 +145,9 @@ class LIS2DW:
|
||||
"This is generally indicative of connection problems\n"
|
||||
"(e.g. faulty wiring) or a faulty lis2dw chip."
|
||||
% (dev_id, LIS2DW_DEV_ID))
|
||||
if self.bus_type == SPI_SERIAL_TYPE:
|
||||
# Disable I2C
|
||||
self.set_reg(REG_LIS2DW_CTRL_REG2_ADDR, 0x06)
|
||||
# Setup chip in requested query rate
|
||||
# ODR/2, +-16g, low-pass filter, Low-noise abled
|
||||
self.set_reg(REG_LIS2DW_CTRL_REG6_ADDR, 0x34)
|
||||
@@ -167,8 +170,8 @@ class LIS2DW:
|
||||
self.set_reg(REG_LIS2DW_CTRL_REG1_ADDR, 0x97)
|
||||
# Disable all filtering
|
||||
self.set_reg(REG_LIS2DW_CTRL_REG2_ADDR, 0)
|
||||
# Set +-8g, High Resolution mode
|
||||
self.set_reg(REG_LIS2DW_CTRL_REG4_ADDR, 0x28)
|
||||
# Set +-16g, High Resolution mode
|
||||
self.set_reg(REG_LIS2DW_CTRL_REG4_ADDR, 0x38)
|
||||
# Enable FIFO
|
||||
self.set_reg(REG_LIS2DW_CTRL_REG5_ADDR, 0x40)
|
||||
# Stream mode
|
||||
|
||||
@@ -53,7 +53,7 @@ class ApiClientHelper(object):
|
||||
wh = self.printer.lookup_object('webhooks')
|
||||
wh.register_mux_endpoint(path, key, value, self._add_webhooks_client)
|
||||
|
||||
# Class for handling commands related ot load cells
|
||||
# Class for handling commands related to load cells
|
||||
class LoadCellCommandHelper:
|
||||
def __init__(self, config, load_cell):
|
||||
self.printer = config.get_printer()
|
||||
|
||||
658
klippy/extras/load_cell_probe.py
Normal file
658
klippy/extras/load_cell_probe.py
Normal file
@@ -0,0 +1,658 @@
|
||||
# Load Cell Probe
|
||||
#
|
||||
# Copyright (C) 2025 Gareth Farrington <gareth@waves.ky>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging, math
|
||||
import mcu
|
||||
from . import probe, sos_filter, load_cell, hx71x, ads1220
|
||||
|
||||
np = None # delay NumPy import until configuration time
|
||||
|
||||
# constants for fixed point numbers
|
||||
Q2_INT_BITS = 2
|
||||
Q2_FRAC_BITS = (32 - (1 + Q2_INT_BITS))
|
||||
Q16_INT_BITS = 16
|
||||
Q16_FRAC_BITS = (32 - (1 + Q16_INT_BITS))
|
||||
|
||||
|
||||
class TapAnalysis:
|
||||
def __init__(self, samples):
|
||||
nd_samples = np.asarray(samples, dtype=np.float64)
|
||||
self.time = nd_samples[:, 0]
|
||||
self.force = nd_samples[:, 1]
|
||||
|
||||
# convert to dictionary for JSON encoder
|
||||
def to_dict(self):
|
||||
return {
|
||||
'time': self.time.tolist(), 'force': self.force.tolist(),
|
||||
'is_valid': True,
|
||||
}
|
||||
|
||||
|
||||
# Access a parameter from config or GCode command via a consistent interface
|
||||
# stores name and constraints to keep things DRY
|
||||
class ParamHelper:
|
||||
def __init__(self, config, name, type_name, default=None, minval=None,
|
||||
maxval=None, above=None, below=None, max_len=None):
|
||||
self._config_section = config.get_name()
|
||||
self._config_error = config.error
|
||||
self.name = name
|
||||
self._type_name = type_name
|
||||
self.value = default
|
||||
self.minval = minval
|
||||
self.maxval = maxval
|
||||
self.above = above
|
||||
self.below = below
|
||||
self.max_len = max_len
|
||||
# read from config once
|
||||
self.value = self.get(config=config)
|
||||
|
||||
def _get_name(self, gcmd):
|
||||
return self.name.upper() if gcmd else self.name
|
||||
|
||||
def _validate_float(self, description, error, value, above, below):
|
||||
above = above or self.above
|
||||
if above is not None and value <= above:
|
||||
raise error("%s must be above %s" % (description, above))
|
||||
below = below or self.below
|
||||
if below is not None and value >= below:
|
||||
raise error("%s must be below %s" % (description, below))
|
||||
|
||||
# support for validating individual options in a list of floats
|
||||
def _validate_float_list(self, gcmd, values, above, below):
|
||||
if gcmd:
|
||||
description = ("Error on '%s': %s" % (
|
||||
gcmd.get_commandline(), self._get_name(gcmd)))
|
||||
error = gcmd.error
|
||||
else:
|
||||
description = ("Option '%s' in section '%s'" % (
|
||||
self._get_name(gcmd), self._config_section))
|
||||
error = self._config_error
|
||||
if self.max_len is not None and len(values) > self.max_len:
|
||||
raise error(
|
||||
"%s has maximum length %s" % (description, self.max_len))
|
||||
for value in values:
|
||||
self._validate_float(description, error, value, above, below)
|
||||
|
||||
def _get_int(self, config, gcmd, minval, maxval):
|
||||
get = gcmd.get_int if gcmd else config.getint
|
||||
return get(self._get_name(gcmd), self.value, minval or self.minval,
|
||||
maxval or self.maxval)
|
||||
|
||||
def _get_float(self, config, gcmd, minval, maxval, above, below):
|
||||
get = gcmd.get_float if gcmd else config.getfloat
|
||||
return get(self._get_name(gcmd), self.value, minval or self.minval,
|
||||
maxval or self.maxval, above or self.above, below or self.below)
|
||||
|
||||
def _get_float_list(self, config, gcmd, above, below):
|
||||
# this code defaults to the empty list, never return None
|
||||
default = (self.value or [])
|
||||
if gcmd:
|
||||
# if the parameter isn't part of the command, return the default
|
||||
if not self._get_name(gcmd) in gcmd.get_command_parameters():
|
||||
return default
|
||||
# parameter exists, always prefer whatever is in the command
|
||||
value = gcmd.get(self._get_name(gcmd), default='')
|
||||
# Return an empty list for empty value
|
||||
if len(value.strip()) == 0:
|
||||
return []
|
||||
try:
|
||||
float_list = [float(p.strip()) for p in value.split(',')]
|
||||
except:
|
||||
raise gcmd.error("Error on '%s': unable to parse %s" % (
|
||||
gcmd.get_commandline(), value))
|
||||
else:
|
||||
float_list = config.getfloatlist(self._get_name(gcmd),
|
||||
default=default)
|
||||
if float_list:
|
||||
self._validate_float_list(gcmd, float_list, above, below)
|
||||
return float_list
|
||||
|
||||
def get(self, gcmd=None, minval=None, maxval=None, above=None, below=None,
|
||||
config=None):
|
||||
if config is None and gcmd is None:
|
||||
return self.value
|
||||
if self._type_name == 'int':
|
||||
return self._get_int(config, gcmd, minval, maxval)
|
||||
elif self._type_name == 'float':
|
||||
return self._get_float(config, gcmd, minval, maxval, above, below)
|
||||
else:
|
||||
return self._get_float_list(config, gcmd, above, below)
|
||||
|
||||
|
||||
def intParamHelper(config, name, default=None, minval=None, maxval=None):
|
||||
return ParamHelper(config, name, 'int', default, minval=minval,
|
||||
maxval=maxval)
|
||||
|
||||
|
||||
def floatParamHelper(config, name, default=None, minval=None, maxval=None,
|
||||
above=None, below=None):
|
||||
return ParamHelper(config, name, 'float', default, minval=minval,
|
||||
maxval=maxval, above=above, below=below)
|
||||
|
||||
|
||||
def floatListParamHelper(config, name, default=None, above=None, below=None,
|
||||
max_len=None):
|
||||
return ParamHelper(config, name, 'float_list', default, above=above,
|
||||
below=below, max_len=max_len)
|
||||
|
||||
|
||||
# container for filter parameters
|
||||
# allows different filter configurations to be compared
|
||||
class ContinuousTareFilter:
|
||||
def __init__(self, sps=None, drift=None, drift_delay=None, buzz=None,
|
||||
buzz_delay=None, notches=None, notch_quality=None):
|
||||
self.sps = sps
|
||||
self.drift = drift
|
||||
self.drift_delay = drift_delay
|
||||
self.buzz = buzz
|
||||
self.buzz_delay = buzz_delay
|
||||
self.notches = notches
|
||||
self.notch_quality = notch_quality
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, ContinuousTareFilter):
|
||||
return False
|
||||
return (
|
||||
self.sps == other.sps and self.drift == other.drift and
|
||||
self.drift_delay == other.drift_delay and self.buzz ==
|
||||
other.buzz and self.buzz_delay == other.buzz_delay and
|
||||
self.notches == other.notches and self.notch_quality ==
|
||||
other.notch_quality)
|
||||
|
||||
# create a filter design from the parameters
|
||||
def design_filter(self, error_func):
|
||||
design = sos_filter.DigitalFilter(self.sps, error_func, self.drift,
|
||||
self.drift_delay, self.buzz, self.buzz_delay, self.notches,
|
||||
self.notch_quality)
|
||||
fixed_filter = sos_filter.FixedPointSosFilter(
|
||||
design.get_filter_sections(), design.get_initial_state(),
|
||||
Q2_INT_BITS, Q16_INT_BITS)
|
||||
return fixed_filter
|
||||
|
||||
|
||||
# Combine ContinuousTareFilter and SosFilter into an easy-to-use class
|
||||
class ContinuousTareFilterHelper:
|
||||
def __init__(self, config, sensor, cmd_queue):
|
||||
self._sensor = sensor
|
||||
self._sps = self._sensor.get_samples_per_second()
|
||||
max_filter_frequency = math.floor(self._sps / 2.)
|
||||
# setup filter parameters
|
||||
self._drift_param = floatParamHelper(config,
|
||||
"drift_filter_cutoff_frequency", default=None, minval=0.1,
|
||||
maxval=20.0)
|
||||
self._drift_delay_param = intParamHelper(config, "drift_filter_delay",
|
||||
default=2, minval=1, maxval=2)
|
||||
self._buzz_param = floatParamHelper(config,
|
||||
"buzz_filter_cutoff_frequency", default=None,
|
||||
above=min(80.0, max_filter_frequency - 1.0),
|
||||
below=max_filter_frequency)
|
||||
self._buzz_delay_param = intParamHelper(config, "buzz_filter_delay",
|
||||
default=2, minval=1, maxval=2)
|
||||
self._notches_param = floatListParamHelper(config,
|
||||
"notch_filter_frequencies", default=[], above=0.,
|
||||
below=max_filter_frequency, max_len=2)
|
||||
self._notch_quality_param = floatParamHelper(config,
|
||||
"notch_filter_quality", default=2.0, minval=0.5, maxval=6.0)
|
||||
# filter design specified in the config file, used for defaults
|
||||
self._config_design = ContinuousTareFilter() # empty filter
|
||||
self._config_design = self._build_filter()
|
||||
# filter design currently inside the MCU
|
||||
self._active_design = self._config_design
|
||||
self._sos_filter = self._create_filter(
|
||||
self._active_design.design_filter(config.error), cmd_queue)
|
||||
|
||||
def _build_filter(self, gcmd=None):
|
||||
drift = self._drift_param.get(gcmd)
|
||||
drift_delay = self._drift_delay_param.get(gcmd)
|
||||
buzz = self._buzz_param.get(gcmd)
|
||||
buzz_delay = self._buzz_delay_param.get(gcmd)
|
||||
# notches must be between drift and buzz:
|
||||
notches = self._notches_param.get(gcmd, above=drift, below=buzz)
|
||||
notch_quality = self._notch_quality_param.get(gcmd)
|
||||
return ContinuousTareFilter(self._sps, drift, drift_delay, buzz,
|
||||
buzz_delay, notches, notch_quality)
|
||||
|
||||
def _create_filter(self, fixed_filter, cmd_queue):
|
||||
return sos_filter.SosFilter(self._sensor.get_mcu(), cmd_queue,
|
||||
fixed_filter, 4)
|
||||
|
||||
def update_from_command(self, gcmd, cq=None):
|
||||
gcmd_filter = self._build_filter(gcmd)
|
||||
# if filters are identical, no change required
|
||||
if self._active_design == gcmd_filter:
|
||||
return
|
||||
# update MCU filter from GCode command
|
||||
self._sos_filter.change_filter(
|
||||
self._active_design.design_filter(gcmd.error))
|
||||
|
||||
def get_sos_filter(self):
|
||||
return self._sos_filter
|
||||
|
||||
|
||||
# check results from the collector for errors and raise an exception is found
|
||||
def check_sensor_errors(results, printer):
|
||||
samples, errors = results
|
||||
if errors:
|
||||
raise printer.command_error("Load cell sensor reported errors while"
|
||||
" probing: %i errors, %i overflows" % (
|
||||
errors[0], errors[1]))
|
||||
return samples
|
||||
|
||||
|
||||
class LoadCellProbeConfigHelper:
|
||||
def __init__(self, config, load_cell_inst):
|
||||
self._printer = config.get_printer()
|
||||
self._load_cell = load_cell_inst
|
||||
self._sensor = load_cell_inst.get_sensor()
|
||||
self._rest_time = 1. / float(self._sensor.get_samples_per_second())
|
||||
# Collect 4 x 60hz power cycles of data to average across power noise
|
||||
self._tare_time_param = floatParamHelper(config, 'tare_time',
|
||||
default=4. / 60., minval=0.01, maxval=1.0)
|
||||
# triggering options
|
||||
self._trigger_force_param = intParamHelper(config, 'trigger_force',
|
||||
default=75, minval=10, maxval=250)
|
||||
self._force_safety_limit_param = intParamHelper(config,
|
||||
'force_safety_limit', minval=100, maxval=5000, default=2000)
|
||||
|
||||
def get_tare_samples(self, gcmd=None):
|
||||
tare_time = self._tare_time_param.get(gcmd)
|
||||
sps = self._sensor.get_samples_per_second()
|
||||
return max(2, math.ceil(tare_time * sps))
|
||||
|
||||
def get_trigger_force_grams(self, gcmd=None):
|
||||
return self._trigger_force_param.get(gcmd)
|
||||
|
||||
def get_safety_limit_grams(self, gcmd=None):
|
||||
return self._force_safety_limit_param.get(gcmd)
|
||||
|
||||
def get_rest_time(self):
|
||||
return self._rest_time
|
||||
|
||||
def get_safety_range(self, gcmd=None):
|
||||
counts_per_gram = self._load_cell.get_counts_per_gram()
|
||||
# calculate the safety band
|
||||
zero = self._load_cell.get_reference_tare_counts()
|
||||
safety_counts = int(counts_per_gram * self.get_safety_limit_grams(gcmd))
|
||||
safety_min = int(zero - safety_counts)
|
||||
safety_max = int(zero + safety_counts)
|
||||
# don't allow a safety range outside the sensor's real range
|
||||
sensor_min, sensor_max = self._load_cell.get_sensor().get_range()
|
||||
if safety_min <= sensor_min or safety_max >= sensor_max:
|
||||
cmd_err = self._printer.command_error
|
||||
raise cmd_err("Load cell force_safety_limit exceeds sensor range!")
|
||||
return safety_min, safety_max
|
||||
|
||||
# calculate 1/counts_per_gram in Q2 fixed point
|
||||
def get_grams_per_count(self):
|
||||
counts_per_gram = self._load_cell.get_counts_per_gram()
|
||||
# The counts_per_gram could be so large that it becomes 0.0 when
|
||||
# converted to Q2 format. This would mean the ADC range only measures a
|
||||
# few grams which seems very unlikely. Treat this as an error:
|
||||
if counts_per_gram >= 2**Q2_FRAC_BITS:
|
||||
raise OverflowError("counts_per_gram value is too large to filter")
|
||||
return sos_filter.to_fixed_32((1. / counts_per_gram), Q2_INT_BITS)
|
||||
|
||||
|
||||
# McuLoadCellProbe is the interface to `load_cell_probe` on the MCU
|
||||
# This also manages the SosFilter so all commands use one command queue
|
||||
class McuLoadCellProbe:
|
||||
WATCHDOG_MAX = 3
|
||||
ERROR_SAFETY_RANGE = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 1
|
||||
ERROR_OVERFLOW = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 2
|
||||
ERROR_WATCHDOG = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 3
|
||||
|
||||
def __init__(self, config, load_cell_inst, sos_filter_inst, config_helper,
|
||||
trigger_dispatch):
|
||||
self._printer = config.get_printer()
|
||||
self._load_cell = load_cell_inst
|
||||
self._sos_filter = sos_filter_inst
|
||||
self._config_helper = config_helper
|
||||
self._sensor = load_cell_inst.get_sensor()
|
||||
self._mcu = self._sensor.get_mcu()
|
||||
# configure MCU objects
|
||||
self._dispatch = trigger_dispatch
|
||||
self._cmd_queue = self._dispatch.get_command_queue()
|
||||
self._oid = self._mcu.create_oid()
|
||||
self._config_commands()
|
||||
self._home_cmd = None
|
||||
self._query_cmd = None
|
||||
self._set_range_cmd = None
|
||||
self._mcu.register_config_callback(self._build_config)
|
||||
self._printer.register_event_handler("klippy:connect", self._on_connect)
|
||||
|
||||
def _config_commands(self):
|
||||
self._sos_filter.create_filter()
|
||||
self._mcu.add_config_cmd(
|
||||
"config_load_cell_probe oid=%d sos_filter_oid=%d" % (
|
||||
self._oid, self._sos_filter.get_oid()))
|
||||
|
||||
def _build_config(self):
|
||||
# Lookup commands
|
||||
self._query_cmd = self._mcu.lookup_query_command(
|
||||
"load_cell_probe_query_state oid=%c",
|
||||
"load_cell_probe_state oid=%c is_homing_trigger=%c "
|
||||
"trigger_ticks=%u", oid=self._oid, cq=self._cmd_queue)
|
||||
self._set_range_cmd = self._mcu.lookup_command(
|
||||
"load_cell_probe_set_range"
|
||||
" oid=%c safety_counts_min=%i safety_counts_max=%i tare_counts=%i"
|
||||
" trigger_grams=%u grams_per_count=%i", cq=self._cmd_queue)
|
||||
self._home_cmd = self._mcu.lookup_command(
|
||||
"load_cell_probe_home oid=%c trsync_oid=%c trigger_reason=%c"
|
||||
" error_reason=%c clock=%u rest_ticks=%u timeout=%u",
|
||||
cq=self._cmd_queue)
|
||||
|
||||
# the sensor data stream is connected on the MCU at the ready event
|
||||
def _on_connect(self):
|
||||
self._sensor.attach_load_cell_probe(self._oid)
|
||||
|
||||
def get_oid(self):
|
||||
return self._oid
|
||||
|
||||
def get_mcu(self):
|
||||
return self._mcu
|
||||
|
||||
def get_load_cell(self):
|
||||
return self._load_cell
|
||||
|
||||
def get_dispatch(self):
|
||||
return self._dispatch
|
||||
|
||||
def set_endstop_range(self, tare_counts, gcmd=None):
|
||||
# update the load cell so it reflects the new tare value
|
||||
self._load_cell.tare(tare_counts)
|
||||
# update internal tare value
|
||||
safety_min, safety_max = self._config_helper.get_safety_range(gcmd)
|
||||
args = [self._oid, safety_min, safety_max, int(tare_counts),
|
||||
self._config_helper.get_trigger_force_grams(gcmd),
|
||||
self._config_helper.get_grams_per_count()]
|
||||
self._set_range_cmd.send(args)
|
||||
self._sos_filter.reset_filter()
|
||||
|
||||
def home_start(self, print_time):
|
||||
clock = self._mcu.print_time_to_clock(print_time)
|
||||
rest_time = self._config_helper.get_rest_time()
|
||||
rest_ticks = self._mcu.seconds_to_clock(rest_time)
|
||||
self._home_cmd.send([self._oid, self._dispatch.get_oid(),
|
||||
mcu.MCU_trsync.REASON_ENDSTOP_HIT, self.ERROR_SAFETY_RANGE, clock,
|
||||
rest_ticks, self.WATCHDOG_MAX], reqclock=clock)
|
||||
|
||||
def clear_home(self):
|
||||
params = self._query_cmd.send([self._oid])
|
||||
# The time of the first sample that triggered is in "trigger_ticks"
|
||||
trigger_ticks = self._mcu.clock32_to_clock64(params['trigger_ticks'])
|
||||
# clear trsync from load_cell_endstop
|
||||
self._home_cmd.send([self._oid, 0, 0, 0, 0, 0, 0, 0])
|
||||
return self._mcu.clock_to_print_time(trigger_ticks)
|
||||
|
||||
|
||||
# Execute probing moves using the McuLoadCellProbe
|
||||
class LoadCellProbingMove:
|
||||
ERROR_MAP = {
|
||||
mcu.MCU_trsync.REASON_COMMS_TIMEOUT: "Communication timeout during "
|
||||
"homing",
|
||||
McuLoadCellProbe.ERROR_SAFETY_RANGE: "Load Cell Probe Error: load "
|
||||
"exceeds safety limit",
|
||||
McuLoadCellProbe.ERROR_OVERFLOW: "Load Cell Probe Error: fixed point "
|
||||
"math overflow",
|
||||
McuLoadCellProbe.ERROR_WATCHDOG: "Load Cell Probe Error: timed out "
|
||||
"waiting for sensor data"
|
||||
}
|
||||
|
||||
def __init__(self, config, mcu_load_cell_probe, param_helper,
|
||||
continuous_tare_filter_helper, config_helper):
|
||||
self._printer = config.get_printer()
|
||||
self._mcu_load_cell_probe = mcu_load_cell_probe
|
||||
self._param_helper = param_helper
|
||||
self._continuous_tare_filter_helper = continuous_tare_filter_helper
|
||||
self._config_helper = config_helper
|
||||
self._mcu = mcu_load_cell_probe.get_mcu()
|
||||
self._load_cell = mcu_load_cell_probe.get_load_cell()
|
||||
self._z_min_position = probe.lookup_minimum_z(config)
|
||||
self._dispatch = mcu_load_cell_probe.get_dispatch()
|
||||
probe.LookupZSteppers(config, self._dispatch.add_stepper)
|
||||
# internal state tracking
|
||||
self._tare_counts = 0
|
||||
self._last_trigger_time = 0
|
||||
|
||||
def _start_collector(self):
|
||||
toolhead = self._printer.lookup_object('toolhead')
|
||||
# homing uses the toolhead last move time which gets special handling
|
||||
# to significantly buffer print_time if the move queue has drained
|
||||
print_time = toolhead.get_last_move_time()
|
||||
collector = self._load_cell.get_collector()
|
||||
collector.start_collecting(min_time=print_time)
|
||||
return collector
|
||||
|
||||
# pauses for the last move to complete and then
|
||||
# sets the endstop tare value and range
|
||||
def _pause_and_tare(self, gcmd):
|
||||
collector = self._start_collector()
|
||||
num_samples = self._config_helper.get_tare_samples(gcmd)
|
||||
# use collect_min collected samples are not wasted
|
||||
results = collector.collect_min(num_samples)
|
||||
tare_samples = check_sensor_errors(results, self._printer)
|
||||
tare_counts = np.average(np.array(tare_samples)[:, 2].astype(float))
|
||||
# update sos_filter with any gcode parameter changes
|
||||
self._continuous_tare_filter_helper.update_from_command(gcmd)
|
||||
self._mcu_load_cell_probe.set_endstop_range(tare_counts, gcmd)
|
||||
|
||||
def _home_start(self, print_time):
|
||||
# start trsync
|
||||
trigger_completion = self._dispatch.start(print_time)
|
||||
self._mcu_load_cell_probe.home_start(print_time)
|
||||
return trigger_completion
|
||||
|
||||
def home_start(self, print_time, sample_time, sample_count, rest_time,
|
||||
triggered=True):
|
||||
return self._home_start(print_time)
|
||||
|
||||
def home_wait(self, home_end_time):
|
||||
self._dispatch.wait_end(home_end_time)
|
||||
# trigger has happened, now to find out why...
|
||||
res = self._dispatch.stop()
|
||||
# clear the homing state so it stops processing samples
|
||||
self._last_trigger_time = self._mcu_load_cell_probe.clear_home()
|
||||
if res >= mcu.MCU_trsync.REASON_COMMS_TIMEOUT:
|
||||
error = "Load Cell Probe Error: unknown reason code %i" % (res,)
|
||||
if res in self.ERROR_MAP:
|
||||
error = self.ERROR_MAP[res]
|
||||
raise self._printer.command_error(error)
|
||||
if res != mcu.MCU_trsync.REASON_ENDSTOP_HIT:
|
||||
return 0.
|
||||
return self._last_trigger_time
|
||||
|
||||
def get_steppers(self):
|
||||
return self._dispatch.get_steppers()
|
||||
|
||||
# Probe towards z_min until the load_cell_probe on the MCU triggers
|
||||
def probing_move(self, gcmd):
|
||||
# do not permit probing if the load cell is not calibrated
|
||||
if not self._load_cell.is_calibrated():
|
||||
raise self._printer.command_error("Load Cell not calibrated")
|
||||
# tare the sensor just before probing
|
||||
self._pause_and_tare(gcmd)
|
||||
# get params for the homing move
|
||||
toolhead = self._printer.lookup_object('toolhead')
|
||||
pos = toolhead.get_position()
|
||||
pos[2] = self._z_min_position
|
||||
speed = self._param_helper.get_probe_params(gcmd)['probe_speed']
|
||||
phoming = self._printer.lookup_object('homing')
|
||||
# start collector after tare samples are consumed
|
||||
collector = self._start_collector()
|
||||
# do homing move
|
||||
return phoming.probing_move(self, pos, speed), collector
|
||||
|
||||
# Wait for the MCU to trigger with no movement
|
||||
def probing_test(self, gcmd, timeout):
|
||||
self._pause_and_tare(gcmd)
|
||||
toolhead = self._printer.lookup_object('toolhead')
|
||||
print_time = toolhead.get_last_move_time()
|
||||
self._home_start(print_time)
|
||||
return self.home_wait(print_time + timeout)
|
||||
|
||||
def get_status(self, eventtime):
|
||||
return {
|
||||
'tare_counts': self._tare_counts,
|
||||
'last_trigger_time': self._last_trigger_time,
|
||||
}
|
||||
|
||||
|
||||
# Perform a single complete tap
|
||||
class TappingMove:
|
||||
def __init__(self, config, load_cell_probing_move, config_helper):
|
||||
self._printer = config.get_printer()
|
||||
self._load_cell_probing_move = load_cell_probing_move
|
||||
self._config_helper = config_helper
|
||||
# track results of the last tap
|
||||
self._last_result = None
|
||||
self._is_last_result_valid = False
|
||||
# webhooks support
|
||||
self._clients = load_cell.ApiClientHelper(config.get_printer())
|
||||
name = config.get_name()
|
||||
header = {"header": ["probe_tap_event"]}
|
||||
self._clients.add_mux_endpoint("load_cell_probe/dump_taps",
|
||||
"load_cell_probe", name, header)
|
||||
|
||||
# perform a probing move and a pullback move
|
||||
def run_tap(self, gcmd):
|
||||
# do the descending move
|
||||
epos, collector = self._load_cell_probing_move.probing_move(gcmd)
|
||||
# collect samples from the tap
|
||||
toolhead = self._printer.lookup_object('toolhead')
|
||||
toolhead.flush_step_generation()
|
||||
move_end = toolhead.get_last_move_time()
|
||||
results = collector.collect_until(move_end)
|
||||
samples = check_sensor_errors(results, self._printer)
|
||||
# Analyze the tap data
|
||||
ppa = TapAnalysis(samples)
|
||||
# broadcast tap event data:
|
||||
self._clients.send({'tap': ppa.to_dict()})
|
||||
self._is_last_result_valid = True
|
||||
self._last_result = epos[2]
|
||||
return epos, self._is_last_result_valid
|
||||
|
||||
def get_status(self, eventtime):
|
||||
return {
|
||||
'last_z_result': self._last_result,
|
||||
'is_last_tap_valid': self._is_last_result_valid
|
||||
}
|
||||
|
||||
|
||||
# ProbeSession that implements Tap logic
|
||||
class TapSession:
|
||||
def __init__(self, config, tapping_move, probe_params_helper):
|
||||
self._printer = config.get_printer()
|
||||
self._tapping_move = tapping_move
|
||||
self._probe_params_helper = probe_params_helper
|
||||
# Session state
|
||||
self._results = []
|
||||
|
||||
def start_probe_session(self, gcmd):
|
||||
return self
|
||||
|
||||
def end_probe_session(self):
|
||||
self._results = []
|
||||
|
||||
# probe until a single good sample is returned or retries are exhausted
|
||||
def run_probe(self, gcmd):
|
||||
epos, is_good = self._tapping_move.run_tap(gcmd)
|
||||
self._results.append(epos)
|
||||
|
||||
def pull_probed_results(self):
|
||||
res = self._results
|
||||
self._results = []
|
||||
return res
|
||||
|
||||
|
||||
class LoadCellProbeCommands:
|
||||
def __init__(self, config, load_cell_probing_move):
|
||||
self._printer = config.get_printer()
|
||||
self._load_cell_probing_move = load_cell_probing_move
|
||||
self._register_commands()
|
||||
|
||||
def _register_commands(self):
|
||||
# Register commands
|
||||
gcode = self._printer.lookup_object('gcode')
|
||||
gcode.register_command("LOAD_CELL_TEST_TAP",
|
||||
self.cmd_LOAD_CELL_TEST_TAP, desc=self.cmd_LOAD_CELL_TEST_TAP_help)
|
||||
|
||||
cmd_LOAD_CELL_TEST_TAP_help = "Tap the load cell probe to verify operation"
|
||||
|
||||
def cmd_LOAD_CELL_TEST_TAP(self, gcmd):
|
||||
taps = gcmd.get_int("TAPS", 3, minval=1, maxval=10)
|
||||
timeout = gcmd.get_float("TIMEOUT", 30., minval=1., maxval=120.)
|
||||
gcmd.respond_info("Tap the load cell %s times:" % (taps,))
|
||||
reactor = self._printer.get_reactor()
|
||||
for i in range(0, taps):
|
||||
result = self._load_cell_probing_move.probing_test(gcmd, timeout)
|
||||
if result == 0.:
|
||||
# notify of error, likely due to timeout
|
||||
raise gcmd.error("Test timeout out")
|
||||
gcmd.respond_info("Tap Detected!")
|
||||
# give the user some time for their finger to move away
|
||||
reactor.pause(reactor.monotonic() + 0.2)
|
||||
gcmd.respond_info("Test complete, %s taps detected" % (taps,))
|
||||
|
||||
|
||||
class LoadCellPrinterProbe:
|
||||
def __init__(self, config):
|
||||
cfg_error = config.error
|
||||
try:
|
||||
global np
|
||||
import numpy as np
|
||||
except:
|
||||
raise cfg_error("[load_cell_probe] requires the NumPy module")
|
||||
self._printer = config.get_printer()
|
||||
# Sensor types supported by load_cell_probe
|
||||
sensors = {}
|
||||
sensors.update(hx71x.HX71X_SENSOR_TYPES)
|
||||
sensors.update(ads1220.ADS1220_SENSOR_TYPE)
|
||||
sensor_class = config.getchoice('sensor_type', sensors)
|
||||
sensor = sensor_class(config)
|
||||
self._load_cell = load_cell.LoadCell(config, sensor)
|
||||
# Read all user configuration and build modules
|
||||
config_helper = LoadCellProbeConfigHelper(config, self._load_cell)
|
||||
self._mcu = self._load_cell.get_sensor().get_mcu()
|
||||
trigger_dispatch = mcu.TriggerDispatch(self._mcu)
|
||||
continuous_tare_filter_helper = ContinuousTareFilterHelper(config,
|
||||
sensor, trigger_dispatch.get_command_queue())
|
||||
# Probe Interface
|
||||
self._param_helper = probe.ProbeParameterHelper(config)
|
||||
self._cmd_helper = probe.ProbeCommandHelper(config, self)
|
||||
self._probe_offsets = probe.ProbeOffsetsHelper(config)
|
||||
self._mcu_load_cell_probe = McuLoadCellProbe(config, self._load_cell,
|
||||
continuous_tare_filter_helper.get_sos_filter(), config_helper,
|
||||
trigger_dispatch)
|
||||
load_cell_probing_move = LoadCellProbingMove(config,
|
||||
self._mcu_load_cell_probe, self._param_helper,
|
||||
continuous_tare_filter_helper, config_helper)
|
||||
self._tapping_move = TappingMove(config, load_cell_probing_move,
|
||||
config_helper)
|
||||
tap_session = TapSession(config, self._tapping_move, self._param_helper)
|
||||
self._probe_session = probe.ProbeSessionHelper(config,
|
||||
self._param_helper, tap_session.start_probe_session)
|
||||
# printer integration
|
||||
LoadCellProbeCommands(config, load_cell_probing_move)
|
||||
probe.ProbeVirtualEndstopDeprecation(config)
|
||||
self._printer.add_object('probe', self)
|
||||
|
||||
def get_probe_params(self, gcmd=None):
|
||||
return self._param_helper.get_probe_params(gcmd)
|
||||
|
||||
def get_offsets(self):
|
||||
return self._probe_offsets.get_offsets()
|
||||
|
||||
def start_probe_session(self, gcmd):
|
||||
return self._probe_session.start_probe_session(gcmd)
|
||||
|
||||
def get_status(self, eventtime):
|
||||
status = self._cmd_helper.get_status(eventtime)
|
||||
status.update(self._load_cell.get_status(eventtime))
|
||||
status.update(self._tapping_move.get_status(eventtime))
|
||||
return status
|
||||
|
||||
|
||||
def load_config(config):
|
||||
return LoadCellPrinterProbe(config)
|
||||
@@ -5,6 +5,14 @@
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging, bisect
|
||||
|
||||
# Helper to lookup the Z stepper config section
|
||||
def lookup_z_endstop_config(config):
|
||||
if config.has_section('stepper_z'):
|
||||
return config.getsection('stepper_z')
|
||||
elif config.has_section('carriage z'):
|
||||
return config.getsection('carriage z')
|
||||
return None
|
||||
|
||||
class ManualProbe:
|
||||
def __init__(self, config):
|
||||
self.printer = config.get_printer()
|
||||
@@ -14,9 +22,13 @@ class ManualProbe:
|
||||
self.gcode.register_command('MANUAL_PROBE', self.cmd_MANUAL_PROBE,
|
||||
desc=self.cmd_MANUAL_PROBE_help)
|
||||
# Endstop value for cartesian printers with separate Z axis
|
||||
zconfig = config.getsection('stepper_z')
|
||||
self.z_position_endstop = zconfig.getfloat('position_endstop', None,
|
||||
note_valid=False)
|
||||
zconfig = lookup_z_endstop_config(config)
|
||||
if zconfig is not None:
|
||||
self.z_position_endstop = zconfig.getfloat('position_endstop', None,
|
||||
note_valid=False)
|
||||
self.z_endstop_config_name = zconfig.get_name()
|
||||
else:
|
||||
self.z_position_endstop = self.z_endstop_config_name = None
|
||||
# Endstop values for linear delta printers with vertical A,B,C towers
|
||||
a_tower_config = config.getsection('stepper_a')
|
||||
self.a_position_endstop = a_tower_config.getfloat('position_endstop',
|
||||
@@ -67,11 +79,13 @@ class ManualProbe:
|
||||
return
|
||||
z_pos = self.z_position_endstop - kin_pos[2]
|
||||
self.gcode.respond_info(
|
||||
"stepper_z: position_endstop: %.3f\n"
|
||||
"%s: position_endstop: %.3f\n"
|
||||
"The SAVE_CONFIG command will update the printer config file\n"
|
||||
"with the above and restart the printer." % (z_pos,))
|
||||
"with the above and restart the printer." % (
|
||||
self.z_endstop_config_name, z_pos,))
|
||||
configfile = self.printer.lookup_object('configfile')
|
||||
configfile.set('stepper_z', 'position_endstop', "%.3f" % (z_pos,))
|
||||
configfile.set(self.z_endstop_config_name, 'position_endstop',
|
||||
"%.3f" % (z_pos,))
|
||||
cmd_Z_ENDSTOP_CALIBRATE_help = "Calibrate a Z endstop"
|
||||
def cmd_Z_ENDSTOP_CALIBRATE(self, gcmd):
|
||||
ManualProbeHelper(self.printer, gcmd, self.z_endstop_finalize)
|
||||
@@ -83,11 +97,12 @@ class ManualProbe:
|
||||
else:
|
||||
new_calibrate = self.z_position_endstop - offset
|
||||
self.gcode.respond_info(
|
||||
"stepper_z: position_endstop: %.3f\n"
|
||||
"%s: position_endstop: %.3f\n"
|
||||
"The SAVE_CONFIG command will update the printer config file\n"
|
||||
"with the above and restart the printer." % (new_calibrate))
|
||||
configfile.set('stepper_z', 'position_endstop',
|
||||
"%.3f" % (new_calibrate,))
|
||||
"with the above and restart the printer." % (
|
||||
self.z_endstop_config_name, new_calibrate))
|
||||
configfile.set(self.z_endstop_config_name, 'position_endstop',
|
||||
"%.3f" % (new_calibrate,))
|
||||
def cmd_Z_OFFSET_APPLY_DELTA_ENDSTOPS(self,gcmd):
|
||||
offset = self.gcode_move.get_status()['homing_origin'].z
|
||||
configfile = self.printer.lookup_object('configfile')
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Support for a manual controlled stepper
|
||||
#
|
||||
# Copyright (C) 2019-2021 Kevin O'Connor <kevin@koconnor.net>
|
||||
# Copyright (C) 2019-2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging
|
||||
import stepper, chelper
|
||||
from . import force_move
|
||||
|
||||
@@ -11,7 +12,7 @@ class ManualStepper:
|
||||
self.printer = config.get_printer()
|
||||
if config.get('endstop_pin', None) is not None:
|
||||
self.can_home = True
|
||||
self.rail = stepper.PrinterRail(
|
||||
self.rail = stepper.LookupRail(
|
||||
config, need_position_minmax=False, default_position_endstop=0.)
|
||||
self.steppers = self.rail.get_steppers()
|
||||
else:
|
||||
@@ -21,13 +22,19 @@ class ManualStepper:
|
||||
self.velocity = config.getfloat('velocity', 5., above=0.)
|
||||
self.accel = self.homing_accel = config.getfloat('accel', 0., minval=0.)
|
||||
self.next_cmd_time = 0.
|
||||
self.commanded_pos = 0.
|
||||
self.pos_min = config.getfloat('position_min', None)
|
||||
self.pos_max = config.getfloat('position_max', None)
|
||||
# Setup iterative solver
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free)
|
||||
self.trapq_append = ffi_lib.trapq_append
|
||||
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
|
||||
self.motion_queuing = self.printer.load_object(config, 'motion_queuing')
|
||||
self.trapq = self.motion_queuing.allocate_trapq()
|
||||
self.trapq_append = self.motion_queuing.lookup_trapq_append()
|
||||
self.rail.setup_itersolve('cartesian_stepper_alloc', b'x')
|
||||
self.rail.set_trapq(self.trapq)
|
||||
# Registered with toolhead as an axtra axis
|
||||
self.axis_gcode_id = None
|
||||
self.instant_corner_v = 0.
|
||||
self.gaxis_limit_velocity = self.gaxis_limit_accel = 0.
|
||||
# Register commands
|
||||
stepper_name = config.get_name().split()[1]
|
||||
gcode = self.printer.lookup_object('gcode')
|
||||
@@ -54,21 +61,25 @@ class ManualStepper:
|
||||
se.motor_disable(self.next_cmd_time)
|
||||
self.sync_print_time()
|
||||
def do_set_position(self, setpos):
|
||||
self.rail.set_position([setpos, 0., 0.])
|
||||
def do_move(self, movepos, speed, accel, sync=True):
|
||||
self.sync_print_time()
|
||||
cp = self.rail.get_commanded_position()
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.flush_step_generation()
|
||||
self.commanded_pos = setpos
|
||||
self.rail.set_position([self.commanded_pos, 0., 0.])
|
||||
def _submit_move(self, movetime, movepos, speed, accel):
|
||||
cp = self.commanded_pos
|
||||
dist = movepos - cp
|
||||
axis_r, accel_t, cruise_t, cruise_v = force_move.calc_move_time(
|
||||
dist, speed, accel)
|
||||
self.trapq_append(self.trapq, self.next_cmd_time,
|
||||
self.trapq_append(self.trapq, movetime,
|
||||
accel_t, cruise_t, accel_t,
|
||||
cp, 0., 0., axis_r, 0., 0.,
|
||||
0., cruise_v, accel)
|
||||
self.next_cmd_time = self.next_cmd_time + accel_t + cruise_t + accel_t
|
||||
self.rail.generate_steps(self.next_cmd_time)
|
||||
self.trapq_finalize_moves(self.trapq, self.next_cmd_time + 99999.9,
|
||||
self.next_cmd_time + 99999.9)
|
||||
self.commanded_pos = movepos
|
||||
return movetime + accel_t + cruise_t + accel_t
|
||||
def do_move(self, movepos, speed, accel, sync=True):
|
||||
self.sync_print_time()
|
||||
self.next_cmd_time = self._submit_move(self.next_cmd_time, movepos,
|
||||
speed, accel)
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.note_mcu_movequeue_activity(self.next_cmd_time)
|
||||
if sync:
|
||||
@@ -85,6 +96,10 @@ class ManualStepper:
|
||||
triggered, check_trigger)
|
||||
cmd_MANUAL_STEPPER_help = "Command a manually configured stepper"
|
||||
def cmd_MANUAL_STEPPER(self, gcmd):
|
||||
if gcmd.get('GCODE_AXIS', None) is not None:
|
||||
return self.command_with_gcode_axis(gcmd)
|
||||
if self.axis_gcode_id is not None:
|
||||
raise gcmd.error("Must unregister from gcode axis first")
|
||||
enable = gcmd.get_int('ENABLE', None)
|
||||
if enable is not None:
|
||||
self.do_enable(enable)
|
||||
@@ -96,19 +111,90 @@ class ManualStepper:
|
||||
homing_move = gcmd.get_int('STOP_ON_ENDSTOP', 0)
|
||||
if homing_move:
|
||||
movepos = gcmd.get_float('MOVE')
|
||||
if ((self.pos_min is not None and movepos < self.pos_min)
|
||||
or (self.pos_max is not None and movepos > self.pos_max)):
|
||||
raise gcmd.error("Move out of range")
|
||||
self.do_homing_move(movepos, speed, accel,
|
||||
homing_move > 0, abs(homing_move) == 1)
|
||||
elif gcmd.get_float('MOVE', None) is not None:
|
||||
movepos = gcmd.get_float('MOVE')
|
||||
if ((self.pos_min is not None and movepos < self.pos_min)
|
||||
or (self.pos_max is not None and movepos > self.pos_max)):
|
||||
raise gcmd.error("Move out of range")
|
||||
sync = gcmd.get_int('SYNC', 1)
|
||||
self.do_move(movepos, speed, accel, sync)
|
||||
elif gcmd.get_int('SYNC', 0):
|
||||
self.sync_print_time()
|
||||
# Register as a gcode axis
|
||||
def command_with_gcode_axis(self, gcmd):
|
||||
gcode_move = self.printer.lookup_object("gcode_move")
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
gcode_axis = gcmd.get('GCODE_AXIS').upper()
|
||||
instant_corner_v = gcmd.get_float('INSTANTANEOUS_CORNER_VELOCITY', 1.,
|
||||
minval=0.)
|
||||
limit_velocity = gcmd.get_float('LIMIT_VELOCITY', 999999.9, above=0.)
|
||||
limit_accel = gcmd.get_float('LIMIT_ACCEL', 999999.9, above=0.)
|
||||
if self.axis_gcode_id is not None:
|
||||
if gcode_axis:
|
||||
raise gcmd.error("Must unregister axis first")
|
||||
# Unregister
|
||||
toolhead.remove_extra_axis(self)
|
||||
self.axis_gcode_id = None
|
||||
return
|
||||
if (len(gcode_axis) != 1 or not gcode_axis.isupper()
|
||||
or gcode_axis in "XYZEFN"):
|
||||
if not gcode_axis:
|
||||
# Request to unregister already unregistered axis
|
||||
return
|
||||
raise gcmd.error("Not a valid GCODE_AXIS")
|
||||
for ea in toolhead.get_extra_axes():
|
||||
if ea is not None and ea.get_axis_gcode_id() == gcode_axis:
|
||||
raise gcmd.error("Axis '%s' already registered" % (gcode_axis,))
|
||||
self.axis_gcode_id = gcode_axis
|
||||
self.instant_corner_v = instant_corner_v
|
||||
self.gaxis_limit_velocity = limit_velocity
|
||||
self.gaxis_limit_accel = limit_accel
|
||||
toolhead.add_extra_axis(self, self.commanded_pos)
|
||||
def process_move(self, print_time, move, ea_index):
|
||||
axis_r = move.axes_r[ea_index]
|
||||
start_pos = move.start_pos[ea_index]
|
||||
accel = move.accel * axis_r
|
||||
start_v = move.start_v * axis_r
|
||||
cruise_v = move.cruise_v * axis_r
|
||||
self.trapq_append(self.trapq, print_time,
|
||||
move.accel_t, move.cruise_t, move.decel_t,
|
||||
start_pos, 0., 0.,
|
||||
1., 0., 0.,
|
||||
start_v, cruise_v, accel)
|
||||
self.commanded_pos = move.end_pos[ea_index]
|
||||
def check_move(self, move, ea_index):
|
||||
# Check move is in bounds
|
||||
movepos = move.end_pos[ea_index]
|
||||
if ((self.pos_min is not None and movepos < self.pos_min)
|
||||
or (self.pos_max is not None and movepos > self.pos_max)):
|
||||
raise move.move_error()
|
||||
# Check if need to limit maximum velocity and acceleration
|
||||
axis_ratio = move.move_d / abs(move.axes_d[ea_index])
|
||||
limit_velocity = self.gaxis_limit_velocity * axis_ratio
|
||||
limit_accel = self.gaxis_limit_accel * axis_ratio
|
||||
if not move.is_kinematic_move and self.accel:
|
||||
limit_accel = min(limit_accel, self.accel * axis_ratio)
|
||||
move.limit_speed(limit_velocity, limit_accel)
|
||||
def calc_junction(self, prev_move, move, ea_index):
|
||||
diff_r = move.axes_r[ea_index] - prev_move.axes_r[ea_index]
|
||||
if diff_r:
|
||||
return (self.instant_corner_v / abs(diff_r))**2
|
||||
return move.max_cruise_v2
|
||||
def get_axis_gcode_id(self):
|
||||
return self.axis_gcode_id
|
||||
def get_trapq(self):
|
||||
return self.trapq
|
||||
# Toolhead wrappers to support homing
|
||||
def flush_step_generation(self):
|
||||
self.sync_print_time()
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.flush_step_generation()
|
||||
def get_position(self):
|
||||
return [self.rail.get_commanded_position(), 0., 0., 0.]
|
||||
return [self.commanded_pos, 0., 0., 0.]
|
||||
def set_position(self, newpos, homing_axes=""):
|
||||
self.do_set_position(newpos[0])
|
||||
def get_last_move_time(self):
|
||||
@@ -117,7 +203,18 @@ class ManualStepper:
|
||||
def dwell(self, delay):
|
||||
self.next_cmd_time += max(0., delay)
|
||||
def drip_move(self, newpos, speed, drip_completion):
|
||||
self.do_move(newpos[0], speed, self.homing_accel)
|
||||
# Submit move to trapq
|
||||
self.sync_print_time()
|
||||
maxtime = self._submit_move(self.next_cmd_time, newpos[0],
|
||||
speed, self.homing_accel)
|
||||
# Drip updates to motors
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.drip_update_time(maxtime, drip_completion)
|
||||
# Clear trapq of any remaining parts of movement
|
||||
reactor = self.printer.get_reactor()
|
||||
self.motion_queuing.wipe_trapq(self.trapq)
|
||||
self.rail.set_position([self.commanded_pos, 0., 0.])
|
||||
self.sync_print_time()
|
||||
def get_kinematics(self):
|
||||
return self
|
||||
def get_steppers(self):
|
||||
|
||||
@@ -1,75 +1,14 @@
|
||||
# MCP4018 digipot support (via bit-banging)
|
||||
# MCP4018 digipot support
|
||||
#
|
||||
# Copyright (C) 2019 Kevin O'Connor <kevin@koconnor.net>
|
||||
# Copyright (C) 2019-2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
class SoftwareI2C:
|
||||
def __init__(self, config, addr):
|
||||
self.addr = addr << 1
|
||||
self.update_pin_cmd = None
|
||||
# Lookup pins
|
||||
ppins = config.get_printer().lookup_object('pins')
|
||||
scl_pin = config.get('scl_pin')
|
||||
scl_params = ppins.lookup_pin(scl_pin, share_type='sw_scl')
|
||||
self.mcu = scl_params['chip']
|
||||
self.scl_pin = scl_params['pin']
|
||||
self.scl_main = scl_params.get('class')
|
||||
if self.scl_main is None:
|
||||
self.scl_main = scl_params['class'] = self
|
||||
self.scl_oid = self.mcu.create_oid()
|
||||
self.cmd_queue = self.mcu.alloc_command_queue()
|
||||
self.mcu.register_config_callback(self.build_config)
|
||||
else:
|
||||
self.scl_oid = self.scl_main.scl_oid
|
||||
self.cmd_queue = self.scl_main.cmd_queue
|
||||
sda_params = ppins.lookup_pin(config.get('sda_pin'))
|
||||
self.sda_oid = self.mcu.create_oid()
|
||||
if sda_params['chip'] != self.mcu:
|
||||
raise ppins.error("%s: scl_pin and sda_pin must be on same mcu" % (
|
||||
config.get_name(),))
|
||||
self.mcu.add_config_cmd("config_digital_out oid=%d pin=%s"
|
||||
" value=%d default_value=%d max_duration=%d" % (
|
||||
self.sda_oid, sda_params['pin'], 1, 1, 0))
|
||||
def get_mcu(self):
|
||||
return self.mcu
|
||||
def build_config(self):
|
||||
self.mcu.add_config_cmd("config_digital_out oid=%d pin=%s value=%d"
|
||||
" default_value=%d max_duration=%d" % (
|
||||
self.scl_oid, self.scl_pin, 1, 1, 0))
|
||||
self.update_pin_cmd = self.mcu.lookup_command(
|
||||
"update_digital_out oid=%c value=%c", cq=self.cmd_queue)
|
||||
def i2c_write(self, msg, minclock=0, reqclock=0):
|
||||
msg = [self.addr] + msg
|
||||
send = self.scl_main.update_pin_cmd.send
|
||||
# Send ack
|
||||
send([self.sda_oid, 0], minclock=minclock, reqclock=reqclock)
|
||||
send([self.scl_oid, 0], minclock=minclock, reqclock=reqclock)
|
||||
# Send bytes
|
||||
sda_last = 0
|
||||
for data in msg:
|
||||
# Transmit 8 data bits
|
||||
for i in range(8):
|
||||
sda_next = not not (data & (0x80 >> i))
|
||||
if sda_last != sda_next:
|
||||
sda_last = sda_next
|
||||
send([self.sda_oid, sda_last],
|
||||
minclock=minclock, reqclock=reqclock)
|
||||
send([self.scl_oid, 1], minclock=minclock, reqclock=reqclock)
|
||||
send([self.scl_oid, 0], minclock=minclock, reqclock=reqclock)
|
||||
# Transmit clock for ack
|
||||
send([self.scl_oid, 1], minclock=minclock, reqclock=reqclock)
|
||||
send([self.scl_oid, 0], minclock=minclock, reqclock=reqclock)
|
||||
# Send stop
|
||||
if sda_last:
|
||||
send([self.sda_oid, 0], minclock=minclock, reqclock=reqclock)
|
||||
send([self.scl_oid, 1], minclock=minclock, reqclock=reqclock)
|
||||
send([self.sda_oid, 1], minclock=minclock, reqclock=reqclock)
|
||||
from . import bus
|
||||
|
||||
class mcp4018:
|
||||
def __init__(self, config):
|
||||
self.printer = config.get_printer()
|
||||
self.i2c = SoftwareI2C(config, 0x2f)
|
||||
self.i2c = bus.MCU_I2C_from_config(config, default_addr=0x2f)
|
||||
self.scale = config.getfloat('scale', 1., above=0.)
|
||||
self.start_value = config.getfloat('wiper',
|
||||
minval=0., maxval=self.scale)
|
||||
|
||||
100
klippy/extras/motion_queuing.py
Normal file
100
klippy/extras/motion_queuing.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# Helper code for low-level motion queuing and flushing
|
||||
#
|
||||
# Copyright (C) 2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging
|
||||
import chelper
|
||||
|
||||
MOVE_HISTORY_EXPIRE = 30.
|
||||
|
||||
class PrinterMotionQueuing:
|
||||
def __init__(self, config):
|
||||
self.printer = config.get_printer()
|
||||
self.trapqs = []
|
||||
self.stepcompress = []
|
||||
self.steppersyncs = []
|
||||
self.flush_callbacks = []
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
|
||||
self.steppersync_generate_steps = ffi_lib.steppersync_generate_steps
|
||||
self.steppersync_flush = ffi_lib.steppersync_flush
|
||||
self.steppersync_history_expire = ffi_lib.steppersync_history_expire
|
||||
self.clear_history_time = 0.
|
||||
is_debug = self.printer.get_start_args().get('debugoutput') is not None
|
||||
self.is_debugoutput = is_debug
|
||||
def allocate_trapq(self):
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free)
|
||||
self.trapqs.append(trapq)
|
||||
return trapq
|
||||
def allocate_stepcompress(self, mcu, oid):
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
sc = ffi_main.gc(ffi_lib.stepcompress_alloc(oid),
|
||||
ffi_lib.stepcompress_free)
|
||||
self.stepcompress.append((mcu, sc))
|
||||
return sc
|
||||
def allocate_steppersync(self, mcu, serialqueue, move_count):
|
||||
stepqueues = []
|
||||
for sc_mcu, sc in self.stepcompress:
|
||||
if sc_mcu is mcu:
|
||||
stepqueues.append(sc)
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
ss = ffi_main.gc(
|
||||
ffi_lib.steppersync_alloc(serialqueue, stepqueues, len(stepqueues),
|
||||
move_count),
|
||||
ffi_lib.steppersync_free)
|
||||
self.steppersyncs.append((mcu, ss))
|
||||
return ss
|
||||
def register_flush_callback(self, callback):
|
||||
self.flush_callbacks.append(callback)
|
||||
def unregister_flush_callback(self, callback):
|
||||
if callback in self.flush_callbacks:
|
||||
fcbs = list(self.flush_callbacks)
|
||||
fcbs.remove(callback)
|
||||
self.flush_callbacks = fcbs
|
||||
def flush_motion_queues(self, must_flush_time, max_step_gen_time,
|
||||
trapq_free_time):
|
||||
# Invoke flush callbacks (if any)
|
||||
for cb in self.flush_callbacks:
|
||||
cb(must_flush_time, max_step_gen_time)
|
||||
# Generate stepper movement and transmit
|
||||
for mcu, ss in self.steppersyncs:
|
||||
clock = max(0, mcu.print_time_to_clock(must_flush_time))
|
||||
# Generate steps
|
||||
ret = self.steppersync_generate_steps(ss, max_step_gen_time, clock)
|
||||
if ret:
|
||||
raise mcu.error("Internal error in MCU '%s' stepcompress"
|
||||
% (mcu.get_name(),))
|
||||
# Flush steps from steppersync
|
||||
ret = self.steppersync_flush(ss, clock)
|
||||
if ret:
|
||||
raise mcu.error("Internal error in MCU '%s' stepcompress"
|
||||
% (mcu.get_name(),))
|
||||
# Determine maximum history to keep
|
||||
clear_history_time = self.clear_history_time
|
||||
if self.is_debugoutput:
|
||||
clear_history_time = trapq_free_time - MOVE_HISTORY_EXPIRE
|
||||
# Move processed trapq moves to history list, and expire old history
|
||||
for trapq in self.trapqs:
|
||||
self.trapq_finalize_moves(trapq, trapq_free_time,
|
||||
clear_history_time)
|
||||
# Clean up old history entries in stepcompress objects
|
||||
for mcu, ss in self.steppersyncs:
|
||||
clock = max(0, mcu.print_time_to_clock(clear_history_time))
|
||||
self.steppersync_history_expire(ss, clock)
|
||||
def wipe_trapq(self, trapq):
|
||||
# Expire any remaining movement in the trapq (force to history list)
|
||||
NEVER = 9999999999999999.
|
||||
self.trapq_finalize_moves(trapq, NEVER, 0.)
|
||||
def lookup_trapq_append(self):
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
return ffi_lib.trapq_append
|
||||
def stats(self, eventtime):
|
||||
mcu = self.printer.lookup_object('mcu')
|
||||
est_print_time = mcu.estimated_print_time(eventtime)
|
||||
self.clear_history_time = est_print_time - MOVE_HISTORY_EXPIRE
|
||||
return False, ""
|
||||
|
||||
def load_config(config):
|
||||
return PrinterMotionQueuing(config)
|
||||
@@ -1,6 +1,6 @@
|
||||
# PWM and digital output pin handling
|
||||
#
|
||||
# Copyright (C) 2017-2024 Kevin O'Connor <kevin@koconnor.net>
|
||||
# Copyright (C) 2017-2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging, ast
|
||||
@@ -11,8 +11,6 @@ from .display import display
|
||||
# G-Code request queuing helper
|
||||
######################################################################
|
||||
|
||||
PIN_MIN_TIME = 0.100
|
||||
|
||||
# Helper code to queue g-code requests
|
||||
class GCodeRequestQueue:
|
||||
def __init__(self, config, mcu, callback):
|
||||
@@ -22,15 +20,17 @@ class GCodeRequestQueue:
|
||||
self.rqueue = []
|
||||
self.next_min_flush_time = 0.
|
||||
self.toolhead = None
|
||||
mcu.register_flush_callback(self._flush_notification)
|
||||
motion_queuing = printer.load_object(config, 'motion_queuing')
|
||||
motion_queuing.register_flush_callback(self._flush_notification)
|
||||
printer.register_event_handler("klippy:connect", self._handle_connect)
|
||||
def _handle_connect(self):
|
||||
self.toolhead = self.printer.lookup_object('toolhead')
|
||||
def _flush_notification(self, print_time, clock):
|
||||
def _flush_notification(self, must_flush_time, max_step_gen_time):
|
||||
min_sched_time = self.mcu.min_schedule_time()
|
||||
rqueue = self.rqueue
|
||||
while rqueue:
|
||||
next_time = max(rqueue[0][0], self.next_min_flush_time)
|
||||
if next_time > print_time:
|
||||
if next_time > must_flush_time:
|
||||
return
|
||||
# Skip requests that have been overridden with a following request
|
||||
pos = 0
|
||||
@@ -49,19 +49,21 @@ class GCodeRequestQueue:
|
||||
if action == "delay":
|
||||
pos -= 1
|
||||
del rqueue[:pos+1]
|
||||
self.next_min_flush_time = next_time + max(min_wait, PIN_MIN_TIME)
|
||||
self.next_min_flush_time = next_time + max(min_wait, min_sched_time)
|
||||
# Ensure following queue items are flushed
|
||||
self.toolhead.note_mcu_movequeue_activity(self.next_min_flush_time)
|
||||
self.toolhead.note_mcu_movequeue_activity(self.next_min_flush_time,
|
||||
is_step_gen=False)
|
||||
def _queue_request(self, print_time, value):
|
||||
self.rqueue.append((print_time, value))
|
||||
self.toolhead.note_mcu_movequeue_activity(print_time)
|
||||
self.toolhead.note_mcu_movequeue_activity(print_time, is_step_gen=False)
|
||||
def queue_gcode_request(self, value):
|
||||
self.toolhead.register_lookahead_callback(
|
||||
(lambda pt: self._queue_request(pt, value)))
|
||||
def send_async_request(self, value, print_time=None):
|
||||
min_sched_time = self.mcu.min_schedule_time()
|
||||
if print_time is None:
|
||||
systime = self.printer.get_reactor().monotonic()
|
||||
print_time = self.mcu.estimated_print_time(systime + PIN_MIN_TIME)
|
||||
print_time = self.mcu.estimated_print_time(systime + min_sched_time)
|
||||
while 1:
|
||||
next_time = max(print_time, self.next_min_flush_time)
|
||||
# Invoke callback for the request
|
||||
@@ -72,7 +74,7 @@ class GCodeRequestQueue:
|
||||
action, min_wait = ret
|
||||
if action == "discard":
|
||||
break
|
||||
self.next_min_flush_time = next_time + max(min_wait, PIN_MIN_TIME)
|
||||
self.next_min_flush_time = next_time + max(min_wait, min_sched_time)
|
||||
if action != "delay":
|
||||
break
|
||||
|
||||
@@ -184,8 +186,6 @@ def lookup_template_eval(config):
|
||||
# Main output pin handling
|
||||
######################################################################
|
||||
|
||||
MAX_SCHEDULE_TIME = 5.0
|
||||
|
||||
class PrinterOutputPin:
|
||||
def __init__(self, config):
|
||||
self.printer = config.get_printer()
|
||||
@@ -194,8 +194,9 @@ class PrinterOutputPin:
|
||||
self.is_pwm = config.getboolean('pwm', False)
|
||||
if self.is_pwm:
|
||||
self.mcu_pin = ppins.setup_pin('pwm', config.get('pin'))
|
||||
max_duration = self.mcu_pin.get_mcu().max_nominal_duration()
|
||||
cycle_time = config.getfloat('cycle_time', 0.100, above=0.,
|
||||
maxval=MAX_SCHEDULE_TIME)
|
||||
maxval=max_duration)
|
||||
hardware_pwm = config.getboolean('hardware_pwm', False)
|
||||
self.mcu_pin.setup_cycle_time(cycle_time, hardware_pwm)
|
||||
self.scale = config.getfloat('scale', 1., above=0.)
|
||||
|
||||
@@ -235,7 +235,7 @@ class Palette2:
|
||||
"Initialize the print, and check connection with the Palette 2")
|
||||
|
||||
def cmd_O1(self, gcmd):
|
||||
logging.info("Initializing print with Pallete 2")
|
||||
logging.info("Initializing print with Palette 2")
|
||||
if not self._check_P2(gcmd):
|
||||
raise self.printer.command_error(
|
||||
"Cannot initialize print, palette 2 is not connected")
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Copyright (C) 2022 Ricardo Alcantara <ricardo@vulcanolabs.com>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
from . import bus, led, mcp4018
|
||||
from . import bus, led
|
||||
|
||||
BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
|
||||
|
||||
@@ -25,10 +25,7 @@ PCA9632_LED3 = 0x06
|
||||
class PCA9632:
|
||||
def __init__(self, config):
|
||||
self.printer = printer = config.get_printer()
|
||||
if config.get("scl_pin", None) is not None:
|
||||
self.i2c = mcp4018.SoftwareI2C(config, 98)
|
||||
else:
|
||||
self.i2c = bus.MCU_I2C_from_config(config, default_addr=98)
|
||||
self.i2c = bus.MCU_I2C_from_config(config, default_addr=98)
|
||||
color_order = config.get("color_order", "RGBW")
|
||||
if sorted(color_order) != sorted("RGBW"):
|
||||
raise config.error("Invalid color_order '%s'" % (color_order,))
|
||||
|
||||
@@ -15,6 +15,11 @@ class PrintStats:
|
||||
self.gcode.register_command(
|
||||
"SET_PRINT_STATS_INFO", self.cmd_SET_PRINT_STATS_INFO,
|
||||
desc=self.cmd_SET_PRINT_STATS_INFO_help)
|
||||
printer.register_event_handler("extruder:activate_extruder",
|
||||
self._handle_activate_extruder)
|
||||
def _handle_activate_extruder(self):
|
||||
gc_status = self.gcode_move.get_status()
|
||||
self.last_epos = gc_status['position'].e
|
||||
def _update_filament_usage(self, eventtime):
|
||||
gc_status = self.gcode_move.get_status(eventtime)
|
||||
cur_epos = gc_status['position'].e
|
||||
|
||||
@@ -172,17 +172,40 @@ class ProbeCommandHelper:
|
||||
configfile = self.printer.lookup_object('configfile')
|
||||
configfile.set(self.name, 'z_offset', "%.3f" % (new_calibrate,))
|
||||
|
||||
# Helper to lookup the minimum Z position for the printer
|
||||
def lookup_minimum_z(config):
|
||||
zconfig = manual_probe.lookup_z_endstop_config(config)
|
||||
if zconfig is not None:
|
||||
return zconfig.getfloat('position_min', 0., note_valid=False)
|
||||
pconfig = config.getsection('printer')
|
||||
return pconfig.getfloat('minimum_z_position', 0., note_valid=False)
|
||||
|
||||
# Helper to lookup all the Z axis steppers
|
||||
class LookupZSteppers:
|
||||
def __init__(self, config, add_stepper_cb):
|
||||
self.printer = config.get_printer()
|
||||
self.add_stepper_cb = add_stepper_cb
|
||||
self.printer.register_event_handler('klippy:mcu_identify',
|
||||
self._handle_mcu_identify)
|
||||
def _handle_mcu_identify(self):
|
||||
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
||||
for stepper in kin.get_steppers():
|
||||
if stepper.is_active_axis('z'):
|
||||
self.add_stepper_cb(stepper)
|
||||
|
||||
# Homing via probe:z_virtual_endstop
|
||||
class HomingViaProbeHelper:
|
||||
def __init__(self, config, mcu_probe):
|
||||
def __init__(self, config, mcu_probe, param_helper):
|
||||
self.printer = config.get_printer()
|
||||
self.mcu_probe = mcu_probe
|
||||
self.param_helper = param_helper
|
||||
self.multi_probe_pending = False
|
||||
self.z_min_position = lookup_minimum_z(config)
|
||||
self.results = []
|
||||
LookupZSteppers(config, self.mcu_probe.add_stepper)
|
||||
# Register z_virtual_endstop pin
|
||||
self.printer.lookup_object('pins').register_chip('probe', self)
|
||||
# Register event handlers
|
||||
self.printer.register_event_handler('klippy:mcu_identify',
|
||||
self._handle_mcu_identify)
|
||||
self.printer.register_event_handler("homing:homing_move_begin",
|
||||
self._handle_homing_move_begin)
|
||||
self.printer.register_event_handler("homing:homing_move_end",
|
||||
@@ -193,11 +216,6 @@ class HomingViaProbeHelper:
|
||||
self._handle_home_rails_end)
|
||||
self.printer.register_event_handler("gcode:command_error",
|
||||
self._handle_command_error)
|
||||
def _handle_mcu_identify(self):
|
||||
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
||||
for stepper in kin.get_steppers():
|
||||
if stepper.is_active_axis('z'):
|
||||
self.mcu_probe.add_stepper(stepper)
|
||||
def _handle_homing_move_begin(self, hmove):
|
||||
if self.mcu_probe in hmove.get_mcu_endstops():
|
||||
self.mcu_probe.probe_prepare(hmove)
|
||||
@@ -227,24 +245,42 @@ class HomingViaProbeHelper:
|
||||
if pin_params['invert'] or pin_params['pullup']:
|
||||
raise pins.error("Can not pullup/invert probe virtual endstop")
|
||||
return self.mcu_probe
|
||||
# Helper to convert probe based commands to use homing module
|
||||
def start_probe_session(self, gcmd):
|
||||
self.mcu_probe.multi_probe_begin()
|
||||
self.results = []
|
||||
return self
|
||||
def run_probe(self, gcmd):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
pos = toolhead.get_position()
|
||||
pos[2] = self.z_min_position
|
||||
speed = self.param_helper.get_probe_params(gcmd)['probe_speed']
|
||||
phoming = self.printer.lookup_object('homing')
|
||||
self.results.append(phoming.probing_move(self.mcu_probe, pos, speed))
|
||||
def pull_probed_results(self):
|
||||
res = self.results
|
||||
self.results = []
|
||||
return res
|
||||
def end_probe_session(self):
|
||||
self.results = []
|
||||
self.mcu_probe.multi_probe_end()
|
||||
|
||||
# Helper to track multiple probe attempts in a single command
|
||||
class ProbeSessionHelper:
|
||||
def __init__(self, config, mcu_probe):
|
||||
self.printer = config.get_printer()
|
||||
self.mcu_probe = mcu_probe
|
||||
gcode = self.printer.lookup_object('gcode')
|
||||
class ProbeVirtualEndstopDeprecation:
|
||||
def __init__(self, config):
|
||||
self._name = config.get_name()
|
||||
self._printer = config.get_printer()
|
||||
# Register z_virtual_endstop pin
|
||||
self._printer.lookup_object('pins').register_chip('probe', self)
|
||||
def setup_pin(self, pin_type, pin_params):
|
||||
raise self._printer.config_error(
|
||||
"Module [%s] does not support `probe:z_virtual_endstop`"
|
||||
", use a pin instead." % (self._name,))
|
||||
|
||||
# Helper to read multi-sample parameters from config
|
||||
class ProbeParameterHelper:
|
||||
def __init__(self, config):
|
||||
gcode = config.get_printer().lookup_object('gcode')
|
||||
self.dummy_gcode_cmd = gcode.create_gcode_command("", "", {})
|
||||
# Infer Z position to move to during a probe
|
||||
if config.has_section('stepper_z'):
|
||||
zconfig = config.getsection('stepper_z')
|
||||
self.z_position = zconfig.getfloat('position_min', 0.,
|
||||
note_valid=False)
|
||||
else:
|
||||
pconfig = config.getsection('printer')
|
||||
self.z_position = pconfig.getfloat('minimum_z_position', 0.,
|
||||
note_valid=False)
|
||||
self.homing_helper = HomingViaProbeHelper(config, mcu_probe)
|
||||
# Configurable probing speeds
|
||||
self.speed = config.getfloat('speed', 5.0, above=0.)
|
||||
self.lift_speed = config.getfloat('lift_speed', self.speed, above=0.)
|
||||
@@ -259,34 +295,6 @@ class ProbeSessionHelper:
|
||||
minval=0.)
|
||||
self.samples_retries = config.getint('samples_tolerance_retries', 0,
|
||||
minval=0)
|
||||
# Session state
|
||||
self.multi_probe_pending = False
|
||||
self.results = []
|
||||
# Register event handlers
|
||||
self.printer.register_event_handler("gcode:command_error",
|
||||
self._handle_command_error)
|
||||
def _handle_command_error(self):
|
||||
if self.multi_probe_pending:
|
||||
try:
|
||||
self.end_probe_session()
|
||||
except:
|
||||
logging.exception("Multi-probe end")
|
||||
def _probe_state_error(self):
|
||||
raise self.printer.command_error(
|
||||
"Internal probe error - start/end probe session mismatch")
|
||||
def start_probe_session(self, gcmd):
|
||||
if self.multi_probe_pending:
|
||||
self._probe_state_error()
|
||||
self.mcu_probe.multi_probe_begin()
|
||||
self.multi_probe_pending = True
|
||||
self.results = []
|
||||
return self
|
||||
def end_probe_session(self):
|
||||
if not self.multi_probe_pending:
|
||||
self._probe_state_error()
|
||||
self.results = []
|
||||
self.multi_probe_pending = False
|
||||
self.mcu_probe.multi_probe_end()
|
||||
def get_probe_params(self, gcmd=None):
|
||||
if gcmd is None:
|
||||
gcmd = self.dummy_gcode_cmd
|
||||
@@ -307,15 +315,49 @@ class ProbeSessionHelper:
|
||||
'samples_tolerance': samples_tolerance,
|
||||
'samples_tolerance_retries': samples_retries,
|
||||
'samples_result': samples_result}
|
||||
def _probe(self, speed):
|
||||
|
||||
# Helper to track multiple probe attempts in a single command
|
||||
class ProbeSessionHelper:
|
||||
def __init__(self, config, param_helper, start_session_cb):
|
||||
self.printer = config.get_printer()
|
||||
self.param_helper = param_helper
|
||||
self.start_session_cb = start_session_cb
|
||||
# Session state
|
||||
self.hw_probe_session = None
|
||||
self.results = []
|
||||
# Register event handlers
|
||||
self.printer.register_event_handler("gcode:command_error",
|
||||
self._handle_command_error)
|
||||
def _handle_command_error(self):
|
||||
if self.hw_probe_session is not None:
|
||||
try:
|
||||
self.end_probe_session()
|
||||
except:
|
||||
logging.exception("Multi-probe end")
|
||||
def _probe_state_error(self):
|
||||
raise self.printer.command_error(
|
||||
"Internal probe error - start/end probe session mismatch")
|
||||
def start_probe_session(self, gcmd):
|
||||
if self.hw_probe_session is not None:
|
||||
self._probe_state_error()
|
||||
self.hw_probe_session = self.start_session_cb(gcmd)
|
||||
self.results = []
|
||||
return self
|
||||
def end_probe_session(self):
|
||||
hw_probe_session = self.hw_probe_session
|
||||
if hw_probe_session is None:
|
||||
self._probe_state_error()
|
||||
self.results = []
|
||||
self.hw_probe_session = None
|
||||
hw_probe_session.end_probe_session()
|
||||
def _probe(self, gcmd):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
curtime = self.printer.get_reactor().monotonic()
|
||||
if 'z' not in toolhead.get_status(curtime)['homed_axes']:
|
||||
raise self.printer.command_error("Must home before probe")
|
||||
pos = toolhead.get_position()
|
||||
pos[2] = self.z_position
|
||||
try:
|
||||
epos = self.mcu_probe.probing_move(pos, speed)
|
||||
self.hw_probe_session.run_probe(gcmd)
|
||||
epos = self.hw_probe_session.pull_probed_results()[0]
|
||||
except self.printer.command_error as e:
|
||||
reason = str(e)
|
||||
if "Timeout during endstop homing" in reason:
|
||||
@@ -329,9 +371,9 @@ class ProbeSessionHelper:
|
||||
% (epos[0], epos[1], epos[2]))
|
||||
return epos[:3]
|
||||
def run_probe(self, gcmd):
|
||||
if not self.multi_probe_pending:
|
||||
if self.hw_probe_session is None:
|
||||
self._probe_state_error()
|
||||
params = self.get_probe_params(gcmd)
|
||||
params = self.param_helper.get_probe_params(gcmd)
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
probexy = toolhead.get_position()[:2]
|
||||
retries = 0
|
||||
@@ -339,7 +381,7 @@ class ProbeSessionHelper:
|
||||
sample_count = params['samples']
|
||||
while len(positions) < sample_count:
|
||||
# Probe position
|
||||
pos = self._probe(params['probe_speed'])
|
||||
pos = self._probe(gcmd)
|
||||
positions.append(pos)
|
||||
# Check samples tolerance
|
||||
z_positions = [p[2] for p in positions]
|
||||
@@ -544,9 +586,6 @@ class ProbeEndstopWrapper:
|
||||
return
|
||||
self._raise_probe()
|
||||
self.multi = 'OFF'
|
||||
def probing_move(self, pos, speed):
|
||||
phoming = self.printer.lookup_object('homing')
|
||||
return phoming.probing_move(self, pos, speed)
|
||||
def probe_prepare(self, hmove):
|
||||
if self.multi == 'OFF' or self.multi == 'FIRST':
|
||||
self._lower_probe()
|
||||
@@ -566,9 +605,13 @@ class PrinterProbe:
|
||||
self.cmd_helper = ProbeCommandHelper(config, self,
|
||||
self.mcu_probe.query_endstop)
|
||||
self.probe_offsets = ProbeOffsetsHelper(config)
|
||||
self.probe_session = ProbeSessionHelper(config, self.mcu_probe)
|
||||
self.param_helper = ProbeParameterHelper(config)
|
||||
self.homing_helper = HomingViaProbeHelper(config, self.mcu_probe,
|
||||
self.param_helper)
|
||||
self.probe_session = ProbeSessionHelper(
|
||||
config, self.param_helper, self.homing_helper.start_probe_session)
|
||||
def get_probe_params(self, gcmd=None):
|
||||
return self.probe_session.get_probe_params(gcmd)
|
||||
return self.param_helper.get_probe_params(gcmd)
|
||||
def get_offsets(self):
|
||||
return self.probe_offsets.get_offsets()
|
||||
def get_status(self, eventtime):
|
||||
|
||||
@@ -302,22 +302,21 @@ class EddyGatherSamples:
|
||||
self._check_samples()
|
||||
|
||||
# Helper for implementing PROBE style commands (descend until trigger)
|
||||
class EddyEndstopWrapper:
|
||||
class EddyDescend:
|
||||
REASON_SENSOR_ERROR = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 1
|
||||
def __init__(self, config, sensor_helper, calibration):
|
||||
def __init__(self, config, sensor_helper, calibration, param_helper):
|
||||
self._printer = config.get_printer()
|
||||
self._sensor_helper = sensor_helper
|
||||
self._mcu = sensor_helper.get_mcu()
|
||||
self._calibration = calibration
|
||||
self._param_helper = param_helper
|
||||
self._z_min_position = probe.lookup_minimum_z(config)
|
||||
self._z_offset = config.getfloat('z_offset', minval=0.)
|
||||
self._dispatch = mcu.TriggerDispatch(self._mcu)
|
||||
self._trigger_time = 0.
|
||||
self._gather = None
|
||||
# Interface for MCU_endstop
|
||||
def get_mcu(self):
|
||||
return self._mcu
|
||||
def add_stepper(self, stepper):
|
||||
self._dispatch.add_stepper(stepper)
|
||||
probe.LookupZSteppers(config, self._dispatch.add_stepper)
|
||||
# Interface for phoming.probing_move()
|
||||
def get_steppers(self):
|
||||
return self._dispatch.get_steppers()
|
||||
def home_start(self, print_time, sample_time, sample_count, rest_time,
|
||||
@@ -344,10 +343,16 @@ class EddyEndstopWrapper:
|
||||
return home_end_time
|
||||
self._trigger_time = trigger_time
|
||||
return trigger_time
|
||||
def query_endstop(self, print_time):
|
||||
return False # XXX
|
||||
# Interface for ProbeEndstopWrapper
|
||||
def probing_move(self, pos, speed):
|
||||
# Probe session interface
|
||||
def start_probe_session(self, gcmd):
|
||||
self._gather = EddyGatherSamples(self._printer, self._sensor_helper,
|
||||
self._calibration, self._z_offset)
|
||||
return self
|
||||
def run_probe(self, gcmd):
|
||||
toolhead = self._printer.lookup_object('toolhead')
|
||||
pos = toolhead.get_position()
|
||||
pos[2] = self._z_min_position
|
||||
speed = self._param_helper.get_probe_params(gcmd)['probe_speed']
|
||||
# Perform probing move
|
||||
phoming = self._printer.lookup_object('homing')
|
||||
trig_pos = phoming.probing_move(self, pos, speed)
|
||||
@@ -356,22 +361,48 @@ class EddyEndstopWrapper:
|
||||
# Extract samples
|
||||
start_time = self._trigger_time + 0.050
|
||||
end_time = start_time + 0.100
|
||||
toolhead = self._printer.lookup_object("toolhead")
|
||||
toolhead_pos = toolhead.get_position()
|
||||
self._gather.note_probe(start_time, end_time, toolhead_pos)
|
||||
return self._gather.pull_probed()[0]
|
||||
def multi_probe_begin(self):
|
||||
self._gather = EddyGatherSamples(self._printer, self._sensor_helper,
|
||||
self._calibration, self._z_offset)
|
||||
def multi_probe_end(self):
|
||||
def pull_probed_results(self):
|
||||
return self._gather.pull_probed()
|
||||
def end_probe_session(self):
|
||||
self._gather.finish()
|
||||
self._gather = None
|
||||
|
||||
# Wrapper to emulate mcu_endstop for probe:z_virtual_endstop
|
||||
# Note that this does not provide accurate results
|
||||
class EddyEndstopWrapper:
|
||||
def __init__(self, sensor_helper, eddy_descend):
|
||||
self._sensor_helper = sensor_helper
|
||||
self._eddy_descend = eddy_descend
|
||||
self._hw_probe_session = None
|
||||
# Interface for MCU_endstop
|
||||
def get_mcu(self):
|
||||
return self._sensor_helper.get_mcu()
|
||||
def add_stepper(self, stepper):
|
||||
pass
|
||||
def get_steppers(self):
|
||||
return self._eddy_descend.get_steppers()
|
||||
def home_start(self, print_time, sample_time, sample_count, rest_time,
|
||||
triggered=True):
|
||||
return self._eddy_descend.home_start(
|
||||
print_time, sample_time, sample_count, rest_time, triggered)
|
||||
def home_wait(self, home_end_time):
|
||||
return self._eddy_descend.home_wait(home_end_time)
|
||||
def query_endstop(self, print_time):
|
||||
return False # XXX
|
||||
# Interface for HomingViaProbeHelper
|
||||
def multi_probe_begin(self):
|
||||
self._hw_probe_session = self._eddy_descend.start_probe_session(None)
|
||||
def multi_probe_end(self):
|
||||
self._hw_probe_session.end_probe_session()
|
||||
self._hw_probe_session = None
|
||||
def probe_prepare(self, hmove):
|
||||
pass
|
||||
def probe_finish(self, hmove):
|
||||
pass
|
||||
def get_position_endstop(self):
|
||||
return self._z_offset
|
||||
return self._eddy_descend._z_offset
|
||||
|
||||
# Implementing probing with "METHOD=scan"
|
||||
class EddyScanningProbe:
|
||||
@@ -423,17 +454,20 @@ class PrinterEddyProbe:
|
||||
sensor_type = config.getchoice('sensor_type', {s: s for s in sensors})
|
||||
self.sensor_helper = sensors[sensor_type](config, self.calibration)
|
||||
# Probe interface
|
||||
self.mcu_probe = EddyEndstopWrapper(config, self.sensor_helper,
|
||||
self.calibration)
|
||||
self.cmd_helper = probe.ProbeCommandHelper(
|
||||
config, self, self.mcu_probe.query_endstop)
|
||||
self.param_helper = probe.ProbeParameterHelper(config)
|
||||
self.eddy_descend = EddyDescend(
|
||||
config, self.sensor_helper, self.calibration, self.param_helper)
|
||||
self.cmd_helper = probe.ProbeCommandHelper(config, self)
|
||||
self.probe_offsets = probe.ProbeOffsetsHelper(config)
|
||||
self.probe_session = probe.ProbeSessionHelper(config, self.mcu_probe)
|
||||
self.probe_session = probe.ProbeSessionHelper(
|
||||
config, self.param_helper, self.eddy_descend.start_probe_session)
|
||||
mcu_probe = EddyEndstopWrapper(self.sensor_helper, self.eddy_descend)
|
||||
probe.HomingViaProbeHelper(config, mcu_probe, self.param_helper)
|
||||
self.printer.add_object('probe', self)
|
||||
def add_client(self, cb):
|
||||
self.sensor_helper.add_client(cb)
|
||||
def get_probe_params(self, gcmd=None):
|
||||
return self.probe_session.get_probe_params(gcmd)
|
||||
return self.param_helper.get_probe_params(gcmd)
|
||||
def get_offsets(self):
|
||||
return self.probe_offsets.get_offsets()
|
||||
def get_status(self, eventtime):
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
# Handle pwm output pins with variable frequency
|
||||
#
|
||||
# Copyright (C) 2017-2023 Kevin O'Connor <kevin@koconnor.net>
|
||||
# Copyright (C) 2017-2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
PIN_MIN_TIME = 0.100
|
||||
MAX_SCHEDULE_TIME = 5.0
|
||||
|
||||
class MCU_pwm_cycle:
|
||||
def __init__(self, pin_params, cycle_time, start_value, shutdown_value):
|
||||
self._mcu = pin_params['chip']
|
||||
@@ -22,6 +19,8 @@ class MCU_pwm_cycle:
|
||||
self._shutdown_value = max(0., min(1., shutdown_value))
|
||||
self._last_clock = self._cycle_ticks = 0
|
||||
self._set_cmd = self._set_cycle_ticks = None
|
||||
def get_mcu(self):
|
||||
return self._mcu
|
||||
def _build_config(self):
|
||||
cmd_queue = self._mcu.alloc_command_queue()
|
||||
curtime = self._mcu.get_printer().get_reactor().monotonic()
|
||||
@@ -77,9 +76,6 @@ class PrinterOutputPWMCycle:
|
||||
def __init__(self, config):
|
||||
self.printer = config.get_printer()
|
||||
self.last_print_time = 0.
|
||||
cycle_time = config.getfloat('cycle_time', 0.100, above=0.,
|
||||
maxval=MAX_SCHEDULE_TIME)
|
||||
self.last_cycle_time = self.default_cycle_time = cycle_time
|
||||
# Determine start and shutdown values
|
||||
self.scale = config.getfloat('scale', 1., above=0.)
|
||||
self.last_value = config.getfloat(
|
||||
@@ -89,8 +85,12 @@ class PrinterOutputPWMCycle:
|
||||
# Create pwm pin object
|
||||
ppins = self.printer.lookup_object('pins')
|
||||
pin_params = ppins.lookup_pin(config.get('pin'), can_invert=True)
|
||||
max_duration = pin_params['chip'].max_nominal_duration()
|
||||
cycle_time = config.getfloat('cycle_time', 0.100, above=0.,
|
||||
maxval=max_duration)
|
||||
self.mcu_pin = MCU_pwm_cycle(pin_params, cycle_time,
|
||||
self.last_value, self.shutdown_value)
|
||||
self.last_cycle_time = self.default_cycle_time = cycle_time
|
||||
# Register commands
|
||||
pin_name = config.get_name().split()[1]
|
||||
gcode = self.printer.lookup_object('gcode')
|
||||
@@ -102,7 +102,8 @@ class PrinterOutputPWMCycle:
|
||||
def _set_pin(self, print_time, value, cycle_time):
|
||||
if value == self.last_value and cycle_time == self.last_cycle_time:
|
||||
return
|
||||
print_time = max(print_time, self.last_print_time + PIN_MIN_TIME)
|
||||
min_sched_time = self.mcu_pin.get_mcu().min_schedule_time()
|
||||
print_time = max(print_time, self.last_print_time + min_sched_time)
|
||||
self.mcu_pin.set_pwm_cycle(print_time, value, cycle_time)
|
||||
self.last_value = value
|
||||
self.last_cycle_time = cycle_time
|
||||
@@ -112,8 +113,9 @@ class PrinterOutputPWMCycle:
|
||||
# Read requested value
|
||||
value = gcmd.get_float('VALUE', minval=0., maxval=self.scale)
|
||||
value /= self.scale
|
||||
max_duration = self.mcu_pin.get_mcu().max_nominal_duration()
|
||||
cycle_time = gcmd.get_float('CYCLE_TIME', self.default_cycle_time,
|
||||
above=0., maxval=MAX_SCHEDULE_TIME)
|
||||
above=0., maxval=max_duration)
|
||||
# Obtain print_time and apply requested settings
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.register_lookahead_callback(
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
# Queued PWM gpio output
|
||||
#
|
||||
# Copyright (C) 2017-2023 Kevin O'Connor <kevin@koconnor.net>
|
||||
# Copyright (C) 2017-2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import chelper
|
||||
|
||||
MAX_SCHEDULE_TIME = 5.0
|
||||
|
||||
class error(Exception):
|
||||
pass
|
||||
|
||||
class MCU_queued_pwm:
|
||||
def __init__(self, pin_params):
|
||||
self._mcu = pin_params['chip']
|
||||
def __init__(self, config, pin_params):
|
||||
self._mcu = mcu = pin_params['chip']
|
||||
self._hardware_pwm = False
|
||||
self._cycle_time = 0.100
|
||||
self._max_duration = 2.
|
||||
self._oid = self._mcu.create_oid()
|
||||
self._oid = oid = mcu.create_oid()
|
||||
printer = mcu.get_printer()
|
||||
motion_queuing = printer.load_object(config, 'motion_queuing')
|
||||
self._stepqueue = motion_queuing.allocate_stepcompress(mcu, oid)
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
self._stepqueue = ffi_main.gc(ffi_lib.stepcompress_alloc(self._oid),
|
||||
ffi_lib.stepcompress_free)
|
||||
self._mcu.register_stepqueue(self._stepqueue)
|
||||
self._stepcompress_queue_mq_msg = ffi_lib.stepcompress_queue_mq_msg
|
||||
self._mcu.register_config_callback(self._build_config)
|
||||
mcu.register_config_callback(self._build_config)
|
||||
self._pin = pin_params['pin']
|
||||
self._invert = pin_params['invert']
|
||||
self._start_value = self._shutdown_value = float(self._invert)
|
||||
@@ -31,7 +29,6 @@ class MCU_queued_pwm:
|
||||
self._pwm_max = 0.
|
||||
self._set_cmd_tag = None
|
||||
self._toolhead = None
|
||||
printer = self._mcu.get_printer()
|
||||
printer.register_event_handler("klippy:connect", self._handle_connect)
|
||||
def _handle_connect(self):
|
||||
self._toolhead = self._mcu.get_printer().lookup_object("toolhead")
|
||||
@@ -49,12 +46,13 @@ class MCU_queued_pwm:
|
||||
self._start_value = max(0., min(1., start_value))
|
||||
self._shutdown_value = max(0., min(1., shutdown_value))
|
||||
def _build_config(self):
|
||||
config_error = self._mcu.get_printer().config_error
|
||||
printer = self._mcu.get_printer()
|
||||
config_error = printer.config_error
|
||||
if self._max_duration and self._start_value != self._shutdown_value:
|
||||
raise config_error("Pin with max duration must have start"
|
||||
" value equal to shutdown value")
|
||||
cmd_queue = self._mcu.alloc_command_queue()
|
||||
curtime = self._mcu.get_printer().get_reactor().monotonic()
|
||||
curtime = printer.get_reactor().monotonic()
|
||||
printtime = self._mcu.estimated_print_time(curtime)
|
||||
self._last_clock = self._mcu.print_time_to_clock(printtime + 0.200)
|
||||
cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time)
|
||||
@@ -64,7 +62,8 @@ class MCU_queued_pwm:
|
||||
if self._duration_ticks >= 1<<31:
|
||||
raise config_error("PWM pin max duration too large")
|
||||
if self._duration_ticks:
|
||||
self._mcu.register_flush_callback(self._flush_notification)
|
||||
motion_queuing = printer.lookup_object('motion_queuing')
|
||||
motion_queuing.register_flush_callback(self._flush_notification)
|
||||
if self._hardware_pwm:
|
||||
self._pwm_max = self._mcu.get_constant_float("PWM_MAX")
|
||||
self._default_value = self._shutdown_value * self._pwm_max
|
||||
@@ -117,14 +116,16 @@ class MCU_queued_pwm:
|
||||
# Continue flushing to resend time
|
||||
wakeclock += self._duration_ticks
|
||||
wake_print_time = self._mcu.clock_to_print_time(wakeclock)
|
||||
self._toolhead.note_mcu_movequeue_activity(wake_print_time)
|
||||
self._toolhead.note_mcu_movequeue_activity(wake_print_time,
|
||||
is_step_gen=False)
|
||||
def set_pwm(self, print_time, value):
|
||||
clock = self._mcu.print_time_to_clock(print_time)
|
||||
if self._invert:
|
||||
value = 1. - value
|
||||
v = int(max(0., min(1., value)) * self._pwm_max + 0.5)
|
||||
self._send_update(clock, v)
|
||||
def _flush_notification(self, print_time, clock):
|
||||
def _flush_notification(self, must_flush_time, max_step_gen_time):
|
||||
clock = self._mcu.print_time_to_clock(must_flush_time)
|
||||
if self._last_value != self._default_value:
|
||||
while clock >= self._last_clock + self._duration_ticks:
|
||||
self._send_update(self._last_clock + self._duration_ticks,
|
||||
@@ -136,17 +137,17 @@ class PrinterOutputPin:
|
||||
ppins = self.printer.lookup_object('pins')
|
||||
# Determine pin type
|
||||
pin_params = ppins.lookup_pin(config.get('pin'), can_invert=True)
|
||||
self.mcu_pin = MCU_queued_pwm(pin_params)
|
||||
self.mcu_pin = MCU_queued_pwm(config, pin_params)
|
||||
max_duration = self.mcu_pin.get_mcu().max_nominal_duration()
|
||||
cycle_time = config.getfloat('cycle_time', 0.100, above=0.,
|
||||
maxval=MAX_SCHEDULE_TIME)
|
||||
maxval=max_duration)
|
||||
hardware_pwm = config.getboolean('hardware_pwm', False)
|
||||
self.mcu_pin.setup_cycle_time(cycle_time, hardware_pwm)
|
||||
self.scale = config.getfloat('scale', 1., above=0.)
|
||||
self.last_print_time = 0.
|
||||
# Support mcu checking for maximum duration
|
||||
max_mcu_duration = config.getfloat('maximum_mcu_duration', 0.,
|
||||
minval=0.500,
|
||||
maxval=MAX_SCHEDULE_TIME)
|
||||
minval=0.500, maxval=max_duration)
|
||||
self.mcu_pin.setup_max_duration(max_mcu_duration)
|
||||
# Determine start and shutdown values
|
||||
self.last_value = config.getfloat(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Mechanicaly conforms a moving gantry to the bed with 4 Z steppers
|
||||
# Mechanically conforms a moving gantry to the bed with 4 Z steppers
|
||||
#
|
||||
# Copyright (C) 2018 Maks Zolin <mzolin@vorondesign.com>
|
||||
#
|
||||
|
||||
@@ -127,7 +127,8 @@ class ResonanceTestExecutor:
|
||||
def run_test(self, test_seq, axis, gcmd):
|
||||
reactor = self.printer.get_reactor()
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
X, Y, Z, E = toolhead.get_position()
|
||||
tpos = toolhead.get_position()
|
||||
X, Y = tpos[:2]
|
||||
# Override maximum acceleration and acceleration to
|
||||
# deceleration based on the maximum test frequency
|
||||
systime = reactor.monotonic()
|
||||
@@ -147,8 +148,7 @@ class ResonanceTestExecutor:
|
||||
last_v = last_t = last_accel = last_freq = 0.
|
||||
for next_t, accel, freq in test_seq:
|
||||
t_seg = next_t - last_t
|
||||
toolhead.cmd_M204(self.gcode.create_gcode_command(
|
||||
"M204", "M204", {"S": abs(accel)}))
|
||||
toolhead.set_max_velocities(None, abs(accel), None, None)
|
||||
v = last_v + accel * t_seg
|
||||
abs_v = abs(v)
|
||||
if abs_v < 0.000001:
|
||||
@@ -166,10 +166,10 @@ class ResonanceTestExecutor:
|
||||
# The move first goes to a complete stop, then changes direction
|
||||
d_decel = -last_v2 * half_inv_accel
|
||||
decel_X, decel_Y = axis.get_point(d_decel)
|
||||
toolhead.move([X + decel_X, Y + decel_Y, Z, E], abs_last_v)
|
||||
toolhead.move([nX, nY, Z, E], abs_v)
|
||||
toolhead.move([X + decel_X, Y + decel_Y] + tpos[2:], abs_last_v)
|
||||
toolhead.move([nX, nY] + tpos[2:], abs_v)
|
||||
else:
|
||||
toolhead.move([nX, nY, Z, E], max(abs_v, abs_last_v))
|
||||
toolhead.move([nX, nY] + tpos[2:], max(abs_v, abs_last_v))
|
||||
if math.floor(freq) > math.floor(last_freq):
|
||||
gcmd.respond_info("Testing frequency %.0f Hz" % (freq,))
|
||||
reactor.pause(reactor.monotonic() + 0.01)
|
||||
@@ -181,9 +181,8 @@ class ResonanceTestExecutor:
|
||||
if last_v:
|
||||
d_decel = -.5 * last_v2 / old_max_accel
|
||||
decel_X, decel_Y = axis.get_point(d_decel)
|
||||
toolhead.cmd_M204(self.gcode.create_gcode_command(
|
||||
"M204", "M204", {"S": old_max_accel}))
|
||||
toolhead.move([X + decel_X, Y + decel_Y, Z, E], abs(last_v))
|
||||
toolhead.set_max_velocities(None, old_max_accel, None, None)
|
||||
toolhead.move([X + decel_X, Y + decel_Y] + tpos[2:], abs(last_v))
|
||||
# Restore the original acceleration values
|
||||
self.gcode.run_script_from_command(
|
||||
"SET_VELOCITY_LIMIT ACCEL=%.3f MINIMUM_CRUISE_RATIO=%.3f"
|
||||
@@ -294,7 +293,7 @@ class ResonanceTester:
|
||||
return parsed_chips
|
||||
def _get_max_calibration_freq(self):
|
||||
return 1.5 * self.generator.get_max_freq()
|
||||
cmd_TEST_RESONANCES_help = ("Runs the resonance test for a specifed axis")
|
||||
cmd_TEST_RESONANCES_help = ("Runs the resonance test for a specified axis")
|
||||
def cmd_TEST_RESONANCES(self, gcmd):
|
||||
# Parse parameters
|
||||
axis = _parse_axis(gcmd, gcmd.get("AXIS").lower())
|
||||
@@ -344,7 +343,7 @@ class ResonanceTester:
|
||||
gcmd.respond_info(
|
||||
"Resonances data written to %s file" % (csv_name,))
|
||||
cmd_SHAPER_CALIBRATE_help = (
|
||||
"Simular to TEST_RESONANCES but suggest input shaper config")
|
||||
"Similar to TEST_RESONANCES but suggest input shaper config")
|
||||
def cmd_SHAPER_CALIBRATE(self, gcmd):
|
||||
# Parse parameters
|
||||
axis = gcmd.get("AXIS", None)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# Copyright (C) 2019 Florian Heilmann <Florian.Heilmann@gmx.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
from . import manual_probe
|
||||
|
||||
class SafeZHoming:
|
||||
def __init__(self, config):
|
||||
@@ -11,7 +12,9 @@ class SafeZHoming:
|
||||
self.home_x_pos, self.home_y_pos = x_pos, y_pos
|
||||
self.z_hop = config.getfloat("z_hop", default=0.0)
|
||||
self.z_hop_speed = config.getfloat('z_hop_speed', 15., above=0.)
|
||||
zconfig = config.getsection('stepper_z')
|
||||
zconfig = manual_probe.lookup_z_endstop_config(config)
|
||||
if zconfig is None:
|
||||
raise config.error('Missing Z endstop config for safe_z_homing')
|
||||
self.max_z = zconfig.getfloat('position_max', note_valid=False)
|
||||
self.speed = config.getfloat('speed', 50.0, above=0.)
|
||||
self.move_to_previous = config.getboolean('move_to_previous', False)
|
||||
|
||||
@@ -9,7 +9,6 @@ from . import probe
|
||||
|
||||
class ScrewsTiltAdjust:
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.printer = config.get_printer()
|
||||
self.screws = []
|
||||
self.results = {}
|
||||
@@ -33,7 +32,7 @@ class ScrewsTiltAdjust:
|
||||
default='CW-M3')
|
||||
# Initialize ProbePointsHelper
|
||||
points = [coord for coord, name in self.screws]
|
||||
self.probe_helper = probe.ProbePointsHelper(self.config,
|
||||
self.probe_helper = probe.ProbePointsHelper(config,
|
||||
self.probe_finalize,
|
||||
default_points=points)
|
||||
self.probe_helper.minimum_points(3)
|
||||
|
||||
@@ -56,6 +56,7 @@ class SHT3X:
|
||||
self.reactor = self.printer.get_reactor()
|
||||
self.i2c = bus.MCU_I2C_from_config(
|
||||
config, default_addr=SHT3X_I2C_ADDR, default_speed=100000)
|
||||
self._error = self.i2c.get_mcu().error
|
||||
self.report_time = config.getint('sht3x_report_time', 1, minval=1)
|
||||
self.deviceId = config.get('sensor_type')
|
||||
self.temp = self.min_temp = self.max_temp = self.humidity = 0.
|
||||
@@ -79,10 +80,10 @@ class SHT3X:
|
||||
|
||||
def _init_sht3x(self):
|
||||
# Device Soft Reset
|
||||
self.i2c.i2c_write_wait_ack(SHT3X_CMD['OTHER']['BREAK'])
|
||||
self.i2c.i2c_write(SHT3X_CMD['OTHER']['BREAK'])
|
||||
# Break takes ~ 1ms
|
||||
self.reactor.pause(self.reactor.monotonic() + .0015)
|
||||
self.i2c.i2c_write_wait_ack(SHT3X_CMD['OTHER']['SOFTRESET'])
|
||||
self.i2c.i2c_write(SHT3X_CMD['OTHER']['SOFTRESET'])
|
||||
# Wait <=1.5ms after reset
|
||||
self.reactor.pause(self.reactor.monotonic() + .0015)
|
||||
|
||||
@@ -96,16 +97,29 @@ class SHT3X:
|
||||
logging.warning("sht3x: Reading status - checksum error!")
|
||||
|
||||
# Enable periodic mode
|
||||
self.i2c.i2c_write_wait_ack(
|
||||
self.i2c.i2c_write(
|
||||
SHT3X_CMD['PERIODIC']['2HZ']['HIGH_REP']
|
||||
)
|
||||
# Wait <=15.5ms for first measurment
|
||||
# Wait <=15.5ms for first measurement
|
||||
self.reactor.pause(self.reactor.monotonic() + .0155)
|
||||
|
||||
def _sample_sht3x(self, eventtime):
|
||||
try:
|
||||
# Read measurment
|
||||
params = self.i2c.i2c_read(SHT3X_CMD['OTHER']['FETCH'], 6)
|
||||
# Read measurement
|
||||
retries = 5
|
||||
params = None
|
||||
error = None
|
||||
while retries > 0 and params is None:
|
||||
try:
|
||||
params = self.i2c.i2c_read(
|
||||
SHT3X_CMD['OTHER']['FETCH'], 6, retry=False
|
||||
)
|
||||
except self._error as e:
|
||||
error = e
|
||||
self.reactor.pause(self.reactor.monotonic() + .5)
|
||||
retries -= 1
|
||||
if params is None:
|
||||
raise error
|
||||
|
||||
response = bytearray(params['response'])
|
||||
rtemp = response[0] << 8
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user