mirror of
https://github.com/Klipper3d/klipper.git
synced 2026-03-05 01:24:41 -07:00
Merge branch 'master' into feature/adxl355-upstream
This commit is contained in:
commit
f9e8191e9f
292 changed files with 10589 additions and 5405 deletions
2
.github/workflows/klipper3d-deploy.yaml
vendored
2
.github/workflows/klipper3d-deploy.yaml
vendored
|
|
@ -26,7 +26,7 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: pip install -r docs/_klipper3d/mkdocs-requirements.txt
|
||||
- name: Build MkDocs Pages
|
||||
run: docs/_klipper3d/build-translations.sh
|
||||
run: docs/_klipper3d/build-website.sh
|
||||
- name: Deploy
|
||||
uses: JamesIves/github-pages-deploy-action@v4.4.3
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -7,36 +7,39 @@
|
|||
|
||||
# See docs/Config_Reference.md for a description of parameters.
|
||||
|
||||
[carriage x]
|
||||
[carriage carriage_x]
|
||||
axis: x
|
||||
position_endstop: 0
|
||||
position_max: 300
|
||||
homing_speed: 50
|
||||
endstop_pin: ^PE5
|
||||
|
||||
[carriage y]
|
||||
[carriage carriage_y]
|
||||
axis: y
|
||||
position_endstop: 0
|
||||
position_max: 200
|
||||
homing_speed: 50
|
||||
endstop_pin: ^PJ1
|
||||
|
||||
[extra_carriage y1]
|
||||
primary_carriage: y
|
||||
[extra_carriage carriage_y1]
|
||||
primary_carriage: carriage_y
|
||||
endstop_pin: ^PB6
|
||||
|
||||
[carriage z]
|
||||
[carriage carriage_z]
|
||||
axis: z
|
||||
position_endstop: 0.5
|
||||
position_max: 100
|
||||
endstop_pin: ^PD3
|
||||
|
||||
[dual_carriage u]
|
||||
primary_carriage: x
|
||||
[dual_carriage carriage_u]
|
||||
primary_carriage: carriage_x
|
||||
position_endstop: 300
|
||||
position_max: 300
|
||||
homing_speed: 50
|
||||
endstop_pin: ^PE4
|
||||
|
||||
[stepper my_stepper_x]
|
||||
carriages: x+y
|
||||
carriages: carriage_x+carriage_y
|
||||
step_pin: PF0
|
||||
dir_pin: PF1
|
||||
enable_pin: !PD7
|
||||
|
|
@ -44,7 +47,7 @@ microsteps: 16
|
|||
rotation_distance: 40
|
||||
|
||||
[stepper my_stepper_u]
|
||||
carriages: u-y1
|
||||
carriages: carriage_u-carriage_y1
|
||||
step_pin: PH1
|
||||
dir_pin: PH0
|
||||
enable_pin: !PA1
|
||||
|
|
@ -52,7 +55,7 @@ microsteps: 16
|
|||
rotation_distance: 40
|
||||
|
||||
[stepper my_stepper_y0]
|
||||
carriages: y
|
||||
carriages: carriage_y
|
||||
step_pin: PF6
|
||||
dir_pin: !PF7
|
||||
enable_pin: !PF2
|
||||
|
|
@ -60,7 +63,7 @@ microsteps: 16
|
|||
rotation_distance: 40
|
||||
|
||||
[stepper my_stepper_y1]
|
||||
carriages: y1
|
||||
carriages: carriage_y1
|
||||
step_pin: PE3
|
||||
dir_pin: !PH6
|
||||
enable_pin: !PG5
|
||||
|
|
@ -68,7 +71,7 @@ microsteps: 16
|
|||
rotation_distance: 40
|
||||
|
||||
[stepper my_stepper_z0]
|
||||
carriages: z
|
||||
carriages: carriage_z
|
||||
step_pin: PL3
|
||||
dir_pin: PL1
|
||||
enable_pin: !PK0
|
||||
|
|
@ -76,7 +79,7 @@ microsteps: 16
|
|||
rotation_distance: 8
|
||||
|
||||
[stepper my_stepper_z1]
|
||||
carriages: z
|
||||
carriages: carriage_z
|
||||
step_pin: PG1
|
||||
dir_pin: PG0
|
||||
enable_pin: !PH3
|
||||
|
|
|
|||
|
|
@ -133,44 +133,34 @@ max_z_accel: 100
|
|||
|
||||
#[tmc2130 stepper_x]
|
||||
#cs_pin: PB8
|
||||
#spi_software_miso_pin: PC11
|
||||
#spi_software_mosi_pin: PC12
|
||||
#spi_software_sclk_pin: PC10
|
||||
#spi_bus: spi3_PC11_PC12_PC10
|
||||
##diag1_pin: PF3
|
||||
#run_current: 0.800
|
||||
#stealthchop_threshold: 999999
|
||||
|
||||
#[tmc2130 stepper_y]
|
||||
#cs_pin: PC9
|
||||
#spi_software_miso_pin: PC11
|
||||
#spi_software_mosi_pin: PC12
|
||||
#spi_software_sclk_pin: PC10
|
||||
#spi_bus: spi3_PC11_PC12_PC10
|
||||
##diag1_pin: PF4
|
||||
#run_current: 0.800
|
||||
#stealthchop_threshold: 999999
|
||||
|
||||
#[tmc2130 stepper_z]
|
||||
#cs_pin: PD0
|
||||
#spi_software_miso_pin: PC11
|
||||
#spi_software_mosi_pin: PC12
|
||||
#spi_software_sclk_pin: PC10
|
||||
#spi_bus: spi3_PC11_PC12_PC10
|
||||
##diag1_pin: PF5
|
||||
#run_current: 0.650
|
||||
#stealthchop_threshold: 999999
|
||||
|
||||
#[tmc2130 extruder]
|
||||
#cs_pin: PD1
|
||||
#spi_software_miso_pin: PC11
|
||||
#spi_software_mosi_pin: PC12
|
||||
#spi_software_sclk_pin: PC10
|
||||
#spi_bus: spi3_PC11_PC12_PC10
|
||||
#run_current: 0.800
|
||||
#stealthchop_threshold: 999999
|
||||
|
||||
#[tmc2130 extruder1]
|
||||
#cs_pin: PB5
|
||||
#spi_software_miso_pin: PC11
|
||||
#spi_software_mosi_pin: PC12
|
||||
#spi_software_sclk_pin: PC10
|
||||
#spi_bus: spi3_PC11_PC12_PC10
|
||||
#run_current: 0.800
|
||||
#stealthchop_threshold: 999999
|
||||
|
||||
|
|
@ -195,6 +185,4 @@ aliases:
|
|||
|
||||
#[adxl345]
|
||||
#cs_pin: PC15
|
||||
#spi_software_miso_pin: PC11
|
||||
#spi_software_mosi_pin: PC12
|
||||
#spi_software_sclk_pin: PC10
|
||||
#spi_bus: spi3_PC11_PC12_PC10
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# This file contains pin mappings for the stock 2020 Creality Ender 5
|
||||
# Pro with the 32-bit Creality 4.2.2 board. To use this config, during
|
||||
# "make menuconfig" select the STM32F103 with a "28KiB bootloader" and
|
||||
# with "Use USB for communication" disabled.
|
||||
# communication interface set to "Serial (on USART1 PA10/PA9)".
|
||||
|
||||
# If you prefer a direct serial connection, in "make menuconfig"
|
||||
# select "Enable extra low-level configuration options" and select the
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@ serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00
|
|||
|
||||
[adxl345]
|
||||
cs_pin: EBBCan:PB12
|
||||
spi_software_sclk_pin: EBBCan:PB10
|
||||
spi_software_mosi_pin: EBBCan:PB11
|
||||
spi_software_miso_pin: EBBCan:PB2
|
||||
spi_bus: spi2_PB2_PB11_PB10
|
||||
axes_map: x,y,z
|
||||
|
||||
[extruder]
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@ serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00
|
|||
|
||||
[adxl345]
|
||||
cs_pin: EBBCan:PB12
|
||||
spi_software_sclk_pin: EBBCan:PB10
|
||||
spi_software_mosi_pin: EBBCan:PB11
|
||||
spi_software_miso_pin: EBBCan:PB2
|
||||
spi_bus: spi2_PB2_PB11_PB10
|
||||
axes_map: x,y,z
|
||||
|
||||
[extruder]
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ sensor_pin: EBBCan:PA2
|
|||
|
||||
[adxl345]
|
||||
cs_pin: EBBCan:PB12
|
||||
spi_software_sclk_pin: EBBCan:PB10
|
||||
spi_software_mosi_pin: EBBCan:PB11
|
||||
spi_software_miso_pin: EBBCan:PB2
|
||||
spi_bus: spi2_PB2_PB11_PB10
|
||||
axes_map: x,y,z
|
||||
|
||||
[extruder]
|
||||
|
|
|
|||
52
config/sample-cartographer-v3.cfg
Normal file
52
config/sample-cartographer-v3.cfg
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# This file contains common pin mappings for the Cartographer board V3
|
||||
# To use this config, the firmware should be compiled for the
|
||||
# STM32F042 with "24 MHz crystal" and
|
||||
# "USB (on PA9/PA10)" or "CAN bus (on PA9/PA10)".
|
||||
# CAN bus requires PA1 GPIO pin to be set at micro-controller start-up
|
||||
# The "carto" micro-controller will be used to control
|
||||
# the components on the board.
|
||||
|
||||
# See docs/Config_Reference.md for a description of parameters.
|
||||
|
||||
[mcu carto]
|
||||
serial: /dev/serial/by-id/usb-Klipper_stm32f042x6_29000380114330394D363620-if00
|
||||
#canbus_uuid: 92cf532ef122
|
||||
|
||||
#[adxl345 carto]
|
||||
#cs_pin: carto:PA3
|
||||
#spi_bus: spi1_PA6_PA7_PA5
|
||||
#axes_map: x,y,z
|
||||
|
||||
[thermistor 50k]
|
||||
temperature1: 25
|
||||
resistance1: 50000
|
||||
temperature2: 50
|
||||
resistance2: 17940
|
||||
temperature3: 100
|
||||
resistance3: 3090
|
||||
|
||||
[temperature_probe carto]
|
||||
pullup_resistor: 10000
|
||||
sensor_type: 50k
|
||||
sensor_pin: carto:PA4
|
||||
min_temp: 0
|
||||
max_temp: 125
|
||||
|
||||
[led carto_led]
|
||||
white_pin: carto:PB5
|
||||
initial_WHITE: 0.03
|
||||
|
||||
[output_pin _LDC1612_en]
|
||||
pin: carto:PA15
|
||||
value: 0 # enable
|
||||
|
||||
[static_pwm_clock ldc1612_clk_in]
|
||||
pin: carto:PB4
|
||||
frequency: 24000000
|
||||
|
||||
[probe_eddy_current carto]
|
||||
sensor_type: ldc1612
|
||||
frequency: 24000000
|
||||
i2c_address: 42
|
||||
i2c_mcu: carto
|
||||
i2c_bus: i2c1_PB6_PB7
|
||||
|
|
@ -3,28 +3,30 @@
|
|||
|
||||
# See docs/Config_Reference.md for a description of parameters.
|
||||
|
||||
[carriage x]
|
||||
[carriage carriage_x]
|
||||
axis: x
|
||||
position_endstop: 0
|
||||
position_max: 300
|
||||
homing_speed: 50
|
||||
endstop_pin: ^PE5
|
||||
|
||||
[carriage y]
|
||||
[carriage carriage_y]
|
||||
axis: y
|
||||
position_endstop: 0
|
||||
position_max: 200
|
||||
homing_speed: 50
|
||||
endstop_pin: ^PJ1
|
||||
|
||||
[dual_carriage u]
|
||||
primary_carriage: x
|
||||
[dual_carriage carriage_u]
|
||||
primary_carriage: carriage_x
|
||||
safe_distance: 70
|
||||
position_endstop: 300
|
||||
position_max: 300
|
||||
homing_speed: 50
|
||||
endstop_pin: ^PE4
|
||||
|
||||
[dual_carriage v]
|
||||
primary_carriage: y
|
||||
[dual_carriage carriage_v]
|
||||
primary_carriage: carriage_y
|
||||
safe_distance: 50
|
||||
position_endstop: 200
|
||||
position_max: 200
|
||||
|
|
@ -32,7 +34,7 @@ homing_speed: 50
|
|||
endstop_pin: ^PD4
|
||||
|
||||
[stepper a]
|
||||
carriages: x+y
|
||||
carriages: carriage_x+carriage_y
|
||||
step_pin: PF0
|
||||
dir_pin: PF1
|
||||
enable_pin: !PD7
|
||||
|
|
@ -40,7 +42,7 @@ microsteps: 16
|
|||
rotation_distance: 40
|
||||
|
||||
[stepper b]
|
||||
carriages: u-v
|
||||
carriages: carriage_u-carriage_v
|
||||
step_pin: PH1
|
||||
dir_pin: PH0
|
||||
enable_pin: !PA1
|
||||
|
|
@ -48,7 +50,7 @@ microsteps: 16
|
|||
rotation_distance: 40
|
||||
|
||||
[stepper c]
|
||||
carriages: x-y
|
||||
carriages: carriage_x-carriage_y
|
||||
step_pin: PF6
|
||||
dir_pin: !PF7
|
||||
enable_pin: !PF2
|
||||
|
|
@ -56,7 +58,7 @@ microsteps: 16
|
|||
rotation_distance: 40
|
||||
|
||||
[stepper d]
|
||||
carriages: u+v
|
||||
carriages: carriage_u+carriage_v
|
||||
step_pin: PE3
|
||||
dir_pin: !PH6
|
||||
enable_pin: !PG5
|
||||
|
|
@ -83,8 +85,8 @@ max_temp: 250
|
|||
|
||||
[gcode_macro PARK_extruder]
|
||||
gcode:
|
||||
SET_DUAL_CARRIAGE CARRIAGE=x
|
||||
SET_DUAL_CARRIAGE CARRIAGE=y
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_x
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_y
|
||||
G90
|
||||
G1 X0 Y0
|
||||
|
||||
|
|
@ -92,8 +94,8 @@ gcode:
|
|||
gcode:
|
||||
PARK_{printer.toolhead.extruder}
|
||||
ACTIVATE_EXTRUDER EXTRUDER=extruder
|
||||
SET_DUAL_CARRIAGE CARRIAGE=x
|
||||
SET_DUAL_CARRIAGE CARRIAGE=y
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_x
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_y
|
||||
|
||||
[extruder1]
|
||||
step_pin: PC1
|
||||
|
|
@ -115,8 +117,8 @@ max_temp: 250
|
|||
|
||||
[gcode_macro PARK_extruder1]
|
||||
gcode:
|
||||
SET_DUAL_CARRIAGE CARRIAGE=u
|
||||
SET_DUAL_CARRIAGE CARRIAGE=v
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_u
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_v
|
||||
G90
|
||||
G1 X300 Y200
|
||||
|
||||
|
|
@ -124,37 +126,59 @@ gcode:
|
|||
gcode:
|
||||
PARK_{printer.toolhead.extruder}
|
||||
ACTIVATE_EXTRUDER EXTRUDER=extruder1
|
||||
SET_DUAL_CARRIAGE CARRIAGE=u
|
||||
SET_DUAL_CARRIAGE CARRIAGE=v
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_u
|
||||
SET_DUAL_CARRIAGE 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
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_x MODE=PRIMARY
|
||||
SET_DUAL_CARRIAGE 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
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_u MODE=PRIMARY
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_v MODE=PRIMARY
|
||||
G1 X150 Y100
|
||||
SET_DUAL_CARRIAGE CARRIAGE=u MODE=COPY
|
||||
SET_DUAL_CARRIAGE CARRIAGE=v MODE=COPY
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_u MODE=COPY
|
||||
SET_DUAL_CARRIAGE 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
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_x MODE=PRIMARY
|
||||
SET_DUAL_CARRIAGE 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
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_u MODE=PRIMARY
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_v MODE=PRIMARY
|
||||
G1 X300 Y100
|
||||
SET_DUAL_CARRIAGE CARRIAGE=u MODE=MIRROR
|
||||
SET_DUAL_CARRIAGE CARRIAGE=v MODE=COPY
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_u MODE=MIRROR
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_v MODE=COPY
|
||||
SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder
|
||||
|
||||
[gcode_macro _DISABLE_AND_PARK_EXTRUDER]
|
||||
gcode:
|
||||
SAVE_GCODE_STATE NAME=disable_extruder
|
||||
SAVE_DUAL_CARRIAGE_STATE NAME=disable_extruder
|
||||
SET_HEATER_TEMPERATURE HEATER={params.EXTRUDER} TARGET=0
|
||||
SYNC_EXTRUDER_MOTION EXTRUDER={params.EXTRUDER} MOTION_QUEUE=
|
||||
PARK_{params.EXTRUDER}
|
||||
RESTORE_DUAL_CARRIAGE_STATE NAME=disable_extruder MOVE=0
|
||||
RESTORE_GCODE_STATE NAME=disable_extruder MOVE=0
|
||||
|
||||
[gcode_macro DISABLE_T0]
|
||||
gcode:
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_x MODE=INACTIVE
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_y MODE=INACTIVE
|
||||
_DISABLE_AND_PARK_EXTRUDER EXTRUDER=extruder
|
||||
|
||||
[gcode_macro DISABLE_T1]
|
||||
gcode:
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_u MODE=INACTIVE
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_v MODE=INACTIVE
|
||||
_DISABLE_AND_PARK_EXTRUDER EXTRUDER=extruder
|
||||
|
||||
[printer]
|
||||
kinematics: generic_cartesian
|
||||
max_velocity: 300
|
||||
|
|
@ -169,9 +193,9 @@ max_z_accel: 100
|
|||
#[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>
|
||||
# SET_DUAL_CARRIAGE CARRIAGE=carriage_u
|
||||
# SET_DUAL_CARRIAGE CARRIAGE=carriage_v
|
||||
# SET_INPUT_SHAPER SHAPER_TYPE_X=<carriage_u_shaper> SHAPER_FREQ_X=<carriage_u_freq> SHAPER_TYPE_Y=<carriage_v_shaper> SHAPER_FREQ_Y=<carriage_v_freq>
|
||||
# SET_DUAL_CARRIAGE CARRIAGE=carriage_x MODE=PRIMARY
|
||||
# SET_DUAL_CARRIAGE CARRIAGE=carriage_y MODE=PRIMARY
|
||||
# SET_INPUT_SHAPER SHAPER_TYPE_X=<carriage_x_shaper> SHAPER_FREQ_X=<carriage_x_freq> SHAPER_TYPE_Y=<carriage_y_shaper> SHAPER_FREQ_Y=<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
|
||||
|
|
@ -506,7 +506,7 @@ gcode:
|
|||
{% if printer["gcode_macro SELECT_TOOL"].tool_selected|int != -1 %}
|
||||
M118 Loading filament to PINDA ...
|
||||
MANUAL_STEPPER STEPPER=gear_stepper SET_POSITION=0
|
||||
MANUAL_STEPPER STEPPER=gear_stepper MOVE={printer["gcode_macro VAR_MMU2S"].pinda_load_length} STOP_ON_ENDSTOP=2
|
||||
MANUAL_STEPPER STEPPER=gear_stepper MOVE={printer["gcode_macro VAR_MMU2S"].pinda_load_length} STOP_ON_ENDSTOP=try_home
|
||||
MANUAL_STEPPER STEPPER=gear_stepper SET_POSITION=0
|
||||
MANUAL_STEPPER STEPPER=gear_stepper MOVE=10
|
||||
IS_FILAMENT_IN_PINDA
|
||||
|
|
@ -579,7 +579,7 @@ gcode:
|
|||
M118 Unloading filament from extruder to PINDA ...
|
||||
MANUAL_STEPPER STEPPER=gear_stepper SET_POSITION=0
|
||||
{% if printer["gcode_macro VAR_MMU2S"].enable_5in1 == 0 %}
|
||||
MANUAL_STEPPER STEPPER=gear_stepper MOVE=-{printer["gcode_macro VAR_MMU2S"].bowden_unload_length} SPEED=120 ACCEL=80 STOP_ON_ENDSTOP=-2
|
||||
MANUAL_STEPPER STEPPER=gear_stepper MOVE=-{printer["gcode_macro VAR_MMU2S"].bowden_unload_length} SPEED=120 ACCEL=80 STOP_ON_ENDSTOP=try_inverted_home
|
||||
IS_FILAMENT_STUCK_IN_PINDA
|
||||
{% else %}
|
||||
MANUAL_STEPPER STEPPER=gear_stepper MOVE=-{printer["gcode_macro VAR_MMU2S"].bowden_unload_length} SPEED=120 ACCEL=80
|
||||
|
|
@ -792,7 +792,7 @@ gcode:
|
|||
{% if printer["gcode_macro VAR_MMU2S"].enable_5in1 == 0 %}
|
||||
M118 Homing selector
|
||||
MANUAL_STEPPER STEPPER=selector_stepper SET_POSITION=0
|
||||
MANUAL_STEPPER STEPPER=selector_stepper MOVE=-76 STOP_ON_ENDSTOP=1
|
||||
MANUAL_STEPPER STEPPER=selector_stepper MOVE=-76 STOP_ON_ENDSTOP=home
|
||||
MANUAL_STEPPER STEPPER=selector_stepper SET_POSITION=0
|
||||
{% endif %}
|
||||
MANUAL_STEPPER STEPPER=idler_stepper MOVE=0
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -102,20 +102,35 @@ some functionality in C code.
|
|||
Initial execution starts in **klippy/klippy.py**. This reads the
|
||||
command-line arguments, opens the printer config file, instantiates
|
||||
the main printer objects, and starts the serial connection. The main
|
||||
execution of G-code commands is in the process_commands() method in
|
||||
execution of G-code commands is in the _process_commands() method in
|
||||
**klippy/gcode.py**. This code translates the G-code commands into
|
||||
printer object calls, which frequently translate the actions to
|
||||
commands to be executed on the micro-controller (as declared via the
|
||||
DECL_COMMAND macro in the micro-controller code).
|
||||
|
||||
There are four threads in the Klippy host code. The main thread
|
||||
handles incoming gcode commands. A second thread (which resides
|
||||
entirely in the **klippy/chelper/serialqueue.c** C code) handles
|
||||
low-level IO with the serial port. The third thread is used to process
|
||||
response messages from the micro-controller in the Python code (see
|
||||
**klippy/serialhdl.py**). The fourth thread writes debug messages to
|
||||
the log (see **klippy/queuelogger.py**) so that the other threads
|
||||
never block on log writes.
|
||||
There are several threads in the Klipper host code:
|
||||
* There is a Python "main thread" that handles incoming G-Code
|
||||
commands and is the starting point for most actions. This thread
|
||||
runs the [reactor](https://en.wikipedia.org/wiki/Reactor_pattern)
|
||||
(**klippy/reactor.py**) and most high-level actions originate from
|
||||
IO and timer event callbacks from that reactor.
|
||||
* A thread for writing messages to the log so that the other threads
|
||||
do not block on log writes. This thread resides in the
|
||||
**klippy/queuelogger.py** code and its multi-threaded nature is not
|
||||
exposed to the main Python thread.
|
||||
* A thread per micro-controller that performs the low-level reading
|
||||
and writing of messages to that micro-controller. It resides in the
|
||||
**klippy/chelper/serialqueue.c** C code and its multi-threaded
|
||||
nature is not exposed to the Python code.
|
||||
* A thread per micro-controller for processing messages received from
|
||||
that micro-controller in the Python code. This thread is created in
|
||||
**klippy/serialhdl.py**. Care must be taken in Python callbacks
|
||||
invoked from this thread as this thread may directly interact with
|
||||
the main Python thread.
|
||||
* A thread per stepper motor that calculates the timing of stepper
|
||||
motor step pulses and compresses those times. This thread resides in
|
||||
the **klippy/chelper/steppersync.c** C code and its multi-threaded
|
||||
nature is not exposed to the Python code.
|
||||
|
||||
## Code flow of a move command
|
||||
|
||||
|
|
@ -136,9 +151,10 @@ provides further information on the mechanics of moves.
|
|||
|
||||
* The ToolHead class (in toolhead.py) handles "look-ahead" and tracks
|
||||
the timing of printing actions. The main codepath for a move is:
|
||||
`ToolHead.move() -> LookAheadQueue.add_move() ->
|
||||
LookAheadQueue.flush() -> Move.set_junction() ->
|
||||
ToolHead._process_moves()`.
|
||||
`ToolHead.move() -> LookAheadQueue.add_move()`, then
|
||||
`ToolHead.move() -> ToolHead._process_lookahead() ->
|
||||
LookAheadQueue.flush() -> Move.set_junction()`, and then
|
||||
`ToolHead._process_lookahead() -> trapq_append()`.
|
||||
* ToolHead.move() creates a Move() object with the parameters of the
|
||||
move (in cartesian space and in units of seconds and millimeters).
|
||||
* The kinematics class is given the opportunity to audit each move
|
||||
|
|
@ -157,39 +173,47 @@ provides further information on the mechanics of moves.
|
|||
phase, followed by a constant deceleration phase. Every move
|
||||
contains these three phases in this order, but some phases may be of
|
||||
zero duration.
|
||||
* When ToolHead._process_moves() is called, everything about the
|
||||
* When ToolHead._process_lookahead() resumes, everything about the
|
||||
move is known - its start location, its end location, its
|
||||
acceleration, its start/cruising/end velocity, and distance traveled
|
||||
during acceleration/cruising/deceleration. All the information is
|
||||
stored in the Move() class and is in cartesian space in units of
|
||||
millimeters and seconds.
|
||||
* The moves are then placed on a "trapezoid motion queue" via
|
||||
trapq_append() (in klippy/chelper/trapq.c). The trapq stores all the
|
||||
information in the Move() class in a C struct accessible to the host
|
||||
C code.
|
||||
|
||||
* Note that the extruder is handled in its own kinematic class:
|
||||
`ToolHead._process_lookahead() -> PrinterExtruder.process_move()`.
|
||||
Since the Move() class specifies the exact movement time and since
|
||||
step pulses are sent to the micro-controller with specific timing,
|
||||
stepper movements produced by the extruder class will be in sync
|
||||
with head movement even though the code is kept separate.
|
||||
|
||||
* For efficiency reasons, stepper motion is generated in the C code in
|
||||
a thread per stepper motor. The threads are notified when steps
|
||||
should be generated by the motion_queuing module
|
||||
(klippy/extras/motion_queuing.py):
|
||||
`PrinterMotionQueuing._flush_handler() ->
|
||||
PrinterMotionQueuing._advance_move_time() ->
|
||||
steppersyncmgr_gen_steps() -> se_start_gen_steps()`.
|
||||
|
||||
* Klipper uses an
|
||||
[iterative solver](https://en.wikipedia.org/wiki/Root-finding_algorithm)
|
||||
to generate the step times for each stepper. For efficiency reasons,
|
||||
the stepper pulse times are generated in C code. The moves are first
|
||||
placed on a "trapezoid motion queue": `ToolHead._process_moves() ->
|
||||
trapq_append()` (in klippy/chelper/trapq.c). The step times are then
|
||||
generated: `ToolHead._process_moves() ->
|
||||
ToolHead._advance_move_time() -> ToolHead._advance_flush_time() ->
|
||||
MCU_Stepper.generate_steps() -> itersolve_generate_steps() ->
|
||||
itersolve_gen_steps_range()` (in klippy/chelper/itersolve.c). The
|
||||
goal of the iterative solver is to find step times given a function
|
||||
that calculates a stepper position from a time. This is done by
|
||||
repeatedly "guessing" various times until the stepper position
|
||||
formula returns the desired position of the next step on the
|
||||
stepper. The feedback produced from each guess is used to improve
|
||||
future guesses so that the process rapidly converges to the desired
|
||||
time. The kinematic stepper position formulas are located in the
|
||||
klippy/chelper/ directory (eg, kin_cart.c, kin_corexy.c,
|
||||
kin_delta.c, kin_extruder.c).
|
||||
|
||||
* Note that the extruder is handled in its own kinematic class:
|
||||
`ToolHead._process_moves() -> PrinterExtruder.move()`. Since
|
||||
the Move() class specifies the exact movement time and since step
|
||||
pulses are sent to the micro-controller with specific timing,
|
||||
stepper movements produced by the extruder class will be in sync
|
||||
with head movement even though the code is kept separate.
|
||||
to generate the step times for each stepper. The step times are
|
||||
generated from the background thread (klippy/chelper/steppersync.c):
|
||||
`se_background_thread() -> se_generate_steps() ->
|
||||
itersolve_generate_steps() -> itersolve_gen_steps_range()` (in
|
||||
klippy/chelper/itersolve.c). The goal of the iterative solver is to
|
||||
find step times given a function that calculates a stepper position
|
||||
from a time. This is done by repeatedly "guessing" various times
|
||||
until the stepper position formula returns the desired position of
|
||||
the next step on the stepper. The feedback produced from each guess
|
||||
is used to improve future guesses so that the process rapidly
|
||||
converges to the desired time. The kinematic stepper position
|
||||
formulas are located in the klippy/chelper/ directory (eg,
|
||||
kin_cart.c, kin_corexy.c, kin_delta.c, kin_extruder.c).
|
||||
|
||||
* After the iterative solver calculates the step times they are added
|
||||
to an array: `itersolve_gen_steps_range() -> stepcompress_append()`
|
||||
|
|
@ -206,7 +230,7 @@ provides further information on the mechanics of moves.
|
|||
commands that correspond to the list of stepper step times built in
|
||||
the previous stage. These "queue_step" commands are then queued,
|
||||
prioritized, and sent to the micro-controller (via
|
||||
stepcompress.c:steppersync and serialqueue.c:serialqueue).
|
||||
steppersync.c:steppersync and serialqueue.c:serialqueue).
|
||||
|
||||
* Processing of the queue_step commands on the micro-controller starts
|
||||
in src/command.c which parses the command and calls
|
||||
|
|
|
|||
|
|
@ -8,6 +8,81 @@ All dates in this document are approximate.
|
|||
|
||||
## Changes
|
||||
|
||||
20260214: The `MANUAL_STEPPER` G-Code command `STOP_ON_ENDSTOP`
|
||||
parameter has changed. See the
|
||||
[MANUAL_STEPPER](G-Codes.md#manual_stepper) documentation for
|
||||
details. Using the previous integer values (-2, -1, 1, 2) is
|
||||
deprecated and support will be removed in the near future.
|
||||
|
||||
20260207: The low-level i2c behavior of sx1509 and uc1701 devices has
|
||||
changed. Previously an i2c error would result in a shutdown, and now
|
||||
i2c errors when communicating with these devices will only generate
|
||||
warnings in the log file.
|
||||
|
||||
20260109: The status value `{printer.probe.last_z_result}` is
|
||||
deprecated; it will be removed in the near future. Use
|
||||
`{printer.probe.last_probe_position}` instead, and note that this new
|
||||
value already has the probe's configured xyz offsets applied.
|
||||
|
||||
20260109: The g-code console text output from the `PROBE`,
|
||||
`PROBE_ACCURACY`, and similar commands has changed. Now Z heights are
|
||||
reported relative to the nominal bed Z position instead of relative to
|
||||
the probe's configured `z_offset`. Similarly, intermediate probe x and
|
||||
y console reports will also have the probe's configured `x_offset` and
|
||||
`y_offset` applied.
|
||||
|
||||
20260109: The `[screws_tilt_adjust]` module now reports the status
|
||||
variable `{printer.screws_tilt_adjust.result.screw1.z}` with the
|
||||
probe's `z_offset` applied. That is, one would previously need to
|
||||
subtract the probe's configured `z_offset` to find the absolute Z
|
||||
deviation at the given screw location and now one must not apply the
|
||||
`z_offset`.
|
||||
|
||||
20251122: An option `axis` has been added to `[carriage <name>]`
|
||||
sections for `generic_cartesian` kinematics, allowing arbitrary names
|
||||
for primary carriages. Users are encouraged to explicitly specify
|
||||
`axis` option now.
|
||||
|
||||
20251106: The status fields `{printer.toolhead.position}`,
|
||||
`{printer.gcode_move.position}`,
|
||||
`{printer.gcode_move.gcode_position}`, and
|
||||
`{printer.motion_report.live_position}` are changing. These
|
||||
coordinates used to always contain four components, but now may
|
||||
contain additional components. The ordering and number of components
|
||||
may change at run-time - see the
|
||||
[status reference](Status_Reference.md#accessing-coordinates) for
|
||||
important details. Accessing any of these coordinates in macros using
|
||||
the ".e" accessor is deprecated - use something like
|
||||
`{printer.toolhead.position[printer.gcode_move.axis_map.E]}` as an
|
||||
alternative.
|
||||
|
||||
20251106: The status fields `{printer.gcode_move.homing_origin}`,
|
||||
`{printer.toolhead.axis_min}`, and `{printer.toolhead.axis_max}`
|
||||
currently contain four components where the fourth component is always
|
||||
zero. This behavior is deprecated. In the future these coordinates may
|
||||
contain only three components. For additional information see the
|
||||
[status reference](Status_Reference.md#accessing-coordinates).
|
||||
|
||||
20251010: During normal printing the command processing will now
|
||||
attempt to stay one second ahead of printer movement (reduced from two
|
||||
seconds previously).
|
||||
|
||||
20251003: Support for the undocumented `max_stepper_error` option in
|
||||
the `[printer]` config section has been removed.
|
||||
|
||||
20250916: The definitions of EI, 2HUMP_EI, and 3HUMP_EI input shapers
|
||||
were updated. For best performance it is recommended to recalibrate
|
||||
input shapers, especially if some of these shapers are currently used.
|
||||
|
||||
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
|
||||
|
|
@ -58,7 +133,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`
|
||||
|
|
@ -131,7 +206,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
|
||||
|
|
@ -365,7 +440,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
|
||||
|
|
|
|||
|
|
@ -126,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]
|
||||
|
|
@ -740,16 +738,17 @@ max_velocity:
|
|||
max_accel:
|
||||
#minimum_cruise_ratio:
|
||||
#square_corner_velocity:
|
||||
#max_accel_to_decel:
|
||||
#max_z_velocity:
|
||||
#max_z_accel:
|
||||
|
||||
```
|
||||
|
||||
Then a user must define the following three carriages: `[carriage x]`,
|
||||
`[carriage y]`, and `[carriage z]`, e.g.
|
||||
Then a user must define three primary carriages for X, Y, and Z axes, e.g.:
|
||||
```
|
||||
[carriage x]
|
||||
[carriage carriage_x]
|
||||
axis:
|
||||
# Axis of a carriage, either x, y, or z. This parameter must be provided,
|
||||
# unless a carriage name is x, y, or z itself.
|
||||
endstop_pin:
|
||||
# Endstop switch detection pin. If this endstop pin is on a
|
||||
# different mcu than the stepper motor(s) moving this carriage,
|
||||
|
|
@ -791,7 +790,8 @@ for instance
|
|||
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.
|
||||
# e.g. carriage_x, carriage_x+carriage_y, carriage_y-0.5*carriage_z,
|
||||
# carriage_x-carriage_z, etc. This parameter must be provided.
|
||||
step_pin:
|
||||
dir_pin:
|
||||
enable_pin:
|
||||
|
|
@ -803,28 +803,29 @@ microsteps:
|
|||
```
|
||||
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.
|
||||
affects the motion of the carriages. For example, `carriage_x+carriage_y`
|
||||
indicates that the motion of the stepper in the positive direction by the
|
||||
distance `d` moves the carriages `carriage_x` and `carriage_y` by the same
|
||||
distance `d` in the positive direction, while `carriage_x-0.5*carriage_y`
|
||||
means the motion of the stepper in the positive direction by the distance
|
||||
`d` moves the carriage `carriage_x` by the distance `d` in the positive
|
||||
direction, but the carriage `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]
|
||||
[carriage carriage_x]
|
||||
endstop_pin: ...
|
||||
...
|
||||
|
||||
[carriage y]
|
||||
[carriage carriage_y]
|
||||
endstop_pin: ...
|
||||
...
|
||||
|
||||
[stepper a0]
|
||||
carriages: x-y
|
||||
carriages: carriage_x-carriage_y
|
||||
step_pin: ...
|
||||
dir_pin: ...
|
||||
enable_pin: ...
|
||||
|
|
@ -832,7 +833,7 @@ rotation_distance: ...
|
|||
...
|
||||
|
||||
[stepper a1]
|
||||
carriages: x-y
|
||||
carriages: carriage_x-carriage_y
|
||||
step_pin: ...
|
||||
dir_pin: ...
|
||||
enable_pin: ...
|
||||
|
|
@ -845,7 +846,7 @@ 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
|
||||
X gantry, 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
|
||||
|
|
@ -863,12 +864,12 @@ endstop_pin:
|
|||
|
||||
and the corresponding stepper motors, for example:
|
||||
```
|
||||
[extra_carriage y1]
|
||||
primary_carriage: y
|
||||
[extra_carriage carriage_y1]
|
||||
primary_carriage: carriage_y
|
||||
endstop_pin: ...
|
||||
|
||||
[stepper sy1]
|
||||
carriages: y1
|
||||
carriages: carriage_y1
|
||||
...
|
||||
```
|
||||
Notably, an `[extra_carriage]` does not define parameters such as
|
||||
|
|
@ -1783,17 +1784,22 @@ the [command reference](G-Codes.md#input_shaper).
|
|||
# input shapers, this parameter can be set from different
|
||||
# considerations. The default value is 0, which disables input
|
||||
# shaping for Y axis.
|
||||
#shaper_freq_z: 0
|
||||
# A frequency (in Hz) of the input shaper for Z axis. The default
|
||||
# value is 0, which disables input shaping for Z axis.
|
||||
#shaper_type: mzv
|
||||
# A type of the input shaper to use for both X and Y axes. Supported
|
||||
# A type of the input shaper to use for all axes. Supported
|
||||
# shapers are zv, mzv, zvd, ei, 2hump_ei, and 3hump_ei. The default
|
||||
# is mzv input shaper.
|
||||
#shaper_type_x:
|
||||
#shaper_type_y:
|
||||
# If shaper_type is not set, these two parameters can be used to
|
||||
# configure different input shapers for X and Y axes. The same
|
||||
#shaper_type_z:
|
||||
# If shaper_type is not set, these parameters can be used to
|
||||
# configure different input shapers for X, Y, and Z axes. The same
|
||||
# values are supported as for shaper_type parameter.
|
||||
#damping_ratio_x: 0.1
|
||||
#damping_ratio_y: 0.1
|
||||
#damping_ratio_z: 0.1
|
||||
# Damping ratios of vibrations of X and Y axes used by input shapers
|
||||
# to improve vibration suppression. Default value is 0.1 which is a
|
||||
# good all-round value for most printers. In most circumstances this
|
||||
|
|
@ -1946,6 +1952,39 @@ Support for LIS3DH accelerometers.
|
|||
# See the "adxl345" section for information on this parameter.
|
||||
```
|
||||
|
||||
### [bmi160]
|
||||
|
||||
BMI160 accelerometer. This sensor can be queried via I2C or SPI bus.
|
||||
```
|
||||
[bmi160]
|
||||
#i2c_address:
|
||||
# Default is 105 (0x69). If SA0 is tied to GND, use 104 (0x68).
|
||||
# Only used for I2C.
|
||||
#i2c_mcu:
|
||||
#i2c_bus:
|
||||
#i2c_speed:
|
||||
# See the "common I2C settings" section for a description of the
|
||||
# above parameters. Only used for I2C.
|
||||
#cs_pin:
|
||||
#spi_speed:
|
||||
#spi_bus:
|
||||
#spi_software_sclk_pin:
|
||||
#spi_software_mosi_pin:
|
||||
#spi_software_miso_pin:
|
||||
# See the "common SPI settings" section for a description of the
|
||||
# above parameters. Only used for SPI.
|
||||
#axes_map: x, y, z
|
||||
# See the "adxl345" section for information on this parameter.
|
||||
```
|
||||
|
||||
**Important:** Many BMI160 modules use ambiguous pin labels. For SPI:
|
||||
- Use **SCL** for clock (not SCX)
|
||||
- Use **SDA** for MOSI (not SDX)
|
||||
- Use **SA0** for MISO
|
||||
- Use **CS** for chip select
|
||||
|
||||
The pins labeled SCX/SDX are for the auxiliary magnetometer bus.
|
||||
|
||||
### [mpu9250]
|
||||
|
||||
Support for MPU-9250, MPU-9255, MPU-6515, MPU-6050, and MPU-6500
|
||||
|
|
@ -2001,6 +2040,10 @@ section of the measuring resonances guide for more information on
|
|||
# and on the toolhead (for X axis). These parameters have the same
|
||||
# format as 'accel_chip' parameter. Only 'accel_chip' or these two
|
||||
# parameters must be provided.
|
||||
#accel_chip_z:
|
||||
# A name of the accelerometer chip to use for measurements of Z axis.
|
||||
# This parameter has the same format as 'accel_chip'. The default is
|
||||
# not to configure an accelerometer for Z axis.
|
||||
#max_smoothing:
|
||||
# Maximum input shaper smoothing to allow for each axis during shaper
|
||||
# auto-calibration (with 'SHAPER_CALIBRATE' command). By default no
|
||||
|
|
@ -2011,16 +2054,20 @@ section of the measuring resonances guide for more information on
|
|||
# during the calibration. The default is 50.
|
||||
#min_freq: 5
|
||||
# Minimum frequency to test for resonances. The default is 5 Hz.
|
||||
#max_freq: 133.33
|
||||
# Maximum frequency to test for resonances. The default is 133.33 Hz.
|
||||
#max_freq: 135
|
||||
# Maximum frequency to test for resonances. The default is 135 Hz.
|
||||
#max_freq_z: 100
|
||||
# Maximum frequency to test Z axis for resonances. The default is 100 Hz.
|
||||
#accel_per_hz: 60
|
||||
# This parameter is used to determine which acceleration to use to
|
||||
# test a specific frequency: accel = accel_per_hz * freq. Higher the
|
||||
# value, the higher is the energy of the oscillations. Can be set to
|
||||
# a lower than the default value if the resonances get too strong on
|
||||
# the printer. However, lower values make measurements of
|
||||
# high-frequency resonances less precise. The default value is 75
|
||||
# (mm/sec).
|
||||
# the printer. However, lower values make measurements of high-frequency
|
||||
# resonances less precise. The default value is 60 (mm/sec).
|
||||
#accel_per_hz_z: 15
|
||||
# This parameter has the same meaning as accel_per_hz, but applies to
|
||||
# Z axis specifically. The default is 15 (mm/sec).
|
||||
#hz_per_sec: 1
|
||||
# Determines the speed of the test. When testing all frequencies in
|
||||
# range [min_freq, max_freq], each second the frequency increases by
|
||||
|
|
@ -2029,6 +2076,8 @@ section of the measuring resonances guide for more information on
|
|||
# (Hz/sec == sec^-2).
|
||||
#sweeping_accel: 400
|
||||
# An acceleration of slow sweeping moves. The default is 400 mm/sec^2.
|
||||
#sweeping_accel_z: 50
|
||||
# Same as sweeping_accel above, but for Z axis. The default is 50 mm/sec^2.
|
||||
#sweeping_period: 1.2
|
||||
# A period of slow sweeping moves. Setting this parameter to 0
|
||||
# disables slow sweeping moves. Avoid setting it to a too small
|
||||
|
|
@ -2308,6 +2357,16 @@ sensor_type: ldc1612
|
|||
#samples_tolerance:
|
||||
#samples_tolerance_retries:
|
||||
# See the "probe" section for information on these parameters.
|
||||
#tap_threshold:
|
||||
# Noise cutoff/stop trigger threshold (in Hz). Specify this value to
|
||||
# enable support for "METHOD=tap" probe commands. See Eddy_Probe.md
|
||||
# for more information. Larger values make the tap detection less
|
||||
# sensitive. That is, larger values make it less likely the toolhead
|
||||
# will incorrectly stop early due to noise, while increasing the
|
||||
# risk of the toolhead not correctly stopping when it first contacts
|
||||
# the bed. If this value is specified then one may override its
|
||||
# value at run-time using the "TAP_THRESHOLD" parameter on probe
|
||||
# commands. The default is to not enable support for "tap" probing.
|
||||
```
|
||||
|
||||
### [axis_twist_compensation]
|
||||
|
|
@ -2467,10 +2526,16 @@ 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.
|
||||
#primary_carriage:
|
||||
# Defines the matching carriage on the same gantry as this dual carriage and
|
||||
# the corresponding dual axis. Must match a name of a defined `[carriage]` or
|
||||
# another independent `[dual_carriage]`. If not set, which is a default,
|
||||
# defines a dual carriage independent of a `[carriage]` with the same axis
|
||||
# as this one (e.g. on a different gantry).
|
||||
#axis:
|
||||
# Axis of a carriage, either x or y. If 'primary_carriage' is defined, then
|
||||
# this parameter defaults to the 'axis' parameter of that primary carriage,
|
||||
# otherwise this parameter must be defined.
|
||||
#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
|
||||
|
|
@ -2479,7 +2544,8 @@ primary_carriage:
|
|||
# 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.
|
||||
# checks will be disabled. Only valid for a dual_carriage with a defined
|
||||
# 'primary_carriage'.
|
||||
endstop_pin:
|
||||
#position_min:
|
||||
position_endstop:
|
||||
|
|
@ -2497,18 +2563,18 @@ 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 carriage_x]
|
||||
...
|
||||
|
||||
[carriage y]
|
||||
[carriage carriage_y]
|
||||
...
|
||||
|
||||
[dual_carriage u]
|
||||
primary_carriage: x
|
||||
[dual_carriage carriage_u]
|
||||
primary_carriage: carriage_x
|
||||
...
|
||||
|
||||
[stepper dc_stepper]
|
||||
carriages: u-y
|
||||
carriages: carriage_u-carriage_y
|
||||
...
|
||||
```
|
||||
|
||||
|
|
@ -2524,14 +2590,14 @@ example above:
|
|||
[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>
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_u
|
||||
SET_INPUT_SHAPER SHAPER_TYPE_X=<carriage_u_shaper> SHAPER_FREQ_X=<carriage_x_freq> SHAPER_TYPE_Y=<carriage_y_shaper> SHAPER_FREQ_Y=<carriage_y_freq>
|
||||
SET_DUAL_CARRIAGE CARRIAGE=carriage_x
|
||||
SET_INPUT_SHAPER SHAPER_TYPE_X=<carriage_x_shaper> SHAPER_FREQ_X=<carriage_x_freq> SHAPER_TYPE_Y=<carriage_y_shaper> SHAPER_FREQ_Y=<carriage_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.
|
||||
of the `carriage_x` and `carriage_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
|
||||
|
|
@ -2960,7 +3026,7 @@ sensor_type: BME280
|
|||
|
||||
### AHT10/AHT20/AHT21 temperature sensor
|
||||
|
||||
AHT10/AHT20/AHT21 two wire interface (I2C) environmental sensors.
|
||||
AHT10/AHT15/AHT20/AHT21/AHT30 two wire interface (I2C) environmental sensors.
|
||||
Note that these sensors are not intended for use with extruders and
|
||||
heater beds, but rather for monitoring ambient temperature (C) and
|
||||
relative humidity. See
|
||||
|
|
@ -2968,8 +3034,9 @@ relative humidity. See
|
|||
that may be used to report humidity in addition to temperature.
|
||||
|
||||
```
|
||||
sensor_type: AHT10
|
||||
# Also use AHT10 for AHT20 and AHT21 sensors.
|
||||
sensor_type: AHT1X
|
||||
# Must be "AHT1X" , "AHT2X", "AHT3X"
|
||||
# Some AHT20 sensors can use "AHT1X"
|
||||
#i2c_address:
|
||||
# Default is 56 (0x38). Some AHT10 sensors give the option to use
|
||||
# 57 (0x39) by moving a resistor.
|
||||
|
|
@ -3507,11 +3574,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.
|
||||
|
|
@ -3635,6 +3697,20 @@ pin:
|
|||
# These options are deprecated and should no longer be specified.
|
||||
```
|
||||
|
||||
### [static_pwm_clock]
|
||||
|
||||
Static configurable output pin (one may define any number of
|
||||
sections with an "static_pwm_clock" prefix).
|
||||
Pins configured here will be set up as clock output pins.
|
||||
Generally used to provide clock input to other hardware on the board.
|
||||
```
|
||||
[static_pwm_clock my_pin]
|
||||
pin:
|
||||
# The pin to configure as an output. This parameter must be provided.
|
||||
#frequency: 100
|
||||
# Target output frequency.
|
||||
```
|
||||
|
||||
### [pwm_tool]
|
||||
|
||||
Pulse width modulation digital output pins capable of high speed
|
||||
|
|
@ -4427,16 +4503,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
|
||||
|
|
@ -4977,8 +5058,8 @@ detection_length: 7.0
|
|||
# a state change on the switch_pin
|
||||
# Default is 7 mm.
|
||||
extruder:
|
||||
# The name of the extruder section this sensor is associated with.
|
||||
# This parameter must be provided.
|
||||
# The name of the extruder or extruder_stepper section this sensor
|
||||
# is associated with. This parameter must be provided.
|
||||
switch_pin:
|
||||
#pause_on_runout:
|
||||
#runout_gcode:
|
||||
|
|
@ -5210,7 +5291,7 @@ sensor_type:
|
|||
# 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 controle the number of
|
||||
# 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
|
||||
|
|
@ -5344,7 +5425,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
|
||||
|
|
@ -5363,7 +5444,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.
|
||||
```
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ later analyzed. To use this feature, Klipper must be started with the
|
|||
|
||||
Data logging is enabled with the `data_logger.py` tool. For example:
|
||||
```
|
||||
~/klipper/scripts/motan/data_logger.py /tmp/klippy_uds mylog
|
||||
~/klipper/scripts/motan/data_logger.py /tmp/klippy_uds mylog -s '*'
|
||||
```
|
||||
|
||||
This command will connect to the Klipper API Server, subscribe to
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ This document describes how to use an
|
|||
[eddy current](https://en.wikipedia.org/wiki/Eddy_current) inductive
|
||||
probe in Klipper.
|
||||
|
||||
Currently, an eddy current probe can not be used for Z homing. The
|
||||
sensor can only be used for Z probing.
|
||||
Currently, an eddy current probe can not precisely home Z (i.e., `G28 Z`).
|
||||
The sensor can precisely do Z probing (i.e., `PROBE ...`).
|
||||
Look at the [homing correction](Eddy_Probe.md#homing-correction-macros)
|
||||
for further details.
|
||||
|
||||
Start by declaring a
|
||||
[probe_eddy_current config section](Config_Reference.md#probe_eddy_current)
|
||||
|
|
@ -24,18 +26,29 @@ named `[probe_eddy_current my_eddy_probe]` then one would run
|
|||
complete in a few seconds. After it completes, issue a `SAVE_CONFIG`
|
||||
command to save the results to the printer.cfg and restart.
|
||||
|
||||
Eddy current is used as a proximity/distance sensor (similar to a laser ruler).
|
||||
The second step in calibration is to correlate the sensor readings to
|
||||
the corresponding Z heights. Home the printer and navigate the
|
||||
toolhead so that the nozzle is near the center of the bed. Then run an
|
||||
toolhead so that the nozzle is near the center of the bed. Then run a
|
||||
`PROBE_EDDY_CURRENT_CALIBRATE CHIP=my_eddy_probe` command. Once the
|
||||
tool starts, follow the steps described at
|
||||
["the paper test"](Bed_Level.md#the-paper-test) to determine the
|
||||
actual distance between the nozzle and bed at the given location. Once
|
||||
those steps are complete one can `ACCEPT` the position. The tool will
|
||||
then move the the toolhead so that the sensor is above the point where
|
||||
the nozzle used to be and run a series of movements to correlate the
|
||||
then move the toolhead so that the sensor is above the point where the
|
||||
nozzle used to be and run a series of movements to correlate the
|
||||
sensor to Z positions. This will take a couple of minutes. After the
|
||||
tool completes, issue a `SAVE_CONFIG` command to save the results to
|
||||
tool completes it will output the sensor performance data:
|
||||
```
|
||||
probe_eddy_current: noise 0.000642mm, MAD_Hz=11.314 in 2525 queries
|
||||
Total frequency range: 45000.012 Hz
|
||||
z_offset: 0.250 # noise 0.000200mm, MAD_Hz=11.000
|
||||
z_offset: 0.530 # noise 0.000300mm, MAD_Hz=12.000
|
||||
z_offset: 1.010 # noise 0.000400mm, MAD_Hz=14.000
|
||||
z_offset: 2.010 # noise 0.000600mm, MAD_Hz=12.000
|
||||
z_offset: 3.010 # noise 0.000700mm, MAD_Hz=9.000
|
||||
```
|
||||
issue a `SAVE_CONFIG` command to save the results to
|
||||
the printer.cfg and restart.
|
||||
|
||||
After initial calibration it is a good idea to verify that the
|
||||
|
|
@ -55,6 +68,133 @@ surface temperature or sensor hardware temperature can skew the
|
|||
results. It is important that calibration and probing is only done
|
||||
when the printer is at a stable temperature.
|
||||
|
||||
## Homing correction macros
|
||||
|
||||
Because of current limitations, homing and probing
|
||||
are implemented differently for the eddy sensors.
|
||||
As a result, homing suffers from an offset error,
|
||||
while probing handles this correctly.
|
||||
|
||||
To correct the homing offset.
|
||||
One can use the suggested macro inside the homing override or
|
||||
inside the starting G-Code.
|
||||
|
||||
[Force move](Config_Reference.md#force_move) section
|
||||
have to be defined in the config.
|
||||
|
||||
```
|
||||
[gcode_macro _RELOAD_Z_OFFSET_FROM_PROBE]
|
||||
gcode:
|
||||
{% set Z = printer.toolhead.position.z %}
|
||||
SET_KINEMATIC_POSITION Z={Z - printer.probe.last_probe_position.z}
|
||||
|
||||
[gcode_macro SET_Z_FROM_PROBE]
|
||||
gcode:
|
||||
{% set METHOD = params.METHOD | default("automatic") %}
|
||||
PROBE METHOD={METHOD}
|
||||
_RELOAD_Z_OFFSET_FROM_PROBE
|
||||
G0 Z5
|
||||
```
|
||||
|
||||
## Tap calibration
|
||||
|
||||
The Eddy probe measures the resonance frequency of the coil.
|
||||
By the absolute value of the frequency and the calibration curve from
|
||||
`PROBE_EDDY_CURRENT_CALIBRATE`, it is therefore possible to detect
|
||||
where the bed is without physical contact.
|
||||
|
||||
By use of the same knowledge, we know that frequency changes with
|
||||
the distance. It is possible to track that change in real time and
|
||||
detect the time/position where contact happens - a change of frequency
|
||||
starts to change in a different way.
|
||||
For example, stopped to change because of the collision.
|
||||
|
||||
Because eddy output is not perfect: there is sensor noise,
|
||||
mechanical oscillation, thermal expansion and other discrepancies,
|
||||
it is required to calibrate the stop threshold for your machine.
|
||||
Practically, it ensures that the Eddy's output data absolute value
|
||||
change per second (velocity) is high enough - higher than the noise level,
|
||||
and that upon collision it always decreases by at least this value.
|
||||
|
||||
```
|
||||
[probe_eddy_current my_probe]
|
||||
# eddy probe configuration...
|
||||
# Recommended starting values for the tap
|
||||
#samples: 3
|
||||
#samples_tolerance: 0.025
|
||||
#samples_tolerance_retries: 3
|
||||
```
|
||||
|
||||
Before setting it to any other value, it is necessary to install `scipy`:
|
||||
|
||||
```bash
|
||||
~/klippy-env/bin/pip install scipy
|
||||
```
|
||||
|
||||
The suggested calibration routine works as follows:
|
||||
1. Home Z
|
||||
2. Place the toolhead at the center of the bed.
|
||||
3. Move the Z axis far away (30 mm, for example).
|
||||
4. Run `PROBE METHOD=tap`
|
||||
5. If it stops before colliding, increase the `tap_threshold`.
|
||||
|
||||
Repeat until the nozzle softly touches the bed.
|
||||
This is easier to do with a clean nozzle and
|
||||
by visually inspecting the process.
|
||||
|
||||
You can streamline the process by placing the toolhead in the center once.
|
||||
Then, upon config restart, trick the machine into thinking that Z is homed.
|
||||
```
|
||||
SET_KINEMATIC_POSITION X=<middle> Y=<middle> Z=0
|
||||
G0 Z5 # Optional retract
|
||||
PROBE METHOD=tap
|
||||
```
|
||||
|
||||
Here is an example sequence of threshold values to test:
|
||||
```
|
||||
1 -> 5 -> 10 -> 20 -> 40 -> 80 -> 160
|
||||
160 -> 120 -> 100
|
||||
```
|
||||
Your value will normally be between those.
|
||||
- Too high a value leaves a less safe margin for early collision -
|
||||
if something is between the nozzle and the bed, or if the nozzle
|
||||
is too close to the bed before the tap.
|
||||
- Too low - can make the toolhead stop in mid-air
|
||||
because of the noise.
|
||||
|
||||
You can estimate the initial threshold value by analyzing your own
|
||||
calibration routine output:
|
||||
```
|
||||
probe_eddy_current: noise 0.000642mm, MAD_Hz=11.314
|
||||
...
|
||||
z_offset: 1.010 # noise 0.000400mm, MAD_Hz=14.000
|
||||
```
|
||||
The estimation will be:
|
||||
```
|
||||
MAD_Hz * 2
|
||||
11.314 * 2 = 22.628
|
||||
```
|
||||
|
||||
To further fine tune threshold, one can use `PROBE_ACCURACY METHOD=tap`.
|
||||
The range is expected to be about 0.02 mm,
|
||||
with the default probe speed of 5 mm/s.
|
||||
Elevated coil temperature may increase noise and may require additional tuning.
|
||||
|
||||
You can validate the tap precision by measuring the paper thickness
|
||||
from the initial calibration guide. It is expected to be ~0.1mm.
|
||||
|
||||
Tap precision is limited by the sampling frequency and
|
||||
the speed of the descent.
|
||||
If you take 24 photos per second of the moving train, you can only estimate
|
||||
where the train was between photos.
|
||||
|
||||
It is possible to reduce the descending speed. It may require decrease of
|
||||
absolute `tap_threshold` value.
|
||||
|
||||
It is possible to tap over non-conductive surfaces as long as there is metal
|
||||
behind it within the sensor's sensitivity range.
|
||||
Max distance can be approximated to be about 1.5x of the coil's narrowest part.
|
||||
|
||||
## Thermal Drift Calibration
|
||||
|
||||
As with all inductive probes, eddy current probes are subject to
|
||||
|
|
@ -144,3 +284,38 @@ to perform thermal drift calibration:
|
|||
|
||||
As one may conclude, the calibration process outlined above is more challenging
|
||||
and time consuming than most other procedures. It may require practice and several attempts to achieve an optimal calibration.
|
||||
|
||||
## Errors description
|
||||
|
||||
Possible homing errors and actionables:
|
||||
|
||||
- Sensor error
|
||||
- Check logs for detailed error
|
||||
- Eddy I2C STATUS/DATA error.
|
||||
- Check loose wiring.
|
||||
- Try software I2C/decrease I2C rate
|
||||
- Invalid read data
|
||||
- Same as I2C
|
||||
|
||||
Possible sensor errors and actionables:
|
||||
- Frequency over valid hard range
|
||||
- Check frequency configuration
|
||||
- Hardware fault
|
||||
- Frequency over valid soft range
|
||||
- Check frequency configuration
|
||||
- Conversion Watchdog timeout
|
||||
- Hardware fault
|
||||
|
||||
Amplitude Low/High warning messages can mean:
|
||||
- Sensor close to the bed
|
||||
- Sensor far from the bed
|
||||
- Higher temperature than was at the current calibration
|
||||
- Capacitor missing
|
||||
|
||||
On some sensors, it is not possible to completely avoid amplitude
|
||||
warning indicator.
|
||||
|
||||
You can try to redo the `LDC_CALIBRATE_DRIVE_CURRENT` calibration at work
|
||||
temperature or increase `reg_drive_current` by 1-2 from the calibrated value.
|
||||
|
||||
Generally, it is like an engine check light. It may indicate an issue.
|
||||
|
|
|
|||
17
docs/FAQ.md
17
docs/FAQ.md
|
|
@ -98,17 +98,16 @@ bootloaders.
|
|||
|
||||
## Can I run Klipper on something other than a Raspberry Pi 3?
|
||||
|
||||
The recommended hardware is a Raspberry Pi 2, Raspberry Pi 3, or
|
||||
Raspberry Pi 4.
|
||||
The recommended hardware is a Raspberry Pi Zero2w, Raspberry Pi 3,
|
||||
Raspberry Pi 4 or Raspberry Pi 5. Klipper will also run on other SBC
|
||||
devices as well as x86 hardware, as described below.
|
||||
|
||||
Klipper will run on a Raspberry Pi 1 and on the Raspberry Pi Zero, but
|
||||
these boards don't have enough processing power to run OctoPrint
|
||||
Klipper will run on a Raspberry Pi 1, 2 and on the Raspberry Pi Zero1,
|
||||
but these boards don't have enough processing power to run Klipper
|
||||
well. It is common for print stalls to occur on these slower machines
|
||||
when printing directly from OctoPrint. (The printer may move faster
|
||||
than OctoPrint can send movement commands.) If you wish to run on one
|
||||
one of these slower boards anyway, consider using the "virtual_sdcard"
|
||||
feature when printing (see
|
||||
[config reference](Config_Reference.md#virtual_sdcard) for details).
|
||||
when printing (The printer may move faster than Klipper can send
|
||||
movement commands.) It is not reccomended to run Klipper on these older
|
||||
machines.
|
||||
|
||||
For running on the Beaglebone, see the
|
||||
[Beaglebone specific installation instructions](Beaglebone.md).
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ Klipper supports many standard 3d printer features:
|
|||
|
||||
* Support for common temperature sensors (eg, common thermistors,
|
||||
AD595, AD597, AD849x, PT100, PT1000, MAX6675, MAX31855, MAX31856,
|
||||
MAX31865, BME280, HTU21D, DS18B20, AHT10, SHT3x, and LM75). Custom
|
||||
MAX31865, BME280, HTU21D, DS18B20, AHT1X, AHT2X, AHT3X, SHT3x, and LM75). Custom
|
||||
thermistors and custom analog temperature sensors can also be
|
||||
configured. One can monitor the internal micro-controller
|
||||
temperature sensor and the internal temperature sensor of a
|
||||
|
|
|
|||
193
docs/G-Codes.md
193
docs/G-Codes.md
|
|
@ -350,18 +350,25 @@ The following command is available when the
|
|||
enabled.
|
||||
|
||||
#### SET_DUAL_CARRIAGE
|
||||
`SET_DUAL_CARRIAGE CARRIAGE=<carriage> [MODE=[PRIMARY|COPY|MIRROR]]`:
|
||||
`SET_DUAL_CARRIAGE CARRIAGE=<carriage> [MODE=[PRIMARY|COPY|MIRROR|INACTIVE]]`:
|
||||
This command will change the mode of the specified carriage.
|
||||
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).
|
||||
deactivates all other carriages on the same axis and makes the specified
|
||||
carriage execute subsequent G-Code movement commands as-is. Before activating
|
||||
`COPY` or `MIRROR` mode for a carriage, a different one must be activated as
|
||||
`PRIMARY` on the same axis. When set to either of these two modes, the carriage
|
||||
will track the subsequent G-Code moves and either copy relative movements
|
||||
(in `COPY` mode) or execute them in the opposite (mirror) direction (in
|
||||
`MIRROR` mode). Setting the mode to `INACTIVE` deactivates the carriage and
|
||||
makes it ignore further G-Code moves. Note that deactivating the primary
|
||||
carriage on the axis does not disable other carriages working in `COPY` or
|
||||
`MIRROR` mode, which can be used to disable printing a failed part by any of
|
||||
the tools and park that tool to prevent collisions with an unfinished part, see
|
||||
this [sample configuration](../config/sample-corexyuv.cfg) for macros examples.
|
||||
|
||||
|
||||
#### SAVE_DUAL_CARRIAGE_STATE
|
||||
`SAVE_DUAL_CARRIAGE_STATE [NAME=<state_name>]`: Save the current positions
|
||||
|
|
@ -372,14 +379,18 @@ to the given string. If NAME is not provided it defaults to "default".
|
|||
|
||||
#### RESTORE_DUAL_CARRIAGE_STATE
|
||||
`RESTORE_DUAL_CARRIAGE_STATE [NAME=<state_name>] [MOVE=[0|1] [MOVE_SPEED=<speed>]]`:
|
||||
Restore the previously saved positions of the dual carriages and their modes,
|
||||
unless "MOVE=0" is specified, in which case only the saved modes will be
|
||||
restored, but not the positions of the carriages. If positions are being
|
||||
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.
|
||||
Restore the previously saved states of all dual and their primary carriages.
|
||||
This command restores the modes of the carriages and moves them to their
|
||||
previously saved positions, unless "MOVE=0" is specified. If positions are being
|
||||
restored and "MOVE_SPEED" is specified, then the carriages will move with at
|
||||
most the provided speed (in mm/s); otherwise the homing speeds of the
|
||||
corresponding carriages will be used as a reference. Note that the carriages
|
||||
restore their positions only over their own axes, which may be necessary to
|
||||
correctly restore COPY and MIRROR mode of the dual carriage. In addition, this
|
||||
command updates the Klipper toolhead position for each axis that has some dual
|
||||
carriages: it is set to match the actual position of the activated primary
|
||||
carriage of an axis or, if an axis does not have a saved primary carriage,
|
||||
to the axis position when `SAVE_DUAL_CARRIAGE_STATE` command was called.
|
||||
|
||||
### [endstop_phase]
|
||||
|
||||
|
|
@ -500,7 +511,7 @@ enabled.
|
|||
`SET_FAN_SPEED FAN=config_name SPEED=<speed>` This command sets the
|
||||
speed of a fan. "speed" must be between 0.0 and 1.0.
|
||||
|
||||
`SET_FAN_SPEED PIN=config_name TEMPLATE=<template_name>
|
||||
`SET_FAN_SPEED FAN=config_name TEMPLATE=<template_name>
|
||||
[<param_x>=<literal>]`: If `TEMPLATE` is specified then it assigns a
|
||||
[display_template](Config_Reference.md#display_template) to the given
|
||||
fan. For example, if one defined a `[display_template
|
||||
|
|
@ -760,11 +771,11 @@ 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
|
||||
if `stepper a` and `stepper b` initially have `carriage_x-carriage_y` and
|
||||
`carriage_x+carriage_y` carriages correspondingly, then the following
|
||||
sequence of commands will let a user effectively swap the carriage controls:
|
||||
`SET_STEPPER_CARRIAGES STEPPER=a CARRIAGES=carriage_x+carriage_y DISABLE_CHECKS=1`
|
||||
and `SET_STEPPER_CARRIAGES STEPPER=b CARRIAGES=carriage_x-carriage_y`, while
|
||||
still validating the final kinematics state.
|
||||
|
||||
### [hall_filament_width_sensor]
|
||||
|
|
@ -835,15 +846,17 @@ been enabled (also see the
|
|||
|
||||
#### SET_INPUT_SHAPER
|
||||
`SET_INPUT_SHAPER [SHAPER_FREQ_X=<shaper_freq_x>]
|
||||
[SHAPER_FREQ_Y=<shaper_freq_y>] [DAMPING_RATIO_X=<damping_ratio_x>]
|
||||
[DAMPING_RATIO_Y=<damping_ratio_y>] [SHAPER_TYPE=<shaper>]
|
||||
[SHAPER_TYPE_X=<shaper_type_x>] [SHAPER_TYPE_Y=<shaper_type_y>]`:
|
||||
[SHAPER_FREQ_Y=<shaper_freq_y>] [SHAPER_FREQ_Y=<shaper_freq_z>]
|
||||
[DAMPING_RATIO_X=<damping_ratio_x>] [DAMPING_RATIO_Y=<damping_ratio_y>]
|
||||
[DAMPING_RATIO_Z=<damping_ratio_z>] [SHAPER_TYPE=<shaper>]
|
||||
[SHAPER_TYPE_X=<shaper_type_x>] [SHAPER_TYPE_Y=<shaper_type_y>]
|
||||
[SHAPER_TYPE_Z=<shaper_type_z>]`:
|
||||
Modify input shaper parameters. Note that SHAPER_TYPE parameter resets
|
||||
input shaper for both X and Y axes even if different shaper types have
|
||||
input shaper for all axes even if different shaper types have
|
||||
been configured in [input_shaper] section. SHAPER_TYPE cannot be used
|
||||
together with either of SHAPER_TYPE_X and SHAPER_TYPE_Y parameters.
|
||||
See [config reference](Config_Reference.md#input_shaper) for more
|
||||
details on each of these parameters.
|
||||
together with any of SHAPER_TYPE_X, SHAPER_TYPE_Y, and SHAPER_TYPE_Z
|
||||
parameters. See [config reference](Config_Reference.md#input_shaper)
|
||||
for more details on each of these parameters.
|
||||
|
||||
### [led]
|
||||
|
||||
|
|
@ -924,10 +937,27 @@ is calibrated a force in grams is also reported.
|
|||
|
||||
### [load_cell_probe]
|
||||
|
||||
The following commands are enabled if a
|
||||
The commands below are enabled if a
|
||||
[load_cell config section](Config_Reference.md#load_cell_probe) has been
|
||||
enabled.
|
||||
|
||||
In addition, 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>`
|
||||
|
||||
### 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
|
||||
|
|
@ -938,23 +968,6 @@ QUERY_ENDSTOPS and QUERY_PROBE for load cell probes.
|
|||
- `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.
|
||||
|
|
@ -994,22 +1007,34 @@ enabled.
|
|||
|
||||
#### MANUAL_STEPPER
|
||||
`MANUAL_STEPPER STEPPER=config_name [ENABLE=[0|1]]
|
||||
[SET_POSITION=<pos>] [SPEED=<speed>] [ACCEL=<accel>] [MOVE=<pos>
|
||||
[STOP_ON_ENDSTOP=[1|2|-1|-2]] [SYNC=0]]`: This command will alter the
|
||||
state of the stepper. Use the ENABLE parameter to enable/disable the
|
||||
stepper. Use the SET_POSITION parameter to force the stepper to think
|
||||
it is at the given position. Use the MOVE parameter to request a
|
||||
movement to the given position. If SPEED and/or ACCEL is specified
|
||||
then the given values will be used instead of the defaults specified
|
||||
in the config file. If an ACCEL of zero is specified then no
|
||||
acceleration will be performed. If STOP_ON_ENDSTOP=1 is specified then
|
||||
the move will end early should the endstop report as triggered (use
|
||||
STOP_ON_ENDSTOP=2 to complete the move without error even if the
|
||||
endstop does not trigger, use -1 or -2 to stop when the endstop
|
||||
reports not triggered). Normally future G-Code commands will be
|
||||
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.
|
||||
[SET_POSITION=<pos>] [SPEED=<speed>] [ACCEL=<accel>] [MOVE=<pos>]
|
||||
[SYNC=0]]`: This command will alter the state of the stepper. Use the
|
||||
ENABLE parameter to enable/disable the stepper. Use the SET_POSITION
|
||||
parameter to force the stepper to think it is at the given
|
||||
position. Use the MOVE parameter to request a movement to the given
|
||||
position. If SPEED and/or ACCEL is specified then the given values
|
||||
will be used instead of the defaults specified in the config file. If
|
||||
an ACCEL of zero is specified then no acceleration will be
|
||||
performed. Normally future G-Code commands will be 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 [SPEED=<speed>] [ACCEL=<accel>]
|
||||
MOVE=<pos> STOP_ON_ENDSTOP=<check_type>`: If STOP_ON_ENDSTOP is
|
||||
specified then the move will end early if an endstop event occurs. The
|
||||
`STOP_ON_ENDSTOP` parameter may be set to one of the following values:
|
||||
|
||||
* `probe`: The movement will stop when the endstop reports triggered.
|
||||
* `home`: The movement will stop when the endstop reports triggered
|
||||
and the final position of the manual_stepper will be set such that
|
||||
the trigger position matches the position specified in the `MOVE`
|
||||
parameter.
|
||||
* `inverted_probe`, `inverted_home`: As above, however, the movement
|
||||
will stop when the endstop reports it is in a non-triggered state.
|
||||
* `try_probe`, `try_inverted_probe`, `try_home`, `try_inverted_home`:
|
||||
As above, but no error will be reported if the movement fully
|
||||
completes without an endstop event stopping the move early.
|
||||
|
||||
`MANUAL_STEPPER STEPPER=config_name GCODE_AXIS=[A-Z]
|
||||
[LIMIT_VELOCITY=<velocity>] [LIMIT_ACCEL=<accel>]
|
||||
|
|
@ -1200,10 +1225,39 @@ Requires a `SAVE_CONFIG` to take effect.
|
|||
|
||||
### [probe_eddy_current]
|
||||
|
||||
The following commands are available when a
|
||||
The commands below are available when a
|
||||
[probe_eddy_current config section](Config_Reference.md#probe_eddy_current)
|
||||
is enabled.
|
||||
|
||||
In addition, 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 `[probe_eddy_current]` section is defined:
|
||||
- `METHOD=<scan|rapid_scan|tap>`: This alters the probing mechanism:
|
||||
- `METHOD=scan`: The toolhead does not descend. Instead the toolhead
|
||||
will pause briefly above each target location and return the
|
||||
measured height at that position.
|
||||
- `METHOD=rapid_scan`: The toolhead does not descend and does not
|
||||
pause at each target location. The value returned is the measured
|
||||
height around the time that the toolhead was near each target
|
||||
position.
|
||||
- `METHOD=tap`: The toolhead will descend until the nozzle makes
|
||||
contact with the bed. This method is only available if
|
||||
`tap_threshold` is specified in the `[probe_eddy_current]` config
|
||||
section.
|
||||
- default: If no `METHOD` parameter is specified then the default
|
||||
behavior is for the toolhead to descend until the sensor detects
|
||||
that the distance to the bed is at or below the `z_offset`
|
||||
parameter specified in the `[probe_eddy_current]` config section.
|
||||
- `SAMPLE_TIME=<time>`: When using `METHOD=scan` probing, this
|
||||
specifies the time (in seconds) to pause at each target point. When
|
||||
using `METHOD=rapid_scan` this specifies the measurement time window
|
||||
at each target. If not specified, the default is 0.100 (which is
|
||||
100ms).
|
||||
- `TAP_THRESHOLD=<value>`: This overrides the `tap_threshold`
|
||||
specified in the `[probe_eddy_current]` config section when probing
|
||||
using `METHOD=tap`.
|
||||
|
||||
#### PROBE_EDDY_CURRENT_CALIBRATE
|
||||
`PROBE_EDDY_CURRENT_CALIBRATE CHIP=<config_name>`: This starts a tool
|
||||
that calibrates the sensor resonance frequencies to corresponding Z
|
||||
|
|
@ -1291,13 +1345,14 @@ all enabled accelerometer chips.
|
|||
[POINT=x,y,z] [INPUT_SHAPING=<0:1>]`: Runs the resonance
|
||||
test in all configured probe points for the requested "axis" and
|
||||
measures the acceleration using the accelerometer chips configured for
|
||||
the respective axis. "axis" can either be X or Y, or specify an
|
||||
arbitrary direction as `AXIS=dx,dy`, where dx and dy are floating
|
||||
the respective axis. "axis" can either be X, Y or Z, or specify an
|
||||
arbitrary direction as `AXIS=dx,dy[,dz]`, where dx, dy, dz are floating
|
||||
point numbers defining a direction vector (e.g. `AXIS=X`, `AXIS=Y`, or
|
||||
`AXIS=1,-1` to define a diagonal direction). Note that `AXIS=dx,dy`
|
||||
and `AXIS=-dx,-dy` is equivalent. `chip_name` can be one or
|
||||
more configured accel chips, delimited with comma, for example
|
||||
`CHIPS="adxl345, adxl345 rpi"`. If POINT is specified it will override the point(s)
|
||||
`AXIS=1,-1` to define a diagonal direction in XY plane, or `AXIS=0,1,1`
|
||||
to define a direction in YZ plane). Note that `AXIS=dx,dy` and `AXIS=-dx,-dy`
|
||||
is equivalent. `chip_name` can be one or more configured accel chips,
|
||||
delimited with comma, for example `CHIPS="adxl345, adxl345 rpi"`.
|
||||
If POINT is specified it will override the point(s)
|
||||
configured in `[resonance_tester]`. If `INPUT_SHAPING=0` or not set(default),
|
||||
disables input shaping for the resonance testing, because
|
||||
it is not valid to run the resonance testing with the input shaper
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ Things you can check with this data:
|
|||
* 'Unique values' should be a large percentage of the 'Samples
|
||||
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
|
||||
|
||||
|
|
@ -189,7 +189,7 @@ 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 wil change the `reference_tare_counts` of the
|
||||
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.
|
||||
|
|
@ -252,12 +252,12 @@ macro. This requires setting up
|
|||
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`.
|
||||
see the result of the probe in `printer.probe.last_probe_position`.
|
||||
|
||||
```gcode
|
||||
[gcode_macro _HOME_Z_FROM_LAST_PROBE]
|
||||
gcode:
|
||||
{% set z_probed = printer.probe.last_z_result %}
|
||||
{% set z_probed = printer.probe.last_probe_position.z %}
|
||||
{% set z_position = printer.toolhead.position[2] %}
|
||||
{% set z_actual = z_position - z_probed %}
|
||||
SET_KINEMATIC_POSITION Z={z_actual}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||

|
||||
|
||||
|
|
@ -714,6 +714,95 @@ If you are doing a shaper re-calibration and the reported smoothing for the
|
|||
suggested shaper configuration is almost the same as what you got during the
|
||||
previous calibration, this step can be skipped.
|
||||
|
||||
### Measuring the resonances of Z axis
|
||||
|
||||
Measuring the resonances of Z axis is similar in many aspects to measuring
|
||||
resonances of X and Y axes, with some subtle differences. Similarly to other
|
||||
axes measurements, you will need to have an accelerometer mounted on the
|
||||
moving parts of Z axis - either the bed itself (if the bed moves over Z axis),
|
||||
or the toolhead (if the toolhead/gantry moves over Z). You will need to
|
||||
add the appropriate chip configuration to `printer.cfg` and also add it to
|
||||
`[resonance_tester]` section, e.g.
|
||||
```
|
||||
[resonance_tester]
|
||||
accel_chip_z: <accelerometer full name>
|
||||
```
|
||||
Also make sure that `probe_points` configured in `[resonance_tester]` allow
|
||||
sufficient clearance for Z axis movements (20 mm above bed surface should
|
||||
provide enough clearance with the default test parameters).
|
||||
|
||||
The next consideration is that Z axis can typically reach lower maximum
|
||||
speeds and accelerations that X and Y axes. Default parameters of the test
|
||||
take that into consideration and are much less agressive, but it may still
|
||||
be necessary to increase `max_z_accel` and `max_z_velocity`. If you have
|
||||
them configured in `[printer]` section, make sure to set them to at least
|
||||
```
|
||||
[printer]
|
||||
max_z_velocity: 20
|
||||
max_z_accel: 1550
|
||||
```
|
||||
but only for the duration of the test, afterwards you can revert them back
|
||||
to their original values if necessary. And if you use custom test parameters
|
||||
for Z axis, `TEST_RESONANCES` and `SHAPER_CALIBRATE` will provide the minimum
|
||||
required limits if necessary for your specific case.
|
||||
|
||||
After all changes to `printer.cfg` have been made, restart Klipper and run
|
||||
either
|
||||
```
|
||||
TEST_RESONANCES AXIS=Z
|
||||
```
|
||||
or
|
||||
```
|
||||
SHAPER_CALIBRATE AXIS=Z
|
||||
```
|
||||
and proceed from there accordingly how you would for other axes.
|
||||
For example, after `TEST_RESONANCES` command you can run
|
||||
`calibrate_shaper.py` script and get shaper recommendations and
|
||||
the chart of resonance response:
|
||||
|
||||

|
||||
|
||||
After the calibration, the shaper parameters can be stored in the
|
||||
`printer.cfg`, e.g. from the example above:
|
||||
```
|
||||
[input_shaper]
|
||||
...
|
||||
shaper_type_z: mzv
|
||||
shaper_freq_z: 42.6
|
||||
```
|
||||
|
||||
Also, given the movements of Z axis are slow, you can easily consider
|
||||
more aggressive input shapers, e.g.
|
||||
```
|
||||
[input_shaper]
|
||||
...
|
||||
shaper_type_z: 2hump_ei
|
||||
shaper_freq_z: 63.0
|
||||
```
|
||||
|
||||
If the test produces bogus results, you may try to increase
|
||||
`accel_per_hz_z` parameter in `[resonance_tester]` from its
|
||||
default value 15 to a larger value in the range of 20-30, e.g.
|
||||
```
|
||||
[resonance_tester]
|
||||
accel_per_hz_z: 25
|
||||
```
|
||||
and repeat the test. Increasing this value will likely require
|
||||
increasing `max_z_accel` and `max_z_velocity` parameters as well.
|
||||
You can run `TEST_RESONANCES AXIS=Z` command to get the required
|
||||
minimum values.
|
||||
|
||||
However, if you are unable to measure the resonances of Z axis,
|
||||
you can consider just using
|
||||
```
|
||||
[input_shaper]
|
||||
...
|
||||
shaper_type_z: 3hump_ei
|
||||
shaper_freq_z: 65
|
||||
```
|
||||
as an acceptable all-round choice, given that the smoothing of
|
||||
Z axis movements is not of particular concerns.
|
||||
|
||||
### Unreliable measurements of resonance frequencies
|
||||
|
||||
Sometimes the resonance measurements can produce bogus results, leading to
|
||||
|
|
|
|||
|
|
@ -438,10 +438,15 @@ gcode:
|
|||
SET_DUAL_CARRIAGE CARRIAGE=0
|
||||
SET_INPUT_SHAPER SHAPER_TYPE_X=<primary_carriage_shaper> SHAPER_FREQ_X=<primary_carriage_freq> SHAPER_TYPE_Y=<y_shaper> SHAPER_FREQ_Y=<y_freq>
|
||||
```
|
||||
However, users of `generic_cartesian` kinematics should specify carriage names
|
||||
in `CARRIAGE=` parameters of `SET_DUAL_CARRIAGE` instead of their numbers.
|
||||
Note that `SHAPER_TYPE_Y` and `SHAPER_FREQ_Y` should be the same in both
|
||||
commands. It is also possible to put a similar snippet into the start g-code
|
||||
in the slicer, however then the shaper will not be enabled until any print
|
||||
is started.
|
||||
commands. If you need to configure an input shaper for Z axis, include
|
||||
its parameters in both `SET_INPUT_SHAPER` commands.
|
||||
|
||||
Besides `delayed_gcode`, it is also possible to put a similar snippet into
|
||||
the start g-code in the slicer, however then the shaper will not be enabled
|
||||
until any print is started.
|
||||
|
||||
Note that the input shaper only needs to be configured once. Subsequent changes
|
||||
of the carriages or their modes via `SET_DUAL_CARRIAGE` command will preserve
|
||||
|
|
@ -453,15 +458,40 @@ No, `input_shaper` feature has pretty much no impact on the print times by
|
|||
itself. However, the value of `max_accel` certainly does (tuning of this
|
||||
parameter described in [this section](#selecting-max_accel)).
|
||||
|
||||
### Should I enable and tune input shaper for Z axis?
|
||||
|
||||
Most of the users are not likely to see improvements in the quality of
|
||||
the prints directly, much unlike X and Y shapers. However, users of
|
||||
delta printers, printers with flying gantry, or printers with heavy
|
||||
moving beds may be able to increase the `max_z_accel` and `max_z_velocity`
|
||||
kinematics limits and thus get faster Z movements. This can be especially
|
||||
useful e.g. for toolchangers, but also when Z-hops are enabled in slicer.
|
||||
And in general, after enabling Z input shaper many users will hear that
|
||||
Z axis operates more smoothly, which may increase the comfort of printer
|
||||
operation, and may somewhat extend lifespan of Z axis parts.
|
||||
|
||||
## Technical details
|
||||
|
||||
### Input shapers
|
||||
|
||||
Input shapers used in Klipper are rather standard, and one can find more
|
||||
in-depth overview in the articles describing the corresponding shapers.
|
||||
This section contains a brief overview of some technical aspects of the
|
||||
supported input shapers. The table below shows some (usually approximate)
|
||||
parameters of each shaper.
|
||||
supported input shapers. Input shapers used in Klipper are rather standard,
|
||||
with the exception of MZV, and one can find more in-depth overview in
|
||||
the articles describing the corresponding shapers.
|
||||
|
||||
MZV stands for a Modified-ZV input shaper. The classic definition of ZV shaper
|
||||
assumes the duration Ts equal to 1/2 of the damped period of oscillations Td and
|
||||
has two pulses. However, ZV input shaper has a generalized form for an arbitrary
|
||||
duration in the range (0, Td] with three pulses (Specified-Duration ZV, see also
|
||||
SNA-ZV), with a negative middle pulse if Ts < Td and a positive one if Ts > Td.
|
||||
The MZV shaper was designed as an intermediate shaper between ZV and ZVD,
|
||||
offering better vibrations suppression than ZV when the determined (measured)
|
||||
shaper parameters deviate from the ones actually required by the printer,
|
||||
and smaller smoothing than ZVD. Effectively, it is a SD-ZV shaper with the
|
||||
specific duration Ts = 3/4 Td, exactly between ZV (Ts = 1/2 Td) and
|
||||
ZVD (Ts = Td), and it happens to work well for many real-life 3D printers.
|
||||
|
||||
The table below shows some (usually approximate) parameters of each shaper.
|
||||
|
||||
| Input <br> shaper | Shaper <br> duration | Vibration reduction 20x <br> (5% vibration tolerance) | Vibration reduction 10x <br> (10% vibration tolerance) |
|
||||
|:--:|:--:|:--:|:--:|
|
||||
|
|
@ -469,8 +499,8 @@ parameters of each shaper.
|
|||
| MZV | 0.75 / shaper_freq | ± 4% shaper_freq | -10%...+15% shaper_freq |
|
||||
| ZVD | 1 / shaper_freq | ± 15% shaper_freq | ± 22% shaper_freq |
|
||||
| EI | 1 / shaper_freq | ± 20% shaper_freq | ± 25% shaper_freq |
|
||||
| 2HUMP_EI | 1.5 / shaper_freq | ± 35% shaper_freq | ± 40 shaper_freq |
|
||||
| 3HUMP_EI | 2 / shaper_freq | -45...+50% shaper_freq | -50%...+55% shaper_freq |
|
||||
| 2HUMP_EI | 1.5 / shaper_freq | -40...+45% shaper_freq | -45..+50% shaper_freq |
|
||||
| 3HUMP_EI | 2 / shaper_freq | -50...+60% shaper_freq | -55%...+65% shaper_freq |
|
||||
|
||||
A note on vibration reduction: the values in the table above are approximate.
|
||||
If the damping ratio of the printer is known for each axis, the shaper can be
|
||||
|
|
@ -502,11 +532,11 @@ so the values for 10% vibration tolerance are provided only for the reference.
|
|||
resonances at 35 Hz and 60 Hz on the same axis: a) EI shaper needs to have
|
||||
shaper_freq = 35 / (1 - 0.2) = 43.75 Hz, and it will reduce resonances
|
||||
until 43.75 * (1 + 0.2) = 52.5 Hz, so it is not sufficient; b) 2HUMP_EI
|
||||
shaper needs to have shaper_freq = 35 / (1 - 0.35) = 53.85 Hz and will
|
||||
reduce vibrations until 53.85 * (1 + 0.35) = 72.7 Hz - so this is an
|
||||
shaper needs to have shaper_freq = 35 / (1 - 0.4) = 58.3 Hz and will
|
||||
reduce vibrations until 58.3 * (1 + 0.45) = 84.5 Hz - so this is an
|
||||
acceptable configuration. Always try to use as high shaper_freq as possible
|
||||
for a given shaper (perhaps with some safety margin, so in this example
|
||||
shaper_freq ≈ 50-52 Hz would work best), and try to use a shaper with as
|
||||
shaper_freq ≈ 55 Hz would work best), and try to use a shaper with as
|
||||
small shaper duration as possible.
|
||||
* If one needs to reduce vibrations at several very different frequencies
|
||||
(say, 30 Hz and 100 Hz), they may see that the table above does not provide
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -214,17 +214,16 @@ The following information is available in the `gcode_move` object
|
|||
(this object is always available):
|
||||
- `gcode_position`: The current position of the toolhead relative to
|
||||
the current G-Code origin. That is, positions that one might
|
||||
directly send to a `G1` command. It is possible to access the x, y,
|
||||
z, and e components of this position (eg, `gcode_position.x`).
|
||||
directly send to a `G1` command. This value is encoded as a
|
||||
[coordinate](#accessing-coordinates).
|
||||
- `position`: The last commanded position of the toolhead using the
|
||||
coordinate system specified in the config file. It is possible to
|
||||
access the x, y, z, and e components of this position (eg,
|
||||
`position.x`).
|
||||
coordinate system specified in the config file. This value is
|
||||
encoded as a [coordinate](#accessing-coordinates).
|
||||
- `homing_origin`: The origin of the gcode coordinate system (relative
|
||||
to the coordinate system specified in the config file) to use after
|
||||
a `G28` command. The `SET_GCODE_OFFSET` command can alter this
|
||||
position. It is possible to access the x, y, and z components of
|
||||
this position (eg, `homing_origin.x`).
|
||||
position. This value is encoded as a
|
||||
[coordinate](#accessing-coordinates).
|
||||
- `speed`: The last speed set in a `G1` command (in mm/s).
|
||||
- `speed_factor`: The "speed factor override" as set by an `M220`
|
||||
command. This is a floating point value such that 1.0 means no
|
||||
|
|
@ -236,6 +235,10 @@ The following information is available in the `gcode_move` object
|
|||
coordinate mode or False if in `G91` relative mode.
|
||||
- `absolute_extrude`: This returns True if in `M82` absolute extrude
|
||||
mode or False if in `M83` relative mode.
|
||||
- `axis_map`: Provides a mechanism for finding the coordinate
|
||||
component for a given G-Code id that is used in `G1` commands. See
|
||||
the [Accessing Coordinates](#accessing-coordinates) section for
|
||||
details.
|
||||
|
||||
## hall_filament_width_sensor
|
||||
|
||||
|
|
@ -291,6 +294,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
|
||||
|
||||
|
|
@ -300,7 +306,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]`.
|
||||
|
|
@ -358,7 +364,8 @@ The following information is available in the `motion_report` object
|
|||
(this object is automatically available if any stepper config section
|
||||
is defined):
|
||||
- `live_position`: The requested toolhead position interpolated to the
|
||||
current time.
|
||||
current time. This value is encoded as a
|
||||
[coordinate](#accessing-coordinates).
|
||||
- `live_velocity`: The requested toolhead velocity (in mm/s) at the
|
||||
current time.
|
||||
- `live_extruder_velocity`: The requested extruder velocity (in mm/s)
|
||||
|
|
@ -412,10 +419,18 @@ is defined):
|
|||
during the last QUERY_PROBE command. Note, if this is used in a
|
||||
macro, due to the order of template expansion, the QUERY_PROBE
|
||||
command must be run prior to the macro containing this reference.
|
||||
- `last_z_result`: Returns the Z result value of the last PROBE
|
||||
command. Note, if this is used in a macro, due to the order of
|
||||
template expansion, the PROBE (or similar) command must be run prior
|
||||
to the macro containing this reference.
|
||||
- `last_probe_position`: The results of the last `PROBE` command. This
|
||||
value is encoded as a [coordinate](#accessing-coordinates). The
|
||||
probe hardware estimates that if one were to command the toolhead to
|
||||
XY position `last_probe_position.x`,`last_probe_position.y` and
|
||||
descend then the tip of the toolhead would first contact the bed at
|
||||
a Z height of `last_probe_position.z`. These coordinates are
|
||||
relative to the frame (that is, they use the coordinate system
|
||||
specified in the config file). Note, if this is used in a macro,
|
||||
due to the order of template expansion, the `PROBE` command must be
|
||||
run prior to the macro containing this reference.
|
||||
- `last_z_result`: This value is deprecated; it will be removed in the
|
||||
near future.
|
||||
|
||||
## pwm_cycle_time
|
||||
|
||||
|
|
@ -547,9 +562,8 @@ objects (eg, `[tmc2208 stepper_x]`):
|
|||
The following information is available in the `toolhead` object
|
||||
(this object is always available):
|
||||
- `position`: The last commanded position of the toolhead relative to
|
||||
the coordinate system specified in the config file. It is possible
|
||||
to access the x, y, z, and e components of this position (eg,
|
||||
`position.x`).
|
||||
the coordinate system specified in the config file. This value is
|
||||
encoded as a [coordinate](#accessing-coordinates).
|
||||
- `extruder`: The name of the currently active extruder. For example,
|
||||
in a macro one could use `printer[printer.toolhead.extruder].target`
|
||||
to get the target temperature of the current extruder.
|
||||
|
|
@ -557,8 +571,8 @@ The following information is available in the `toolhead` object
|
|||
"homed" state. This is a string containing one or more of "x", "y",
|
||||
"z".
|
||||
- `axis_minimum`, `axis_maximum`: The axis travel limits (mm) after
|
||||
homing. It is possible to access the x, y, z components of this
|
||||
limit value (eg, `axis_minimum.x`, `axis_maximum.z`).
|
||||
homing. This value is encoded as a
|
||||
[coordinate](#accessing-coordinates).
|
||||
- For Delta printers the `cone_start_z` is the max z height at
|
||||
maximum radius (`printer.toolhead.cone_start_z`).
|
||||
- `max_velocity`, `max_accel`, `minimum_cruise_ratio`,
|
||||
|
|
@ -568,6 +582,10 @@ The following information is available in the `toolhead` object
|
|||
- `stalls`: The total number of times (since the last restart) that
|
||||
the printer had to be paused because the toolhead moved faster than
|
||||
moves could be read from the G-Code input.
|
||||
- `extra_axes`: Provides a mechanism for finding the coordinate
|
||||
component for extra axes available in standard `G1` type move
|
||||
commands. See the [Accessing Coordinates](#accessing-coordinates)
|
||||
section for details.
|
||||
|
||||
## dual_carriage
|
||||
|
||||
|
|
@ -624,3 +642,29 @@ The following information is available in the `z_tilt` object (this
|
|||
object is available if z_tilt is defined):
|
||||
- `applied`: True if the z-tilt leveling process has been run and completed
|
||||
successfully.
|
||||
|
||||
## Accessing Coordinates
|
||||
|
||||
Some status fields provide a "coordinate". For macro users these
|
||||
fields may be accessed by component name
|
||||
(eg,`{printer.toolhead.position.x}`), where the component name may be
|
||||
"x", "y", or "z".
|
||||
|
||||
For developers using the Klipper API Server these fields are
|
||||
transmitted as a list - for example: `{"toolhead": {"position": [1.0,
|
||||
2.0, 3.0, 7.3, 19.2]}}` . The first three components of the list
|
||||
correspond with the x, y, and z axes.
|
||||
|
||||
A coordinate will typically have at least 3 components (x, y, and z),
|
||||
however there may also be additional components. Care should be taken
|
||||
when accessing any of these additional components as the ordering and
|
||||
number of components may change at run-time.
|
||||
|
||||
One may use `{printer.gcode_move.axis_map}` and/or
|
||||
`{printer.toolhead.extra_axes}` to determine the number of components
|
||||
and the ordering of components. For example, to access the "E"
|
||||
component one could use
|
||||
`{printer.toolhead.position[printer.gcode_move.axis_map.E]}`. Or, if
|
||||
one wanted to find the component associated with the "extruder"
|
||||
object, one could use
|
||||
`{printer.toolhead.position[printer.toolhead.extra_axes.extruder]}`.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
#!/bin/bash
|
||||
# This script extracts the Klipper translations and builds multiple
|
||||
# mdocs sites - one for each supported language. See the README file
|
||||
# for additional details.
|
||||
# This script creates the main klipper3d.org website hosted on github.
|
||||
# It extracts the Klipper translations and builds multiple mdocs sites
|
||||
# - one for each supported language. See the README file for
|
||||
# additional details.
|
||||
|
||||
MKDOCS_DIR="docs/_klipper3d/"
|
||||
WORK_DIR="work/"
|
||||
|
|
@ -22,6 +23,11 @@ done < <(egrep -v '^ *(#|$)' ${TRANS_FILE})
|
|||
echo "building site for en"
|
||||
mkdocs build -f ${MKDOCS_MAIN}
|
||||
|
||||
# Cleanup files (mkdocs copies _klipper3d dir and its sitemap doesn't work)
|
||||
rm -rf ${PWD}/site/_klipper3d/__pycache__
|
||||
find ${PWD}/site/_klipper3d ! -name '*.css' -type f -delete
|
||||
rm ${PWD}/site/sitemap.xml ${PWD}/site/sitemap.xml.gz
|
||||
|
||||
# Build each additional language website
|
||||
while IFS="," read dirname langsite langdesc langsearch; do
|
||||
new_docs_dir="${WORK_DIR}lang/${langsite}/docs/"
|
||||
|
|
@ -81,4 +87,10 @@ while IFS="," read dirname langsite langdesc langsearch; do
|
|||
mkdir -p "${PWD}/site/${langsite}/"
|
||||
ln -sf "${PWD}/site/${langsite}/" "${WORK_DIR}lang/${langsite}/site"
|
||||
mkdocs build -f "${new_mkdocs_file}"
|
||||
|
||||
# Cleanup files (mkdocs copies _klipper3d dir and its sitemap doesn't work)
|
||||
rm -rf "${PWD}/site/${langsite}/_klipper3d/__pycache__"
|
||||
find "${PWD}/site/${langsite}/_klipper3d" ! -name '*.css' -type f -delete
|
||||
rm "${PWD}/site/${langsite}/sitemap.xml" "${PWD}/site/${langsite}/sitemap.xml.gz"
|
||||
|
||||
done < <(egrep -v '^ *(#|$)' ${TRANS_FILE})
|
||||
BIN
docs/img/calibrate-z.png
Normal file
BIN
docs/img/calibrate-z.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 158 KiB |
|
|
@ -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_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 = """
|
||||
|
|
@ -36,48 +36,57 @@ defs_stepcompress = """
|
|||
int step_count, interval, add;
|
||||
};
|
||||
|
||||
struct stepcompress *stepcompress_alloc(uint32_t oid);
|
||||
void stepcompress_fill(struct stepcompress *sc, uint32_t max_error
|
||||
, int32_t queue_step_msgtag, int32_t set_next_step_dir_msgtag);
|
||||
void stepcompress_fill(struct stepcompress *sc, uint32_t oid
|
||||
, uint32_t max_error, int32_t queue_step_msgtag
|
||||
, int32_t set_next_step_dir_msgtag);
|
||||
void stepcompress_set_invert_sdir(struct stepcompress *sc
|
||||
, uint32_t invert_sdir);
|
||||
void stepcompress_free(struct stepcompress *sc);
|
||||
int stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock);
|
||||
int stepcompress_set_last_position(struct stepcompress *sc
|
||||
, uint64_t clock, int64_t last_position);
|
||||
int64_t stepcompress_find_past_position(struct stepcompress *sc
|
||||
, uint64_t clock);
|
||||
int stepcompress_queue_msg(struct stepcompress *sc
|
||||
, uint32_t *data, int len);
|
||||
int stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock
|
||||
, uint32_t *data, int len);
|
||||
int stepcompress_extract_old(struct stepcompress *sc
|
||||
, struct pull_history_steps *p, int max
|
||||
, uint64_t start_clock, uint64_t end_clock);
|
||||
"""
|
||||
|
||||
struct steppersync *steppersync_alloc(struct serialqueue *sq
|
||||
, struct stepcompress **sc_list, int sc_num, int move_num);
|
||||
void steppersync_free(struct steppersync *ss);
|
||||
defs_steppersync = """
|
||||
struct stepcompress *syncemitter_get_stepcompress(struct syncemitter *se);
|
||||
void syncemitter_set_stepper_kinematics(struct syncemitter *se
|
||||
, struct stepper_kinematics *sk);
|
||||
struct stepper_kinematics *syncemitter_get_stepper_kinematics(
|
||||
struct syncemitter *se);
|
||||
void syncemitter_queue_msg(struct syncemitter *se, uint64_t req_clock
|
||||
, uint32_t *data, int len);
|
||||
struct syncemitter *steppersync_alloc_syncemitter(struct steppersync *ss
|
||||
, char name[16], int alloc_stepcompress);
|
||||
void steppersync_setup_movequeue(struct steppersync *ss
|
||||
, struct serialqueue *sq, int move_num);
|
||||
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 steppersyncmgr *steppersyncmgr_alloc(void);
|
||||
void steppersyncmgr_free(struct steppersyncmgr *ssm);
|
||||
struct steppersync *steppersyncmgr_alloc_steppersync(
|
||||
struct steppersyncmgr *ssm);
|
||||
int32_t steppersyncmgr_gen_steps(struct steppersyncmgr *ssm
|
||||
, double flush_time, double gen_steps_time, double clear_history_time);
|
||||
"""
|
||||
|
||||
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);
|
||||
struct trapq *itersolve_get_trapq(struct stepper_kinematics *sk);
|
||||
double itersolve_calc_position_from_coord(struct stepper_kinematics *sk
|
||||
, double x, double y, double z);
|
||||
void itersolve_set_position(struct stepper_kinematics *sk
|
||||
, double x, double y, double z);
|
||||
double itersolve_get_commanded_pos(struct stepper_kinematics *sk);
|
||||
double itersolve_get_gen_steps_pre_active(struct stepper_kinematics *sk);
|
||||
double itersolve_get_gen_steps_post_active(struct stepper_kinematics *sk);
|
||||
"""
|
||||
|
||||
defs_trapq = """
|
||||
|
|
@ -154,8 +163,6 @@ defs_kin_extruder = """
|
|||
"""
|
||||
|
||||
defs_kin_shaper = """
|
||||
double input_shaper_get_step_generation_window(
|
||||
struct stepper_kinematics *sk);
|
||||
int input_shaper_set_shaper_params(struct stepper_kinematics *sk, char axis
|
||||
, int n, double a[], double t[]);
|
||||
int input_shaper_set_sk(struct stepper_kinematics *sk
|
||||
|
|
@ -182,7 +189,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);
|
||||
|
|
@ -219,6 +226,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 = """
|
||||
|
|
@ -227,7 +235,7 @@ 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,
|
||||
|
|
@ -270,11 +278,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))
|
||||
|
||||
|
|
@ -282,17 +312,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,14 +143,14 @@ 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;
|
||||
if (!sk->tq)
|
||||
return 0;
|
||||
trapq_check_sentinels(sk->tq);
|
||||
struct move *m = list_first_entry(&sk->tq->moves, struct move, node);
|
||||
while (last_flush_time >= m->print_time + m->move_t)
|
||||
m = list_next_entry(m, node);
|
||||
|
|
@ -170,15 +170,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 +195,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,17 +240,17 @@ 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;
|
||||
sk->step_dist = step_dist;
|
||||
}
|
||||
|
||||
void __visible
|
||||
itersolve_set_stepcompress(struct stepper_kinematics *sk
|
||||
, struct stepcompress *sc, double step_dist)
|
||||
struct trapq * __visible
|
||||
itersolve_get_trapq(struct stepper_kinematics *sk)
|
||||
{
|
||||
sk->sc = sc;
|
||||
sk->step_dist = step_dist;
|
||||
return sk->tq;
|
||||
}
|
||||
|
||||
double __visible
|
||||
|
|
@ -278,3 +278,15 @@ itersolve_get_commanded_pos(struct stepper_kinematics *sk)
|
|||
{
|
||||
return sk->commanded_pos;
|
||||
}
|
||||
|
||||
double __visible
|
||||
itersolve_get_gen_steps_pre_active(struct stepper_kinematics *sk)
|
||||
{
|
||||
return sk->gen_steps_pre_active;
|
||||
}
|
||||
|
||||
double __visible
|
||||
itersolve_get_gen_steps_post_active(struct stepper_kinematics *sk)
|
||||
{
|
||||
return sk->gen_steps_post_active;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,16 +26,18 @@ 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);
|
||||
struct trapq *itersolve_get_trapq(struct stepper_kinematics *sk);
|
||||
double itersolve_calc_position_from_coord(struct stepper_kinematics *sk
|
||||
, double x, double y, double z);
|
||||
void itersolve_set_position(struct stepper_kinematics *sk
|
||||
, double x, double y, double z);
|
||||
double itersolve_get_commanded_pos(struct stepper_kinematics *sk);
|
||||
double itersolve_get_gen_steps_pre_active(struct stepper_kinematics *sk);
|
||||
double itersolve_get_gen_steps_post_active(struct stepper_kinematics *sk);
|
||||
|
||||
#endif // itersolve.h
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Kinematic input shapers to minimize motion vibrations in XY plane
|
||||
//
|
||||
// Copyright (C) 2019-2020 Kevin O'Connor <kevin@koconnor.net>
|
||||
// Copyright (C) 2020 Dmitry Butyugin <dmbutyugin@google.com>
|
||||
// Copyright (C) 2020-2025 Dmitry Butyugin <dmbutyugin@google.com>
|
||||
//
|
||||
// This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
|
|
@ -18,6 +18,8 @@
|
|||
* Shaper initialization
|
||||
****************************************************************/
|
||||
|
||||
static const int KIN_FLAGS[3] = { AF_X, AF_Y, AF_Z };
|
||||
|
||||
struct shaper_pulses {
|
||||
int num_pulses;
|
||||
struct {
|
||||
|
|
@ -113,7 +115,7 @@ struct input_shaper {
|
|||
struct stepper_kinematics sk;
|
||||
struct stepper_kinematics *orig_sk;
|
||||
struct move m;
|
||||
struct shaper_pulses sx, sy;
|
||||
struct shaper_pulses sp[3];
|
||||
};
|
||||
|
||||
// Optimized calc_position when only x axis is needed
|
||||
|
|
@ -122,9 +124,10 @@ shaper_x_calc_position(struct stepper_kinematics *sk, struct move *m
|
|||
, double move_time)
|
||||
{
|
||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||
if (!is->sx.num_pulses)
|
||||
struct shaper_pulses *sx = &is->sp[0];
|
||||
if (!sx->num_pulses)
|
||||
return is->orig_sk->calc_position_cb(is->orig_sk, m, move_time);
|
||||
is->m.start_pos.x = calc_position(m, 'x', move_time, &is->sx);
|
||||
is->m.start_pos.x = calc_position(m, 'x', move_time, sx);
|
||||
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
|
||||
}
|
||||
|
||||
|
|
@ -134,25 +137,41 @@ shaper_y_calc_position(struct stepper_kinematics *sk, struct move *m
|
|||
, double move_time)
|
||||
{
|
||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||
if (!is->sy.num_pulses)
|
||||
struct shaper_pulses *sy = &is->sp[1];
|
||||
if (!sy->num_pulses)
|
||||
return is->orig_sk->calc_position_cb(is->orig_sk, m, move_time);
|
||||
is->m.start_pos.y = calc_position(m, 'y', move_time, &is->sy);
|
||||
is->m.start_pos.y = calc_position(m, 'y', move_time, sy);
|
||||
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
|
||||
}
|
||||
|
||||
// General calc_position for both x and y axes
|
||||
// Optimized calc_position when only z axis is needed
|
||||
static double
|
||||
shaper_xy_calc_position(struct stepper_kinematics *sk, struct move *m
|
||||
, double move_time)
|
||||
shaper_z_calc_position(struct stepper_kinematics *sk, struct move *m
|
||||
, double move_time)
|
||||
{
|
||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||
if (!is->sx.num_pulses && !is->sy.num_pulses)
|
||||
struct shaper_pulses *sz = &is->sp[2];
|
||||
if (!sz->num_pulses)
|
||||
return is->orig_sk->calc_position_cb(is->orig_sk, m, move_time);
|
||||
is->m.start_pos.z = calc_position(m, 'z', move_time, sz);
|
||||
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
|
||||
}
|
||||
|
||||
// General calc_position for all x, y, and z axes
|
||||
static double
|
||||
shaper_xyz_calc_position(struct stepper_kinematics *sk, struct move *m
|
||||
, double move_time)
|
||||
{
|
||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||
if (!is->sp[0].num_pulses && !is->sp[1].num_pulses && !is->sp[2].num_pulses)
|
||||
return is->orig_sk->calc_position_cb(is->orig_sk, m, move_time);
|
||||
is->m.start_pos = move_get_coord(m, move_time);
|
||||
if (is->sx.num_pulses)
|
||||
is->m.start_pos.x = calc_position(m, 'x', move_time, &is->sx);
|
||||
if (is->sy.num_pulses)
|
||||
is->m.start_pos.y = calc_position(m, 'y', move_time, &is->sy);
|
||||
if (is->sp[0].num_pulses)
|
||||
is->m.start_pos.x = calc_position(m, 'x', move_time, &is->sp[0]);
|
||||
if (is->sp[1].num_pulses)
|
||||
is->m.start_pos.y = calc_position(m, 'y', move_time, &is->sp[1]);
|
||||
if (is->sp[2].num_pulses)
|
||||
is->m.start_pos.z = calc_position(m, 'z', move_time, &is->sp[2]);
|
||||
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
|
||||
}
|
||||
|
||||
|
|
@ -170,15 +189,24 @@ 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;
|
||||
struct shaper_pulses *sx = &is->sp[0];
|
||||
if ((is->sk.active_flags & AF_X) && sx->num_pulses) {
|
||||
pre_active = sx->pulses[sx->num_pulses-1].t;
|
||||
post_active = -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;
|
||||
struct shaper_pulses *sy = &is->sp[1];
|
||||
if ((is->sk.active_flags & AF_Y) && sy->num_pulses) {
|
||||
pre_active = sy->pulses[sy->num_pulses-1].t > pre_active
|
||||
? sy->pulses[sy->num_pulses-1].t : pre_active;
|
||||
post_active = -sy->pulses[0].t > post_active
|
||||
? -sy->pulses[0].t : post_active;
|
||||
}
|
||||
struct shaper_pulses *sz = &is->sp[2];
|
||||
if ((is->sk.active_flags & AF_Z) && sz->num_pulses) {
|
||||
pre_active = sz->pulses[sz->num_pulses-1].t > pre_active
|
||||
? sz->pulses[sz->num_pulses-1].t : pre_active;
|
||||
post_active = -sz->pulses[0].t > post_active
|
||||
? -sz->pulses[0].t : post_active;
|
||||
}
|
||||
is->sk.gen_steps_pre_active = pre_active;
|
||||
is->sk.gen_steps_post_active = post_active;
|
||||
|
|
@ -188,12 +216,15 @@ 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)
|
||||
int kin_flags = is->orig_sk->active_flags & (AF_X | AF_Y | AF_Z);
|
||||
if (kin_flags == AF_X)
|
||||
is->sk.calc_position_cb = shaper_x_calc_position;
|
||||
else if (is->orig_sk->active_flags & AF_Y)
|
||||
else if (kin_flags == AF_Y)
|
||||
is->sk.calc_position_cb = shaper_y_calc_position;
|
||||
else if (kin_flags == AF_Z)
|
||||
is->sk.calc_position_cb = shaper_z_calc_position;
|
||||
else
|
||||
is->sk.calc_position_cb = shaper_xyz_calc_position;
|
||||
is->sk.active_flags = is->orig_sk->active_flags;
|
||||
shaper_note_generation_time(is);
|
||||
}
|
||||
|
|
@ -207,8 +238,10 @@ input_shaper_set_sk(struct stepper_kinematics *sk
|
|||
is->sk.calc_position_cb = shaper_x_calc_position;
|
||||
else if (orig_sk->active_flags == AF_Y)
|
||||
is->sk.calc_position_cb = shaper_y_calc_position;
|
||||
else if (orig_sk->active_flags & (AF_X | AF_Y))
|
||||
is->sk.calc_position_cb = shaper_xy_calc_position;
|
||||
else if (orig_sk->active_flags == AF_Z)
|
||||
is->sk.calc_position_cb = shaper_z_calc_position;
|
||||
else if (orig_sk->active_flags & (AF_X | AF_Y | AF_Z))
|
||||
is->sk.calc_position_cb = shaper_xyz_calc_position;
|
||||
else
|
||||
return -1;
|
||||
is->sk.active_flags = orig_sk->active_flags;
|
||||
|
|
@ -226,27 +259,20 @@ int __visible
|
|||
input_shaper_set_shaper_params(struct stepper_kinematics *sk, char axis
|
||||
, int n, double a[], double t[])
|
||||
{
|
||||
if (axis != 'x' && axis != 'y')
|
||||
int axis_ind = axis-'x';
|
||||
if (axis_ind < 0 || axis_ind >= ARRAY_SIZE(KIN_FLAGS))
|
||||
return -1;
|
||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||
struct shaper_pulses *sp = axis == 'x' ? &is->sx : &is->sy;
|
||||
struct shaper_pulses *sp = &is->sp[axis_ind];
|
||||
int status = 0;
|
||||
// Ignore input shaper update if the axis is not active
|
||||
if (is->orig_sk->active_flags & (axis == 'x' ? AF_X : AF_Y)) {
|
||||
if (is->orig_sk->active_flags & KIN_FLAGS[axis_ind]) {
|
||||
status = init_shaper(n, a, t, sp);
|
||||
shaper_note_generation_time(is);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
double __visible
|
||||
input_shaper_get_step_generation_window(struct stepper_kinematics *sk)
|
||||
{
|
||||
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
|
||||
return is->sk.gen_steps_pre_active > is->sk.gen_steps_post_active
|
||||
? is->sk.gen_steps_pre_active : is->sk.gen_steps_post_active;
|
||||
}
|
||||
|
||||
struct stepper_kinematics * __visible
|
||||
input_shaper_alloc(void)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -116,6 +116,11 @@ list_join_tail(struct list_head *add, struct list_head *h)
|
|||
; &pos->member != &(head)->root \
|
||||
; pos = list_next_entry(pos, member))
|
||||
|
||||
#define list_for_each_entry_reverse(pos, head, member) \
|
||||
for (pos = list_last_entry((head), typeof(*pos), member) \
|
||||
; &pos->member != &(head)->root \
|
||||
; pos = list_prev_entry(pos, member))
|
||||
|
||||
#define list_for_each_entry_safe(pos, n, head, member) \
|
||||
for (pos = list_first_entry((head), typeof(*pos), member) \
|
||||
, n = list_next_entry(pos, member) \
|
||||
|
|
|
|||
|
|
@ -207,3 +207,14 @@ clock_from_time(struct clock_estimate *ce, double time)
|
|||
{
|
||||
return (int64_t)((time - ce->conv_time)*ce->est_freq + .5) + ce->conv_clock;
|
||||
}
|
||||
|
||||
// Fill the fields of a 'struct clock_estimate'
|
||||
void
|
||||
clock_fill(struct clock_estimate *ce, double est_freq, double conv_time
|
||||
, uint64_t conv_clock, uint64_t last_clock)
|
||||
{
|
||||
ce->est_freq = est_freq;
|
||||
ce->conv_time = conv_time;
|
||||
ce->conv_clock = conv_clock;
|
||||
ce->last_clock = last_clock;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,5 +50,7 @@ void message_queue_free(struct list_head *root);
|
|||
uint64_t clock_from_clock32(struct clock_estimate *ce, uint32_t clock32);
|
||||
double clock_to_time(struct clock_estimate *ce, uint64_t clock);
|
||||
uint64_t clock_from_time(struct clock_estimate *ce, double time);
|
||||
void clock_fill(struct clock_estimate *ce, double est_freq, double conv_time
|
||||
, uint64_t conv_clock, uint64_t last_clock);
|
||||
|
||||
#endif // msgblock.h
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <stdio.h> // fprintf
|
||||
#include <string.h> // strerror
|
||||
#include <time.h> // struct timespec
|
||||
#include <sys/prctl.h> // prctl
|
||||
#include "compiler.h" // __visible
|
||||
#include "pyhelper.h" // get_monotonic
|
||||
|
||||
|
|
@ -92,3 +93,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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Serial port command queuing
|
||||
//
|
||||
// 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.
|
||||
|
||||
|
|
@ -29,24 +29,45 @@
|
|||
#include "pyhelper.h" // get_monotonic
|
||||
#include "serialqueue.h" // struct queue_message
|
||||
|
||||
struct command_queue {
|
||||
struct list_head upcoming_queue, ready_queue;
|
||||
struct message_sub_queue {
|
||||
struct list_head msg_queue;
|
||||
struct list_node node;
|
||||
};
|
||||
|
||||
struct command_queue {
|
||||
struct message_sub_queue ready, upcoming;
|
||||
};
|
||||
|
||||
struct receiver {
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond;
|
||||
int waiting;
|
||||
struct list_head queue;
|
||||
struct list_head old_receive;
|
||||
};
|
||||
|
||||
struct transmit_requests {
|
||||
int pipe_fds[2];
|
||||
pthread_mutex_t lock; // protects variables below
|
||||
struct list_head upcoming_queues;
|
||||
int upcoming_bytes;
|
||||
uint64_t need_kick_clock, min_release_clock;
|
||||
};
|
||||
|
||||
struct serialqueue {
|
||||
// Input reading
|
||||
struct pollreactor *pr;
|
||||
int serial_fd, serial_fd_type, client_id;
|
||||
int pipe_fds[2];
|
||||
uint8_t input_buf[4096];
|
||||
uint8_t need_sync;
|
||||
int input_pos;
|
||||
// Multi-threaded support for pushing and pulling messages
|
||||
struct receiver receiver;
|
||||
struct transmit_requests transmit_requests;
|
||||
// Threading
|
||||
char name[16];
|
||||
pthread_t tid;
|
||||
pthread_mutex_t lock; // protects variables below
|
||||
pthread_cond_t cond;
|
||||
int receive_waiting;
|
||||
// Baud / clock tracking
|
||||
int receive_window;
|
||||
double bittime_adjust, idle_time;
|
||||
|
|
@ -58,18 +79,15 @@ struct serialqueue {
|
|||
struct list_head sent_queue;
|
||||
double srtt, rttvar, rto;
|
||||
// Pending transmission message queues
|
||||
struct list_head pending_queues;
|
||||
int ready_bytes, upcoming_bytes, need_ack_bytes, last_ack_bytes;
|
||||
uint64_t need_kick_clock;
|
||||
struct list_head ready_queues;
|
||||
int ready_bytes, need_ack_bytes, last_ack_bytes;
|
||||
struct list_head notify_queue;
|
||||
double last_write_fail_time;
|
||||
// Received messages
|
||||
struct list_head receive_queue;
|
||||
// Fastreader support
|
||||
pthread_mutex_t fast_reader_dispatch_lock;
|
||||
struct list_head fast_readers;
|
||||
// Debugging
|
||||
struct list_head old_sent, old_receive;
|
||||
struct list_head old_sent;
|
||||
// Stats
|
||||
uint32_t bytes_write, bytes_read, bytes_retransmit, bytes_invalid;
|
||||
};
|
||||
|
|
@ -89,7 +107,7 @@ struct serialqueue {
|
|||
#define MIN_RTO 0.025
|
||||
#define MAX_RTO 5.000
|
||||
#define MAX_PENDING_BLOCKS 12
|
||||
#define MIN_REQTIME_DELTA 0.250
|
||||
#define MIN_REQTIME_DELTA 0.100
|
||||
#define MIN_BACKGROUND_DELTA 0.005
|
||||
#define IDLE_QUERY_TIME 1.0
|
||||
|
||||
|
|
@ -108,31 +126,44 @@ debug_queue_alloc(struct list_head *root, int count)
|
|||
}
|
||||
|
||||
// Copy a message to a debug queue and free old debug messages
|
||||
static void
|
||||
debug_queue_add(struct list_head *root, struct queue_message *qm)
|
||||
static struct queue_message *
|
||||
_debug_queue_add(struct list_head *root, struct queue_message *qm)
|
||||
{
|
||||
list_add_tail(&qm->node, root);
|
||||
struct queue_message *old = list_first_entry(
|
||||
root, struct queue_message, node);
|
||||
list_del(&old->node);
|
||||
return old;
|
||||
}
|
||||
|
||||
static void
|
||||
debug_queue_add(struct list_head *root, struct queue_message *qm)
|
||||
{
|
||||
struct queue_message *old = _debug_queue_add(root, qm);
|
||||
message_free(old);
|
||||
}
|
||||
|
||||
// Wake up the receiver thread if it is waiting
|
||||
// Add messages and wake up the receiver thread if it is waiting
|
||||
static void
|
||||
check_wake_receive(struct serialqueue *sq)
|
||||
receive_append_wake(struct receiver *receiver, struct list_head *msgs)
|
||||
{
|
||||
if (sq->receive_waiting) {
|
||||
sq->receive_waiting = 0;
|
||||
pthread_cond_signal(&sq->cond);
|
||||
int dokick = 0;
|
||||
pthread_mutex_lock(&receiver->lock);
|
||||
list_join_tail(msgs, &receiver->queue);
|
||||
if (receiver->waiting) {
|
||||
receiver->waiting = 0;
|
||||
dokick = 1;
|
||||
}
|
||||
pthread_mutex_unlock(&receiver->lock);
|
||||
if (dokick)
|
||||
pthread_cond_signal(&receiver->cond);
|
||||
}
|
||||
|
||||
// Write to the internal pipe to wake the background thread if in poll
|
||||
static void
|
||||
kick_bg_thread(struct serialqueue *sq)
|
||||
{
|
||||
int ret = write(sq->pipe_fds[1], ".", 1);
|
||||
int ret = write(sq->transmit_requests.pipe_fds[1], ".", 1);
|
||||
if (ret < 0)
|
||||
report_errno("pipe write", ret);
|
||||
}
|
||||
|
|
@ -238,7 +269,8 @@ handle_message(struct serialqueue *sq, double eventtime, int len)
|
|||
sq->bytes_read += len;
|
||||
|
||||
// Check for pending messages on notify_queue
|
||||
int must_wake = 0;
|
||||
struct list_head received;
|
||||
list_init(&received);
|
||||
while (!list_empty(&sq->notify_queue)) {
|
||||
struct queue_message *qm = list_first_entry(
|
||||
&sq->notify_queue, struct queue_message, node);
|
||||
|
|
@ -250,8 +282,7 @@ handle_message(struct serialqueue *sq, double eventtime, int len)
|
|||
qm->len = 0;
|
||||
qm->sent_time = sq->last_receive_sent_time;
|
||||
qm->receive_time = eventtime;
|
||||
list_add_tail(&qm->node, &sq->receive_queue);
|
||||
must_wake = 1;
|
||||
list_add_tail(&qm->node, &received);
|
||||
}
|
||||
|
||||
// Process message
|
||||
|
|
@ -269,10 +300,12 @@ handle_message(struct serialqueue *sq, double eventtime, int len)
|
|||
? sq->last_receive_sent_time : 0.);
|
||||
qm->receive_time = get_monotonic(); // must be time post read()
|
||||
qm->receive_time -= calculate_bittime(sq, len);
|
||||
list_add_tail(&qm->node, &sq->receive_queue);
|
||||
must_wake = 1;
|
||||
list_add_tail(&qm->node, &received);
|
||||
}
|
||||
|
||||
if (!list_empty(&received))
|
||||
receive_append_wake(&sq->receiver, &received);
|
||||
|
||||
// Check fast readers
|
||||
struct fastreader *fr;
|
||||
list_for_each_entry(fr, &sq->fast_readers, node) {
|
||||
|
|
@ -282,16 +315,11 @@ handle_message(struct serialqueue *sq, double eventtime, int len)
|
|||
continue;
|
||||
// Release main lock and invoke callback
|
||||
pthread_mutex_lock(&sq->fast_reader_dispatch_lock);
|
||||
if (must_wake)
|
||||
check_wake_receive(sq);
|
||||
pthread_mutex_unlock(&sq->lock);
|
||||
fr->func(fr, sq->input_buf, len);
|
||||
pthread_mutex_unlock(&sq->fast_reader_dispatch_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
if (must_wake)
|
||||
check_wake_receive(sq);
|
||||
pthread_mutex_unlock(&sq->lock);
|
||||
}
|
||||
|
||||
|
|
@ -350,7 +378,7 @@ static void
|
|||
kick_event(struct serialqueue *sq, double eventtime)
|
||||
{
|
||||
char dummy[4096];
|
||||
int ret = read(sq->pipe_fds[0], dummy, sizeof(dummy));
|
||||
int ret = read(sq->transmit_requests.pipe_fds[0], dummy, sizeof(dummy));
|
||||
if (ret < 0)
|
||||
report_errno("pipe read", ret);
|
||||
pollreactor_update_timer(sq->pr, SQPT_COMMAND, PR_NOW);
|
||||
|
|
@ -451,23 +479,21 @@ build_and_send_command(struct serialqueue *sq, uint8_t *buf, int pending
|
|||
uint64_t min_clock = MAX_CLOCK;
|
||||
struct command_queue *q, *cq = NULL;
|
||||
struct queue_message *qm = NULL;
|
||||
list_for_each_entry(q, &sq->pending_queues, node) {
|
||||
if (!list_empty(&q->ready_queue)) {
|
||||
struct queue_message *m = list_first_entry(
|
||||
&q->ready_queue, struct queue_message, node);
|
||||
if (m->req_clock < min_clock) {
|
||||
min_clock = m->req_clock;
|
||||
cq = q;
|
||||
qm = m;
|
||||
}
|
||||
list_for_each_entry(q, &sq->ready_queues, ready.node) {
|
||||
struct queue_message *m = list_first_entry(
|
||||
&q->ready.msg_queue, struct queue_message, node);
|
||||
if (m->req_clock < min_clock) {
|
||||
min_clock = m->req_clock;
|
||||
cq = q;
|
||||
qm = m;
|
||||
}
|
||||
}
|
||||
// Append message to outgoing command
|
||||
if (len + qm->len > MESSAGE_MAX - MESSAGE_TRAILER_SIZE)
|
||||
break;
|
||||
list_del(&qm->node);
|
||||
if (list_empty(&cq->ready_queue) && list_empty(&cq->upcoming_queue))
|
||||
list_del(&cq->node);
|
||||
if (list_empty(&cq->ready.msg_queue))
|
||||
list_del(&cq->ready.node);
|
||||
memcpy(&buf[len], qm->msg, qm->len);
|
||||
len += qm->len;
|
||||
sq->ready_bytes -= qm->len;
|
||||
|
|
@ -507,74 +533,129 @@ build_and_send_command(struct serialqueue *sq, uint8_t *buf, int pending
|
|||
return len;
|
||||
}
|
||||
|
||||
// Determine the time the next serial data should be sent
|
||||
static double
|
||||
check_send_command(struct serialqueue *sq, int pending, double eventtime)
|
||||
// Move messages from upcoming queues to ready queues
|
||||
static uint64_t
|
||||
check_upcoming_queues(struct serialqueue *sq, uint64_t ack_clock)
|
||||
{
|
||||
if (sq->send_seq - sq->receive_seq >= MAX_PENDING_BLOCKS
|
||||
&& sq->receive_seq != (uint64_t)-1)
|
||||
// Need an ack before more messages can be sent
|
||||
return PR_NEVER;
|
||||
if (sq->send_seq > sq->receive_seq && sq->receive_window) {
|
||||
int need_ack_bytes = sq->need_ack_bytes + MESSAGE_MAX;
|
||||
if (sq->last_ack_seq < sq->receive_seq)
|
||||
need_ack_bytes += sq->last_ack_bytes;
|
||||
if (need_ack_bytes > sq->receive_window)
|
||||
// Wait for ack from past messages before sending next message
|
||||
return PR_NEVER;
|
||||
pthread_mutex_lock(&sq->transmit_requests.lock);
|
||||
sq->transmit_requests.need_kick_clock = 0;
|
||||
uint64_t min_release_clock = sq->transmit_requests.min_release_clock;
|
||||
if (ack_clock < min_release_clock) {
|
||||
pthread_mutex_unlock(&sq->transmit_requests.lock);
|
||||
return min_release_clock;
|
||||
}
|
||||
|
||||
// Check for stalled messages now ready
|
||||
double idletime = eventtime > sq->idle_time ? eventtime : sq->idle_time;
|
||||
idletime += calculate_bittime(sq, pending + MESSAGE_MIN);
|
||||
uint64_t ack_clock = clock_from_time(&sq->ce, idletime);
|
||||
uint64_t min_stalled_clock = MAX_CLOCK, min_ready_clock = MAX_CLOCK;
|
||||
struct command_queue *cq;
|
||||
list_for_each_entry(cq, &sq->pending_queues, node) {
|
||||
// Move messages from the upcoming_queue to the ready_queue
|
||||
while (!list_empty(&cq->upcoming_queue)) {
|
||||
struct queue_message *qm = list_first_entry(
|
||||
&cq->upcoming_queue, struct queue_message, node);
|
||||
uint64_t min_stalled_clock = MAX_CLOCK;
|
||||
struct command_queue *cq, *_ncq;
|
||||
list_for_each_entry_safe(cq, _ncq, &sq->transmit_requests.upcoming_queues,
|
||||
upcoming.node) {
|
||||
int not_in_ready_queues = list_empty(&cq->ready.msg_queue);
|
||||
// Move messages from the upcoming.msg_queue to the ready.msg_queue
|
||||
struct queue_message *qm, *_nqm;
|
||||
list_for_each_entry_safe(qm, _nqm, &cq->upcoming.msg_queue, node) {
|
||||
if (ack_clock < qm->min_clock) {
|
||||
if (qm->min_clock < min_stalled_clock)
|
||||
min_stalled_clock = qm->min_clock;
|
||||
break;
|
||||
}
|
||||
list_del(&qm->node);
|
||||
list_add_tail(&qm->node, &cq->ready_queue);
|
||||
sq->upcoming_bytes -= qm->len;
|
||||
list_add_tail(&qm->node, &cq->ready.msg_queue);
|
||||
sq->transmit_requests.upcoming_bytes -= qm->len;
|
||||
sq->ready_bytes += qm->len;
|
||||
}
|
||||
// Update min_ready_clock
|
||||
if (!list_empty(&cq->ready_queue)) {
|
||||
struct queue_message *qm = list_first_entry(
|
||||
&cq->ready_queue, struct queue_message, node);
|
||||
uint64_t req_clock = qm->req_clock;
|
||||
double bgtime = pending ? idletime : sq->idle_time;
|
||||
double bgoffset = MIN_REQTIME_DELTA + MIN_BACKGROUND_DELTA;
|
||||
if (req_clock == BACKGROUND_PRIORITY_CLOCK)
|
||||
req_clock = clock_from_time(&sq->ce, bgtime + bgoffset);
|
||||
if (req_clock < min_ready_clock)
|
||||
min_ready_clock = req_clock;
|
||||
}
|
||||
// Remove cq from the list if it is now empty
|
||||
if (list_empty(&cq->upcoming.msg_queue))
|
||||
list_del(&cq->upcoming.node);
|
||||
// Add to ready queues
|
||||
if (not_in_ready_queues && !list_empty(&cq->ready.msg_queue))
|
||||
list_add_tail(&cq->ready.node, &sq->ready_queues);
|
||||
}
|
||||
sq->transmit_requests.min_release_clock = min_stalled_clock;
|
||||
pthread_mutex_unlock(&sq->transmit_requests.lock);
|
||||
return min_stalled_clock;
|
||||
}
|
||||
|
||||
// Set the next transmit queue need_kick_clock
|
||||
static int
|
||||
update_need_kick_clock(struct serialqueue *sq, uint64_t wantclock)
|
||||
{
|
||||
pthread_mutex_lock(&sq->transmit_requests.lock);
|
||||
if (wantclock > sq->transmit_requests.min_release_clock) {
|
||||
pthread_mutex_unlock(&sq->transmit_requests.lock);
|
||||
return -1;
|
||||
}
|
||||
sq->transmit_requests.need_kick_clock = wantclock;
|
||||
pthread_mutex_unlock(&sq->transmit_requests.lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Determine if ready to send commands (or the amount of time to sleep if not)
|
||||
static double
|
||||
check_send_command(struct serialqueue *sq, int pending, double eventtime)
|
||||
{
|
||||
// Check for upcoming messages now ready
|
||||
double idletime = eventtime > sq->idle_time ? eventtime : sq->idle_time;
|
||||
idletime += calculate_bittime(sq, pending + MESSAGE_MIN);
|
||||
uint64_t ack_clock = clock_from_time(&sq->ce, idletime);
|
||||
uint64_t min_stalled_clock = check_upcoming_queues(sq, ack_clock);
|
||||
|
||||
// Check if valid to send messages
|
||||
if (sq->send_seq - sq->receive_seq >= MAX_PENDING_BLOCKS
|
||||
&& sq->receive_seq != (uint64_t)-1)
|
||||
// Need an ack before more messages can be sent
|
||||
return eventtime + 0.250;
|
||||
if (sq->send_seq > sq->receive_seq && sq->receive_window) {
|
||||
int need_ack_bytes = sq->need_ack_bytes + MESSAGE_MAX;
|
||||
if (sq->last_ack_seq < sq->receive_seq)
|
||||
need_ack_bytes += sq->last_ack_bytes;
|
||||
if (need_ack_bytes > sq->receive_window)
|
||||
// Wait for ack from past messages before sending next message
|
||||
return eventtime + 0.250;
|
||||
}
|
||||
|
||||
// Check for messages to send
|
||||
// Check if a block is fully ready to send
|
||||
if (sq->ready_bytes >= MESSAGE_PAYLOAD_MAX)
|
||||
return PR_NOW;
|
||||
if (! sq->ce.est_freq) {
|
||||
// Clock unknown during initial startup - recheck on each add
|
||||
if (sq->ready_bytes)
|
||||
return PR_NOW;
|
||||
sq->need_kick_clock = MAX_CLOCK;
|
||||
int mustwake = update_need_kick_clock(sq, 1);
|
||||
if (mustwake)
|
||||
return eventtime;
|
||||
return PR_NEVER;
|
||||
}
|
||||
|
||||
// Check if it is still needed to send messages from the ready_queues
|
||||
uint64_t min_ready_clock = MAX_CLOCK;
|
||||
struct command_queue *cq;
|
||||
list_for_each_entry(cq, &sq->ready_queues, ready.node) {
|
||||
// Update min_ready_clock
|
||||
struct queue_message *qm = list_first_entry(
|
||||
&cq->ready.msg_queue, struct queue_message, node);
|
||||
uint64_t req_clock = qm->req_clock;
|
||||
double bgtime = pending ? idletime : sq->idle_time;
|
||||
double bgoffset = MIN_REQTIME_DELTA + MIN_BACKGROUND_DELTA;
|
||||
if (req_clock == BACKGROUND_PRIORITY_CLOCK)
|
||||
req_clock = clock_from_time(&sq->ce, bgtime + bgoffset);
|
||||
if (req_clock < min_ready_clock)
|
||||
min_ready_clock = req_clock;
|
||||
}
|
||||
uint64_t reqclock_delta = MIN_REQTIME_DELTA * sq->ce.est_freq;
|
||||
if (min_ready_clock <= ack_clock + reqclock_delta)
|
||||
return PR_NOW;
|
||||
|
||||
// Determine next wakeup time
|
||||
if (pending)
|
||||
// Caller wont sleep anyway - just return
|
||||
return eventtime;
|
||||
uint64_t wantclock = min_ready_clock - reqclock_delta;
|
||||
if (min_stalled_clock < wantclock)
|
||||
wantclock = min_stalled_clock;
|
||||
sq->need_kick_clock = wantclock;
|
||||
int mustwake = update_need_kick_clock(sq, wantclock);
|
||||
if (mustwake)
|
||||
// Raced with add of new command - avoid sleeping
|
||||
return eventtime;
|
||||
return idletime + (wantclock - ack_clock) / sq->ce.est_freq;
|
||||
}
|
||||
|
||||
|
|
@ -588,20 +669,19 @@ command_event(struct serialqueue *sq, double eventtime)
|
|||
double waketime;
|
||||
for (;;) {
|
||||
waketime = check_send_command(sq, buflen, eventtime);
|
||||
if (waketime != PR_NOW || buflen + MESSAGE_MAX > sizeof(buf)) {
|
||||
if (buflen) {
|
||||
// Write message blocks
|
||||
do_write(sq, buf, buflen);
|
||||
sq->bytes_write += buflen;
|
||||
double idletime = (eventtime > sq->idle_time
|
||||
? eventtime : sq->idle_time);
|
||||
sq->idle_time = idletime + calculate_bittime(sq, buflen);
|
||||
buflen = 0;
|
||||
}
|
||||
if (waketime != PR_NOW)
|
||||
break;
|
||||
}
|
||||
if (waketime != PR_NOW)
|
||||
break;
|
||||
buflen += build_and_send_command(sq, &buf[buflen], buflen, eventtime);
|
||||
if (buflen + MESSAGE_MAX > sizeof(buf))
|
||||
break;
|
||||
}
|
||||
if (buflen) {
|
||||
// Write message blocks
|
||||
do_write(sq, buf, buflen);
|
||||
sq->bytes_write += buflen;
|
||||
double idletime = eventtime > sq->idle_time ? eventtime : sq->idle_time;
|
||||
sq->idle_time = idletime + calculate_bittime(sq, buflen);
|
||||
waketime = PR_NOW;
|
||||
}
|
||||
pthread_mutex_unlock(&sq->lock);
|
||||
return waketime;
|
||||
|
|
@ -612,26 +692,31 @@ static void *
|
|||
background_thread(void *data)
|
||||
{
|
||||
struct serialqueue *sq = data;
|
||||
set_thread_name(sq->name);
|
||||
pollreactor_run(sq->pr);
|
||||
|
||||
pthread_mutex_lock(&sq->lock);
|
||||
check_wake_receive(sq);
|
||||
pthread_mutex_unlock(&sq->lock);
|
||||
// Wake any waiting receivers
|
||||
struct list_head dummy;
|
||||
list_init(&dummy);
|
||||
receive_append_wake(&sq->receiver, &dummy);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 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);
|
||||
int ret = pipe(sq->transmit_requests.pipe_fds);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
|
|
@ -639,12 +724,13 @@ serialqueue_alloc(int serial_fd, char serial_fd_type, int client_id)
|
|||
sq->pr = pollreactor_alloc(SQPF_NUM, SQPT_NUM, sq);
|
||||
pollreactor_add_fd(sq->pr, SQPF_SERIAL, serial_fd, input_event
|
||||
, serial_fd_type==SQT_DEBUGFILE);
|
||||
pollreactor_add_fd(sq->pr, SQPF_PIPE, sq->pipe_fds[0], kick_event, 0);
|
||||
pollreactor_add_fd(sq->pr, SQPF_PIPE, sq->transmit_requests.pipe_fds[0]
|
||||
, kick_event, 0);
|
||||
pollreactor_add_timer(sq->pr, SQPT_RETRANSMIT, retransmit_event);
|
||||
pollreactor_add_timer(sq->pr, SQPT_COMMAND, command_event);
|
||||
fd_set_non_blocking(serial_fd);
|
||||
fd_set_non_blocking(sq->pipe_fds[0]);
|
||||
fd_set_non_blocking(sq->pipe_fds[1]);
|
||||
fd_set_non_blocking(sq->transmit_requests.pipe_fds[0]);
|
||||
fd_set_non_blocking(sq->transmit_requests.pipe_fds[1]);
|
||||
|
||||
// Retransmit setup
|
||||
sq->send_seq = 1;
|
||||
|
|
@ -658,24 +744,30 @@ serialqueue_alloc(int serial_fd, char serial_fd_type, int client_id)
|
|||
}
|
||||
|
||||
// Queues
|
||||
sq->need_kick_clock = MAX_CLOCK;
|
||||
list_init(&sq->pending_queues);
|
||||
sq->transmit_requests.need_kick_clock = MAX_CLOCK;
|
||||
sq->transmit_requests.min_release_clock = MAX_CLOCK;
|
||||
list_init(&sq->transmit_requests.upcoming_queues);
|
||||
pthread_mutex_init(&sq->transmit_requests.lock, NULL);
|
||||
list_init(&sq->ready_queues);
|
||||
list_init(&sq->sent_queue);
|
||||
list_init(&sq->receive_queue);
|
||||
list_init(&sq->receiver.queue);
|
||||
list_init(&sq->notify_queue);
|
||||
list_init(&sq->fast_readers);
|
||||
|
||||
// Debugging
|
||||
list_init(&sq->old_sent);
|
||||
list_init(&sq->old_receive);
|
||||
list_init(&sq->receiver.old_receive);
|
||||
debug_queue_alloc(&sq->old_sent, DEBUG_QUEUE_SENT);
|
||||
debug_queue_alloc(&sq->old_receive, DEBUG_QUEUE_RECEIVE);
|
||||
debug_queue_alloc(&sq->receiver.old_receive, DEBUG_QUEUE_RECEIVE);
|
||||
|
||||
// Thread setup
|
||||
ret = pthread_mutex_init(&sq->lock, NULL);
|
||||
if (ret)
|
||||
goto fail;
|
||||
ret = pthread_cond_init(&sq->cond, NULL);
|
||||
ret = pthread_mutex_init(&sq->receiver.lock, NULL);
|
||||
if (ret)
|
||||
goto fail;
|
||||
ret = pthread_cond_init(&sq->receiver.cond, NULL);
|
||||
if (ret)
|
||||
goto fail;
|
||||
ret = pthread_mutex_init(&sq->fast_reader_dispatch_lock, NULL);
|
||||
|
|
@ -713,17 +805,27 @@ serialqueue_free(struct serialqueue *sq)
|
|||
serialqueue_exit(sq);
|
||||
pthread_mutex_lock(&sq->lock);
|
||||
message_queue_free(&sq->sent_queue);
|
||||
message_queue_free(&sq->receive_queue);
|
||||
pthread_mutex_lock(&sq->receiver.lock);
|
||||
message_queue_free(&sq->receiver.queue);
|
||||
message_queue_free(&sq->receiver.old_receive);
|
||||
pthread_mutex_unlock(&sq->receiver.lock);
|
||||
message_queue_free(&sq->notify_queue);
|
||||
message_queue_free(&sq->old_sent);
|
||||
message_queue_free(&sq->old_receive);
|
||||
while (!list_empty(&sq->pending_queues)) {
|
||||
struct command_queue *cq = list_first_entry(
|
||||
&sq->pending_queues, struct command_queue, node);
|
||||
list_del(&cq->node);
|
||||
message_queue_free(&cq->ready_queue);
|
||||
message_queue_free(&cq->upcoming_queue);
|
||||
while (!list_empty(&sq->ready_queues)) {
|
||||
struct command_queue* cq = list_first_entry(
|
||||
&sq->ready_queues, struct command_queue, ready.node);
|
||||
list_del(&cq->ready.node);
|
||||
message_queue_free(&cq->ready.msg_queue);
|
||||
}
|
||||
pthread_mutex_lock(&sq->transmit_requests.lock);
|
||||
while (!list_empty(&sq->transmit_requests.upcoming_queues)) {
|
||||
struct command_queue *cq = list_first_entry(
|
||||
&sq->transmit_requests.upcoming_queues,
|
||||
struct command_queue, upcoming.node);
|
||||
list_del(&cq->upcoming.node);
|
||||
message_queue_free(&cq->upcoming.msg_queue);
|
||||
}
|
||||
pthread_mutex_unlock(&sq->transmit_requests.lock);
|
||||
pthread_mutex_unlock(&sq->lock);
|
||||
pollreactor_free(sq->pr);
|
||||
free(sq);
|
||||
|
|
@ -735,8 +837,8 @@ serialqueue_alloc_commandqueue(void)
|
|||
{
|
||||
struct command_queue *cq = malloc(sizeof(*cq));
|
||||
memset(cq, 0, sizeof(*cq));
|
||||
list_init(&cq->ready_queue);
|
||||
list_init(&cq->upcoming_queue);
|
||||
list_init(&cq->ready.msg_queue);
|
||||
list_init(&cq->upcoming.msg_queue);
|
||||
return cq;
|
||||
}
|
||||
|
||||
|
|
@ -746,7 +848,8 @@ serialqueue_free_commandqueue(struct command_queue *cq)
|
|||
{
|
||||
if (!cq)
|
||||
return;
|
||||
if (!list_empty(&cq->ready_queue) || !list_empty(&cq->upcoming_queue)) {
|
||||
if (!list_empty(&cq->ready.msg_queue) ||
|
||||
!list_empty(&cq->upcoming.msg_queue)) {
|
||||
errorf("Memory leak! Can't free non-empty commandqueue");
|
||||
return;
|
||||
}
|
||||
|
|
@ -783,27 +886,33 @@ serialqueue_send_batch(struct serialqueue *sq, struct command_queue *cq
|
|||
int len = 0;
|
||||
struct queue_message *qm;
|
||||
list_for_each_entry(qm, msgs, node) {
|
||||
if (qm->min_clock + (1LL<<31) < qm->req_clock
|
||||
if (qm->min_clock + (3LL<<29) < qm->req_clock
|
||||
&& qm->req_clock != BACKGROUND_PRIORITY_CLOCK)
|
||||
qm->min_clock = qm->req_clock - (1LL<<31);
|
||||
// Avoid mcu clock comparison 31-bit overflow issues
|
||||
qm->min_clock = qm->req_clock - (3LL<<29);
|
||||
len += qm->len;
|
||||
}
|
||||
if (! len)
|
||||
return;
|
||||
qm = list_first_entry(msgs, struct queue_message, node);
|
||||
uint64_t min_clock = qm->min_clock;
|
||||
|
||||
// Add list to cq->upcoming_queue
|
||||
pthread_mutex_lock(&sq->lock);
|
||||
if (list_empty(&cq->ready_queue) && list_empty(&cq->upcoming_queue))
|
||||
list_add_tail(&cq->node, &sq->pending_queues);
|
||||
list_join_tail(msgs, &cq->upcoming_queue);
|
||||
sq->upcoming_bytes += len;
|
||||
int mustwake = 0;
|
||||
if (qm->min_clock < sq->need_kick_clock) {
|
||||
sq->need_kick_clock = 0;
|
||||
mustwake = 1;
|
||||
pthread_mutex_lock(&sq->transmit_requests.lock);
|
||||
if (list_empty(&cq->upcoming.msg_queue)) {
|
||||
list_add_tail(&cq->upcoming.node,
|
||||
&sq->transmit_requests.upcoming_queues);
|
||||
if (min_clock < sq->transmit_requests.min_release_clock)
|
||||
sq->transmit_requests.min_release_clock = min_clock;
|
||||
if (min_clock < sq->transmit_requests.need_kick_clock) {
|
||||
sq->transmit_requests.need_kick_clock = 0;
|
||||
mustwake = 1;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&sq->lock);
|
||||
list_join_tail(msgs, &cq->upcoming.msg_queue);
|
||||
sq->transmit_requests.upcoming_bytes += len;
|
||||
pthread_mutex_unlock(&sq->transmit_requests.lock);
|
||||
|
||||
// Wake the background thread if necessary
|
||||
if (mustwake)
|
||||
|
|
@ -840,20 +949,21 @@ serialqueue_send(struct serialqueue *sq, struct command_queue *cq, uint8_t *msg
|
|||
void __visible
|
||||
serialqueue_pull(struct serialqueue *sq, struct pull_queue_message *pqm)
|
||||
{
|
||||
pthread_mutex_lock(&sq->lock);
|
||||
struct receiver *receiver = &sq->receiver;
|
||||
pthread_mutex_lock(&receiver->lock);
|
||||
// Wait for message to be available
|
||||
while (list_empty(&sq->receive_queue)) {
|
||||
while (list_empty(&receiver->queue)) {
|
||||
if (pollreactor_is_exit(sq->pr))
|
||||
goto exit;
|
||||
sq->receive_waiting = 1;
|
||||
int ret = pthread_cond_wait(&sq->cond, &sq->lock);
|
||||
receiver->waiting = 1;
|
||||
int ret = pthread_cond_wait(&receiver->cond, &receiver->lock);
|
||||
if (ret)
|
||||
report_errno("pthread_cond_wait", ret);
|
||||
}
|
||||
|
||||
// Remove message from queue
|
||||
struct queue_message *qm = list_first_entry(
|
||||
&sq->receive_queue, struct queue_message, node);
|
||||
&receiver->queue, struct queue_message, node);
|
||||
list_del(&qm->node);
|
||||
|
||||
// Copy message
|
||||
|
|
@ -863,16 +973,14 @@ serialqueue_pull(struct serialqueue *sq, struct pull_queue_message *pqm)
|
|||
pqm->receive_time = qm->receive_time;
|
||||
pqm->notify_id = qm->notify_id;
|
||||
if (qm->len)
|
||||
debug_queue_add(&sq->old_receive, qm);
|
||||
else
|
||||
message_free(qm);
|
||||
|
||||
pthread_mutex_unlock(&sq->lock);
|
||||
qm = _debug_queue_add(&receiver->old_receive, qm);
|
||||
pthread_mutex_unlock(&receiver->lock);
|
||||
message_free(qm);
|
||||
return;
|
||||
|
||||
exit:
|
||||
pqm->len = -1;
|
||||
pthread_mutex_unlock(&sq->lock);
|
||||
pthread_mutex_unlock(&receiver->lock);
|
||||
}
|
||||
|
||||
void __visible
|
||||
|
|
@ -904,10 +1012,7 @@ serialqueue_set_clock_est(struct serialqueue *sq, double est_freq
|
|||
, uint64_t last_clock)
|
||||
{
|
||||
pthread_mutex_lock(&sq->lock);
|
||||
sq->ce.est_freq = est_freq;
|
||||
sq->ce.conv_time = conv_time;
|
||||
sq->ce.conv_clock = conv_clock;
|
||||
sq->ce.last_clock = last_clock;
|
||||
clock_fill(&sq->ce, est_freq, conv_time, conv_clock, last_clock);
|
||||
pthread_mutex_unlock(&sq->lock);
|
||||
}
|
||||
|
||||
|
|
@ -926,7 +1031,9 @@ serialqueue_get_stats(struct serialqueue *sq, char *buf, int len)
|
|||
{
|
||||
struct serialqueue stats;
|
||||
pthread_mutex_lock(&sq->lock);
|
||||
pthread_mutex_lock(&sq->transmit_requests.lock);
|
||||
memcpy(&stats, sq, sizeof(stats));
|
||||
pthread_mutex_unlock(&sq->transmit_requests.lock);
|
||||
pthread_mutex_unlock(&sq->lock);
|
||||
|
||||
snprintf(buf, len, "bytes_write=%u bytes_read=%u"
|
||||
|
|
@ -939,7 +1046,7 @@ serialqueue_get_stats(struct serialqueue *sq, char *buf, int len)
|
|||
, (int)stats.send_seq, (int)stats.receive_seq
|
||||
, (int)stats.retransmit_seq
|
||||
, stats.srtt, stats.rttvar, stats.rto
|
||||
, stats.ready_bytes, stats.upcoming_bytes);
|
||||
, stats.ready_bytes, stats.transmit_requests.upcoming_bytes);
|
||||
}
|
||||
|
||||
// Extract old messages stored in the debug queues
|
||||
|
|
@ -948,18 +1055,25 @@ serialqueue_extract_old(struct serialqueue *sq, int sentq
|
|||
, struct pull_queue_message *q, int max)
|
||||
{
|
||||
int count = sentq ? DEBUG_QUEUE_SENT : DEBUG_QUEUE_RECEIVE;
|
||||
struct list_head *rootp = sentq ? &sq->old_sent : &sq->old_receive;
|
||||
struct list_head replacement, current;
|
||||
list_init(&replacement);
|
||||
debug_queue_alloc(&replacement, count);
|
||||
list_init(¤t);
|
||||
|
||||
// Atomically replace existing debug list with new zero'd list
|
||||
pthread_mutex_lock(&sq->lock);
|
||||
list_join_tail(rootp, ¤t);
|
||||
list_init(rootp);
|
||||
list_join_tail(&replacement, rootp);
|
||||
pthread_mutex_unlock(&sq->lock);
|
||||
if (sentq) {
|
||||
pthread_mutex_lock(&sq->lock);
|
||||
list_join_tail(&sq->old_sent, ¤t);
|
||||
list_init(&sq->old_sent);
|
||||
list_join_tail(&replacement, &sq->old_sent);
|
||||
pthread_mutex_unlock(&sq->lock);
|
||||
} else {
|
||||
pthread_mutex_lock(&sq->receiver.lock);
|
||||
list_join_tail(&sq->receiver.old_receive, ¤t);
|
||||
list_init(&sq->receiver.old_receive);
|
||||
list_join_tail(&replacement, &sq->receiver.old_receive);
|
||||
pthread_mutex_unlock(&sq->receiver.lock);
|
||||
}
|
||||
|
||||
// Walk the debug list
|
||||
int pos = 0;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -28,15 +28,22 @@
|
|||
#define CHECK_LINES 1
|
||||
#define QUEUE_START_SIZE 1024
|
||||
|
||||
// Storage for queuing steps (only lower 32 bits of step clock are stored as
|
||||
// optimization to reduce memory, improve cache usage, and reduce 64 bit ops)
|
||||
struct qstep {
|
||||
uint32_t clock32;
|
||||
};
|
||||
|
||||
// Main stepcompress object storage
|
||||
struct stepcompress {
|
||||
// Buffer management
|
||||
uint32_t *queue, *queue_end, *queue_pos, *queue_next;
|
||||
struct qstep *queue, *queue_end, *queue_pos, *queue_next;
|
||||
// Internal tracking
|
||||
uint32_t max_error;
|
||||
double mcu_time_offset, mcu_freq, last_step_print_time;
|
||||
// Message generation
|
||||
uint64_t last_step_clock;
|
||||
struct list_head msg_queue;
|
||||
struct list_head *msg_queue;
|
||||
uint32_t oid;
|
||||
int32_t queue_step_msgtag, set_next_step_dir_msgtag;
|
||||
int sdir, invert_sdir;
|
||||
|
|
@ -48,12 +55,14 @@ struct stepcompress {
|
|||
struct list_head history_list;
|
||||
};
|
||||
|
||||
// Parameters of a single queue_step command
|
||||
struct step_move {
|
||||
uint32_t interval;
|
||||
uint16_t count;
|
||||
int16_t add;
|
||||
};
|
||||
|
||||
// Storage for internal history of recently sent queue_step commands
|
||||
struct history_steps {
|
||||
struct list_node node;
|
||||
uint64_t first_clock, last_clock;
|
||||
|
|
@ -85,10 +94,10 @@ struct points {
|
|||
// Given a requested step time, return the minimum and maximum
|
||||
// acceptable times
|
||||
static inline struct points
|
||||
minmax_point(struct stepcompress *sc, uint32_t *pos)
|
||||
minmax_point(struct stepcompress *sc, struct qstep *pos)
|
||||
{
|
||||
uint32_t lsc = sc->last_step_clock, point = *pos - lsc;
|
||||
uint32_t prevpoint = pos > sc->queue_pos ? *(pos-1) - lsc : 0;
|
||||
uint32_t lsc = sc->last_step_clock, point = pos->clock32 - lsc;
|
||||
uint32_t prevpoint = pos > sc->queue_pos ? (pos-1)->clock32 - lsc : 0;
|
||||
uint32_t max_error = (point - prevpoint) / 2;
|
||||
if (max_error > sc->max_error)
|
||||
max_error = sc->max_error;
|
||||
|
|
@ -105,7 +114,7 @@ minmax_point(struct stepcompress *sc, uint32_t *pos)
|
|||
static struct step_move
|
||||
compress_bisect_add(struct stepcompress *sc)
|
||||
{
|
||||
uint32_t *qlast = sc->queue_next;
|
||||
struct qstep *qlast = sc->queue_next;
|
||||
if (qlast > sc->queue_pos + 65535)
|
||||
qlast = sc->queue_pos + 65535;
|
||||
struct points point = minmax_point(sc, sc->queue_pos);
|
||||
|
|
@ -242,23 +251,23 @@ check_line(struct stepcompress *sc, struct step_move move)
|
|||
****************************************************************/
|
||||
|
||||
// Allocate a new 'stepcompress' object
|
||||
struct stepcompress * __visible
|
||||
stepcompress_alloc(uint32_t oid)
|
||||
struct stepcompress *
|
||||
stepcompress_alloc(struct list_head *msg_queue)
|
||||
{
|
||||
struct stepcompress *sc = malloc(sizeof(*sc));
|
||||
memset(sc, 0, sizeof(*sc));
|
||||
list_init(&sc->msg_queue);
|
||||
list_init(&sc->history_list);
|
||||
sc->oid = oid;
|
||||
sc->sdir = -1;
|
||||
sc->msg_queue = msg_queue;
|
||||
return sc;
|
||||
}
|
||||
|
||||
// Fill message id information
|
||||
void __visible
|
||||
stepcompress_fill(struct stepcompress *sc, uint32_t max_error
|
||||
stepcompress_fill(struct stepcompress *sc, uint32_t oid, uint32_t max_error
|
||||
, int32_t queue_step_msgtag, int32_t set_next_step_dir_msgtag)
|
||||
{
|
||||
sc->oid = oid;
|
||||
sc->max_error = max_error;
|
||||
sc->queue_step_msgtag = queue_step_msgtag;
|
||||
sc->set_next_step_dir_msgtag = set_next_step_dir_msgtag;
|
||||
|
|
@ -276,9 +285,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,22 +299,14 @@ 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
|
||||
void
|
||||
stepcompress_free(struct stepcompress *sc)
|
||||
{
|
||||
if (!sc)
|
||||
return;
|
||||
free(sc->queue);
|
||||
message_queue_free(&sc->msg_queue);
|
||||
free_history(sc, UINT64_MAX);
|
||||
stepcompress_history_expire(sc, UINT64_MAX);
|
||||
free(sc);
|
||||
}
|
||||
|
||||
|
|
@ -330,7 +331,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)
|
||||
{
|
||||
|
|
@ -358,7 +359,7 @@ add_move(struct stepcompress *sc, uint64_t first_clock, struct step_move *move)
|
|||
qm->min_clock = qm->req_clock = sc->last_step_clock;
|
||||
if (move->count == 1 && first_clock >= sc->last_step_clock + CLOCK_DIFF_MAX)
|
||||
qm->req_clock = first_clock;
|
||||
list_add_tail(&qm->node, &sc->msg_queue);
|
||||
list_add_tail(&qm->node, sc->msg_queue);
|
||||
sc->last_step_clock = last_clock;
|
||||
|
||||
// Create and store move in history tracking
|
||||
|
|
@ -422,7 +423,7 @@ set_next_step_dir(struct stepcompress *sc, int sdir)
|
|||
};
|
||||
struct queue_message *qm = message_alloc_and_encode(msg, 3);
|
||||
qm->req_clock = sc->last_step_clock;
|
||||
list_add_tail(&qm->node, &sc->msg_queue);
|
||||
list_add_tail(&qm->node, sc->msg_queue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -437,7 +438,8 @@ queue_append_far(struct stepcompress *sc)
|
|||
return ret;
|
||||
if (step_clock >= sc->last_step_clock + CLOCK_DIFF_MAX)
|
||||
return stepcompress_flush_far(sc, step_clock);
|
||||
*sc->queue_next++ = step_clock;
|
||||
sc->queue_next->clock32 = step_clock;
|
||||
sc->queue_next++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -447,7 +449,7 @@ queue_append_extend(struct stepcompress *sc)
|
|||
{
|
||||
if (sc->queue_next - sc->queue_pos > 65535 + 2000) {
|
||||
// No point in keeping more than 64K steps in memory
|
||||
uint32_t flush = (*(sc->queue_next-65535)
|
||||
uint32_t flush = ((sc->queue_next-65535)->clock32
|
||||
- (uint32_t)sc->last_step_clock);
|
||||
int ret = queue_flush(sc, sc->last_step_clock + flush);
|
||||
if (ret)
|
||||
|
|
@ -474,7 +476,8 @@ queue_append_extend(struct stepcompress *sc)
|
|||
sc->queue_next = sc->queue + in_use;
|
||||
}
|
||||
|
||||
*sc->queue_next++ = sc->next_step_clock;
|
||||
sc->queue_next->clock32 = sc->next_step_clock;
|
||||
sc->queue_next++;
|
||||
sc->next_step_clock = 0;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -492,7 +495,8 @@ queue_append(struct stepcompress *sc)
|
|||
return queue_append_far(sc);
|
||||
if (unlikely(sc->queue_next >= sc->queue_end))
|
||||
return queue_append_extend(sc);
|
||||
*sc->queue_next++ = sc->next_step_clock;
|
||||
sc->queue_next->clock32 = sc->next_step_clock;
|
||||
sc->queue_next++;
|
||||
sc->next_step_clock = 0;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -539,7 +543,7 @@ stepcompress_commit(struct stepcompress *sc)
|
|||
}
|
||||
|
||||
// Flush pending steps
|
||||
static int
|
||||
int
|
||||
stepcompress_flush(struct stepcompress *sc, uint64_t move_clock)
|
||||
{
|
||||
if (sc->next_step_clock && move_clock >= sc->next_step_clock) {
|
||||
|
|
@ -611,35 +615,6 @@ stepcompress_find_past_position(struct stepcompress *sc, uint64_t clock)
|
|||
return last_position;
|
||||
}
|
||||
|
||||
// Queue an mcu command to go out in order with stepper commands
|
||||
int __visible
|
||||
stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len)
|
||||
{
|
||||
int ret = stepcompress_flush(sc, UINT64_MAX);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
struct queue_message *qm = message_alloc_and_encode(data, len);
|
||||
qm->req_clock = sc->last_step_clock;
|
||||
list_add_tail(&qm->node, &sc->msg_queue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Queue an mcu command that will consume space in the mcu move queue
|
||||
int __visible
|
||||
stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock
|
||||
, uint32_t *data, int len)
|
||||
{
|
||||
int ret = stepcompress_flush(sc, UINT64_MAX);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
struct queue_message *qm = message_alloc_and_encode(data, len);
|
||||
qm->min_clock = qm->req_clock = req_clock;
|
||||
list_add_tail(&qm->node, &sc->msg_queue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Return history of queue_step commands
|
||||
int __visible
|
||||
stepcompress_extract_old(struct stepcompress *sc, struct pull_history_steps *p
|
||||
|
|
@ -663,165 +638,3 @@ 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
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,38 +11,30 @@ struct pull_history_steps {
|
|||
int step_count, interval, add;
|
||||
};
|
||||
|
||||
struct stepcompress *stepcompress_alloc(uint32_t oid);
|
||||
void stepcompress_fill(struct stepcompress *sc, uint32_t max_error
|
||||
struct list_head;
|
||||
struct stepcompress *stepcompress_alloc(struct list_head *msg_queue);
|
||||
void stepcompress_fill(struct stepcompress *sc, uint32_t oid, uint32_t max_error
|
||||
, int32_t queue_step_msgtag
|
||||
, 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);
|
||||
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);
|
||||
int stepcompress_flush(struct stepcompress *sc, uint64_t move_clock);
|
||||
int stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock);
|
||||
int stepcompress_set_last_position(struct stepcompress *sc, uint64_t clock
|
||||
, int64_t last_position);
|
||||
int64_t stepcompress_find_past_position(struct stepcompress *sc
|
||||
, uint64_t clock);
|
||||
int stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len);
|
||||
int stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock
|
||||
, uint32_t *data, int len);
|
||||
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);
|
||||
|
||||
#endif // stepcompress.h
|
||||
|
|
|
|||
436
klippy/chelper/steppersync.c
Normal file
436
klippy/chelper/steppersync.c
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
// 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 <pthread.h> // pthread_mutex_lock
|
||||
#include <stddef.h> // offsetof
|
||||
#include <stdlib.h> // malloc
|
||||
#include <string.h> // memset
|
||||
#include "compiler.h" // __visible
|
||||
#include "pyhelper.h" // set_thread_name
|
||||
#include "itersolve.h" // itersolve_generate_steps
|
||||
#include "serialqueue.h" // struct queue_message
|
||||
#include "stepcompress.h" // stepcompress_flush
|
||||
#include "steppersync.h" // steppersync_alloc
|
||||
#include "trapq.h" // trapq_check_sentinels
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* SyncEmitter - message generation for each stepper
|
||||
****************************************************************/
|
||||
|
||||
struct syncemitter {
|
||||
// List node for storage in steppersync list
|
||||
struct list_node ss_node;
|
||||
// Transmit message queue
|
||||
struct list_head msg_queue;
|
||||
// Thread for step generation
|
||||
struct stepcompress *sc;
|
||||
struct stepper_kinematics *sk;
|
||||
char name[16];
|
||||
pthread_t tid;
|
||||
pthread_mutex_t lock; // protects variables below
|
||||
pthread_cond_t cond;
|
||||
int have_work;
|
||||
double bg_gen_steps_time;
|
||||
uint64_t bg_flush_clock, bg_clear_history_clock;
|
||||
int32_t bg_result;
|
||||
};
|
||||
|
||||
// Return this emitters 'struct stepcompress' (or NULL if not allocated)
|
||||
struct stepcompress * __visible
|
||||
syncemitter_get_stepcompress(struct syncemitter *se)
|
||||
{
|
||||
return se->sc;
|
||||
}
|
||||
|
||||
// Store a reference to stepper_kinematics
|
||||
void __visible
|
||||
syncemitter_set_stepper_kinematics(struct syncemitter *se
|
||||
, struct stepper_kinematics *sk)
|
||||
{
|
||||
se->sk = sk;
|
||||
}
|
||||
|
||||
// Report current stepper_kinematics
|
||||
struct stepper_kinematics * __visible
|
||||
syncemitter_get_stepper_kinematics(struct syncemitter *se)
|
||||
{
|
||||
return se->sk;
|
||||
}
|
||||
|
||||
// Queue an mcu command that will consume space in the mcu move queue
|
||||
void __visible
|
||||
syncemitter_queue_msg(struct syncemitter *se, uint64_t req_clock
|
||||
, uint32_t *data, int len)
|
||||
{
|
||||
struct queue_message *qm = message_alloc_and_encode(data, len);
|
||||
qm->min_clock = qm->req_clock = req_clock;
|
||||
list_add_tail(&qm->node, &se->msg_queue);
|
||||
}
|
||||
|
||||
// Generate steps (via itersolve) and flush
|
||||
static int32_t
|
||||
se_generate_steps(struct syncemitter *se)
|
||||
{
|
||||
if (!se->sc || !se->sk)
|
||||
return 0;
|
||||
double gen_steps_time = se->bg_gen_steps_time;
|
||||
uint64_t flush_clock = se->bg_flush_clock;
|
||||
uint64_t clear_history_clock = se->bg_clear_history_clock;
|
||||
// Generate steps
|
||||
int32_t ret = itersolve_generate_steps(se->sk, se->sc, gen_steps_time);
|
||||
if (ret)
|
||||
return ret;
|
||||
// Flush steps
|
||||
ret = stepcompress_flush(se->sc, flush_clock);
|
||||
if (ret)
|
||||
return ret;
|
||||
// Clear history
|
||||
stepcompress_history_expire(se->sc, clear_history_clock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Main background thread for generating steps
|
||||
static void *
|
||||
se_background_thread(void *data)
|
||||
{
|
||||
struct syncemitter *se = data;
|
||||
set_thread_name(se->name);
|
||||
|
||||
pthread_mutex_lock(&se->lock);
|
||||
for (;;) {
|
||||
if (!se->have_work) {
|
||||
pthread_cond_wait(&se->cond, &se->lock);
|
||||
continue;
|
||||
}
|
||||
if (se->have_work < 0)
|
||||
// Exit request
|
||||
break;
|
||||
|
||||
// Request to generate steps
|
||||
se->bg_result = se_generate_steps(se);
|
||||
if (se->bg_result)
|
||||
errorf("Error in syncemitter '%s' step generation", se->name);
|
||||
se->have_work = 0;
|
||||
pthread_cond_signal(&se->cond);
|
||||
}
|
||||
pthread_mutex_unlock(&se->lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Signal background thread to start step generation
|
||||
static void
|
||||
se_start_gen_steps(struct syncemitter *se, double gen_steps_time
|
||||
, uint64_t flush_clock, uint64_t clear_history_clock)
|
||||
{
|
||||
if (!se->sc || !se->sk)
|
||||
return;
|
||||
pthread_mutex_lock(&se->lock);
|
||||
while (se->have_work)
|
||||
pthread_cond_wait(&se->cond, &se->lock);
|
||||
se->bg_gen_steps_time = gen_steps_time;
|
||||
se->bg_flush_clock = flush_clock;
|
||||
se->bg_clear_history_clock = clear_history_clock;
|
||||
se->have_work = 1;
|
||||
pthread_mutex_unlock(&se->lock);
|
||||
pthread_cond_signal(&se->cond);
|
||||
}
|
||||
|
||||
// Wait for background thread to complete last step generation request
|
||||
static int32_t
|
||||
se_finalize_gen_steps(struct syncemitter *se)
|
||||
{
|
||||
if (!se->sc || !se->sk)
|
||||
return 0;
|
||||
pthread_mutex_lock(&se->lock);
|
||||
while (se->have_work)
|
||||
pthread_cond_wait(&se->cond, &se->lock);
|
||||
int32_t res = se->bg_result;
|
||||
pthread_mutex_unlock(&se->lock);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Allocate syncemitter and start thread
|
||||
static struct syncemitter *
|
||||
syncemitter_alloc(char name[16], int alloc_stepcompress)
|
||||
{
|
||||
struct syncemitter *se = malloc(sizeof(*se));
|
||||
memset(se, 0, sizeof(*se));
|
||||
list_init(&se->msg_queue);
|
||||
strncpy(se->name, name, sizeof(se->name));
|
||||
se->name[sizeof(se->name)-1] = '\0';
|
||||
if (!alloc_stepcompress)
|
||||
return se;
|
||||
se->sc = stepcompress_alloc(&se->msg_queue);
|
||||
int ret = pthread_mutex_init(&se->lock, NULL);
|
||||
if (ret)
|
||||
goto fail;
|
||||
ret = pthread_cond_init(&se->cond, NULL);
|
||||
if (ret)
|
||||
goto fail;
|
||||
ret = pthread_create(&se->tid, NULL, se_background_thread, se);
|
||||
if (ret)
|
||||
goto fail;
|
||||
return se;
|
||||
fail:
|
||||
report_errno("se alloc", ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Free syncemitter and exit background thread
|
||||
static void
|
||||
syncemitter_free(struct syncemitter *se)
|
||||
{
|
||||
if (!se)
|
||||
return;
|
||||
if (se->sc) {
|
||||
pthread_mutex_lock(&se->lock);
|
||||
while (se->have_work)
|
||||
pthread_cond_wait(&se->cond, &se->lock);
|
||||
se->have_work = -1;
|
||||
pthread_cond_signal(&se->cond);
|
||||
pthread_mutex_unlock(&se->lock);
|
||||
int ret = pthread_join(se->tid, NULL);
|
||||
if (ret)
|
||||
report_errno("se pthread_join", ret);
|
||||
stepcompress_free(se->sc);
|
||||
}
|
||||
message_queue_free(&se->msg_queue);
|
||||
free(se);
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* StepperSync - sort move queue for a micro-controller
|
||||
****************************************************************/
|
||||
|
||||
struct steppersync {
|
||||
// List node for storage in steppersyncmgr list
|
||||
struct list_node ssm_node;
|
||||
// Serial port
|
||||
struct serialqueue *sq;
|
||||
struct command_queue *cq;
|
||||
// The syncemitters that generate messages on this mcu
|
||||
struct list_head se_list;
|
||||
// Convert from time to clock
|
||||
struct clock_estimate ce;
|
||||
// Storage for list of pending move clocks
|
||||
uint64_t *move_clocks;
|
||||
int num_move_clocks;
|
||||
};
|
||||
|
||||
// Allocate a new syncemitter instance
|
||||
struct syncemitter * __visible
|
||||
steppersync_alloc_syncemitter(struct steppersync *ss, char name[16]
|
||||
, int alloc_stepcompress)
|
||||
{
|
||||
struct syncemitter *se = syncemitter_alloc(name, alloc_stepcompress);
|
||||
if (se)
|
||||
list_add_tail(&se->ss_node, &ss->se_list);
|
||||
return se;
|
||||
}
|
||||
|
||||
// Fill information on mcu move queue
|
||||
void __visible
|
||||
steppersync_setup_movequeue(struct steppersync *ss, struct serialqueue *sq
|
||||
, int move_num)
|
||||
{
|
||||
serialqueue_free_commandqueue(ss->cq);
|
||||
free(ss->move_clocks);
|
||||
|
||||
ss->sq = sq;
|
||||
ss->cq = serialqueue_alloc_commandqueue();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Set the conversion rate of 'print_time' to mcu clock
|
||||
void __visible
|
||||
steppersync_set_time(struct steppersync *ss, double time_offset
|
||||
, double mcu_freq)
|
||||
{
|
||||
clock_fill(&ss->ce, mcu_freq, time_offset, 0, 0);
|
||||
struct syncemitter *se;
|
||||
list_for_each_entry(se, &ss->se_list, ss_node) {
|
||||
if (se->sc)
|
||||
stepcompress_set_time(se->sc, time_offset, mcu_freq);
|
||||
}
|
||||
}
|
||||
|
||||
// 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'
|
||||
static void
|
||||
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;
|
||||
struct syncemitter *se;
|
||||
list_for_each_entry(se, &ss->se_list, ss_node) {
|
||||
if (!list_empty(&se->msg_queue)) {
|
||||
struct queue_message *m = list_first_entry(
|
||||
&se->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);
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************
|
||||
* StepperSyncMgr - manage a list of steppersync
|
||||
****************************************************************/
|
||||
|
||||
struct steppersyncmgr {
|
||||
struct list_head ss_list;
|
||||
};
|
||||
|
||||
// Allocate a new 'steppersyncmgr' object
|
||||
struct steppersyncmgr * __visible
|
||||
steppersyncmgr_alloc(void)
|
||||
{
|
||||
struct steppersyncmgr *ssm = malloc(sizeof(*ssm));
|
||||
memset(ssm, 0, sizeof(*ssm));
|
||||
list_init(&ssm->ss_list);
|
||||
return ssm;
|
||||
}
|
||||
|
||||
// Free memory associated with a 'steppersync' object
|
||||
void __visible
|
||||
steppersyncmgr_free(struct steppersyncmgr *ssm)
|
||||
{
|
||||
if (!ssm)
|
||||
return;
|
||||
while (!list_empty(&ssm->ss_list)) {
|
||||
struct steppersync *ss = list_first_entry(
|
||||
&ssm->ss_list, struct steppersync, ssm_node);
|
||||
list_del(&ss->ssm_node);
|
||||
free(ss->move_clocks);
|
||||
serialqueue_free_commandqueue(ss->cq);
|
||||
while (!list_empty(&ss->se_list)) {
|
||||
struct syncemitter *se = list_first_entry(
|
||||
&ss->se_list, struct syncemitter, ss_node);
|
||||
list_del(&se->ss_node);
|
||||
syncemitter_free(se);
|
||||
}
|
||||
free(ss);
|
||||
}
|
||||
free(ssm);
|
||||
}
|
||||
|
||||
// Allocate a new 'steppersync' object
|
||||
struct steppersync * __visible
|
||||
steppersyncmgr_alloc_steppersync(struct steppersyncmgr *ssm)
|
||||
{
|
||||
struct steppersync *ss = malloc(sizeof(*ss));
|
||||
memset(ss, 0, sizeof(*ss));
|
||||
list_init(&ss->se_list);
|
||||
list_add_tail(&ss->ssm_node, &ssm->ss_list);
|
||||
return ss;
|
||||
}
|
||||
|
||||
// Generate and flush steps
|
||||
int32_t __visible
|
||||
steppersyncmgr_gen_steps(struct steppersyncmgr *ssm, double flush_time
|
||||
, double gen_steps_time, double clear_history_time)
|
||||
{
|
||||
struct steppersync *ss;
|
||||
// Prepare trapqs for step generation
|
||||
list_for_each_entry(ss, &ssm->ss_list, ssm_node) {
|
||||
struct syncemitter *se;
|
||||
list_for_each_entry(se, &ss->se_list, ss_node) {
|
||||
if (!se->sc || !se->sk)
|
||||
continue;
|
||||
struct trapq *tq = itersolve_get_trapq(se->sk);
|
||||
if (tq)
|
||||
trapq_check_sentinels(tq);
|
||||
}
|
||||
}
|
||||
// Start step generation threads
|
||||
list_for_each_entry(ss, &ssm->ss_list, ssm_node) {
|
||||
uint64_t flush_clock = clock_from_time(&ss->ce, flush_time);
|
||||
uint64_t clear_clock = clock_from_time(&ss->ce, clear_history_time);
|
||||
struct syncemitter *se;
|
||||
list_for_each_entry(se, &ss->se_list, ss_node) {
|
||||
se_start_gen_steps(se, gen_steps_time, flush_clock, clear_clock);
|
||||
}
|
||||
}
|
||||
// Wait for step generation threads to complete
|
||||
int32_t res = 0;
|
||||
list_for_each_entry(ss, &ssm->ss_list, ssm_node) {
|
||||
struct syncemitter *se;
|
||||
list_for_each_entry(se, &ss->se_list, ss_node) {
|
||||
int32_t ret = se_finalize_gen_steps(se);
|
||||
if (ret)
|
||||
res = ret;
|
||||
}
|
||||
if (res)
|
||||
continue;
|
||||
uint64_t flush_clock = clock_from_time(&ss->ce, flush_time);
|
||||
steppersync_flush(ss, flush_clock);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
32
klippy/chelper/steppersync.h
Normal file
32
klippy/chelper/steppersync.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef STEPPERSYNC_H
|
||||
#define STEPPERSYNC_H
|
||||
|
||||
#include <stdint.h> // uint64_t
|
||||
|
||||
struct syncemitter;
|
||||
struct stepcompress *syncemitter_get_stepcompress(struct syncemitter *se);
|
||||
void syncemitter_set_stepper_kinematics(struct syncemitter *se
|
||||
, struct stepper_kinematics *sk);
|
||||
struct stepper_kinematics *syncemitter_get_stepper_kinematics(
|
||||
struct syncemitter *se);
|
||||
void syncemitter_queue_msg(struct syncemitter *se, uint64_t req_clock
|
||||
, uint32_t *data, int len);
|
||||
|
||||
struct steppersync;
|
||||
struct syncemitter *steppersync_alloc_syncemitter(
|
||||
struct steppersync *ss, char name[16], int alloc_stepcompress);
|
||||
void steppersync_setup_movequeue(struct steppersync *ss, struct serialqueue *sq
|
||||
, int move_num);
|
||||
void steppersync_set_time(struct steppersync *ss, double time_offset
|
||||
, double mcu_freq);
|
||||
|
||||
struct steppersyncmgr *steppersyncmgr_alloc(void);
|
||||
void steppersyncmgr_free(struct steppersyncmgr *ssm);
|
||||
struct serialqueue;
|
||||
struct steppersync *steppersyncmgr_alloc_steppersync(
|
||||
struct steppersyncmgr *ssm);
|
||||
int32_t steppersyncmgr_gen_steps(struct steppersyncmgr *ssm, double flush_time
|
||||
, double gen_steps_time
|
||||
, double clear_history_time);
|
||||
|
||||
#endif // steppersync.h
|
||||
|
|
@ -49,6 +49,7 @@ trapq_alloc(void)
|
|||
list_init(&tq->moves);
|
||||
list_init(&tq->history);
|
||||
struct move *head_sentinel = move_alloc(), *tail_sentinel = move_alloc();
|
||||
head_sentinel->print_time = -1.0;
|
||||
tail_sentinel->print_time = tail_sentinel->move_t = NEVER_TIME;
|
||||
list_add_head(&head_sentinel->node, &tq->moves);
|
||||
list_add_tail(&tail_sentinel->node, &tq->moves);
|
||||
|
|
@ -103,7 +104,7 @@ trapq_add_move(struct trapq *tq, struct move *m)
|
|||
// Add a null move to fill time gap
|
||||
struct move *null_move = move_alloc();
|
||||
null_move->start_pos = m->start_pos;
|
||||
if (!prev->print_time && m->print_time > MAX_NULL_MOVE)
|
||||
if (prev->print_time <= 0. && m->print_time > MAX_NULL_MOVE)
|
||||
// Limit the first null move to improve numerical stability
|
||||
null_move->print_time = m->print_time - MAX_NULL_MOVE;
|
||||
else
|
||||
|
|
@ -227,6 +228,22 @@ trapq_set_position(struct trapq *tq, double print_time
|
|||
list_add_head(&m->node, &tq->history);
|
||||
}
|
||||
|
||||
// Copy the info in a 'struct move' to a 'struct pull_move'
|
||||
static void
|
||||
copy_pull_move(struct pull_move *p, struct move *m)
|
||||
{
|
||||
p->print_time = m->print_time;
|
||||
p->move_t = m->move_t;
|
||||
p->start_v = m->start_v;
|
||||
p->accel = 2. * m->half_accel;
|
||||
p->start_x = m->start_pos.x;
|
||||
p->start_y = m->start_pos.y;
|
||||
p->start_z = m->start_pos.z;
|
||||
p->x_r = m->axes_r.x;
|
||||
p->y_r = m->axes_r.y;
|
||||
p->z_r = m->axes_r.z;
|
||||
}
|
||||
|
||||
// Return history of movement queue
|
||||
int __visible
|
||||
trapq_extract_old(struct trapq *tq, struct pull_move *p, int max
|
||||
|
|
@ -234,21 +251,21 @@ trapq_extract_old(struct trapq *tq, struct pull_move *p, int max
|
|||
{
|
||||
int res = 0;
|
||||
struct move *m;
|
||||
list_for_each_entry_reverse(m, &tq->moves, node) {
|
||||
if (start_time >= m->print_time + m->move_t || res >= max)
|
||||
break;
|
||||
if (end_time <= m->print_time || (!m->start_v && !m->half_accel))
|
||||
continue;
|
||||
copy_pull_move(p, m);
|
||||
p++;
|
||||
res++;
|
||||
}
|
||||
list_for_each_entry(m, &tq->history, node) {
|
||||
if (start_time >= m->print_time + m->move_t || res >= max)
|
||||
break;
|
||||
if (end_time <= m->print_time)
|
||||
continue;
|
||||
p->print_time = m->print_time;
|
||||
p->move_t = m->move_t;
|
||||
p->start_v = m->start_v;
|
||||
p->accel = 2. * m->half_accel;
|
||||
p->start_x = m->start_pos.x;
|
||||
p->start_y = m->start_pos.y;
|
||||
p->start_z = m->start_pos.z;
|
||||
p->x_r = m->axes_r.x;
|
||||
p->y_r = m->axes_r.y;
|
||||
p->z_r = m->axes_r.z;
|
||||
copy_pull_move(p, m);
|
||||
p++;
|
||||
res++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,14 +130,8 @@ class ConfigWrapper:
|
|||
def deprecate(self, option, value=None):
|
||||
if not self.fileconfig.has_option(self.section, option):
|
||||
return
|
||||
if value is None:
|
||||
msg = ("Option '%s' in section '%s' is deprecated."
|
||||
% (option, self.section))
|
||||
else:
|
||||
msg = ("Value '%s' in option '%s' in section '%s' is deprecated."
|
||||
% (value, option, self.section))
|
||||
pconfig = self.printer.lookup_object("configfile")
|
||||
pconfig.deprecate(self.section, option, value, msg)
|
||||
pconfig.deprecate(self.section, option, value)
|
||||
|
||||
|
||||
######################################################################
|
||||
|
|
@ -468,8 +462,6 @@ class PrinterConfig:
|
|||
self.autosave = ConfigAutoSave(printer)
|
||||
self.validate = ConfigValidate(printer)
|
||||
self.deprecated = {}
|
||||
self.runtime_warnings = []
|
||||
self.deprecate_warnings = []
|
||||
self.status_raw_config = {}
|
||||
self.status_warnings = []
|
||||
def get_printer(self):
|
||||
|
|
@ -496,27 +488,58 @@ class PrinterConfig:
|
|||
def check_unused_options(self, config):
|
||||
self.validate.check_unused(config.fileconfig)
|
||||
# Deprecation warnings
|
||||
def _add_deprecated(self, data):
|
||||
key = tuple(list(data.items()))
|
||||
if key in self.deprecated:
|
||||
return False
|
||||
self.deprecated[key] = True
|
||||
self.status_warnings = self.status_warnings + [data]
|
||||
return True
|
||||
def runtime_warning(self, msg):
|
||||
logging.warning(msg)
|
||||
res = {'type': 'runtime_warning', 'message': msg}
|
||||
self.runtime_warnings.append(res)
|
||||
self.status_warnings = self.runtime_warnings + self.deprecate_warnings
|
||||
did_add = self._add_deprecated(res)
|
||||
if did_add:
|
||||
logging.warning(msg)
|
||||
def deprecate(self, section, option, value=None, msg=None):
|
||||
key = (section, option, value)
|
||||
if key in self.deprecated and self.deprecated[key] == msg:
|
||||
return
|
||||
self.deprecated[key] = msg
|
||||
self.deprecate_warnings = []
|
||||
for (section, option, value), msg in self.deprecated.items():
|
||||
if value is None:
|
||||
res = {'type': 'deprecated_option'}
|
||||
else:
|
||||
res = {'type': 'deprecated_value', 'value': value}
|
||||
res['message'] = msg
|
||||
res['section'] = section
|
||||
res['option'] = option
|
||||
self.deprecate_warnings.append(res)
|
||||
self.status_warnings = self.runtime_warnings + self.deprecate_warnings
|
||||
if value is None:
|
||||
res = {'type': 'deprecated_option'}
|
||||
defmsg = ("Option '%s' in section '%s' is deprecated."
|
||||
% (option, self.section))
|
||||
else:
|
||||
res = {'type': 'deprecated_value', 'value': value}
|
||||
defmsg = ("Value '%s' in option '%s' in section '%s' is deprecated."
|
||||
% (value, option, self.section))
|
||||
if msg is None:
|
||||
msg = defmsg
|
||||
res['message'] = msg
|
||||
res['section'] = section
|
||||
res['option'] = option
|
||||
self._add_deprecated(res)
|
||||
def deprecate_gcode(self, cmd, param=None, value=None, msg=None):
|
||||
if param is None:
|
||||
defmsg = "Command '%s' is deprecated." % (cmd,)
|
||||
elif value is None:
|
||||
defmsg = ("Parameter '%s' in command '%s' is deprecated."
|
||||
% (param, cmd))
|
||||
else:
|
||||
defmsg = ("Value '%s=%s' in command '%s' is deprecated."
|
||||
% (param, value, cmd))
|
||||
if msg is None:
|
||||
msg = defmsg
|
||||
res = {'type': 'deprecated_gcode', 'message': msg,
|
||||
'command': cmd, 'parameter': param, 'value': str(value)}
|
||||
self._add_deprecated(res)
|
||||
def deprecate_mcu_code(self, mcu, feature, msg=None):
|
||||
mcu_name = mcu.get_name()
|
||||
if msg is None:
|
||||
vhost = self.printer.start_args['software_version']
|
||||
vmcu = mcu.get_status()['mcu_version']
|
||||
msg = ("MCU '%s' has deprecated code (it is missing feature '%s')."
|
||||
" Recompiling and flashing is recommended (MCU version '%s',"
|
||||
" host version '%s')." % (mcu_name, feature, vmcu, vhost))
|
||||
res = {'type': 'deprecated_mcu_code', 'message': msg,
|
||||
'mcu': mcu_name, 'feature': feature}
|
||||
self._add_deprecated(res)
|
||||
# Status reporting
|
||||
def _build_status_config(self, config):
|
||||
self.status_raw_config = {}
|
||||
|
|
|
|||
|
|
@ -19,15 +19,16 @@ class MCU_scaled_adc:
|
|||
self._callback = None
|
||||
self.setup_adc_sample = self._mcu_adc.setup_adc_sample
|
||||
self.get_mcu = self._mcu_adc.get_mcu
|
||||
def _handle_callback(self, read_time, read_value):
|
||||
def _handle_callback(self, samples):
|
||||
max_adc = self._main.last_vref[1]
|
||||
min_adc = self._main.last_vssa[1]
|
||||
scaled_val = (read_value - min_adc) / (max_adc - min_adc)
|
||||
self._last_state = (scaled_val, read_time)
|
||||
self._callback(read_time, scaled_val)
|
||||
def setup_adc_callback(self, report_time, callback):
|
||||
adjsamples = [(t, (read_value - min_adc) / (max_adc - min_adc))
|
||||
for t, read_value in samples]
|
||||
self._last_state = adjsamples[-1]
|
||||
self._callback(adjsamples)
|
||||
def setup_adc_callback(self, callback):
|
||||
self._callback = callback
|
||||
self._mcu_adc.setup_adc_callback(report_time, self._handle_callback)
|
||||
self._mcu_adc.setup_adc_callback(self._handle_callback)
|
||||
def get_last_value(self):
|
||||
return self._last_state
|
||||
|
||||
|
|
@ -52,8 +53,8 @@ class PrinterADCScaled:
|
|||
pin_name = config.get(name + '_pin')
|
||||
ppins = self.printer.lookup_object('pins')
|
||||
mcu_adc = ppins.setup_pin('adc', pin_name)
|
||||
mcu_adc.setup_adc_callback(REPORT_TIME, callback)
|
||||
mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT)
|
||||
mcu_adc.setup_adc_callback(callback)
|
||||
mcu_adc.setup_adc_sample(REPORT_TIME, SAMPLE_TIME, SAMPLE_COUNT)
|
||||
query_adc = config.get_printer().load_object(config, 'query_adc')
|
||||
query_adc.register_adc(self.name + ":" + name, mcu_adc)
|
||||
return mcu_adc
|
||||
|
|
@ -68,9 +69,11 @@ class PrinterADCScaled:
|
|||
adj_time = min(time_diff * self.inv_smooth_time, 1.)
|
||||
smoothed_value = last_value + value_diff * adj_time
|
||||
return (read_time, smoothed_value)
|
||||
def vref_callback(self, read_time, read_value):
|
||||
def vref_callback(self, samples):
|
||||
read_time, read_value = samples[-1]
|
||||
self.last_vref = self.calc_smooth(read_time, read_value, self.last_vref)
|
||||
def vssa_callback(self, read_time, read_value):
|
||||
def vssa_callback(self, samples):
|
||||
read_time, read_value = samples[-1]
|
||||
self.last_vssa = self.calc_smooth(read_time, read_value, self.last_vssa)
|
||||
|
||||
def load_config_prefix(config):
|
||||
|
|
|
|||
|
|
@ -21,20 +21,21 @@ class PrinterADCtoTemperature:
|
|||
self.adc_convert = adc_convert
|
||||
ppins = config.get_printer().lookup_object('pins')
|
||||
self.mcu_adc = ppins.setup_pin('adc', config.get('sensor_pin'))
|
||||
self.mcu_adc.setup_adc_callback(REPORT_TIME, self.adc_callback)
|
||||
self.mcu_adc.setup_adc_callback(self.adc_callback)
|
||||
self.diag_helper = HelperTemperatureDiagnostics(
|
||||
config, self.mcu_adc, adc_convert.calc_temp)
|
||||
def setup_callback(self, temperature_callback):
|
||||
self.temperature_callback = temperature_callback
|
||||
def get_report_time_delta(self):
|
||||
return REPORT_TIME
|
||||
def adc_callback(self, read_time, read_value):
|
||||
def adc_callback(self, samples):
|
||||
read_time, read_value = samples[-1]
|
||||
temp = self.adc_convert.calc_temp(read_value)
|
||||
self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp)
|
||||
def setup_minmax(self, min_temp, max_temp):
|
||||
arange = [self.adc_convert.calc_adc(t) for t in [min_temp, max_temp]]
|
||||
min_adc, max_adc = sorted(arange)
|
||||
self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT,
|
||||
self.mcu_adc.setup_adc_sample(REPORT_TIME, SAMPLE_TIME, SAMPLE_COUNT,
|
||||
minval=min_adc, maxval=max_adc,
|
||||
range_check_count=RANGE_CHECK_COUNT)
|
||||
self.diag_helper.setup_diag_minmax(min_temp, max_temp, min_adc, max_adc)
|
||||
|
|
@ -57,7 +58,7 @@ class HelperTemperatureDiagnostics:
|
|||
def _clarify_adc_range(self, msg, details):
|
||||
if self.min_temp is None:
|
||||
return None
|
||||
last_value, last_read_time = self.mcu_adc.get_last_value()
|
||||
last_read_time, last_value = self.mcu_adc.get_last_value()
|
||||
if not last_read_time:
|
||||
return None
|
||||
if last_value >= self.min_adc and last_value <= self.max_adc:
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ 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))
|
||||
|
|
@ -105,12 +104,15 @@ class ADS1220:
|
|||
mcu.register_config_callback(self._build_config)
|
||||
self.query_ads1220_cmd = None
|
||||
|
||||
def setup_trigger_analog(self, trigger_analog_oid):
|
||||
self.mcu.add_config_cmd(
|
||||
"ads1220_attach_trigger_analog oid=%d trigger_analog_oid=%d"
|
||||
% (self.oid, trigger_analog_oid), is_init=True)
|
||||
|
||||
def _build_config(self):
|
||||
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)
|
||||
|
||||
|
|
@ -120,6 +122,9 @@ class ADS1220:
|
|||
def get_samples_per_second(self):
|
||||
return self.sps
|
||||
|
||||
def lookup_sensor_error(self, error_code):
|
||||
return "Unknown ads1220 error" % (error_code,)
|
||||
|
||||
# returns a tuple of the minimum and maximum value of the sensor, used to
|
||||
# detect if a data value is saturated
|
||||
def get_range(self):
|
||||
|
|
@ -129,9 +134,6 @@ 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)
|
||||
|
|
@ -175,6 +177,8 @@ class ADS1220:
|
|||
# read startup register state and validate
|
||||
val = self.read_reg(0x0, 4)
|
||||
if val != RESET_STATE:
|
||||
if self.mcu.is_fileoutput():
|
||||
return
|
||||
raise self.printer.command_error(
|
||||
"Invalid ads1220 reset state (got %s vs %s).\n"
|
||||
"This is generally indicative of connection problems\n"
|
||||
|
|
@ -209,6 +213,8 @@ class ADS1220:
|
|||
self.spi.spi_send(write_command)
|
||||
stored_val = self.read_reg(reg, len(register_bytes))
|
||||
if bytearray(register_bytes) != stored_val:
|
||||
if self.mcu.is_fileoutput():
|
||||
return
|
||||
raise self.printer.command_error(
|
||||
"Failed to set ADS1220 register [0x%x] to %s: got %s. "
|
||||
"This may be a connection problem (e.g. faulty wiring)" % (
|
||||
|
|
|
|||
|
|
@ -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,11 @@ 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.pcfg = pcfg
|
||||
self._last_state = (0., 0.)
|
||||
self.invalid_count = 0
|
||||
|
||||
self.chip._printer.register_event_handler("klippy:connect", \
|
||||
|
|
@ -360,9 +361,10 @@ class ADS1X1X_pin:
|
|||
self.invalid_count = 0
|
||||
|
||||
# Publish result
|
||||
measured_time = self._reactor.monotonic()
|
||||
self.callback(self.chip.mcu.estimated_print_time(measured_time),
|
||||
target_value)
|
||||
systime = self._reactor.monotonic()
|
||||
measured_time = self.chip.mcu.estimated_print_time(systime)
|
||||
self._last_state = (measured_time, target_value)
|
||||
self.callback([(measured_time, target_value)])
|
||||
else:
|
||||
self.invalid_count = self.invalid_count + 1
|
||||
self.check_invalid()
|
||||
|
|
@ -377,16 +379,20 @@ class ADS1X1X_pin:
|
|||
def get_mcu(self):
|
||||
return self.mcu
|
||||
|
||||
def setup_adc_callback(self, report_time, callback):
|
||||
self.report_time = report_time
|
||||
def setup_adc_callback(self, callback):
|
||||
self.callback = callback
|
||||
self.chip.handle_report_time_update()
|
||||
|
||||
def setup_adc_sample(self, sample_time, sample_count,
|
||||
minval=0., maxval=1., range_check_count=0):
|
||||
def setup_adc_sample(self, report_time, sample_time=0., sample_count=1,
|
||||
batch_num=1, minval=0., maxval=1.,
|
||||
range_check_count=0):
|
||||
self.report_time = report_time
|
||||
self.minval = minval
|
||||
self.maxval = maxval
|
||||
self.range_check_count = range_check_count
|
||||
self.chip.handle_report_time_update()
|
||||
|
||||
def get_last_value(self):
|
||||
return self._last_state
|
||||
|
||||
def load_config_prefix(config):
|
||||
return ADS1X1X_chip(config)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# AHT10/AHT20/AHT21 I2c-based humiditure sensor support
|
||||
# Support for AHTxx family I2C temperature and humidity sensors
|
||||
#
|
||||
# Copyright (C) 2023 Scott Mudge <mail@scottmudge.com>
|
||||
# Copyright (C) 2025 Lev Voronov <minicx@disroot.org>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging
|
||||
|
|
@ -9,49 +10,67 @@ from . import bus
|
|||
######################################################################
|
||||
# Compatible Sensors:
|
||||
# AHT10 - Tested w/ BTT GTR 1.0 MCU on i2c3
|
||||
# AHT20 - Untested but should work
|
||||
# AHT20 - Tested w/ N32G455 on i2c2
|
||||
# AHT21 - Tested w/ BTT GTR 1.0 MCU on i2c3
|
||||
# AHT30 - Untested, but should work
|
||||
######################################################################
|
||||
|
||||
AHT10_I2C_ADDR= 0x38
|
||||
I2C_ADDR = 0x38
|
||||
|
||||
AHT10_COMMANDS = {
|
||||
'INIT' :[0xE1, 0x08, 0x00],
|
||||
'MEASURE' :[0xAC, 0x33, 0x00],
|
||||
'RESET' :[0xBA, 0x08, 0x00]
|
||||
}
|
||||
CMD_MEASURE = [0xAC, 0x33, 0x00]
|
||||
CMD_RESET = [0xBA]
|
||||
CMD_INIT_AHT1X = [0xE1, 0x08, 0x00]
|
||||
CMD_INIT_AHT2X = [0xBE, 0x08, 0x00]
|
||||
|
||||
AHT10_MAX_BUSY_CYCLES= 5
|
||||
# Status bits
|
||||
STATUS_BUSY = 0x80
|
||||
STATUS_CALIBRATED = 0x08
|
||||
|
||||
MAX_BUSY_CYCLES = 5
|
||||
|
||||
class AHTBase:
|
||||
model = None
|
||||
|
||||
class AHT10:
|
||||
def __init__(self, config):
|
||||
self.printer = config.get_printer()
|
||||
self.name = config.get_name().split()[-1]
|
||||
self.reactor = self.printer.get_reactor()
|
||||
self.i2c = bus.MCU_I2C_from_config(
|
||||
config, default_addr=AHT10_I2C_ADDR, default_speed=100000)
|
||||
self.report_time = config.getint('aht10_report_time',30,minval=5)
|
||||
config, default_addr=I2C_ADDR, default_speed=100000)
|
||||
self.report_time = config.getint('aht10_report_time', 30, minval=5)
|
||||
self.temp = self.min_temp = self.max_temp = self.humidity = 0.
|
||||
self.sample_timer = self.reactor.register_timer(self._sample_aht10)
|
||||
self.sample_timer = self.reactor.register_timer(self._sample_aht)
|
||||
|
||||
self.printer.add_object("aht10 " + self.name, self)
|
||||
self.printer.register_event_handler("klippy:connect",
|
||||
self.handle_connect)
|
||||
self.is_calibrated = False
|
||||
self.handle_connect)
|
||||
self.is_calibrated = False
|
||||
self.init_sent = False
|
||||
self._callback = None
|
||||
|
||||
def handle_connect(self):
|
||||
self._init_aht10()
|
||||
self._init_sensor()
|
||||
self.reactor.update_timer(self.sample_timer, self.reactor.NOW)
|
||||
|
||||
def setup_minmax(self, min_temp, max_temp):
|
||||
self.min_temp = min_temp
|
||||
self.max_temp = max_temp
|
||||
def _send_init(self):
|
||||
raise NotImplementedError("Subclass must implement _send_init")
|
||||
|
||||
def setup_callback(self, cb):
|
||||
self._callback = cb
|
||||
def _init_sensor(self):
|
||||
self._send_init()
|
||||
|
||||
def get_report_time_delta(self):
|
||||
return self.report_time
|
||||
self.init_sent = True
|
||||
|
||||
if self._make_measurement():
|
||||
if not self.is_calibrated:
|
||||
logging.warning("%s %s: not calibrated, possible OTP fault"
|
||||
% (self.model, self.name))
|
||||
logging.info("%s %s: successfully initialized, "
|
||||
"initial temp: %.3f, humidity: %.3f"
|
||||
% (self.model, self.name, self.temp, self.humidity))
|
||||
def _soft_reset(self):
|
||||
logging.info("%s %s: performing soft reset" % (self.model, self.name))
|
||||
self.i2c.i2c_write(CMD_RESET)
|
||||
self.reactor.pause(self.reactor.monotonic() + 0.020)
|
||||
|
||||
def _make_measurement(self):
|
||||
if not self.init_sent:
|
||||
|
|
@ -66,45 +85,51 @@ class AHT10:
|
|||
while is_busy:
|
||||
# Check if we're constantly busy. If so, send soft-reset
|
||||
# and issue warning.
|
||||
if is_busy and cycles > AHT10_MAX_BUSY_CYCLES:
|
||||
logging.warning("aht10: device reported busy after " +
|
||||
"%d cycles, resetting device"% AHT10_MAX_BUSY_CYCLES)
|
||||
self._reset_device()
|
||||
if is_busy and cycles > MAX_BUSY_CYCLES:
|
||||
logging.warning("%s %s: device reported busy after "
|
||||
"%d cycles, resetting device"
|
||||
% (self.model, self.name, MAX_BUSY_CYCLES))
|
||||
self._soft_reset()
|
||||
data = None
|
||||
break
|
||||
|
||||
cycles += 1
|
||||
# Write command for updating temperature+status bit
|
||||
self.i2c.i2c_write(AHT10_COMMANDS['MEASURE'])
|
||||
self.i2c.i2c_write(CMD_MEASURE)
|
||||
# Wait 110ms after first read, 75ms minimum
|
||||
self.reactor.pause(self.reactor.monotonic() + .110)
|
||||
|
||||
# Read data
|
||||
# Read 6 bytes of data
|
||||
read = self.i2c.i2c_read([], 6)
|
||||
if read is None:
|
||||
logging.warning("aht10: received data from" +
|
||||
" i2c_read is None")
|
||||
continue
|
||||
data = bytearray(read['response'])
|
||||
if len(data) < 6:
|
||||
logging.warning("aht10: received bytes less than" +
|
||||
" expected 6 [%d]"%len(data))
|
||||
logging.warning("%s %s: received data from i2c_read is None"
|
||||
% (self.model, self.name))
|
||||
continue
|
||||
|
||||
self.is_calibrated = True if (data[0] & 0b00000100) else False
|
||||
is_busy = True if (data[0] & 0b01000000) else False
|
||||
data = bytearray(read['response'])
|
||||
if len(data) < 6:
|
||||
logging.warning("%s %s: received bytes less than expected:"
|
||||
" got %d, need 6"
|
||||
% (self.model, self.name, len(data)))
|
||||
continue
|
||||
|
||||
self.is_calibrated = bool(data[0] & STATUS_CALIBRATED)
|
||||
is_busy = bool(data[0] & STATUS_BUSY)
|
||||
|
||||
if is_busy:
|
||||
return False
|
||||
except Exception as e:
|
||||
logging.exception("aht10: exception encountered" +
|
||||
" reading data: %s"%str(e))
|
||||
logging.exception("%s %s: exception encountered reading data: %s"
|
||||
% (self.model, self.name, str(e)))
|
||||
return False
|
||||
|
||||
temp = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]
|
||||
self.temp = ((temp*200) / 1048576) - 50
|
||||
hum = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4
|
||||
self.humidity = int(hum * 100 / 1048576)
|
||||
# Parse temperature: 20 bits starting at data[3] (low nibble)
|
||||
temp_raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]
|
||||
self.temp = ((temp_raw * 200.0) / 1048576.0) - 50.0
|
||||
|
||||
# Parse humidity: 20 bits starting at data[1]
|
||||
hum_raw = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4
|
||||
self.humidity = int(hum_raw * 100 / 1048576)
|
||||
|
||||
# Clamp humidity
|
||||
if (self.humidity > 100):
|
||||
|
|
@ -114,49 +139,65 @@ class AHT10:
|
|||
|
||||
return True
|
||||
|
||||
def _reset_device(self):
|
||||
if not self.init_sent:
|
||||
return
|
||||
|
||||
# Reset device
|
||||
self.i2c.i2c_write(AHT10_COMMANDS['RESET'])
|
||||
# Wait 100ms after reset
|
||||
self.reactor.pause(self.reactor.monotonic() + .10)
|
||||
|
||||
def _init_aht10(self):
|
||||
# Init device
|
||||
self.i2c.i2c_write(AHT10_COMMANDS['INIT'])
|
||||
# Wait 100ms after init
|
||||
self.reactor.pause(self.reactor.monotonic() + .10)
|
||||
self.init_sent = True
|
||||
|
||||
if self._make_measurement():
|
||||
logging.info("aht10: successfully initialized, initial temp: " +
|
||||
"%.3f, humidity: %.3f"%(self.temp, self.humidity))
|
||||
|
||||
def _sample_aht10(self, eventtime):
|
||||
def _sample_aht(self, eventtime):
|
||||
if not self._make_measurement():
|
||||
self.temp = self.humidity = .0
|
||||
return self.reactor.NEVER
|
||||
|
||||
if self.temp < self.min_temp or self.temp > self.max_temp:
|
||||
self.printer.invoke_shutdown(
|
||||
"AHT10 temperature %0.1f outside range of %0.1f:%.01f"
|
||||
% (self.temp, self.min_temp, self.max_temp))
|
||||
"%s temperature %.1f outside range of %.1f:%.1f" %
|
||||
(self.model.upper(), self.temp, self.min_temp, self.max_temp))
|
||||
|
||||
measured_time = self.reactor.monotonic()
|
||||
print_time = self.i2c.get_mcu().estimated_print_time(measured_time)
|
||||
self._callback(print_time, self.temp)
|
||||
return measured_time + self.report_time
|
||||
|
||||
def setup_minmax(self, min_temp, max_temp):
|
||||
self.min_temp = min_temp
|
||||
self.max_temp = max_temp
|
||||
|
||||
def setup_callback(self, cb):
|
||||
self._callback = cb
|
||||
|
||||
def get_report_time_delta(self):
|
||||
return self.report_time
|
||||
|
||||
def get_status(self, eventtime):
|
||||
return {
|
||||
'temperature': round(self.temp, 2),
|
||||
'humidity': self.humidity,
|
||||
}
|
||||
|
||||
class AHT1x(AHTBase):
|
||||
model = "aht1x"
|
||||
|
||||
def _send_init(self):
|
||||
self.i2c.i2c_write(CMD_INIT_AHT1X)
|
||||
self.reactor.pause(self.reactor.monotonic() + 0.040)
|
||||
|
||||
class AHT2x(AHTBase):
|
||||
model = "aht2x"
|
||||
|
||||
def _send_init(self):
|
||||
self.i2c.i2c_write(CMD_INIT_AHT2X)
|
||||
self.reactor.pause(self.reactor.monotonic() + 0.100)
|
||||
|
||||
class AHT3x(AHTBase):
|
||||
model = "aht3x"
|
||||
|
||||
def _send_init(self):
|
||||
# Wait for auto-calibration at power-on
|
||||
self.reactor.pause(self.reactor.monotonic() + 0.100)
|
||||
|
||||
def load_config(config):
|
||||
# Register sensor
|
||||
pheater = config.get_printer().lookup_object("heaters")
|
||||
pheater.add_sensor_factory("AHT10", AHT10)
|
||||
|
||||
# Backwards compatibility alias
|
||||
pheater.add_sensor_factory("AHT10", AHT1x)
|
||||
|
||||
pheater.add_sensor_factory("AHT1X", AHT1x)
|
||||
pheater.add_sensor_factory("AHT2X", AHT2x)
|
||||
pheater.add_sensor_factory("AHT3X", AHT3x)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -51,20 +51,27 @@ class AxisTwistCompensation:
|
|||
self.printer.register_event_handler("probe:update_results",
|
||||
self._update_z_compensation_value)
|
||||
|
||||
def _update_z_compensation_value(self, pos):
|
||||
if self.z_compensations:
|
||||
pos[2] += self._get_interpolated_z_compensation(
|
||||
pos[0], self.z_compensations,
|
||||
self.compensation_start_x,
|
||||
self.compensation_end_x
|
||||
)
|
||||
def _update_z_compensation_value(self, poslist):
|
||||
for i in range(len(poslist)):
|
||||
pos = poslist[i]
|
||||
zo = 0.
|
||||
if self.z_compensations:
|
||||
zo += self._get_interpolated_z_compensation(
|
||||
pos.test_x, self.z_compensations,
|
||||
self.compensation_start_x,
|
||||
self.compensation_end_x
|
||||
)
|
||||
|
||||
if self.zy_compensations:
|
||||
pos[2] += self._get_interpolated_z_compensation(
|
||||
pos[1], self.zy_compensations,
|
||||
self.compensation_start_y,
|
||||
self.compensation_end_y
|
||||
)
|
||||
if self.zy_compensations:
|
||||
zo += self._get_interpolated_z_compensation(
|
||||
pos.test_y, self.zy_compensations,
|
||||
self.compensation_start_y,
|
||||
self.compensation_end_y
|
||||
)
|
||||
|
||||
pos = manual_probe.ProbeResult(pos.bed_x, pos.bed_y, pos.bed_z + zo,
|
||||
pos.test_x, pos.test_y, pos.test_z)
|
||||
poslist[i] = pos
|
||||
|
||||
def _get_interpolated_z_compensation(
|
||||
self, coord, z_compensations,
|
||||
|
|
@ -101,8 +108,7 @@ class Calibrater:
|
|||
self.gcode = self.printer.lookup_object('gcode')
|
||||
self.probe = None
|
||||
# probe settings are set to none, until they are available
|
||||
self.lift_speed, self.probe_x_offset, self.probe_y_offset, _ = \
|
||||
None, None, None, None
|
||||
self.lift_speed = None
|
||||
self.printer.register_event_handler("klippy:connect",
|
||||
self._handle_connect)
|
||||
self.speed = compensation.speed
|
||||
|
|
@ -129,8 +135,6 @@ class Calibrater:
|
|||
raise self.printer.config_error(
|
||||
"AXIS_TWIST_COMPENSATION requires [probe] to be defined")
|
||||
self.lift_speed = self.probe.get_probe_params()['lift_speed']
|
||||
self.probe_x_offset, self.probe_y_offset, _ = \
|
||||
self.probe.get_offsets()
|
||||
|
||||
def _register_gcode_handlers(self):
|
||||
# register gcode handlers
|
||||
|
|
@ -148,6 +152,7 @@ class Calibrater:
|
|||
|
||||
def cmd_AXIS_TWIST_COMPENSATION_CALIBRATE(self, gcmd):
|
||||
self.gcmd = gcmd
|
||||
probe_x_offset, probe_y_offset, _ = self.probe.get_offsets(gcmd)
|
||||
sample_count = gcmd.get_int('SAMPLE_COUNT', DEFAULT_SAMPLE_COUNT)
|
||||
axis = gcmd.get('AXIS', 'X')
|
||||
|
||||
|
|
@ -219,7 +224,7 @@ class Calibrater:
|
|||
"Invalid axis.")
|
||||
|
||||
probe_points = self._calculate_probe_points(
|
||||
nozzle_points, self.probe_x_offset, self.probe_y_offset)
|
||||
nozzle_points, probe_x_offset, probe_y_offset)
|
||||
|
||||
# verify no other manual probe is in progress
|
||||
manual_probe.verify_no_manual_probe(self.printer)
|
||||
|
|
@ -231,7 +236,7 @@ class Calibrater:
|
|||
self._calibration(probe_points, nozzle_points, interval_dist)
|
||||
|
||||
def _calculate_probe_points(self, nozzle_points,
|
||||
probe_x_offset, probe_y_offset):
|
||||
probe_x_offset, probe_y_offset):
|
||||
# calculate the points to put the nozzle at
|
||||
# returned as a list of tuples
|
||||
probe_points = []
|
||||
|
|
@ -267,7 +272,7 @@ class Calibrater:
|
|||
|
||||
# probe the point
|
||||
pos = probe.run_single_probe(self.probe, self.gcmd)
|
||||
self.current_measured_z = pos[2]
|
||||
self.current_measured_z = pos.bed_z
|
||||
|
||||
# horizontal_move_z (to prevent probe trigger or hitting bed)
|
||||
self._move_helper((None, None, self.horizontal_move_z))
|
||||
|
|
@ -286,14 +291,14 @@ class Calibrater:
|
|||
# returns a callback function for the manual probe
|
||||
is_end = self.current_point_index == len(probe_points) - 1
|
||||
|
||||
def callback(kin_pos):
|
||||
if kin_pos is None:
|
||||
def callback(mpresult):
|
||||
if mpresult is None:
|
||||
# probe was cancelled
|
||||
self.gcmd.respond_info(
|
||||
"AXIS_TWIST_COMPENSATION_CALIBRATE: Probe cancelled, "
|
||||
"calibration aborted")
|
||||
return
|
||||
z_offset = self.current_measured_z - kin_pos[2]
|
||||
z_offset = self.current_measured_z - mpresult.bed_z
|
||||
self.results.append(z_offset)
|
||||
if is_end:
|
||||
# end of calibration
|
||||
|
|
|
|||
|
|
@ -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(',')]
|
||||
|
|
@ -308,7 +308,7 @@ class BedMesh:
|
|||
result["calibration"] = self.bmc.dump_calibration(gcmd)
|
||||
else:
|
||||
result["calibration"] = self.bmc.dump_calibration()
|
||||
offsets = [0, 0, 0] if prb is None else prb.get_offsets()
|
||||
offsets = [0, 0, 0] if prb is None else prb.get_offsets(gcmd)
|
||||
result["probe_offsets"] = offsets
|
||||
result["axis_minimum"] = th_sts["axis_minimum"]
|
||||
result["axis_maximum"] = th_sts["axis_maximum"]
|
||||
|
|
@ -651,9 +651,9 @@ class BedMeshCalibrate:
|
|||
except BedMeshError as e:
|
||||
raise gcmd.error(str(e))
|
||||
self.probe_mgr.start_probe(gcmd)
|
||||
def probe_finalize(self, offsets, positions):
|
||||
z_offset = offsets[2]
|
||||
positions = [[round(p[0], 2), round(p[1], 2), p[2]]
|
||||
def probe_finalize(self, positions):
|
||||
z_offset = 0.
|
||||
positions = [[round(p.bed_x, 2), round(p.bed_y, 2), p.bed_z]
|
||||
for p in positions]
|
||||
if self.probe_mgr.get_zero_ref_mode() == ZrefMode.PROBE:
|
||||
ref_pos = positions.pop()
|
||||
|
|
@ -682,7 +682,7 @@ class BedMeshCalibrate:
|
|||
idx_offset = 0
|
||||
start_idx = 0
|
||||
for i, pts in substitutes.items():
|
||||
fpt = [p - o for p, o in zip(base_points[i], offsets[:2])]
|
||||
fpt = list(base_points[i][:2])
|
||||
# offset the index to account for additional samples
|
||||
idx = i + idx_offset
|
||||
# Add "normal" points
|
||||
|
|
@ -702,7 +702,7 @@ class BedMeshCalibrate:
|
|||
|
||||
# validate length of result
|
||||
if len(base_points) != len(positions):
|
||||
self._dump_points(probed_pts, positions, offsets)
|
||||
self._dump_points(probed_pts, positions)
|
||||
raise self.gcode.error(
|
||||
"bed_mesh: invalid position list size, "
|
||||
"generated count: %d, probed count: %d"
|
||||
|
|
@ -713,7 +713,7 @@ class BedMeshCalibrate:
|
|||
row = []
|
||||
prev_pos = base_points[0]
|
||||
for pos, result in zip(base_points, positions):
|
||||
offset_pos = [p - o for p, o in zip(pos, offsets[:2])]
|
||||
offset_pos = pos[:2]
|
||||
if (
|
||||
not isclose(offset_pos[0], result[0], abs_tol=.5) or
|
||||
not isclose(offset_pos[1], result[1], abs_tol=.5)
|
||||
|
|
@ -786,7 +786,7 @@ class BedMeshCalibrate:
|
|||
self.gcode.respond_info("Mesh Bed Leveling Complete")
|
||||
if self._profile_name is not None:
|
||||
self.bedmesh.save_profile(self._profile_name)
|
||||
def _dump_points(self, probed_pts, corrected_pts, offsets):
|
||||
def _dump_points(self, probed_pts, corrected_pts):
|
||||
# logs generated points with offset applied, points received
|
||||
# from the finalize callback, and the list of corrected points
|
||||
points = self.probe_mgr.get_base_points()
|
||||
|
|
@ -797,7 +797,7 @@ class BedMeshCalibrate:
|
|||
for i in list(range(max_len)):
|
||||
gen_pt = probed_pt = corr_pt = ""
|
||||
if i < len(points):
|
||||
off_pt = [p - o for p, o in zip(points[i], offsets[:2])]
|
||||
off_pt = points[i][:2]
|
||||
gen_pt = "(%.2f, %.2f)" % tuple(off_pt)
|
||||
if i < len(probed_pts):
|
||||
probed_pt = "(%.2f, %.2f, %.4f)" % tuple(probed_pts[i])
|
||||
|
|
@ -914,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
|
||||
|
|
@ -1164,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
|
||||
|
|
@ -1209,7 +1209,7 @@ class RapidScanHelper:
|
|||
gcmd_params["SAMPLE_TIME"] = half_window * 2
|
||||
self._raise_tool(gcmd, scan_height)
|
||||
probe_session = pprobe.start_probe_session(gcmd)
|
||||
offsets = pprobe.get_offsets()
|
||||
offsets = pprobe.get_offsets(gcmd)
|
||||
initial_move = True
|
||||
for pos, is_probe_pt in self.probe_manager.iter_rapid_path():
|
||||
pos = self._apply_offsets(pos[:2], offsets)
|
||||
|
|
@ -1221,7 +1221,7 @@ class RapidScanHelper:
|
|||
probe_session.run_probe(gcmd)
|
||||
results = probe_session.pull_probed_results()
|
||||
toolhead.get_last_move_time()
|
||||
self.finalize_callback(offsets, results)
|
||||
self.finalize_callback(results)
|
||||
probe_session.end_probe_session()
|
||||
|
||||
def _raise_tool(self, gcmd, scan_height):
|
||||
|
|
|
|||
|
|
@ -58,19 +58,17 @@ class BedTiltCalibrate:
|
|||
cmd_BED_TILT_CALIBRATE_help = "Bed tilt calibration script"
|
||||
def cmd_BED_TILT_CALIBRATE(self, gcmd):
|
||||
self.probe_helper.start_probe(gcmd)
|
||||
def probe_finalize(self, offsets, positions):
|
||||
def probe_finalize(self, positions):
|
||||
# Setup for coordinate descent analysis
|
||||
z_offset = offsets[2]
|
||||
logging.info("Calculating bed_tilt with: %s", positions)
|
||||
params = { 'x_adjust': self.bedtilt.x_adjust,
|
||||
'y_adjust': self.bedtilt.y_adjust,
|
||||
'z_adjust': z_offset }
|
||||
'z_adjust': 0. }
|
||||
logging.info("Initial bed_tilt parameters: %s", params)
|
||||
# Perform coordinate descent
|
||||
def adjusted_height(pos, params):
|
||||
x, y, z = pos
|
||||
return (z - x*params['x_adjust'] - y*params['y_adjust']
|
||||
- params['z_adjust'])
|
||||
return (pos.bed_z - pos.bed_x*params['x_adjust']
|
||||
- pos.bed_y*params['y_adjust'] - params['z_adjust'])
|
||||
def errorfunc(params):
|
||||
total_error = 0.
|
||||
for pos in positions:
|
||||
|
|
@ -81,8 +79,7 @@ class BedTiltCalibrate:
|
|||
# Update current bed_tilt calculations
|
||||
x_adjust = new_params['x_adjust']
|
||||
y_adjust = new_params['y_adjust']
|
||||
z_adjust = (new_params['z_adjust'] - z_offset
|
||||
- x_adjust * offsets[0] - y_adjust * offsets[1])
|
||||
z_adjust = new_params['z_adjust']
|
||||
self.bedtilt.update_adjust(x_adjust, y_adjust, z_adjust)
|
||||
# Log and report results
|
||||
logging.info("Calculated bed_tilt parameters: %s", new_params)
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ class BLTouchProbe:
|
|||
config, self, self.mcu_endstop.query_endstop)
|
||||
self.probe_offsets = probe.ProbeOffsetsHelper(config)
|
||||
self.param_helper = probe.ProbeParameterHelper(config)
|
||||
self.homing_helper = probe.HomingViaProbeHelper(config, self,
|
||||
self.param_helper)
|
||||
self.homing_helper = probe.HomingViaProbeHelper(
|
||||
config, self, self.probe_offsets, self.param_helper)
|
||||
self.probe_session = probe.ProbeSessionHelper(
|
||||
config, self.param_helper, self.homing_helper.start_probe_session)
|
||||
# Register BLTOUCH_DEBUG command
|
||||
|
|
@ -80,8 +80,8 @@ class BLTouchProbe:
|
|||
self.handle_connect)
|
||||
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 get_offsets(self, gcmd=None):
|
||||
return self.probe_offsets.get_offsets(gcmd)
|
||||
def get_status(self, eventtime):
|
||||
return self.cmd_helper.get_status(eventtime)
|
||||
def start_probe_session(self, gcmd):
|
||||
|
|
|
|||
|
|
@ -87,8 +87,8 @@ MODE_PERIODIC = 3
|
|||
RUN_GAS = 1 << 4
|
||||
NB_CONV_0 = 0
|
||||
EAS_NEW_DATA = 1 << 7
|
||||
GAS_DONE = 1 << 6
|
||||
MEASURE_DONE = 1 << 5
|
||||
GAS_IN_PROGRESS = 1 << 6
|
||||
MEASURE_IN_PROGRESS = 1 << 5
|
||||
RESET_CHIP_VALUE = 0xB6
|
||||
|
||||
BME_CHIPS = {
|
||||
|
|
@ -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)
|
||||
|
|
@ -511,14 +511,6 @@ class BME280:
|
|||
return comp_press
|
||||
|
||||
def _sample_bme680(self, eventtime):
|
||||
def data_ready(stat, run_gas):
|
||||
new_data = (stat & EAS_NEW_DATA)
|
||||
gas_done = not (stat & GAS_DONE)
|
||||
meas_done = not (stat & MEASURE_DONE)
|
||||
if not run_gas:
|
||||
gas_done = True
|
||||
return new_data and gas_done and meas_done
|
||||
|
||||
run_gas = False
|
||||
# Check VOC once a while
|
||||
if self.reactor.monotonic() - self.last_gas_time > 3:
|
||||
|
|
@ -528,7 +520,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
|
||||
|
|
@ -536,11 +528,14 @@ class BME280:
|
|||
try:
|
||||
# wait until results are ready
|
||||
status = self.read_register('EAS_STATUS_0', 1)[0]
|
||||
while not data_ready(status, run_gas):
|
||||
while status & MEASURE_IN_PROGRESS:
|
||||
self.reactor.pause(
|
||||
self.reactor.monotonic() + self.max_sample_time)
|
||||
status = self.read_register('EAS_STATUS_0', 1)[0]
|
||||
|
||||
# Nothing in progress and no new data
|
||||
if not status & EAS_NEW_DATA:
|
||||
return self.reactor.monotonic() + REPORT_TIME
|
||||
data = self.read_register('PRESSURE_MSB', 8)
|
||||
gas_data = [0, 0]
|
||||
if run_gas:
|
||||
|
|
@ -776,15 +771,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 = {
|
||||
|
|
|
|||
188
klippy/extras/bmi160.py
Normal file
188
klippy/extras/bmi160.py
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
# Support for reading acceleration data from a BMI160 chip
|
||||
#
|
||||
# Copyright (C) 2025 Francisco Stephens <francisco.stephens.g@gmail.com>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging
|
||||
from . import bus, adxl345, bulk_sensor
|
||||
|
||||
# BMI160 registers
|
||||
REG_CHIPID = 0x00
|
||||
REG_ACC_DATA_START = 0x12
|
||||
REG_ACC_CONF = 0x40
|
||||
REG_ACC_RANGE = 0x41
|
||||
REG_FIFO_DOWNS = 0x45
|
||||
REG_FIFO_CONFIG_0 = 0x46
|
||||
REG_FIFO_CONFIG_1 = 0x47
|
||||
REG_FIFO_DATA = 0x24
|
||||
REG_FIFO_LENGTH_0 = 0x22
|
||||
REG_CMD = 0x7E
|
||||
|
||||
REG_MOD_READ = 0x80
|
||||
|
||||
# BMI160 commands for CMD register
|
||||
CMD_ACC_PM_SUSPEND = 0x10
|
||||
CMD_ACC_PM_NORMAL = 0x11
|
||||
CMD_FIFO_FLUSH = 0xB0
|
||||
|
||||
# BMI160 constants
|
||||
BMI160_DEV_ID = 0xD1
|
||||
# Target 1600Hz ODR, normal bandwidth, no undersampling
|
||||
SET_ACC_CONF_1600HZ = 0x2C
|
||||
# Set accelerometer range to +/-16g
|
||||
SET_ACC_RANGE_16G = 0x0C
|
||||
# Enable accelerometer FIFO, headerless mode
|
||||
SET_FIFO_CONFIG_1 = 0x40
|
||||
# No FIFO downsampling
|
||||
SET_FIFO_DOWNS = 0x00
|
||||
|
||||
# Scale factor for +/-16g range (Datasheet: 2048 LSB/g)
|
||||
FREEFALL_ACCEL = 9.80665 * 1000.
|
||||
SCALE = FREEFALL_ACCEL / 2048.
|
||||
|
||||
BATCH_UPDATES = 0.100
|
||||
|
||||
BMI_I2C_ADDR = 0x69
|
||||
|
||||
# Printer class that controls BMI160 chip
|
||||
class BMI160:
|
||||
def __init__(self, config):
|
||||
self.printer = config.get_printer()
|
||||
self.reactor = self.printer.get_reactor()
|
||||
adxl345.AccelCommandHelper(config, self)
|
||||
self.axes_map = adxl345.read_axes_map(config, SCALE, SCALE, SCALE)
|
||||
self.data_rate = 1600
|
||||
# Setup mcu sensor_bmi160 bulk query code
|
||||
# Check for SPI or I2C
|
||||
if config.get('cs_pin', None) is not None:
|
||||
# Using 1MHz to match working Arduino test
|
||||
self.bus = bus.MCU_SPI_from_config(config, 0, default_speed=1000000)
|
||||
self.bus_type = 'spi'
|
||||
else:
|
||||
self.bus = bus.MCU_I2C_from_config(config,
|
||||
default_addr=BMI_I2C_ADDR, default_speed=400000)
|
||||
self.bus_type = 'i2c'
|
||||
self.mcu = mcu = self.bus.get_mcu()
|
||||
self.oid = oid = mcu.create_oid()
|
||||
self.query_bmi160_cmd = None
|
||||
mcu.add_config_cmd("config_bmi160 oid=%d bus_oid=%d bus_oid_type=%s"
|
||||
% (oid, self.bus.get_oid(), self.bus_type))
|
||||
mcu.add_config_cmd("query_bmi160 oid=%d rest_ticks=0"
|
||||
% (oid,), on_restart=True)
|
||||
mcu.register_config_callback(self._build_config)
|
||||
# Bulk sample message reading
|
||||
chip_smooth = self.data_rate * BATCH_UPDATES * 2
|
||||
self.ffreader = bulk_sensor.FixedFreqReader(mcu, chip_smooth, "<hhh")
|
||||
self.last_error_count = 0
|
||||
# Process messages in batches
|
||||
self.batch_bulk = bulk_sensor.BatchBulkHelper(
|
||||
self.printer, self._process_batch,
|
||||
self._start_measurements, self._finish_measurements, BATCH_UPDATES)
|
||||
self.name = config.get_name().split()[-1]
|
||||
hdr = ('time', 'x_acceleration', 'y_acceleration', 'z_acceleration')
|
||||
self.batch_bulk.add_mux_endpoint("bmi160/dump_bmi160", "sensor",
|
||||
self.name, {'header': hdr})
|
||||
def _build_config(self):
|
||||
cmdqueue = self.bus.get_command_queue()
|
||||
self.query_bmi160_cmd = self.mcu.lookup_command(
|
||||
"query_bmi160 oid=%c rest_ticks=%u", cq=cmdqueue)
|
||||
self.ffreader.setup_query_command("query_bmi160_status oid=%c",
|
||||
oid=self.oid, cq=cmdqueue)
|
||||
def read_reg(self, reg):
|
||||
if self.bus_type == 'spi':
|
||||
params = self.bus.spi_transfer([reg | REG_MOD_READ, 0x00])
|
||||
response = bytearray(params['response'])
|
||||
return response[1]
|
||||
else:
|
||||
params = self.bus.i2c_read([reg], 1)
|
||||
return bytearray(params['response'])[0]
|
||||
def set_reg(self, reg, val, minclock=0):
|
||||
if self.bus_type == 'spi':
|
||||
# spi_transfer to ensure command completes on MCU before continuing
|
||||
self.bus.spi_transfer([reg, val & 0xFF], minclock=minclock)
|
||||
else:
|
||||
# I2C already waits for completion by default
|
||||
self.bus.i2c_write([reg, val & 0xFF], minclock=minclock)
|
||||
# Small delay between register writes for stability
|
||||
self.reactor.pause(0.002)
|
||||
# Don't verify CMD register (0x7E) or registers below 0x40
|
||||
if reg >= 0x40 and reg != REG_CMD:
|
||||
stored_val = self.read_reg(reg)
|
||||
if stored_val != val:
|
||||
raise self.printer.command_error(
|
||||
"Failed to set BMI160 register [0x%x] to 0x%x: "
|
||||
"got 0x%x. This is generally indicative of connection "
|
||||
"problems (e.g. faulty wiring) or a faulty bmi160 "
|
||||
"chip." % (reg, val, stored_val))
|
||||
def start_internal_client(self):
|
||||
aqh = adxl345.AccelQueryHelper(self.printer)
|
||||
self.batch_bulk.add_client(aqh.handle_batch)
|
||||
return aqh
|
||||
def _convert_samples(self, samples):
|
||||
(x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map
|
||||
count = 0
|
||||
for ptime, rx, ry, rz in samples:
|
||||
raw_xyz = (rx, ry, rz)
|
||||
x = round(raw_xyz[x_pos] * x_scale, 6)
|
||||
y = round(raw_xyz[y_pos] * y_scale, 6)
|
||||
z = round(raw_xyz[z_pos] * z_scale, 6)
|
||||
samples[count] = (round(ptime, 6), x, y, z)
|
||||
count += 1
|
||||
del samples[count:]
|
||||
def _start_measurements(self):
|
||||
# 1. Force SPI Mode (Dummy Read)
|
||||
if self.bus_type == 'spi':
|
||||
self.read_reg(0x7F)
|
||||
self.reactor.pause(0.010) # 10ms for mode switch
|
||||
|
||||
# 2. Verify ID
|
||||
dev_id = self.read_reg(REG_CHIPID)
|
||||
if dev_id != BMI160_DEV_ID:
|
||||
raise self.printer.command_error(
|
||||
"Invalid bmi160 id (got %x vs %x).\n"
|
||||
"This is generally indicative of connection problems\n"
|
||||
"(e.g. faulty wiring) or a faulty bmi160 chip."
|
||||
% (dev_id, BMI160_DEV_ID))
|
||||
|
||||
# 3. Wake Up FIRST
|
||||
# Send Normal Mode command
|
||||
self.set_reg(REG_CMD, CMD_ACC_PM_NORMAL)
|
||||
# CRITICAL: Wait 50ms for startup/PLL locking
|
||||
self.reactor.pause(0.050)
|
||||
|
||||
# 4. Configure Registers (While Awake)
|
||||
self.set_reg(REG_ACC_CONF, SET_ACC_CONF_1600HZ)
|
||||
self.set_reg(REG_ACC_RANGE, SET_ACC_RANGE_16G)
|
||||
self.set_reg(REG_FIFO_DOWNS, SET_FIFO_DOWNS)
|
||||
self.set_reg(REG_FIFO_CONFIG_1, SET_FIFO_CONFIG_1)
|
||||
|
||||
# 5. Flush FIFO
|
||||
self.set_reg(REG_CMD, CMD_FIFO_FLUSH)
|
||||
self.reactor.pause(0.010)
|
||||
|
||||
# 6. Start Bulk Reading
|
||||
# Start timer roughly immediately
|
||||
rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate)
|
||||
self.query_bmi160_cmd.send([self.oid, rest_ticks])
|
||||
logging.info("BMI160 starting '%s' measurements", self.name)
|
||||
self.ffreader.note_start()
|
||||
self.last_error_count = 0
|
||||
|
||||
def _finish_measurements(self):
|
||||
self.set_reg(REG_CMD, CMD_ACC_PM_SUSPEND)
|
||||
self.query_bmi160_cmd.send_wait_ack([self.oid, 0])
|
||||
self.ffreader.note_end()
|
||||
logging.info("BMI160 finished '%s' measurements", self.name)
|
||||
def _process_batch(self, eventtime):
|
||||
samples = self.ffreader.pull_samples()
|
||||
self._convert_samples(samples)
|
||||
if not samples:
|
||||
return {}
|
||||
return {'data': samples, 'errors': self.last_error_count,
|
||||
'overflows': self.ffreader.get_last_overflows()}
|
||||
|
||||
def load_config(config):
|
||||
return BMI160(config)
|
||||
|
||||
def load_config_prefix(config):
|
||||
return BMI160(config)
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import mcu
|
||||
import logging
|
||||
|
||||
def resolve_bus_name(mcu, param, bus):
|
||||
# Find enumerations for the given bus
|
||||
|
|
@ -91,6 +92,9 @@ class MCU_SPI:
|
|||
"mode=%u pulse_ticks=%u"):
|
||||
pulse_ticks = self.mcu.seconds_to_clock(1./self.speed)
|
||||
self.config_fmt = self.config_fmt_ticks % (pulse_ticks,)
|
||||
else:
|
||||
configfile = self.mcu.get_printer().lookup_object('configfile')
|
||||
configfile.deprecate_mcu_code(self.mcu, 'spi_set_sw_bus')
|
||||
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)
|
||||
|
|
@ -155,12 +159,14 @@ def MCU_SPI_from_config(config, mode, pin_option="cs_pin",
|
|||
|
||||
# Helper code for working with devices connected to an MCU via an I2C bus
|
||||
class MCU_I2C:
|
||||
def __init__(self, mcu, bus, addr, speed, sw_pins=None):
|
||||
def __init__(self, mcu, bus, addr, speed, sw_pins=None,
|
||||
async_write_only=False):
|
||||
self.mcu = mcu
|
||||
self.bus = bus
|
||||
self.i2c_address = addr
|
||||
self.oid = self.mcu.create_oid()
|
||||
self.speed = speed
|
||||
self.async_write_only = async_write_only
|
||||
self.config_fmt_ticks = None
|
||||
mcu.add_config_cmd("config_i2c oid=%d" % (self.oid,))
|
||||
# Generate I2C bus config message
|
||||
|
|
@ -180,6 +186,16 @@ 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
|
||||
self.i2c_transfer_cmd = self.i2c_write_only_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 = []
|
||||
# Host side I2C error handling
|
||||
self._configured = False
|
||||
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):
|
||||
|
|
@ -188,6 +204,13 @@ class MCU_I2C:
|
|||
return self.i2c_address
|
||||
def get_command_queue(self):
|
||||
return self.cmd_queue
|
||||
def _async_write_status(self, params):
|
||||
status = params["i2c_bus_status"]
|
||||
if status == "SUCCESS":
|
||||
return
|
||||
err_msg = "MCU '%s' I2C request to addr %i reports error %s" % (
|
||||
self.mcu.get_name(), self.i2c_address, status)
|
||||
logging.error(err_msg)
|
||||
def build_config(self):
|
||||
if '%' in self.config_fmt:
|
||||
bus = resolve_bus_name(self.mcu, "i2c_bus", self.bus)
|
||||
|
|
@ -198,29 +221,84 @@ class MCU_I2C:
|
|||
" 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,)
|
||||
else:
|
||||
configfile = self.mcu.get_printer().lookup_object('configfile')
|
||||
configfile.deprecate_mcu_code(self.mcu, 'i2c_set_sw_bus')
|
||||
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)
|
||||
self.i2c_read_cmd = self.mcu.lookup_query_command(
|
||||
"i2c_read oid=%c reg=%*s read_len=%u",
|
||||
"i2c_read_response oid=%c response=%*s", oid=self.oid,
|
||||
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)
|
||||
if self.mcu.try_lookup_command("i2c_read oid=%c reg=%*s read_len=%u"):
|
||||
self.i2c_write_cmd = self.mcu.lookup_command(
|
||||
"i2c_write oid=%c data=%*s", cq=self.cmd_queue)
|
||||
self.i2c_write_only_cmd = self.i2c_write_cmd
|
||||
self.i2c_read_cmd = self.mcu.lookup_query_command(
|
||||
"i2c_read oid=%c reg=%*s read_len=%u",
|
||||
"i2c_read_response oid=%c response=%*s", oid=self.oid,
|
||||
cq=self.cmd_queue)
|
||||
configfile = self.mcu.get_printer().lookup_object('configfile')
|
||||
configfile.deprecate_mcu_code(self.mcu, 'i2c_transfer')
|
||||
else:
|
||||
self.i2c_transfer_cmd = self.mcu.lookup_query_command(
|
||||
"i2c_transfer oid=%c write=%*s read_len=%u",
|
||||
"i2c_response oid=%c i2c_bus_status=%c response=%*s",
|
||||
oid=self.oid, cq=self.cmd_queue)
|
||||
self.i2c_write_only_cmd = self.mcu.lookup_command(
|
||||
"i2c_transfer oid=%c write=%*s read_len=%u",
|
||||
cq=self.cmd_queue)
|
||||
if self.mcu.is_fileoutput():
|
||||
self.i2c_transfer_cmd = self.mcu.lookup_command(
|
||||
"i2c_transfer oid=%c write=%*s read_len=%u",
|
||||
cq=self.cmd_queue)
|
||||
if self.async_write_only:
|
||||
self.mcu.register_response(self._async_write_status,
|
||||
"i2c_response", self.oid)
|
||||
self._configured = True
|
||||
def i2c_write_noack(self, data, minclock=0, reqclock=0):
|
||||
if self.async_write_only:
|
||||
self.i2c_write_only_cmd.send([self.oid, data, 0],
|
||||
minclock=minclock, reqclock=reqclock)
|
||||
return
|
||||
self.i2c_write_cmd.send([self.oid, data],
|
||||
minclock=minclock, reqclock=reqclock)
|
||||
def i2c_write_wait_ack(self, data, minclock=0, reqclock=0):
|
||||
def i2c_write(self, data, minclock=0, reqclock=0, retry=True):
|
||||
if not self._configured:
|
||||
self._to_write.append(data)
|
||||
return
|
||||
if self.async_write_only:
|
||||
self.i2c_write_only_cmd.send([self.oid, data, 0],
|
||||
minclock=minclock, reqclock=reqclock)
|
||||
return
|
||||
if self.i2c_transfer_cmd is not None:
|
||||
self.i2c_transfer(data, minclock=minclock, reqclock=reqclock,
|
||||
retry=retry)
|
||||
return
|
||||
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):
|
||||
if self.async_write_only:
|
||||
raise mcu.error("I2C dev is write only")
|
||||
if self.i2c_transfer_cmd is not None:
|
||||
return self.i2c_transfer(write, read_len, retry=retry)
|
||||
return self.i2c_read_cmd.send([self.oid, write, read_len],
|
||||
retry=retry)
|
||||
def i2c_transfer(self, write, read_len=0, minclock=0, reqclock=0,
|
||||
retry=True):
|
||||
if self.async_write_only:
|
||||
raise mcu.error("I2C dev is write only")
|
||||
cmd = self.i2c_transfer_cmd
|
||||
if self.mcu.is_fileoutput():
|
||||
cmd.send([self.oid, write, read_len],
|
||||
minclock=minclock, reqclock=reqclock)
|
||||
return
|
||||
param = cmd.send([self.oid, write, read_len],
|
||||
minclock=minclock, reqclock=reqclock, retry=retry)
|
||||
status = param["i2c_bus_status"]
|
||||
if status == "SUCCESS":
|
||||
return param
|
||||
err_msg = "MCU '%s' I2C request to addr %i reports error %s" % (
|
||||
self.mcu.get_name(), self.i2c_address, status)
|
||||
self.mcu.get_printer().invoke_shutdown(err_msg)
|
||||
|
||||
def MCU_I2C_from_config(config, default_addr=None, default_speed=100000):
|
||||
def MCU_I2C_from_config(config, default_addr=None, default_speed=100000,
|
||||
async_write_only=False):
|
||||
# Load bus parameters
|
||||
printer = config.get_printer()
|
||||
i2c_mcu = mcu.get_printer_mcu(printer, config.get('i2c_mcu', 'mcu'))
|
||||
|
|
@ -236,13 +314,17 @@ def MCU_I2C_from_config(config, default_addr=None, default_speed=100000):
|
|||
for name in ['scl', 'sda']]
|
||||
sw_pin_params = [ppins.lookup_pin(config.get(name), share_type=name)
|
||||
for name in sw_pin_names]
|
||||
for pin_params in sw_pin_params:
|
||||
if pin_params['chip'] != i2c_mcu:
|
||||
raise ppins.error("%s: i2c pins must be on same mcu" % (
|
||||
config.get_name(),))
|
||||
sw_pins = tuple([pin_params['pin'] for pin_params in sw_pin_params])
|
||||
bus = None
|
||||
else:
|
||||
bus = config.get('i2c_bus', None)
|
||||
sw_pins = None
|
||||
# Create MCU_I2C object
|
||||
return MCU_I2C(i2c_mcu, bus, addr, speed, sw_pins)
|
||||
return MCU_I2C(i2c_mcu, bus, addr, speed, sw_pins, async_write_only)
|
||||
|
||||
|
||||
######################################################################
|
||||
|
|
|
|||
|
|
@ -86,10 +86,11 @@ class MCU_buttons:
|
|||
# ADC button tracking
|
||||
######################################################################
|
||||
|
||||
ADC_REPORT_TIME = 0.015
|
||||
ADC_REPORT_TIME = 0.010
|
||||
ADC_DEBOUNCE_TIME = 0.025
|
||||
ADC_SAMPLE_TIME = 0.001
|
||||
ADC_SAMPLE_COUNT = 6
|
||||
ADC_BATCH_COUNT = 5
|
||||
|
||||
class MCU_ADC_buttons:
|
||||
def __init__(self, printer, pin, pullup):
|
||||
|
|
@ -104,8 +105,10 @@ class MCU_ADC_buttons:
|
|||
self.max_value = 0.
|
||||
ppins = printer.lookup_object('pins')
|
||||
self.mcu_adc = ppins.setup_pin('adc', self.pin)
|
||||
self.mcu_adc.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
|
||||
self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback)
|
||||
self.mcu_adc.setup_adc_sample(ADC_REPORT_TIME,
|
||||
ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT,
|
||||
batch_num=ADC_BATCH_COUNT)
|
||||
self.mcu_adc.setup_adc_callback(self.adc_callback)
|
||||
query_adc = printer.lookup_object('query_adc')
|
||||
query_adc.register_adc('adc_button:' + pin.strip(), self.mcu_adc)
|
||||
|
||||
|
|
@ -114,35 +117,36 @@ class MCU_ADC_buttons:
|
|||
self.max_value = max(self.max_value, max_value)
|
||||
self.buttons.append((min_value, max_value, callback))
|
||||
|
||||
def adc_callback(self, read_time, read_value):
|
||||
adc = max(.00001, min(.99999, read_value))
|
||||
value = self.pullup * adc / (1.0 - adc)
|
||||
def adc_callback(self, samples):
|
||||
for read_time, read_value in samples:
|
||||
adc = max(.00001, min(.99999, read_value))
|
||||
value = self.pullup * adc / (1.0 - adc)
|
||||
|
||||
# Determine button pressed
|
||||
btn = None
|
||||
if self.min_value <= value <= self.max_value:
|
||||
for i, (min_value, max_value, cb) in enumerate(self.buttons):
|
||||
if min_value < value < max_value:
|
||||
btn = i
|
||||
break
|
||||
# Determine button pressed
|
||||
btn = None
|
||||
if self.min_value <= value <= self.max_value:
|
||||
for i, (min_value, max_value, cb) in enumerate(self.buttons):
|
||||
if min_value < value < max_value:
|
||||
btn = i
|
||||
break
|
||||
|
||||
# If the button changed, due to noise or pressing:
|
||||
if btn != self.last_button:
|
||||
# reset the debouncing timer
|
||||
self.last_debouncetime = read_time
|
||||
# If the button changed, due to noise or pressing:
|
||||
if btn != self.last_button:
|
||||
# reset the debouncing timer
|
||||
self.last_debouncetime = read_time
|
||||
|
||||
# button debounce check & new button pressed
|
||||
if ((read_time - self.last_debouncetime) >= ADC_DEBOUNCE_TIME
|
||||
and self.last_button == btn and self.last_pressed != btn):
|
||||
# release last_pressed
|
||||
if self.last_pressed is not None:
|
||||
self.call_button(self.last_pressed, False)
|
||||
self.last_pressed = None
|
||||
if btn is not None:
|
||||
self.call_button(btn, True)
|
||||
self.last_pressed = btn
|
||||
# button debounce check & new button pressed
|
||||
if ((read_time - self.last_debouncetime) >= ADC_DEBOUNCE_TIME
|
||||
and self.last_button == btn and self.last_pressed != btn):
|
||||
# release last_pressed
|
||||
if self.last_pressed is not None:
|
||||
self.call_button(self.last_pressed, False)
|
||||
self.last_pressed = None
|
||||
if btn is not None:
|
||||
self.call_button(btn, True)
|
||||
self.last_pressed = btn
|
||||
|
||||
self.last_button = btn
|
||||
self.last_button = btn
|
||||
|
||||
def call_button(self, button, state):
|
||||
minval, maxval, callback = self.buttons[button]
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ class PrinterCANBusStats:
|
|||
self.mcu = self.printer.lookup_object(mcu_name)
|
||||
# Lookup status query command
|
||||
if self.mcu.try_lookup_command("get_canbus_status") is None:
|
||||
configfile = self.printer.lookup_object('configfile')
|
||||
configfile.deprecate_mcu_code(self.mcu, 'get_canbus_status')
|
||||
return
|
||||
self.get_canbus_status_cmd = self.mcu.lookup_query_command(
|
||||
"get_canbus_status",
|
||||
|
|
|
|||
|
|
@ -152,12 +152,12 @@ class DeltaCalibrate:
|
|||
"%.3f,%.3f,%.3f" % tuple(spos1))
|
||||
configfile.set(section, "distance%d_pos2" % (i,),
|
||||
"%.3f,%.3f,%.3f" % tuple(spos2))
|
||||
def probe_finalize(self, offsets, positions):
|
||||
def probe_finalize(self, positions):
|
||||
# Convert positions into (z_offset, stable_position) pairs
|
||||
z_offset = offsets[2]
|
||||
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
||||
delta_params = kin.get_calibration()
|
||||
probe_positions = [(z_offset, delta_params.calc_stable_position(p))
|
||||
csp = kin.get_calibration().calc_stable_position
|
||||
probe_positions = [(p.test_z - p.bed_z,
|
||||
csp([p.test_x, p.test_y, p.test_z]))
|
||||
for p in positions]
|
||||
# Perform analysis
|
||||
self.calculate_params(probe_positions, self.last_distances)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -236,6 +236,8 @@ class PrinterLCD:
|
|||
except:
|
||||
logging.exception("Error during display screen update")
|
||||
self.lcd_chip.flush()
|
||||
if self.redraw_request_pending:
|
||||
return self.redraw_time
|
||||
return eventtime + REDRAW_TIME
|
||||
def request_redraw(self):
|
||||
if self.redraw_request_pending:
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -130,7 +130,8 @@ class SPI4wire:
|
|||
class I2C:
|
||||
def __init__(self, config, default_addr):
|
||||
self.i2c = bus.MCU_I2C_from_config(config, default_addr=default_addr,
|
||||
default_speed=400000)
|
||||
default_speed=400000,
|
||||
async_write_only=True)
|
||||
def send(self, cmds, is_data=False):
|
||||
if is_data:
|
||||
hdr = 0x40
|
||||
|
|
@ -138,7 +139,7 @@ class I2C:
|
|||
hdr = 0x00
|
||||
cmds = bytearray(cmds)
|
||||
cmds.insert(0, hdr)
|
||||
self.i2c.i2c_write(cmds, reqclock=BACKGROUND_PRIORITY_CLOCK)
|
||||
self.i2c.i2c_write_noack(cmds, reqclock=BACKGROUND_PRIORITY_CLOCK)
|
||||
|
||||
# Helper code for toggling a reset pin on startup
|
||||
class ResetHelper:
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ class PrinterMCUError:
|
|||
def __init__(self, config):
|
||||
self.printer = config.get_printer()
|
||||
self.clarify_callbacks = {}
|
||||
self.printer.register_event_handler("klippy:notify_mcu_shutdown",
|
||||
self._handle_notify_mcu_shutdown)
|
||||
self.printer.register_event_handler("klippy:analyze_shutdown",
|
||||
self._handle_analyze_shutdown)
|
||||
self.printer.register_event_handler("klippy:notify_mcu_error",
|
||||
self._handle_notify_mcu_error)
|
||||
def add_clarify(self, msg, callback):
|
||||
|
|
@ -88,11 +88,14 @@ class PrinterMCUError:
|
|||
newmsg = "%s%s%s%s%s" % (prefix, mcu_msg, clarify_msg,
|
||||
hint, message_shutdown)
|
||||
self.printer.update_error_msg(msg, newmsg)
|
||||
def _handle_notify_mcu_shutdown(self, msg, details):
|
||||
def _handle_analyze_shutdown(self, msg, details):
|
||||
if msg == "MCU shutdown":
|
||||
self._check_mcu_shutdown(msg, details)
|
||||
else:
|
||||
self.printer.update_error_msg(msg, "%s%s" % (msg, message_shutdown))
|
||||
# Report reactor info (no good place to do this, so done here)
|
||||
logging.info("Reactor garbage collection: %s",
|
||||
self.printer.get_reactor().get_gc_stats())
|
||||
def _check_protocol_error(self, msg, details):
|
||||
host_version = self.printer.start_args['software_version']
|
||||
msg_update = []
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ class ExcludeObject:
|
|||
offset = [0.] * num_coord
|
||||
self.extrusion_offsets[ename] = offset
|
||||
if len(offset) < num_coord:
|
||||
offset.extend([0.] * (len(num_coord) - len(offset)))
|
||||
offset.extend([0.] * (num_coord - len(offset)))
|
||||
return offset
|
||||
|
||||
def get_position(self):
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ class PrinterExtruderStepper:
|
|||
self.handle_connect)
|
||||
def handle_connect(self):
|
||||
self.extruder_stepper.sync_to_extruder(self.extruder_name)
|
||||
def find_past_position(self, print_time):
|
||||
return self.extruder_stepper.find_past_position(print_time)
|
||||
def get_status(self, eventtime):
|
||||
return self.extruder_stepper.get_status(eventtime)
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class Fan:
|
|||
self.last_req_value = value
|
||||
self.last_fan_value = self.max_power
|
||||
self.mcu_fan.set_pwm(print_time, self.max_power)
|
||||
return "delay", self.kick_start_time
|
||||
return "repeat", print_time + self.kick_start_time
|
||||
self.last_fan_value = self.last_req_value = value
|
||||
self.mcu_fan.set_pwm(print_time, value)
|
||||
def set_speed(self, value, print_time=None):
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Utility for manually moving a stepper for diagnostic purposes
|
||||
#
|
||||
# Copyright (C) 2018-2019 Kevin O'Connor <kevin@koconnor.net>
|
||||
# Copyright (C) 2018-2025 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import math, logging
|
||||
|
|
@ -10,7 +10,6 @@ BUZZ_DISTANCE = 1.
|
|||
BUZZ_VELOCITY = BUZZ_DISTANCE / .250
|
||||
BUZZ_RADIANS_DISTANCE = math.radians(1.)
|
||||
BUZZ_RADIANS_VELOCITY = BUZZ_RADIANS_DISTANCE / .250
|
||||
STALL_TIME = 0.100
|
||||
|
||||
# Calculate a move's accel_t, cruise_t, and cruise_v
|
||||
def calc_move_time(dist, speed, accel):
|
||||
|
|
@ -33,47 +32,46 @@ 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
|
||||
gcode = self.printer.lookup_object('gcode')
|
||||
gcode.register_command('STEPPER_BUZZ', self.cmd_STEPPER_BUZZ,
|
||||
desc=self.cmd_STEPPER_BUZZ_help)
|
||||
if config.getboolean("enable_force_move", False):
|
||||
gcode.register_command('FORCE_MOVE', self.cmd_FORCE_MOVE,
|
||||
desc=self.cmd_FORCE_MOVE_help)
|
||||
self._enable_force_move = config.getboolean("enable_force_move", False)
|
||||
if self._enable_force_move:
|
||||
gcode = self.printer.lookup_object('gcode')
|
||||
gcode.register_command('SET_KINEMATIC_POSITION',
|
||||
self.cmd_SET_KINEMATIC_POSITION,
|
||||
desc=self.cmd_SET_KINEMATIC_POSITION_help)
|
||||
def register_stepper(self, config, mcu_stepper):
|
||||
self.steppers[mcu_stepper.get_name()] = mcu_stepper
|
||||
name = mcu_stepper.get_name()
|
||||
self.steppers[name] = mcu_stepper
|
||||
# Reuse mux helper args checks
|
||||
gcode = self.printer.lookup_object('gcode')
|
||||
gcode.register_mux_command('STEPPER_BUZZ', "STEPPER", name,
|
||||
self.cmd_STEPPER_BUZZ,
|
||||
desc=self.cmd_STEPPER_BUZZ_help)
|
||||
if self._enable_force_move:
|
||||
gcode.register_mux_command('FORCE_MOVE', "STEPPER", name,
|
||||
self.cmd_FORCE_MOVE,
|
||||
desc=self.cmd_FORCE_MOVE_help)
|
||||
def lookup_stepper(self, name):
|
||||
if name not in self.steppers:
|
||||
raise self.printer.config_error("Unknown stepper %s" % (name,))
|
||||
return self.steppers[name]
|
||||
def _force_enable(self, stepper):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
print_time = toolhead.get_last_move_time()
|
||||
stepper_name = stepper.get_name()
|
||||
stepper_enable = self.printer.lookup_object('stepper_enable')
|
||||
enable = stepper_enable.lookup_enable(stepper.get_name())
|
||||
was_enable = enable.is_motor_enabled()
|
||||
if not was_enable:
|
||||
enable.motor_enable(print_time)
|
||||
toolhead.dwell(STALL_TIME)
|
||||
return was_enable
|
||||
def _restore_enable(self, stepper, was_enable):
|
||||
if not was_enable:
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.dwell(STALL_TIME)
|
||||
print_time = toolhead.get_last_move_time()
|
||||
stepper_enable = self.printer.lookup_object('stepper_enable')
|
||||
enable = stepper_enable.lookup_enable(stepper.get_name())
|
||||
enable.motor_disable(print_time)
|
||||
toolhead.dwell(STALL_TIME)
|
||||
did_enable = stepper_enable.set_motors_enable([stepper_name], True)
|
||||
return did_enable
|
||||
def _restore_enable(self, stepper, did_enable):
|
||||
if not did_enable:
|
||||
return
|
||||
stepper_name = stepper.get_name()
|
||||
stepper_enable = self.printer.lookup_object('stepper_enable')
|
||||
stepper_enable.set_motors_enable([stepper_name], False)
|
||||
def manual_move(self, stepper, dist, speed, accel=0.):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.flush_step_generation()
|
||||
|
|
@ -85,24 +83,17 @@ 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)
|
||||
self.motion_queuing.note_mcu_movequeue_activity(print_time)
|
||||
toolhead.dwell(accel_t + cruise_t + accel_t)
|
||||
toolhead.flush_step_generation()
|
||||
def _lookup_stepper(self, gcmd):
|
||||
name = gcmd.get('STEPPER')
|
||||
if name not in self.steppers:
|
||||
raise gcmd.error("Unknown stepper %s" % (name,))
|
||||
return self.steppers[name]
|
||||
stepper.set_trapq(prev_trapq)
|
||||
stepper.set_stepper_kinematics(prev_sk)
|
||||
self.motion_queuing.wipe_trapq(self.trapq)
|
||||
cmd_STEPPER_BUZZ_help = "Oscillate a given stepper to help id it"
|
||||
def cmd_STEPPER_BUZZ(self, gcmd):
|
||||
stepper = self._lookup_stepper(gcmd)
|
||||
stepper = self.lookup_stepper(gcmd.get('STEPPER'))
|
||||
logging.info("Stepper buzz %s", stepper.get_name())
|
||||
was_enable = self._force_enable(stepper)
|
||||
did_enable = self._force_enable(stepper)
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
dist, speed = BUZZ_DISTANCE, BUZZ_VELOCITY
|
||||
if stepper.units_in_radians():
|
||||
|
|
@ -112,10 +103,10 @@ class ForceMove:
|
|||
toolhead.dwell(.050)
|
||||
self.manual_move(stepper, -dist, speed)
|
||||
toolhead.dwell(.450)
|
||||
self._restore_enable(stepper, was_enable)
|
||||
self._restore_enable(stepper, did_enable)
|
||||
cmd_FORCE_MOVE_help = "Manually move a stepper; invalidates kinematics"
|
||||
def cmd_FORCE_MOVE(self, gcmd):
|
||||
stepper = self._lookup_stepper(gcmd)
|
||||
stepper = self.lookup_stepper(gcmd.get('STEPPER'))
|
||||
distance = gcmd.get_float('DISTANCE')
|
||||
speed = gcmd.get_float('VELOCITY', above=0.)
|
||||
accel = gcmd.get_float('ACCEL', 0., minval=0.)
|
||||
|
|
|
|||
|
|
@ -24,9 +24,12 @@ class GetStatusWrapper:
|
|||
po = self.printer.lookup_object(sval, None)
|
||||
if po is None or not hasattr(po, 'get_status'):
|
||||
raise KeyError(val)
|
||||
reactor = self.printer.get_reactor()
|
||||
if self.eventtime is None:
|
||||
self.eventtime = self.printer.get_reactor().monotonic()
|
||||
self.cache[sval] = res = copy.deepcopy(po.get_status(self.eventtime))
|
||||
self.eventtime = reactor.monotonic()
|
||||
with reactor.assert_no_pause():
|
||||
sts = po.get_status(self.eventtime)
|
||||
self.cache[sval] = res = copy.deepcopy(sts)
|
||||
return res
|
||||
def __contains__(self, val):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -8,20 +8,6 @@ import logging
|
|||
class GCodeMove:
|
||||
def __init__(self, config):
|
||||
self.printer = printer = config.get_printer()
|
||||
printer.register_event_handler("klippy:ready", self._handle_ready)
|
||||
printer.register_event_handler("klippy:shutdown", self._handle_shutdown)
|
||||
printer.register_event_handler("toolhead:set_position",
|
||||
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",
|
||||
self._handle_activate_extruder)
|
||||
printer.register_event_handler("homing:home_rails_end",
|
||||
self._handle_home_rails_end)
|
||||
self.is_printer_ready = False
|
||||
# Register g-code commands
|
||||
gcode = printer.lookup_object('gcode')
|
||||
|
|
@ -52,6 +38,23 @@ class GCodeMove:
|
|||
self.saved_states = {}
|
||||
self.move_transform = self.move_with_transform = None
|
||||
self.position_with_transform = (lambda: [0., 0., 0., 0.])
|
||||
# Register callbacks
|
||||
printer.register_event_handler("klippy:ready", self._handle_ready)
|
||||
printer.register_event_handler("klippy:shutdown", self._handle_shutdown)
|
||||
printer.register_event_handler("klippy:analyze_shutdown",
|
||||
self._handle_analyze_shutdown)
|
||||
printer.register_event_handler("toolhead:set_position",
|
||||
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",
|
||||
self._handle_activate_extruder)
|
||||
printer.register_event_handler("homing:home_rails_end",
|
||||
self._handle_home_rails_end)
|
||||
def _handle_ready(self):
|
||||
self.is_printer_ready = True
|
||||
if self.move_transform is None:
|
||||
|
|
@ -60,9 +63,8 @@ class GCodeMove:
|
|||
self.position_with_transform = toolhead.get_position
|
||||
self.reset_last_position()
|
||||
def _handle_shutdown(self):
|
||||
if not self.is_printer_ready:
|
||||
return
|
||||
self.is_printer_ready = False
|
||||
def _handle_analyze_shutdown(self, msg, details):
|
||||
logging.info("gcode state: absolute_coord=%s absolute_extrude=%s"
|
||||
" base_position=%s last_position=%s homing_position=%s"
|
||||
" speed_factor=%s extrude_factor=%s speed=%s",
|
||||
|
|
@ -105,9 +107,10 @@ class GCodeMove:
|
|||
'extrude_factor': self.extrude_factor,
|
||||
'absolute_coordinates': self.absolute_coord,
|
||||
'absolute_extrude': self.absolute_extrude,
|
||||
'homing_origin': self.Coord(*self.homing_position[:4]),
|
||||
'position': self.Coord(*self.last_position[:4]),
|
||||
'gcode_position': self.Coord(*move_position[:4]),
|
||||
'homing_origin': self.Coord(self.homing_position),
|
||||
'position': self.Coord(self.last_position),
|
||||
'gcode_position': self.Coord(move_position),
|
||||
'axis_map': self.axis_map,
|
||||
}
|
||||
def reset_last_position(self):
|
||||
if self.is_printer_ready:
|
||||
|
|
@ -120,7 +123,8 @@ class GCodeMove:
|
|||
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":
|
||||
if (gcode_id is None or len(gcode_id) != 1 or not gcode_id.isupper()
|
||||
or gcode_id in axis_map or gcode_id in "FN"):
|
||||
continue
|
||||
axis_map[gcode_id] = index
|
||||
self.axis_map = axis_map
|
||||
|
|
@ -187,7 +191,7 @@ class GCodeMove:
|
|||
def cmd_M114(self, gcmd):
|
||||
# Get Current Position
|
||||
p = self._get_gcode_position()
|
||||
gcmd.respond_raw("X:%.3f Y:%.3f Z:%.3f E:%.3f" % tuple(p))
|
||||
gcmd.respond_raw("X:%.3f Y:%.3f Z:%.3f E:%.3f" % tuple(p[:4]))
|
||||
def cmd_M220(self, gcmd):
|
||||
# Set speed factor override percentage
|
||||
value = gcmd.get_float('S', 100., above=0.) / (60. * 100.)
|
||||
|
|
|
|||
|
|
@ -49,11 +49,13 @@ class HallFilamentWidthSensor:
|
|||
# Start adc
|
||||
self.ppins = self.printer.lookup_object('pins')
|
||||
self.mcu_adc = self.ppins.setup_pin('adc', self.pin1)
|
||||
self.mcu_adc.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
|
||||
self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback)
|
||||
self.mcu_adc.setup_adc_sample(ADC_REPORT_TIME,
|
||||
ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
|
||||
self.mcu_adc.setup_adc_callback(self.adc_callback)
|
||||
self.mcu_adc2 = self.ppins.setup_pin('adc', self.pin2)
|
||||
self.mcu_adc2.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
|
||||
self.mcu_adc2.setup_adc_callback(ADC_REPORT_TIME, self.adc2_callback)
|
||||
self.mcu_adc2.setup_adc_sample(ADC_REPORT_TIME,
|
||||
ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
|
||||
self.mcu_adc2.setup_adc_callback(self.adc2_callback)
|
||||
# extrude factor updating
|
||||
self.extrude_factor_update_timer = self.reactor.register_timer(
|
||||
self.extrude_factor_update_event)
|
||||
|
|
@ -83,12 +85,14 @@ class HallFilamentWidthSensor:
|
|||
self.reactor.update_timer(self.extrude_factor_update_timer,
|
||||
self.reactor.NOW)
|
||||
|
||||
def adc_callback(self, read_time, read_value):
|
||||
def adc_callback(self, samples):
|
||||
# read sensor value
|
||||
read_time, read_value = samples[-1]
|
||||
self.lastFilamentWidthReading = round(read_value * 10000)
|
||||
|
||||
def adc2_callback(self, read_time, read_value):
|
||||
def adc2_callback(self, samples):
|
||||
# read sensor value
|
||||
read_time, read_value = samples[-1]
|
||||
self.lastFilamentWidthReading2 = round(read_value * 10000)
|
||||
# calculate diameter
|
||||
diameter_new = round((self.dia2 - self.dia1)/
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ 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):
|
||||
|
|
@ -252,8 +255,6 @@ class PrinterHeaters:
|
|||
gcode.register_command("TURN_OFF_HEATERS", self.cmd_TURN_OFF_HEATERS,
|
||||
desc=self.cmd_TURN_OFF_HEATERS_help)
|
||||
gcode.register_command("M105", self.cmd_M105, when_not_ready=True)
|
||||
gcode.register_command("TEMPERATURE_WAIT", self.cmd_TEMPERATURE_WAIT,
|
||||
desc=self.cmd_TEMPERATURE_WAIT_help)
|
||||
def load_config(self, config):
|
||||
self.have_load_sensors = True
|
||||
# Load default temperature sensors
|
||||
|
|
@ -296,7 +297,12 @@ class PrinterHeaters:
|
|||
"Unknown temperature sensor '%s'" % (sensor_type,))
|
||||
return self.sensor_factories[sensor_type](config)
|
||||
def register_sensor(self, config, psensor, gcode_id=None):
|
||||
self.available_sensors.append(config.get_name())
|
||||
sensor_name = config.get_name()
|
||||
self.available_sensors.append(sensor_name)
|
||||
gcode = self.printer.lookup_object('gcode')
|
||||
gcode.register_mux_command('TEMPERATURE_WAIT', "SENSOR", sensor_name,
|
||||
self.cmd_TEMPERATURE_WAIT,
|
||||
desc=self.cmd_TEMPERATURE_WAIT_help)
|
||||
if gcode_id is None:
|
||||
gcode_id = config.get('gcode_id', None)
|
||||
if gcode_id is None:
|
||||
|
|
@ -358,8 +364,6 @@ class PrinterHeaters:
|
|||
cmd_TEMPERATURE_WAIT_help = "Wait for a temperature on a sensor"
|
||||
def cmd_TEMPERATURE_WAIT(self, gcmd):
|
||||
sensor_name = gcmd.get('SENSOR')
|
||||
if sensor_name not in self.available_sensors:
|
||||
raise gcmd.error("Unknown sensor '%s'" % (sensor_name,))
|
||||
min_temp = gcmd.get_float('MINIMUM', float('-inf'))
|
||||
max_temp = gcmd.get_float('MAXIMUM', float('inf'), above=min_temp)
|
||||
if min_temp == float('-inf') and max_temp == float('inf'):
|
||||
|
|
|
|||
|
|
@ -249,16 +249,18 @@ class PrinterHoming:
|
|||
gcode = self.printer.lookup_object('gcode')
|
||||
gcode.register_command('G28', self.cmd_G28)
|
||||
def manual_home(self, toolhead, endstops, pos, speed,
|
||||
triggered, check_triggered):
|
||||
probe_pos, triggered, check_triggered):
|
||||
hmove = HomingMove(self.printer, endstops, toolhead)
|
||||
try:
|
||||
hmove.homing_move(pos, speed, triggered=triggered,
|
||||
check_triggered=check_triggered)
|
||||
epos = hmove.homing_move(pos, speed, probe_pos=probe_pos,
|
||||
triggered=triggered,
|
||||
check_triggered=check_triggered)
|
||||
except self.printer.command_error:
|
||||
if self.printer.is_shutdown():
|
||||
raise self.printer.command_error(
|
||||
"Homing failed due to printer shutdown")
|
||||
raise
|
||||
return epos
|
||||
def probing_move(self, mcu_probe, pos, speed):
|
||||
endstops = [(mcu_probe, "probe")]
|
||||
hmove = HomingMove(self.printer, endstops)
|
||||
|
|
|
|||
|
|
@ -158,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,7 +53,6 @@ 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))
|
||||
|
|
@ -62,14 +61,17 @@ class HX71xBase:
|
|||
|
||||
mcu.register_config_callback(self._build_config)
|
||||
|
||||
def setup_trigger_analog(self, trigger_analog_oid):
|
||||
self.mcu.add_config_cmd(
|
||||
"hx71x_attach_trigger_analog oid=%d trigger_analog_oid=%d"
|
||||
% (self.oid, trigger_analog_oid), is_init=True)
|
||||
|
||||
def _build_config(self):
|
||||
cmd_queue = self.mcu.alloc_command_queue()
|
||||
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")
|
||||
"query_hx71x oid=%c rest_ticks=%u", cq=cmd_queue)
|
||||
self.ffreader.setup_query_command("query_hx71x_status oid=%c",
|
||||
oid=self.oid,
|
||||
cq=self.mcu.alloc_command_queue())
|
||||
oid=self.oid, cq=cmd_queue)
|
||||
|
||||
|
||||
def get_mcu(self):
|
||||
|
|
@ -78,6 +80,9 @@ class HX71xBase:
|
|||
def get_samples_per_second(self):
|
||||
return self.sps
|
||||
|
||||
def lookup_sensor_error(self, error_code):
|
||||
return "Unknown hx71x error %d" % (error_code,)
|
||||
|
||||
# returns a tuple of the minimum and maximum value of the sensor, used to
|
||||
# detect if a data value is saturated
|
||||
def get_range(self):
|
||||
|
|
@ -87,9 +92,6 @@ 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)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Kinematic input shaper to minimize motion vibrations in XY plane
|
||||
#
|
||||
# Copyright (C) 2019-2020 Kevin O'Connor <kevin@koconnor.net>
|
||||
# Copyright (C) 2020 Dmitry Butyugin <dmbutyugin@google.com>
|
||||
# Copyright (C) 2020-2025 Dmitry Butyugin <dmbutyugin@google.com>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import collections
|
||||
|
|
@ -11,34 +11,39 @@ from . import shaper_defs
|
|||
class InputShaperParams:
|
||||
def __init__(self, axis, config):
|
||||
self.axis = axis
|
||||
self.shapers = {s.name : s.init_func for s in shaper_defs.INPUT_SHAPERS}
|
||||
self.shapers = {s.name : s for s in shaper_defs.INPUT_SHAPERS}
|
||||
shaper_type = config.get('shaper_type', 'mzv')
|
||||
self.shaper_type = config.get('shaper_type_' + axis, shaper_type)
|
||||
if self.shaper_type not in self.shapers:
|
||||
raise config.error(
|
||||
'Unsupported shaper type: %s' % (self.shaper_type,))
|
||||
self.damping_ratio = config.getfloat('damping_ratio_' + axis,
|
||||
shaper_defs.DEFAULT_DAMPING_RATIO,
|
||||
minval=0., maxval=1.)
|
||||
self.damping_ratio = config.getfloat(
|
||||
'damping_ratio_' + axis,
|
||||
shaper_defs.DEFAULT_DAMPING_RATIO, minval=0.,
|
||||
maxval=self.shapers[self.shaper_type].max_damping_ratio)
|
||||
self.shaper_freq = config.getfloat('shaper_freq_' + axis, 0., minval=0.)
|
||||
def update(self, gcmd):
|
||||
axis = self.axis.upper()
|
||||
self.damping_ratio = gcmd.get_float('DAMPING_RATIO_' + axis,
|
||||
self.damping_ratio,
|
||||
minval=0., maxval=1.)
|
||||
self.shaper_freq = gcmd.get_float('SHAPER_FREQ_' + axis,
|
||||
self.shaper_freq, minval=0.)
|
||||
shaper_type = gcmd.get('SHAPER_TYPE', None)
|
||||
if shaper_type is None:
|
||||
shaper_type = gcmd.get('SHAPER_TYPE_' + axis, self.shaper_type)
|
||||
if shaper_type.lower() not in self.shapers:
|
||||
raise gcmd.error('Unsupported shaper type: %s' % (shaper_type,))
|
||||
damping_ratio = gcmd.get_float('DAMPING_RATIO_' + axis,
|
||||
self.damping_ratio, minval=0.)
|
||||
if damping_ratio > self.shapers[shaper_type.lower()].max_damping_ratio:
|
||||
raise gcmd.error(
|
||||
'Too high value of damping_ratio=%.3f for shaper %s'
|
||||
' on axis %c' % (damping_ratio, shaper_type, axis))
|
||||
self.shaper_freq = gcmd.get_float('SHAPER_FREQ_' + axis,
|
||||
self.shaper_freq, minval=0.)
|
||||
self.damping_ratio = damping_ratio
|
||||
self.shaper_type = shaper_type.lower()
|
||||
def get_shaper(self):
|
||||
if not self.shaper_freq:
|
||||
A, T = shaper_defs.get_none_shaper()
|
||||
else:
|
||||
A, T = self.shapers[self.shaper_type](
|
||||
A, T = self.shapers[self.shaper_type].init_func(
|
||||
self.shaper_freq, self.damping_ratio)
|
||||
return len(A), A, T
|
||||
def get_status(self):
|
||||
|
|
@ -95,7 +100,8 @@ class InputShaper:
|
|||
self._update_kinematics)
|
||||
self.toolhead = None
|
||||
self.shapers = [AxisInputShaper('x', config),
|
||||
AxisInputShaper('y', config)]
|
||||
AxisInputShaper('y', config),
|
||||
AxisInputShaper('z', config)]
|
||||
self.input_shaper_stepper_kinematics = []
|
||||
self.orig_stepper_kinematics = []
|
||||
# Register gcode commands
|
||||
|
|
@ -138,6 +144,7 @@ class InputShaper:
|
|||
if self.toolhead is None:
|
||||
# Klipper initialization is not yet completed
|
||||
return
|
||||
self.toolhead.flush_step_generation()
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
kin = self.toolhead.get_kinematics()
|
||||
for s in kin.get_steppers():
|
||||
|
|
@ -146,12 +153,9 @@ class InputShaper:
|
|||
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)
|
||||
motion_queuing = self.printer.lookup_object("motion_queuing")
|
||||
motion_queuing.check_step_generation_scan_windows()
|
||||
def _update_input_shaping(self, error=None):
|
||||
self.toolhead.flush_step_generation()
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
|
|
@ -163,16 +167,13 @@ class InputShaper:
|
|||
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)
|
||||
for shaper in self.shapers:
|
||||
if shaper in failed_shapers:
|
||||
continue
|
||||
if not shaper.set_shaper_kinematics(is_sk):
|
||||
failed_shapers.append(shaper)
|
||||
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)
|
||||
motion_queuing = self.printer.lookup_object("motion_queuing")
|
||||
motion_queuing.check_step_generation_scan_windows()
|
||||
if failed_shapers:
|
||||
error = error or self.printer.command_error
|
||||
raise error("Failed to configure shaper(s) %s with given parameters"
|
||||
|
|
@ -191,8 +192,9 @@ class InputShaper:
|
|||
for shaper in self.shapers:
|
||||
shaper.update(gcmd)
|
||||
self._update_input_shaping()
|
||||
for shaper in self.shapers:
|
||||
shaper.report(gcmd)
|
||||
for ind, shaper in enumerate(self.shapers):
|
||||
if ind < 2 or shaper.is_enabled():
|
||||
shaper.report(gcmd)
|
||||
|
||||
def load_config(config):
|
||||
return InputShaper(config)
|
||||
|
|
|
|||
|
|
@ -84,11 +84,15 @@ class LDC1612:
|
|||
default_addr=LDC1612_ADDR,
|
||||
default_speed=400000)
|
||||
self.mcu = mcu = self.i2c.get_mcu()
|
||||
self._sensor_errors = {}
|
||||
self.oid = oid = mcu.create_oid()
|
||||
self.query_ldc1612_cmd = None
|
||||
self.ldc1612_setup_home_cmd = self.query_ldc1612_home_state_cmd = None
|
||||
self.frequency = config.getint("frequency", DEFAULT_LDC1612_FREQ,
|
||||
2000000, 40000000)
|
||||
self.clock_freq = config.getint("frequency", DEFAULT_LDC1612_FREQ,
|
||||
2000000, 40000000)
|
||||
# Coil frequency divider, assume 12MHz is BTT Eddy
|
||||
# BTT Eddy's coil frequency is > 1/4 of reference clock
|
||||
self.sensor_div = 1 if self.clock_freq != DEFAULT_LDC1612_FREQ else 2
|
||||
self.freq_conv = float(self.clock_freq * self.sensor_div) / (1<<28)
|
||||
if config.get('intb_pin', None) is not None:
|
||||
ppins = config.get_printer().lookup_object("pins")
|
||||
pin_params = ppins.lookup_pin(config.get('intb_pin'))
|
||||
|
|
@ -115,22 +119,25 @@ class LDC1612:
|
|||
hdr = ('time', 'frequency', 'z')
|
||||
self.batch_bulk.add_mux_endpoint("ldc1612/dump_ldc1612", "sensor",
|
||||
self.name, {'header': hdr})
|
||||
def setup_trigger_analog(self, trigger_analog_oid):
|
||||
self.mcu.add_config_cmd(
|
||||
"ldc1612_attach_trigger_analog oid=%d trigger_analog_oid=%d"
|
||||
% (self.oid, trigger_analog_oid), is_init=True)
|
||||
def _build_config(self):
|
||||
cmdqueue = self.i2c.get_command_queue()
|
||||
self.query_ldc1612_cmd = self.mcu.lookup_command(
|
||||
"query_ldc1612 oid=%c rest_ticks=%u", cq=cmdqueue)
|
||||
self.ffreader.setup_query_command("query_status_ldc1612 oid=%c",
|
||||
oid=self.oid, cq=cmdqueue)
|
||||
self.ldc1612_setup_home_cmd = self.mcu.lookup_command(
|
||||
"ldc1612_setup_home oid=%c clock=%u threshold=%u"
|
||||
" trsync_oid=%c trigger_reason=%c error_reason=%c", cq=cmdqueue)
|
||||
self.query_ldc1612_home_state_cmd = self.mcu.lookup_query_command(
|
||||
"query_ldc1612_home_state oid=%c",
|
||||
"ldc1612_home_state oid=%c homing=%c trigger_clock=%u",
|
||||
oid=self.oid, cq=cmdqueue)
|
||||
errors = self.mcu.get_enumerations().get("ldc1612_error:", {})
|
||||
self._sensor_errors = {v: k for k, v in errors.items()}
|
||||
def get_mcu(self):
|
||||
return self.i2c.get_mcu()
|
||||
def get_samples_per_second(self):
|
||||
return self.data_rate
|
||||
def read_reg(self, reg):
|
||||
if self.mcu.is_fileoutput():
|
||||
return 0
|
||||
params = self.i2c.i2c_read([reg], 2)
|
||||
response = bytearray(params['response'])
|
||||
return (response[0] << 8) | response[1]
|
||||
|
|
@ -139,48 +146,61 @@ class LDC1612:
|
|||
minclock=minclock)
|
||||
def add_client(self, cb):
|
||||
self.batch_bulk.add_client(cb)
|
||||
# Homing
|
||||
def setup_home(self, print_time, trigger_freq,
|
||||
trsync_oid, hit_reason, err_reason):
|
||||
clock = self.mcu.print_time_to_clock(print_time)
|
||||
tfreq = int(trigger_freq * (1<<28) / float(self.frequency) + 0.5)
|
||||
self.ldc1612_setup_home_cmd.send(
|
||||
[self.oid, clock, tfreq, trsync_oid, hit_reason, err_reason])
|
||||
def clear_home(self):
|
||||
self.ldc1612_setup_home_cmd.send([self.oid, 0, 0, 0, 0, 0])
|
||||
if self.mcu.is_fileoutput():
|
||||
return 0.
|
||||
params = self.query_ldc1612_home_state_cmd.send([self.oid])
|
||||
tclock = self.mcu.clock32_to_clock64(params['trigger_clock'])
|
||||
return self.mcu.clock_to_print_time(tclock)
|
||||
def lookup_sensor_error(self, error):
|
||||
return self._sensor_errors.get(error, "Unknown ldc1612 error")
|
||||
def convert_frequency(self, freq):
|
||||
return int(freq / self.freq_conv + 0.5)
|
||||
# Measurement decoding
|
||||
def _convert_samples(self, samples):
|
||||
freq_conv = float(self.frequency) / (1<<28)
|
||||
freq_conv = self.freq_conv
|
||||
count = 0
|
||||
errors = {}
|
||||
def log_once(msg):
|
||||
if not errors.get(msg, 0):
|
||||
errors[msg] = 0
|
||||
errors[msg] += 1
|
||||
for ptime, val in samples:
|
||||
mv = val & 0x0fffffff
|
||||
if mv != val:
|
||||
if val > 0x03ffffff or val == 0x0:
|
||||
self.last_error_count += 1
|
||||
if (val >> 16 & 0xffff) == 0xffff:
|
||||
# Encoded error from sensor_ldc1612.c
|
||||
log_once(self.lookup_sensor_error(val & 0xffff))
|
||||
continue
|
||||
error_bits = (val >> 28) & 0x0f
|
||||
if error_bits & 0x8 or mv == 0x0000000:
|
||||
log_once("Frequency under valid range")
|
||||
if error_bits & 0x4 or mv > 0x3ffffff:
|
||||
type = "hard" if error_bits & 0x4 else "soft"
|
||||
log_once("Frequency over valid %s range" % (type))
|
||||
if error_bits & 0x2:
|
||||
log_once("Conversion Watchdog timeout")
|
||||
if error_bits & 0x1:
|
||||
log_once("Amplitude Low/High warning")
|
||||
samples[count] = (round(ptime, 6), round(freq_conv * mv, 3), 999.9)
|
||||
count += 1
|
||||
del samples[count:]
|
||||
for msg in errors:
|
||||
logging.error("%s: %s (%d)" % (self.name, msg, errors[msg]))
|
||||
# Start, stop, and process message batches
|
||||
def _start_measurements(self):
|
||||
# In case of miswiring, testing LDC1612 device ID prevents treating
|
||||
# noise or wrong signal as a correctly initialized device
|
||||
manuf_id = self.read_reg(REG_MANUFACTURER_ID)
|
||||
dev_id = self.read_reg(REG_DEVICE_ID)
|
||||
if manuf_id != LDC1612_MANUF_ID or dev_id != LDC1612_DEV_ID:
|
||||
if ((manuf_id != LDC1612_MANUF_ID or dev_id != LDC1612_DEV_ID)
|
||||
and not self.mcu.is_fileoutput()):
|
||||
raise self.printer.command_error(
|
||||
"Invalid ldc1612 id (got %x,%x vs %x,%x).\n"
|
||||
"This is generally indicative of connection problems\n"
|
||||
"(e.g. faulty wiring) or a faulty ldc1612 chip."
|
||||
% (manuf_id, dev_id, LDC1612_MANUF_ID, LDC1612_DEV_ID))
|
||||
# Setup chip in requested query rate
|
||||
rcount0 = self.frequency / (16. * (self.data_rate - 4))
|
||||
rcount0 = self.clock_freq / (16. * self.data_rate)
|
||||
self.set_reg(REG_RCOUNT0, int(rcount0 + 0.5))
|
||||
self.set_reg(REG_OFFSET0, 0)
|
||||
self.set_reg(REG_SETTLECOUNT0, int(SETTLETIME*self.frequency/16. + .5))
|
||||
self.set_reg(REG_CLOCK_DIVIDERS0, (1 << 12) | 1)
|
||||
self.set_reg(REG_SETTLECOUNT0, int(SETTLETIME*self.clock_freq/16. + .5))
|
||||
self.set_reg(REG_CLOCK_DIVIDERS0, (self.sensor_div << 12) | 1)
|
||||
self.set_reg(REG_ERROR_CONFIG, (0x1f << 11) | 1)
|
||||
self.set_reg(REG_MUX_CONFIG, 0x0208 | DEGLITCH)
|
||||
self.set_reg(REG_CONFIG, 0x001 | (1<<12) | (1<<10) | (1<<9))
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from . import output_pin
|
|||
class LEDHelper:
|
||||
def __init__(self, config, update_func, led_count=1):
|
||||
self.printer = config.get_printer()
|
||||
self.mutex = self.printer.get_reactor().mutex()
|
||||
self.update_func = update_func
|
||||
self.led_count = led_count
|
||||
self.need_transmit = False
|
||||
|
|
@ -59,11 +60,16 @@ class LEDHelper:
|
|||
def _check_transmit(self, print_time=None):
|
||||
if not self.need_transmit:
|
||||
return
|
||||
# Just avoid any race conditions
|
||||
led_state = self.led_state
|
||||
self.need_transmit = False
|
||||
try:
|
||||
self.update_func(self.led_state, print_time)
|
||||
except self.printer.command_error as e:
|
||||
logging.exception("led update transmit error")
|
||||
def reactor_cb(eventtime):
|
||||
try:
|
||||
with self.mutex:
|
||||
self.update_func(led_state, print_time)
|
||||
except self.printer.command_error as e:
|
||||
logging.exception("led update transmit error")
|
||||
self.printer.get_reactor().register_callback(reactor_cb)
|
||||
cmd_SET_LED_help = "Set the color of an LED"
|
||||
def cmd_SET_LED(self, gcmd):
|
||||
# Parse parameters
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -313,6 +313,8 @@ class LoadCellSampleCollector:
|
|||
self._errors = 0
|
||||
overflows = self._overflows
|
||||
self._overflows = 0
|
||||
if self._mcu.is_fileoutput():
|
||||
samples = [(0., 0., 0.)]
|
||||
return samples, (errors, overflows) if errors or overflows else 0
|
||||
|
||||
def _collect_until(self, timeout):
|
||||
|
|
@ -324,6 +326,8 @@ class LoadCellSampleCollector:
|
|||
raise self._printer.command_error(
|
||||
"LoadCellSampleCollector timed out! Errors: %i,"
|
||||
" Overflows: %i" % (self._errors, self._overflows))
|
||||
if self._mcu.is_fileoutput():
|
||||
break
|
||||
self._reactor.pause(now + RETRY_DELAY)
|
||||
return self._finish_collecting()
|
||||
|
||||
|
|
@ -383,7 +387,7 @@ class LoadCell:
|
|||
# startup, when klippy is ready, start capturing data
|
||||
printer.register_event_handler("klippy:ready", self._handle_ready)
|
||||
|
||||
def _handle_ready(self):
|
||||
def _handle_do_ready(self, eventtime):
|
||||
self.sensor.add_client(self._sensor_data_event)
|
||||
self.add_client(self._track_force)
|
||||
# announce calibration status on ready
|
||||
|
|
@ -391,6 +395,8 @@ class LoadCell:
|
|||
self.printer.send_event("load_cell:calibrate", self)
|
||||
if self.is_tared():
|
||||
self.printer.send_event("load_cell:tare", self)
|
||||
def _handle_ready(self):
|
||||
self.printer.get_reactor().register_callback(self._handle_do_ready)
|
||||
|
||||
# convert raw counts to grams and broadcast to clients
|
||||
def _sensor_data_event(self, msg):
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue