2 Commits

Author SHA1 Message Date
Kevin O'Connor
c1feb47dbd heaters: Add "heaters/set_target_temperature" API endpoint
Add a mechanism for api clients to asynchronously set a target
temperature.  That is, a mechanism to set the temperature without
needing to wait for G-Code commands to complete.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2024-08-05 01:12:19 -04:00
Kevin O'Connor
36b5595290 heaters: Add 'temperature_wait' status to heaters object
Report if g-code processing is delayed waiting for a heater to reach a
temperature, along with the sensor that is being checked.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2024-08-05 00:43:41 -04:00
586 changed files with 12029 additions and 160467 deletions

View File

@@ -4,7 +4,7 @@ on: [push, pull_request]
jobs: jobs:
build: build:
runs-on: ubuntu-22.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -21,7 +21,7 @@ jobs:
run: ./scripts/ci-build.sh 2>&1 run: ./scripts/ci-build.sh 2>&1
- name: Upload micro-controller data dictionaries - name: Upload micro-controller data dictionaries
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: data-dict name: data-dict
path: ci_build/dict path: ci_build/dict

View File

@@ -4,14 +4,15 @@ Welcome to the Klipper project!
https://www.klipper3d.org/ https://www.klipper3d.org/
The Klipper firmware controls 3d-Printers. It combines the power of a Klipper is a 3d-Printer firmware. It combines the power of a general
general purpose computer with one or more micro-controllers. See the purpose computer with one or more micro-controllers. See the
[features document](https://www.klipper3d.org/Features.html) for more [features document](https://www.klipper3d.org/Features.html) for more
information on why you should use the Klipper software. information on why you should use Klipper.
Start by [installing Klipper software](https://www.klipper3d.org/Installation.html). To begin using Klipper start by
[installing](https://www.klipper3d.org/Installation.html) it.
Klipper software is Free Software. See the [license](COPYING) or read Klipper is Free Software. See the [license](COPYING) or read the
the [documentation](https://www.klipper3d.org/Overview.html). We [documentation](https://www.klipper3d.org/Overview.html). We depend on
depend on the generous support from our the generous support from our
[sponsors](https://www.klipper3d.org/Sponsors.html). [sponsors](https://www.klipper3d.org/Sponsors.html).

View File

@@ -1,138 +0,0 @@
# This file is an example config file for cartesian style printers.
# One may copy and edit this file to configure a new printer with
# a generic cartesian kinematics.
# DO NOT COPY THIS FILE WITHOUT CAREFULLY READING AND UPDATING IT
# FIRST. Incorrectly configured parameters may cause damage.
# See docs/Config_Reference.md for a description of parameters.
[carriage x]
position_endstop: 0
position_max: 300
homing_speed: 50
endstop_pin: ^PE5
[carriage y]
position_endstop: 0
position_max: 200
homing_speed: 50
endstop_pin: ^PJ1
[extra_carriage y1]
primary_carriage: y
endstop_pin: ^PB6
[carriage z]
position_endstop: 0.5
position_max: 100
endstop_pin: ^PD3
[dual_carriage u]
primary_carriage: x
position_endstop: 300
position_max: 300
homing_speed: 50
endstop_pin: ^PE4
[stepper my_stepper_x]
carriages: x+y
step_pin: PF0
dir_pin: PF1
enable_pin: !PD7
microsteps: 16
rotation_distance: 40
[stepper my_stepper_u]
carriages: u-y1
step_pin: PH1
dir_pin: PH0
enable_pin: !PA1
microsteps: 16
rotation_distance: 40
[stepper my_stepper_y0]
carriages: y
step_pin: PF6
dir_pin: !PF7
enable_pin: !PF2
microsteps: 16
rotation_distance: 40
[stepper my_stepper_y1]
carriages: y1
step_pin: PE3
dir_pin: !PH6
enable_pin: !PG5
microsteps: 16
rotation_distance: 40
[stepper my_stepper_z0]
carriages: z
step_pin: PL3
dir_pin: PL1
enable_pin: !PK0
microsteps: 16
rotation_distance: 8
[stepper my_stepper_z1]
carriages: z
step_pin: PG1
dir_pin: PG0
enable_pin: !PH3
microsteps: 16
rotation_distance: 8
[extruder]
step_pin: PA4
dir_pin: PA6
enable_pin: !PA2
microsteps: 16
rotation_distance: 33.5
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PB4
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PK5
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[extruder1]
step_pin: PC1
dir_pin: PC3
enable_pin: !PC7
microsteps: 16
rotation_distance: 33.5
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PB5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PK7
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[heater_bed]
heater_pin: PH5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PK6
control: watermark
min_temp: 0
max_temp: 110
[mcu]
serial: /dev/ttyACM0
[printer]
kinematics: generic_cartesian
max_velocity: 500
max_accel: 3000
max_z_velocity: 20
max_z_accel: 100

View File

@@ -39,7 +39,7 @@ position_max: 270
# Motor4 # Motor4
# The M8P only has 4 heater outputs which leaves an extra stepper # 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, # This can be used for a second Z stepper, dual_carriage, extruder co-stepper,
# or other accessory such as an MMU # or other accesory such as an MMU
#[stepper_] #[stepper_]
#step_pin: PD3 #step_pin: PD3
#dir_pin: PD2 #dir_pin: PD2

View File

@@ -40,7 +40,7 @@ position_max: 270
# Motor4 # Motor4
# The M8P only has 4 heater outputs which leaves an extra stepper # 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, # This can be used for a second Z stepper, dual_carriage, extruder co-stepper,
# or other accessory such as an MMU # or other accesory such as an MMU
#[stepper_] #[stepper_]
#step_pin: PD3 #step_pin: PD3
#dir_pin: PD2 #dir_pin: PD2

View File

@@ -43,7 +43,7 @@ position_max: 200
# Motor-4 # Motor-4
# The Octopus only has 4 heater outputs which leaves an extra stepper # 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, # This can be used for a second Z stepper, dual_carriage, extruder co-stepper,
# or other accessory such as an MMU # or other accesory such as an MMU
#[stepper_] #[stepper_]
#step_pin: PB8 #step_pin: PB8
#dir_pin: PB9 #dir_pin: PB9

View File

@@ -52,7 +52,7 @@ position_max: 200
# Driver3 # Driver3
# The Octopus only has 4 heater outputs which leaves an extra stepper # 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, # This can be used for a second Z stepper, dual_carriage, extruder co-stepper,
# or other accessory such as an MMU # or other accesory such as an MMU
#[stepper_] #[stepper_]
#step_pin: PG4 #step_pin: PG4
#dir_pin: PC1 #dir_pin: PC1

View File

@@ -153,48 +153,3 @@ aliases:
#uart_pin: PD12 #uart_pin: PD12
#run_current: 0.600 #run_current: 0.600
#diag_pin: #diag_pin:
########################################
# TMC2130 configuration
########################################
#[tmc2130 stepper_x]
#cs_pin: PE0
#spi_software_miso_pin: PA14
#spi_software_mosi_pin: PE14
#spi_software_sclk_pin: PE15
#run_current: 0.800
#diag1_pin: PC1
#[tmc2130 stepper_y]
#cs_pin: PD3
#spi_software_miso_pin: PA14
#spi_software_mosi_pin: PE14
#spi_software_sclk_pin: PE15
#run_current: 0.800
#diag1_pin: PC3
#[tmc2130 stepper_z]
#cs_pin: PD0
#spi_software_miso_pin: PA14
#spi_software_mosi_pin: PE14
#spi_software_sclk_pin: PE15
#run_current: 0.800
#diag1_pin: PC0
#[tmc2130 extruder]
#cs_pin: PC6
#spi_software_miso_pin: PA14
#spi_software_mosi_pin: PE14
#spi_software_sclk_pin: PE15
#run_current: 0.600
#diag1_pin: PC2
#[tmc2130 extruder1]
#cs_pin: PD12
#spi_software_miso_pin: PA14
#spi_software_mosi_pin: PE14
#spi_software_sclk_pin: PE15
#run_current: 0.600
#stealthchop_threshold: 999999
#diag1_pin: PA0

View File

@@ -122,12 +122,6 @@ max_z_accel: 100
[static_digital_output usb_pullup_enable] [static_digital_output usb_pullup_enable]
pins: !PA14 pins: !PA14
#[neopixel my_neopixel]
#pin: PA8
[output_pin red_led]
pin: PA13
[board_pins] [board_pins]
aliases: aliases:
# EXP1 header # EXP1 header

View File

@@ -95,4 +95,4 @@ max_z_accel: 100
aliases: aliases:
EXP1_1=PC6,EXP1_3=PB10,EXP1_5=PB14,EXP1_7=PB12,EXP1_9=<GND>, EXP1_1=PC6,EXP1_3=PB10,EXP1_5=PB14,EXP1_7=PB12,EXP1_9=<GND>,
EXP1_2=PB2,EXP1_4=PB11,EXP1_6=PB13,EXP1_8=PB15,EXP1_10=<5V>, EXP1_2=PB2,EXP1_4=PB11,EXP1_6=PB13,EXP1_8=PB15,EXP1_10=<5V>,
PROBE_IN=PB0,PROBE_OUT=PB1,FIL_RUNOUT=PA4 PROBE_IN=PB0,PROBE_OUT=PB1,FIL_RUNOUT=PC6

View File

@@ -1,232 +0,0 @@
# This file contains common pin mappings for the Mellow Fly-E3-v2.
# To use this config, the firmware should be compiled for the
# STM32F407 with a "32KiB bootloader".
# The "make flash" command does not work on the Fly-E3-v2. Instead,
# after running "make", copy the generated "out/klipper.bin" file to a
# file named "firmware.bin" or "klipper.bin" on an SD card and then restart the Fly-E3-v2
# with that SD card.
# See docs/Config_Reference.md for a description of parameters.
[mcu]
serial: /dev/serial/by-id/usb-Klipper_stm32f407xx_27004A001851323333353137-if00
[stepper_x]
step_pin: PE5
dir_pin: PC0
enable_pin: !PC1
microsteps: 16
rotation_distance: 30
full_steps_per_rotation: 200
endstop_pin: PE7 #X-STOP
position_endstop: 0
position_max: 200
homing_speed: 50
second_homing_speed: 10
homing_retract_dist: 5.0
homing_positive_dir: false
step_pulse_duration: 0.000004
[stepper_y]
step_pin: PE4
dir_pin: !PC13
enable_pin: !PC14
microsteps: 16
rotation_distance: 30
full_steps_per_rotation: 200
endstop_pin: PE8 #Y-STOP
position_endstop: 0
position_max: 200
homing_speed: 50
second_homing_speed: 10
homing_retract_dist: 5.0
homing_positive_dir: false
step_pulse_duration: 0.000004
[stepper_z]
step_pin: PE1
dir_pin: !PB7
enable_pin: !PE3
microsteps: 16
rotation_distance: 30
full_steps_per_rotation: 200
endstop_pin: PE9 #Z-STOP
position_min: 0
position_endstop: 0
position_max: 200
homing_speed: 5
second_homing_speed: 3
homing_retract_dist: 5.0
homing_positive_dir: false
step_pulse_duration: 0.000004
[extruder]
step_pin: PE2
dir_pin: PD5
enable_pin: !PD6
microsteps: 16
rotation_distance: 33.500
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PC6 #E0
########################################
# Extruder 100K thermistor configuration
########################################
sensor_type: ATC Semitec 104GT-2
sensor_pin: PC4 #T0 TEMP
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 275
########################################
# Extruder MAX31865 PT100 2 wire config
########################################
# sensor_type: MAX31865
# sensor_pin: PD15 #PT-100
# spi_speed: 4000000
# spi_software_sclk_pin: PD12
# spi_software_mosi_pin: PD11
# spi_software_miso_pin: PD13
# rtd_nominal_r: 100
# rtd_reference_r: 430
# rtd_num_of_wires: 2
# rtd_use_50Hz_filter: True
min_temp: 0
max_temp: 300
#[extruder1]
#step_pin: PE0
#dir_pin: PD1
#enable_pin: !PD3
#microsteps: 16
#heater_pin: PC7 #E1
#sensor_pin: PC5 #T1 TEMP
########################################
# TMC2209 configuration
########################################
[tmc2209 stepper_x]
uart_pin: PC15
interpolate: False
run_current: 0.3
sense_resistor: 0.110
stealthchop_threshold: 999999
[tmc2209 stepper_y]
uart_pin: PB6
interpolate: False
run_current: 0.3
sense_resistor: 0.110
stealthchop_threshold: 999999
[tmc2209 stepper_z]
uart_pin: PD7
interpolate: False
run_current: 0.4
sense_resistor: 0.110
stealthchop_threshold: 999999
[tmc2209 extruder]
uart_pin: PD4
interpolate: False
run_current: 0.27
sense_resistor: 0.075
stealthchop_threshold: 999999
#[tmc2209 extruder1]
#uart_pin: PD0
#interpolate: False
#run_current: 0.27
#sense_resistor: 0.075
#stealthchop_threshold: 999999
#######################################
# Heated Bed
#######################################
[heater_bed]
heater_pin: PB0 #BED
sensor_type: Generic 3950
sensor_pin: PB1 #B-TEMP
max_power: 1.0
min_temp: 0
max_temp: 120
control: pid
pid_kp: 58.437
pid_ki: 2.347
pid_kd: 363.769
#######################################
# LIGHTING
#######################################
#[led Toolhead]
#white_pin: PA2 #FAN2
#cycle_time: 0.010
#initial_white: 0
#######################################
# COOLING
#######################################
[heater_fan hotend_fan]
pin: PA1 #FAN1
max_power: 1.0
kick_start_time: 0.5
heater: extruder
heater_temp: 50
fan_speed: 1.0
[controller_fan controller_fan]
pin: PA0 #FAN0
max_power: 1.0
kick_start_time: 0.5
heater: extruder
stepper: stepper_x, stepper_y, stepper_z
fan_speed: 1.0
idle_timeout: 60
[fan]
pin: PA3 #FAN3
max_power: 1.0
off_below: 0.2
[temperature_sensor Mellow_Fly_E3_V2]
sensor_type: temperature_mcu
min_temp: 5
max_temp: 80
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 50
max_z_accel: 100
########################################
# EXP1 / EXP2 (display) pins
########################################
[board_pins]
aliases:
EXP1_1=PD10, EXP1_3=PA8, EXP1_5=PE15, EXP1_7=PA14, EXP1_9=<GND>,
EXP1_2=PA9, EXP1_4=PA10, EXP1_6=PE14, EXP1_8=PA13, EXP1_10=<5V>,
# EXP2 header
EXP2_1=PA6, EXP2_3=PB11, EXP2_5=PB10, EXP2_7=PE13, EXP2_9=<GND>,
EXP2_2=PA5, EXP2_4=PA4, EXP2_6=PA7, EXP2_8=<RST>, EXP2_10=<NC>,
# See the sample-lcd.cfg file for definitions of common LCD displays.
#######################################
# BL-Touch
#######################################
#[bltouch]
#sensor_pin: PC2
#control_pin: PE6
#z_offset: 0

View File

@@ -89,32 +89,32 @@ max_z_velocity: 5
max_z_accel: 100 max_z_accel: 100
[mcp4018 x_axis_pot] [mcp4018 x_axis_pot]
i2c_software_scl_pin: PJ5 scl_pin: PJ5
i2c_software_sda_pin: PF3 sda_pin: PF3
wiper: 0.50 wiper: 0.50
scale: 0.773 scale: 0.773
[mcp4018 y_axis_pot] [mcp4018 y_axis_pot]
i2c_software_scl_pin: PJ5 scl_pin: PJ5
i2c_software_sda_pin: PF7 sda_pin: PF7
wiper: 0.50 wiper: 0.50
scale: 0.773 scale: 0.773
[mcp4018 z_axis_pot] [mcp4018 z_axis_pot]
i2c_software_scl_pin: PJ5 scl_pin: PJ5
i2c_software_sda_pin: PK3 sda_pin: PK3
wiper: 0.50 wiper: 0.50
scale: 0.773 scale: 0.773
[mcp4018 a_axis_pot] [mcp4018 a_axis_pot]
i2c_software_scl_pin: PJ5 scl_pin: PJ5
i2c_software_sda_pin: PA5 sda_pin: PA5
wiper: 0.50 wiper: 0.50
scale: 0.773 scale: 0.773
[mcp4018 b_axis_pot] [mcp4018 b_axis_pot]
i2c_software_scl_pin: PJ5 scl_pin: PJ5
i2c_software_sda_pin: PJ6 sda_pin: PJ6
wiper: 0.50 wiper: 0.50
scale: 0.773 scale: 0.773

View File

@@ -19,7 +19,7 @@
# FSR switch (z endstop) location [homing_override] section # FSR switch (z endstop) location [homing_override] section
# FSR switch (z endstop) offset for Z0 [stepper_z] section # FSR switch (z endstop) offset for Z0 [stepper_z] section
# Probe points [quad_gantry_level] section # Probe points [quad_gantry_level] section
# Min & Max gantry corner positions [quad_gantry_level] section # Min & Max gantry corner postions [quad_gantry_level] section
# PID tune [extruder] and [heater_bed] sections # PID tune [extruder] and [heater_bed] sections
# Fine tune E steps [extruder] section # Fine tune E steps [extruder] section

View File

@@ -20,7 +20,7 @@
# FSR switch (z endstop) location [homing_override] section # FSR switch (z endstop) location [homing_override] section
# FSR switch (z endstop) offset for Z0 [stepper_z] section # FSR switch (z endstop) offset for Z0 [stepper_z] section
# Probe points [quad_gantry_level] section # Probe points [quad_gantry_level] section
# Min & Max gantry corner positions [quad_gantry_level] section # Min & Max gantry corner postions [quad_gantry_level] section
# PID tune [extruder] and [heater_bed] sections # PID tune [extruder] and [heater_bed] sections
# Fine tune E steps [extruder] section # Fine tune E steps [extruder] section

View File

@@ -17,7 +17,7 @@ endstop_pin: ^PE4
homing_speed: 60 homing_speed: 60
# The next parameter needs to be adjusted for # The next parameter needs to be adjusted for
# your printer. You may want to start with 280 # your printer. You may want to start with 280
# and measure the distance from nozzle to bed. # and meassure the distance from nozzle to bed.
# This value then needs to be added. # This value then needs to be added.
position_endstop: 273.0 position_endstop: 273.0
arm_length: 229.4 arm_length: 229.4

View File

@@ -43,7 +43,7 @@ position_max: 400
#Uncomment if you have a BL-Touch: #Uncomment if you have a BL-Touch:
#position_min: -4 #position_min: -4
#endstop_pin: probe:z_virtual_endstop #endstop_pin: probe:z_virtual_endstop
#and comment the following lines: #and comment the follwing lines:
position_endstop: 0.0 position_endstop: 0.0
endstop_pin: ^PD3 #ar18 endstop_pin: ^PD3 #ar18

View File

@@ -1,5 +1,4 @@
# This file contains pin mappings for the stock 2020 Creality CR6-SE # This file contains pin mappings for the stock 2020 Creality CR6-SE.
# with the early 4.5.2 board only.
# To use this config, during "make menuconfig" select the STM32F103 # To use this config, during "make menuconfig" select the STM32F103
# with a "28KiB bootloader" and serial (on USART1 PA10/PA9) # with a "28KiB bootloader" and serial (on USART1 PA10/PA9)
# communication. # communication.

View File

@@ -1,6 +1,4 @@
# This file contains pin mappings for the Creality CR6-SE # This file contains pin mappings for the Creality CR6-SE with Rev. 4.5.3 Motherboard (Late 2020/2021) as the heater pins changed.
# with Rev. 4.5.3 Motherboard (Late 2020/2021) as the heater pins changed.
# This config also works for the CR-ERA_V1.1.0.3
# To use this config, during "make menuconfig" select the STM32F103 # To use this config, during "make menuconfig" select the STM32F103
# with a "28KiB bootloader" and serial (on USART1 PA10/PA9) # with a "28KiB bootloader" and serial (on USART1 PA10/PA9)
# communication. # communication.

View File

@@ -81,7 +81,7 @@ pin: PA0
kick_start_time: 0.5 kick_start_time: 0.5
# Hotend fan # Hotend fan
# set fan running when extruder temperature is over 60 # set fan runnig when extruder temperature is over 60
[heater_fan heatbreak_fan] [heater_fan heatbreak_fan]
pin: PC0 pin: PC0
heater:extruder heater:extruder

View File

@@ -127,32 +127,32 @@ max_z_velocity: 5
max_z_accel: 100 max_z_accel: 100
[mcp4018 x_axis_pot] [mcp4018 x_axis_pot]
i2c_software_scl_pin: PJ5 scl_pin: PJ5
i2c_software_sda_pin: PF3 sda_pin: PF3
wiper: 118 wiper: 118
scale: 127 scale: 127
[mcp4018 y_axis_pot] [mcp4018 y_axis_pot]
i2c_software_scl_pin: PJ5 scl_pin: PJ5
i2c_software_sda_pin: PF7 sda_pin: PF7
wiper: 118 wiper: 118
scale: 127 scale: 127
[mcp4018 z_axis_pot] [mcp4018 z_axis_pot]
i2c_software_scl_pin: PJ5 scl_pin: PJ5
i2c_software_sda_pin: PK3 sda_pin: PK3
wiper: 40 wiper: 40
scale: 127 scale: 127
[mcp4018 a_axis_pot] [mcp4018 a_axis_pot]
i2c_software_scl_pin: PJ5 scl_pin: PJ5
i2c_software_sda_pin: PA5 sda_pin: PA5
wiper: 118 wiper: 118
scale: 127 scale: 127
[mcp4018 b_axis_pot] [mcp4018 b_axis_pot]
i2c_software_scl_pin: PJ5 scl_pin: PJ5
i2c_software_sda_pin: PJ6 sda_pin: PJ6
wiper: 118 wiper: 118
scale: 127 scale: 127

View File

@@ -1,256 +0,0 @@
# This file contains common pin mappings for the Geeetech GT2560 v4.0 and v4.1b
# boards. These boards use a firmware compiled for the AVR atmega2560.
# For default Geeetech A10/A20 (1 extruder),
# A10M/A20M (mixing 2 in 1 out),
# A10T/A20T (mixing 3 in 1 out) printers
# Installation: https://www.klipper3d.org/Installation.html
# Always read for first start: https://www.klipper3d.org/Config_checks.html
[mcu]
# Might need to be changed: https://www.klipper3d.org/Installation.html
serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0
[printer]
kinematics: cartesian
max_velocity: 200
max_accel: 1500
max_z_velocity: 20
max_z_accel: 500
# # uncomment for BLTouch/3DTouch
# [bltouch]
# sensor_pin: PC7 # there is an external pull up so no need in ^
# control_pin: PB5
# speed: 3.0
# samples: 2
# x_offset: -42.0
# y_offset: -1.0
# z_offset: 1.0 # during calibration this line is commented out and new record added at the end of file
[safe_z_home]
home_xy_position: 100, 100 # Change coordinates to the center of your print bed
speed: 50
z_hop: 10 # Move up 10mm
z_hop_speed: 5
[stepper_x]
enable_pin: !PC2
dir_pin: !PG2
step_pin: PC0
microsteps: 16
rotation_distance: 40
endstop_pin: !PA2 # there are external pull ups
position_endstop: 0
position_max: 220 # for A10/M/T / change to 250 for A20/M/T
homing_speed: 40
[stepper_y]
enable_pin: !PA7
dir_pin: !PC4
step_pin: PC6
microsteps: 16
rotation_distance: 40
endstop_pin: !PA6 # there are external pull ups
position_endstop: 0
position_max: 220 # for A10/M/T / change to 250 for A20/M/T
homing_speed: 40
[stepper_z]
enable_pin: !PA5
dir_pin: PA1
step_pin: PA3
microsteps: 16
rotation_distance: 8
#endstop_pin: probe:z_virtual_endstop # uncomment for BLTouch/3DTouch
endstop_pin: !PC7 # comment for BLTouch/3DTouch
position_endstop: 0 # comment for BLTouch/3DTouch
position_max: 230 # for A10/M/T / change to 250 for A20/M/T
position_min: -5
homing_speed: 20
[extruder]
enable_pin: !PB6
dir_pin: PL5
step_pin: PL3
microsteps: 16
rotation_distance: 8 # Needs to be optimized: https://www.klipper3d.org/Rotation_Distance.html#calibrating-rotation_distance-on-extruders
nozzle_diameter: 0.4
filament_diameter: 1.750
heater_pin: PB4
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PK3
min_temp: 0
max_temp: 250
max_extrude_only_distance: 200.0
# Parameters for stock hotend on A10M
# Please recalibrate according to https://www.klipper3d.org/Config_checks.html#calibrate-pid-settings
control: pid
pid_kp: 54.722
pid_ki: 4.800
pid_kd: 155.958
[extruder_stepper extruder_1]
extruder:
enable_pin: !PL1
dir_pin: PL2
step_pin: PL0
microsteps: 16
rotation_distance: 8 # Needs to be optimized: https://www.klipper3d.org/Rotation_Distance.html#calibrating-rotation_distance-on-extruders
[extruder_stepper extruder_2]
extruder:
enable_pin: !PG0
dir_pin: PL4
step_pin: PL6
microsteps: 16
rotation_distance: 8 # Needs to be optimized: https://www.klipper3d.org/Rotation_Distance.html#calibrating-rotation_distance-on-extruders
[heater_bed]
heater_pin: PG5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PK2
min_temp: 0
max_temp: 120
# Parameters for `SuperPlate` on A10M
# Please recalibrate according to https://www.klipper3d.org/Config_checks.html#calibrate-pid-settings
control: pid
pid_kp: 70.936
pid_ki: 1.785
pid_kd: 704.924
[fan]
pin: PH6
cycle_time: 0.150
kick_start_time: 0.300
# # for GT2560V4.0 with 20pin flat cable toward the display
# [display]
# lcd_type: hd44780
# hd44780_protocol_init: True
# rs_pin: PD1
# e_pin: PH0
# d4_pin: PH1
# d5_pin: PD0
# d6_pin: PE3
# d7_pin: PC1
# encoder_pins: ^PG1, ^PL7
# click_pin: ^!PD2
# for GT2560V4.1B with 12pin flat cable toward the display YHCB2004-06 ver3.0
# the aip31068_spi driver was added to Klipper on 2024-12-02, commit aecb29d2
[display]
lcd_type: aip31068_spi
latch_pin: PE3
spi_software_sclk_pin: PD0
spi_software_mosi_pin: PC1
spi_software_miso_pin: PH7 # any unused pin
encoder_pins: ^PH0, ^PH1
click_pin: ^!PD2
[filament_switch_sensor sensor_e0]
switch_pin: !PK4
[filament_switch_sensor sensor_e1]
switch_pin: !PK5
[filament_switch_sensor sensor_e2]
# switch_pin: !PE2 # for GT2560V4.0
switch_pin: !PF0 # for GT2560V4.1B
# to enable M118 echo command
[respond]
# Specific macros for mixing colors.
# Add in slicer new filament color and in filament start G-Code add desired mixing factor:
# M163 S0 P50 ; set extruder 0 to 50%
# M163 S1 P40 ; set extruder 1 to 40%
# M163 S2 P10 ; set extruder 2 to 10%
# M164 ; commit the mix factors
[gcode_macro M163]
description: M163 [P<factor>] [S<index>] Set a single mix factor (in proportion to the sum total of all mix factors). The mix must be committed to a virtual tool by M164 before it takes effect.
gcode:
{% if 'P' in params %}
{% set s = params.S|default(0)| int %}
{% if s == 0 %}
SET_GCODE_VARIABLE MACRO=M164 VARIABLE=e0_parts VALUE={params.P|default(0)|float}
M118 Set Mixing factor for extruder 0 to {params.P|default(0)|float}
{% elif s == 1 %}
SET_GCODE_VARIABLE MACRO=M164 VARIABLE=e1_parts VALUE={params.P|default(0)|float}
M118 Set Mixing factor for extruder 1 to {params.P|default(0)|float}
{% elif s == 2 %}
SET_GCODE_VARIABLE MACRO=M164 VARIABLE=e2_parts VALUE={params.P|default(0)|float}
M118 Set Mixing factor for extruder 2 to {params.P|default(0)|float}
{% endif %}
{% else %}
M118 No Mixing factor set, missing value for P
{% endif %}
M118 {e0_parts} {e1_parts} {e2_parts}
[gcode_macro M164]
description: Applies the set mixing factors to the extruders
# default values:
variable_e0_parts : 100
variable_e1_parts : 0
variable_e2_parts : 0
gcode:
# normalize the parts to sum of 1
{% set e0 = e0_parts / (e0_parts + e1_parts + e2_parts) | float %}
{% set e1 = e1_parts / (e0_parts + e1_parts + e2_parts) | float %}
{% set e2 = e2_parts / (e0_parts + e1_parts + e2_parts) | float %}
M118 scaled rot-dist_e0 { printer.configfile.settings.extruder.rotation_distance / (e0 + 0.000001) | float }
M118 scaled rot-dist_e1 { printer.configfile.settings['extruder_stepper extruder_1'].rotation_distance / (e1 + 0.000001) | float }
M118 scaled rot-dist_e2 { printer.configfile.settings['extruder_stepper extruder_2'].rotation_distance / (e2 + 0.000001) |float }
# activate stepper percentages
SYNC_EXTRUDER_MOTION EXTRUDER=extruder MOTION_QUEUE=extruder
SYNC_EXTRUDER_MOTION EXTRUDER=extruder_1 MOTION_QUEUE=extruder
SYNC_EXTRUDER_MOTION EXTRUDER=extruder_2 MOTION_QUEUE=extruder
SET_EXTRUDER_ROTATION_DISTANCE EXTRUDER=extruder DISTANCE={ printer.configfile.settings.extruder.rotation_distance / (e0+0.000001)|float }
SET_EXTRUDER_ROTATION_DISTANCE EXTRUDER=extruder_1 DISTANCE={ printer.configfile.settings['extruder_stepper extruder_1'].rotation_distance / (e1+0.000001)|float }
SET_EXTRUDER_ROTATION_DISTANCE EXTRUDER=extruder_2 DISTANCE={ printer.configfile.settings['extruder_stepper extruder_2'].rotation_distance / (e2+0.000001)|float }
M118 Mixing factors {e0} {e1} {e2} are activated
# In PrusaSlicer:
# - you can add as many extruders as mixing ratios you want
# - in Printer Settings -> Custom G-code -> Tool change G-code add:
# TOOL_CHANGE EXTRUDER={next_extruder}
# - in this config file add:
# [gcode_macro TOOL_CHANGE]
# description: Tool change macro with mix ratio setup for 11 extruders
# variable_extruder: 0
# gcode:
# {% set extruder = params.EXTRUDER|default(0)| int %}
# {% if extruder == 0 %}
# M163 S0 P100
# M163 S1 P0
# M163 S2 P0
# M164
# M118 Switching to Extruder 0
# {% elif extruder == 1 %}
# M163 S0 P90
# M163 S1 P10
# M163 S2P0
# M164
# M118 Switching to Extruder 1
# {% elif extruder == 2 %}
# # and so on ...
# {% else %}
# M118 Unknown extruder number: {extruder}
# {% endif %}
# In OrcaSlicer:
# you can add as many filaments as mixing ratios you want
# in Material settings -> Advanced -> Filament start G-code add desired mixing ratio:
# ; filament start gcode
# M163 S0 P100 ; set extruder 0
# M163 S1 P0 ; set extruder 1
# M163 S2 P0 ; set extruder 2
# M164 ; commit the mix factors
# For gradient over Z axis:
# In `Printer -> Custom G-code -> After layer change G-code` add:
# M163 S0 P{ layer_num * 100 / total_layer_count } ; Gradient 0-100
# M163 S1 P{(total_layer_count-layer_num) * 100 / total_layer_count} ; Gradient 100-0
# M164 ; commit the mix factors

View File

@@ -195,7 +195,7 @@ samples_tolerance: 0.200
samples_tolerance_retries: 2 samples_tolerance_retries: 2
[bed_tilt] [bed_tilt]
# Enable bed tilt measurements using the probe we defined above # Enable bed tilt measurments using the probe we defined above
# Probe points using X0 Y0 offsets @ 0.01mm/step # Probe points using X0 Y0 offsets @ 0.01mm/step
points: -2, -6 points: -2, -6
156, -6 156, -6

View File

@@ -183,7 +183,7 @@ samples: 2
samples_tolerance: 0.100 samples_tolerance: 0.100
[bed_tilt] [bed_tilt]
#Enable bed tilt measurements using the probe we defined above #Enable bed tilt measurments using the probe we defined above
#Probe points using X0 Y0 offsets @ 0.01mm/step #Probe points using X0 Y0 offsets @ 0.01mm/step
points: -3, -6 points: -3, -6
282, -6 282, -6

View File

@@ -37,7 +37,7 @@ microsteps: 16
rotation_distance: 4 rotation_distance: 4
# Required if not using probe for the virtual endstop # Required if not using probe for the virtual endstop
# endstop_pin: ^PD3 # endstop_pin: ^PD3
# position_endstop: 250 # Will need adjustment # position_endstop: 250 # Will need ajustment
endstop_pin: probe:z_virtual_endstop endstop_pin: probe:z_virtual_endstop
homing_speed: 10.0 homing_speed: 10.0
position_max: 250 position_max: 250

View File

@@ -1,4 +1,4 @@
# This file contains the pin mappings for the SeeMeCNC Rostock Max # This file constains the pin mappings for the SeeMeCNC Rostock Max
# (version 2) delta printer from 2015. To use this config, the # (version 2) delta printer from 2015. To use this config, the
# firmware should be compiled for the AVR atmega2560. # firmware should be compiled for the AVR atmega2560.

View File

@@ -1,177 +0,0 @@
# This file contains a configuration snippet for a CoreXYUV
# printer with an independent dual extruder moving over X and Y axes.
# See docs/Config_Reference.md for a description of parameters.
[carriage x]
position_endstop: 0
position_max: 300
homing_speed: 50
endstop_pin: ^PE5
[carriage y]
position_endstop: 0
position_max: 200
homing_speed: 50
endstop_pin: ^PJ1
[dual_carriage u]
primary_carriage: x
safe_distance: 70
position_endstop: 300
position_max: 300
homing_speed: 50
endstop_pin: ^PE4
[dual_carriage v]
primary_carriage: y
safe_distance: 50
position_endstop: 200
position_max: 200
homing_speed: 50
endstop_pin: ^PD4
[stepper a]
carriages: x+y
step_pin: PF0
dir_pin: PF1
enable_pin: !PD7
microsteps: 16
rotation_distance: 40
[stepper b]
carriages: u-v
step_pin: PH1
dir_pin: PH0
enable_pin: !PA1
microsteps: 16
rotation_distance: 40
[stepper c]
carriages: x-y
step_pin: PF6
dir_pin: !PF7
enable_pin: !PF2
microsteps: 16
rotation_distance: 40
[stepper d]
carriages: u+v
step_pin: PE3
dir_pin: !PH6
enable_pin: !PG5
microsteps: 16
rotation_distance: 40
[extruder]
step_pin: PA4
dir_pin: PA6
enable_pin: !PA2
microsteps: 16
rotation_distance: 33.5
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PB4
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PK5
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[gcode_macro PARK_extruder]
gcode:
SET_DUAL_CARRIAGE CARRIAGE=x
SET_DUAL_CARRIAGE CARRIAGE=y
G90
G1 X0 Y0
[gcode_macro T0]
gcode:
PARK_{printer.toolhead.extruder}
ACTIVATE_EXTRUDER EXTRUDER=extruder
SET_DUAL_CARRIAGE CARRIAGE=x
SET_DUAL_CARRIAGE CARRIAGE=y
[extruder1]
step_pin: PC1
dir_pin: PC3
enable_pin: !PC7
microsteps: 16
rotation_distance: 33.5
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PB5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PK7
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[gcode_macro PARK_extruder1]
gcode:
SET_DUAL_CARRIAGE CARRIAGE=u
SET_DUAL_CARRIAGE CARRIAGE=v
G90
G1 X300 Y200
[gcode_macro T1]
gcode:
PARK_{printer.toolhead.extruder}
ACTIVATE_EXTRUDER EXTRUDER=extruder1
SET_DUAL_CARRIAGE CARRIAGE=u
SET_DUAL_CARRIAGE CARRIAGE=v
# A helper script to activate copy mode
[gcode_macro ACTIVATE_COPY_MODE]
gcode:
SET_DUAL_CARRIAGE CARRIAGE=x MODE=PRIMARY
SET_DUAL_CARRIAGE CARRIAGE=y MODE=PRIMARY
G1 X0 Y0
ACTIVATE_EXTRUDER EXTRUDER=extruder
SET_DUAL_CARRIAGE CARRIAGE=u MODE=PRIMARY
SET_DUAL_CARRIAGE CARRIAGE=v MODE=PRIMARY
G1 X150 Y100
SET_DUAL_CARRIAGE CARRIAGE=u MODE=COPY
SET_DUAL_CARRIAGE CARRIAGE=v MODE=COPY
SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder
# A helper script to activate mirror mode
[gcode_macro ACTIVATE_MIRROR_MODE]
gcode:
SET_DUAL_CARRIAGE CARRIAGE=x MODE=PRIMARY
SET_DUAL_CARRIAGE CARRIAGE=y MODE=PRIMARY
G1 X0 Y0
ACTIVATE_EXTRUDER EXTRUDER=extruder
SET_DUAL_CARRIAGE CARRIAGE=u MODE=PRIMARY
SET_DUAL_CARRIAGE CARRIAGE=v MODE=PRIMARY
G1 X300 Y100
SET_DUAL_CARRIAGE CARRIAGE=u MODE=MIRROR
SET_DUAL_CARRIAGE CARRIAGE=v MODE=COPY
SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder
[printer]
kinematics: generic_cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100
## An optional input shaper support
#[input_shaper]
## The section is intentionally empty
#
#[delayed_gcode init_shaper]
#initial_duration: 0.1
#gcode:
# SET_DUAL_CARRIAGE CARRIAGE=u
# SET_DUAL_CARRIAGE CARRIAGE=v
# SET_INPUT_SHAPER SHAPER_TYPE_X=<dual_carriage_x_shaper> SHAPER_FREQ_X=<dual_carriage_x_freq> SHAPER_TYPE_Y=<dual_carriage_y_shaper> SHAPER_FREQ_Y=<dual_carriage_y_freq>
# SET_DUAL_CARRIAGE CARRIAGE=x MODE=PRIMARY
# SET_DUAL_CARRIAGE CARRIAGE=y MODE=PRIMARY
# SET_INPUT_SHAPER SHAPER_TYPE_X=<primary_carriage_x_shaper> SHAPER_FREQ_X=<primary_carriage_x_freq> SHAPER_TYPE_Y=<primary_carriage_y_shaper> SHAPER_FREQ_Y=<primary_carriage_y_freq>

View File

@@ -6,7 +6,7 @@
# Communication interface of "CAN bus (on PA25/PA24)" # Communication interface of "CAN bus (on PA25/PA24)"
# To flash the board use a debugger, or use a raspberry pi and follow # To flash the board use a debugger, or use a raspberry pi and follow
# the instructions at docs/Bootloaders.md for the SAMC21. You may # the instructions at docs/Bootloaders.md fot the SAMC21. You may
# supply power to the 1LC by connecting the 3.3v rail on the Pi to the # 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. # 5v input of the SWD header on the 1LC.
@@ -77,14 +77,5 @@ heater_temp: 50.0
pin: toolboard:PA9 pin: toolboard:PA9
z_offset: 20 z_offset: 20
[samd_sercom sercom_i2c]
sercom: sercom1
tx_pin: toolboard:PA16
clk_pin: toolboard:PA17
[lis3dh]
i2c_mcu: toolboard
i2c_bus: sercom1
[mcu toolboard] [mcu toolboard]
canbus_uuid: 4b194673554e canbus_uuid: 4b194673554e

View File

@@ -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_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_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_extruder_eject_temp : heater temperature used to eject filament during home if the filament is already loaded
# variable_enable_5in1 : pass from MMU2S standard (0) to MMU2S-5in1 mode with splitter # variable_enable_5in1 : pass from MMU2S standart (0) to MMU2S-5in1 mode with splitter
# #
################################ ################################
[gcode_macro VAR_MMU2S] [gcode_macro VAR_MMU2S]
@@ -394,7 +394,7 @@ gcode:
{% endif %} {% endif %}
{% endif %} {% endif %}
# Retry unload, try correct misalignment of bondtech gear # Retry unload, try correct misalignement of bondtech gear
[gcode_macro RETRY_UNLOAD_FILAMENT_IN_EXTRUDER] [gcode_macro RETRY_UNLOAD_FILAMENT_IN_EXTRUDER]
gcode: gcode:
{% if printer["filament_switch_sensor ir_sensor"].filament_detected == True %} {% if printer["filament_switch_sensor ir_sensor"].filament_detected == True %}
@@ -444,7 +444,7 @@ gcode:
{% endif %} {% endif %}
{% endif %} {% endif %}
# Ramming process for standard PLA, code extracted from slic3r gcode # Ramming process for standart PLA, code extracted from slic3r gcode
[gcode_macro RAMMING_SLICER] [gcode_macro RAMMING_SLICER]
gcode: gcode:
G91 G91

View File

@@ -282,6 +282,22 @@ window" interface. Parsing content from the G-Code terminal output is
discouraged. Use the "objects/subscribe" endpoint to obtain updates on discouraged. Use the "objects/subscribe" endpoint to obtain updates on
Klipper's state. Klipper's state.
### heaters/set_target_temperature
This endpoint is used to asynchronously set the target temperature for
a heater. For example:
`{"id": 123, "method": "heaters/set_target_temperature", "params":
{"heater":"heater_generic my_heater", "target": 100.3}}`
This endpoint is similar to the `SET_HEATER_TEMPERATURE` G-Code
command, but the target temperature takes effect immediately. It does
not wait for pending G-Code commands to complete.
If this endpoint is issued for a heater while a `WAIT_TEMPERATURE`
command (or `M109`, `M190`) is pending for that heater, then the
requested target temperature will be set and the `WAIT_TEMPERATURE`
command will exit with an error.
### motion_report/dump_stepper ### motion_report/dump_stepper
This endpoint is used to subscribe to Klipper's internal stepper This endpoint is used to subscribe to Klipper's internal stepper
@@ -364,42 +380,35 @@ and might later produce asynchronous messages such as:
The "header" field in the initial query response is used to describe The "header" field in the initial query response is used to describe
the fields found in later "data" responses. the fields found in later "data" responses.
### load_cell/dump_force ### hx71x/dump_hx71x
This endpoint is used to subscribe to force data produced by a load_cell. This endpoint is used to subscribe to raw HX711 and HX717 ADC data.
Using this endpoint may increase Klipper's system load. Obtaining these low-level ADC updates may be useful for diagnostic
and debugging purposes. Using this endpoint may increase Klipper's
system load.
A request may look like: A request may look like:
`{"id": 123, "method":"load_cell/dump_force", `{"id": 123, "method":"hx71x/dump_hx71x",
"params": {"sensor": "load_cell", "response_template": {}}}` "params": {"sensor": "load_cell", "response_template": {}}}`
and might return: and might return:
`{"id": 123,"result":{"header":["time", "force (g)", "counts", "tare_counts"]}}` `{"id": 123,"result":{"header":["time","counts"]}}`
and might later produce asynchronous messages such as: and might later produce asynchronous messages such as:
`{"params":{"data":[[3292.432935, 40.65, 562534, -234467]]}}` `{"params":{"data":[[3292.432935, 562534], [3292.4394937, 5625322]]}}`
The "header" field in the initial query response is used to describe ### ads1220/dump_ads1220
the fields found in later "data" responses.
### load_cell_probe/dump_taps This endpoint is used to subscribe to raw ADS1220 ADC data.
Obtaining these low-level ADC updates may be useful for diagnostic
This endpoint is used to subscribe to details of probing "tap" events. and debugging purposes. Using this endpoint may increase Klipper's
Using this endpoint may increase Klipper's system load. system load.
A request may look like: A request may look like:
`{"id": 123, "method":"load_cell/dump_force", `{"id": 123, "method":"ads1220/dump_ads1220",
"params": {"sensor": "load_cell", "response_template": {}}}` "params": {"sensor": "load_cell", "response_template": {}}}`
and might return: and might return:
`{"id": 123,"result":{"header":["probe_tap_event"]}}` `{"id": 123,"result":{"header":["time","counts"]}}`
and might later produce asynchronous messages such as: and might later produce asynchronous messages such as:
``` `{"params":{"data":[[3292.432935, 562534], [3292.4394937, 5625322]]}}`
{"params":{"tap":'{
"time": [118032.28039, 118032.2834, ...],
"force": [-459.4213119680034, -458.1640702543264, ...],
}}}
```
This data can be used to render:
* The time/force graph
### pause_resume/cancel ### pause_resume/cancel

View File

@@ -1,6 +1,6 @@
# Axis Twist Compensation # Axis Twist Compensation
This document describes the `[axis_twist_compensation]` module. This document describes the [axis_twist_compensation] module.
Some printers may have a small twist in their X rail which can skew the results Some printers may have a small twist in their X rail which can skew the results
of a probe attached to the X carriage. of a probe attached to the X carriage.
@@ -24,50 +24,27 @@ try to probe the bed without attaching the probe if you use it.
> **Tip:** Make sure the [probe X and Y offsets](Config_Reference.md#probe) are > **Tip:** Make sure the [probe X and Y offsets](Config_Reference.md#probe) are
> correctly set as they greatly influence calibration. > correctly set as they greatly influence calibration.
### Basic Usage: X-Axis Calibration 1. After setting up the [axis_twist_compensation] module,
1. After setting up the `[axis_twist_compensation]` module, run: perform `AXIS_TWIST_COMPENSATION_CALIBRATE`
``` * The calibration wizard will prompt you to measure the probe Z offset at a few
AXIS_TWIST_COMPENSATION_CALIBRATE points along the bed
``` * The calibration defaults to 3 points but you can use the option
This command will calibrate the X-axis by default. `SAMPLE_COUNT=` to use a different number.
- The calibration wizard will prompt you to measure the probe Z offset at 2. [Adjust your Z offset](Probe_Calibrate.md#calibrating-probe-z-offset)
several points along the bed. 3. Perform automatic/probe-based bed tramming operations, such as
- By default, the calibration uses 3 points, but you can specify a different [Screws Tilt Adjust](G-Codes.md#screws_tilt_adjust),
number with the option: [Z Tilt Adjust](G-Codes.md#z_tilt_adjust) etc
`` 4. Home all axis, then perform a [Bed Mesh](Bed_Mesh.md) if required
SAMPLE_COUNT=<value> 5. Perform a test print, followed by any
`` [fine-tuning](Axis_Twist_Compensation.md#fine-tuning) as desired
2. **Adjust Your Z Offset:**
After completing the calibration, be sure to
[adjust your Z offset](Probe_Calibrate.md#calibrating-probe-z-offset).
3. **Perform Bed Leveling Operations:**
Use probe-based operations as needed, such as:
- [Screws Tilt Adjust](G-Codes.md#screws_tilt_adjust)
- [Z Tilt Adjust](G-Codes.md#z_tilt_adjust)
4. **Finalize the Setup:**
- Home all axes, and perform a [Bed Mesh](Bed_Mesh.md) if necessary.
- Run a test print, followed by any
[fine-tuning](Axis_Twist_Compensation.md#fine-tuning)
if needed.
### For Y-Axis Calibration
The calibration process for the Y-axis is similar to the X-axis. To calibrate
the Y-axis, use:
```
AXIS_TWIST_COMPENSATION_CALIBRATE AXIS=Y
```
This will guide you through the same measuring process as for the X-axis.
> **Tip:** Bed temperature and nozzle temperature and size do not seem to have > **Tip:** Bed temperature and nozzle temperature and size do not seem to have
> an influence to the calibration process. > an influence to the calibration process.
## [axis_twist_compensation] setup and commands ## [axis_twist_compensation] setup and commands
Configuration options for `[axis_twist_compensation]` can be found in the Configuration options for [axis_twist_compensation] can be found in the
[Configuration Reference](Config_Reference.md#axis_twist_compensation). [Configuration Reference](Config_Reference.md#axis_twist_compensation).
Commands for `[axis_twist_compensation]` can be found in the Commands for [axis_twist_compensation] can be found in the
[G-Codes Reference](G-Codes.md#axis_twist_compensation) [G-Codes Reference](G-Codes.md#axis_twist_compensation)

View File

@@ -267,9 +267,9 @@ by heat or interference. This can make calculating the probe's z-offset
challenging, particularly at different bed temperatures. As such, some challenging, particularly at different bed temperatures. As such, some
printers use an endstop for homing the Z axis and a probe for calibrating the 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) mesh. In this configuration it is possible offset the mesh so that the (X, Y)
`reference position` applies zero adjustment. The `reference position` should `reference position` applies zero adjustment. The `reference postion` should
be the location on the bed where a be the location on the bed where a
[Z_ENDSTOP_CALIBRATE](./Manual_Level.md#calibrating-a-z-endstop) [Z_ENDSTOP_CALIBRATE](./Manual_Level#calibrating-a-z-endstop)
paper test is performed. The bed_mesh module provides the paper test is performed. The bed_mesh module provides the
`zero_reference_position` option for specifying this coordinate: `zero_reference_position` option for specifying this coordinate:
@@ -292,6 +292,33 @@ probe_count: 5, 3
z-offset. Note that this coordinate must NOT be in a location specified as z-offset. Note that this coordinate must NOT be in a location specified as
a `faulty_region` if a probe is necessary. 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 ### Faulty Regions
It is possible for some areas of a bed to report inaccurate results when It is possible for some areas of a bed to report inaccurate results when
@@ -470,8 +497,7 @@ _Default Adaptive Margin: 0_
Initiates the probing procedure for Bed Mesh Calibration. Initiates the probing procedure for Bed Mesh Calibration.
The mesh will be immediately ready to use when the command completes and saved The mesh will be saved into a profile specified by the `PROFILE` parameter,
into a profile specified by the `PROFILE` parameter,
or `default` if unspecified. The `METHOD` parameter takes one of the following or `default` if unspecified. The `METHOD` parameter takes one of the following
values: values:
@@ -535,10 +561,6 @@ load the `default` profile it is recommended to add
`BED_MESH_PROFILE LOAD=default` to either their `START_PRINT` macro or their `BED_MESH_PROFILE LOAD=default` to either their `START_PRINT` macro or their
slicer's "Start G-Code" configuration, whichever is applicable. slicer's "Start G-Code" configuration, whichever is applicable.
Note that this is not required if a new mesh is generated with
`BED_MESH_CALIBRATE` in the `START_PRINT` macro or the slicer's "Start G-Code"
and may produce unexpected results, especially with adaptive meshing.
Alternatively the old behavior of loading a profile at startup can be Alternatively the old behavior of loading a profile at startup can be
restored with a `[delayed_gcode]`: restored with a `[delayed_gcode]`:

View File

@@ -250,22 +250,23 @@ results were obtained by running an STM32F407 binary on an STM32F446
### STM32H7 step rate benchmark ### STM32H7 step rate benchmark
The following configuration sequence is used on STM32H723: The following configuration sequence is used on a STM32H743VIT6:
``` ```
allocate_oids count=3 allocate_oids count=3
config_stepper oid=0 step_pin=PA13 dir_pin=PB5 invert_step=-1 step_pulse_ticks=52 config_stepper oid=0 step_pin=PD4 dir_pin=PD3 invert_step=-1 step_pulse_ticks=0
config_stepper oid=1 step_pin=PB2 dir_pin=PB6 invert_step=-1 step_pulse_ticks=52 config_stepper oid=1 step_pin=PA15 dir_pin=PA8 invert_step=-1 step_pulse_ticks=0
config_stepper oid=2 step_pin=PB3 dir_pin=PB7 invert_step=-1 step_pulse_ticks=52 config_stepper oid=2 step_pin=PE2 dir_pin=PE3 invert_step=-1 step_pulse_ticks=0
finalize_config crc=0 finalize_config crc=0
``` ```
The test was last run on commit `554ae78d` with gcc version The test was last run on commit `00191b5c` with gcc version
`arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0`. `arm-none-eabi-gcc (15:8-2019-q3-1+b1) 8.3.1 20190703 (release)
[gcc-8-branch revision 273027]`.
| stm32h723 | ticks | | stm32h7 | ticks |
| -------------------- | ----- | | -------------------- | ----- |
| 1 stepper | 70 | | 1 stepper | 44 |
| 3 stepper | 181 | | 3 stepper | 198 |
### STM32G0B1 step rate benchmark ### STM32G0B1 step rate benchmark
@@ -286,25 +287,6 @@ The test was last run on commit `247cd753` with gcc version
| 1 stepper | 58 | | 1 stepper | 58 |
| 3 stepper | 243 | | 3 stepper | 243 |
### STM32G4 step rate benchmark
The following configuration sequence is used on the STM32G431:
```
allocate_oids count=3
config_stepper oid=0 step_pin=PA0 dir_pin=PB5 invert_step=-1 step_pulse_ticks=17
config_stepper oid=1 step_pin=PB2 dir_pin=PB6 invert_step=-1 step_pulse_ticks=17
config_stepper oid=2 step_pin=PB3 dir_pin=PB7 invert_step=-1 step_pulse_ticks=17
finalize_config crc=0
```
The test was last run on commit `cfa48fe3` with gcc version
`arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0`.
| stm32g431 | ticks |
| ---------------- | ----- |
| 1 stepper | 47 |
| 3 stepper | 208 |
### LPC176x step rate benchmark ### LPC176x step rate benchmark
The following configuration sequence is used on the LPC176x: The following configuration sequence is used on the LPC176x:
@@ -372,26 +354,6 @@ micro-controller.
| 1 stepper (200Mhz) | 39 | | 1 stepper (200Mhz) | 39 |
| 3 stepper (200Mhz) | 181 | | 3 stepper (200Mhz) | 181 |
### SAME70 step rate benchmark
The following configuration sequence is used on the SAME70:
```
allocate_oids count=3
config_stepper oid=0 step_pin=PC18 dir_pin=PB5 invert_step=-1 step_pulse_ticks=0
config_stepper oid=1 step_pin=PC16 dir_pin=PD10 invert_step=-1 step_pulse_ticks=0
config_stepper oid=2 step_pin=PC28 dir_pin=PA4 invert_step=-1 step_pulse_ticks=0
finalize_config crc=0
```
The test was last run on commit `34e9ea55` with gcc version
`arm-none-eabi-gcc (NixOS 10.3-2021.10) 10.3.1` on a SAME70Q20B
micro-controller.
| same70 | ticks |
| -------------------- | ----- |
| 1 stepper | 45 |
| 3 stepper | 190 |
### AR100 step rate benchmark ### ### AR100 step rate benchmark ###
The following configuration sequence is used on AR100 CPU (Allwinner A64): The following configuration sequence is used on AR100 CPU (Allwinner A64):
@@ -404,7 +366,7 @@ finalize_config crc=0
``` ```
The test was last run on commit `b7978d37` with gcc version The test was last run on commit `08d037c6` with gcc version
`or1k-linux-musl-gcc (GCC) 9.2.0` on an Allwinner A64-H `or1k-linux-musl-gcc (GCC) 9.2.0` on an Allwinner A64-H
micro-controller. micro-controller.
@@ -413,9 +375,9 @@ micro-controller.
| 1 stepper | 85 | | 1 stepper | 85 |
| 3 stepper | 359 | | 3 stepper | 359 |
### RPxxxx step rate benchmark ### RP2040 step rate benchmark
The following configuration sequence is used on the RP2040 and RP2350: The following configuration sequence is used on the RP2040:
``` ```
allocate_oids count=3 allocate_oids count=3
@@ -425,25 +387,14 @@ config_stepper oid=2 step_pin=gpio27 dir_pin=gpio5 invert_step=-1 step_pulse_tic
finalize_config crc=0 finalize_config crc=0
``` ```
The test was last run on commit `14c105b8` with gcc version The test was last run on commit `59314d99` with gcc version
`arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0` on Raspberry Pi `arm-none-eabi-gcc (Fedora 10.2.0-4.fc34) 10.2.0` on a Raspberry Pi
Pico and Pico 2 boards. Pico board.
| rp2040 (*) | ticks | | rp2040 | ticks |
| -------------------- | ----- | | -------------------- | ----- |
| 1 stepper | 3 | | 1 stepper | 5 |
| 3 stepper | 14 | | 3 stepper | 22 |
| rp2350 | ticks |
| -------------------- | ----- |
| 1 stepper | 36 |
| 3 stepper | 169 |
(*) Note that the reported rp2040 ticks are relative to a 12Mhz
scheduling timer and do not correspond to its 200Mhz internal ARM
processing rate. It is expected that 3 scheduling ticks corresponds to
~42 ARM core cycles and 14 scheduling ticks corresponds to ~225 ARM
core cycles.
### Linux MCU step rate benchmark ### Linux MCU step rate benchmark
@@ -482,23 +433,18 @@ When the test completes, determine the difference between the clocks
reported in the two "uptime" response messages. The total number of reported in the two "uptime" response messages. The total number of
commands per second is then `100000 * mcu_frequency / clock_diff`. commands per second is then `100000 * mcu_frequency / clock_diff`.
The USB tests may exceed the CPU capacity of a Raspberry Pi. If Note that this test may saturate the USB/CPU capacity of a Raspberry
running on a Raspberry Pi, Beaglebone, or similar host computer then Pi. If running on a Raspberry Pi, Beaglebone, or similar host computer
increase the delay (eg, `DELAY {clock + 20*freq} get_uptime`). Where then increase the delay (eg, `DELAY {clock + 20*freq} get_uptime`).
applicable, the benchmarks below are with console.py running on a Where applicable, the benchmarks below are with console.py running on
desktop class machine with the device connected via a super-speed hub. a desktop class machine with the device connected via a high-speed
hub.
The CAN bus tests may saturate the USB host controller of a Raspberry
Pi (when testing via a standard gs_usb USB to CAN bus adapter). Where
applicable, the CAN bus benchmarks below are with console.py running
on a desktop class machine with a USB to CAN bus adapter connected via
a super-speed USB hub.
| MCU | Rate | Build | Build compiler | | MCU | Rate | Build | Build compiler |
| ------------------- | ---- | -------- | ------------------- | | ------------------- | ---- | -------- | ------------------- |
| stm32f042 (CAN) | 18K | c105adc8 | arm-none-eabi-gcc (GNU Tools 7-2018-q3-update) 7.3.1 |
| atmega2560 (serial) | 23K | b161a69e | avr-gcc (GCC) 4.8.1 | | atmega2560 (serial) | 23K | b161a69e | avr-gcc (GCC) 4.8.1 |
| sam3x8e (serial) | 23K | b161a69e | arm-none-eabi-gcc (Fedora 7.1.0-5.fc27) 7.1.0 | | sam3x8e (serial) | 23K | b161a69e | arm-none-eabi-gcc (Fedora 7.1.0-5.fc27) 7.1.0 |
| rp2350 (CAN) | 59K | 17b8ce4c | arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0 |
| at90usb1286 (USB) | 75K | 01d2183f | avr-gcc (GCC) 5.4.0 | | at90usb1286 (USB) | 75K | 01d2183f | avr-gcc (GCC) 5.4.0 |
| ar100 (serial) | 138K | 08d037c6 | or1k-linux-musl-gcc 9.3.0 | | ar100 (serial) | 138K | 08d037c6 | or1k-linux-musl-gcc 9.3.0 |
| samd21 (USB) | 223K | 01d2183f | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 | | samd21 (USB) | 223K | 01d2183f | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 |
@@ -510,8 +456,7 @@ a super-speed USB hub.
| sam4s8c (USB) | 650K | 8d4a5c16 | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 | | sam4s8c (USB) | 650K | 8d4a5c16 | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 |
| samd51 (USB) | 864K | 01d2183f | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 | | samd51 (USB) | 864K | 01d2183f | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 |
| stm32f446 (USB) | 870K | 01d2183f | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 | | stm32f446 (USB) | 870K | 01d2183f | arm-none-eabi-gcc (Fedora 7.4.0-1.fc30) 7.4.0 |
| rp2040 (USB) | 885K | f6718291 | arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0 | | rp2040 (USB) | 873K | c5667193 | arm-none-eabi-gcc (Fedora 10.2.0-4.fc34) 10.2.0 |
| rp2350 (USB) | 885K | f6718291 | arm-none-eabi-gcc (Fedora 14.1.0-1.fc40) 14.1.0 |
## Host Benchmarks ## Host Benchmarks

View File

@@ -194,7 +194,7 @@ Alternatively, one can use a
When using OpenOCD with the SAMC21, extra steps must be taken to first 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 put the chip into Cold Plugging mode if the board makes use of the
SWD pins for other purposes. If using OpenOCD on a Raspberry Pi, this SWD pins for other purposes. If using OpenOCD on a Rasberry Pi, this
can be done by running the following commands before invoking OpenOCD. can be done by running the following commands before invoking OpenOCD.
``` ```
SWCLK=25 SWCLK=25

View File

@@ -125,14 +125,10 @@ iface can0 can static
frequency. As a result, it is recommended to use a CAN bus frequency frequency. As a result, it is recommended to use a CAN bus frequency
of 1000000 when using "USB to CAN bus bridge mode". of 1000000 when using "USB to CAN bus bridge mode".
* It is only valid to use USB to CAN bridge mode if there is a Even at a CAN bus frequency of 1000000, there may not be sufficient
functioning CAN bus with at least one other node available (in bandwidth to run a `SHAPER_CALIBRATE` test if both the XY steppers
addition to the bridge node itself). Use a standard USB and the accelerometer all communicate via a single "USB to CAN bus"
configuration if the goal is to communicate only with the single USB interface.
device. Using USB to CAN bridge mode without a fully functioning CAN
bus (including terminating resistors and an additional node) may
result in sporadic errors even when communicating with the bridge
node.
* A USB to CAN bridge board will not appear as a USB serial device, it * A USB to CAN bridge board will not appear as a USB serial device, it
will not show up when running `ls /dev/serial/by-id`, and it can not will not show up when running `ls /dev/serial/by-id`, and it can not

View File

@@ -37,36 +37,20 @@ hours or more frequently) then it is an indication of a severe
problem. problem.
Incrementing `bytes_invalid` on a CAN bus connection is a symptom of Incrementing `bytes_invalid` on a CAN bus connection is a symptom of
reordered messages on the CAN bus. If seen, make sure to: reordered messages on the CAN bus. There are two known causes of
* Use a Linux kernel version 6.6.0 or later. reordered messages:
* If using a USB-to-CANBUS adapter running candlelight firmware, use 1. Old versions of the popular candlight_firmware for USB CAN adapters
v2.0 or later of candleLight_fw. had a bug that could cause reordered messages. If using a USB CAN
* If using Klipper's USB-to-CANBUS bridge mode, make sure the bridge adapter running this firmware then make sure to update to the
node is flashed with Klipper v0.12.0 or later. latest firmware if incrementing `bytes_invalid` is observed.
2. Some Linux kernel builds for embedded devices have been known to
reorder CAN bus messages. It may be necessary to use an alternative
Linux kernel or to use alternative hardware that supports
mainstream Linux kernels that do not exhibit this problem.
Reordered messages is a severe problem that must be fixed. It will Reordered messages is a severe problem that must be fixed. It will
result in unstable behavior and can lead to confusing errors at any result in unstable behavior and can lead to confusing errors at any
part of a print. An incrementing `bytes_invalid` is not caused by part of a print.
wiring or similar hardware issues and can only be fixed by identifying
and updating the faulty software.
Older versions of the Linux kernel had a bug in the gs_usb canbus
driver code that could cause reordered canbus packets. The issue is
thought to be fixed in
[Linux commit 24bc41b4](https://github.com/torvalds/linux/commit/24bc41b4558347672a3db61009c339b1f5692169)
which was released in v6.6.0. In some cases, older Linux versions may
not show the problem (due to how hardware interrupts are configured),
however if problems are seen the recommended solution is to upgrade to
a newer kernel.
Older versions of candlelight firmware could reorder canbus packets,
and the issue is thought to be fixed in
[candlelight_fw commit 8b3a7b45](https://github.com/candle-usb/candleLight_fw/commit/8b3a7b4565a3c9521b762b154c94c72c5acb2bcf).
Older versions of Klipper's USB-to-CANBUS bridge code could
incorrectly drop canbus messages. This is not as severe as reordering
messages, but it should still be fixed. It is thought to be fixed with
[Klipper PR #6175](https://github.com/Klipper3d/klipper/pull/6175).
## Use an appropriate txqueuelen setting ## Use an appropriate txqueuelen setting
@@ -118,23 +102,6 @@ necessary to increase the `txqueuelen` above the recommended value
of 128. However, as above, care should be taken when selecting a new of 128. However, as above, care should be taken when selecting a new
value to avoid excessive round-trip-time latency. value to avoid excessive round-trip-time latency.
## Use `canbus_query.py` only to identify nodes never previously seen
It is only valid to use the
[`canbus_query.py` tool](CANBUS.md#finding-the-canbus_uuid-for-new-micro-controllers)
to identify micro-controllers that have never been previously
identified. Once all nodes on a bus are identified, record the
resulting uuids in the printer.cfg, and avoid running the tool
unnecessarily.
The tool is implemented using a low-level mechanism that can cause
nodes to internally observe bus errors. These internal errors may
result in communication interruptions and may result is some nodes
disconnecting from the bus.
It is not valid to use the tool to "ping" if a node is connected. Do
not run the tool during an active print.
## Obtaining candump logs ## Obtaining candump logs
The CAN bus messages sent to and from the micro-controller are handled The CAN bus messages sent to and from the micro-controller are handled

View File

@@ -323,7 +323,7 @@ a month without updates.
Once the requirements are met, you need to: Once the requirements are met, you need to:
1. update klipper-translations repository 1. update klipper-tranlations repository
[active_translations](https://github.com/Klipper3d/klipper-translations/blob/translations/active_translations) [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 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 `docs\locals\<lang>` folder to replace the language specific index.md (generated

View File

@@ -286,11 +286,6 @@ The following may also be useful:
during the `load_config()` or "connect event" phases. Use either during the `load_config()` or "connect event" phases. Use either
`raise config.error("my error")` or `raise printer.config_error("my `raise config.error("my error")` or `raise printer.config_error("my
error")` to report the error. error")` to report the error.
* Do not store a reference to the `config` object in a class member
variable (nor in any similar location that may persist past initial
module loading). The `config` object is a reference to a "config
loading phase" class and it is not valid to invoke its methods after
the "config loading phase" has completed.
* Use the "pins" module to configure a pin on a micro-controller. This * Use the "pins" module to configure a pin on a micro-controller. This
is typically done with something similar to is typically done with something similar to
`printer.lookup_object("pins").setup_pin("pwm", `printer.lookup_object("pins").setup_pin("pwm",
@@ -364,10 +359,10 @@ Useful steps:
be efficient as it is typically only called during homing and be efficient as it is typically only called during homing and
probing operations. probing operations.
5. Other methods. Implement the `check_move()`, `get_status()`, 5. Other methods. Implement the `check_move()`, `get_status()`,
`get_steppers()`, `home()`, `clear_homing_state()`, and `set_position()` `get_steppers()`, `home()`, and `set_position()` methods. These
methods. These functions are typically used to provide kinematic functions are typically used to provide kinematic specific checks.
specific checks. However, at the start of development one can use However, at the start of development one can use boiler-plate code
boiler-plate code here. here.
6. Implement test cases. Create a g-code file with a series of moves 6. Implement test cases. Create a g-code file with a series of moves
that can test important cases for the given kinematics. Follow the that can test important cases for the given kinematics. Follow the
[debugging documentation](Debugging.md) to convert this g-code file [debugging documentation](Debugging.md) to convert this g-code file

View File

@@ -8,72 +8,6 @@ All dates in this document are approximate.
## Changes ## Changes
20250811: Support for the `max_accel_to_decel` parameter in the
`[printer]` config section has been removed and support for the
`ACCEL_TO_DECEL` parameter in the `SET_VELOCITY_LIMIT` command has
been removed. These capabilities were deprecated on 20240313.
20250721: The `[pca9632]` and `[mcp4018]` modules no longer accept the
`scl_pin` and `sda_pin` options. Use `i2c_software_scl_pin` and
`i2c_software_sda_pin` instead.
20250428: The maximum `cycle_time` for pwm `[output_pin]`,
`[pwm_cycle_time]`, `[pwm_tool]`, and similar config sections is now 3
seconds (reduced from 5 seconds). The `maximum_mcu_duration` in
`[pwm_tool]` is now also 3 seconds.
20250418: The manual_stepper `STOP_ON_ENDSTOP` feature may now take
less time to complete. Previously, the command would wait the entire
time the move could possibly take even if the endstop triggered
earlier. Now, the command finishes shortly after the endstop trigger.
20250417: SPI devices using "software SPI" are now rate limited.
Previously, the `spi_speed` in the config was ignored and the
transmission speed was only limited by the processing speed of the
micro-controller. Now, speeds are limited by the `spi_speed` config
parameter (actual hardware speeds are likely to be lower than the
configured value due to software overhead).
20250411: Klipper v0.13.0 released.
20250308: The `AUTO` parameter of the
`AXIS_TWIST_COMPENSATION_CALIBRATE` command has been removed.
20250131: Option `VARIABLE=<name>` in `SAVE_VARIABLE` requires lowercase
value. For example, `extruder` instead of mixedcase `Extruder` or
uppercase `EXTRUDER`. Using any uppercase letter will raise an error.
20241203: The resonance test has been changed to include slow sweeping
moves. This change requires that testing point(s) have some clearance
in X/Y plane (+/- 30 mm from the test point should suffice when using
the default settings). The new test should generally produce more
accurate and reliable test results. However, if required, the previous
test behavior can be restored by adding options `sweeping_period: 0` and
`accel_per_hz: 75` to the `[resonance_tester]` config section.
20241201: In some cases Klipper may have ignored leading characters or
spaces in a traditional G-Code command. For example, "99M123" may have
been interpreted as "M123" and "M 321" may have been interpreted as
"M321". Klipper will now report these cases with an "Unknown command"
warning.
20241112: Option `CHIPS=<chip_name>` in `TEST_RESONANCES` and
`SHAPER_CALIBRATE` requires specifying the full name(s) of the accel
chip(s). For example, `adxl345 rpi` instead of short name - `rpi`.
20240912: `SET_PIN`, `SET_SERVO`, `SET_FAN_SPEED`, `M106`, and `M107`
commands are now collated. Previously, if many updates to the same
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
required then consider adding explicit `G4` delay commands between
updates.
20240912: Support for `maximum_mcu_duration` and `static_value`
parameters in `[output_pin]` config sections have been removed. These
options have been deprecated since 20240123.
20240415: The `on_error_gcode` parameter in the `[virtual_sdcard]` 20240415: The `on_error_gcode` parameter in the `[virtual_sdcard]`
config section now has a default. If this parameter is not specified config section now has a default. If this parameter is not specified
it now defaults to `TURN_OFF_HEATERS`. If the previous behavior is it now defaults to `TURN_OFF_HEATERS`. If the previous behavior is
@@ -140,7 +74,7 @@ carriage are exported as `printer.dual_carriage.carriage_0` and
`printer.dual_carriage.carriage_1`. `printer.dual_carriage.carriage_1`.
20230619: The `relative_reference_index` option has been deprecated 20230619: The `relative_reference_index` option has been deprecated
and superseded by the `zero_reference_position` option. Refer to the and superceded by the `zero_reference_position` option. Refer to the
[Bed Mesh Documentation](./Bed_Mesh.md#the-deprecated-relative_reference_index) [Bed Mesh Documentation](./Bed_Mesh.md#the-deprecated-relative_reference_index)
for details on how to update the configuration. With this deprecation for details on how to update the configuration. With this deprecation
the `RELATIVE_REFERENCE_INDEX` is no longer available as a parameter the `RELATIVE_REFERENCE_INDEX` is no longer available as a parameter
@@ -374,7 +308,7 @@ endstop phases by running the ENDSTOP_PHASE_CALIBRATE command.
`gear_ratio` for their rotary steppers, and they may no longer specify `gear_ratio` for their rotary steppers, and they may no longer specify
a `step_distance` parameter. See the a `step_distance` parameter. See the
[config reference](Config_Reference.md#stepper) for the format of the [config reference](Config_Reference.md#stepper) for the format of the
new gear_ratio parameter. new gear_ratio paramter.
20201213: It is not valid to specify a Z "position_endstop" when using 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 "probe:z_virtual_endstop". An error will now be raised if a Z

View File

@@ -84,9 +84,8 @@ The printer section controls high level printer settings.
[printer] [printer]
kinematics: kinematics:
# The type of printer in use. This option may be one of: cartesian, # The type of printer in use. This option may be one of: cartesian,
# corexy, corexz, hybrid_corexy, hybrid_corexz, generic_cartesian, # corexy, corexz, hybrid_corexy, hybrid_corexz, rotary_delta, delta,
# rotary_delta, delta, deltesian, polar, winch, or none. # deltesian, polar, winch, or none. This parameter must be specified.
# This parameter must be specified.
max_velocity: max_velocity:
# Maximum velocity (in mm/s) of the toolhead (relative to the # Maximum velocity (in mm/s) of the toolhead (relative to the
# print). This value may be changed at runtime using the # print). This value may be changed at runtime using the
@@ -126,6 +125,8 @@ max_accel:
# decelerate to zero at each corner. The value specified here may be # decelerate to zero at each corner. The value specified here may be
# changed at runtime using the SET_VELOCITY_LIMIT command. The # changed at runtime using the SET_VELOCITY_LIMIT command. The
# default is 5mm/s. # default is 5mm/s.
#max_accel_to_decel:
# This parameter is deprecated and should no longer be used.
``` ```
### [stepper] ### [stepper]
@@ -711,171 +712,6 @@ anchor_z:
# These parameters must be provided. # These parameters must be provided.
``` ```
### Generic Cartesian Kinematics
See [example-generic-cartesian.cfg](../config/example-generic-caretesian.cfg)
for an example generic Cartesian kinematics config file.
This printer kinematic class allows a user to define in a pretty flexible
manner an arbitrary Cartesian-style kinematics. In principle, the regular
cartesian, corexy, hybrid_corexy can be defined this way too. However,
more importantly, various otherwise unsupported kinematics such as
inverted hybrid_corexy or corexyuv can be defined using this kinematic.
Notably, the definition of a generic Cartesian kinematic deviates
significantly from the other kinematic types. It follows the following
convention: a user defines a set of carriages with certain range of motion
that can move independently from each other (they should move over the
Cartesian axes X, Y, and Z, hence the name of the kinematic) and
corresponding endstops that allow the firmware to determine the position
of carriages during homing, as well as a set of steppers that move those
carriages. The `[printer]` section must specify the kinematic and
other printer-level settings same as the regular Cartesian kinematic:
```
[printer]
kinematics: generic_cartesian
max_velocity:
max_accel:
#minimum_cruise_ratio:
#square_corner_velocity:
#max_z_velocity:
#max_z_accel:
```
Then a user must define the following three carriages: `[carriage x]`,
`[carriage y]`, and `[carriage z]`, e.g.
```
[carriage x]
endstop_pin:
# Endstop switch detection pin. If this endstop pin is on a
# different mcu than the stepper motor(s) moving this carriage,
# then it enables "multi-mcu homing". This parameter must be provided.
#position_min: 0
# Minimum valid distance (in mm) the user may command the carriage to
# move to. The default is 0mm.
position_endstop:
# Location of the endstop (in mm). This parameter must be provided.
position_max:
# Maximum valid distance (in mm) the user may command the stepper to
# move to. This parameter must be provided.
#homing_speed: 5.0
# Maximum velocity (in mm/s) of the carriage when homing. The default
# is 5mm/s.
#homing_retract_dist: 5.0
# Distance to backoff (in mm) before homing a second time during
# homing. Set this to zero to disable the second home. The default
# is 5mm.
#homing_retract_speed:
# Speed to use on the retract move after homing in case this should
# be different from the homing speed, which is the default for this
# parameter
#second_homing_speed:
# Velocity (in mm/s) of the carriage when performing the second home.
# The default is homing_speed/2.
#homing_positive_dir:
# If true, homing will cause the carriage to move in a positive
# direction (away from zero); if false, home towards zero. It is
# better to use the default than to specify this parameter. The
# default is true if position_endstop is near position_max and false
# if near position_min.
```
Afterwards, a user specifies the stepper motors that move these carriages,
for instance
```
[stepper my_stepper]
carriages:
# A string describing the carriages the stepper moves. All defined
# carriages can be specified here, as well as their linear combinations,
# e.g. x, x+y, y-0.5*z, x-z, etc. This parameter must be provided.
step_pin:
dir_pin:
enable_pin:
rotation_distance:
microsteps:
#full_steps_per_rotation: 200
#gear_ratio:
#step_pulse_duration:
```
See [stepper](#stepper) section for more information on the regular
stepper parameters. The `carriages` parameter defines how the stepper
affects the motion of the carriages. For example, `x+y` indicates that
the motion of the stepper in the positive direction by the distance `d`
moves the carriages `x` and `y` by the same distance `d` in the positive
direction, while `x-0.5*y` means the motion of the stepper in the positive
direction by the distance `d` moves the carriage `x` by the distance `d`
in the positive direction, but the carriage `y` will travel distance `d/2`
in the negative direction.
More than a single stepper motor can be defined to drive the same axis
or belt. For example, on a CoreXY AWD setups two motors driving the same
belt can be defined as
```
[carriage x]
endstop_pin: ...
...
[carriage y]
endstop_pin: ...
...
[stepper a0]
carriages: x-y
step_pin: ...
dir_pin: ...
enable_pin: ...
rotation_distance: ...
...
[stepper a1]
carriages: x-y
step_pin: ...
dir_pin: ...
enable_pin: ...
rotation_distance: ...
...
```
with `a0` and `a1` steppers having their own control pins, but
sharing the same `carriages` and corresponding endstops.
There are situations when a user wants to have more than one endstop
per axis. Examples of such configurations include Y axis driven by
two independent stepper motors with belts attached to both ends of the
X beam, with effectively two carriages on Y axis each having an
independent endstop, and multi-stepper Z axis with each stepper having
its own endstop (not to be confused with the configurations with
multiple Z motors but only a single endstop). These configurations
can be declared by specifying additional carriage(s) with their endstops:
```
[extra_carriage my_carriage]
primary_carriage:
# The name of the primary carriage this carriage corresponds to.
# It also effectively defines the axis the carriage moves over.
# This parameter must be provided.
endstop_pin:
# Endstop switch detection pin. This parameter must be provided.
```
and the corresponding stepper motors, for example:
```
[extra_carriage y1]
primary_carriage: y
endstop_pin: ...
[stepper sy1]
carriages: y1
...
```
Notably, an `[extra_carriage]` does not define parameters such as
`position_min`, `position_max`, and `position_endstop`, but instead
inherits them from the specified `primary_carriage`, thus sharing
the same range of motion with the primary carriage.
For the references on how to configure IDEX setups, see the
[dual carriage](#dual-carriage) section.
### None Kinematics ### None Kinematics
It is possible to define a special "none" kinematics to disable It is possible to define a special "none" kinematics to disable
@@ -1833,34 +1669,14 @@ cs_pin:
# measurements. # measurements.
``` ```
### [icm20948]
Support for icm20948 accelerometers.
```
[icm20948]
#i2c_address:
# Default is 104 (0x68). If AD0 is high, it would be 0x69 instead.
#i2c_mcu:
#i2c_bus:
#i2c_software_scl_pin:
#i2c_software_sda_pin:
#i2c_speed: 400000
# See the "common I2C settings" section for a description of the
# above parameters. The default "i2c_speed" is 400000.
#axes_map: x, y, z
# See the "adxl345" section for information on this parameter.
```
### [lis2dw] ### [lis2dw]
Support for LIS2DW accelerometers. Support for LIS2DW accelerometers.
``` ```
[lis2dw] [lis2dw]
#cs_pin: cs_pin:
# The SPI enable pin for the sensor. This parameter must be provided # The SPI enable pin for the sensor. This parameter must be provided.
# if using SPI.
#spi_speed: 5000000 #spi_speed: 5000000
# The SPI speed (in hz) to use when communicating with the chip. # The SPI speed (in hz) to use when communicating with the chip.
# The default is 5000000. # The default is 5000000.
@@ -1870,46 +1686,6 @@ Support for LIS2DW accelerometers.
#spi_software_miso_pin: #spi_software_miso_pin:
# See the "common SPI settings" section for a description of the # See the "common SPI settings" section for a description of the
# above parameters. # above parameters.
#i2c_address:
# Default is 25 (0x19). If SA0 is high, it would be 24 (0x18) instead.
#i2c_mcu:
#i2c_bus:
#i2c_software_scl_pin:
#i2c_software_sda_pin:
#i2c_speed: 400000
# See the "common I2C settings" section for a description of the
# above parameters. The default "i2c_speed" is 400000.
#axes_map: x, y, z
# See the "adxl345" section for information on this parameter.
```
### [lis3dh]
Support for LIS3DH accelerometers.
```
[lis3dh]
#cs_pin:
# The SPI enable pin for the sensor. This parameter must be provided
# if using SPI.
#spi_speed: 5000000
# The SPI speed (in hz) to use when communicating with the chip.
# The default is 5000000.
#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.
#i2c_address:
# Default is 25 (0x19). If SA0 is high, it would be 24 (0x18) instead.
#i2c_mcu:
#i2c_bus:
#i2c_software_scl_pin:
#i2c_software_sda_pin:
#i2c_speed: 400000
# See the "common I2C settings" section for a description of the
# above parameters. The default "i2c_speed" is 400000.
#axes_map: x, y, z #axes_map: x, y, z
# See the "adxl345" section for information on this parameter. # See the "adxl345" section for information on this parameter.
``` ```
@@ -1973,14 +1749,11 @@ section of the measuring resonances guide for more information on
# auto-calibration (with 'SHAPER_CALIBRATE' command). By default no # auto-calibration (with 'SHAPER_CALIBRATE' command). By default no
# maximum smoothing is specified. Refer to Measuring_Resonances guide # maximum smoothing is specified. Refer to Measuring_Resonances guide
# for more details on using this feature. # for more details on using this feature.
#move_speed: 50
# The speed (in mm/s) to move the toolhead to and between test points
# during the calibration. The default is 50.
#min_freq: 5 #min_freq: 5
# Minimum frequency to test for resonances. The default is 5 Hz. # Minimum frequency to test for resonances. The default is 5 Hz.
#max_freq: 133.33 #max_freq: 133.33
# Maximum frequency to test for resonances. The default is 133.33 Hz. # Maximum frequency to test for resonances. The default is 133.33 Hz.
#accel_per_hz: 60 #accel_per_hz: 75
# This parameter is used to determine which acceleration to use to # This parameter is used to determine which acceleration to use to
# test a specific frequency: accel = accel_per_hz * freq. Higher the # test a specific frequency: accel = accel_per_hz * freq. Higher the
# value, the higher is the energy of the oscillations. Can be set to # value, the higher is the energy of the oscillations. Can be set to
@@ -1994,13 +1767,6 @@ section of the measuring resonances guide for more information on
# hz_per_sec. Small values make the test slow, and the large values # hz_per_sec. Small values make the test slow, and the large values
# will decrease the precision of the test. The default value is 1.0 # will decrease the precision of the test. The default value is 1.0
# (Hz/sec == sec^-2). # (Hz/sec == sec^-2).
#sweeping_accel: 400
# An acceleration of slow sweeping moves. The default is 400 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
# non-zero value in order to not poison the measurements.
# The default is 1.2 sec which is a good all-round choice.
``` ```
## Config file helpers ## Config file helpers
@@ -2248,9 +2014,6 @@ Support for eddy current inductive probes. One may define this section
sensor_type: ldc1612 sensor_type: ldc1612
# The sensor chip used to perform eddy current measurements. This # The sensor chip used to perform eddy current measurements. This
# parameter must be provided and must be set to ldc1612. # parameter must be provided and must be set to ldc1612.
#frequency:
# The external crystal frequency (in Hz) of the LDC1612 chip.
# The default is 12000000.
#intb_pin: #intb_pin:
# MCU gpio pin connected to the ldc1612 sensor's INTB pin (if # MCU gpio pin connected to the ldc1612 sensor's INTB pin (if
# available). The default is to not use the INTB pin. # available). The default is to not use the INTB pin.
@@ -2279,9 +2042,9 @@ sensor_type: ldc1612
### [axis_twist_compensation] ### [axis_twist_compensation]
A tool to compensate for inaccurate probe readings due to twist in X or Y A tool to compensate for inaccurate probe readings due to twist in X gantry. See
gantry. See the [Axis Twist Compensation Guide](Axis_Twist_Compensation.md) the [Axis Twist Compensation Guide](Axis_Twist_Compensation.md) for more
for more detailed information regarding symptoms, configuration and setup. detailed information regarding symptoms, configuration and setup.
``` ```
[axis_twist_compensation] [axis_twist_compensation]
@@ -2294,33 +2057,16 @@ for more detailed information regarding symptoms, configuration and setup.
calibrate_start_x: 20 calibrate_start_x: 20
# Defines the minimum X coordinate of the calibration # Defines the minimum X coordinate of the calibration
# This should be the X coordinate that positions the nozzle at the starting # This should be the X coordinate that positions the nozzle at the starting
# calibration position. # calibration position. This parameter must be provided.
calibrate_end_x: 200 calibrate_end_x: 200
# Defines the maximum X coordinate of the calibration # Defines the maximum X coordinate of the calibration
# This should be the X coordinate that positions the nozzle at the ending # This should be the X coordinate that positions the nozzle at the ending
# calibration position. # calibration position. This parameter must be provided.
calibrate_y: 112.5 calibrate_y: 112.5
# Defines the Y coordinate of the calibration # Defines the Y coordinate of the calibration
# This should be the Y coordinate that positions the nozzle during the # This should be the Y coordinate that positions the nozzle during the
# calibration process. This parameter is recommended to # calibration process. This parameter must be provided and is recommended to
# be near the center of the bed # be near the center of the bed
# For Y-axis twist compensation, specify the following parameters:
calibrate_start_y: ...
# Defines the minimum Y coordinate of the calibration
# This should be the Y coordinate that positions the nozzle at the starting
# calibration position for the Y axis. This parameter must be provided if
# compensating for Y axis twist.
calibrate_end_y: ...
# Defines the maximum Y coordinate of the calibration
# This should be the Y coordinate that positions the nozzle at the ending
# calibration position for the Y axis. This parameter must be provided if
# compensating for Y axis twist.
calibrate_x: ...
# Defines the X coordinate of the calibration for Y axis twist compensation
# This should be the X coordinate that positions the nozzle during the
# calibration process for Y axis twist compensation. This parameter must be
# provided and is recommended to be near the center of the bed.
``` ```
## Additional stepper motors and extruders ## Additional stepper motors and extruders
@@ -2371,8 +2117,8 @@ for an example configuration.
### [dual_carriage] ### [dual_carriage]
Support for cartesian, generic_cartesian and hybrid_corexy/z printers with Support for cartesian and hybrid_corexy/z printers with dual carriages
dual carriages on a single axis. The carriage mode can be set via the on a single axis. The carriage mode can be set via the
SET_DUAL_CARRIAGE extended g-code command. For example, SET_DUAL_CARRIAGE extended g-code command. For example,
"SET_DUAL_CARRIAGE CARRIAGE=1" command will activate the carriage defined "SET_DUAL_CARRIAGE CARRIAGE=1" command will activate the carriage defined
in this section (CARRIAGE=0 will return activation to the primary carriage). in this section (CARRIAGE=0 will return activation to the primary carriage).
@@ -2399,7 +2145,7 @@ typically be achieved with
or a similar command. or a similar command.
See [sample-idex.cfg](../config/sample-idex.cfg) for an example See [sample-idex.cfg](../config/sample-idex.cfg) for an example
configuration with a regular Cartesian kinematic. configuration.
``` ```
[dual_carriage] [dual_carriage]
@@ -2413,7 +2159,7 @@ axis:
# error. If safe_distance is not provided, it will be inferred from # error. If safe_distance is not provided, it will be inferred from
# position_min and position_max for the dual and primary carriages. If set # 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 # to 0 (or safe_distance is unset and position_min and position_max are
# identical for the primary and dual carriages), the carriages proximity # identical for the primary and dual carraiges), the carriages proximity
# checks will be disabled. # checks will be disabled.
#step_pin: #step_pin:
#dir_pin: #dir_pin:
@@ -2427,83 +2173,6 @@ axis:
# See the "stepper" section for the definition of the above parameters. # See the "stepper" section for the definition of the above parameters.
``` ```
For an example of dual carriage configuration with `generic_cartesian`
kinematic, see the following configuration
[sample](../config/example-generic-caretesian.cfg).
Please note that in this case the `[dual_carriage]` configuration deviates
from the configuration described above:
```
[dual_carriage my_dc_carriage]
primary_carriage:
# Defines the matching primary carriage of this dual carriage and
# the corresponding IDEX axis. Valid choices are x, y, z.
# This parameter must be provided.
#safe_distance:
# The minimum distance (in mm) to enforce between the dual and the primary
# carriages. If a G-Code command is executed that will bring the carriages
# closer than the specified limit, such a command will be rejected with an
# error. If safe_distance is not provided, it will be inferred from
# position_min and position_max for the dual and primary carriages. If set
# to 0 (or safe_distance is unset and position_min and position_max are
# identical for the primary and dual carriages), the carriages proximity
# checks will be disabled.
endstop_pin:
#position_min:
position_endstop:
position_max:
#homing_speed:
#homing_retract_dist:
#homing_retract_speed:
#second_homing_speed:
#homing_positive_dir:
...
```
Refer to [generic cartesian](#generic-cartesian) section for more information
on the regular `carriage` parameters.
Then a user must define one or more stepper motors moving the dual carriage
(and other carriages as appropriate), for instance
```
[carriage x]
...
[carriage y]
...
[dual_carriage u]
primary_carriage: x
...
[stepper dc_stepper]
carriages: u-y
...
```
`[dual_carriage]` requires special configuration for the input shaper.
In general, it is necessary to run input shaper calibration twice -
for the `dual_carriage` and its `primary_carriage` for the axis they
share. Then the input shaper can be configured as follows, assuming the
example above:
```
[input_shaper]
# Intentionally empty
[delayed_gcode init_shaper]
initial_duration: 0.1
gcode:
SET_DUAL_CARRIAGE CARRIAGE=u
SET_INPUT_SHAPER SHAPER_TYPE_X=<dual_carriage_x_shaper> SHAPER_FREQ_X=<dual_carriage_x_freq> SHAPER_TYPE_Y=<y_shaper> SHAPER_FREQ_Y=<y_freq>
SET_DUAL_CARRIAGE CARRIAGE=x
SET_INPUT_SHAPER SHAPER_TYPE_X=<primary_carriage_x_shaper> SHAPER_FREQ_X=<primary_carriage_x_freq> SHAPER_TYPE_Y=<y_shaper> SHAPER_FREQ_Y=<y_freq>
```
Note that `SHAPER_TYPE_Y` and `SHAPER_FREQ_Y` must be the same in both
commands in this case, since the same motors drive Y axis when either
of the `x` and `u` carriages are active.
It is worth noting that `generic_cartesian` kinematic can support two
dual carriages for X and Y axes. For reference, see for instance a
[sample](../config/sample-corexyuv.cfg) of CoreXYUV configuration.
### [extruder_stepper] ### [extruder_stepper]
Support for additional steppers synchronized to the movement of an Support for additional steppers synchronized to the movement of an
@@ -2558,13 +2227,6 @@ printer kinematics.
# Endstop switch detection pin. If specified, then one may perform # Endstop switch detection pin. If specified, then one may perform
# "homing moves" by adding a STOP_ON_ENDSTOP parameter to # "homing moves" by adding a STOP_ON_ENDSTOP parameter to
# MANUAL_STEPPER movement commands. # MANUAL_STEPPER movement commands.
#position_min:
#position_max:
# The minimum and maximum position the stepper can be commanded to
# move to. If specified then one may not command the stepper to move
# past the given position. Note that these limits do not prevent
# setting an arbitrary position with the `MANUAL_STEPPER
# SET_POSITION=x` command. The default is to not enforce a limit.
``` ```
## Custom heaters and sensors ## Custom heaters and sensors
@@ -2796,10 +2458,6 @@ postfix for both sections.
# "calibration_extruder_temp" option is set. Its recommended to heat # "calibration_extruder_temp" option is set. Its recommended to heat
# the extruder some distance from the bed to minimize its impact on # the extruder some distance from the bed to minimize its impact on
# the probe coil temperature. The default is 50. # the probe coil temperature. The default is 50.
#max_validation_temp: 60.
# The maximum temperature used to validate the calibration. It is
# recommended to set this to a value between 100 and 120 for enclosed
# printers. The default is 60.
``` ```
## Temperature sensors ## Temperature sensors
@@ -3474,6 +3132,11 @@ PCA9632 LED support. The PCA9632 is used on the FlashForge Dreamer.
#i2c_speed: #i2c_speed:
# See the "common I2C settings" section for a description of the # See the "common I2C settings" section for a description of the
# above parameters. # 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 #color_order: RGBW
# Set the pixel order of the LED (using a string containing the # Set the pixel order of the LED (using a string containing the
# letters R, G, B, W). The default is RGBW. # letters R, G, B, W). The default is RGBW.
@@ -3543,10 +3206,6 @@ pin:
# A list of G-Code commands to execute when the button is released. # A list of G-Code commands to execute when the button is released.
# G-Code templates are supported. The default is to not run any # G-Code templates are supported. The default is to not run any
# commands on a button release. # commands on a button release.
#debounce_delay:
# A period of time in seconds to debounce events prior to running the
# button gcode. If the button is pressed and released during this
# delay, the entire button press is ignored. Default is 0.
``` ```
### [output_pin] ### [output_pin]
@@ -3725,9 +3384,8 @@ run_current:
#stealthchop_threshold: 0 #stealthchop_threshold: 0
# The velocity (in mm/s) to set the "stealthChop" threshold to. When # The velocity (in mm/s) to set the "stealthChop" threshold to. When
# set, "stealthChop" mode will be enabled if the stepper motor # set, "stealthChop" mode will be enabled if the stepper motor
# velocity is below this value. Note that the "sensorless homing" # velocity is below this value. The default is 0, which disables
# code may temporarily override this setting during homing # "stealthChop" mode.
# operations. The default is 0, which disables "stealthChop" mode.
#coolstep_threshold: #coolstep_threshold:
# The velocity (in mm/s) to set the TMC driver internal "CoolStep" # The velocity (in mm/s) to set the TMC driver internal "CoolStep"
# threshold to. If set, the coolstep feature will be enabled when # threshold to. If set, the coolstep feature will be enabled when
@@ -3776,7 +3434,6 @@ run_current:
#driver_PWM_FREQ: 1 #driver_PWM_FREQ: 1
#driver_PWM_GRAD: 4 #driver_PWM_GRAD: 4
#driver_PWM_AMPL: 128 #driver_PWM_AMPL: 128
#driver_FREEWHEEL: 0
#driver_SGT: 0 #driver_SGT: 0
#driver_SEMIN: 0 #driver_SEMIN: 0
#driver_SEUP: 0 #driver_SEUP: 0
@@ -3840,9 +3497,8 @@ run_current:
#stealthchop_threshold: 0 #stealthchop_threshold: 0
# The velocity (in mm/s) to set the "stealthChop" threshold to. When # The velocity (in mm/s) to set the "stealthChop" threshold to. When
# set, "stealthChop" mode will be enabled if the stepper motor # set, "stealthChop" mode will be enabled if the stepper motor
# velocity is below this value. Note that the "sensorless homing" # velocity is below this value. The default is 0, which disables
# code may temporarily override this setting during homing # "stealthChop" mode.
# operations. The default is 0, which disables "stealthChop" mode.
#driver_MULTISTEP_FILT: True #driver_MULTISTEP_FILT: True
#driver_IHOLDDELAY: 8 #driver_IHOLDDELAY: 8
#driver_TPOWERDOWN: 20 #driver_TPOWERDOWN: 20
@@ -3857,7 +3513,6 @@ run_current:
#driver_PWM_FREQ: 1 #driver_PWM_FREQ: 1
#driver_PWM_GRAD: 14 #driver_PWM_GRAD: 14
#driver_PWM_OFS: 36 #driver_PWM_OFS: 36
#driver_FREEWHEEL: 0
# Set the given register during the configuration of the TMC2208 # Set the given register during the configuration of the TMC2208
# chip. This may be used to set custom motor parameters. The # chip. This may be used to set custom motor parameters. The
# defaults for each parameter are next to the parameter name in the # defaults for each parameter are next to the parameter name in the
@@ -3907,7 +3562,6 @@ run_current:
#driver_PWM_FREQ: 1 #driver_PWM_FREQ: 1
#driver_PWM_GRAD: 14 #driver_PWM_GRAD: 14
#driver_PWM_OFS: 36 #driver_PWM_OFS: 36
#driver_FREEWHEEL: 0
#driver_SGTHRS: 0 #driver_SGTHRS: 0
#driver_SEMIN: 0 #driver_SEMIN: 0
#driver_SEUP: 0 #driver_SEUP: 0
@@ -4046,9 +3700,8 @@ run_current:
#stealthchop_threshold: 0 #stealthchop_threshold: 0
# The velocity (in mm/s) to set the "stealthChop" threshold to. When # The velocity (in mm/s) to set the "stealthChop" threshold to. When
# set, "stealthChop" mode will be enabled if the stepper motor # set, "stealthChop" mode will be enabled if the stepper motor
# velocity is below this value. Note that the "sensorless homing" # velocity is below this value. The default is 0, which disables
# code may temporarily override this setting during homing # "stealthChop" mode.
# operations. The default is 0, which disables "stealthChop" mode.
#coolstep_threshold: #coolstep_threshold:
# The velocity (in mm/s) to set the TMC driver internal "CoolStep" # The velocity (in mm/s) to set the TMC driver internal "CoolStep"
# threshold to. If set, the coolstep feature will be enabled when # threshold to. If set, the coolstep feature will be enabled when
@@ -4121,7 +3774,6 @@ run_current:
#driver_SEIMIN: 0 #driver_SEIMIN: 0
#driver_SFILT: 0 #driver_SFILT: 0
#driver_SG4_ANGLE_OFFSET: 1 #driver_SG4_ANGLE_OFFSET: 1
#driver_SLOPE_CONTROL: 0
# Set the given register during the configuration of the TMC2240 # Set the given register during the configuration of the TMC2240
# chip. This may be used to set custom motor parameters. The # chip. This may be used to set custom motor parameters. The
# defaults for each parameter are next to the parameter name in the # defaults for each parameter are next to the parameter name in the
@@ -4181,9 +3833,8 @@ run_current:
#stealthchop_threshold: 0 #stealthchop_threshold: 0
# The velocity (in mm/s) to set the "stealthChop" threshold to. When # The velocity (in mm/s) to set the "stealthChop" threshold to. When
# set, "stealthChop" mode will be enabled if the stepper motor # set, "stealthChop" mode will be enabled if the stepper motor
# velocity is below this value. Note that the "sensorless homing" # velocity is below this value. The default is 0, which disables
# code may temporarily override this setting during homing # "stealthChop" mode.
# operations. The default is 0, which disables "stealthChop" mode.
#coolstep_threshold: #coolstep_threshold:
# The velocity (in mm/s) to set the TMC driver internal "CoolStep" # The velocity (in mm/s) to set the TMC driver internal "CoolStep"
# threshold to. If set, the coolstep feature will be enabled when # threshold to. If set, the coolstep feature will be enabled when
@@ -4389,21 +4040,16 @@ prefix).
### [mcp4018] ### [mcp4018]
Statically configured MCP4018 digipot connected via i2c (one may Statically configured MCP4018 digipot connected via two gpio "bit
define any number of sections with an "mcp4018" prefix). banging" pins (one may define any number of sections with an "mcp4018"
prefix).
``` ```
[mcp4018 my_digipot] [mcp4018 my_digipot]
#i2c_address: 47 scl_pin:
# The i2c address that the chip is using on the i2c bus. The default # The SCL "clock" pin. This parameter must be provided.
# is 47. sda_pin:
#i2c_mcu: # The SDA "data" pin. This parameter must be provided.
#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: wiper:
# The value to statically set the given MCP4018 "wiper" to. This is # 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 # typically set to a number between 0.0 and 1.0 with 1.0 being the
@@ -4431,16 +4077,15 @@ Support for a display attached to the micro-controller.
[display] [display]
lcd_type: lcd_type:
# The type of LCD chip in use. This may be "hd44780", "hd44780_spi", # The type of LCD chip in use. This may be "hd44780", "hd44780_spi",
# "aip31068_spi", "st7920", "emulated_st7920", "uc1701", "ssd1306", or # "st7920", "emulated_st7920", "uc1701", "ssd1306", or "sh1106".
# "sh1106".
# See the display sections below for information on each type and # See the display sections below for information on each type and
# additional parameters they provide. This parameter must be # additional parameters they provide. This parameter must be
# provided. # provided.
#display_group: #display_group:
# The name of the display_data group to show on the display. This # The name of the display_data group to show on the display. This
# controls the content of the screen (see the "display_data" section # controls the content of the screen (see the "display_data" section
# for more information). The default is _default_20x4 for hd44780 or # for more information). The default is _default_20x4 for hd44780
# aip31068_spi displays and _default_16x4 for other displays. # displays and _default_16x4 for other displays.
#menu_timeout: #menu_timeout:
# Timeout for menu. Being inactive this amount of seconds will # Timeout for menu. Being inactive this amount of seconds will
# trigger menu exit or return to root menu when having autorun # trigger menu exit or return to root menu when having autorun
@@ -4566,31 +4211,6 @@ spi_software_miso_pin:
... ...
``` ```
#### aip31068_spi display
Information on configuring an aip31068_spi display - a very similar to hd44780_spi
a 20x04 (20 symbols by 4 lines) display with slightly different internal
protocol.
```
[display]
lcd_type: aip31068_spi
latch_pin:
spi_software_sclk_pin:
spi_software_mosi_pin:
spi_software_miso_pin:
# The pins connected to the shift register controlling the display.
# The spi_software_miso_pin needs to be set to an unused pin of the
# printer mainboard as the shift register does not have a MISO pin,
# but the software spi implementation requires this pin to be
# configured.
#line_length:
# Set the number of characters per line for an hd44780 type lcd.
# Possible values are 20 (default) and 16. The number of lines is
# fixed to 4.
...
```
#### st7920 display #### st7920 display
Information on configuring st7920 displays (which is used in Information on configuring st7920 displays (which is used in
@@ -4918,11 +4538,6 @@ more information.
# dispatch and execution of the runout_gcode. It may be useful to # dispatch and execution of the runout_gcode. It may be useful to
# increase this delay if OctoPrint exhibits strange pause behavior. # increase this delay if OctoPrint exhibits strange pause behavior.
# Default is 0.5 seconds. # Default is 0.5 seconds.
#debounce_delay:
# A period of time in seconds to debounce events prior to running the
# switch gcode. The switch must he held in a single state for at least
# this long to activate. If the switch is toggled on/off during this delay,
# the event is ignored. Default is 0.
#switch_pin: #switch_pin:
# The pin on which the switch is connected. This parameter must be # The pin on which the switch is connected. This parameter must be
# provided. # provided.
@@ -5040,19 +4655,9 @@ scale.
[load_cell] [load_cell]
sensor_type: sensor_type:
# This must be one of the supported sensor types, see below. # This must be one of the supported sensor types, see below.
#counts_per_gram:
# The floating point number of sensor counts that indicates 1 gram of force.
# This value is calculated by the LOAD_CELL_CALIBRATE command.
#reference_tare_counts:
# The integer tare value, in raw sensor counts, taken when LOAD_CELL_CALIBRATE
# is run. This is the default tare value when klipper starts up.
#sensor_orientation:
# Change the sensor's orientation. Can be either 'normal' or 'inverted'.
# The default is 'normal'. Use 'inverted' if the sensor reports a
# decreasing force value when placed under load.
``` ```
#### HX711 #### XH711
This is a 24 bit low sample rate chip using "bit-bang" communications. It is This is a 24 bit low sample rate chip using "bit-bang" communications. It is
suitable for filament scales. suitable for filament scales.
``` ```
@@ -5120,89 +4725,13 @@ data_ready_pin:
#gain: 128 #gain: 128
# Valid gain values are 128, 64, 32, 16, 8, 4, 2, 1 # Valid gain values are 128, 64, 32, 16, 8, 4, 2, 1
# The default is 128 # The default is 128
#pga_bypass: False
# Disable the internal Programmable Gain Amplifier. If
# True the PGA will be disabled for gains 1, 2, and 4. The PGA is always
# enabled for gain settings 8 to 128, regardless of the pga_bypass setting.
# If AVSS is used as an input pga_bypass is forced to True.
# The default is False.
#sample_rate: 660 #sample_rate: 660
# This chip supports two ranges of sample rates, Normal and Turbo. In turbo # This chip supports two ranges of sample rates, Normal and Turbo. In turbo
# mode the chip's internal clock runs twice as fast and the SPI communication # mode the chips c internal clock runs twice as fast and the SPI communication
# speed is also doubled. # speed is also doubled.
# Normal sample rates: 20, 45, 90, 175, 330, 600, 1000 # Normal sample rates: 20, 45, 90, 175, 330, 600, 1000
# Turbo sample rates: 40, 90, 180, 350, 660, 1200, 2000 # Turbo sample rates: 40, 90, 180, 350, 660, 1200, 2000
# The default is 660 # The default is 660
#input_mux:
# Input multiplexer configuration, select a pair of pins to use. The first pin
# is the positive, AINP, and the second pin is the negative, AINN. Valid
# values are: 'AIN0_AIN1', 'AIN0_AIN2', 'AIN0_AIN3', 'AIN1_AIN2', 'AIN1_AIN3',
# 'AIN2_AIN3', 'AIN1_AIN0', 'AIN3_AIN2', 'AIN0_AVSS', 'AIN1_AVSS', 'AIN2_AVSS'
# and 'AIN3_AVSS'. If AVSS is used the PGA is bypassed and the pga_bypass
# setting will be forced to True.
# The default is AIN0_AIN1.
#vref:
# The selected voltage reference. Valid values are: 'internal', 'REF0', 'REF1'
# and 'analog_supply'. Default is 'internal'.
```
### [load_cell_probe]
Load Cell Probe. This combines the functionality of a [probe] and a [load_cell].
```
[load_cell_probe]
sensor_type:
# This must be one of the supported bulk ADC sensor types and support
# load cell endstops on the mcu.
#counts_per_gram:
#reference_tare_counts:
#sensor_orientation:
# These parameters must be configured before the probe will operate.
# See the [load_cell] section for further details.
#force_safety_limit: 2000
# The safe limit for probing force relative to the reference_tare_counts on
# the load_cell. The default is +/-2Kg.
#trigger_force: 75.0
# The force that the probe will trigger at. 75g is the default.
#drift_filter_cutoff_frequency: 0.8
# Enable optional continuous taring while homing & probing to reject drift.
# The value is a frequency, in Hz, below which drift will be ignored. This
# option requires the SciPy library. Default: None
#drift_filter_delay: 2
# The delay, or 'order', of the drift filter. This controls the number of
# samples required to make a trigger detection. Can be 1 or 2, the default
# is 2.
#buzz_filter_cutoff_frequency: 100.0
# The value is a frequency, in Hz, above which high frequency noise in the
# load cell will be igfiltered outnored. This option requires the SciPy
# library. Default: None
#buzz_filter_delay: 2
# The delay, or 'order', of the buzz filter. This controls the number of
# samples required to make a trigger detection. Can be 1 or 2, the default
# is 2.
#notch_filter_frequencies: 50, 60
# 1 or 2 frequencies, in Hz, to filter out of the load cell data. This is
# intended to reject power line noise. This option requires the SciPy
# library. Default: None
#notch_filter_quality: 2.0
# Controls how narrow the range of frequencies are that the notch filter
# removes. Larger numbers produce a narrower filter. Minimum value is 0.5 and
# maximum is 3.0. Default: 2.0
#tare_time:
# The rime in seconds used for taring the load_cell before each probe. The
# default value is: 4 / 60 = 0.066. This collects samples from 4 cycles of
# 60Hz mains power to cancel power line noise.
#z_offset:
#speed:
#samples:
#sample_retract_dist:
#lift_speed:
#samples_result:
#samples_tolerance:
#samples_tolerance_retries:
#activate_gcode:
#deactivate_gcode:
# See the "[probe]" section for a description of the above parameters.
``` ```
## Board specific hardware support ## Board specific hardware support
@@ -5291,50 +4820,6 @@ vssa_pin:
# noise. The default is 2 seconds. # noise. The default is 2 seconds.
``` ```
### [ads1x1x]
ADS1013, ADS1014, ADS1015, ADS1113, ADS1114 and ADS1115 are I2C based Analog to
Digital Converters that can be used for temperature sensors. They provide 4
analog input pins either as single line or as differential input.
Note: Use caution if using this sensor to control heaters. The heater min_temp
and max_temp are only verified in the host and only if the host is running and
operating normally. (ADC inputs directly connected to the micro-controller
verify min_temp and max_temp within the micro-controller and do not require a
working connection to the host.)
```
[ads1x1x my_ads1x1x]
chip: ADS1115
#pga: 4.096V
# Default value is 4.096V. The maximum voltage range used for the input. This
# 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 supply voltage for the device. This allows additional software scaling
# for all values read from the ADC.
i2c_mcu: host
i2c_bus: i2c.1
#address_pin: GND
# Default value is GND. There can be up to four addressed devices depending
# upon wiring of the device. Check the datasheet for details. The i2c_address
# can be specified directly instead of using the address_pin.
```
The chip provides pins that can be used on other sensors.
```
sensor_type: ...
# Can be any thermistor or adc_temperature.
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
# corresponding lines. For example
# DIFF03 measures the differential between line 0 and 3. Only specific
# combinations for the differentials are allowed.
```
### [replicape] ### [replicape]
Replicape support - see the [beaglebone guide](Beaglebone.md) and the Replicape support - see the [beaglebone guide](Beaglebone.md) and the
@@ -5416,7 +4901,7 @@ Octoprint as they will conflict, and 1 will fail to initialize
properly likely aborting your print. properly likely aborting your print.
If you use Octoprint and stream gcode over the serial port instead of If you use Octoprint and stream gcode over the serial port instead of
printing from virtual_sd, then remove **M1** and **M0** from *Pausing commands* printing from virtual_sd, then remo **M1** and **M0** from *Pausing commands*
in *Settings > Serial Connection > Firmware & protocol* will prevent in *Settings > Serial Connection > Firmware & protocol* will prevent
the need to start print on the Palette 2 and unpausing in Octoprint the need to start print on the Palette 2 and unpausing in Octoprint
for your print to begin. for your print to begin.
@@ -5440,9 +4925,8 @@ serial:
### [angle] ### [angle]
Magnetic hall angle sensor support for reading stepper motor angle Magnetic hall angle sensor support for reading stepper motor angle
shaft measurements using a1333, as5047d, mt6816, mt6826s, shaft measurements using a1333, as5047d, or tle5012b SPI chips. The
or tle5012b SPI chips. measurements are available via the [API Server](API_Server.md) and
The measurements are available via the [API Server](API_Server.md) and
[motion analysis tool](Debugging.md#motion-analysis-and-data-logging). [motion analysis tool](Debugging.md#motion-analysis-and-data-logging).
See the [G-Code reference](G-Codes.md#angle) for available commands. See the [G-Code reference](G-Codes.md#angle) for available commands.
@@ -5450,7 +4934,7 @@ See the [G-Code reference](G-Codes.md#angle) for available commands.
[angle my_angle_sensor] [angle my_angle_sensor]
sensor_type: sensor_type:
# The type of the magnetic hall sensor chip. Available choices are # The type of the magnetic hall sensor chip. Available choices are
# "a1333", "as5047d", "mt6816", "mt6826s", and "tle5012b". This parameter must be # "a1333", "as5047d", and "tle5012b". This parameter must be
# specified. # specified.
#sample_period: 0.000400 #sample_period: 0.000400
# The query period (in seconds) to use during measurements. The # The query period (in seconds) to use during measurements. The
@@ -5513,9 +4997,8 @@ Most Klipper micro-controller implementations only support an
micro-controller supports a 400000 speed (*fast mode*, 400kbit/s), but it must be micro-controller supports a 400000 speed (*fast mode*, 400kbit/s), but it must be
[set in the operating system](RPi_microcontroller.md#optional-enabling-i2c) [set in the operating system](RPi_microcontroller.md#optional-enabling-i2c)
and the `i2c_speed` parameter is otherwise ignored. The Klipper and the `i2c_speed` parameter is otherwise ignored. The Klipper
"RP2040" micro-controller and ATmega AVR family and some STM32 "RP2040" micro-controller and ATmega AVR family support a rate of 400000
(F0, G0, G4, L4, F7, H7) support a rate of 400000 via the `i2c_speed` parameter. via the `i2c_speed` parameter. All other Klipper micro-controllers use a
All other Klipper micro-controllers use a
100000 rate and ignore the `i2c_speed` parameter. 100000 rate and ignore the `i2c_speed` parameter.
``` ```

View File

@@ -132,10 +132,3 @@ There are several
you have questions on the code then you can also ask in the you have questions on the code then you can also ask in the
[Klipper Discourse Forum](#discourse-forum) or on the [Klipper Discourse Forum](#discourse-forum) or on the
[Klipper Discord Chat](#discord-chat). [Klipper Discord Chat](#discord-chat).
## Professional Services
![](img/klipper-logo-small.png)
Custom software development, software support, and solutions:
[https://ko-fi.com/koconnor](https://ko-fi.com/koconnor)

View File

@@ -78,9 +78,7 @@ for further details on how to configure a `temperature_probe`. It is
advised to configure the `calibration_position`, advised to configure the `calibration_position`,
`calibration_extruder_temp`, `extruder_heating_z`, and `calibration_extruder_temp`, `extruder_heating_z`, and
`calibration_bed_temp` options, as doing so will automate some of the `calibration_bed_temp` options, as doing so will automate some of the
steps outlined below. If the printer to be calibrated is enclosed, it steps outlined below.
is strongly recommended to set the `max_validation_temp` option to a value
between 100 and 120.
Eddy probe manufacturers may offer a stock drift calibration that can be Eddy probe manufacturers may offer a stock drift calibration that can be
manually added to `drift_calibration` option of the `[probe_eddy_current]` manually added to `drift_calibration` option of the `[probe_eddy_current]`

View File

@@ -102,13 +102,11 @@ Klipper supports many standard 3d printer features:
printers. printers.
* Automatic bed leveling support. Klipper can be configured for basic * Automatic bed leveling support. Klipper can be configured for basic
bed tilt detection or full mesh bed leveling. The bed mesh can be bed tilt detection or full mesh bed leveling. If the bed uses
customized to the print size (adaptive bed mesh). If the bed uses
multiple Z steppers then Klipper can also level by independently multiple Z steppers then Klipper can also level by independently
manipulating the Z steppers. Most Z height probes are supported, manipulating the Z steppers. Most Z height probes are supported,
including BL-Touch probes and servo activated probes. Probes may be including BL-Touch probes and servo activated probes. Probes may be
calibrated for axis twist compensation. If using an "eddy current calibrated for axis twist compensation.
probe" then one can utilize fast bed mesh scanning,
* Automatic delta calibration support. The calibration tool can * Automatic delta calibration support. The calibration tool can
perform basic height calibration as well as an enhanced X and Y perform basic height calibration as well as an enhanced X and Y
@@ -120,7 +118,7 @@ Klipper supports many standard 3d printer features:
* Support for common temperature sensors (eg, common thermistors, * Support for common temperature sensors (eg, common thermistors,
AD595, AD597, AD849x, PT100, PT1000, MAX6675, MAX31855, MAX31856, AD595, AD597, AD849x, PT100, PT1000, MAX6675, MAX31855, MAX31856,
MAX31865, BME280, HTU21D, DS18B20, AHT10, SHT3x, and LM75). Custom MAX31865, BME280, HTU21D, DS18B20, AHT10, and LM75). Custom
thermistors and custom analog temperature sensors can also be thermistors and custom analog temperature sensors can also be
configured. One can monitor the internal micro-controller configured. One can monitor the internal micro-controller
temperature sensor and the internal temperature sensor of a temperature sensor and the internal temperature sensor of a
@@ -130,8 +128,7 @@ Klipper supports many standard 3d printer features:
* Support for standard fans, nozzle fans, and temperature controlled * Support for standard fans, nozzle fans, and temperature controlled
fans. No need to keep fans running when the printer is idle. Fan fans. No need to keep fans running when the printer is idle. Fan
speed can be monitored on fans that have a tachometer. One can speed can be monitored on fans that have a tachometer.
assign a "math formula" to a fan for automatic fan speed updating.
* Support for run-time configuration of TMC2130, TMC2208/TMC2224, * Support for run-time configuration of TMC2130, TMC2208/TMC2224,
TMC2209, TMC2240, TMC2660, and TMC5160 stepper motor drivers. There TMC2209, TMC2240, TMC2660, and TMC5160 stepper motor drivers. There
@@ -157,7 +154,7 @@ Klipper supports many standard 3d printer features:
filament width sensors. filament width sensors.
* Support for measuring and recording acceleration using adxl345, * Support for measuring and recording acceleration using adxl345,
mpu9250, mpu6050, lis2dw12, lis3dh, and icm20948 accelerometers. mpu9250, mpu6050, and lis2dw12 accelerometers.
* Support for limiting the top speed of short "zigzag" moves to reduce * Support for limiting the top speed of short "zigzag" moves to reduce
printer vibration and noise. See the [kinematics](Kinematics.md) printer vibration and noise. See the [kinematics](Kinematics.md)
@@ -187,16 +184,13 @@ represent total number of steps per second on the micro-controller.
| SAM4S8C | 1690K | 1385K | | SAM4S8C | 1690K | 1385K |
| LPC1768 | 1923K | 1351K | | LPC1768 | 1923K | 1351K |
| LPC1769 | 2353K | 1622K | | LPC1769 | 2353K | 1622K |
| RP2040 | 2400K | 1636K |
| SAM4E8E | 2500K | 1674K | | SAM4E8E | 2500K | 1674K |
| SAMD51 | 3077K | 1885K | | SAMD51 | 3077K | 1885K |
| AR100 | 3529K | 2507K | | AR100 | 3529K | 2507K |
| STM32G431 | 3617K | 2452K |
| STM32F407 | 3652K | 2459K | | STM32F407 | 3652K | 2459K |
| STM32F446 | 3913K | 2634K | | STM32F446 | 3913K | 2634K |
| RP2040 | 4000K | 2571K | | STM32H743 | 9091K | 6061K |
| RP2350 | 4167K | 2663K |
| SAME70 | 6667K | 4737K |
| STM32H723 | 7429K | 8619K |
If unsure of the micro-controller on a particular board, find the If unsure of the micro-controller on a particular board, find the
appropriate [config file](../config/), and look for the appropriate [config file](../config/), and look for the

View File

@@ -127,14 +127,6 @@ use this tool the Python "numpy" package must be installed (see the
[measuring resonance document](Measuring_Resonances.md#software-installation) [measuring resonance document](Measuring_Resonances.md#software-installation)
for more information). for more information).
#### ANGLE_CHIP_CALIBRATE
`ANGLE_CHIP_CALIBRATE CHIP=<chip_name>`: Perform internal sensor calibration,
if implemented (MT6826S/MT6835).
- **MT68XX**: The motor should be disconnected
from any printer carriage before performing calibration.
After calibration, the sensor should be reset by disconnecting the power.
#### ANGLE_DEBUG_READ #### ANGLE_DEBUG_READ
`ANGLE_DEBUG_READ CHIP=<config_name> REG=<register>`: Queries sensor `ANGLE_DEBUG_READ CHIP=<config_name> REG=<register>`: Queries sensor
register "register" (e.g. 44 or 0x2C). Can be useful for debugging register "register" (e.g. 44 or 0x2C). Can be useful for debugging
@@ -154,13 +146,9 @@ The following commands are available when the
section](Config_Reference.md#axis_twist_compensation) is enabled. section](Config_Reference.md#axis_twist_compensation) is enabled.
#### AXIS_TWIST_COMPENSATION_CALIBRATE #### AXIS_TWIST_COMPENSATION_CALIBRATE
`AXIS_TWIST_COMPENSATION_CALIBRATE [AXIS=<X|Y>] [SAMPLE_COUNT=<value>]` `AXIS_TWIST_COMPENSATION_CALIBRATE [SAMPLE_COUNT=<value>]`: Initiates the X
twist calibration wizard. `SAMPLE_COUNT` specifies the number of points along
Calibrates axis twist compensation by specifying the target axis or the X axis to calibrate at and defaults to 3.
enabling automatic calibration.
- **AXIS:** Define the axis (`X` or `Y`) for which the twist compensation
will be calibrated. If not specified, the axis defaults to `'X'`.
### [bed_mesh] ### [bed_mesh]
@@ -174,10 +162,8 @@ The following commands are available when the
[ADAPTIVE_MARGIN=<value>]`: This command probes the bed using generated points [ADAPTIVE_MARGIN=<value>]`: This command probes the bed using generated points
specified by the parameters in the config. After probing, a mesh is generated specified by the parameters in the config. After probing, a mesh is generated
and z-movement is adjusted according to the mesh. and z-movement is adjusted according to the mesh.
The mesh is immediately active after successful completion of `BED_MESH_CALIBRATE`.
The mesh will be saved into a profile specified by the `PROFILE` parameter, The mesh will be saved into a profile specified by the `PROFILE` parameter,
or `default` if unspecified. If ADAPTIVE=1 is specified then the profile or `default` if unspecified.
name will begin with `adaptive-` and should not be saved for reuse.
See the PROBE command for details on the optional probe parameters. If See the PROBE command for details on the optional probe parameters. If
METHOD=manual is specified then the manual probing tool is activated - see the METHOD=manual is specified then the manual probing tool is activated - see the
MANUAL_PROBE command above for details on the additional commands available MANUAL_PROBE command above for details on the additional commands available
@@ -343,18 +329,15 @@ The following command is available when the
enabled. enabled.
#### SET_DUAL_CARRIAGE #### SET_DUAL_CARRIAGE
`SET_DUAL_CARRIAGE CARRIAGE=<carriage> [MODE=[PRIMARY|COPY|MIRROR]]`: `SET_DUAL_CARRIAGE CARRIAGE=[0|1] [MODE=[PRIMARY|COPY|MIRROR]]`:
This command will change the mode of the specified carriage. This command will change the mode of the specified carriage.
If no `MODE` is provided it defaults to `PRIMARY`. `<carriage>` must If no `MODE` is provided it defaults to `PRIMARY`. Setting the mode
reference a defined primary or dual carriage for `generic_cartesian` to `PRIMARY` deactivates the other carriage and makes the specified
kinematics or be 0 (for primary carriage) or 1 (for dual carriage) carriage execute subsequent G-Code commands as-is. `COPY` and `MIRROR`
for all other kinematics supporting IDEX. Setting the mode to `PRIMARY` modes are supported only for `CARRIAGE=1`. When set to either of these
deactivates the other carriage and makes the specified carriage execute modes, carriage 1 will then track the subsequent moves of the carriage 0
subsequent G-Code commands as-is. `COPY` and `MIRROR` modes are supported and either copy relative movements of it (in `COPY` mode) or execute them
only for dual carriages. When set to either of these modes, dual carriage in the opposite (mirror) direction (in `MIRROR` mode).
will then track the subsequent moves of its primary carriage and either
copy relative movements of it (in `COPY` mode) or execute them in the
opposite (mirror) direction (in `MIRROR` mode).
#### SAVE_DUAL_CARRIAGE_STATE #### SAVE_DUAL_CARRIAGE_STATE
`SAVE_DUAL_CARRIAGE_STATE [NAME=<state_name>]`: Save the current positions `SAVE_DUAL_CARRIAGE_STATE [NAME=<state_name>]`: Save the current positions
@@ -372,7 +355,7 @@ restored and "MOVE_SPEED" is specified, then the toolhead moves will be
performed with the given speed (in mm/s); otherwise the toolhead move will 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 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 only over their own axis, which may be necessary to correctly restore COPY
and MIRROR mode of the dual carriage. and MIRROR mode of the dual carraige.
### [endstop_phase] ### [endstop_phase]
@@ -493,20 +476,6 @@ enabled.
`SET_FAN_SPEED FAN=config_name SPEED=<speed>` This command sets the `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. speed of a fan. "speed" must be between 0.0 and 1.0.
`SET_FAN_SPEED PIN=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
my_fan_template]` config section then one could assign
`TEMPLATE=my_fan_template` here. The display_template should produce a
string containing a floating point number with the desired value. The
template will be continuously evaluated and the fan will be
automatically set to the resulting speed. One may set display_template
parameters to use during template evaluation (parameters will be
parsed as Python literals). If TEMPLATE is an empty string then this
command will clear any previous template assigned to the pin (one can
then use `SET_FAN_SPEED` commands to manage the values directly).
### [filament_switch_sensor] ### [filament_switch_sensor]
The following command is available when a The following command is available when a
@@ -584,51 +553,15 @@ state; issue a G28 afterwards to reset the kinematics. This command is
intended for low-level diagnostics and debugging. intended for low-level diagnostics and debugging.
#### SET_KINEMATIC_POSITION #### SET_KINEMATIC_POSITION
`SET_KINEMATIC_POSITION [X=<value>] [Y=<value>] [Z=<value>]`: Force
`SET_KINEMATIC_POSITION [X=<value>] [Y=<value>] [Z=<value>] the low-level kinematic code to believe the toolhead is at the given
[SET_HOMED=<[X][Y][Z]>] [CLEAR_HOMED=<[X][Y][Z]>]`: Force the cartesian position. This is a diagnostic and debugging command; use
low-level kinematic code to believe the toolhead is at the given SET_GCODE_OFFSET and/or G92 for regular axis transformations. If an
cartesian position and set/clear homed status. This is a diagnostic axis is not specified then it will default to the position that the
and debugging command; use SET_GCODE_OFFSET and/or G92 for regular head was last commanded to. Setting an incorrect or invalid position
axis transformations. Setting an incorrect or invalid position may may lead to internal software errors. This command may invalidate
lead to internal software errors. future boundary checks; issue a G28 afterwards to reset the
kinematics.
The `X`, `Y`, and `Z` parameters are used to alter the low-level
kinematic position tracking. If any of these parameters are not set
then the position is not changed - for example `SET_KINEMATIC_POSITION
Z=10` would set all axes as homed, set the internal Z position to 10,
and leave the X and Y positions unchanged. Changing the internal
position tracking is not dependent on the internal homing state - one
may alter the position for both homed and not homed axes, and
similarly one may set or clear the homing state of an axis without
altering its internal position.
The `SET_HOMED` parameter defaults to `XYZ` which instructs the
kinematics to consider all axes as homed. A bare
`SET_KINEMATIC_POSITION` command will result in all axes being
considered homed (and not change its current position). If it is not
desired to change the state of homed axes then assign `SET_HOMED` to
an empty string - for example:
`SET_KINEMATIC_POSITION SET_HOMED= X=10`. It is also possible to
request an individual axis be considered homed (eg, `SET_HOMED=X`),
but note that non-cartesian style kinematics (such as delta
kinematics) may not support setting an individual axis as homed.
The `CLEAR_HOMED` parameter instructs the kinematics to consider the
given axes as not homed. For example, `CLEAR_HOMED=XYZ` would request
all axes to be considered not homed (and thus require homing prior to
movement on those axes). The default is `SET_HOMED=XYZ` even if
`CLEAR_HOMED` is present, so the command `SET_KINEMATIC_POSITION
CLEAR_HOMED=Z` will set X and Y as homed and clear the homing state
for Z. Use `SET_KINEMATIC_POSITION SET_HOMED= CLEAR_HOMED=Z` if the
goal is to clear only the Z homing state. If an axis is specified in
neither `SET_HOMED` nor `CLEAR_HOMED` then its homing state is not
changed and if it is specified in both then `CLEAR_HOMED` has
precedence. It is possible to request clearing of an individual axis,
but on non-cartesian style kinematics (such as delta kinematics) doing
so may result in clearing the homing state of additional axes. Note
the `CLEAR` parameter is currently an alias for the `CLEAR_HOMED`
parameter, but this alias will be removed in the future.
### [gcode] ### [gcode]
@@ -720,46 +653,6 @@ is specified then the toolhead move will be performed with the given
speed (in mm/s); otherwise the toolhead move will use the restored speed (in mm/s); otherwise the toolhead move will use the restored
g-code speed. g-code speed.
### [generic_cartesian]
The commands in this section become automatically available when
`kinematics: generic_cartesian` is specified as the printer kinematics.
#### SET_STEPPER_CARRIAGES
`SET_STEPPER_CARRIAGES STEPPER=<stepper_name> CARRIAGES=<carriages>
[DISABLE_CHECKS=[0|1]]`: Set or update the stepper carriages.
`<stepper_name>` must reference an existing stepper defined in `printer.cfg`,
and `<carriages>` describes the carriages the stepper moves. See
[Generic Cartesian Kinematics](Config_Reference.md#generic-cartesian-kinematics)
for a more detailed overview of the `carriages` parameter in the
stepper configuration section. Note that it is only possible
to change the coefficients or signs of the carriages with this
command, but a user cannot add or remove the carriages that the stepper
controls.
`SET_STEPPER_CARRIAGES` is an advanced tool, and the user is advised
to exercise an extreme caution using it, since specifying incorrect
configuration may physically damage the printer.
Note that `SET_STEPPER_CARRIAGES` performs certain internal validations
of the new printer kinematics after the change. Keep in mind that if it
detects an issue, it may leave printer kinematics in an invalid state.
This means that if `SET_STEPPER_CARRIAGES` reports an error, it is unsafe
to issue other GCode commands, and the user must inspect the error message
and either fix the problem, or manually restore the previous stepper(s)
configuration.
Since `SET_STEPPER_CARRIAGES` can update a configuration of a single
stepper at a time, some sequences of changes can lead to invalid
intermediate kinematic configurations, even if the final configuration
is valid. In such cases a user can pass `DISABLE_CHECKS=1` parameters to
all but the last command to disable intermediate checks. For example,
if `stepper a` and `stepper b` initially have `x-y` and `x+y` carriages
correspondingly, then the following sequence of commands will let a user
effectively swap the carriage controls:
`SET_STEPPER_CARRIAGES STEPPER=a CARRIAGES=x+y DISABLE_CHECKS=1`
and `SET_STEPPER_CARRIAGES STEPPER=b CARRIAGES=x-y`, while
still validating the final kinematics state.
### [hall_filament_width_sensor] ### [hall_filament_width_sensor]
The following commands are available when the The following commands are available when the
@@ -838,116 +731,6 @@ together with either of SHAPER_TYPE_X and SHAPER_TYPE_Y parameters.
See [config reference](Config_Reference.md#input_shaper) for more See [config reference](Config_Reference.md#input_shaper) for more
details on each of these parameters. details on each of these parameters.
### [led]
The following command is available when any of the
[led config sections](Config_Reference.md#leds) are enabled.
#### SET_LED
`SET_LED LED=<config_name> RED=<value> GREEN=<value> BLUE=<value>
WHITE=<value> [INDEX=<index>] [TRANSMIT=0] [SYNC=1]`: This sets the
LED output. Each color `<value>` must be between 0.0 and 1.0. The
WHITE option is only valid on RGBW LEDs. If the LED supports multiple
chips in a daisy-chain then one may specify INDEX to alter the color
of just the given chip (1 for the first chip, 2 for the second,
etc.). If INDEX is not provided then all LEDs in the daisy-chain will
be set to the provided color. If TRANSMIT=0 is specified then the
color change will only be made on the next SET_LED command that does
not specify TRANSMIT=0; this may be useful in combination with the
INDEX parameter to batch multiple updates in a daisy-chain. By
default, the SET_LED command will sync it's changes with other ongoing
gcode commands. This can lead to undesirable behavior if LEDs are
being set while the printer is not printing as it will reset the idle
timeout. If careful timing is not needed, the optional SYNC=0
parameter can be specified to apply the changes without resetting the
idle timeout.
#### SET_LED_TEMPLATE
`SET_LED_TEMPLATE LED=<led_name> TEMPLATE=<template_name>
[<param_x>=<literal>] [INDEX=<index>]`: Assign a
[display_template](Config_Reference.md#display_template) to a given
[LED](Config_Reference.md#leds). For example, if one defined a
`[display_template my_led_template]` config section then one could
assign `TEMPLATE=my_led_template` here. The display_template should
produce a comma separated string containing four floating point
numbers corresponding to red, green, blue, and white color settings.
The template will be continuously evaluated and the LED will be
automatically set to the resulting colors. One may set
display_template parameters to use during template evaluation
(parameters will be parsed as Python literals). If INDEX is not
specified then all chips in the LED's daisy-chain will be set to the
template, otherwise only the chip with the given index will be
updated. If TEMPLATE is an empty string then this command will clear
any previous template assigned to the LED (one can then use `SET_LED`
commands to manage the LED's color settings).
### [load_cell]
The following commands are enabled if a
[load_cell config section](Config_Reference.md#load_cell) has been enabled.
### LOAD_CELL_DIAGNOSTIC
`LOAD_CELL_DIAGNOSTIC [LOAD_CELL=<config_name>]`: This command collects 10
seconds of load cell data and reports statistics that can help you verify proper
operation of the load cell. This command can be run on both calibrated and
uncalibrated load cells.
### LOAD_CELL_CALIBRATE
`LOAD_CELL_CALIBRATE [LOAD_CELL=<config_name>]`: Start the guided calibration
utility. Calibration is a 3 step process:
1. First you remove all load from the load cell and run the `TARE` command
2. Next you apply a known load to the load cell and run the
`CALIBRATE GRAMS=nnn` command
3. Finally use the `ACCEPT` command to save the results
You can cancel the calibration process at any time with `ABORT`.
### LOAD_CELL_TARE
`LOAD_CELL_TARE [LOAD_CELL=<config_name>]`: This works just like the tare button
on digital scale. It sets the current raw reading of the load cell to be the
zero point reference value. The response is the percentage of the sensors range
that was read and the raw value in counts. If the load cell is calibrated a
force in grams is also reported.
### LOAD_CELL_READ load_cell="name"
`LOAD_CELL_READ [LOAD_CELL=<config_name>]`:
This command takes a reading from the load cell. The response is the percentage
of the sensors range that was read and the raw value in counts. If the load cell
is calibrated a force in grams is also reported.
### [load_cell_probe]
The following commands are enabled if a
[load_cell config section](Config_Reference.md#load_cell_probe) has been
enabled.
### LOAD_CELL_TEST_TAP
`LOAD_CELL_TEST_TAP [TAPS=<taps>] [TIMEOUT=<timeout>]`: Run a testing routine
that reports taps on the load cell. The toolhead will not move but the load cell
probe will sense taps just as if it was probing. This can be used as a
sanity check to make sure that the probe works. This tool replaces
QUERY_ENDSTOPS and QUERY_PROBE for load cell probes.
- `TAPS`: the number of taps the tool expects
- `TIMEOOUT`: the time, in seconds, that the tool waits for each tab before
aborting.
### Load Cell Command Extensions
Commands that perform probes, such as [`PROBE`](#probe),
[`PROBE_ACCURACY`](#probe_accuracy),
[`BED_MESH_CALIBRATE`](#bed_mesh_calibrate) etc. will accept additional
parameters if a `[load_cell_probe]` is defined. The parameters override the
corresponding settings from the
[`[load_cell_probe]`](./Config_Reference.md#load_cell_probe) configuration:
- `FORCE_SAFETY_LIMIT=<grams>`
- `TRIGGER_FORCE=<grams>`
- `DRIFT_FILTER_CUTOFF_FREQUENCY=<frequency_hz>`
- `DRIFT_FILTER_DELAY=<1|2>`
- `BUZZ_FILTER_CUTOFF_FREQUENCY=<frequency_hz>`
- `BUZZ_FILTER_DELAY=<1|2>`
- `NOTCH_FILTER_FREQUENCIES=<list of frequency_hz>`
- `NOTCH_FILTER_QUALITY=<quality>`
- `TARE_TIME=<seconds>`
### [manual_probe] ### [manual_probe]
The manual_probe module is automatically loaded. The manual_probe module is automatically loaded.
@@ -1004,25 +787,6 @@ 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 stepper move uses SYNC=0 then future G-Code movement commands may run
in parallel with the stepper movement. in parallel with the stepper movement.
`MANUAL_STEPPER STEPPER=config_name GCODE_AXIS=[A-Z]
[LIMIT_VELOCITY=<velocity>] [LIMIT_ACCEL=<accel>]
[INSTANTANEOUS_CORNER_VELOCITY=<velocity>]`: If the `GCODE_AXIS`
parameter is specified then it configures the stepper motor as an
extra axis on `G1` move commands. For example, if one were to issue a
`MANUAL_STEPPER ... GCODE_AXIS=R` command then one could issue
commands like `G1 X10 Y20 R30` to move the stepper motor. The
resulting moves will occur synchronously with the associated toolhead
xyz movements. If the motor is associated with a `GCODE_AXIS` then
one may no longer issue movements using the above `MANUAL_STEPPER`
command - one may unregister the stepper with a `MANUAL_STEPPER
... GCODE_AXIS=` command to resume manual control of the motor. The
`LIMIT_VELOCITY` and `LIMIT_ACCEL` parameters allow one to reduce the
speed of `G1` moves if those moves would result in a velocity or
acceleration above the specified limits. The
`INSTANTANEOUS_CORNER_VELOCITY` specifies the maximum instantaneous
velocity change (in mm/s) of the motor during the junction of two
moves (the default is 1mm/s).
### [mcp4018] ### [mcp4018]
The following command is available when a The following command is available when a
@@ -1037,6 +801,49 @@ be between 0.0 and 1.0, unless a 'scale' is defined in the config.
When 'scale' is defined, then this value should be between 0.0 and When 'scale' is defined, then this value should be between 0.0 and
'scale'. 'scale'.
### [led]
The following command is available when any of the
[led config sections](Config_Reference.md#leds) are enabled.
#### SET_LED
`SET_LED LED=<config_name> RED=<value> GREEN=<value> BLUE=<value>
WHITE=<value> [INDEX=<index>] [TRANSMIT=0] [SYNC=1]`: This sets the
LED output. Each color `<value>` must be between 0.0 and 1.0. The
WHITE option is only valid on RGBW LEDs. If the LED supports multiple
chips in a daisy-chain then one may specify INDEX to alter the color
of just the given chip (1 for the first chip, 2 for the second,
etc.). If INDEX is not provided then all LEDs in the daisy-chain will
be set to the provided color. If TRANSMIT=0 is specified then the
color change will only be made on the next SET_LED command that does
not specify TRANSMIT=0; this may be useful in combination with the
INDEX parameter to batch multiple updates in a daisy-chain. By
default, the SET_LED command will sync it's changes with other ongoing
gcode commands. This can lead to undesirable behavior if LEDs are
being set while the printer is not printing as it will reset the idle
timeout. If careful timing is not needed, the optional SYNC=0
parameter can be specified to apply the changes without resetting the
idle timeout.
#### SET_LED_TEMPLATE
`SET_LED_TEMPLATE LED=<led_name> TEMPLATE=<template_name>
[<param_x>=<literal>] [INDEX=<index>]`: Assign a
[display_template](Config_Reference.md#display_template) to a given
[LED](Config_Reference.md#leds). For example, if one defined a
`[display_template my_led_template]` config section then one could
assign `TEMPLATE=my_led_template` here. The display_template should
produce a comma separated string containing four floating point
numbers corresponding to red, green, blue, and white color settings.
The template will be continuously evaluated and the LED will be
automatically set to the resulting colors. One may set
display_template parameters to use during template evaluation
(parameters will be parsed as Python literals). If INDEX is not
specified then all chips in the LED's daisy-chain will be set to the
template, otherwise only the chip with the given index will be
updated. If TEMPLATE is an empty string then this command will clear
any previous template assigned to the LED (one can then use `SET_LED`
commands to manage the LED's color settings).
### [output_pin] ### [output_pin]
The following command is available when an The following command is available when an
@@ -1050,20 +857,6 @@ output `VALUE`. VALUE should be 0 or 1 for "digital" output pins. For
PWM pins, set to a value between 0.0 and 1.0, or between 0.0 and PWM pins, set to a value between 0.0 and 1.0, or between 0.0 and
`scale` if a scale is configured in the output_pin config section. `scale` if a scale is configured in the output_pin config section.
`SET_PIN PIN=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
pin. For example, if one defined a `[display_template
my_pin_template]` config section then one could assign
`TEMPLATE=my_pin_template` here. The display_template should produce a
string containing a floating point number with the desired value. The
template will be continuously evaluated and the pin will be
automatically set to the resulting value. One may set display_template
parameters to use during template evaluation (parameters will be
parsed as Python literals). If TEMPLATE is an empty string then this
command will clear any previous template assigned to the pin (one can
then use `SET_PIN` commands to manage the values directly).
### [palette2] ### [palette2]
The following commands are available when the The following commands are available when the
@@ -1099,6 +892,20 @@ Palette 2 once the loading has been completed. This command is the
same as pressing **Smart Load** directly on the Palette 2 screen after same as pressing **Smart Load** directly on the Palette 2 screen after
the filament load is complete. the filament load is complete.
### [pid_calibrate]
The pid_calibrate module is automatically loaded if a heater is defined
in the config file.
#### PID_CALIBRATE
`PID_CALIBRATE HEATER=<config_name> TARGET=<temperature>
[WRITE_FILE=1]`: Perform a PID calibration test. The specified heater
will be enabled until the specified target temperature is reached, and
then the heater will be turned off and on for several cycles. If the
WRITE_FILE parameter is enabled, then the file /tmp/heattest.txt will
be created with a log of all temperature samples taken during the
test.
### [pause_resume] ### [pause_resume]
The following commands are available when the The following commands are available when the
@@ -1124,20 +931,6 @@ the paused state is fresh for each print.
#### CANCEL_PRINT #### CANCEL_PRINT
`CANCEL_PRINT`: Cancels the current print. `CANCEL_PRINT`: Cancels the current print.
### [pid_calibrate]
The pid_calibrate module is automatically loaded if a heater is defined
in the config file.
#### PID_CALIBRATE
`PID_CALIBRATE HEATER=<config_name> TARGET=<temperature>
[WRITE_FILE=1]`: Perform a PID calibration test. The specified heater
will be enabled until the specified target temperature is reached, and
then the heater will be turned off and on for several cycles. If the
WRITE_FILE parameter is enabled, then the file /tmp/heattest.txt will
be created with a log of all temperature samples taken during the
test.
### [print_stats] ### [print_stats]
The print_stats module is automatically loaded. The print_stats module is automatically loaded.
@@ -1228,21 +1021,6 @@ CYCLE_TIME parameter is not stored between SET_PIN commands (any
SET_PIN command without an explicit CYCLE_TIME parameter will use the SET_PIN command without an explicit CYCLE_TIME parameter will use the
`cycle_time` specified in the pwm_cycle_time config section). `cycle_time` specified in the pwm_cycle_time config section).
### [quad_gantry_level]
The following commands are available when the
[quad_gantry_level config section](Config_Reference.md#quad_gantry_level)
is enabled.
#### QUAD_GANTRY_LEVEL
`QUAD_GANTRY_LEVEL [RETRIES=<value>] [RETRY_TOLERANCE=<value>]
[HORIZONTAL_MOVE_Z=<value>] [<probe_parameter>=<value>]`: This command
will probe the points specified in the config and then make
independent adjustments to each Z stepper to compensate for tilt. See
the PROBE command for details on the optional probe parameters. The
optional `RETRIES`, `RETRY_TOLERANCE`, and `HORIZONTAL_MOVE_Z` values
override those options specified in the config file.
### [query_adc] ### [query_adc]
The query_adc module is automatically loaded. The query_adc module is automatically loaded.
@@ -1278,19 +1056,20 @@ is enabled (also see the
all enabled accelerometer chips. all enabled accelerometer chips.
#### TEST_RESONANCES #### TEST_RESONANCES
`TEST_RESONANCES AXIS=<axis> [OUTPUT=<resonances,raw_data>] `TEST_RESONANCES AXIS=<axis> OUTPUT=<resonances,raw_data>
[NAME=<name>] [FREQ_START=<min_freq>] [FREQ_END=<max_freq>] [NAME=<name>] [FREQ_START=<min_freq>] [FREQ_END=<max_freq>]
[ACCEL_PER_HZ=<accel_per_hz>] [HZ_PER_SEC=<hz_per_sec>] [CHIPS=<chip_name>] [HZ_PER_SEC=<hz_per_sec>] [CHIPS=<adxl345_chip_name>]
[POINT=x,y,z] [INPUT_SHAPING=<0:1>]`: Runs the resonance [POINT=x,y,z] [INPUT_SHAPING=[<0:1>]]`: Runs the resonance
test in all configured probe points for the requested "axis" and test in all configured probe points for the requested "axis" and
measures the acceleration using the accelerometer chips configured for measures the acceleration using the accelerometer chips configured for
the respective axis. "axis" can either be X or Y, or specify an 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 arbitrary direction as `AXIS=dx,dy`, where dx and dy are floating
point numbers defining a direction vector (e.g. `AXIS=X`, `AXIS=Y`, or 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` `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 and `AXIS=-dx,-dy` is equivalent. `adxl345_chip_name` can be one or
more configured accel chips, delimited with comma, for example more configured adxl345 chip,delimited with comma, for example
`CHIPS="adxl345, adxl345 rpi"`. If POINT is specified it will override the point(s) `CHIPS="adxl345, adxl345 rpi"`. Note that `adxl345` can be omitted from
named adxl345 chips. If POINT is specified it will override the point(s)
configured in `[resonance_tester]`. If `INPUT_SHAPING=0` or not set(default), configured in `[resonance_tester]`. If `INPUT_SHAPING=0` or not set(default),
disables input shaping for the resonance testing, because disables input shaping for the resonance testing, because
it is not valid to run the resonance testing with the input shaper it is not valid to run the resonance testing with the input shaper
@@ -1307,9 +1086,8 @@ frequency response is calculated (across all probe points) and written into
#### SHAPER_CALIBRATE #### SHAPER_CALIBRATE
`SHAPER_CALIBRATE [AXIS=<axis>] [NAME=<name>] [FREQ_START=<min_freq>] `SHAPER_CALIBRATE [AXIS=<axis>] [NAME=<name>] [FREQ_START=<min_freq>]
[FREQ_END=<max_freq>] [ACCEL_PER_HZ=<accel_per_hz>][HZ_PER_SEC=<hz_per_sec>] [FREQ_END=<max_freq>] [HZ_PER_SEC=<hz_per_sec>] [CHIPS=<adxl345_chip_name>]
[CHIPS=<chip_name>] [MAX_SMOOTHING=<max_smoothing>] [INPUT_SHAPING=<0:1>]`: [MAX_SMOOTHING=<max_smoothing>]`: Similarly to `TEST_RESONANCES`, runs
Similarly to `TEST_RESONANCES`, runs
the resonance test as configured, and tries to find the optimal the resonance test as configured, and tries to find the optimal
parameters for the input shaper for the requested axis (or both X and parameters for the input shaper for the requested axis (or both X and
Y axes if `AXIS` parameter is unset). If `MAX_SMOOTHING` is unset, its Y axes if `AXIS` parameter is unset). If `MAX_SMOOTHING` is unset, its
@@ -1359,9 +1137,8 @@ has been enabled.
#### SAVE_VARIABLE #### SAVE_VARIABLE
`SAVE_VARIABLE VARIABLE=<name> VALUE=<value>`: Saves the variable to `SAVE_VARIABLE VARIABLE=<name> VALUE=<value>`: Saves the variable to
disk so that it can be used across restarts. The VARIABLE must be lowercase. disk so that it can be used across restarts. All stored variables are
All stored variables are loaded into the loaded into the `printer.save_variables.variables` dict at startup and
`printer.save_variables.variables` dict at startup and
can be used in gcode macros. The provided VALUE is parsed as a Python can be used in gcode macros. The provided VALUE is parsed as a Python
literal. literal.
@@ -1505,42 +1282,6 @@ temperature_fan. If a target is not supplied, it is set to the
specified temperature in the config file. If speeds are not supplied, specified temperature in the config file. If speeds are not supplied,
no change is applied. no change is applied.
### [temperature_probe]
The following commands are available when a
[temperature_probe config section](Config_Reference.md#temperature_probe)
is enabled.
#### TEMPERATURE_PROBE_CALIBRATE
`TEMPERATURE_PROBE_CALIBRATE [PROBE=<probe name>] [TARGET=<value>] [STEP=<value>]`:
Initiates probe drift calibration for eddy current based probes. The `TARGET`
is a target temperature for the last sample. When the temperature recorded
during a sample exceeds the `TARGET` calibration will complete. The `STEP`
parameter sets temperature delta (in C) between samples. After a sample has
been taken, this delta is used to schedule a call to `TEMPERATURE_PROBE_NEXT`.
The default `STEP` is 2.
#### TEMPERATURE_PROBE_NEXT
`TEMPERATURE_PROBE_NEXT`: After calibration has started this command is run to
take the next sample. It is automatically scheduled to run when the delta
specified by `STEP` has been reached, however its also possible to manually run
this command to force a new sample. This command is only available during
calibration.
#### TEMPERATURE_PROBE_COMPLETE:
`TEMPERATURE_PROBE_COMPLETE`: Can be used to end calibration and save the
current result before the `TARGET` temperature is reached. This command
is only available during calibration.
#### ABORT
`ABORT`: Aborts the calibration process, discarding the current results.
This command is only available during drift calibration.
### TEMPERATURE_PROBE_ENABLE
`TEMPERATURE_PROBE_ENABLE ENABLE=[0|1]`: Sets temperature drift
compensation on or off. If ENABLE is set to 0, drift compensation
will be disabled, if set to 1 it is enabled.
### [tmcXXXX] ### [tmcXXXX]
The following commands are available when any of the The following commands are available when any of the
@@ -1669,10 +1410,44 @@ The following commands are available when the
[z_tilt config section](Config_Reference.md#z_tilt) is enabled. [z_tilt config section](Config_Reference.md#z_tilt) is enabled.
#### Z_TILT_ADJUST #### Z_TILT_ADJUST
`Z_TILT_ADJUST [RETRIES=<value>] [RETRY_TOLERANCE=<value>] `Z_TILT_ADJUST [HORIZONTAL_MOVE_Z=<value>] [<probe_parameter>=<value>]`: This
[HORIZONTAL_MOVE_Z=<value>] [<probe_parameter>=<value>]`: This command command will probe the points specified in the config and then make independent
will probe the points specified in the config and then make adjustments to each Z stepper to compensate for tilt. See the PROBE command for
independent adjustments to each Z stepper to compensate for tilt. See details on the optional probe parameters. The optional `HORIZONTAL_MOVE_Z`
the PROBE command for details on the optional probe parameters. The value overrides the `horizontal_move_z` option specified in the config file.
optional `RETRIES`, `RETRY_TOLERANCE`, and `HORIZONTAL_MOVE_Z` values
override those options specified in the config file. ### [temperature_probe]
The following commands are available when a
[temperature_probe config section](Config_Reference.md#temperature_probe)
is enabled.
#### TEMPERATURE_PROBE_CALIBRATE
`TEMPERATURE_PROBE_CALIBRATE [PROBE=<probe name>] [TARGET=<value>] [STEP=<value>]`:
Initiates probe drift calibration for eddy current based probes. The `TARGET`
is a target temperature for the last sample. When the temperature recorded
during a sample exceeds the `TARGET` calibration will complete. The `STEP`
parameter sets temperature delta (in C) between samples. After a sample has
been taken, this delta is used to schedule a call to `TEMPERATURE_PROBE_NEXT`.
The default `STEP` is 2.
#### TEMPERATURE_PROBE_NEXT
`TEMPERATURE_PROBE_NEXT`: After calibration has started this command is run to
take the next sample. It is automatically scheduled to run when the delta
specified by `STEP` has been reached, however its also possible to manually run
this command to force a new sample. This command is only available during
calibration.
#### TEMPERATURE_PROBE_COMPLETE:
`TEMPERATURE_PROBE_COMPLETE`: Can be used to end calibration and save the
current result before the `TARGET` temperature is reached. This command
is only available during calibration.
#### ABORT
`ABORT`: Aborts the calibration process, discarding the current results.
This command is only available during drift calibration.
### TEMPERATURE_PROBE_ENABLE
`TEMPERATURE_PROBE_ENABLE ENABLE=[0|1]`: Sets temperature drift
compensation on or off. If ENABLE is set to 0, drift compensation
will be disabled, if set to 1 it is enabled.

View File

@@ -1,20 +1,15 @@
# Installation # Installation
These instructions assume the software will run on a Linux-based host These instructions assume the software will run on a Raspberry Pi
running a Klipper-compatible front end. It is recommended that a computer in conjunction with OctoPrint. It is recommended that a
SBC(Small Board Computer) such as a Raspberry Pi or Debian-based Linux Raspberry Pi 2 (or later) be used as the host machine (see the
device be used as the host machine (see the
[FAQ](FAQ.md#can-i-run-klipper-on-something-other-than-a-raspberry-pi-3) [FAQ](FAQ.md#can-i-run-klipper-on-something-other-than-a-raspberry-pi-3)
for other options). for other machines).
For the purposes of these instructions, host relates to the Linux device and
mcu relates to the printer board. SBC relates to the term Small Board Computer
such as the Raspberry Pi.
## Obtain a Klipper Configuration File ## Obtain a Klipper Configuration File
Most Klipper settings are determined by a "printer configuration file" Most Klipper settings are determined by a "printer configuration file"
printer.cfg, that will be stored on the host. An appropriate configuration that will be stored on the Raspberry Pi. An appropriate configuration
file can often be found by looking in the Klipper file can often be found by looking in the Klipper
[config directory](../config/) for a file starting with a "printer-" [config directory](../config/) for a file starting with a "printer-"
prefix that corresponds to the target printer. The Klipper prefix that corresponds to the target printer. The Klipper
@@ -40,51 +35,38 @@ printer configuration file, then start with the closest example
[config file](../config/) and use the Klipper [config file](../config/) and use the Klipper
[config reference](Config_Reference.md) for further information. [config reference](Config_Reference.md) for further information.
## Interacting with Klipper ## Prepping an OS image
Klipper is a 3d printer firmware, so it needs some way for the user to Start by installing [OctoPi](https://github.com/guysoft/OctoPi) on the
interact with it. Raspberry Pi computer. Use OctoPi v0.17.0 or later - see the
[OctoPi releases](https://github.com/guysoft/OctoPi/releases) for
release information. One should verify that OctoPi boots and that the
OctoPrint web server works. After connecting to the OctoPrint web
page, follow the prompt to upgrade OctoPrint to v1.4.2 or later.
Currently the best choices are front ends that retrieve information through After installing OctoPi and upgrading OctoPrint, it will be necessary
the [Moonraker web API](https://moonraker.readthedocs.io/) and there is also to ssh into the target machine to run a handful of system commands. If
the option to use [Octoprint](https://octoprint.org/) to control Klipper. using a Linux or MacOS desktop, then the "ssh" software should already
be installed on the desktop. There are free ssh clients available for
other desktops (eg,
[PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/)). Use the
ssh utility to connect to the Raspberry Pi (`ssh pi@octopi` -- password
is "raspberry") and run the following commands:
The choice is up to the user on what to use, but the underlying Klipper is the ```
same in all cases. We encourage users to research the options available and git clone https://github.com/Klipper3d/klipper
make an informed decision. ./klipper/scripts/install-octopi.sh
```
## Obtaining an OS image for SBC's The above will download Klipper, install some system dependencies,
setup Klipper to run at system startup, and start the Klipper host
There are many ways to obtain an OS image for Klipper for SBC use, most depend on software. It will require an internet connection and it may take a few
what front end you wish to use. Some manufacturers of these SBC boards also provide minutes to complete.
their own Klipper-centric images.
The two main Moonraker-based front ends are [Fluidd](https://docs.fluidd.xyz/)
and [Mainsail](https://docs.mainsail.xyz/), the latter of which has a premade install
image ["MainsailOS"](https://docs-os.mainsail.xyz/), this has the option for Raspberry Pi
and some OrangePi variants.
Fluidd can be installed via KIAUH(Klipper Install And Update Helper), which
is explained below and is a 3rd party installer for all things Klipper.
OctoPrint can be installed via the popular OctoPi image or via KIAUH, this
process is explained in [OctoPrint.md](OctoPrint.md)
## Installing via KIAUH
Normally you would start with a base image for your SBC, RPiOS Lite for example,
or in the case of an x86 Linux device, Ubuntu Server. Please note that Desktop
variants are not recommended due to certain helper programs that can stop some
Klipper functions from working and even mask access to some printer boards.
KIAUH can be used to install Klipper and its associated programs on a variety
of Linux-based systems that run a form of Debian. More information can be found
at https://github.com/dw-0/kiauh
## Building and flashing the micro-controller ## Building and flashing the micro-controller
To compile the micro-controller code, start by running these commands To compile the micro-controller code, start by running these commands
on your host device: on the Raspberry Pi:
``` ```
cd ~/klipper/ cd ~/klipper/
@@ -106,7 +88,7 @@ make
If the comments at the top of the If the comments at the top of the
[printer configuration file](#obtain-a-klipper-configuration-file) [printer configuration file](#obtain-a-klipper-configuration-file)
describe custom steps for "flashing" the final image to the printer describe custom steps for "flashing" the final image to the printer
control board, then follow those steps and then proceed to control board then follow those steps and then proceed to
[configuring OctoPrint](#configuring-octoprint-to-use-klipper). [configuring OctoPrint](#configuring-octoprint-to-use-klipper).
Otherwise, the following steps are often used to "flash" the printer Otherwise, the following steps are often used to "flash" the printer
@@ -126,40 +108,10 @@ It should report something similar to the following:
It's common for each printer to have its own unique serial port name. It's common for each printer to have its own unique serial port name.
This unique name will be used when flashing the micro-controller. It's This unique name will be used when flashing the micro-controller. It's
possible there may be multiple lines in the above output - if so, possible there may be multiple lines in the above output - if so,
choose the line corresponding to the micro-controller. If many choose the line corresponding to the micro-controller (see the
items are listed and the choice is ambiguous, unplug the board and
run the command again, the missing item will be your print board(see the
[FAQ](FAQ.md#wheres-my-serial-port) for more information). [FAQ](FAQ.md#wheres-my-serial-port) for more information).
For common micro-controllers with STM32 or clone chips, LPC chips and For common micro-controllers, the code can be flashed with something
others, it is usual that these need an initial Klipper flash via SD card.
When flashing with this method, it is important to make sure that the
print board is not connected with USB to the host, due to some boards
being able to feed power back to the board and stopping a flash from
occurring.
Please note, that most print boards that use SD cards for flash will
implement some kind of flash loop protection for when the sd card is left
in place. There are two common methods:
Filename Change Required (usually "stock" print boards):
These boards require the firmware file to have a different name each
time you flash (for example, firmware1.bin, firmware2.bin, etc.).
If you reuse the same filename, the board may ignore it and not update.
Automatic File Renaming (usually aftermarket print boards:
Other boards allow using the same filename, commonly firmware.bin,
but after flashing, the board renames the file to firmware.cur.
This helps indicate the firmware was successfully flashed and prevents
it from flashing again on the next startup.
Before flashing, make sure to check which behavior your board follows.
For common micro-controllers using Atmega chips, for example the 2560,
the code can be flashed with something
similar to: similar to:
``` ```
@@ -171,38 +123,53 @@ sudo service klipper start
Be sure to update the FLASH_DEVICE with the printer's unique serial Be sure to update the FLASH_DEVICE with the printer's unique serial
port name. port name.
For common micro-controllers using RP2040 chips, the code can be flashed When flashing for the first time, make sure that OctoPrint is not
with something similar to: connected directly to the printer (from the OctoPrint web page, under
the "Connection" section, click "Disconnect").
``` ## Configuring OctoPrint to use Klipper
sudo service klipper stop
make flash FLASH_DEVICE=first
sudo service klipper start
```
It is important to note that RP2040 chips may need to be put into Boot mode The OctoPrint web server needs to be configured to communicate with
before this operation. the Klipper host software. Using a web browser, login to the OctoPrint
web page and then configure the following items:
Navigate to the Settings tab (the wrench icon at the top of the
page). Under "Serial Connection" in "Additional serial ports" add
`/tmp/printer`. Then click "Save".
Enter the Settings tab again and under "Serial Connection" change the
"Serial Port" setting to `/tmp/printer`.
In the Settings tab, navigate to the "Behavior" sub-tab and select the
"Cancel any ongoing prints but stay connected to the printer"
option. Click "Save".
From the main page, under the "Connection" section (at the top left of
the page) make sure the "Serial Port" is set to `/tmp/printer` and
click "Connect". (If `/tmp/printer` is not an available selection then
try reloading the page.)
Once connected, navigate to the "Terminal" tab and type "status"
(without the quotes) into the command entry box and click "Send". The
terminal window will likely report there is an error opening the
config file - that means OctoPrint is successfully communicating with
Klipper. Proceed to the next section.
## Configuring Klipper ## Configuring Klipper
The next step is to copy the The next step is to copy the
[printer configuration file](#obtain-a-klipper-configuration-file) to [printer configuration file](#obtain-a-klipper-configuration-file) to
the host. the Raspberry Pi.
Arguably the easiest way to set the Klipper configuration file is using the Arguably the easiest way to set the Klipper configuration file is to
built-in editors in Mainsail or Fluidd. These will allow the user to open use a desktop editor that supports editing files over the "scp" and/or
the configuration examples and save them to be printer.cfg. "sftp" protocols. There are freely available tools that support this
(eg, Notepad++, WinSCP, and Cyberduck). Load the printer config file
Another option is to use a desktop editor that supports editing files in the editor and then save it as a file named `printer.cfg` in the
over the "scp" and/or "sftp" protocols. There are freely available tools home directory of the pi user (ie, `/home/pi/printer.cfg`).
that support this (eg, Notepad++, WinSCP, and Cyberduck).
Load the printer config file in the editor and then save it as a file
named "printer.cfg" in the home directory of the pi user
(ie, /home/pi/printer.cfg).
Alternatively, one can also copy and edit the file directly on the Alternatively, one can also copy and edit the file directly on the
host via SSH. That may look something like the following (be Raspberry Pi via ssh. That may look something like the following (be
sure to update the command to use the appropriate printer config sure to update the command to use the appropriate printer config
filename): filename):
@@ -233,9 +200,9 @@ the `[mcu]` section to look something similar to:
serial: /dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0 serial: /dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0
``` ```
After creating and editing the file, it will be necessary to issue a After creating and editing the file it will be necessary to issue a
"restart" command in the command console to load the config. A "restart" command in the OctoPrint web terminal to load the config. A
"status" command will report that the printer is ready if the Klipper "status" command will report the printer is ready if the Klipper
config file is successfully read and the micro-controller is config file is successfully read and the micro-controller is
successfully found and configured. successfully found and configured.
@@ -244,10 +211,10 @@ Klipper to report a configuration error. If an error occurs, make any
necessary corrections to the printer config file and issue "restart" necessary corrections to the printer config file and issue "restart"
until "status" reports the printer is ready. until "status" reports the printer is ready.
Klipper reports error messages via the command console and pop-ups in Klipper reports error messages via the OctoPrint terminal tab. The
Fluidd and Mainsail. The "status" command can be used to re-report error "status" command can be used to re-report error messages. The default
messages. A log is available and usually located at Klipper startup script also places a log in **/tmp/klippy.log** which
`~/printer_data/logs/klippy.log`. provides more detailed information.
After Klipper reports that the printer is ready, proceed to the After Klipper reports that the printer is ready, proceed to the
[config check document](Config_checks.md) to perform some basic checks [config check document](Config_checks.md) to perform some basic checks

View File

@@ -1,489 +0,0 @@
# Load Cells
This document describes Klipper's support for load cells. Basic load cell
functionality can be used to read force data and to weigh things like filament.
A calibrated force sensor is an important part of a load cell based probe.
## Related Documentation
* [load_cell Config Reference](Config_Reference.md#load_cell)
* [load_cell G-Code Commands](G-Codes.md#load_cell)
* [load_cell Status Reference](Status_Reference.md#load_cell)
## Using `LOAD_CELL_DIAGNOSTIC`
When you first connect a load cell its good practice to check for issues by
running `LOAD_CELL_DIAGNOSTIC`. This tool collects 10 seconds of data from the
load cell and resport statistics:
```
$ LOAD_CELL_DIAGNOSTIC
// Collecting load cell data for 10 seconds...
// Samples Collected: 3211
// Measured samples per second: 332.0
// Good samples: 3211, Saturated samples: 0, Unique values: 900
// Sample range: [4.01% to 4.02%]
// Sample range / sensor capacity: 0.00524%
```
Things you can check with this data:
* The configured sample rate of the sensor should be close to the 'Measured
samples per second' value. If it is not you may have a configuration or wiring
issue.
* 'Saturated samples' should be 0. If you have saturated samples it means the
load sell is seeing more force than it can measure.
* 'Unique values' should be a large percentage of the 'Samples
Collected' value. If 'Unique values' is 1 it is very likely a wiring issue.
* Tap or push on the sensor while `LOAD_CELL_DIAGNOSTIC` runs. If
things are working correctly this should increase the 'Sample range'.
## Calibrating a Load Cell
Load cells are calibrated using the `LOAD_CELL_CALIBRATE` command. This is an
interactive calibration utility that walks you though a 3 step process:
1. First use the `TARE` command to establish the zero force value. This is the
`reference_tare_counts` config value.
2. Next you apply a known load or force to the load cell and run the
`CALIBRATE GRAMS=nnn` command. From this the `counts_per_gram` value is
calculated. See [the next section](#applying-a-known-force-or-load) for some
suggestions on how to do this.
3. Finally, use the `ACCEPT` command to save the results.
You can cancel the calibration process at any time with `ABORT`.
### Applying a Known Force or Load
The `CALIBRATE GRAMS=nnn` step can be accomplished in a number of ways. If your
load cell is under a platform like a bed or filament holder it might be easiest
to put a known mass on the platform. E.g. you could use a couple of 1KG filament
spools.
If your load cell is in the printer's toolhead a different approach is easier.
Put a digital scale on the printers bed and gently lower the toolhead onto the
scale (or raise the bed into the toolhead if your bed moves). You may be able to
do this using the `FORCE_MOVE` command. But more likely you will have to
manually moving the z axis with the motors off until the toolhead presses on the
scale.
A good calibration force would ideally be a large percentage of the load cell's
rated capacity. E.g. if you have a 5Kg load cell you would ideally calibrate it
with a 5kg mass. This might work well with under-bed sensors that have to
support a lot of weight. For toolhead probes this may not be a load that your
printer bed or toolhead can tolerate without damage. Do try to use at least 1Kg
of force, most printers should tolerate this without issue.
When calibrating make careful note of the values reported:
```
$ CALIBRATE GRAMS=555
// Calibration value: -2.78% (-59803108), Counts/gram: 73039.78739,
Total capacity: +/- 29.14Kg
```
The `Total capacity` should be close to the theoretical rating of the load cell
based on the sensor's capacity. If it is much larger you could have used a
higher gain setting in the sensor or a more sensitive load cell. This isn't as
critical for 32bit and 24bit sensors but is much more critical for low bit width
sensors.
## Reading Force Data
Force data can be read with a GCode command:
```
LOAD_CELL_READ
// 10.6g (1.94%)
```
Data is also continuously read and can be consumed from the load_cell printer
object in a macro:
```
{% set grams = printer.load_cell.force_g %}
```
This provides an average force over the last 1 second, similar to how
temperature sensors work.
## Taring a Load Cell
Taring, sometimes called zeroing, sets the current weight reported by the
load_cell to 0. This is useful for measuring relative to a known weight. e.g.
when measuring a filament spool, using `LOAD_CELL_TARE` sets the weight to 0.
Then as filament is printed the load_cell will report the weight of the
filament used.
```
LOAD_CELL_TARE
// Load cell tare value: 5.32% (445903)
```
The current tare value is reported in the printers status and can be read in
a macro:
```
{% set tare_counts = printer.load_cell.tare_counts %}
```
# Load Cell Probes
## Related Documentation
* [load_cell_probe Config Reference](Config_Reference.md#load_cell_probe)
* [load_cell_probe G-Code Commands](G-Codes.md#load_cell_probe)
* [load_cell_probe Statuc Reference](Status_Reference.md#load_cell_probe)
## Load Cell Probe Safety
Because load cells are a direct nozzle contact probe there is a risk of
damage to your printer if too much force is used. The load cell probing system
includes a number of safety checks that try to keep your machine safe from
excessive force to the toolhead. It's important to understand what they are
and how they work as you can defeat most of them with poorly chosen config
values.
#### Calibration Check
Every time a homing move starts, load_cell_probe checks
that the load_cell is calibrated. If not it will stop the move with an error:
`!! Load Cell not calibrated`.
#### `counts_per_gram`
This setting is used to convert raw sensor counts into grams. All the safety
limits are in gram units for your convenience. If the `counts_per_gram`
setting is not accurate you can easily exceed the safe force on the toolhead.
You should never guess this value. Use `LOAD_CELL_CALIBRATE` to find your load
cells actual `counts_per_gram`.
#### `trigger_force`
This is the force in grams that triggers the endstop to halt the homing move.
When a homing move starts the endstop tares itself with the current reading
from the load cell. `trigger_force` is measured from that tare value. There is
always some overshoot of this value when the probe collides with the bed,
so be conservative. e.g. a setting of 100g could result in 350g of peak force
before the toolhead stops. This overshoot will increase with faster probing
`speed`, a low ADC sample rate or [multi MCU homing](Multi_MCU_Homing.md).
#### `reference_tare_counts`
This is the baseline tare value that is set by `LOAD_CELL_CALIBRATE`.
This value works with `force_safety_limit` to limit the maximum force on the
toolhead.
#### `force_safety_limit`
This is the maximum absolute force, relative to `reference_tare_counts`,
that the probe will allow while homing or probing. If the MCU sees this
force exceeded it will shut down the printer with the error `!! Load cell
endstop: too much force!`. There are a number of ways this can be triggered:
The first risk this protects against is picking too large of a value for
`drift_filter_cutoff_frequency`. This can cause the drift filter to filter out
a probe event and continue the homing move. If this happens the
`force_safety_limit` acts as a backup protection.
The second problem is probing repeatedly in one place. Klipper does not retract
the probe when doing a single `PROBE` command. This can result
in force applied to the toolhead at the end of a probing cycle. Because
external forces can vary greatly between probing locations,
`load_cell_probe` performs a tare before beginning each probe. If you repeat
the `PROBE` command, load_cell_probe will tare the endstop at the current force.
Multiple cycles of this will result in ever-increasing force on the toolhead.
`force_safety_limit` stops this cycle from running out of control.
Another way this run-away can happen is damage to a strain gauge. If the metal
part is permanently bent it will change the `reference_tare_counts` of the
device. This puts the starting tare value much closer to the limit making it
more likely to be violated. You want to be notified if this is happening
because your hardware has been permanently damaged.
The final way this can be triggered is due to temperature changes. If your
strain gauges are heated their `reference_tare_counts` may be very different
at ambient temperature vs operating temperature. In this case you may need
to increase the `force_safety_limit` to allow for thermal changes.
#### Load Cell Endstop Watchdog Task
When homing the load_cell_endstop starts a task on the MCU to trac
measurements arriving from the sensor. If the sensor fails to send
measurements for 2 sample periods the watchdog will shut down the printer
with an error `!! LoadCell Endstop timed out waiting on ADC data`.
If this happens, the most likely cause is a fault from the ADC. Inadequate
grounding of your printer can be the root cause. The frame, power supply
case and pint bed should all be connected to ground. You may need to ground
the frame in multiple places. Anodized aluminum extrusions do not conduct
electricity well. You might need to sand the area where the grounding wire
is attached to make good electrical contact.
## Load Cell Probe Setup
This section covers the process for commissioning a load cell probe.
### Verify the Load Cell First
A `[load_cell_probe]` is also a `[load_cell]` and G-code commands related to
`[load_cell]` work with `[load_cell_probe]`. Before attempting to use a load
cell probe, follow the directions for
[calibrating the load cell](Load_Cell.md#calibrating-a-load-cell) with
`CALIBRATE_LOAD_CELL` and checking its operation with `LOAD_CELL_DIAGNOSTIC`.
### Verify Probe Operation With LOAD_CELL_TEST_TAP
Use the command `LOAD_CELL_TEST_TAP` to test the operation of the load cell
probe before actually trying to probe with it. This command detects taps,
just like the PROBE command, but it does not move the z axis. By default, it
listens for 3 taps before ending the test. You have 30 seconds to do each
tap, if no taps are detected the command will time out.
If this test fails, check your configuration and `LOAD_CELL_DIAGNOSTIC`
carefully to look for issues.
Load cell probes don't support the `QUERY_ENDSTOPS` or `QUERY_PROBE`
commands. Use `LOAD_CELL_TEST_TAP` for testing functionality before probing.
### Homing Macros
Load cell probe is not an endstop and doesn't support `endstop:
prove:z_virtual_endstop`. For the time being you'll need to configure your z
axis with an MCU pin as its endstop. You won't actually be using the pin but
for the time being you have to configure something.
To home the axis with just the probe you need to set up a custom homing
macro. This requires setting up
[homing_override](Config_Reference.md#homing_override).
Here is a simple macro that can accomplish this. Note that the
`_HOME_Z_FROM_LAST_PROBE` macro has to be separate because of the way macros
work. The sub-call is needed so that the `_HOME_Z_FROM_LAST_PROBE` macro can
see the result of the probe in `printer.probe.last_z_result`.
```gcode
[gcode_macro _HOME_Z_FROM_LAST_PROBE]
gcode:
{% set z_probed = printer.probe.last_z_result %}
{% set z_position = printer.toolhead.position[2] %}
{% set z_actual = z_position - z_probed %}
SET_KINEMATIC_POSITION Z={z_actual}
[gcode_macro _HOME_Z]
gcode:
SET_GCODE_OFFSET Z=0 # load cell probes dont need a Z offset
# position toolhead for homing Z, edit for your printers size
#G90 # absolute move
#G1 Y50 X50 F{5 * 60} # move to X/Y position for homing
# soft home the z axis to its limit so it can be moved:
SET_KINEMATIC_POSITION Z={printer.toolhead.axis_maximum[2]}
# Fast approach and tap
PROBE PROBE_SPEED={5 * 60} # override the speed for faster homing
_HOME_Z_FROM_LAST_PROBE
# lift z to 2mm
G91 # relative move
G1 Z2 F{5 * 60}
# probe at standard speed
PROBE
_HOME_Z_FROM_LAST_PROBE
# lift z to 10mm for clearance
G91 # relative move
G1 Z10 F{5 * 60}
```
### Suggested Probing Temperature
Currently, we suggest keeping the nozzle temperature below the level that causes
the filament to ooze while homing and probing. 140C is a good starting
point. This temperature is also low enough not to scar PEI build surfaces.
Fouling of the nozzle and the print bed due to oozing filament is the #1 source
of probing error with the load cell probe. Klipper does not yet have a universal
way to detect poor quality taps due to filament ooze. The existing code may
decide that a tap is valid when it is of poor quality. Classifying these poor
quality taps is an area of active research.
Klipper also lacks support for re-locating a probe point if the
location has become fouled by filament ooze. Modules like `quad_gantry_level`
will repeatedly probe the same coordinates even if a probe previously failed
there.
Give the above it is strongly suggested not to probe at printing temperatures.
### Hot Nozzle Protection
The Voron project has a great macro for protecting your print surface from the
hot nozzle. See [Voron Tap's
`activate_gcode`](https://github.com/VoronDesign/Voron-Tap/blob/main/config/tap_klipper_instructions.md)
It is highly suggested to add something like this to your config.
### Nozzle Cleaning
Before probing the nozzle should be clean. You could do this manually before
every print. You can also implement a nozzle scrubber and automate the process.
Here is a suggested sequence:
1. Wait for the nozzle to heat up to probing temp (e.g. `M109 S140`)
1. Home the machine (`G28`)
1. Scrub the nozzle on a brush
1. Heat soak the print bed
1. Perform probing tasks: QGL, bed mesh etc.
### Temperature Compensation for Nozzle Growth
If you are probing at a safe temperature, the nozzle will expand after
heating to printing temperatures. This will cause the nozzle to get longer
and closer to the print surface. You can compensate for this with
[[z_thermal_adjust]](Config_Reference.md#z_thermal_adjust). This adjustment will
work across a range of printing
temperatures from PLA to PC.
#### Calculating the `temp_coeff` for `[z_thermal_adjust]`
The easiest way to do this is to measure at 2 different temperatures.
Ideally these should be the upper and lower limits of the printing
temperature range. E.g. 180C and 290C. You can perform a `PROBE_ACCURACY` at
both temperatures and then calculate the difference of the `average z` at both.
The adjustment value is the change in nozzle length divided by the change in
temperature. e.g.
```
temp_coeff = -0.05 / (290 - 180) = -0.00045455
```
The expected result is a negative number. Positive values for `temp_coeff` move
the nozzle closer to the bed and negative values move it further away.
Expect to have to move the nozzle further away as it gets longer when hot.
#### Configure `[z_thermal_adjust]`
Set up z_thermal_adjust to reference the `extruder` as the source of temperature
data. E.g.:
```
[z_thermal_adjust nozzle]
temp_coeff=-0.00045455
sensor_type: temperature_combined
sensor_list: extruder
combination_method: max
min_temp: 0
max_temp: 400
max_z_adjustment: 0.1
```
## Continuous Tare Filters for Toolhead Load Cells
Klipper implements a configurable IIR filter on the MCU to provide continuous
tareing of the load cell while probing. Continuous taring means the 0 value
moves with drift caused by external factors like bowden tubes and thermal
changes. This is aimed at toolhead sensors and moving beds that experience lots
of external forces that change while probing.
### Installing SciPy
The filtering code uses the excellent [SciPy](https://scipy.org/) library to
compute the filter coefficients based on the values your enter into the config.
Pre-compiled SciPi builds are available for Python 3 on 32 bit Raspberry Pi
systems. 32 bit + Python 3 is strongly recommended because it will streamline
your installation experience. It does work with Python 2 but installation can
take 30+ minutes and require installing additional tools.
```bash
~/klippy-env/bin/pip install scipy
```
### Filter Workbench
The filter parameters should be selected based on drift seen on the printer
during normal operation. A Jupyter notebook is provided in scripts,
[filter_workbench.ipynb](../scripts/filter_workbench.ipynb), to perform a
detailed investigation with real captured data and FFTs.
### Filtering Suggestions
For those just trying to get a filter working follow these suggestions:
* The only essential option is `drift_filter_cutoff_frequency`. A conservative
starting value is `0.5`Hz. Prusa shipped the MK4 with a setting of `0.8`Hz and
the XL with `11.2`Hz. This is probably a safe range to experiment with. This
value should be increased only until normal drift due to bowden tube force is
eliminated. Setting this value too high will result in slow triggering and
excess force going through the toolhead.
* Keep `trigger_force` low. The default is `75`g. The drift filter keeps the
internal grams value very close to 0 so a large trigger force is not needed.
* Keep `force_safety_limit` to a conservative value. The default value is 2Kg
and should keep your toolhead safe while experimenting. If you hit this limit
the `drift_filter_cutoff_frequency` value may be too high.
## Suggestions for Load Cell Tool Boards
This section covers suggestions for those developing toolhead boards that want
to support [load_cell_probe]
### ADC Sensor Selection & Board Development Hints
Ideally a sensor would meet these criteria:
* At least 24 bits wide
* Use SPI communications
* Has a pin can be used to indicate sample ready without SPI communications.
This is often called the "data ready" or "DRDY" pin. Checking a pin is much
faster than running an SPI query.
* Has a programmable gain amplifier gain setting of 128. This should eliminate
the need for a separate amplifier.
* Indicates via SPI if the sensor has been reset. Detecting resets avoids
timing errors in homing and using noisy data at startup. It can also help
users
track down wiring and grounding issues.
* A selectable sample rate between 350Hz and 2Khz. Very high sample rates don't
turn out to be beneficial in our 3D printers because they produce so much
noise
when moving fast. Sample rates below 250Hz will require slower probing speeds.
They also increase the force on the toolhead due to longer delays between
measurements. E.g. a 500Hz sensor moving at 5mm/s has the same safety factor
as
a 100Hz sensor moving at only 1mm/s.
* If designing for under-bed applications, and you want to sense multiple load
cells, use a chip that can sample all of its inputs simultaneously. Multiplex
ADCs that require switching channels have a settling of several samples after
each channel switch making them unsuitable for probing applications.
Implementing support for a new sensor chip is not particularly difficult with
Klipper's `bulk_sensor` and `load_cell_endstop` infrastructure.
### 5V Power Filtering
It is strongly suggested to use larger capacitors than specified by the ADC chip
manufacturer. ADC chips are usually targeted at low noise environments, like
battery powered devices. Sensor manufacturers suggested application notes
generally assume a quiet power supply. Treat their suggested capacitor values as
minimums.
3D printers put huge amounts of noise onto the 5V bus and this can ruin the
sensor's accuracy. Test the sensor on the board with a typical 3D printer power
supply and active stepper drivers before deciding on smoothing capacitor sizes.
### Grounding & Ground Planes
Analog ADC chips contain components that are very vulnerable to noise and
ESD. A large ground plane on the first board layer under the chip can help with
noise. Keep the chip away from power sections and DC to DC converters. The board
should have proper grounding back to the DC supply.
### HX711 and HX717 Notes
This sensor is popular because of its low cost and availability in the
supply chain. However, this is a sensor with some drawbacks:
* The HX71x sensors use bit-bang communication which has a high overhead on the
MCU. Using a sensor that communicates via SPI would save resources on the tool
board's CPU.
* The HX71x lacks a way to communicate reset events to the MCU. Klipper detects
resets with a timing heuristic but this is not ideal. Resets indicate a
problem with wiring or grounding.
* For probing applications the HX717 version is strongly preferred because
of its higher sample rate (320 vs 80). Probing speed on the HX711 should be
limited to less than 2mm/s.
* The sample rate on the HX71x cannot be set from klipper's config. If you have
the 10SPS version of the sensor (which is widely distributed) it needs to
be physically re-wired to run at 80SPS.

View File

@@ -1,26 +1,24 @@
# Measuring Resonances # Measuring Resonances
Klipper has built-in support for the ADXL345, MPU-9250, LIS2DW and LIS3DH compatible Klipper has built-in support for the ADXL345, MPU-9250 and LIS2DW compatible
accelerometers which can be used to measure resonance frequencies of the printer accelerometers which can be used to measure resonance frequencies of the printer
for different axes, and auto-tune [input shapers](Resonance_Compensation.md) to for different axes, and auto-tune [input shapers](Resonance_Compensation.md) to
compensate for resonances. Note that using accelerometers requires some compensate for resonances. Note that using accelerometers requires some
soldering and crimping. The ADXL345 can be connected to the SPI interface soldering and crimping. The ADXL345/LIS2DW can be connected to the SPI interface
of a Raspberry Pi or MCU board (it needs to be reasonably fast). The MPU family can of a Raspberry Pi or MCU board (it needs to be reasonably fast). The MPU family can
be connected to the I2C interface of a Raspberry Pi directly, or to an I2C be connected to the I2C interface of a Raspberry Pi directly, or to an I2C
interface of an MCU board that supports 400kbit/s *fast mode* in Klipper. The interface of an MCU board that supports 400kbit/s *fast mode* in Klipper.
LIS2DW and LIS3DH can be connected to either SPI or I2C with the same considerations
as above.
When sourcing accelerometers, be aware that there are a variety of different PCB When sourcing accelerometers, be aware that there are a variety of different PCB
board designs and different clones of them. If it is going to be connected to a board designs and different clones of them. If it is going to be connected to a
5V printer MCU ensure it has a voltage regulator and level shifters. 5V printer MCU ensure it has a voltage regulator and level shifters.
For ADXL345s, make sure that the board supports SPI mode (a small number of For ADXL345s/LIS2DWs, make sure that the board supports SPI mode (a small number of
boards appear to be hard-configured for I2C by pulling SDO to GND). boards appear to be hard-configured for I2C by pulling SDO to GND).
For MPU-9250/MPU-9255/MPU-6515/MPU-6050/MPU-6500/ICM20948s and LIS2DW/LIS3DH there For MPU-9250/MPU-9255/MPU-6515/MPU-6050/MPU-6500s there are also a variety of
are also a variety of board designs and clones with different I2C pull-up resistors board designs and clones with different I2C pull-up resistors which will need
which will need supplementing. supplementing.
## MCUs with Klipper I2C *fast-mode* Support ## MCUs with Klipper I2C *fast-mode* Support
@@ -29,7 +27,6 @@ which will need supplementing.
| Raspberry Pi | 3B+, Pico | 3A, 3A+, 3B, 4 | | Raspberry Pi | 3B+, Pico | 3A, 3A+, 3B, 4 |
| AVR ATmega | ATmega328p | ATmega32u4, ATmega128, ATmega168, ATmega328, ATmega644p, ATmega1280, ATmega1284, ATmega2560 | | AVR ATmega | ATmega328p | ATmega32u4, ATmega128, ATmega168, ATmega328, ATmega644p, ATmega1280, ATmega1284, ATmega2560 |
| AVR AT90 | - | AT90usb646, AT90usb1286 | | AVR AT90 | - | AT90usb646, AT90usb1286 |
| SAMD | SAMC21G18 | SAMC21G18, SAMD21G18, SAMD21E18, SAMD21J18, SAMD21E15, SAMD51G19, SAMD51J19, SAMD51N19, SAMD51P20, SAME51J19, SAME51N19, SAME54P20 |
## Installation instructions ## Installation instructions
@@ -136,7 +133,7 @@ GND+SCL
Note that unlike a cable shield, any GND(s) should be connected at both ends. Note that unlike a cable shield, any GND(s) should be connected at both ends.
#### MPU-9250/MPU-9255/MPU-6515/MPU-6050/MPU-6500/ICM20948 #### MPU-9250/MPU-9255/MPU-6515/MPU-6050/MPU-6500
These accelerometers have been tested to work over I2C on the RPi, RP2040 (Pico) These accelerometers have been tested to work over I2C on the RPi, RP2040 (Pico)
and AVR at 400kbit/s (*fast mode*). Some MPU accelerometer modules include and AVR at 400kbit/s (*fast mode*). Some MPU accelerometer modules include
@@ -152,7 +149,7 @@ Recommended connection scheme for I2C on the Raspberry Pi:
| SDA | 03 | GPIO02 (SDA1) | | SDA | 03 | GPIO02 (SDA1) |
| SCL | 05 | GPIO03 (SCL1) | | SCL | 05 | GPIO03 (SCL1) |
The RPi has built-in 1.8K pull-ups on both SCL and SDA. The RPi has buit-in 1.8K pull-ups on both SCL and SDA.
![MPU-9250 connected to Pi](img/mpu9250-PI-fritzing.png) ![MPU-9250 connected to Pi](img/mpu9250-PI-fritzing.png)
@@ -215,20 +212,12 @@ sudo apt install python3-numpy python3-matplotlib libatlas-base-dev libopenblas-
Next, in order to install NumPy in the Klipper environment, run the command: Next, in order to install NumPy in the Klipper environment, run the command:
``` ```
~/klippy-env/bin/pip install -v "numpy<1.26" ~/klippy-env/bin/pip install -v numpy
``` ```
Note that, depending on the performance of the CPU, it may take *a lot* Note that, depending on the performance of the CPU, it may take *a lot*
of time, up to 10-20 minutes. Be patient and wait for the completion of of time, up to 10-20 minutes. Be patient and wait for the completion of
the installation. On some occasions, if the board has too little RAM the installation. On some occasions, if the board has too little RAM
the installation may fail and you will need to enable swap. Also note the installation may fail and you will need to enable swap.
the forced version, due to newer versions of NumPY having requirements
that may not be satisfied in some klipper python environments.
Once installed please check that no errors show from the command:
```
~/klippy-env/bin/python -c 'import numpy;'
```
The correct output should simply be a new line.
#### Configure ADXL345 With RPi #### Configure ADXL345 With RPi
@@ -316,7 +305,7 @@ you'll also want to modify your `printer.cfg` file to include this:
Restart Klipper via the `RESTART` command. Restart Klipper via the `RESTART` command.
#### Configure LIS2DW series over SPI #### Configure LIS2DW series
``` ```
[mcu lis] [mcu lis]
@@ -355,7 +344,6 @@ accel_chip: mpu9250
probe_points: probe_points:
100, 100, 20 # an example 100, 100, 20 # an example
``` ```
If you are using the ICM20948, replace instances of "mpu9250" with "icm20948".
#### Configure MPU-9520 Compatibles With Pico #### Configure MPU-9520 Compatibles With Pico
@@ -378,7 +366,6 @@ probe_points:
[static_digital_output pico_3V3pwm] # Improve power stability [static_digital_output pico_3V3pwm] # Improve power stability
pins: pico:gpio23 pins: pico:gpio23
``` ```
If you are using the ICM20948, replace instances of "mpu9250" with "icm20948".
#### Configure MPU-9520 Compatibles with AVR #### Configure MPU-9520 Compatibles with AVR
@@ -397,7 +384,6 @@ accel_chip: mpu9250
probe_points: probe_points:
100, 100, 20 # an example 100, 100, 20 # an example
``` ```
If you are using the ICM20948, replace instances of "mpu9250" with "icm20948".
Restart Klipper via the `RESTART` command. Restart Klipper via the `RESTART` command.
@@ -697,24 +683,6 @@ 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 suggested shaper configuration is almost the same as what you got during the
previous calibration, this step can be skipped. previous calibration, this step can be skipped.
### Unreliable measurements of resonance frequencies
Sometimes the resonance measurements can produce bogus results, leading to
the incorrect suggestions for the input shapers. This can be caused by a
variety of reasons, including running fans on the toolhead, incorrect
position or non-rigid mounting of the accelerometer, or mechanical problems
such as loose belts or binding or bumpy axis. Keep in mind that all fans
should be disabled for resonance testing, especially the noisy ones, and
that the accelerometer should be rigidly mounted on the corresponding
moving part (e.g. on the bed itself for the bed slinger, or on the extruder
of the printer itself and not the carriage, and some people get better
results by mounting the accelerometer on the nozzle itself). As for
mechanical problems, the user should inspect if there is any fault that
can be fixed with a moving axis (e.g. linear guide rails cleaned up and
lubricated and V-slot wheels tension adjusted correctly). If none of that
helps, a user may try the other shapers from the produced list besides the
one recommended by default.
### Testing custom axes ### Testing custom axes
`TEST_RESONANCES` command supports custom axes. While this is not really `TEST_RESONANCES` command supports custom axes. While this is not really

View File

@@ -1,79 +0,0 @@
# OctoPrint for Klipper
Klipper has a few options for its front ends, Octoprint was the first
and original front end for Klipper. This document will give
a brief overview of installing with this option.
## Install with OctoPi
Start by installing [OctoPi](https://github.com/guysoft/OctoPi) on the
Raspberry Pi computer. Use OctoPi v0.17.0 or later - see the
[OctoPi releases](https://github.com/guysoft/OctoPi/releases) for
release information.
One should verify that OctoPi boots and that the
OctoPrint web server works. After connecting to the OctoPrint web
page, follow the prompt to upgrade OctoPrint if needed.
After installing OctoPi and upgrading OctoPrint, it will be necessary
to ssh into the target machine to run a handful of system commands.
Start by running these commands on your host device:
__If you do not have git installed, please do so with:__
```
sudo apt install git
```
then proceed:
```
cd ~
git clone https://github.com/Klipper3d/klipper
./klipper/scripts/install-octopi.sh
```
The above will download Klipper, install the needed system dependencies,
setup Klipper to run at system startup, and start the Klipper host
software. It will require an internet connection and it may take a few
minutes to complete.
## Installing with KIAUH
KIAUH can be used to install OctoPrint on a variety of Linux based systems
that run a form of Debian. More information can be found
at https://github.com/dw-0/kiauh
## Configuring OctoPrint to use Klipper
The OctoPrint web server needs to be configured to communicate with the Klipper
host software. Using a web browser, login to the OctoPrint web page and then
configure the following items:
Navigate to the Settings tab (the wrench icon at the top of the page).
Under "Serial Connection" in "Additional serial ports" add:
```
~/printer_data/comms/klippy.serial
```
Then click "Save".
_In some older setups this address may be `/tmp/printer`_
Enter the Settings tab again and under "Serial Connection" change the "Serial Port"
setting to the one added above.
In the Settings tab, navigate to the "Behavior" sub-tab and select the
"Cancel any ongoing prints but stay connected to the printer" option. Click "Save".
From the main page, under the "Connection" section (at the top left of the page)
make sure the "Serial Port" is set to the new additional one added
and click "Connect". (If it is not in the available selection then
try reloading the page.)
Once connected, navigate to the "Terminal" tab and type "status" (without the quotes)
into the command entry box and click "Send". The terminal window will likely report
there is an error opening the config file - that means OctoPrint is successfully
communicating with Klipper.
Please proceed to [Installation.md](Installation.md) and the
_Building and flashing the micro-controller_ section

View File

@@ -17,7 +17,6 @@ communication with the Klipper developers.
## Installation and Configuration ## Installation and Configuration
- [Installation](Installation.md): Guide to installing Klipper. - [Installation](Installation.md): Guide to installing Klipper.
- [Octoprint](OctoPrint.md): Guide to installing Octoprint with Klipper.
- [Config Reference](Config_Reference.md): Description of config - [Config Reference](Config_Reference.md): Description of config
parameters. parameters.
- [Rotation Distance](Rotation_Distance.md): Calculating the - [Rotation Distance](Rotation_Distance.md): Calculating the
@@ -101,4 +100,3 @@ communication with the Klipper developers.
- [TSL1401CL filament width sensor](TSL1401CL_Filament_Width_Sensor.md) - [TSL1401CL filament width sensor](TSL1401CL_Filament_Width_Sensor.md)
- [Hall filament width sensor](Hall_Filament_Width_Sensor.md) - [Hall filament width sensor](Hall_Filament_Width_Sensor.md)
- [Eddy Current Inductive probe](Eddy_Probe.md) - [Eddy Current Inductive probe](Eddy_Probe.md)
- [Load Cells](Load_Cell.md)

View File

@@ -22,7 +22,7 @@ Use a slicer to generate g-code for the large hollow square found in
[docs/prints/square_tower.stl](prints/square_tower.stl). Use a high [docs/prints/square_tower.stl](prints/square_tower.stl). Use a high
speed (eg, 100mm/s), zero infill, and a coarse layer height (the layer speed (eg, 100mm/s), zero infill, and a coarse layer height (the layer
height should be around 75% of the nozzle diameter). Make sure any height should be around 75% of the nozzle diameter). Make sure any
"dynamic acceleration control" and "scarf joint" seams are disabled in the slicer. "dynamic acceleration control" is disabled in the slicer.
Prepare for the test by issuing the following G-Code command: Prepare for the test by issuing the following G-Code command:
``` ```

View File

@@ -3,35 +3,6 @@
History of Klipper releases. Please see History of Klipper releases. Please see
[installation](Installation.md) for information on installing Klipper. [installation](Installation.md) for information on installing Klipper.
## Klipper 0.13.0
Available on 20250411. Major changes in this release:
* New "sweeping vibrations" resonance testing mechanism for input
shaper.
* Fans and GPIO pins can now be assigned a formula (via Jinja2
"templates").
* The bed_mesh code now supports "adaptive bed mesh". The area probed
can be adjusted for the size of the print.
* A new `minimum_cruise_ratio` kinematic parameter has been added (it
replaces the previous `max_accel_to_decel` parameter).
* Several new sensors added:
* Support for ldc1612 "eddy" current sensors. This includes probing
support, fast "scan" probing, and temperature calibration.
* New support for "load cell" measurements. Support for connecting
these load cells to hx71x and ads1220 ADC sensors.
* Support for BMP180, BMP388, and SHT3x temperature sensors. Support
for measuring temperature with ADS1x1x ADC chips.
* New lis3dh and icm20948 accelerometer support.
* Support for mt6816 and mt6826s "hall angle" sensors.
* New micro-controller improvements:
* New support for rp2350 micro-controllers.
* Existing rp2040 chips now run at 200MHz (up from 125Mhz).
* The micro-controller code can now define many more commands (up to
16384 from 128).
* Other modules added: aip31068_spi, canbus_stats, error_mcu,
garbage_collection, pwm_cycle_time, pwm_tool, garbage_collection.
* Several bug fixes and code cleanups.
## Klipper 0.12.0 ## Klipper 0.12.0
Available on 20231110. Major changes in this release: Available on 20231110. Major changes in this release:

View File

@@ -31,7 +31,7 @@ AD do not include the flats on the corners that some test objects provide.
## Configure your skew ## Configure your skew
Make sure `[skew_correction]` is in printer.cfg. You may now use the `SET_SKEW` Make sure `[skew_correction]` is in printer.cfg. You may now use the `SET_SKEW`
gcode to configure skew_correction. For example, if your measured lengths gcode to configure skew_correcton. For example, if your measured lengths
along XY are as follows: along XY are as follows:
``` ```

View File

@@ -121,5 +121,5 @@ M104 S0
before the macro call. Also note that SuperSlicer has a before the macro call. Also note that SuperSlicer has a
"custom gcode only" button option, which achieves the same outcome. "custom gcode only" button option, which achieves the same outcome.
An example of a START_PRINT macro using these parameters can An example of a START_PRINT macro using these paramaters can
be found in config/sample-macros.cfg be found in config/sample-macros.cfg

View File

@@ -17,6 +17,7 @@ serve the 3D printing community better. Follow them on
## Sponsors ## Sponsors
[<img src="./img/sponsors/obico-light-horizontal.png" width="200" style="margin:25px" />](https://obico.io/klipper.html?source=klipper_sponsor) [<img src="./img/sponsors/obico-light-horizontal.png" width="200" style="margin:25px" />](https://obico.io/klipper.html?source=klipper_sponsor)
[<img src="./img/sponsors/peopoly-logo.png" width="200" style="margin:25px" />](https://peopoly.net)
## Klipper Developers ## Klipper Developers

View File

@@ -31,7 +31,7 @@ The following information is available in the
## bed_screws ## bed_screws
The following information is available in the The following information is available in the
[bed_screws](Config_Reference.md#bed_screws) object: `Config_Reference.md#bed_screws` object:
- `is_active`: Returns True if the bed screws adjustment tool is currently - `is_active`: Returns True if the bed screws adjustment tool is currently
active. active.
- `state`: The bed screws adjustment tool state. It is one of - `state`: The bed screws adjustment tool state. It is one of
@@ -39,27 +39,6 @@ the following strings: "adjust", "fine".
- `current_screw`: The index for the current screw being adjusted. - `current_screw`: The index for the current screw being adjusted.
- `accepted_screws`: The number of accepted screws. - `accepted_screws`: The number of accepted screws.
## canbus_stats
The following information is available in the `canbus_stats
some_mcu_name` object (this object is automatically available if an
mcu is configured to use canbus):
- `rx_error`: The number of receive errors detected by the
micro-controller canbus hardware.
- `tx_error`: The number of transmit errors detected by the
micro-controller canbus hardware.
- `tx_retries`: The number of transmit attempts that were retried due
to bus contention or errors.
- `bus_state`: The status of the interface (typically "active" for a
bus in normal operation, "warn" for a bus with recent errors,
"passive" for a bus that will no longer transmit canbus error
frames, or "off" for a bus that will no longer transmit or receive
messages).
Note that only the rp2XXX micro-controllers report a non-zero
`tx_retries` field and the rp2XXX micro-controllers always report
`tx_error` as zero and `bus_state` as "active".
## configfile ## configfile
The following information is available in the `configfile` object The following information is available in the `configfile` object
@@ -242,8 +221,6 @@ The following information is available in the `gcode_move` object
The following information is available in the The following information is available in the
[hall_filament_width_sensor](Config_Reference.md#hall_filament_width_sensor) [hall_filament_width_sensor](Config_Reference.md#hall_filament_width_sensor)
object: object:
- all items from
[filament_switch_sensor](Status_Reference.md#filament_switch_sensor)
- `is_active`: Returns True if the sensor is currently active. - `is_active`: Returns True if the sensor is currently active.
- `Diameter`: The last reading from the sensor in mm. - `Diameter`: The last reading from the sensor in mm.
- `Raw`: The last raw ADC reading from the sensor. - `Raw`: The last raw ADC reading from the sensor.
@@ -279,6 +256,11 @@ object is available if any heater is defined):
e.g. `["tmc2240 stepper_x"]`. While a temperature sensor is always e.g. `["tmc2240 stepper_x"]`. While a temperature sensor is always
available to read, a temperature monitor may not be available and available to read, a temperature monitor may not be available and
will return null in such case. will return null in such case.
- `temperature_wait`: Indicates if G-Code processing is stalled
waiting for a requested temperature (typically via
`TEMPERATURE_WAIT`, `M109`, or `M190` commands). The value will
contain the name of the sensor that is causing the stall or `None`
if no wait is in progress.
## idle_timeout ## idle_timeout
@@ -291,9 +273,6 @@ is always available):
- `printing_time`: The amount of time (in seconds) the printer has - `printing_time`: The amount of time (in seconds) the printer has
been in the "Printing" state (as tracked by the idle_timeout been in the "Printing" state (as tracked by the idle_timeout
module). 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 ## led
@@ -303,31 +282,11 @@ The following information is available for each `[led led_name]`,
- `color_data`: A list of color lists containing the RGBW values for a - `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 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 1.0. Each color list contains 4 items (red, green, blue, white) even
if the underlying LED supports fewer color channels. For example, if the underyling LED supports fewer color channels. For example,
the blue value (3rd item in color list) of the second neopixel in a the blue value (3rd item in color list) of the second neopixel in a
chain could be accessed at chain could be accessed at
`printer["neopixel <config_name>"].color_data[1][2]`. `printer["neopixel <config_name>"].color_data[1][2]`.
## load_cell
The following information is available for each `[load_cell name]`:
- 'is_calibrated': True/False is the load cell calibrated
- 'counts_per_gram': The number of raw sensor counts that equals 1 gram of force
- 'reference_tare_counts': The reference number of raw sensor counts for 0 force
- 'tare_counts': The current number of raw sensor counts for 0 force
- 'force_g': The force in grams, averaged over the last polling period.
- 'min_force_g': The minimum force in grams, over the last polling period.
- 'max_force_g': The maximum force in grams, over the last polling period.
## load_cell_probe
The following information is available for `[load_cell_probe]`:
- all items from [load_cell](Status_Reference.md#load_cell)
- all items from [probe](Status_Reference.md#probe)
- 'endstop_tare_counts': the load cell probe keeps a tare value independent of
the load cell. This re-set at the start of each probe.
- 'last_trigger_time': timestamp of the last homing trigger
## manual_probe ## manual_probe
The following information is available in the The following information is available in the
@@ -472,12 +431,6 @@ The following information is available in
- `printer["servo <config_name>"].value`: The last setting of the PWM - `printer["servo <config_name>"].value`: The last setting of the PWM
pin (a value between 0.0 and 1.0) associated with the servo. pin (a value between 0.0 and 1.0) associated with the servo.
## skew_correction.py
The following information is available in the `skew_correction` object (this
object is available if any skew_correction is defined):
- `current_profile_name`: Returns the name of the currently loaded SKEW_PROFILE.
## stepper_enable ## stepper_enable
The following information is available in the `stepper_enable` object (this The following information is available in the `stepper_enable` object (this
@@ -582,12 +535,6 @@ on a cartesian, hybrid_corexy or hybrid_corexz robot
- `carriage_1`: The mode of the carriage 1. Possible values are: - `carriage_1`: The mode of the carriage 1. Possible values are:
"INACTIVE", "PRIMARY", "COPY", and "MIRROR". "INACTIVE", "PRIMARY", "COPY", and "MIRROR".
On a `generic_cartesian` kinematic, the following information is
available in `dual_carriage`:
- `carriages["<carriage>"]`: The mode of the carriage `<carriage>`. Possible
values are "INACTIVE" and "PRIMARY" for the primary carriage and "INACTIVE",
"PRIMARY", "COPY", and "MIRROR" for the dual carriage.
## virtual_sdcard ## virtual_sdcard
The following information is available in the The following information is available in the

View File

@@ -83,10 +83,6 @@ setting `stealthchop_threshold` to 999999). Unfortunately, the drivers
often produce poor and confusing results if the mode changes while the often produce poor and confusing results if the mode changes while the
motor is at a non-zero velocity. motor is at a non-zero velocity.
Note that the `stealthchop_threshold` config option does not impact
sensorless homing as Klipper automatically switches the TMC driver to
an appropriate mode during sensorless homing operations.
## TMC interpolate setting introduces small position deviation ## TMC interpolate setting introduces small position deviation
The TMC driver `interpolate` setting may reduce the audible noise of The TMC driver `interpolate` setting may reduce the audible noise of

View File

@@ -8,13 +8,13 @@ directory, the docs/CNAME file also controls the website generation.
To test deploy the main English site locally one can use commands To test deploy the main English site locally one can use commands
similar to the following: similar to the following:
virtualenv ~/mkdocs-env && ~/mkdocs-env/bin/pip install -r ~/klipper/docs/_klipper3d/mkdocs-requirements.txt virtualenv ~/mkdocs-env && ~/python-env/bin/pip install -r ~/klipper/docs/_klipper3d/mkdocs-requirements.txt
cd ~/klipper && ~/mkdocs-env/bin/mkdocs serve --config-file ~/klipper/docs/_klipper3d/mkdocs.yml -a 0.0.0.0:8000 cd ~/klipper && ~/mkdocs-env/bin/mkdocs serve --config-file ~/klipper/docs/_klipper3d/mkdocs.yml -a 0.0.0.0:8000
To test deploy the multi-language site locally one can use commands To test deploy the multi-language site locally one can use commands
similar to the following: similar to the following:
virtualenv ~/mkdocs-env && ~/mkdocs-env/bin/pip install -r ~/klipper/docs/_klipper3d/mkdocs-requirements.txt virtualenv ~/mkdocs-env && ~/python-env/bin/pip install -r ~/klipper/docs/_klipper3d/mkdocs-requirements.txt
source ~/mkdocs-env/bin/activate source ~/mkdocs-env/bin/activate
cd ~/klipper && ./docs/_klipper3d/build-translations.sh cd ~/klipper && ./docs/_klipper3d/build-translations.sh
cd ~/klipper/site/ && python3 -m http.server 8000 cd ~/klipper/site/ && python3 -m http.server 8000

View File

@@ -1,5 +1,5 @@
# Python virtualenv module requirements for mkdocs # Python virtualenv module requirements for mkdocs
jinja2==3.1.6 jinja2==3.1.4
mkdocs==1.2.4 mkdocs==1.2.4
mkdocs-material==8.1.3 mkdocs-material==8.1.3
mkdocs-simple-hooks==0.1.3 mkdocs-simple-hooks==0.1.3

View File

@@ -88,9 +88,7 @@ nav:
- Config_Changes.md - Config_Changes.md
- Contact.md - Contact.md
- Installation and Configuration: - Installation and Configuration:
- Installation: - Installation.md
- Installation.md
- OctoPrint.md
- Configuration Reference: - Configuration Reference:
- Config_Reference.md - Config_Reference.md
- Rotation_Distance.md - Rotation_Distance.md
@@ -141,5 +139,4 @@ nav:
- TSL1401CL_Filament_Width_Sensor.md - TSL1401CL_Filament_Width_Sensor.md
- Hall_Filament_Width_Sensor.md - Hall_Filament_Width_Sensor.md
- Eddy_Probe.md - Eddy_Probe.md
- Load_Cell.md
- Sponsors.md - Sponsors.md

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -6,14 +6,13 @@ title: Welcome
![](img/klipper-logo.png){ .center-image } ![](img/klipper-logo.png){ .center-image }
The Klipper firmware controls 3d-Printers. It combines the power of a Klipper is a 3d-Printer firmware. It combines the power of a general
general purpose computer with one or more micro-controllers. See the purpose computer with one or more micro-controllers. See the
[features document](Features.md) for more information on why you [features](Features.md) document for more information on why you
should use the Klipper software. should use Klipper.
Start by [installing Klipper software](Installation.md). To begin using Klipper start by [installing](Installation.md) it.
Klipper software is Free Software. Read the Klipper is Free Software. Read the [documentation](Overview.md) or
[documentation](Overview.md), see the [license](../COPYING), or view [the Klipper code on github](https://github.com/Klipper3d/klipper).
[download](https://github.com/Klipper3d/Klipper) the software. We We depend on the generous support from our [sponsors](Sponsors.md).
depend on the generous support from our [sponsors](Sponsors.md).

View File

@@ -17,16 +17,16 @@ COMPILE_ARGS = ("-Wall -g -O2 -shared -fPIC"
" -o %s %s") " -o %s %s")
SSE_FLAGS = "-mfpmath=sse -msse2" SSE_FLAGS = "-mfpmath=sse -msse2"
SOURCE_FILES = [ SOURCE_FILES = [
'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'steppersync.c', 'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'itersolve.c', 'trapq.c',
'itersolve.c', 'trapq.c', 'pollreactor.c', 'msgblock.c', 'trdispatch.c', 'pollreactor.c', 'msgblock.c', 'trdispatch.c',
'kin_cartesian.c', 'kin_corexy.c', 'kin_corexz.c', 'kin_delta.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_deltesian.c', 'kin_polar.c', 'kin_rotary_delta.c', 'kin_winch.c',
'kin_extruder.c', 'kin_shaper.c', 'kin_idex.c', 'kin_generic.c' 'kin_extruder.c', 'kin_shaper.c', 'kin_idex.c',
] ]
DEST_LIB = "c_helper.so" DEST_LIB = "c_helper.so"
OTHER_FILES = [ OTHER_FILES = [
'list.h', 'serialqueue.h', 'stepcompress.h', 'steppersync.h', 'list.h', 'serialqueue.h', 'stepcompress.h', 'itersolve.h', 'pyhelper.h',
'itersolve.h', 'pyhelper.h', 'trapq.h', 'pollreactor.h', 'msgblock.h' 'trapq.h', 'pollreactor.h', 'msgblock.h'
] ]
defs_stepcompress = """ defs_stepcompress = """
@@ -54,28 +54,25 @@ defs_stepcompress = """
int stepcompress_extract_old(struct stepcompress *sc int stepcompress_extract_old(struct stepcompress *sc
, struct pull_history_steps *p, int max , struct pull_history_steps *p, int max
, uint64_t start_clock, uint64_t end_clock); , uint64_t start_clock, uint64_t end_clock);
void stepcompress_set_stepper_kinematics(struct stepcompress *sc
, struct stepper_kinematics *sk);
"""
defs_steppersync = """
struct steppersync *steppersync_alloc(struct serialqueue *sq struct steppersync *steppersync_alloc(struct serialqueue *sq
, struct stepcompress **sc_list, int sc_num, int move_num); , struct stepcompress **sc_list, int sc_num, int move_num);
void steppersync_free(struct steppersync *ss); void steppersync_free(struct steppersync *ss);
void steppersync_set_time(struct steppersync *ss void steppersync_set_time(struct steppersync *ss
, double time_offset, double mcu_freq); , double time_offset, double mcu_freq);
int32_t steppersync_generate_steps(struct steppersync *ss int steppersync_flush(struct steppersync *ss, uint64_t move_clock
, double gen_steps_time, uint64_t flush_clock); , uint64_t clear_history_clock);
void steppersync_history_expire(struct steppersync *ss, uint64_t end_clock);
int steppersync_flush(struct steppersync *ss, uint64_t move_clock);
""" """
defs_itersolve = """ defs_itersolve = """
int32_t itersolve_generate_steps(struct stepper_kinematics *sk
, double flush_time);
double itersolve_check_active(struct stepper_kinematics *sk double itersolve_check_active(struct stepper_kinematics *sk
, double flush_time); , double flush_time);
int32_t itersolve_is_active_axis(struct stepper_kinematics *sk, char axis); 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_trapq(struct stepper_kinematics *sk, struct trapq *tq);
, double step_dist); void itersolve_set_stepcompress(struct stepper_kinematics *sk
, struct stepcompress *sc, double step_dist);
double itersolve_calc_position_from_coord(struct stepper_kinematics *sk double itersolve_calc_position_from_coord(struct stepper_kinematics *sk
, double x, double y, double z); , double x, double y, double z);
void itersolve_set_position(struct stepper_kinematics *sk void itersolve_set_position(struct stepper_kinematics *sk
@@ -109,12 +106,6 @@ defs_trapq = """
defs_kin_cartesian = """ defs_kin_cartesian = """
struct stepper_kinematics *cartesian_stepper_alloc(char axis); struct stepper_kinematics *cartesian_stepper_alloc(char axis);
""" """
defs_kin_generic_cartesian = """
struct stepper_kinematics *generic_cartesian_stepper_alloc(double a_x
, double a_y, double a_z);
void generic_cartesian_stepper_set_coeffs(struct stepper_kinematics *sk
, double a_x, double a_y, double a_z);
"""
defs_kin_corexy = """ defs_kin_corexy = """
struct stepper_kinematics *corexy_stepper_alloc(char type); struct stepper_kinematics *corexy_stepper_alloc(char type);
@@ -163,7 +154,6 @@ defs_kin_shaper = """
, int n, double a[], double t[]); , int n, double a[], double t[]);
int input_shaper_set_sk(struct stepper_kinematics *sk int input_shaper_set_sk(struct stepper_kinematics *sk
, struct stepper_kinematics *orig_sk); , struct stepper_kinematics *orig_sk);
void input_shaper_update_sk(struct stepper_kinematics *sk);
struct stepper_kinematics * input_shaper_alloc(void); struct stepper_kinematics * input_shaper_alloc(void);
""" """
@@ -185,7 +175,7 @@ defs_serialqueue = """
}; };
struct serialqueue *serialqueue_alloc(int serial_fd, char serial_fd_type struct serialqueue *serialqueue_alloc(int serial_fd, char serial_fd_type
, int client_id, char name[16]); , int client_id);
void serialqueue_exit(struct serialqueue *sq); void serialqueue_exit(struct serialqueue *sq);
void serialqueue_free(struct serialqueue *sq); void serialqueue_free(struct serialqueue *sq);
struct command_queue *serialqueue_alloc_commandqueue(void); struct command_queue *serialqueue_alloc_commandqueue(void);
@@ -222,7 +212,6 @@ defs_trdispatch = """
defs_pyhelper = """ defs_pyhelper = """
void set_python_logging_callback(void (*func)(const char *)); void set_python_logging_callback(void (*func)(const char *));
double get_monotonic(void); double get_monotonic(void);
int set_thread_name(char name[16]);
""" """
defs_std = """ defs_std = """
@@ -231,11 +220,10 @@ defs_std = """
defs_all = [ defs_all = [
defs_pyhelper, defs_serialqueue, defs_std, defs_stepcompress, defs_pyhelper, defs_serialqueue, defs_std, defs_stepcompress,
defs_steppersync, defs_itersolve, defs_trapq, defs_trdispatch, defs_itersolve, defs_trapq, defs_trdispatch,
defs_kin_cartesian, defs_kin_corexy, defs_kin_corexz, defs_kin_delta, 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_deltesian, defs_kin_polar, defs_kin_rotary_delta, defs_kin_winch,
defs_kin_extruder, defs_kin_shaper, defs_kin_idex, defs_kin_extruder, defs_kin_shaper, defs_kin_idex,
defs_kin_generic_cartesian,
] ]
# Update filenames to an absolute path # Update filenames to an absolute path
@@ -274,33 +262,11 @@ def do_build_code(cmd):
logging.error(msg) logging.error(msg)
raise Exception(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_main = None
FFI_lib = None FFI_lib = None
pyhelper_logging_callback = None pyhelper_logging_callback = None
# Helper invoked from C errorf() code to log errors # Hepler invoked from C errorf() code to log errors
def logging_callback(msg): def logging_callback(msg):
logging.error(FFI_main.string(msg)) logging.error(FFI_main.string(msg))
@@ -308,9 +274,17 @@ def logging_callback(msg):
def get_ffi(): def get_ffi():
global FFI_main, FFI_lib, pyhelper_logging_callback global FFI_main, FFI_lib, pyhelper_logging_callback
if FFI_lib is None: if FFI_lib is None:
# Check if library needs to be built, and build if so srcdir = os.path.dirname(os.path.realpath(__file__))
destlib = check_build_c_library() srcfiles = get_abs_files(srcdir, SOURCE_FILES)
# Open library 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)))
FFI_main = cffi.FFI() FFI_main = cffi.FFI()
for d in defs_all: for d in defs_all:
FFI_main.cdef(d) FFI_main.cdef(d)

View File

@@ -26,8 +26,8 @@ struct timepos {
// Generate step times for a portion of a move // Generate step times for a portion of a move
static int32_t static int32_t
itersolve_gen_steps_range(struct stepper_kinematics *sk, struct stepcompress *sc itersolve_gen_steps_range(struct stepper_kinematics *sk, struct move *m
, struct move *m, double abs_start, double abs_end) , double abs_start, double abs_end)
{ {
sk_calc_callback calc_position_cb = sk->calc_position_cb; sk_calc_callback calc_position_cb = sk->calc_position_cb;
double half_step = .5 * sk->step_dist; double half_step = .5 * sk->step_dist;
@@ -37,7 +37,7 @@ itersolve_gen_steps_range(struct stepper_kinematics *sk, struct stepcompress *sc
if (end > m->move_t) if (end > m->move_t)
end = m->move_t; end = m->move_t;
struct timepos old_guess = {start, sk->commanded_pos}, guess = old_guess; struct timepos old_guess = {start, sk->commanded_pos}, guess = old_guess;
int sdir = stepcompress_get_step_dir(sc); int sdir = stepcompress_get_step_dir(sk->sc);
int is_dir_change = 0, have_bracket = 0, check_oscillate = 0; int is_dir_change = 0, have_bracket = 0, check_oscillate = 0;
double target = sk->commanded_pos + (sdir ? half_step : -half_step); double target = sk->commanded_pos + (sdir ? half_step : -half_step);
double last_time=start, low_time=start, high_time=start + SEEK_TIME_RESET; 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 stepcompress *sc
if (!have_bracket || high_time - low_time > .000000001) { if (!have_bracket || high_time - low_time > .000000001) {
if (!is_dir_change && rel_dist >= -half_step) if (!is_dir_change && rel_dist >= -half_step)
// Avoid rollback if stepper fully reaches step position // Avoid rollback if stepper fully reaches step position
stepcompress_commit(sc); stepcompress_commit(sk->sc);
// Guess is not close enough - guess again with new time // Guess is not close enough - guess again with new time
continue; continue;
} }
} }
// Found next step - submit it // Found next step - submit it
int ret = stepcompress_append(sc, sdir, m->print_time, guess.time); int ret = stepcompress_append(sk->sc, sdir, m->print_time, guess.time);
if (ret) if (ret)
return ret; return ret;
target = sdir ? target+half_step+half_step : target-half_step-half_step; target = sdir ? target+half_step+half_step : target-half_step-half_step;
@@ -143,9 +143,8 @@ check_active(struct stepper_kinematics *sk, struct move *m)
} }
// Generate step times for a range of moves on the trapq // Generate step times for a range of moves on the trapq
int32_t int32_t __visible
itersolve_generate_steps(struct stepper_kinematics *sk, struct stepcompress *sc itersolve_generate_steps(struct stepper_kinematics *sk, double flush_time)
, double flush_time)
{ {
double last_flush_time = sk->last_flush_time; double last_flush_time = sk->last_flush_time;
sk->last_flush_time = flush_time; sk->last_flush_time = flush_time;
@@ -171,15 +170,15 @@ itersolve_generate_steps(struct stepper_kinematics *sk, struct stepcompress *sc
while (--skip_count && pm->print_time > abs_start) while (--skip_count && pm->print_time > abs_start)
pm = list_prev_entry(pm, node); pm = list_prev_entry(pm, node);
do { do {
int32_t ret = itersolve_gen_steps_range( int32_t ret = itersolve_gen_steps_range(sk, pm, abs_start
sk, sc, pm, abs_start, flush_time); , flush_time);
if (ret) if (ret)
return ret; return ret;
pm = list_next_entry(pm, node); pm = list_next_entry(pm, node);
} while (pm != m); } while (pm != m);
} }
// Generate steps for this move // Generate steps for this move
int32_t ret = itersolve_gen_steps_range(sk, sc, m, last_flush_time int32_t ret = itersolve_gen_steps_range(sk, m, last_flush_time
, flush_time); , flush_time);
if (ret) if (ret)
return ret; return ret;
@@ -196,8 +195,8 @@ itersolve_generate_steps(struct stepper_kinematics *sk, struct stepcompress *sc
double abs_end = force_steps_time; double abs_end = force_steps_time;
if (abs_end > flush_time) if (abs_end > flush_time)
abs_end = flush_time; abs_end = flush_time;
int32_t ret = itersolve_gen_steps_range( int32_t ret = itersolve_gen_steps_range(sk, m, last_flush_time
sk, sc, m, last_flush_time, abs_end); , abs_end);
if (ret) if (ret)
return ret; return ret;
skip_count = 1; skip_count = 1;
@@ -241,10 +240,16 @@ itersolve_is_active_axis(struct stepper_kinematics *sk, char axis)
} }
void __visible 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->tq = tq;
}
void __visible
itersolve_set_stepcompress(struct stepper_kinematics *sk
, struct stepcompress *sc, double step_dist)
{
sk->sc = sc;
sk->step_dist = step_dist; sk->step_dist = step_dist;
} }

View File

@@ -26,11 +26,12 @@ struct stepper_kinematics {
}; };
int32_t itersolve_generate_steps(struct stepper_kinematics *sk int32_t itersolve_generate_steps(struct stepper_kinematics *sk
, struct stepcompress *sc, double flush_time); , double flush_time);
double itersolve_check_active(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); 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_trapq(struct stepper_kinematics *sk, struct trapq *tq);
, double step_dist); void itersolve_set_stepcompress(struct stepper_kinematics *sk
, struct stepcompress *sc, double step_dist);
double itersolve_calc_position_from_coord(struct stepper_kinematics *sk double itersolve_calc_position_from_coord(struct stepper_kinematics *sk
, double x, double y, double z); , double x, double y, double z);
void itersolve_set_position(struct stepper_kinematics *sk void itersolve_set_position(struct stepper_kinematics *sk

View File

@@ -1,52 +0,0 @@
// Generic cartesian kinematics stepper position calculation
//
// Copyright (C) 2024 Dmitry Butyugin <dmbutyugin@google.com>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <stddef.h> // offsetof
#include <stdlib.h> // malloc
#include <string.h> // memset
#include "compiler.h" // __visible
#include "itersolve.h" // struct stepper_kinematics
#include "trapq.h" // move_get_coord
struct generic_cartesian_stepper {
struct stepper_kinematics sk;
struct coord a;
};
static double
generic_cartesian_stepper_calc_position(struct stepper_kinematics *sk
, struct move *m, double move_time)
{
struct generic_cartesian_stepper *cs = container_of(
sk, struct generic_cartesian_stepper, sk);
struct coord c = move_get_coord(m, move_time);
return cs->a.x * c.x + cs->a.y * c.y + cs->a.z * c.z;
}
void __visible
generic_cartesian_stepper_set_coeffs(struct stepper_kinematics *sk
, double a_x, double a_y, double a_z)
{
struct generic_cartesian_stepper *cs = container_of(
sk, struct generic_cartesian_stepper, sk);
cs->a.x = a_x;
cs->a.y = a_y;
cs->a.z = a_z;
cs->sk.active_flags = 0;
if (a_x) cs->sk.active_flags |= AF_X;
if (a_y) cs->sk.active_flags |= AF_Y;
if (a_z) cs->sk.active_flags |= AF_Z;
}
struct stepper_kinematics * __visible
generic_cartesian_stepper_alloc(double a_x, double a_y, double a_z)
{
struct generic_cartesian_stepper *cs = malloc(sizeof(*cs));
memset(cs, 0, sizeof(*cs));
cs->sk.calc_position_cb = generic_cartesian_stepper_calc_position;
generic_cartesian_stepper_set_coeffs(&cs->sk, a_x, a_y, a_z);
return &cs->sk;
}

View File

@@ -77,6 +77,5 @@ dual_carriage_alloc(void)
struct dual_carriage_stepper *dc = malloc(sizeof(*dc)); struct dual_carriage_stepper *dc = malloc(sizeof(*dc));
memset(dc, 0, sizeof(*dc)); memset(dc, 0, sizeof(*dc));
dc->m.move_t = 2. * DUMMY_T; dc->m.move_t = 2. * DUMMY_T;
dc->x_scale = dc->y_scale = 1.0;
return &dc->sk; return &dc->sk;
} }

View File

@@ -156,48 +156,6 @@ shaper_xy_calc_position(struct stepper_kinematics *sk, struct move *m
return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T); return is->orig_sk->calc_position_cb(is->orig_sk, &is->m, DUMMY_T);
} }
// A callback that forwards post_cb call to the original kinematics
static void
shaper_commanded_pos_post_fixup(struct stepper_kinematics *sk)
{
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
is->orig_sk->commanded_pos = sk->commanded_pos;
is->orig_sk->post_cb(is->orig_sk);
sk->commanded_pos = is->orig_sk->commanded_pos;
}
static void
shaper_note_generation_time(struct input_shaper *is)
{
double pre_active = 0., post_active = 0.;
if ((is->sk.active_flags & AF_X) && is->sx.num_pulses) {
pre_active = is->sx.pulses[is->sx.num_pulses-1].t;
post_active = -is->sx.pulses[0].t;
}
if ((is->sk.active_flags & AF_Y) && is->sy.num_pulses) {
pre_active = is->sy.pulses[is->sy.num_pulses-1].t > pre_active
? is->sy.pulses[is->sy.num_pulses-1].t : pre_active;
post_active = -is->sy.pulses[0].t > post_active
? -is->sy.pulses[0].t : post_active;
}
is->sk.gen_steps_pre_active = pre_active;
is->sk.gen_steps_post_active = post_active;
}
void __visible
input_shaper_update_sk(struct stepper_kinematics *sk)
{
struct input_shaper *is = container_of(sk, struct input_shaper, sk);
if ((is->orig_sk->active_flags & (AF_X | AF_Y)) == (AF_X | AF_Y))
is->sk.calc_position_cb = shaper_xy_calc_position;
else if (is->orig_sk->active_flags & AF_X)
is->sk.calc_position_cb = shaper_x_calc_position;
else if (is->orig_sk->active_flags & AF_Y)
is->sk.calc_position_cb = shaper_y_calc_position;
is->sk.active_flags = is->orig_sk->active_flags;
shaper_note_generation_time(is);
}
int __visible int __visible
input_shaper_set_sk(struct stepper_kinematics *sk input_shaper_set_sk(struct stepper_kinematics *sk
, struct stepper_kinematics *orig_sk) , struct stepper_kinematics *orig_sk)
@@ -216,12 +174,27 @@ input_shaper_set_sk(struct stepper_kinematics *sk
is->sk.commanded_pos = orig_sk->commanded_pos; is->sk.commanded_pos = orig_sk->commanded_pos;
is->sk.last_flush_time = orig_sk->last_flush_time; is->sk.last_flush_time = orig_sk->last_flush_time;
is->sk.last_move_time = orig_sk->last_move_time; is->sk.last_move_time = orig_sk->last_move_time;
if (orig_sk->post_cb) {
is->sk.post_cb = shaper_commanded_pos_post_fixup;
}
return 0; return 0;
} }
static void
shaper_note_generation_time(struct input_shaper *is)
{
double pre_active = 0., post_active = 0.;
if ((is->sk.active_flags & AF_X) && is->sx.num_pulses) {
pre_active = is->sx.pulses[is->sx.num_pulses-1].t;
post_active = -is->sx.pulses[0].t;
}
if ((is->sk.active_flags & AF_Y) && is->sy.num_pulses) {
pre_active = is->sy.pulses[is->sy.num_pulses-1].t > pre_active
? is->sy.pulses[is->sy.num_pulses-1].t : pre_active;
post_active = -is->sy.pulses[0].t > post_active
? -is->sy.pulses[0].t : post_active;
}
is->sk.gen_steps_pre_active = pre_active;
is->sk.gen_steps_post_active = post_active;
}
int __visible int __visible
input_shaper_set_shaper_params(struct stepper_kinematics *sk, char axis input_shaper_set_shaper_params(struct stepper_kinematics *sk, char axis
, int n, double a[], double t[]) , int n, double a[], double t[])

View File

@@ -10,8 +10,6 @@
#include <stdio.h> // fprintf #include <stdio.h> // fprintf
#include <string.h> // strerror #include <string.h> // strerror
#include <time.h> // struct timespec #include <time.h> // struct timespec
#include <linux/prctl.h> // PR_SET_NAME
#include <sys/prctl.h> // prctl
#include "compiler.h" // __visible #include "compiler.h" // __visible
#include "pyhelper.h" // get_monotonic #include "pyhelper.h" // get_monotonic
@@ -94,10 +92,3 @@ dump_string(char *outbuf, int outbuf_size, char *inbuf, int inbuf_size)
*o = '\0'; *o = '\0';
return outbuf; return outbuf;
} }
// Set custom thread names
int __visible
set_thread_name(char name[16])
{
return prctl(PR_SET_NAME, name);
}

View File

@@ -7,6 +7,5 @@ void set_python_logging_callback(void (*func)(const char *));
void errorf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); void errorf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
void report_errno(char *where, int rc); void report_errno(char *where, int rc);
char *dump_string(char *outbuf, int outbuf_size, char *inbuf, int inbuf_size); char *dump_string(char *outbuf, int outbuf_size, char *inbuf, int inbuf_size);
int set_thread_name(char name[16]);
#endif // pyhelper.h #endif // pyhelper.h

View File

@@ -43,7 +43,6 @@ struct serialqueue {
uint8_t need_sync; uint8_t need_sync;
int input_pos; int input_pos;
// Threading // Threading
char name[16];
pthread_t tid; pthread_t tid;
pthread_mutex_t lock; // protects variables below pthread_mutex_t lock; // protects variables below
pthread_cond_t cond; pthread_cond_t cond;
@@ -613,7 +612,6 @@ static void *
background_thread(void *data) background_thread(void *data)
{ {
struct serialqueue *sq = data; struct serialqueue *sq = data;
set_thread_name(sq->name);
pollreactor_run(sq->pr); pollreactor_run(sq->pr);
pthread_mutex_lock(&sq->lock); pthread_mutex_lock(&sq->lock);
@@ -625,16 +623,13 @@ background_thread(void *data)
// Create a new 'struct serialqueue' object // Create a new 'struct serialqueue' object
struct serialqueue * __visible 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)); struct serialqueue *sq = malloc(sizeof(*sq));
memset(sq, 0, sizeof(*sq)); memset(sq, 0, sizeof(*sq));
sq->serial_fd = serial_fd; sq->serial_fd = serial_fd;
sq->serial_fd_type = serial_fd_type; sq->serial_fd_type = serial_fd_type;
sq->client_id = client_id; 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->pipe_fds);
if (ret) if (ret)

View File

@@ -27,7 +27,7 @@ struct pull_queue_message {
struct serialqueue; struct serialqueue;
struct serialqueue *serialqueue_alloc(int serial_fd, char serial_fd_type struct serialqueue *serialqueue_alloc(int serial_fd, char serial_fd_type
, int client_id, char name[16]); , int client_id);
void serialqueue_exit(struct serialqueue *sq); void serialqueue_exit(struct serialqueue *sq);
void serialqueue_free(struct serialqueue *sq); void serialqueue_free(struct serialqueue *sq);
struct command_queue *serialqueue_alloc_commandqueue(void); struct command_queue *serialqueue_alloc_commandqueue(void);

View File

@@ -1,6 +1,6 @@
// Stepper pulse schedule compression // Stepper pulse schedule compression
// //
// Copyright (C) 2016-2025 Kevin O'Connor <kevin@koconnor.net> // Copyright (C) 2016-2021 Kevin O'Connor <kevin@koconnor.net>
// //
// This file may be distributed under the terms of the GNU GPLv3 license. // This file may be distributed under the terms of the GNU GPLv3 license.
@@ -21,7 +21,6 @@
#include <stdlib.h> // malloc #include <stdlib.h> // malloc
#include <string.h> // memset #include <string.h> // memset
#include "compiler.h" // DIV_ROUND_UP #include "compiler.h" // DIV_ROUND_UP
#include "itersolve.h" // itersolve_generate_steps
#include "pyhelper.h" // errorf #include "pyhelper.h" // errorf
#include "serialqueue.h" // struct queue_message #include "serialqueue.h" // struct queue_message
#include "stepcompress.h" // stepcompress_alloc #include "stepcompress.h" // stepcompress_alloc
@@ -47,8 +46,6 @@ struct stepcompress {
// History tracking // History tracking
int64_t last_position; int64_t last_position;
struct list_head history_list; struct list_head history_list;
// Itersolve reference
struct stepper_kinematics *sk;
}; };
struct step_move { struct step_move {
@@ -279,9 +276,9 @@ stepcompress_set_invert_sdir(struct stepcompress *sc, uint32_t invert_sdir)
} }
} }
// Expire the stepcompress history older than the given clock // Helper to free items from the history_list
void static void
stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock) free_history(struct stepcompress *sc, uint64_t end_clock)
{ {
while (!list_empty(&sc->history_list)) { while (!list_empty(&sc->history_list)) {
struct history_steps *hs = list_last_entry( struct history_steps *hs = list_last_entry(
@@ -293,6 +290,13 @@ stepcompress_history_expire(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 // Free memory associated with a 'stepcompress' object
void __visible void __visible
stepcompress_free(struct stepcompress *sc) stepcompress_free(struct stepcompress *sc)
@@ -301,7 +305,7 @@ stepcompress_free(struct stepcompress *sc)
return; return;
free(sc->queue); free(sc->queue);
message_queue_free(&sc->msg_queue); message_queue_free(&sc->msg_queue);
stepcompress_history_expire(sc, UINT64_MAX); free_history(sc, UINT64_MAX);
free(sc); free(sc);
} }
@@ -317,12 +321,6 @@ stepcompress_get_step_dir(struct stepcompress *sc)
return sc->next_step_dir; return sc->next_step_dir;
} }
struct list_head *
stepcompress_get_msg_queue(struct stepcompress *sc)
{
return &sc->msg_queue;
}
// Determine the "print time" of the last_step_clock // Determine the "print time" of the last_step_clock
static void static void
calc_last_step_print_time(struct stepcompress *sc) calc_last_step_print_time(struct stepcompress *sc)
@@ -332,7 +330,7 @@ calc_last_step_print_time(struct stepcompress *sc)
} }
// Set the conversion rate of 'print_time' to mcu clock // Set the conversion rate of 'print_time' to mcu clock
void static void
stepcompress_set_time(struct stepcompress *sc stepcompress_set_time(struct stepcompress *sc
, double time_offset, double mcu_freq) , double time_offset, double mcu_freq)
{ {
@@ -666,25 +664,164 @@ stepcompress_extract_old(struct stepcompress *sc, struct pull_history_steps *p
return res; return res;
} }
// Store a reference to stepper_kinematics
void __visible /****************************************************************
stepcompress_set_stepper_kinematics(struct stepcompress *sc * Step compress synchronization
, struct stepper_kinematics *sk) ****************************************************************/
// 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)
{ {
sc->sk = sk; 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;
} }
// Generate steps (via itersolve) and flush // Free memory associated with a 'steppersync' object
int32_t void __visible
stepcompress_generate_steps(struct stepcompress *sc, double gen_steps_time steppersync_free(struct steppersync *ss)
, uint64_t flush_clock)
{ {
if (!sc->sk) if (!ss)
return 0; return;
// Generate steps free(ss->sc_list);
int32_t ret = itersolve_generate_steps(sc->sk, sc, gen_steps_time); free(ss->move_clocks);
if (ret) serialqueue_free_commandqueue(ss->cq);
return ret; free(ss);
// Flush steps }
return stepcompress_flush(sc, flush_clock);
// 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;
} }

View File

@@ -17,13 +17,9 @@ void stepcompress_fill(struct stepcompress *sc, uint32_t max_error
, int32_t set_next_step_dir_msgtag); , int32_t set_next_step_dir_msgtag);
void stepcompress_set_invert_sdir(struct stepcompress *sc void stepcompress_set_invert_sdir(struct stepcompress *sc
, uint32_t invert_sdir); , uint32_t invert_sdir);
void stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock);
void stepcompress_free(struct stepcompress *sc); void stepcompress_free(struct stepcompress *sc);
uint32_t stepcompress_get_oid(struct stepcompress *sc); uint32_t stepcompress_get_oid(struct stepcompress *sc);
int stepcompress_get_step_dir(struct stepcompress *sc); int stepcompress_get_step_dir(struct stepcompress *sc);
struct list_head *stepcompress_get_msg_queue(struct stepcompress *sc);
void stepcompress_set_time(struct stepcompress *sc
, double time_offset, double mcu_freq);
int stepcompress_append(struct stepcompress *sc, int sdir int stepcompress_append(struct stepcompress *sc, int sdir
, double print_time, double step_time); , double print_time, double step_time);
int stepcompress_commit(struct stepcompress *sc); int stepcompress_commit(struct stepcompress *sc);
@@ -38,11 +34,15 @@ int stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock
int stepcompress_extract_old(struct stepcompress *sc int stepcompress_extract_old(struct stepcompress *sc
, struct pull_history_steps *p, int max , struct pull_history_steps *p, int max
, uint64_t start_clock, uint64_t end_clock); , uint64_t start_clock, uint64_t end_clock);
struct stepper_kinematics;
void stepcompress_set_stepper_kinematics(struct stepcompress *sc struct serialqueue;
, struct stepper_kinematics *sk); struct steppersync *steppersync_alloc(
int32_t stepcompress_generate_steps(struct stepcompress *sc struct serialqueue *sq, struct stepcompress **sc_list, int sc_num
, double gen_steps_time , int move_num);
, uint64_t flush_clock); 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 #endif // stepcompress.h

View File

@@ -1,177 +0,0 @@
// Stepper step transmit synchronization
//
// Copyright (C) 2016-2025 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
// The steppersync object is used to synchronize the output of mcu
// step commands. The mcu can only queue a limited number of step
// commands - this code tracks when items on the mcu step queue become
// free so that new commands can be transmitted. It also ensures the
// mcu step queue is ordered between steppers so that no stepper
// starves the other steppers of space in the mcu step queue.
#include <stddef.h> // offsetof
#include <stdlib.h> // malloc
#include <string.h> // memset
#include "compiler.h" // __visible
#include "serialqueue.h" // struct queue_message
#include "stepcompress.h" // stepcompress_flush
#include "steppersync.h" // steppersync_alloc
struct steppersync {
// Serial port
struct serialqueue *sq;
struct command_queue *cq;
// Storage for associated stepcompress objects
struct stepcompress **sc_list;
int sc_num;
// Storage for list of pending move clocks
uint64_t *move_clocks;
int num_move_clocks;
};
// Allocate a new 'steppersync' object
struct steppersync * __visible
steppersync_alloc(struct serialqueue *sq, struct stepcompress **sc_list
, int sc_num, int move_num)
{
struct steppersync *ss = malloc(sizeof(*ss));
memset(ss, 0, sizeof(*ss));
ss->sq = sq;
ss->cq = serialqueue_alloc_commandqueue();
ss->sc_list = malloc(sizeof(*sc_list)*sc_num);
memcpy(ss->sc_list, sc_list, sizeof(*sc_list)*sc_num);
ss->sc_num = sc_num;
ss->move_clocks = malloc(sizeof(*ss->move_clocks)*move_num);
memset(ss->move_clocks, 0, sizeof(*ss->move_clocks)*move_num);
ss->num_move_clocks = move_num;
return ss;
}
// Free memory associated with a 'steppersync' object
void __visible
steppersync_free(struct steppersync *ss)
{
if (!ss)
return;
free(ss->sc_list);
free(ss->move_clocks);
serialqueue_free_commandqueue(ss->cq);
free(ss);
}
// Set the conversion rate of 'print_time' to mcu clock
void __visible
steppersync_set_time(struct steppersync *ss, double time_offset
, double mcu_freq)
{
int i;
for (i=0; i<ss->sc_num; i++) {
struct stepcompress *sc = ss->sc_list[i];
stepcompress_set_time(sc, time_offset, mcu_freq);
}
}
// Generate steps and flush stepcompress objects
int32_t __visible
steppersync_generate_steps(struct steppersync *ss, double gen_steps_time
, uint64_t flush_clock)
{
int i;
for (i=0; i<ss->sc_num; i++) {
struct stepcompress *sc = ss->sc_list[i];
int32_t ret = stepcompress_generate_steps(sc, gen_steps_time
, flush_clock);
if (ret)
return ret;
}
return 0;
}
// Expire the stepcompress history before the given clock time
void __visible
steppersync_history_expire(struct steppersync *ss, uint64_t end_clock)
{
int i;
for (i = 0; i < ss->sc_num; i++) {
struct stepcompress *sc = ss->sc_list[i];
stepcompress_history_expire(sc, end_clock);
}
}
// Implement a binary heap algorithm to track when the next available
// 'struct move' in the mcu will be available
static void
heap_replace(struct steppersync *ss, uint64_t req_clock)
{
uint64_t *mc = ss->move_clocks;
int nmc = ss->num_move_clocks, pos = 0;
for (;;) {
int child1_pos = 2*pos+1, child2_pos = 2*pos+2;
uint64_t child2_clock = child2_pos < nmc ? mc[child2_pos] : UINT64_MAX;
uint64_t child1_clock = child1_pos < nmc ? mc[child1_pos] : UINT64_MAX;
if (req_clock <= child1_clock && req_clock <= child2_clock) {
mc[pos] = req_clock;
break;
}
if (child1_clock < child2_clock) {
mc[pos] = child1_clock;
pos = child1_pos;
} else {
mc[pos] = child2_clock;
pos = child2_pos;
}
}
}
// Find and transmit any scheduled steps prior to the given 'move_clock'
int __visible
steppersync_flush(struct steppersync *ss, uint64_t move_clock)
{
// Order commands by the reqclock of each pending command
struct list_head msgs;
list_init(&msgs);
for (;;) {
// Find message with lowest reqclock
uint64_t req_clock = MAX_CLOCK;
struct queue_message *qm = NULL;
int i;
for (i=0; i<ss->sc_num; i++) {
struct stepcompress *sc = ss->sc_list[i];
struct list_head *sc_mq = stepcompress_get_msg_queue(sc);
if (!list_empty(sc_mq)) {
struct queue_message *m = list_first_entry(
sc_mq, struct queue_message, node);
if (m->req_clock < req_clock) {
qm = m;
req_clock = m->req_clock;
}
}
}
if (!qm || (qm->min_clock && req_clock > move_clock))
break;
uint64_t next_avail = ss->move_clocks[0];
if (qm->min_clock)
// The qm->min_clock field is overloaded to indicate that
// the command uses the 'move queue' and to store the time
// that move queue item becomes available.
heap_replace(ss, qm->min_clock);
// Reset the min_clock to its normal meaning (minimum transmit time)
qm->min_clock = next_avail;
// Batch this command
list_del(&qm->node);
list_add_tail(&qm->node, &msgs);
}
// Transmit commands
if (!list_empty(&msgs))
serialqueue_send_batch(ss->sq, ss->cq, &msgs);
return 0;
}

View File

@@ -1,18 +0,0 @@
#ifndef STEPPERSYNC_H
#define STEPPERSYNC_H
#include <stdint.h> // uint64_t
struct serialqueue;
struct steppersync *steppersync_alloc(
struct serialqueue *sq, struct stepcompress **sc_list, int sc_num
, int move_num);
void steppersync_free(struct steppersync *ss);
void steppersync_set_time(struct steppersync *ss, double time_offset
, double mcu_freq);
int32_t steppersync_generate_steps(struct steppersync *ss, double gen_steps_time
, uint64_t flush_clock);
void steppersync_history_expire(struct steppersync *ss, uint64_t end_clock);
int steppersync_flush(struct steppersync *ss, uint64_t move_clock);
#endif // steppersync.h

View File

@@ -1,17 +1,12 @@
# Code for reading and writing the Klipper config file # Code for reading and writing the Klipper config file
# #
# Copyright (C) 2016-2024 Kevin O'Connor <kevin@koconnor.net> # Copyright (C) 2016-2021 Kevin O'Connor <kevin@koconnor.net>
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import sys, os, glob, re, time, logging, configparser, io import sys, os, glob, re, time, logging, configparser, io
error = configparser.Error error = configparser.Error
######################################################################
# Config section parsing helper
######################################################################
class sentinel: class sentinel:
pass pass
@@ -139,13 +134,30 @@ class ConfigWrapper:
pconfig = self.printer.lookup_object("configfile") pconfig = self.printer.lookup_object("configfile")
pconfig.deprecate(self.section, option, value, msg) pconfig.deprecate(self.section, option, value, msg)
AUTOSAVE_HEADER = """
#*# <---------------------- SAVE_CONFIG ---------------------->
#*# DO NOT EDIT THIS BLOCK OR BELOW. The contents are auto-generated.
#*#
"""
###################################################################### class PrinterConfig:
# Config file parsing (with include file support) def __init__(self, printer):
###################################################################### self.printer = printer
self.autosave = None
class ConfigFileReader: self.deprecated = {}
def read_config_file(self, filename): self.runtime_warnings = []
self.deprecate_warnings = []
self.status_raw_config = {}
self.status_save_pending = {}
self.status_settings = {}
self.status_warnings = []
self.save_config_pending = False
gcode = self.printer.lookup_object('gcode')
gcode.register_command("SAVE_CONFIG", self.cmd_SAVE_CONFIG,
desc=self.cmd_SAVE_CONFIG_help)
def get_printer(self):
return self.printer
def _read_config_file(self, filename):
try: try:
f = open(filename, 'r') f = open(filename, 'r')
data = f.read() data = f.read()
@@ -155,102 +167,6 @@ class ConfigFileReader:
logging.exception(msg) logging.exception(msg)
raise error(msg) raise error(msg)
return data.replace('\r\n', '\n') return data.replace('\r\n', '\n')
def build_config_string(self, fileconfig):
sfile = io.StringIO()
fileconfig.write(sfile)
return sfile.getvalue().strip()
def append_fileconfig(self, fileconfig, data, filename):
if not data:
return
# Strip trailing comments
lines = data.split('\n')
for i, line in enumerate(lines):
pos = line.find('#')
if pos >= 0:
lines[i] = line[:pos]
sbuffer = io.StringIO('\n'.join(lines))
if sys.version_info.major >= 3:
fileconfig.read_file(sbuffer, filename)
else:
fileconfig.readfp(sbuffer, filename)
def _create_fileconfig(self):
if sys.version_info.major >= 3:
fileconfig = configparser.RawConfigParser(
strict=False, inline_comment_prefixes=(';', '#'))
else:
fileconfig = configparser.RawConfigParser()
return fileconfig
def build_fileconfig(self, data, filename):
fileconfig = self._create_fileconfig()
self.append_fileconfig(fileconfig, data, filename)
return fileconfig
def _resolve_include(self, source_filename, include_spec, fileconfig,
visited):
dirname = os.path.dirname(source_filename)
include_spec = include_spec.strip()
include_glob = os.path.join(dirname, include_spec)
include_filenames = glob.glob(include_glob)
if not include_filenames and not glob.has_magic(include_glob):
# Empty set is OK if wildcard but not for direct file reference
raise error("Include file '%s' does not exist" % (include_glob,))
include_filenames.sort()
for include_filename in include_filenames:
include_data = self.read_config_file(include_filename)
self._parse_config(include_data, include_filename, fileconfig,
visited)
return include_filenames
def _parse_config(self, data, filename, fileconfig, visited):
path = os.path.abspath(filename)
if path in visited:
raise error("Recursive include of config file '%s'" % (filename))
visited.add(path)
lines = data.split('\n')
# Buffer lines between includes and parse as a unit so that overrides
# in includes apply linearly as they do within a single file
buf = []
for line in lines:
# Strip trailing comment
pos = line.find('#')
if pos >= 0:
line = line[:pos]
# Process include or buffer line
mo = configparser.RawConfigParser.SECTCRE.match(line)
header = mo and mo.group('header')
if header and header.startswith('include '):
self.append_fileconfig(fileconfig, '\n'.join(buf), filename)
del buf[:]
include_spec = header[8:].strip()
self._resolve_include(filename, include_spec, fileconfig,
visited)
else:
buf.append(line)
self.append_fileconfig(fileconfig, '\n'.join(buf), filename)
visited.remove(path)
def build_fileconfig_with_includes(self, data, filename):
fileconfig = self._create_fileconfig()
self._parse_config(data, filename, fileconfig, set())
return fileconfig
######################################################################
# Config auto save helper
######################################################################
AUTOSAVE_HEADER = """
#*# <---------------------- SAVE_CONFIG ---------------------->
#*# DO NOT EDIT THIS BLOCK OR BELOW. The contents are auto-generated.
#*#
"""
class ConfigAutoSave:
def __init__(self, printer):
self.printer = printer
self.fileconfig = None
self.status_save_pending = {}
self.save_config_pending = False
gcode = self.printer.lookup_object('gcode')
gcode.register_command("SAVE_CONFIG", self.cmd_SAVE_CONFIG,
desc=self.cmd_SAVE_CONFIG_help)
def _find_autosave_data(self, data): def _find_autosave_data(self, data):
regular_data = data regular_data = data
autosave_data = "" autosave_data = ""
@@ -259,7 +175,7 @@ class ConfigAutoSave:
regular_data = data[:pos] regular_data = data[:pos]
autosave_data = data[pos + len(AUTOSAVE_HEADER):].strip() autosave_data = data[pos + len(AUTOSAVE_HEADER):].strip()
# Check for errors and strip line prefixes # Check for errors and strip line prefixes
if "\n#*# " in regular_data or autosave_data.find(AUTOSAVE_HEADER) >= 0: if "\n#*# " in regular_data:
logging.warning("Can't read autosave from config file" logging.warning("Can't read autosave from config file"
" - autosave state corrupted") " - autosave state corrupted")
return data, "" return data, ""
@@ -276,7 +192,7 @@ class ConfigAutoSave:
return regular_data, "\n".join(out) return regular_data, "\n".join(out)
comment_r = re.compile('[#;].*$') comment_r = re.compile('[#;].*$')
value_r = re.compile('[^A-Za-z0-9_].*$') value_r = re.compile('[^A-Za-z0-9_].*$')
def _strip_duplicates(self, data, fileconfig): def _strip_duplicates(self, data, config):
# Comment out fields in 'data' that are defined in 'config' # Comment out fields in 'data' that are defined in 'config'
lines = data.split('\n') lines = data.split('\n')
section = None section = None
@@ -294,31 +210,152 @@ class ConfigAutoSave:
section = pruned_line[1:-1].strip() section = pruned_line[1:-1].strip()
continue continue
field = self.value_r.sub('', pruned_line) field = self.value_r.sub('', pruned_line)
if fileconfig.has_option(section, field): if config.fileconfig.has_option(section, field):
is_dup_field = True is_dup_field = True
lines[lineno] = '#' + lines[lineno] lines[lineno] = '#' + lines[lineno]
return "\n".join(lines) return "\n".join(lines)
def load_main_config(self): def _parse_config_buffer(self, buffer, filename, fileconfig):
if not buffer:
return
data = '\n'.join(buffer)
del buffer[:]
sbuffer = io.StringIO(data)
if sys.version_info.major >= 3:
fileconfig.read_file(sbuffer, filename)
else:
fileconfig.readfp(sbuffer, filename)
def _resolve_include(self, source_filename, include_spec, fileconfig,
visited):
dirname = os.path.dirname(source_filename)
include_spec = include_spec.strip()
include_glob = os.path.join(dirname, include_spec)
include_filenames = glob.glob(include_glob)
if not include_filenames and not glob.has_magic(include_glob):
# Empty set is OK if wildcard but not for direct file reference
raise error("Include file '%s' does not exist" % (include_glob,))
include_filenames.sort()
for include_filename in include_filenames:
include_data = self._read_config_file(include_filename)
self._parse_config(include_data, include_filename, fileconfig,
visited)
return include_filenames
def _parse_config(self, data, filename, fileconfig, visited):
path = os.path.abspath(filename)
if path in visited:
raise error("Recursive include of config file '%s'" % (filename))
visited.add(path)
lines = data.split('\n')
# Buffer lines between includes and parse as a unit so that overrides
# in includes apply linearly as they do within a single file
buffer = []
for line in lines:
# Strip trailing comment
pos = line.find('#')
if pos >= 0:
line = line[:pos]
# Process include or buffer line
mo = configparser.RawConfigParser.SECTCRE.match(line)
header = mo and mo.group('header')
if header and header.startswith('include '):
self._parse_config_buffer(buffer, filename, fileconfig)
include_spec = header[8:].strip()
self._resolve_include(filename, include_spec, fileconfig,
visited)
else:
buffer.append(line)
self._parse_config_buffer(buffer, filename, fileconfig)
visited.remove(path)
def _build_config_wrapper(self, data, filename):
if sys.version_info.major >= 3:
fileconfig = configparser.RawConfigParser(
strict=False, inline_comment_prefixes=(';', '#'))
else:
fileconfig = configparser.RawConfigParser()
self._parse_config(data, filename, fileconfig, set())
return ConfigWrapper(self.printer, fileconfig, {}, 'printer')
def _build_config_string(self, config):
sfile = io.StringIO()
config.fileconfig.write(sfile)
return sfile.getvalue().strip()
def read_config(self, filename):
return self._build_config_wrapper(self._read_config_file(filename),
filename)
def read_main_config(self):
filename = self.printer.get_start_args()['config_file'] filename = self.printer.get_start_args()['config_file']
cfgrdr = ConfigFileReader() data = self._read_config_file(filename)
data = cfgrdr.read_config_file(filename)
regular_data, autosave_data = self._find_autosave_data(data) regular_data, autosave_data = self._find_autosave_data(data)
regular_fileconfig = cfgrdr.build_fileconfig_with_includes( regular_config = self._build_config_wrapper(regular_data, filename)
regular_data, filename) autosave_data = self._strip_duplicates(autosave_data, regular_config)
autosave_data = self._strip_duplicates(autosave_data, self.autosave = self._build_config_wrapper(autosave_data, filename)
regular_fileconfig) cfg = self._build_config_wrapper(regular_data + autosave_data, filename)
self.fileconfig = cfgrdr.build_fileconfig(autosave_data, filename) return cfg
cfgrdr.append_fileconfig(regular_fileconfig, def check_unused_options(self, config):
autosave_data, '*AUTOSAVE*') fileconfig = config.fileconfig
return regular_fileconfig, self.fileconfig objects = dict(self.printer.lookup_objects())
# Determine all the fields that have been accessed
access_tracking = dict(config.access_tracking)
for section in self.autosave.fileconfig.sections():
for option in self.autosave.fileconfig.options(section):
access_tracking[(section.lower(), option.lower())] = 1
# Validate that there are no undefined parameters in the config file
valid_sections = { s: 1 for s, o in access_tracking }
for section_name in fileconfig.sections():
section = section_name.lower()
if section not in valid_sections and section not in objects:
raise error("Section '%s' is not a valid config section"
% (section,))
for option in fileconfig.options(section_name):
option = option.lower()
if (section, option) not in access_tracking:
raise error("Option '%s' is not valid in section '%s'"
% (option, section))
# Setup get_status()
self._build_status(config)
def log_config(self, config):
lines = ["===== Config file =====",
self._build_config_string(config),
"======================="]
self.printer.set_rollover_info("config", "\n".join(lines))
# Status reporting
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
def deprecate(self, section, option, value=None, msg=None):
self.deprecated[(section, option, value)] = msg
def _build_status(self, config):
self.status_raw_config.clear()
for section in config.get_prefix_sections(''):
self.status_raw_config[section.get_name()] = section_status = {}
for option in section.get_prefix_options(''):
section_status[option] = section.get(option, note_valid=False)
self.status_settings = {}
for (section, option), value in config.access_tracking.items():
self.status_settings.setdefault(section, {})[option] = value
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
def get_status(self, eventtime): def get_status(self, eventtime):
return {'save_config_pending': self.save_config_pending, return {'config': self.status_raw_config,
'settings': self.status_settings,
'warnings': self.status_warnings,
'save_config_pending': self.save_config_pending,
'save_config_pending_items': self.status_save_pending} 'save_config_pending_items': self.status_save_pending}
# Autosave functions
def set(self, section, option, value): def set(self, section, option, value):
if not self.fileconfig.has_section(section): if not self.autosave.fileconfig.has_section(section):
self.fileconfig.add_section(section) self.autosave.fileconfig.add_section(section)
svalue = str(value) svalue = str(value)
self.fileconfig.set(section, option, svalue) self.autosave.fileconfig.set(section, option, svalue)
pending = dict(self.status_save_pending) pending = dict(self.status_save_pending)
if not section in pending or pending[section] is None: if not section in pending or pending[section] is None:
pending[section] = {} pending[section] = {}
@@ -329,8 +366,8 @@ class ConfigAutoSave:
self.save_config_pending = True self.save_config_pending = True
logging.info("save_config: set [%s] %s = %s", section, option, svalue) logging.info("save_config: set [%s] %s = %s", section, option, svalue)
def remove_section(self, section): def remove_section(self, section):
if self.fileconfig.has_section(section): if self.autosave.fileconfig.has_section(section):
self.fileconfig.remove_section(section) self.autosave.fileconfig.remove_section(section)
pending = dict(self.status_save_pending) pending = dict(self.status_save_pending)
pending[section] = None pending[section] = None
self.status_save_pending = pending self.status_save_pending = pending
@@ -341,20 +378,21 @@ class ConfigAutoSave:
del pending[section] del pending[section]
self.status_save_pending = pending self.status_save_pending = pending
self.save_config_pending = True self.save_config_pending = True
def _disallow_include_conflicts(self, regular_fileconfig): def _disallow_include_conflicts(self, regular_data, cfgname, gcode):
for section in self.fileconfig.sections(): config = self._build_config_wrapper(regular_data, cfgname)
for option in self.fileconfig.options(section): for section in self.autosave.fileconfig.sections():
if regular_fileconfig.has_option(section, option): for option in self.autosave.fileconfig.options(section):
if config.fileconfig.has_option(section, option):
msg = ("SAVE_CONFIG section '%s' option '%s' conflicts " msg = ("SAVE_CONFIG section '%s' option '%s' conflicts "
"with included value" % (section, option)) "with included value" % (section, option))
raise self.printer.command_error(msg) raise gcode.error(msg)
cmd_SAVE_CONFIG_help = "Overwrite config file and restart" cmd_SAVE_CONFIG_help = "Overwrite config file and restart"
def cmd_SAVE_CONFIG(self, gcmd): def cmd_SAVE_CONFIG(self, gcmd):
if not self.fileconfig.sections(): if not self.autosave.fileconfig.sections():
return return
gcode = self.printer.lookup_object('gcode')
# Create string containing autosave data # Create string containing autosave data
cfgrdr = ConfigFileReader() autosave_data = self._build_config_string(self.autosave)
autosave_data = cfgrdr.build_config_string(self.fileconfig)
lines = [('#*# ' + l).strip() lines = [('#*# ' + l).strip()
for l in autosave_data.split('\n')] for l in autosave_data.split('\n')]
lines.insert(0, "\n" + AUTOSAVE_HEADER.rstrip()) lines.insert(0, "\n" + AUTOSAVE_HEADER.rstrip())
@@ -363,27 +401,16 @@ class ConfigAutoSave:
# Read in and validate current config file # Read in and validate current config file
cfgname = self.printer.get_start_args()['config_file'] cfgname = self.printer.get_start_args()['config_file']
try: try:
data = cfgrdr.read_config_file(cfgname) data = self._read_config_file(cfgname)
except error as e: regular_data, old_autosave_data = self._find_autosave_data(data)
msg = "Unable to read existing config on SAVE_CONFIG" config = self._build_config_wrapper(regular_data, cfgname)
logging.exception(msg)
raise gcmd.error(msg)
regular_data, old_autosave_data = self._find_autosave_data(data)
regular_data = self._strip_duplicates(regular_data, self.fileconfig)
data = regular_data.rstrip() + autosave_data
new_regular_data, new_autosave_data = self._find_autosave_data(data)
if not new_autosave_data:
raise gcmd.error(
"Existing config autosave is corrupted."
" Can't complete SAVE_CONFIG")
try:
regular_fileconfig = cfgrdr.build_fileconfig_with_includes(
new_regular_data, cfgname)
except error as e: except error as e:
msg = "Unable to parse existing config on SAVE_CONFIG" msg = "Unable to parse existing config on SAVE_CONFIG"
logging.exception(msg) logging.exception(msg)
raise gcmd.error(msg) raise gcode.error(msg)
self._disallow_include_conflicts(regular_fileconfig) regular_data = self._strip_duplicates(regular_data, self.autosave)
self._disallow_include_conflicts(regular_data, cfgname, gcode)
data = regular_data.rstrip() + autosave_data
# Determine filenames # Determine filenames
datestr = time.strftime("-%Y%m%d_%H%M%S") datestr = time.strftime("-%Y%m%d_%H%M%S")
backup_name = cfgname + datestr backup_name = cfgname + datestr
@@ -403,135 +430,6 @@ class ConfigAutoSave:
except: except:
msg = "Unable to write config file during SAVE_CONFIG" msg = "Unable to write config file during SAVE_CONFIG"
logging.exception(msg) logging.exception(msg)
raise gcmd.error(msg) raise gcode.error(msg)
# Request a restart # Request a restart
gcode = self.printer.lookup_object('gcode')
gcode.request_restart('restart') gcode.request_restart('restart')
######################################################################
# Config validation (check for undefined options)
######################################################################
class ConfigValidate:
def __init__(self, printer):
self.printer = printer
self.status_settings = {}
self.access_tracking = {}
self.autosave_options = {}
def start_access_tracking(self, autosave_fileconfig):
# Note autosave options for use during undefined options check
self.autosave_options = {}
for section in autosave_fileconfig.sections():
for option in autosave_fileconfig.options(section):
self.autosave_options[(section.lower(), option.lower())] = 1
self.access_tracking = {}
return self.access_tracking
def check_unused(self, fileconfig):
# Don't warn on fields set in autosave segment
access_tracking = dict(self.access_tracking)
access_tracking.update(self.autosave_options)
# Note locally used sections
valid_sections = { s: 1 for s, o in self.printer.lookup_objects() }
valid_sections.update({ s: 1 for s, o in access_tracking })
# Validate that there are no undefined parameters in the config file
for section_name in fileconfig.sections():
section = section_name.lower()
if section not in valid_sections:
raise error("Section '%s' is not a valid config section"
% (section,))
for option in fileconfig.options(section_name):
option = option.lower()
if (section, option) not in access_tracking:
raise error("Option '%s' is not valid in section '%s'"
% (option, section))
# Setup get_status()
self._build_status_settings()
# Clear tracking state
self.access_tracking.clear()
self.autosave_options.clear()
def _build_status_settings(self):
self.status_settings = {}
for (section, option), value in self.access_tracking.items():
self.status_settings.setdefault(section, {})[option] = value
def get_status(self, eventtime):
return {'settings': self.status_settings}
######################################################################
# Main printer config tracking
######################################################################
class PrinterConfig:
def __init__(self, printer):
self.printer = printer
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):
return self.printer
def read_config(self, filename):
cfgrdr = ConfigFileReader()
data = cfgrdr.read_config_file(filename)
fileconfig = cfgrdr.build_fileconfig(data, filename)
return ConfigWrapper(self.printer, fileconfig, {}, 'printer')
def read_main_config(self):
fileconfig, autosave_fileconfig = self.autosave.load_main_config()
access_tracking = self.validate.start_access_tracking(
autosave_fileconfig)
config = ConfigWrapper(self.printer, fileconfig,
access_tracking, 'printer')
self._build_status_config(config)
return config
def log_config(self, config):
cfgrdr = ConfigFileReader()
lines = ["===== Config file =====",
cfgrdr.build_config_string(config.fileconfig),
"======================="]
self.printer.set_rollover_info("config", "\n".join(lines))
def check_unused_options(self, config):
self.validate.check_unused(config.fileconfig)
# Deprecation warnings
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
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
# Status reporting
def _build_status_config(self, config):
self.status_raw_config = {}
for section in config.get_prefix_sections(''):
self.status_raw_config[section.get_name()] = section_status = {}
for option in section.get_prefix_options(''):
section_status[option] = section.get(option, note_valid=False)
def get_status(self, eventtime):
status = {'config': self.status_raw_config,
'warnings': self.status_warnings}
status.update(self.autosave.get_status(eventtime))
status.update(self.validate.get_status(eventtime))
return status
# Autosave functions
def set(self, section, option, value):
self.autosave.set(section, option, value)
def remove_section(self, section):
self.autosave.remove_section(section)

View File

@@ -24,7 +24,7 @@ def hexify(byte_array):
return "[%s]" % (", ".join([hex(b) for b in byte_array])) return "[%s]" % (", ".join([hex(b) for b in byte_array]))
class ADS1220: class ADS1220():
def __init__(self, config): def __init__(self, config):
self.printer = printer = config.get_printer() self.printer = printer = config.get_printer()
self.name = config.get_name().split()[-1] self.name = config.get_name().split()[-1]
@@ -42,35 +42,8 @@ class ADS1220:
'660': 660, '1200': 1200, '2000': 2000} '660': 660, '1200': 1200, '2000': 2000}
self.sps_options = self.sps_normal.copy() self.sps_options = self.sps_normal.copy()
self.sps_options.update(self.sps_turbo) self.sps_options.update(self.sps_turbo)
self.sps = config.getchoice('sample_rate', self.sps_options, self.sps = config.getchoice('sps', self.sps_options, default='660')
default='660')
self.is_turbo = str(self.sps) in self.sps_turbo self.is_turbo = str(self.sps) in self.sps_turbo
# Input multiplexer: AINP and AINN
mux_options = {'AIN0_AIN1': 0b0000, 'AIN0_AIN2': 0b0001,
'AIN0_AIN3': 0b0010, 'AIN1_AIN2': 0b0011,
'AIN1_AIN3': 0b0100, 'AIN2_AIN3': 0b0101,
'AIN1_AIN0': 0b0110, 'AIN3_AIN2': 0b0111,
'AIN0_AVSS': 0b1000, 'AIN1_AVSS': 0b1001,
'AIN2_AVSS': 0b1010, 'AIN3_AVSS': 0b1011}
self.mux = config.getchoice('input_mux', mux_options,
default='AIN0_AIN1')
# PGA Bypass
self.pga_bypass = config.getboolean('pga_bypass', default=False)
# bypass PGA when AVSS is the negative input
force_pga_bypass = self.mux >= 0b1000
self.pga_bypass = force_pga_bypass or self.pga_bypass
# Voltage Reference
self.vref_options = {'internal': 0b0, 'REF0': 0b01, 'REF1': 0b10,
'analog_supply': 0b11}
self.vref = config.getchoice('vref', self.vref_options,
default='internal')
# check for conflict between REF1 and AIN0/AIN3
mux_conflict = [0b0000, 0b0001, 0b0010, 0b0100, 0b0101, 0b0110, 0b0111,
0b1000, 0b1011]
if self.vref == 0b10 and self.mux in mux_conflict:
raise config.error("ADS1220 config error: AIN0/REFP1 and AIN3/REFN1"
" cant be used as a voltage reference and"
" an input at the same time")
# SPI Setup # SPI Setup
spi_speed = 512000 if self.is_turbo else 256000 spi_speed = 512000 if self.is_turbo else 256000
self.spi = bus.MCU_SPI_from_config(config, 1, default_speed=spi_speed) self.spi = bus.MCU_SPI_from_config(config, 1, default_speed=spi_speed)
@@ -95,8 +68,11 @@ class ADS1220:
self.batch_bulk = bulk_sensor.BatchBulkHelper( self.batch_bulk = bulk_sensor.BatchBulkHelper(
self.printer, self._process_batch, self._start_measurements, self.printer, self._process_batch, self._start_measurements,
self._finish_measurements, UPDATE_INTERVAL) self._finish_measurements, UPDATE_INTERVAL)
# publish raw samples to the socket
self.batch_bulk.add_mux_endpoint("ads1220/dump_ads1220", "sensor",
self.name,
{'header': ('time', 'counts')})
# Command Configuration # Command Configuration
self.attach_probe_cmd = None
mcu.add_config_cmd( mcu.add_config_cmd(
"config_ads1220 oid=%d spi_oid=%d data_ready_pin=%s" "config_ads1220 oid=%d spi_oid=%d data_ready_pin=%s"
% (self.oid, self.spi.get_oid(), self.data_ready_pin)) % (self.oid, self.spi.get_oid(), self.data_ready_pin))
@@ -109,8 +85,6 @@ class ADS1220:
cmdqueue = self.spi.get_command_queue() cmdqueue = self.spi.get_command_queue()
self.query_ads1220_cmd = self.mcu.lookup_command( self.query_ads1220_cmd = self.mcu.lookup_command(
"query_ads1220 oid=%c rest_ticks=%u", cq=cmdqueue) "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", self.ffreader.setup_query_command("query_ads1220_status oid=%c",
oid=self.oid, cq=cmdqueue) oid=self.oid, cq=cmdqueue)
@@ -129,9 +103,6 @@ class ADS1220:
def add_client(self, callback): def add_client(self, callback):
self.batch_bulk.add_client(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 # Measurement decoding
def _convert_samples(self, samples): def _convert_samples(self, samples):
adc_factor = 1. / (1 << 23) adc_factor = 1. / (1 << 23)
@@ -186,10 +157,8 @@ class ADS1220:
mode = 0x2 if self.is_turbo else 0x0 # turbo mode mode = 0x2 if self.is_turbo else 0x0 # turbo mode
sps_list = self.sps_turbo if self.is_turbo else self.sps_normal sps_list = self.sps_turbo if self.is_turbo else self.sps_normal
data_rate = list(sps_list.keys()).index(str(self.sps)) data_rate = list(sps_list.keys()).index(str(self.sps))
reg_values = [(self.mux << 4) | (self.gain << 1) | int(self.pga_bypass), reg_values = [(self.gain << 1),
(data_rate << 5) | (mode << 3) | (continuous << 2), (data_rate << 5) | (mode << 3) | (continuous << 2)]
(self.vref << 6),
0x0]
self.write_reg(0x0, reg_values) self.write_reg(0x0, reg_values)
# start measurements immediately # start measurements immediately
self.send_command(START_SYNC_CMD) self.send_command(START_SYNC_CMD)
@@ -208,7 +177,7 @@ class ADS1220:
write_command.extend(register_bytes) write_command.extend(register_bytes)
self.spi.spi_send(write_command) self.spi.spi_send(write_command)
stored_val = self.read_reg(reg, len(register_bytes)) stored_val = self.read_reg(reg, len(register_bytes))
if bytearray(register_bytes) != stored_val: if register_bytes != stored_val:
raise self.printer.command_error( raise self.printer.command_error(
"Failed to set ADS1220 register [0x%x] to %s: got %s. " "Failed to set ADS1220 register [0x%x] to %s: got %s. "
"This may be a connection problem (e.g. faulty wiring)" % ( "This may be a connection problem (e.g. faulty wiring)" % (

View File

@@ -1,393 +0,0 @@
# Support for I2C based ADS1013, ADS1014, ADS1015, ADS1113, ADS1114 and ADS1115
#
# Copyright (C) 2024 Konstantin Koch <korsarnek@gmail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
import pins
from . import bus
# Supported chip types
ADS1X1X_CHIP_TYPE = {
'ADS1013': 3,
'ADS1014': 4,
'ADS1015': 5,
'ADS1113': 13,
'ADS1114': 14,
'ADS1115': 15
}
def isADS101X(chip):
return (chip == ADS1X1X_CHIP_TYPE['ADS1013'] \
or chip == ADS1X1X_CHIP_TYPE['ADS1014'] \
or chip == ADS1X1X_CHIP_TYPE['ADS1015'])
def isADS111X(chip):
return (chip == ADS1X1X_CHIP_TYPE['ADS1113'] \
or chip == ADS1X1X_CHIP_TYPE['ADS1114'] \
or chip == ADS1X1X_CHIP_TYPE['ADS1115'])
# Address is defined by how the address pin is wired
ADS1X1X_CHIP_ADDR = {
'GND': 0x48,
'VCC': 0x49,
'SDA': 0x4a,
'SCL': 0x4b
}
# Chip "pointer" registers
ADS1X1X_REG_POINTER_MASK = 0x03
ADS1X1X_REG_POINTER = {
'CONVERSION': 0x00,
'CONFIG': 0x01,
'LO_THRESH': 0x02,
'HI_THRESH': 0x03
}
# Config register masks
ADS1X1X_REG_CONFIG = {
'OS_MASK': 0x8000,
'MULTIPLEXER_MASK': 0x7000,
'PGA_MASK': 0x0E00,
'MODE_MASK': 0x0100,
'DATA_RATE_MASK': 0x00E0,
'COMPARATOR_MODE_MASK': 0x0010,
'COMPARATOR_POLARITY_MASK': 0x0008,
# Determines if ALERT/RDY pin latches once asserted
'COMPARATOR_LATCHING_MASK': 0x0004,
'COMPARATOR_QUEUE_MASK': 0x0003
}
#
# The following enums are to be used with the configuration functions.
#
ADS1X1X_OS = {
'OS_IDLE': 0x8000, # Device is not performing a conversion
'OS_SINGLE': 0x8000 # Single-conversion
}
ADS1X1X_MUX = {
'DIFF01': 0x0000, # Differential P = AIN0, N = AIN1 0
'DIFF03': 0x1000, # Differential P = AIN0, N = AIN3 4096
'DIFF13': 0x2000, # Differential P = AIN1, N = AIN3 8192
'DIFF23': 0x3000, # Differential P = AIN2, N = AIN3 12288
'AIN0': 0x4000, # Single-ended (ADS1015: AIN0 16384)
'AIN1': 0x5000, # Single-ended (ADS1015: AIN1 20480)
'AIN2': 0x6000, # Single-ended (ADS1015: AIN2 24576)
'AIN3': 0x7000 # Single-ended (ADS1015: AIN3 28672)
}
ADS1X1X_PGA = {
'6.144V': 0x0000, # +/-6.144V range = Gain 2/3
'4.096V': 0x0200, # +/-4.096V range = Gain 1
'2.048V': 0x0400, # +/-2.048V range = Gain 2
'1.024V': 0x0600, # +/-1.024V range = Gain 4
'0.512V': 0x0800, # +/-0.512V range = Gain 8
'0.256V': 0x0A00 # +/-0.256V range = Gain 16
}
ADS1X1X_PGA_VALUE = {
0x0000: 6.144,
0x0200: 4.096,
0x0400: 2.048,
0x0600: 1.024,
0x0800: 0.512,
0x0A00: 0.256,
}
ADS111X_RESOLUTION = 32767.0
ADS111X_PGA_SCALAR = {
0x0000: 6.144 / ADS111X_RESOLUTION, # +/-6.144V range = Gain 2/3
0x0200: 4.096 / ADS111X_RESOLUTION, # +/-4.096V range = Gain 1
0x0400: 2.048 / ADS111X_RESOLUTION, # +/-2.048V range = Gain 2
0x0600: 1.024 / ADS111X_RESOLUTION, # +/-1.024V range = Gain 4
0x0800: 0.512 / ADS111X_RESOLUTION, # +/-0.512V range = Gain 8
0x0A00: 0.256 / ADS111X_RESOLUTION # +/-0.256V range = Gain 16
}
ADS101X_RESOLUTION = 2047.0
ADS101X_PGA_SCALAR = {
0x0000: 6.144 / ADS101X_RESOLUTION, # +/-6.144V range = Gain 2/3
0x0200: 4.096 / ADS101X_RESOLUTION, # +/-4.096V range = Gain 1
0x0400: 2.048 / ADS101X_RESOLUTION, # +/-2.048V range = Gain 2
0x0600: 1.024 / ADS101X_RESOLUTION, # +/-1.024V range = Gain 4
0x0800: 0.512 / ADS101X_RESOLUTION, # +/-0.512V range = Gain 8
0x0A00: 0.256 / ADS101X_RESOLUTION # +/-0.256V range = Gain 16
}
ADS1X1X_MODE = {
'continuous': 0x0000, # Continuous conversion mode
'single': 0x0100 # Power-down single-shot mode
}
# Lesser samples per second means it takes and averages more samples before
# returning a result.
ADS101X_SAMPLES_PER_SECOND = {
'128': 0x0000, # 128 samples per second
'250': 0x0020, # 250 samples per second
'490': 0x0040, # 490 samples per second
'920': 0x0060, # 920 samples per second
'1600': 0x0080, # 1600 samples per second
'2400': 0x00a0, # 2400 samples per second
'3300': 0x00c0, # 3300 samples per second
}
ADS111X_SAMPLES_PER_SECOND = {
'8': 0x0000, # 8 samples per second
'16': 0x0020, # 16 samples per second
'32': 0x0040, # 32 samples per second
'64': 0x0060, # 64 samples per second
'128': 0x0080, # 128 samples per second
'250': 0x00a0, # 250 samples per second
'475': 0x00c0, # 475 samples per second
'860': 0x00e0 # 860 samples per second
}
ADS1X1X_COMPARATOR_MODE = {
'TRADITIONAL': 0x0000, # Traditional comparator with hysteresis
'WINDOW': 0x0010 # Window comparator
}
ADS1X1X_COMPARATOR_POLARITY = {
'ACTIVE_LO': 0x0000, # ALERT/RDY pin is low when active
'ACTIVE_HI': 0x0008 # ALERT/RDY pin is high when active
}
ADS1X1X_COMPARATOR_LATCHING = {
'NON_LATCHING': 0x0000, # Non-latching comparator
'LATCHING': 0x0004 # Latching comparator
}
ADS1X1X_COMPARATOR_QUEUE = {
'QUEUE_1': 0x0000, # Assert ALERT/RDY after one conversions
'QUEUE_2': 0x0001, # Assert ALERT/RDY after two conversions
'QUEUE_4': 0x0002, # Assert ALERT/RDY after four conversions
'QUEUE_NONE': 0x0003 # Disable the comparator and put ALERT/RDY
# in high state
}
ADS1X1_OPERATIONS = {
'SET_MUX': 0,
'READ_CONVERSION': 1
}
class ADS1X1X_chip:
def __init__(self, config):
self._printer = config.get_printer()
self._reactor = self._printer.get_reactor()
self.name = config.get_name().split()[-1]
self.chip = config.getchoice('chip', ADS1X1X_CHIP_TYPE)
address = ADS1X1X_CHIP_ADDR['GND']
# If none is specified, i2c_address can be used for a specific address
if config.get('address_pin', None) is not None:
address = config.getchoice('address_pin', ADS1X1X_CHIP_ADDR)
self._ppins = self._printer.lookup_object("pins")
self._ppins.register_chip(self.name, self)
self.pga = config.getchoice('pga', ADS1X1X_PGA, '4.096V')
self.adc_voltage = config.getfloat('adc_voltage', above=0., default=3.3)
# Comparators are not implemented, they would only be useful if the
# alert pin is used, which we haven't made configurable.
# But that wouldn't be useful for a normal temperature sensor anyway.
self.comp_mode = ADS1X1X_COMPARATOR_MODE['TRADITIONAL']
self.comp_polarity = ADS1X1X_COMPARATOR_POLARITY['ACTIVE_LO']
self.comp_latching = ADS1X1X_COMPARATOR_LATCHING['NON_LATCHING']
self.comp_queue = ADS1X1X_COMPARATOR_QUEUE['QUEUE_NONE']
self._i2c = bus.MCU_I2C_from_config(config, address)
self.mcu = self._i2c.get_mcu()
self._printer.add_object("ads1x1x " + self.name, self)
self._printer.register_event_handler("klippy:connect", \
self._handle_connect)
self._pins = {}
self._mutex = self._reactor.mutex()
def setup_pin(self, pin_type, pin_params):
pin = pin_params['pin']
if pin_type == 'adc':
if (pin not in ADS1X1X_MUX):
raise pins.error('ADS1x1x pin %s is not valid' % \
pin_params['pin'])
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.
pcfg |= (ADS1X1X_MODE['single'] & \
ADS1X1X_REG_CONFIG['MODE_MASK'])
# lowest sample rate per default, until report time has been set in
# setup_adc_sample
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, pcfg)
if pin in self._pins:
raise pins.error(
'pin %s for chip %s is used multiple times' \
% (pin, self.name))
self._pins[pin] = pin_obj
return pin_obj
raise pins.error('Wrong pin or incompatible type: %s with type %s! ' % (
pin, pin_type))
def _handle_connect(self):
try:
# Init all devices on bus for this kind of device
self._i2c.i2c_write([0x06, 0x00, 0x00])
except Exception:
logging.exception("ADS1X1X: error while resetting device")
def is_ready(self):
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):
pin_count = len(self._pins)
lowest_report_time = 1
for pin in self._pins.values():
lowest_report_time = min(lowest_report_time, pin.report_time)
sample_rate = 1 / lowest_report_time * pin_count
samples_per_second = ADS111X_SAMPLES_PER_SECOND
if isADS101X(self.chip):
samples_per_second = ADS101X_SAMPLES_PER_SECOND
# make sure the samples list is sorted correctly by number.
samples_per_second = sorted(samples_per_second.items(), \
key=lambda t: int(t[0]))
for rate, bits in samples_per_second:
rate_number = int(rate)
if sample_rate <= rate_number:
return (rate_number, bits)
logging.warning(
"ADS1X1X: requested sample rate %s is higher than supported by %s."\
% (sample_rate, self.name))
return (rate_number, bits)
def handle_report_time_update(self):
(sample_rate, sample_rate_bits) = self.calculate_sample_rate()
for pin in self._pins.values():
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)
def sample(self, pin):
with self._mutex:
try:
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():
self._reactor.pause(self._reactor.monotonic() + 0.001)
# if we waited twice the expected time, mark this an error
if start_time + self.delay < self._reactor.monotonic():
logging.warning("ADS1X1X: timeout during sampling")
return None
return self._read_register(ADS1X1X_REG_POINTER['CONVERSION'])
except Exception as e:
logging.exception("ADS1X1X: error while sampling: %s" % str(e))
return None
def _read_register(self, reg):
# read a single register
params = self._i2c.i2c_read([reg], 2)
buff = bytearray(params['response'])
return (buff[0]<<8 | buff[1])
def _write_register(self, reg, data):
data = [
(reg & 0xFF), # Control register
((data>>8) & 0xFF), # High byte
(data & 0xFF), # Lo byte
]
self._i2c.i2c_write(data)
class ADS1X1X_pin:
def __init__(self, chip, pcfg):
self.mcu = chip.mcu
self.chip = chip
self.pcfg = pcfg
self.invalid_count = 0
self.chip._printer.register_event_handler("klippy:connect", \
self._handle_connect)
def _handle_connect(self):
self._reactor = self.chip._printer.get_reactor()
self._sample_timer = \
self._reactor.register_timer(self._process_sample, \
self._reactor.NOW)
def _process_sample(self, eventtime):
sample = self.chip.sample(self)
if sample is not None:
# The sample is encoded in the top 12 or full 16 bits
# Value's meaning is defined by ADS1X1X_REG_CONFIG['PGA_MASK']
if isADS101X(self.chip.chip):
sample >>= 4
target_value = sample / ADS101X_RESOLUTION
else:
target_value = sample / ADS111X_RESOLUTION
# Thermistors expect a value between 0 and 1 to work. If we use a
# PGA with 4.096V but supply only 3.3V, the reference voltage for
# voltage divider is only 3.3V, not 4.096V. So we remap the range
# from what the PGA allows as range to end up between 0 and 1 for
# the thermistor logic to work as expected.
target_value = target_value * (ADS1X1X_PGA_VALUE[self.chip.pga] / \
self.chip.adc_voltage)
if target_value > self.maxval or target_value < self.minval:
self.invalid_count = self.invalid_count + 1
logging.warning("ADS1X1X: temperature outside range")
self.check_invalid()
else:
self.invalid_count = 0
# Publish result
measured_time = self._reactor.monotonic()
self.callback(self.chip.mcu.estimated_print_time(measured_time),
target_value)
else:
self.invalid_count = self.invalid_count + 1
self.check_invalid()
return eventtime + self.report_time
def check_invalid(self):
if self.invalid_count > self.range_check_count:
self.chip._printer.invoke_shutdown(
"ADS1X1X temperature check failed")
def get_mcu(self):
return self.mcu
def setup_adc_callback(self, report_time, callback):
self.report_time = report_time
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):
self.minval = minval
self.maxval = maxval
self.range_check_count = range_check_count
def load_config_prefix(config):
return ADS1X1X_chip(config)

View File

@@ -166,12 +166,12 @@ class AccelCommandHelper:
% (accel_x, accel_y, accel_z)) % (accel_x, accel_y, accel_z))
cmd_ACCELEROMETER_DEBUG_READ_help = "Query register (for debugging)" cmd_ACCELEROMETER_DEBUG_READ_help = "Query register (for debugging)"
def cmd_ACCELEROMETER_DEBUG_READ(self, gcmd): def cmd_ACCELEROMETER_DEBUG_READ(self, gcmd):
reg = gcmd.get("REG", minval=0, maxval=127, parser=lambda x: int(x, 0)) reg = gcmd.get("REG", minval=0, maxval=126, parser=lambda x: int(x, 0))
val = self.chip.read_reg(reg) val = self.chip.read_reg(reg)
gcmd.respond_info("Accelerometer REG[0x%x] = 0x%x" % (reg, val)) gcmd.respond_info("Accelerometer REG[0x%x] = 0x%x" % (reg, val))
cmd_ACCELEROMETER_DEBUG_WRITE_help = "Set register (for debugging)" cmd_ACCELEROMETER_DEBUG_WRITE_help = "Set register (for debugging)"
def cmd_ACCELEROMETER_DEBUG_WRITE(self, gcmd): def cmd_ACCELEROMETER_DEBUG_WRITE(self, gcmd):
reg = gcmd.get("REG", minval=0, maxval=127, parser=lambda x: int(x, 0)) reg = gcmd.get("REG", minval=0, maxval=126, parser=lambda x: int(x, 0))
val = gcmd.get("VAL", minval=0, maxval=255, parser=lambda x: int(x, 0)) val = gcmd.get("VAL", minval=0, maxval=255, parser=lambda x: int(x, 0))
self.chip.set_reg(reg, val) self.chip.set_reg(reg, val)

View File

@@ -97,7 +97,7 @@ class AngleCalibration:
return None return None
return self.mcu_stepper.mcu_to_commanded_position(self.mcu_pos_offset) return self.mcu_stepper.mcu_to_commanded_position(self.mcu_pos_offset)
def load_calibration(self, angles): def load_calibration(self, angles):
# Calculate linear interpolation calibration buckets by solving # Calculate linear intepolation calibration buckets by solving
# linear equations # linear equations
angle_max = 1 << ANGLE_BITS angle_max = 1 << ANGLE_BITS
calibration_count = 1 << CALIBRATION_BITS calibration_count = 1 << CALIBRATION_BITS
@@ -411,196 +411,6 @@ class HelperTLE5012B:
parser=lambda x: int(x, 0)) parser=lambda x: int(x, 0))
self._write_reg(reg, val) self._write_reg(reg, val)
class HelperMT6816:
SPI_MODE = 3
SPI_SPEED = 10000000
def __init__(self, config, spi, oid):
self.printer = config.get_printer()
self.spi = spi
self.oid = oid
self.mcu = spi.get_mcu()
self.mcu.register_config_callback(self._build_config)
self.spi_angle_transfer_cmd = None
self.is_tcode_absolute = False
self.last_temperature = None
name = config.get_name().split()[-1]
gcode = self.printer.lookup_object("gcode")
gcode.register_mux_command("ANGLE_DEBUG_READ", "CHIP", name,
self.cmd_ANGLE_DEBUG_READ,
desc=self.cmd_ANGLE_DEBUG_READ_help)
def _build_config(self):
cmdqueue = self.spi.get_command_queue()
self.spi_angle_transfer_cmd = self.mcu.lookup_query_command(
"spi_angle_transfer oid=%c data=%*s",
"spi_angle_transfer_response oid=%c clock=%u response=%*s",
oid=self.oid, cq=cmdqueue)
def _send_spi(self, msg):
return self.spi.spi_transfer(msg)
def get_static_delay(self):
return .000001
def _read_reg(self, reg):
msg = [reg, 0, 0]
params = self._send_spi(msg)
resp = bytearray(params['response'])
val = (resp[1] << 8) | resp[2]
return val
def start(self):
pass
cmd_ANGLE_DEBUG_READ_help = "Query low-level angle sensor register"
def cmd_ANGLE_DEBUG_READ(self, gcmd):
reg = 0x83
val = self._read_reg(reg)
gcmd.respond_info("ANGLE REG[0x%02x] = 0x%04x" % (reg, val))
angle = val >> 2
parity = bin(val >> 1).count("1") % 2
gcmd.respond_info("Angle %i ~ %.2f" % (angle, angle * 360 / (1 << 14)))
gcmd.respond_info("No Mag: %i" % (val >> 1 & 0x1))
gcmd.respond_info("Parity: %i == %i" % (parity, val & 0x1))
class HelperMT6826S:
SPI_MODE = 3
SPI_SPEED = 10000000
def __init__(self, config, spi, oid):
self.printer = config.get_printer()
self.stepper_name = config.get('stepper', None)
self.spi = spi
self.oid = oid
self.mcu = spi.get_mcu()
self.mcu.register_config_callback(self._build_config)
self.spi_angle_transfer_cmd = None
self.is_tcode_absolute = False
self.last_temperature = None
name = config.get_name().split()[-1]
gcode = self.printer.lookup_object("gcode")
gcode.register_mux_command("ANGLE_DEBUG_READ", "CHIP", name,
self.cmd_ANGLE_DEBUG_READ,
desc=self.cmd_ANGLE_DEBUG_READ_help)
gcode.register_mux_command("ANGLE_CHIP_CALIBRATE", "CHIP", name,
self.cmd_ANGLE_CHIP_CALIBRATE,
desc=self.cmd_ANGLE_CHIP_CALIBRATE_help)
self.status_map = {
0: "No Calibration",
1: "Running Calibration",
2: "Calibration Failed",
3: "Calibration Successful"
}
def _build_config(self):
cmdqueue = self.spi.get_command_queue()
self.spi_angle_transfer_cmd = self.mcu.lookup_query_command(
"spi_angle_transfer oid=%c data=%*s",
"spi_angle_transfer_response oid=%c clock=%u response=%*s",
oid=self.oid, cq=cmdqueue)
def _send_spi(self, msg):
params = self.spi.spi_transfer(msg)
return params
def get_static_delay(self):
return .00001
def _read_reg(self, reg):
reg = 0x3000 | reg
msg = [reg >> 8, reg & 0xff, 0]
params = self._send_spi(msg)
resp = bytearray(params['response'])
return resp[2]
def _write_reg(self, reg, data):
reg = 0x6000 | reg
msg = [reg >> 8, reg & 0xff, data]
self._send_spi(msg)
def crc8(self, data):
polynomial = 0x07
crc = 0x00
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 0x80:
crc = (crc << 1) ^ polynomial
else:
crc <<= 1
crc &= 0xFF
return crc
def _read_angle(self, reg):
reg = 0x3000 | reg
msg = [reg >> 8, reg & 0xff, 0, 0, 0, 0]
params = self._send_spi(msg)
resp = bytearray(params['response'])
angle = (resp[2] << 7) | (resp[3] >> 1)
status = resp[4]
crc_computed = self.crc8([resp[2], resp[3], resp[4]])
crc = resp[5]
return angle, status, crc, crc_computed
def start(self):
val = self._read_reg(0x00d)
# Set histeresis to 0.003 degree
self._write_reg(0x00d, (val & 0xf8) | 0x5)
def get_microsteps(self):
configfile = self.printer.lookup_object('configfile')
sconfig = configfile.get_status(None)['settings']
stconfig = sconfig.get(self.stepper_name, {})
microsteps = stconfig['microsteps']
full_steps = stconfig['full_steps_per_rotation']
return microsteps, full_steps
cmd_ANGLE_CHIP_CALIBRATE_help = "Run MT6826s calibration sequence"
def cmd_ANGLE_CHIP_CALIBRATE(self, gcmd):
fmove = self.printer.lookup_object('force_move')
mcu_stepper = fmove.lookup_stepper(self.stepper_name)
if self.stepper_name is None:
gcmd.respond_info("stepper not defined")
return
gcmd.respond_info("MT6826S Run calibration sequence")
gcmd.respond_info("Motor will do 18+ rotations -" +
" ensure pulley is disconnected")
req_freq = self._read_reg(0x00e) >> 4 & 0x7
# Minimal calibration speed
rpm = (3200 >> req_freq) + 1
rps = rpm / 60
move = fmove.manual_move
# Move stepper several turns (to allow internal sensor calibration)
microsteps, full_steps = self.get_microsteps()
step_dist = mcu_stepper.get_step_dist()
full_step_dist = step_dist * microsteps
rotation_dist = full_steps * full_step_dist
move(mcu_stepper, 2 * rotation_dist, rps * rotation_dist)
self._write_reg(0x155, 0x5e)
move(mcu_stepper, 20 * rotation_dist, rps * rotation_dist)
val = self._read_reg(0x113)
code = val >> 6
gcmd.respond_info("Status: %s" % (self.status_map[code]))
while code == 1:
move(mcu_stepper, 5 * rotation_dist, rps * rotation_dist)
val = self._read_reg(0x113)
code = val >> 6
gcmd.respond_info("Status: %s" % (self.status_map[code]))
if code == 2:
gcmd.respond_info("Calibration failed")
if code == 3:
gcmd.respond_info("Calibration success, please poweroff sensor")
cmd_ANGLE_DEBUG_READ_help = "Query low-level angle sensor register"
def cmd_ANGLE_DEBUG_READ(self, gcmd):
reg = gcmd.get("REG", minval=0, maxval=0x155,
parser=lambda x: int(x, 0))
if reg == 0x003:
angle, status, crc1, crc2 = self._read_angle(reg)
gcmd.respond_info("ANGLE REG[0x003] = 0x%02x" %
(angle >> 7))
gcmd.respond_info("ANGLE REG[0x004] = 0x%02x" %
((angle << 1) & 0xff))
gcmd.respond_info("Angle %i ~ %.2f" % (angle,
angle * 360 / (1 << 15)))
gcmd.respond_info("Weak Mag: %i" % (status >> 1 & 0x1))
gcmd.respond_info("Under Voltage: %i" % (status >> 2 & 0x1))
gcmd.respond_info("CRC: 0x%02x == 0x%02x" % (crc1, crc2))
elif reg == 0x00e:
val = self._read_reg(reg)
gcmd.respond_info("GPIO_DS = %i" % (val >> 7))
gcmd.respond_info("AUTOCAL_FREQ = %i" % (val >> 4 & 0x7))
elif reg == 0x113:
val = self._read_reg(reg)
gcmd.respond_info("Status: %s" % (self.cal_status[val >> 6]))
else:
val = self._read_reg(reg)
gcmd.respond_info("REG[0x%04x] = 0x%02x" % (reg, val))
BYTES_PER_SAMPLE = 3 BYTES_PER_SAMPLE = 3
SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE
@@ -617,11 +427,8 @@ class Angle:
self.start_clock = self.time_shift = self.sample_ticks = 0 self.start_clock = self.time_shift = self.sample_ticks = 0
self.last_sequence = self.last_angle = 0 self.last_sequence = self.last_angle = 0
# Sensor type # Sensor type
sensors = { "a1333": HelperA1333, sensors = { "a1333": HelperA1333, "as5047d": HelperAS5047D,
"as5047d": HelperAS5047D, "tle5012b": HelperTLE5012B }
"tle5012b": HelperTLE5012B,
"mt6816": HelperMT6816,
"mt6826s": HelperMT6826S }
sensor_type = config.getchoice('sensor_type', {s: s for s in sensors}) sensor_type = config.getchoice('sensor_type', {s: s for s in sensors})
sensor_class = sensors[sensor_type] sensor_class = sensors[sensor_type]
self.spi = bus.MCU_SPI_from_config(config, sensor_class.SPI_MODE, self.spi = bus.MCU_SPI_from_config(config, sensor_class.SPI_MODE,

View File

@@ -23,27 +23,18 @@ class AxisTwistCompensation:
self.horizontal_move_z = config.getfloat('horizontal_move_z', self.horizontal_move_z = config.getfloat('horizontal_move_z',
DEFAULT_HORIZONTAL_MOVE_Z) DEFAULT_HORIZONTAL_MOVE_Z)
self.speed = config.getfloat('speed', DEFAULT_SPEED) self.speed = config.getfloat('speed', DEFAULT_SPEED)
self.calibrate_start_x = config.getfloat('calibrate_start_x', self.calibrate_start_x = config.getfloat('calibrate_start_x')
default=None) self.calibrate_end_x = config.getfloat('calibrate_end_x')
self.calibrate_end_x = config.getfloat('calibrate_end_x', default=None) self.calibrate_y = config.getfloat('calibrate_y')
self.calibrate_y = config.getfloat('calibrate_y', default=None)
self.z_compensations = config.getlists('z_compensations', self.z_compensations = config.getlists('z_compensations',
default=[], parser=float) default=[], parser=float)
self.compensation_start_x = config.getfloat('compensation_start_x', self.compensation_start_x = config.getfloat('compensation_start_x',
default=None) default=None)
self.compensation_end_x = config.getfloat('compensation_end_x', self.compensation_end_x = config.getfloat('compensation_start_y',
default=None) default=None)
self.calibrate_start_y = config.getfloat('calibrate_start_y', self.m = None
default=None) self.b = None
self.calibrate_end_y = config.getfloat('calibrate_end_y', default=None)
self.calibrate_x = config.getfloat('calibrate_x', default=None)
self.compensation_start_y = config.getfloat('compensation_start_y',
default=None)
self.compensation_end_y = config.getfloat('compensation_end_y',
default=None)
self.zy_compensations = config.getlists('zy_compensations',
default=[], parser=float)
# setup calibrater # setup calibrater
self.calibrater = Calibrater(self, config) self.calibrater = Calibrater(self, config)
@@ -52,46 +43,28 @@ class AxisTwistCompensation:
self._update_z_compensation_value) self._update_z_compensation_value)
def _update_z_compensation_value(self, pos): def _update_z_compensation_value(self, pos):
if self.z_compensations: if not self.z_compensations:
pos[2] += self._get_interpolated_z_compensation( return
pos[0], 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
)
def _get_interpolated_z_compensation(
self, coord, z_compensations,
comp_start,
comp_end
):
x_coord = pos[0]
z_compensations = self.z_compensations
sample_count = len(z_compensations) sample_count = len(z_compensations)
spacing = ((comp_end - comp_start) spacing = ((self.calibrate_end_x - self.calibrate_start_x)
/ (sample_count - 1)) / (sample_count - 1))
interpolate_t = (coord - comp_start) / spacing interpolate_t = (x_coord - self.calibrate_start_x) / spacing
interpolate_i = int(math.floor(interpolate_t)) interpolate_i = int(math.floor(interpolate_t))
interpolate_i = bed_mesh.constrain(interpolate_i, 0, sample_count - 2) interpolate_i = bed_mesh.constrain(interpolate_i, 0, sample_count - 2)
interpolate_t -= interpolate_i interpolate_t -= interpolate_i
interpolated_z_compensation = bed_mesh.lerp( interpolated_z_compensation = bed_mesh.lerp(
interpolate_t, z_compensations[interpolate_i], interpolate_t, z_compensations[interpolate_i],
z_compensations[interpolate_i + 1]) z_compensations[interpolate_i + 1])
return interpolated_z_compensation pos[2] += interpolated_z_compensation
def clear_compensations(self):
self.z_compensations = []
self.m = None
self.b = None
def clear_compensations(self, axis=None):
if axis is None:
self.z_compensations = []
self.zy_compensations = []
elif axis == 'X':
self.z_compensations = []
elif axis == 'Y':
self.zy_compensations = []
class Calibrater: class Calibrater:
def __init__(self, compensation, config): def __init__(self, compensation, config):
@@ -107,14 +80,10 @@ class Calibrater:
self._handle_connect) self._handle_connect)
self.speed = compensation.speed self.speed = compensation.speed
self.horizontal_move_z = compensation.horizontal_move_z self.horizontal_move_z = compensation.horizontal_move_z
self.x_start_point = (compensation.calibrate_start_x, self.start_point = (compensation.calibrate_start_x,
compensation.calibrate_y) compensation.calibrate_y)
self.x_end_point = (compensation.calibrate_end_x, self.end_point = (compensation.calibrate_end_x,
compensation.calibrate_y) compensation.calibrate_y)
self.y_start_point = (compensation.calibrate_x,
compensation.calibrate_start_y)
self.y_end_point = (compensation.calibrate_x,
compensation.calibrate_end_y)
self.results = None self.results = None
self.current_point_index = None self.current_point_index = None
self.gcmd = None self.gcmd = None
@@ -125,8 +94,9 @@ class Calibrater:
def _handle_connect(self): def _handle_connect(self):
self.probe = self.printer.lookup_object('probe', None) self.probe = self.printer.lookup_object('probe', None)
if self.probe is None: if (self.probe is None):
raise self.printer.config_error( config = self.printer.lookup_object('configfile')
raise config.error(
"AXIS_TWIST_COMPENSATION requires [probe] to be defined") "AXIS_TWIST_COMPENSATION requires [probe] to be defined")
self.lift_speed = self.probe.get_probe_params()['lift_speed'] self.lift_speed = self.probe.get_probe_params()['lift_speed']
self.probe_x_offset, self.probe_y_offset, _ = \ self.probe_x_offset, self.probe_y_offset, _ = \
@@ -149,75 +119,20 @@ class Calibrater:
def cmd_AXIS_TWIST_COMPENSATION_CALIBRATE(self, gcmd): def cmd_AXIS_TWIST_COMPENSATION_CALIBRATE(self, gcmd):
self.gcmd = gcmd self.gcmd = gcmd
sample_count = gcmd.get_int('SAMPLE_COUNT', DEFAULT_SAMPLE_COUNT) sample_count = gcmd.get_int('SAMPLE_COUNT', DEFAULT_SAMPLE_COUNT)
axis = gcmd.get('AXIS', 'X')
# check for valid sample_count # check for valid sample_count
if sample_count < 2: if sample_count is None or sample_count < 2:
raise self.gcmd.error( raise self.gcmd.error(
"SAMPLE_COUNT to probe must be at least 2") "SAMPLE_COUNT to probe must be at least 2")
# calculate the points to put the probe at, returned as a list of tuples # clear the current config
nozzle_points = [] self.compensation.clear_compensations()
if axis == 'X':
self.compensation.clear_compensations('X')
if not all([
self.x_start_point[0],
self.x_end_point[0],
self.x_start_point[1]
]):
raise self.gcmd.error(
"""AXIS_TWIST_COMPENSATION for X axis requires
calibrate_start_x, calibrate_end_x and calibrate_y
to be defined
"""
)
start_point = self.x_start_point
end_point = self.x_end_point
x_axis_range = end_point[0] - start_point[0]
interval_dist = x_axis_range / (sample_count - 1)
for i in range(sample_count):
x = start_point[0] + i * interval_dist
y = start_point[1]
nozzle_points.append((x, y))
elif axis == 'Y':
self.compensation.clear_compensations('Y')
if not all([
self.y_start_point[0],
self.y_end_point[0],
self.y_start_point[1]
]):
raise self.gcmd.error(
"""AXIS_TWIST_COMPENSATION for Y axis requires
calibrate_start_y, calibrate_end_y and calibrate_x
to be defined
"""
)
start_point = self.y_start_point
end_point = self.y_end_point
y_axis_range = end_point[1] - start_point[1]
interval_dist = y_axis_range / (sample_count - 1)
for i in range(sample_count):
x = start_point[0]
y = start_point[1] + i * interval_dist
nozzle_points.append((x, y))
else:
raise self.gcmd.error(
"AXIS_TWIST_COMPENSATION_CALIBRATE: "
"Invalid axis.")
# calculate some values
x_range = self.end_point[0] - self.start_point[0]
interval_dist = x_range / (sample_count - 1)
nozzle_points = self._calculate_nozzle_points(sample_count,
interval_dist)
probe_points = self._calculate_probe_points( probe_points = self._calculate_probe_points(
nozzle_points, self.probe_x_offset, self.probe_y_offset) nozzle_points, self.probe_x_offset, self.probe_y_offset)
@@ -227,9 +142,17 @@ class Calibrater:
# begin calibration # begin calibration
self.current_point_index = 0 self.current_point_index = 0
self.results = [] self.results = []
self.current_axis = axis
self._calibration(probe_points, nozzle_points, interval_dist) self._calibration(probe_points, nozzle_points, interval_dist)
def _calculate_nozzle_points(self, sample_count, interval_dist):
# calculate the points to put the probe at, returned as a list of tuples
nozzle_points = []
for i in range(sample_count):
x = self.start_point[0] + i * interval_dist
y = self.start_point[1]
nozzle_points.append((x, y))
return nozzle_points
def _calculate_probe_points(self, nozzle_points, 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 # calculate the points to put the nozzle at
@@ -315,31 +238,14 @@ class Calibrater:
configfile = self.printer.lookup_object('configfile') configfile = self.printer.lookup_object('configfile')
values_as_str = ', '.join(["{:.6f}".format(x) values_as_str = ', '.join(["{:.6f}".format(x)
for x in self.results]) for x in self.results])
configfile.set(self.configname, 'z_compensations', values_as_str)
if(self.current_axis == 'X'): configfile.set(self.configname, 'compensation_start_x',
self.start_point[0])
configfile.set(self.configname, 'z_compensations', values_as_str) configfile.set(self.configname, 'compensation_end_x',
configfile.set(self.configname, 'compensation_start_x', self.end_point[0])
self.x_start_point[0]) self.compensation.z_compensations = self.results
configfile.set(self.configname, 'compensation_end_x', self.compensation.compensation_start_x = self.start_point[0]
self.x_end_point[0]) self.compensation.compensation_end_x = self.end_point[0]
self.compensation.z_compensations = self.results
self.compensation.compensation_start_x = self.x_start_point[0]
self.compensation.compensation_end_x = self.x_end_point[0]
elif(self.current_axis == 'Y'):
configfile.set(self.configname, 'zy_compensations', values_as_str)
configfile.set(self.configname, 'compensation_start_y',
self.y_start_point[1])
configfile.set(self.configname, 'compensation_end_y',
self.y_end_point[1])
self.compensation.zy_compensations = self.results
self.compensation.compensation_start_y = self.y_start_point[1]
self.compensation.compensation_end_y = self.y_end_point[1]
self.gcode.respond_info( self.gcode.respond_info(
"AXIS_TWIST_COMPENSATION state has been saved " "AXIS_TWIST_COMPENSATION state has been saved "
"for the current session. The SAVE_CONFIG command will " "for the current session. The SAVE_CONFIG command will "

View File

@@ -34,7 +34,7 @@ def constrain(val, min_val, max_val):
def lerp(t, v0, v1): def lerp(t, v0, v1):
return (1. - t) * v0 + t * v1 return (1. - t) * v0 + t * v1
# retrieve comma separated pair from config # retreive commma separated pair from config
def parse_config_pair(config, option, default, minval=None, maxval=None): def parse_config_pair(config, option, default, minval=None, maxval=None):
pair = config.getintlist(option, (default, default)) pair = config.getintlist(option, (default, default))
if len(pair) != 2: if len(pair) != 2:
@@ -54,7 +54,7 @@ def parse_config_pair(config, option, default, minval=None, maxval=None):
% (option, str(maxval))) % (option, str(maxval)))
return pair return pair
# retrieve comma separated pair from a g-code command # retreive commma separated pair from a g-code command
def parse_gcmd_pair(gcmd, name, minval=None, maxval=None): def parse_gcmd_pair(gcmd, name, minval=None, maxval=None):
try: try:
pair = [int(v.strip()) for v in gcmd.get(name).split(',')] 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)) % (name, maxval))
return pair return pair
# retrieve comma separated coordinate from a g-code command # retreive commma separated coordinate from a g-code command
def parse_gcmd_coord(gcmd, name): def parse_gcmd_coord(gcmd, name):
try: try:
v1, v2 = [float(v.strip()) for v in gcmd.get(name).split(',')] v1, v2 = [float(v.strip()) for v in gcmd.get(name).split(',')]
@@ -133,7 +133,7 @@ class BedMesh:
self.update_status() self.update_status()
def handle_connect(self): def handle_connect(self):
self.toolhead = self.printer.lookup_object('toolhead') self.toolhead = self.printer.lookup_object('toolhead')
self.bmc.print_generated_points(logging.info, truncate=True) self.bmc.print_generated_points(logging.info)
def set_mesh(self, mesh): def set_mesh(self, mesh):
if mesh is not None and self.fade_end != self.FADE_DISABLE: if mesh is not None and self.fade_end != self.FADE_DISABLE:
self.log_fade_complete = True self.log_fade_complete = True
@@ -186,8 +186,7 @@ class BedMesh:
self.last_position[2] -= self.fade_target self.last_position[2] -= self.fade_target
else: else:
# return current position minus the current z-adjustment # return current position minus the current z-adjustment
cur_pos = self.toolhead.get_position() x, y, z, e = self.toolhead.get_position()
x, y, z = cur_pos[:3]
max_adj = self.z_mesh.calc_z(x, y) max_adj = self.z_mesh.calc_z(x, y)
factor = 1. factor = 1.
z_adj = max_adj - self.fade_target z_adj = max_adj - self.fade_target
@@ -203,19 +202,19 @@ class BedMesh:
(self.fade_dist - z_adj)) (self.fade_dist - z_adj))
factor = constrain(factor, 0., 1.) factor = constrain(factor, 0., 1.)
final_z_adj = factor * z_adj + self.fade_target final_z_adj = factor * z_adj + self.fade_target
self.last_position[:] = [x, y, z - final_z_adj] + cur_pos[3:] self.last_position[:] = [x, y, z - final_z_adj, e]
return list(self.last_position) return list(self.last_position)
def move(self, newpos, speed): def move(self, newpos, speed):
factor = self.get_z_factor(newpos[2]) factor = self.get_z_factor(newpos[2])
if self.z_mesh is None or not factor: if self.z_mesh is None or not factor:
# No mesh calibrated, or mesh leveling phased out. # No mesh calibrated, or mesh leveling phased out.
x, y, z = newpos[:3] x, y, z, e = newpos
if self.log_fade_complete: if self.log_fade_complete:
self.log_fade_complete = False self.log_fade_complete = False
logging.info( logging.info(
"bed_mesh fade complete: Current Z: %.4f fade_target: %.4f " "bed_mesh fade complete: Current Z: %.4f fade_target: %.4f "
% (z, self.fade_target)) % (z, self.fade_target))
self.toolhead.move([x, y, z + self.fade_target] + newpos[3:], speed) self.toolhead.move([x, y, z + self.fade_target, e], speed)
else: else:
self.splitter.build_move(self.last_position, newpos, factor) self.splitter.build_move(self.last_position, newpos, factor)
while not self.splitter.traverse_complete: while not self.splitter.traverse_complete:
@@ -347,7 +346,7 @@ class BedMeshCalibrate:
self.gcode.register_command( self.gcode.register_command(
'BED_MESH_CALIBRATE', self.cmd_BED_MESH_CALIBRATE, 'BED_MESH_CALIBRATE', self.cmd_BED_MESH_CALIBRATE,
desc=self.cmd_BED_MESH_CALIBRATE_help) desc=self.cmd_BED_MESH_CALIBRATE_help)
def print_generated_points(self, print_func, truncate=False): def print_generated_points(self, print_func):
x_offset = y_offset = 0. x_offset = y_offset = 0.
probe = self.printer.lookup_object('probe', None) probe = self.printer.lookup_object('probe', None)
if probe is not None: if probe is not None:
@@ -356,10 +355,6 @@ class BedMeshCalibrate:
" | Tool Adjusted | Probe") " | Tool Adjusted | Probe")
points = self.probe_mgr.get_base_points() points = self.probe_mgr.get_base_points()
for i, (x, y) in enumerate(points): for i, (x, y) in enumerate(points):
if i >= 50 and truncate:
end = len(points) - 1
print_func("...points %d through %d truncated" % (i, end))
break
adj_pt = "(%.1f, %.1f)" % (x - x_offset, y - y_offset) adj_pt = "(%.1f, %.1f)" % (x - x_offset, y - y_offset)
mesh_pt = "(%.1f, %.1f)" % (x, y) mesh_pt = "(%.1f, %.1f)" % (x, y)
print_func( print_func(
@@ -618,6 +613,8 @@ class BedMeshCalibrate:
self.mesh_config, self.mesh_min, self.mesh_max, self.mesh_config, self.mesh_min, self.mesh_max,
self.radius, self.origin, probe_method self.radius, self.origin, probe_method
) )
gcmd.respond_info("Generating new points...")
self.print_generated_points(gcmd.respond_info)
msg = "\n".join(["%s: %s" % (k, v) msg = "\n".join(["%s: %s" % (k, v)
for k, v in self.mesh_config.items()]) for k, v in self.mesh_config.items()])
logging.info("Updated Mesh Configuration:\n" + msg) logging.info("Updated Mesh Configuration:\n" + msg)
@@ -914,7 +911,7 @@ class ProbeManager:
for i in range(y_cnt): for i in range(y_cnt):
for j in range(x_cnt): for j in range(x_cnt):
if not i % 2: if not i % 2:
# move in positive direction # move in positive directon
pos_x = min_x + j * x_dist pos_x = min_x + j * x_dist
else: else:
# move in negative direction # move in negative direction
@@ -1164,7 +1161,7 @@ class ProbeManager:
def _gen_arc(self, origin, radius, start, step, count): def _gen_arc(self, origin, radius, start, step, count):
end = start + step * count end = start + step * count
# create a segent for every 3 degrees of travel # create a segent for every 3 degress of travel
for angle in range(start, end, step): for angle in range(start, end, step):
rad = math.radians(angle % 360) rad = math.radians(angle % 360)
opp = math.sin(rad) * radius opp = math.sin(rad) * radius
@@ -1274,7 +1271,7 @@ class MoveSplitter:
self.z_offset = self._calc_z_offset(prev_pos) self.z_offset = self._calc_z_offset(prev_pos)
self.traverse_complete = False self.traverse_complete = False
self.distance_checked = 0. self.distance_checked = 0.
axes_d = [np - pp for np, pp in zip(self.next_pos, self.prev_pos)] axes_d = [self.next_pos[i] - self.prev_pos[i] for i in range(4)]
self.total_move_length = math.sqrt(sum([d*d for d in axes_d[:3]])) self.total_move_length = math.sqrt(sum([d*d for d in axes_d[:3]]))
self.axis_move = [not isclose(d, 0., abs_tol=1e-10) for d in axes_d] self.axis_move = [not isclose(d, 0., abs_tol=1e-10) for d in axes_d]
def _calc_z_offset(self, pos): def _calc_z_offset(self, pos):
@@ -1287,7 +1284,7 @@ class MoveSplitter:
raise self.gcode.error( raise self.gcode.error(
"bed_mesh: Slice distance is negative " "bed_mesh: Slice distance is negative "
"or greater than entire move length") "or greater than entire move length")
for i in range(len(self.next_pos)): for i in range(4):
if self.axis_move[i]: if self.axis_move[i]:
self.current_pos[i] = lerp( self.current_pos[i] = lerp(
t, self.prev_pos[i], self.next_pos[i]) t, self.prev_pos[i], self.next_pos[i])
@@ -1302,9 +1299,9 @@ class MoveSplitter:
next_z = self._calc_z_offset(self.current_pos) next_z = self._calc_z_offset(self.current_pos)
if abs(next_z - self.z_offset) >= self.split_delta_z: if abs(next_z - self.z_offset) >= self.split_delta_z:
self.z_offset = next_z self.z_offset = next_z
newpos = list(self.current_pos) return self.current_pos[0], self.current_pos[1], \
newpos[2] += self.z_offset self.current_pos[2] + self.z_offset, \
return newpos self.current_pos[3]
# end of move reached # end of move reached
self.current_pos[:] = self.next_pos self.current_pos[:] = self.next_pos
self.z_offset = self._calc_z_offset(self.current_pos) self.z_offset = self._calc_z_offset(self.current_pos)

View File

@@ -24,14 +24,12 @@ class BedTilt:
def handle_connect(self): def handle_connect(self):
self.toolhead = self.printer.lookup_object('toolhead') self.toolhead = self.printer.lookup_object('toolhead')
def get_position(self): def get_position(self):
pos = self.toolhead.get_position() x, y, z, e = self.toolhead.get_position()
x, y, z = pos[:3] return [x, y, z - x*self.x_adjust - y*self.y_adjust - self.z_adjust, e]
z -= x*self.x_adjust + y*self.y_adjust + self.z_adjust
return [x, y, z] + pos[3:]
def move(self, newpos, speed): def move(self, newpos, speed):
x, y, z = newpos[:3] x, y, z, e = newpos
z += x*self.x_adjust + y*self.y_adjust + self.z_adjust self.toolhead.move([x, y, z + x*self.x_adjust + y*self.y_adjust
self.toolhead.move([x, y, z] + newpos[3:], speed) + self.z_adjust, e], speed)
def update_adjust(self, x_adjust, y_adjust, z_adjust): def update_adjust(self, x_adjust, y_adjust, z_adjust):
self.x_adjust = x_adjust self.x_adjust = x_adjust
self.y_adjust = y_adjust self.y_adjust = y_adjust

View File

@@ -64,11 +64,7 @@ class BLTouchProbe:
self.cmd_helper = probe.ProbeCommandHelper( self.cmd_helper = probe.ProbeCommandHelper(
config, self, self.mcu_endstop.query_endstop) config, self, self.mcu_endstop.query_endstop)
self.probe_offsets = probe.ProbeOffsetsHelper(config) self.probe_offsets = probe.ProbeOffsetsHelper(config)
self.param_helper = probe.ProbeParameterHelper(config) self.probe_session = probe.ProbeSessionHelper(config, self)
self.homing_helper = probe.HomingViaProbeHelper(config, self,
self.param_helper)
self.probe_session = probe.ProbeSessionHelper(
config, self.param_helper, self.homing_helper.start_probe_session)
# Register BLTOUCH_DEBUG command # Register BLTOUCH_DEBUG command
self.gcode = self.printer.lookup_object('gcode') self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command("BLTOUCH_DEBUG", self.cmd_BLTOUCH_DEBUG, self.gcode.register_command("BLTOUCH_DEBUG", self.cmd_BLTOUCH_DEBUG,
@@ -79,7 +75,7 @@ class BLTouchProbe:
self.printer.register_event_handler("klippy:connect", self.printer.register_event_handler("klippy:connect",
self.handle_connect) self.handle_connect)
def get_probe_params(self, gcmd=None): def get_probe_params(self, gcmd=None):
return self.param_helper.get_probe_params(gcmd) return self.probe_session.get_probe_params(gcmd)
def get_offsets(self): def get_offsets(self):
return self.probe_offsets.get_offsets() return self.probe_offsets.get_offsets()
def get_status(self, eventtime): def get_status(self, eventtime):
@@ -195,6 +191,9 @@ class BLTouchProbe:
self.verify_raise_probe() self.verify_raise_probe()
self.sync_print_time() self.sync_print_time()
self.multi = 'OFF' self.multi = 'OFF'
def probing_move(self, pos, speed):
phoming = self.printer.lookup_object('homing')
return phoming.probing_move(self, pos, speed)
def probe_prepare(self, hmove): def probe_prepare(self, hmove):
if self.multi == 'OFF' or self.multi == 'FIRST': if self.multi == 'OFF' or self.multi == 'FIRST':
self.lower_probe() self.lower_probe()

View File

@@ -83,7 +83,6 @@ BMP180_REGS = {
STATUS_MEASURING = 1 << 3 STATUS_MEASURING = 1 << 3
STATUS_IM_UPDATE = 1 STATUS_IM_UPDATE = 1
MODE = 1 MODE = 1
MODE_PERIODIC = 3
RUN_GAS = 1 << 4 RUN_GAS = 1 << 4
NB_CONV_0 = 0 NB_CONV_0 = 0
EAS_NEW_DATA = 1 << 7 EAS_NEW_DATA = 1 << 7
@@ -144,7 +143,6 @@ class BME280:
pow(2, self.os_temp - 1), pow(2, self.os_hum - 1), pow(2, self.os_temp - 1), pow(2, self.os_hum - 1),
pow(2, self.os_pres - 1))) pow(2, self.os_pres - 1)))
logging.info("BMxx80: IIR: %dx" % (pow(2, self.iir_filter) - 1)) logging.info("BMxx80: IIR: %dx" % (pow(2, self.iir_filter) - 1))
self.iir_filter = self.iir_filter & 0x07
self.temp = self.pressure = self.humidity = self.gas = self.t_fine = 0. self.temp = self.pressure = self.humidity = self.gas = self.t_fine = 0.
self.min_temp = self.max_temp = self.range_switching_error = 0. self.min_temp = self.max_temp = self.range_switching_error = 0.
@@ -157,7 +155,6 @@ class BME280:
return return
self.printer.register_event_handler("klippy:connect", self.printer.register_event_handler("klippy:connect",
self.handle_connect) self.handle_connect)
self.last_gas_time = 0
def handle_connect(self): def handle_connect(self):
self._init_bmxx80() self._init_bmxx80()
@@ -296,15 +293,15 @@ class BME280:
status = self.read_register('STATUS', 1)[0] status = self.read_register('STATUS', 1)[0]
if self.chip_type == 'BME680': if self.chip_type == 'BME680':
self.max_sample_time = \ self.max_sample_time = 0.5
(1.25 + (2.3 * self.os_temp) + ((2.3 * self.os_pres) + .575)
+ ((2.3 * self.os_hum) + .575)) / 1000
self.sample_timer = self.reactor.register_timer(self._sample_bme680) self.sample_timer = self.reactor.register_timer(self._sample_bme680)
self.chip_registers = BME680_REGS self.chip_registers = BME680_REGS
elif self.chip_type == 'BMP180': elif self.chip_type == 'BMP180':
self.max_sample_time = (1.25 + ((2.3 * self.os_pres) + .575)) / 1000
self.sample_timer = self.reactor.register_timer(self._sample_bmp180) self.sample_timer = self.reactor.register_timer(self._sample_bmp180)
self.chip_registers = BMP180_REGS self.chip_registers = BMP180_REGS
elif self.chip_type == 'BMP388': elif self.chip_type == 'BMP388':
self.max_sample_time = 0.5
self.chip_registers = BMP388_REGS self.chip_registers = BMP388_REGS
self.write_register( self.write_register(
"PWR_CTRL", "PWR_CTRL",
@@ -321,18 +318,15 @@ class BME280:
self.write_register("INT_CTRL", [BMP388_REG_VAL_DRDY_EN]) self.write_register("INT_CTRL", [BMP388_REG_VAL_DRDY_EN])
self.sample_timer = self.reactor.register_timer(self._sample_bmp388) self.sample_timer = self.reactor.register_timer(self._sample_bmp388)
elif self.chip_type == 'BME280': else:
self.max_sample_time = \ self.max_sample_time = \
(1.25 + (2.3 * self.os_temp) + ((2.3 * self.os_pres) + .575) (1.25 + (2.3 * self.os_temp) + ((2.3 * self.os_pres) + .575)
+ ((2.3 * self.os_hum) + .575)) / 1000 + ((2.3 * self.os_hum) + .575)) / 1000
self.sample_timer = self.reactor.register_timer(self._sample_bme280) self.sample_timer = self.reactor.register_timer(self._sample_bme280)
self.chip_registers = BME280_REGS self.chip_registers = BME280_REGS
else:
self.max_sample_time = \ if self.chip_type in ('BME680', 'BME280'):
(1.25 + (2.3 * self.os_temp) self.write_register('CONFIG', (self.iir_filter & 0x07) << 2)
+ ((2.3 * self.os_pres) + .575)) / 1000
self.sample_timer = self.reactor.register_timer(self._sample_bme280)
self.chip_registers = BME280_REGS
# Read out and calculate the trimming parameters # Read out and calculate the trimming parameters
if self.chip_type == 'BMP180': if self.chip_type == 'BMP180':
@@ -353,64 +347,21 @@ class BME280:
elif self.chip_type == 'BMP388': elif self.chip_type == 'BMP388':
self.dig = read_calibration_data_bmp388(cal_1) self.dig = read_calibration_data_bmp388(cal_1)
if self.chip_type in ('BME280', 'BMP280'):
max_standby_time = REPORT_TIME - self.max_sample_time
# 0.5 ms
t_sb = 0
if self.chip_type == 'BME280':
if max_standby_time > 1:
t_sb = 5
elif max_standby_time > 0.5:
t_sb = 4
elif max_standby_time > 0.25:
t_sb = 3
elif max_standby_time > 0.125:
t_sb = 2
elif max_standby_time > 0.0625:
t_sb = 1
elif max_standby_time > 0.020:
t_sb = 7
elif max_standby_time > 0.010:
t_sb = 6
else:
if max_standby_time > 4:
t_sb = 7
elif max_standby_time > 2:
t_sb = 6
elif max_standby_time > 1:
t_sb = 5
elif max_standby_time > 0.5:
t_sb = 4
elif max_standby_time > 0.25:
t_sb = 3
elif max_standby_time > 0.125:
t_sb = 2
elif max_standby_time > 0.0625:
t_sb = 1
cfg = t_sb << 5 | self.iir_filter << 2
self.write_register('CONFIG', cfg)
if self.chip_type == '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)
if self.chip_type == 'BME680':
self.write_register('CONFIG', self.iir_filter << 2)
# Should be set once and reused on every mode register write
self.write_register('CTRL_HUM', self.os_hum & 0x07)
gas_wait_0 = self._calc_gas_heater_duration(self.gas_heat_duration)
self.write_register('GAS_WAIT_0', [gas_wait_0])
res_heat_0 = self._calc_gas_heater_resistance(self.gas_heat_temp)
self.write_register('RES_HEAT_0', [res_heat_0])
# Set initial heater current to reach Gas heater target on start
self.write_register('IDAC_HEAT_0', 96)
def _sample_bme280(self, eventtime): def _sample_bme280(self, eventtime):
# In normal mode data shadowing is performed # Enter forced mode
# So reading can be done while measurements are in process if self.chip_type == 'BME280':
self.write_register('CTRL_HUM', self.os_hum)
meas = self.os_temp << 5 | self.os_pres << 2 | MODE
self.write_register('CTRL_MEAS', meas)
try: try:
# wait until results are ready
status = self.read_register('STATUS', 1)[0]
while status & STATUS_MEASURING:
self.reactor.pause(
self.reactor.monotonic() + self.max_sample_time)
status = self.read_register('STATUS', 1)[0]
if self.chip_type == 'BME280': if self.chip_type == 'BME280':
data = self.read_register('PRESSURE_MSB', 8) data = self.read_register('PRESSURE_MSB', 8)
elif self.chip_type == 'BMP280': elif self.chip_type == 'BMP280':
@@ -511,40 +462,36 @@ class BME280:
return comp_press return comp_press
def _sample_bme680(self, eventtime): def _sample_bme680(self, eventtime):
def data_ready(stat, run_gas): self.write_register('CTRL_HUM', self.os_hum & 0x07)
meas = self.os_temp << 5 | self.os_pres << 2
self.write_register('CTRL_MEAS', [meas])
gas_wait_0 = self._calculate_gas_heater_duration(self.gas_heat_duration)
self.write_register('GAS_WAIT_0', [gas_wait_0])
res_heat_0 = self._calculate_gas_heater_resistance(self.gas_heat_temp)
self.write_register('RES_HEAT_0', [res_heat_0])
gas_config = RUN_GAS | NB_CONV_0
self.write_register('CTRL_GAS_1', [gas_config])
def data_ready(stat):
new_data = (stat & EAS_NEW_DATA) new_data = (stat & EAS_NEW_DATA)
gas_done = not (stat & GAS_DONE) gas_done = not (stat & GAS_DONE)
meas_done = not (stat & MEASURE_DONE) meas_done = not (stat & MEASURE_DONE)
if not run_gas:
gas_done = True
return new_data and gas_done and meas_done 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:
gas_config = RUN_GAS | NB_CONV_0
self.write_register('CTRL_GAS_1', [gas_config])
run_gas = True
# Enter forced mode # Enter forced mode
meas = self.os_temp << 5 | self.os_pres << 2 | MODE meas = meas | MODE
self.write_register('CTRL_MEAS', meas) self.write_register('CTRL_MEAS', meas)
max_sample_time = self.max_sample_time
if run_gas:
max_sample_time += self.gas_heat_duration / 1000
self.reactor.pause(self.reactor.monotonic() + max_sample_time)
try: try:
# wait until results are ready # wait until results are ready
status = self.read_register('EAS_STATUS_0', 1)[0] status = self.read_register('EAS_STATUS_0', 1)[0]
while not data_ready(status, run_gas): while not data_ready(status):
self.reactor.pause( self.reactor.pause(
self.reactor.monotonic() + self.max_sample_time) self.reactor.monotonic() + self.max_sample_time)
status = self.read_register('EAS_STATUS_0', 1)[0] status = self.read_register('EAS_STATUS_0', 1)[0]
data = self.read_register('PRESSURE_MSB', 8) data = self.read_register('PRESSURE_MSB', 8)
gas_data = [0, 0] gas_data = self.read_register('GAS_R_MSB', 2)
if run_gas:
gas_data = self.read_register('GAS_R_MSB', 2)
except Exception: except Exception:
logging.exception("BME680: Error reading data") logging.exception("BME680: Error reading data")
self.temp = self.pressure = self.humidity = self.gas = .0 self.temp = self.pressure = self.humidity = self.gas = .0
@@ -568,10 +515,6 @@ class BME280:
gas_raw = (gas_data[0] << 2) | ((gas_data[1] & 0xC0) >> 6) gas_raw = (gas_data[0] << 2) | ((gas_data[1] & 0xC0) >> 6)
gas_range = (gas_data[1] & 0x0F) gas_range = (gas_data[1] & 0x0F)
self.gas = self._compensate_gas(gas_raw, gas_range) self.gas = self._compensate_gas(gas_raw, gas_range)
# Disable gas measurement on success
gas_config = NB_CONV_0
self.write_register('CTRL_GAS_1', [gas_config])
self.last_gas_time = self.reactor.monotonic()
if self.temp < self.min_temp or self.temp > self.max_temp: if self.temp < self.min_temp or self.temp > self.max_temp:
self.printer.invoke_shutdown( self.printer.invoke_shutdown(
@@ -700,7 +643,7 @@ class BME280:
gas_raw - 512. + var1) gas_raw - 512. + var1)
return gas return gas
def _calc_gas_heater_resistance(self, target_temp): def _calculate_gas_heater_resistance(self, target_temp):
amb_temp = self.temp amb_temp = self.temp
heater_data = self.read_register('RES_HEAT_VAL', 3) heater_data = self.read_register('RES_HEAT_VAL', 3)
res_heat_val = get_signed_byte(heater_data[0]) res_heat_val = get_signed_byte(heater_data[0])
@@ -715,7 +658,7 @@ class BME280:
* (1. / (1. + (res_heat_val * 0.002)))) - 25)) * (1. / (1. + (res_heat_val * 0.002)))) - 25))
return int(res_heat) return int(res_heat)
def _calc_gas_heater_duration(self, duration_ms): def _calculate_gas_heater_duration(self, duration_ms):
if duration_ms >= 4032: if duration_ms >= 4032:
duration_reg = 0xff duration_reg = 0xff
else: else:

View File

@@ -43,7 +43,6 @@ class MCU_SPI:
cs_active_high=False): cs_active_high=False):
self.mcu = mcu self.mcu = mcu
self.bus = bus self.bus = bus
self.speed = speed
# Config SPI object (set all CS pins high before spi_set_bus commands) # Config SPI object (set all CS pins high before spi_set_bus commands)
self.oid = mcu.create_oid() self.oid = mcu.create_oid()
if pin is None: if pin is None:
@@ -52,17 +51,11 @@ class MCU_SPI:
mcu.add_config_cmd("config_spi oid=%d pin=%s cs_active_high=%d" mcu.add_config_cmd("config_spi oid=%d pin=%s cs_active_high=%d"
% (self.oid, pin, cs_active_high)) % (self.oid, pin, cs_active_high))
# Generate SPI bus config message # Generate SPI bus config message
self.config_fmt_ticks = None
if sw_pins is not None: if sw_pins is not None:
self.config_fmt = ( self.config_fmt = (
"spi_set_software_bus oid=%d" "spi_set_software_bus oid=%d"
" miso_pin=%s mosi_pin=%s sclk_pin=%s mode=%d rate=%d" " miso_pin=%s mosi_pin=%s sclk_pin=%s mode=%d rate=%d"
% (self.oid, sw_pins[0], sw_pins[1], sw_pins[2], mode, speed)) % (self.oid, sw_pins[0], sw_pins[1], sw_pins[2], mode, speed))
self.config_fmt_ticks = (
"spi_set_sw_bus oid=%d"
" miso_pin=%s mosi_pin=%s sclk_pin=%s mode=%d pulse_ticks=%%d"
% (self.oid, sw_pins[0], sw_pins[1],
sw_pins[2], mode))
else: else:
self.config_fmt = ( self.config_fmt = (
"spi_set_bus oid=%d spi_bus=%%s mode=%d rate=%d" "spi_set_bus oid=%d spi_bus=%%s mode=%d rate=%d"
@@ -85,12 +78,6 @@ class MCU_SPI:
if '%' in self.config_fmt: if '%' in self.config_fmt:
bus = resolve_bus_name(self.mcu, "spi_bus", self.bus) bus = resolve_bus_name(self.mcu, "spi_bus", self.bus)
self.config_fmt = self.config_fmt % (bus,) self.config_fmt = self.config_fmt % (bus,)
if self.config_fmt_ticks:
if self.mcu.try_lookup_command("spi_set_sw_bus oid=%c miso_pin=%u "
"mosi_pin=%u sclk_pin=%u "
"mode=%u pulse_ticks=%u"):
pulse_ticks = self.mcu.seconds_to_clock(1./self.speed)
self.config_fmt = self.config_fmt_ticks % (pulse_ticks,)
self.mcu.add_config_cmd(self.config_fmt) self.mcu.add_config_cmd(self.config_fmt)
self.spi_send_cmd = self.mcu.lookup_command( self.spi_send_cmd = self.mcu.lookup_command(
"spi_send oid=%c data=%*s", cq=self.cmd_queue) "spi_send oid=%c data=%*s", cq=self.cmd_queue)
@@ -160,8 +147,6 @@ class MCU_I2C:
self.bus = bus self.bus = bus
self.i2c_address = addr self.i2c_address = addr
self.oid = self.mcu.create_oid() self.oid = self.mcu.create_oid()
self.speed = speed
self.config_fmt_ticks = None
mcu.add_config_cmd("config_i2c oid=%d" % (self.oid,)) mcu.add_config_cmd("config_i2c oid=%d" % (self.oid,))
# Generate I2C bus config message # Generate I2C bus config message
if sw_pins is not None: if sw_pins is not None:
@@ -169,24 +154,13 @@ class MCU_I2C:
"i2c_set_software_bus oid=%d" "i2c_set_software_bus oid=%d"
" scl_pin=%s sda_pin=%s rate=%d address=%d" " scl_pin=%s sda_pin=%s rate=%d address=%d"
% (self.oid, sw_pins[0], sw_pins[1], speed, addr)) % (self.oid, sw_pins[0], sw_pins[1], speed, addr))
self.config_fmt_ticks = (
"i2c_set_sw_bus oid=%d"
" scl_pin=%s sda_pin=%s pulse_ticks=%%d address=%d"
% (self.oid, sw_pins[0], sw_pins[1], addr))
else: else:
self.config_fmt = ( self.config_fmt = (
"i2c_set_bus oid=%d i2c_bus=%%s rate=%d address=%d" "i2c_set_bus oid=%d i2c_bus=%%s rate=%d address=%d"
% (self.oid, speed, addr)) % (self.oid, speed, addr))
self.cmd_queue = self.mcu.alloc_command_queue() self.cmd_queue = self.mcu.alloc_command_queue()
self.mcu.register_config_callback(self.build_config) self.mcu.register_config_callback(self.build_config)
self.i2c_write_cmd = self.i2c_read_cmd = None self.i2c_write_cmd = self.i2c_read_cmd = self.i2c_modify_bits_cmd = None
printer = self.mcu.get_printer()
printer.register_event_handler("klippy:connect", self._handle_connect)
# backward support i2c_write inside the init section
self._to_write = []
def _handle_connect(self):
for data in self._to_write:
self.i2c_write(data)
def get_oid(self): def get_oid(self):
return self.oid return self.oid
def get_mcu(self): def get_mcu(self):
@@ -199,12 +173,6 @@ class MCU_I2C:
if '%' in self.config_fmt: if '%' in self.config_fmt:
bus = resolve_bus_name(self.mcu, "i2c_bus", self.bus) bus = resolve_bus_name(self.mcu, "i2c_bus", self.bus)
self.config_fmt = self.config_fmt % (bus,) self.config_fmt = self.config_fmt % (bus,)
if self.config_fmt_ticks:
if self.mcu.try_lookup_command("i2c_set_sw_bus oid=%c"
" scl_pin=%u sda_pin=%u"
" pulse_ticks=%u address=%u"):
pulse_ticks = self.mcu.seconds_to_clock(1./self.speed/2)
self.config_fmt = self.config_fmt_ticks % (pulse_ticks,)
self.mcu.add_config_cmd(self.config_fmt) self.mcu.add_config_cmd(self.config_fmt)
self.i2c_write_cmd = self.mcu.lookup_command( self.i2c_write_cmd = self.mcu.lookup_command(
"i2c_write oid=%c data=%*s", cq=self.cmd_queue) "i2c_write oid=%c data=%*s", cq=self.cmd_queue)
@@ -212,14 +180,36 @@ class MCU_I2C:
"i2c_read oid=%c reg=%*s read_len=%u", "i2c_read oid=%c reg=%*s read_len=%u",
"i2c_read_response oid=%c response=%*s", oid=self.oid, "i2c_read_response oid=%c response=%*s", oid=self.oid,
cq=self.cmd_queue) cq=self.cmd_queue)
self.i2c_modify_bits_cmd = self.mcu.lookup_command(
"i2c_modify_bits oid=%c reg=%*s clear_set_bits=%*s",
cq=self.cmd_queue)
def i2c_write(self, data, minclock=0, reqclock=0): def i2c_write(self, data, minclock=0, reqclock=0):
if self.i2c_write_cmd is None: if self.i2c_write_cmd is None:
self._to_write.append(data) # 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)
return return
self.i2c_write_cmd.send([self.oid, data],
minclock=minclock, reqclock=reqclock)
def i2c_write_wait_ack(self, data, minclock=0, reqclock=0):
self.i2c_write_cmd.send_wait_ack([self.oid, data], self.i2c_write_cmd.send_wait_ack([self.oid, data],
minclock=minclock, reqclock=reqclock) minclock=minclock, reqclock=reqclock)
def i2c_read(self, write, read_len, retry=True): def i2c_read(self, write, read_len):
return self.i2c_read_cmd.send([self.oid, write, read_len], retry) return self.i2c_read_cmd.send([self.oid, write, read_len])
def i2c_modify_bits(self, reg, clear_bits, set_bits,
minclock=0, reqclock=0):
clearset = clear_bits + set_bits
if self.i2c_modify_bits_cmd is None:
# Send setup message via mcu initialization
reg_msg = "".join(["%02x" % (x,) for x in reg])
clearset_msg = "".join(["%02x" % (x,) for x in clearset])
self.mcu.add_config_cmd(
"i2c_modify_bits oid=%d reg=%s clear_set_bits=%s" % (
self.oid, reg_msg, clearset_msg), is_init=True)
return
self.i2c_modify_bits_cmd.send([self.oid, reg, clearset],
minclock=minclock, reqclock=reqclock)
def MCU_I2C_from_config(config, default_addr=None, default_speed=100000): def MCU_I2C_from_config(config, default_addr=None, default_speed=100000):
# Load bus parameters # Load bus parameters

View File

@@ -244,33 +244,6 @@ class HalfStepRotaryEncoder(BaseRotaryEncoder):
BaseRotaryEncoder.R_START | BaseRotaryEncoder.R_DIR_CCW), BaseRotaryEncoder.R_START | BaseRotaryEncoder.R_DIR_CCW),
) )
class DebounceButton:
def __init__(self, config, button_action):
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
self.button_action = button_action
self.debounce_delay = config.getfloat('debounce_delay', 0., minval=0.)
self.logical_state = None
self.physical_state = None
self.latest_eventtime = None
def button_handler(self, eventtime, state):
self.physical_state = state
self.latest_eventtime = eventtime
# if there would be no state transition, ignore the event:
if self.logical_state == self.physical_state:
return
trigger_time = eventtime + self.debounce_delay
self.reactor.register_callback(self._debounce_event, trigger_time)
def _debounce_event(self, eventtime):
# if there would be no state transition, ignore the event:
if self.logical_state == self.physical_state:
return
# if there were more recent events, they supersede this one:
if (eventtime - self.debounce_delay) < self.latest_eventtime:
return
# enact state transition and trigger action
self.logical_state = self.physical_state
self.button_action(self.latest_eventtime, self.logical_state)
###################################################################### ######################################################################
# Button registration code # Button registration code
@@ -288,14 +261,6 @@ class PrinterButtons:
self.adc_buttons[pin] = adc_buttons = MCU_ADC_buttons( self.adc_buttons[pin] = adc_buttons = MCU_ADC_buttons(
self.printer, pin, pullup) self.printer, pin, pullup)
adc_buttons.setup_button(min_val, max_val, callback) adc_buttons.setup_button(min_val, max_val, callback)
def register_debounce_button(self, pin, callback, config):
debounce = DebounceButton(config, callback)
return self.register_buttons([pin], debounce.button_handler)
def register_debounce_adc_button(self, pin, min_val, max_val, pullup
, callback, config):
debounce = DebounceButton(config, callback)
return self.register_adc_button(pin, min_val, max_val, pullup
, debounce.button_handler)
def register_adc_button_push(self, pin, min_val, max_val, pullup, callback): def register_adc_button_push(self, pin, min_val, max_val, pullup, callback):
def helper(eventtime, state, callback=callback): def helper(eventtime, state, callback=callback):
if state: if state:

View File

@@ -1,80 +0,0 @@
# Report canbus connection status
#
# Copyright (C) 2025 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
class PrinterCANBusStats:
def __init__(self, config):
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
self.name = config.get_name().split()[-1]
self.mcu = None
self.get_canbus_status_cmd = None
self.status = {'rx_error': None, 'tx_error': None, 'tx_retries': None,
'bus_state': None}
self.printer.register_event_handler("klippy:connect",
self.handle_connect)
self.printer.register_event_handler("klippy:shutdown",
self.handle_shutdown)
def handle_shutdown(self):
status = self.status.copy()
if status['bus_state'] is not None:
# Clear bus_state on shutdown to note that the values may be stale
status['bus_state'] = 'unknown'
self.status = status
def handle_connect(self):
# Lookup mcu
mcu_name = self.name
if mcu_name != 'mcu':
mcu_name = 'mcu ' + mcu_name
self.mcu = self.printer.lookup_object(mcu_name)
# Lookup status query command
if self.mcu.try_lookup_command("get_canbus_status") is None:
return
self.get_canbus_status_cmd = self.mcu.lookup_query_command(
"get_canbus_status",
"canbus_status rx_error=%u tx_error=%u tx_retries=%u"
" canbus_bus_state=%u")
# Register usb_canbus_state message handling (for usb to canbus bridge)
self.mcu.register_response(self.handle_usb_canbus_state,
"usb_canbus_state")
# Register periodic query timer
self.reactor.register_timer(self.query_event, self.reactor.NOW)
def handle_usb_canbus_state(self, params):
discard = params['discard']
if discard:
logging.warning("USB CANBUS bridge '%s' is discarding!"
% (self.name,))
else:
logging.warning("USB CANBUS bridge '%s' is no longer discarding."
% (self.name,))
def query_event(self, eventtime):
prev_rx = self.status['rx_error']
prev_tx = self.status['tx_error']
prev_retries = self.status['tx_retries']
if prev_rx is None:
prev_rx = prev_tx = prev_retries = 0
params = self.get_canbus_status_cmd.send()
rx = prev_rx + ((params['rx_error'] - prev_rx) & 0xffffffff)
tx = prev_tx + ((params['tx_error'] - prev_tx) & 0xffffffff)
retries = prev_retries + ((params['tx_retries'] - prev_retries)
& 0xffffffff)
state = params['canbus_bus_state']
self.status = {'rx_error': rx, 'tx_error': tx, 'tx_retries': retries,
'bus_state': state}
return self.reactor.monotonic() + 1.
def stats(self, eventtime):
status = self.status
if status['rx_error'] is None:
return (False, '')
return (False, 'canstat_%s: bus_state=%s rx_error=%d'
' tx_error=%d tx_retries=%d'
% (self.name, status['bus_state'], status['rx_error'],
status['tx_error'], status['tx_retries']))
def get_status(self, eventtime):
return self.status
def load_config_prefix(config):
return PrinterCANBusStats(config)

View File

@@ -62,7 +62,9 @@ class ControllerFan:
self.last_on += 1 self.last_on += 1
if speed != self.last_speed: if speed != self.last_speed:
self.last_speed = speed self.last_speed = speed
self.fan.set_speed(speed) curtime = self.printer.get_reactor().monotonic()
print_time = self.fan.get_mcu().estimated_print_time(curtime)
self.fan.set_speed(print_time + PIN_MIN_TIME, speed)
return eventtime + 1. return eventtime + 1.
def load_config_prefix(config): def load_config_prefix(config):

View File

@@ -12,7 +12,7 @@ def load_config_prefix(config):
if not config.has_section('display'): if not config.has_section('display'):
raise config.error( raise config.error(
"A primary [display] section must be defined in printer.cfg " "A primary [display] section must be defined in printer.cfg "
"to use auxiliary displays") "to use auxilary displays")
name = config.get_name().split()[-1] name = config.get_name().split()[-1]
if name == "display": if name == "display":
raise config.error( raise config.error(

View File

@@ -1,209 +0,0 @@
# Support for YHCB2004 (20x4 text) LCD displays based on AiP31068 controller
#
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2018 Eric Callahan <arksine.code@gmail.com>
# Copyright (C) 2021 Marc-Andre Denis <marcadenis@msn.com>
# Copyright (C) 2024 Alexander Bazarov <oss@bazarov.dev>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
# This file is a modified version of hd44780_spi.py, introducing slightly
# different protocol as implemented in Marlin FW (based on
# https://github.com/red-scorp/LiquidCrystal_AIP31068 ).
# In addition, a hack is used to send 8 commands, each 9 bits, at once,
# allowing the transmission of a full 9 bytes.
# This helps avoid modifying the SW_SPI driver to handle non-8-bit data.
from .. import bus
LINE_LENGTH_DEFAULT=20
LINE_LENGTH_OPTIONS={16:16, 20:20}
TextGlyphs = { 'right_arrow': b'\x7e' }
# Each command is 9 bits long:
# 1 bit for RS (Register Select) - 0 for command, 1 for data
# 8 bits for the command/data
# Command is a bitwise OR of CMND(=opcode) and flg_CMND(=parameters) multiplied
# by 1 or 0 as En/Dis flag.
# cmd = CMND | flg_CMND.param0*0 | flg_CMND.param1*1
# or just by OR with enabled flags:
# cmd = CMND | flg_CMND.param1
class CMND:
CLR = 1 # Clear display
HOME = 2 # Return home
ENTERY_MODE = 2**2 # Entry mode set
DISPLAY = 2**3 # Display on/off control
SHIFT = 2**4 # Cursor or display shift
FUNCTION = 2**5 # Function set
CGRAM = 2**6 # Character Generator RAM
DDRAM = 2**7 # Display Data RAM
WRITE_RAM = 2**8 # Write to RAM
# Define flags for all commands:
class flg_ENTERY_MODE:
INC = 2**1 # Increment
SHIFT = 2**0 # Shift display
class flg_DISPLAY:
ON = 2**2 # Display ON
CURSOR = 2**1 # Cursor ON
BLINK = 2**0 # Blink ON
class flg_SHIFT:
WHOLE_DISPLAY = 2**3 # Shift whole display
RIGHT = 2**2 # Shift right
class flg_FUNCTION:
TWO_LINES = 2**3 # 2-line display mode
FIVE_BY_ELEVEN = 2**2 # 5x11 dot character font
class flg_CGRAM:
MASK = 0b00111111 # CGRAM address mask
class flg_DDRAM:
MASK = 0b01111111 # DDRAM address mask
class flg_WRITE_RAM:
MASK = 0b11111111 # Write RAM mask
DISPLAY_INIT_CMNDS= [
# CMND.CLR - no need as framebuffer will rewrite all
CMND.HOME, # move cursor to home (0x00)
CMND.ENTERY_MODE | flg_ENTERY_MODE.INC, # increment cursor and no shift
CMND.DISPLAY | flg_DISPLAY.ON, # keep cursor and blinking off
CMND.SHIFT | flg_SHIFT.RIGHT, # shift right cursor only
CMND.FUNCTION | flg_FUNCTION.TWO_LINES, # 2-line display mode, 5x8 dots
]
class aip31068_spi:
def __init__(self, config):
self.printer = config.get_printer()
# spi config
self.spi = bus.MCU_SPI_from_config(
config, 0x00, pin_option="latch_pin") # (config, mode, cs_name)
self.mcu = self.spi.get_mcu()
self.icons = {}
self.line_length = config.getchoice('line_length', LINE_LENGTH_OPTIONS,
LINE_LENGTH_DEFAULT)
# each controller's line is 2 lines on the display and hence twice
# line length
self.text_framebuffers = [bytearray(b' '*2*self.line_length),
bytearray(b' '*2*self.line_length)]
self.glyph_framebuffer = bytearray(64)
# all_framebuffers - list of tuples per buffer type.
# Each tuple contains:
# 1. the updated framebuffer
# 2. a copy of the old framebuffer == data on the display
# 3. the command to send to write to this buffer
# Then flush() will compare new data with data on the display
# and send only the differences to the display controller
# and update the old framebuffer with the new data
# (immutable tuple is allowed to store mutable bytearray)
self.all_framebuffers = [
# Text framebuffers
(self.text_framebuffers[0], bytearray(b'~'*2*self.line_length),
CMND.DDRAM | (flg_DDRAM.MASK & 0x00) ),
(self.text_framebuffers[1], bytearray(b'~'*2*self.line_length),
CMND.DDRAM | (flg_DDRAM.MASK & 0x40) ),
# Glyph framebuffer
(self.glyph_framebuffer, bytearray(b'~'*64),
CMND.CGRAM | (flg_CGRAM.MASK & 0x00) ) ]
@staticmethod
def encode(data, width = 9):
encoded_bytes = []
accumulator = 0 # To accumulate bits
acc_bits = 0 # Count of bits in the accumulator
for num in data:
# check that num will fit in width bits
if num >= (1 << width):
raise ValueError("Number {} does not fit in {} bits".
format(num, width))
# Shift the current number into the accumulator from the right
accumulator = (accumulator << width) | num
acc_bits += width # Update the count of bits in the accumulator
# While we have at least 8 bits, form a byte and append it
while acc_bits >= 8:
acc_bits -= 8 # Decrease bit count by 8
# Extract the 8 most significant bits to form a byte
byte = (accumulator >> acc_bits) & 0xFF
# Remove msb 8 bits from the accumulator
accumulator &= (1 << acc_bits) - 1
encoded_bytes.append(byte)
# Handle any remaining bits by padding them on the right to byte
if acc_bits > 0:
last_byte = accumulator << (8 - acc_bits)
encoded_bytes.append(last_byte)
return encoded_bytes
def send(self, data, minclock=0):
# different commands have different processing time
# to avoid timing violation pad with some fast command, e.g. ENTRY_MODE
# that has execution time of 39us (for comparison CLR is 1.53ms)
pad = CMND.ENTERY_MODE | flg_ENTERY_MODE.INC
for i in range(0, len(data), 8):
# Take a slice of 8 numbers
group = data[i:i+8]
# Pad the group if it has fewer than 8 elements
if len(group) < 8:
group.extend([pad] * (8 - len(group)))
self.spi.spi_send(self.encode(group), minclock)
def flush(self):
# Find all differences in the framebuffers and send them to the chip
for new_data, old_data, fb_cmnd in self.all_framebuffers:
if new_data == old_data:
continue
# Find the position of all changed bytes in this framebuffer
diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data))
if n != o]
# Batch together changes that are close to each other
for i in range(len(diffs)-2, -1, -1):
pos, count = diffs[i]
nextpos, nextcount = diffs[i+1]
if pos + 4 >= nextpos and nextcount < 16:
diffs[i][1] = nextcount + (nextpos - pos)
del diffs[i+1]
# Transmit changes
for pos, count in diffs:
chip_pos = pos
self.send([fb_cmnd + chip_pos])
self.send([CMND.WRITE_RAM | byte for byte in
new_data[pos:pos+count]])
old_data[:] = new_data
def init(self):
curtime = self.printer.get_reactor().monotonic()
print_time = self.mcu.estimated_print_time(curtime)
for i, cmds in enumerate(DISPLAY_INIT_CMNDS):
minclock = self.mcu.print_time_to_clock(print_time + i * .100)
self.send([cmds], minclock=minclock)
self.flush()
def write_text(self, x, y, data):
if x + len(data) > self.line_length:
data = data[:self.line_length - min(x, self.line_length)]
pos = x + ((y & 0x02) >> 1) * self.line_length
self.text_framebuffers[y & 1][pos:pos+len(data)] = data
def set_glyphs(self, glyphs):
for glyph_name, glyph_data in glyphs.items():
data = glyph_data.get('icon5x8')
if data is not None:
self.icons[glyph_name] = data
def write_glyph(self, x, y, glyph_name):
data = self.icons.get(glyph_name)
if data is not None:
slot, bits = data
self.write_text(x, y, [slot])
self.glyph_framebuffer[slot * 8:(slot + 1) * 8] = bits
return 1
char = TextGlyphs.get(glyph_name)
if char is not None:
# Draw character
self.write_text(x, y, char)
return 1
return 0
def write_graphics(self, x, y, data):
pass # this display supports only hardcoded or 8 user defined glyphs
def clear(self):
spaces = b' ' * 2*self.line_length
self.text_framebuffers[0][:] = spaces
self.text_framebuffers[1][:] = spaces
def get_dimensions(self):
return (self.line_length, 4)

View File

@@ -6,7 +6,7 @@
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import logging, os, ast import logging, os, ast
from . import aip31068_spi, hd44780, hd44780_spi, st7920, uc1701, menu from . import hd44780, hd44780_spi, st7920, uc1701, menu
# Normal time between each screen redraw # Normal time between each screen redraw
REDRAW_TIME = 0.500 REDRAW_TIME = 0.500
@@ -17,8 +17,7 @@ LCD_chips = {
'st7920': st7920.ST7920, 'emulated_st7920': st7920.EmulatedST7920, 'st7920': st7920.ST7920, 'emulated_st7920': st7920.EmulatedST7920,
'hd44780': hd44780.HD44780, 'uc1701': uc1701.UC1701, 'hd44780': hd44780.HD44780, 'uc1701': uc1701.UC1701,
'ssd1306': uc1701.SSD1306, 'sh1106': uc1701.SH1106, 'ssd1306': uc1701.SSD1306, 'sh1106': uc1701.SH1106,
'hd44780_spi': hd44780_spi.hd44780_spi, 'hd44780_spi': hd44780_spi.hd44780_spi
'aip31068_spi':aip31068_spi.aip31068_spi
} }
# Storage of [display_template my_template] config sections # Storage of [display_template my_template] config sections

View File

@@ -13,7 +13,7 @@
# ftp://ftp.simtel.net/pub/simtelnet/msdos/screen/fntcol16.zip # ftp://ftp.simtel.net/pub/simtelnet/msdos/screen/fntcol16.zip
# (c) Joseph Gil # (c) Joseph Gil
# #
# Individual fonts are public domain # Indivdual fonts are public domain
###################################################################### ######################################################################
VGA_FONT = [ VGA_FONT = [

View File

@@ -1,9 +1,9 @@
# Support for "dotstar" leds # Support for "dotstar" leds
# #
# Copyright (C) 2019-2024 Kevin O'Connor <kevin@koconnor.net> # Copyright (C) 2019-2022 Kevin O'Connor <kevin@koconnor.net>
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
from . import bus, led from . import bus
BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000 BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
@@ -22,8 +22,9 @@ class PrinterDotstar:
self.spi = bus.MCU_SPI(mcu, None, None, 0, 500000, sw_spi_pins) self.spi = bus.MCU_SPI(mcu, None, None, 0, 500000, sw_spi_pins)
# Initialize color data # Initialize color data
self.chain_count = config.getint('chain_count', 1, minval=1) self.chain_count = config.getint('chain_count', 1, minval=1)
self.led_helper = led.LEDHelper(config, self.update_leds, pled = printer.load_object(config, "led")
self.chain_count) self.led_helper = pled.setup_helper(config, self.update_leds,
self.chain_count)
self.prev_data = None self.prev_data = None
# Register commands # Register commands
printer.register_event_handler("klippy:connect", self.handle_connect) printer.register_event_handler("klippy:connect", self.handle_connect)

View File

@@ -52,7 +52,7 @@ class PhaseCalc:
class EndstopPhase: class EndstopPhase:
def __init__(self, config): def __init__(self, config):
self.printer = config.get_printer() self.printer = config.get_printer()
self.name = " ".join(config.get_name().split()[1:]) self.name = config.get_name().split()[1]
# Obtain step_distance and microsteps from stepper config section # Obtain step_distance and microsteps from stepper config section
sconfig = config.getsection(self.name) sconfig = config.getsection(self.name)
rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig) rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig)
@@ -118,7 +118,7 @@ class EndstopPhase:
return delta * self.step_dist return delta * self.step_dist
def handle_home_rails_end(self, homing_state, rails): def handle_home_rails_end(self, homing_state, rails):
for rail in rails: for rail in rails:
stepper = rail.get_endstops()[0][0].get_steppers()[0] stepper = rail.get_steppers()[0]
if stepper.get_name() == self.name: if stepper.get_name() == self.name:
trig_mcu_pos = homing_state.get_trigger_position(self.name) trig_mcu_pos = homing_state.get_trigger_position(self.name)
align = self.align_endstop(rail) align = self.align_endstop(rail)

View File

@@ -82,25 +82,24 @@ class ExcludeObject:
self._reset_state() self._reset_state()
self._unregister_transform() self._unregister_transform()
def _get_extrusion_offsets(self, num_coord): def _get_extrusion_offsets(self):
ename = self.toolhead.get_extruder().get_name() offset = self.extrusion_offsets.get(
offset = self.extrusion_offsets.get(ename) self.toolhead.get_extruder().get_name())
if offset is None: if offset is None:
offset = [0.] * num_coord offset = [0., 0., 0., 0.]
self.extrusion_offsets[ename] = offset self.extrusion_offsets[self.toolhead.get_extruder().get_name()] = \
if len(offset) < num_coord: offset
offset.extend([0.] * (len(num_coord) - len(offset)))
return offset return offset
def get_position(self): def get_position(self):
offset = self._get_extrusion_offsets()
pos = self.next_transform.get_position() pos = self.next_transform.get_position()
offset = self._get_extrusion_offsets(len(pos)) for i in range(4):
for i in range(len(pos)):
self.last_position[i] = pos[i] + offset[i] self.last_position[i] = pos[i] + offset[i]
return list(self.last_position) return list(self.last_position)
def _normal_move(self, newpos, speed): def _normal_move(self, newpos, speed):
offset = self._get_extrusion_offsets(len(newpos)) offset = self._get_extrusion_offsets()
if self.initial_extrusion_moves > 0 and \ if self.initial_extrusion_moves > 0 and \
self.last_position[3] != newpos[3]: self.last_position[3] != newpos[3]:
@@ -123,9 +122,9 @@ class ExcludeObject:
if (offset[0] != 0 or offset[1] != 0) and \ if (offset[0] != 0 or offset[1] != 0) and \
(newpos[0] != self.last_position_excluded[0] or \ (newpos[0] != self.last_position_excluded[0] or \
newpos[1] != self.last_position_excluded[1]): newpos[1] != self.last_position_excluded[1]):
for i in range(len(newpos)): offset[0] = 0
if i != 3: offset[1] = 0
offset[i] = 0 offset[2] = 0
offset[3] += self.extruder_adj offset[3] += self.extruder_adj
self.extruder_adj = 0 self.extruder_adj = 0
@@ -138,18 +137,17 @@ class ExcludeObject:
self.extruder_adj = 0 self.extruder_adj = 0
tx_pos = newpos[:] tx_pos = newpos[:]
for i in range(len(newpos)): for i in range(4):
tx_pos[i] = newpos[i] - offset[i] tx_pos[i] = newpos[i] - offset[i]
self.next_transform.move(tx_pos, speed) self.next_transform.move(tx_pos, speed)
def _ignore_move(self, newpos, speed): def _ignore_move(self, newpos, speed):
offset = self._get_extrusion_offsets(len(newpos)) offset = self._get_extrusion_offsets()
for i in range(len(newpos)): for i in range(3):
if i != 3: offset[i] = newpos[i] - self.last_position_extruded[i]
offset[i] = newpos[i] - self.last_position_extruded[i]
offset[3] = offset[3] + newpos[3] - self.last_position[3] offset[3] = offset[3] + newpos[3] - self.last_position[3]
self.last_position[:] = newpos self.last_position[:] = newpos
self.last_position_excluded[:] = self.last_position self.last_position_excluded[:] =self.last_position
self.max_position_excluded = max(self.max_position_excluded, newpos[3]) self.max_position_excluded = max(self.max_position_excluded, newpos[3])
def _move_into_excluded_region(self, newpos, speed): def _move_into_excluded_region(self, newpos, speed):

View File

@@ -1,14 +1,17 @@
# Printer cooling fan # Printer cooling fan
# #
# Copyright (C) 2016-2024 Kevin O'Connor <kevin@koconnor.net> # Copyright (C) 2016-2020 Kevin O'Connor <kevin@koconnor.net>
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
from . import pulse_counter, output_pin from . import pulse_counter
FAN_MIN_TIME = 0.100
class Fan: class Fan:
def __init__(self, config, default_shutdown_speed=0.): def __init__(self, config, default_shutdown_speed=0.):
self.printer = config.get_printer() self.printer = config.get_printer()
self.last_fan_value = self.last_req_value = 0. self.last_fan_value = 0.
self.last_fan_time = 0.
# Read config # Read config
self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.) self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.)
self.kick_start_time = config.getfloat('kick_start_time', 0.1, self.kick_start_time = config.getfloat('kick_start_time', 0.1,
@@ -33,10 +36,6 @@ class Fan:
self.enable_pin = ppins.setup_pin('digital_out', enable_pin) self.enable_pin = ppins.setup_pin('digital_out', enable_pin)
self.enable_pin.setup_max_duration(0.) self.enable_pin.setup_max_duration(0.)
# Create gcode request queue
self.gcrq = output_pin.GCodeRequestQueue(config, self.mcu_fan.get_mcu(),
self._apply_speed)
# Setup tachometer # Setup tachometer
self.tachometer = FanTachometer(config) self.tachometer = FanTachometer(config)
@@ -46,37 +45,37 @@ class Fan:
def get_mcu(self): def get_mcu(self):
return self.mcu_fan.get_mcu() return self.mcu_fan.get_mcu()
def _apply_speed(self, print_time, value): def set_speed(self, print_time, value):
if value < self.off_below: if value < self.off_below:
value = 0. value = 0.
value = max(0., min(self.max_power, value * self.max_power)) value = max(0., min(self.max_power, value * self.max_power))
if value == self.last_fan_value: if value == self.last_fan_value:
return "discard", 0. return
print_time = max(self.last_fan_time + FAN_MIN_TIME, print_time)
if self.enable_pin: if self.enable_pin:
if value > 0 and self.last_fan_value == 0: if value > 0 and self.last_fan_value == 0:
self.enable_pin.set_digital(print_time, 1) self.enable_pin.set_digital(print_time, 1)
elif value == 0 and self.last_fan_value > 0: elif value == 0 and self.last_fan_value > 0:
self.enable_pin.set_digital(print_time, 0) self.enable_pin.set_digital(print_time, 0)
if (value and self.kick_start_time if (value and value < self.max_power and self.kick_start_time
and (not self.last_fan_value or value - self.last_fan_value > .5)): and (not self.last_fan_value or value - self.last_fan_value > .5)):
# Run fan at full speed for specified kick_start_time # Run fan at full speed for specified kick_start_time
self.last_req_value = value
self.last_fan_value = self.max_power
self.mcu_fan.set_pwm(print_time, self.max_power) self.mcu_fan.set_pwm(print_time, self.max_power)
return "delay", self.kick_start_time print_time += self.kick_start_time
self.last_fan_value = self.last_req_value = value
self.mcu_fan.set_pwm(print_time, value) self.mcu_fan.set_pwm(print_time, value)
def set_speed(self, value, print_time=None): self.last_fan_time = print_time
self.gcrq.send_async_request(value, print_time) self.last_fan_value = value
def set_speed_from_command(self, value): def set_speed_from_command(self, value):
self.gcrq.queue_gcode_request(value) toolhead = self.printer.lookup_object('toolhead')
toolhead.register_lookahead_callback((lambda pt:
self.set_speed(pt, value)))
def _handle_request_restart(self, print_time): def _handle_request_restart(self, print_time):
self.set_speed(0., print_time) self.set_speed(print_time, 0.)
def get_status(self, eventtime): def get_status(self, eventtime):
tachometer_status = self.tachometer.get_status(eventtime) tachometer_status = self.tachometer.get_status(eventtime)
return { return {
'speed': self.last_req_value, 'speed': self.last_fan_value,
'rpm': tachometer_status['rpm'], 'rpm': tachometer_status['rpm'],
} }

View File

@@ -1,10 +1,9 @@
# Support fans that are controlled by gcode # Support fans that are controlled by gcode
# #
# Copyright (C) 2016-2024 Kevin O'Connor <kevin@koconnor.net> # Copyright (C) 2016-2020 Kevin O'Connor <kevin@koconnor.net>
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import logging from . import fan
from . import fan, output_pin
class PrinterFanGeneric: class PrinterFanGeneric:
cmd_SET_FAN_SPEED_help = "Sets the speed of a fan" cmd_SET_FAN_SPEED_help = "Sets the speed of a fan"
@@ -13,9 +12,6 @@ class PrinterFanGeneric:
self.fan = fan.Fan(config, default_shutdown_speed=0.) self.fan = fan.Fan(config, default_shutdown_speed=0.)
self.fan_name = config.get_name().split()[-1] self.fan_name = config.get_name().split()[-1]
# Template handling
self.template_eval = output_pin.lookup_template_eval(config)
gcode = self.printer.lookup_object("gcode") gcode = self.printer.lookup_object("gcode")
gcode.register_mux_command("SET_FAN_SPEED", "FAN", gcode.register_mux_command("SET_FAN_SPEED", "FAN",
self.fan_name, self.fan_name,
@@ -24,22 +20,8 @@ class PrinterFanGeneric:
def get_status(self, eventtime): def get_status(self, eventtime):
return self.fan.get_status(eventtime) return self.fan.get_status(eventtime)
def _template_update(self, text):
try:
value = float(text)
except ValueError as e:
logging.exception("fan_generic template render error")
value = 0.
self.fan.set_speed(value)
def cmd_SET_FAN_SPEED(self, gcmd): def cmd_SET_FAN_SPEED(self, gcmd):
speed = gcmd.get_float('SPEED', None, 0.) speed = gcmd.get_float('SPEED', 0.)
template = gcmd.get('TEMPLATE', None)
if (speed is None) == (template is None):
raise gcmd.error("SET_FAN_SPEED must specify SPEED or TEMPLATE")
# Check for template setting
if template is not None:
self.template_eval.set_template(gcmd, self._template_update)
return
self.fan.set_speed_from_command(speed) self.fan.set_speed_from_command(speed)
def load_config_prefix(config): def load_config_prefix(config):

View File

@@ -63,7 +63,7 @@ class EncoderSensor:
def _extruder_pos_update_event(self, eventtime): def _extruder_pos_update_event(self, eventtime):
extruder_pos = self._get_extruder_pos(eventtime) extruder_pos = self._get_extruder_pos(eventtime)
# Check for filament runout # Check for filament runout
self.runout_helper.note_filament_present(eventtime, self.runout_helper.note_filament_present(
extruder_pos < self.filament_runout_pos) extruder_pos < self.filament_runout_pos)
return eventtime + CHECK_RUNOUT_TIMEOUT return eventtime + CHECK_RUNOUT_TIMEOUT
def encoder_event(self, eventtime, state): def encoder_event(self, eventtime, state):
@@ -71,7 +71,7 @@ class EncoderSensor:
self._update_filament_runout_pos(eventtime) self._update_filament_runout_pos(eventtime)
# Check for filament insertion # Check for filament insertion
# Filament is always assumed to be present on an encoder event # Filament is always assumed to be present on an encoder event
self.runout_helper.note_filament_present(eventtime, True) self.runout_helper.note_filament_present(True)
def load_config_prefix(config): def load_config_prefix(config):
return EncoderSensor(config) return EncoderSensor(config)

View File

@@ -5,7 +5,6 @@
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import logging import logging
class RunoutHelper: class RunoutHelper:
def __init__(self, config): def __init__(self, config):
self.name = config.get_name().split()[-1] self.name = config.get_name().split()[-1]
@@ -25,7 +24,7 @@ class RunoutHelper:
self.insert_gcode = gcode_macro.load_template( self.insert_gcode = gcode_macro.load_template(
config, 'insert_gcode') config, 'insert_gcode')
self.pause_delay = config.getfloat('pause_delay', .5, above=.0) self.pause_delay = config.getfloat('pause_delay', .5, above=.0)
self.event_delay = config.getfloat('event_delay', 3., minval=.0) self.event_delay = config.getfloat('event_delay', 3., above=0.)
# Internal state # Internal state
self.min_event_systime = self.reactor.NEVER self.min_event_systime = self.reactor.NEVER
self.filament_present = False self.filament_present = False
@@ -60,20 +59,19 @@ class RunoutHelper:
except Exception: except Exception:
logging.exception("Script running error") logging.exception("Script running error")
self.min_event_systime = self.reactor.monotonic() + self.event_delay self.min_event_systime = self.reactor.monotonic() + self.event_delay
def note_filament_present(self, eventtime, is_filament_present): def note_filament_present(self, is_filament_present):
if is_filament_present == self.filament_present: if is_filament_present == self.filament_present:
return return
self.filament_present = is_filament_present self.filament_present = is_filament_present
eventtime = self.reactor.monotonic()
if eventtime < self.min_event_systime or not self.sensor_enabled: if eventtime < self.min_event_systime or not self.sensor_enabled:
# do not process during the initialization time, duplicates, # do not process during the initialization time, duplicates,
# during the event delay time, while an event is running, or # during the event delay time, while an event is running, or
# when the sensor is disabled # when the sensor is disabled
return return
# Determine "printing" status # Determine "printing" status
now = self.reactor.monotonic()
idle_timeout = self.printer.lookup_object("idle_timeout") idle_timeout = self.printer.lookup_object("idle_timeout")
is_printing = idle_timeout.get_status(now)["state"] == "Printing" is_printing = idle_timeout.get_status(eventtime)["state"] == "Printing"
# Perform filament action associated with status change (if any) # Perform filament action associated with status change (if any)
if is_filament_present: if is_filament_present:
if not is_printing and self.insert_gcode is not None: if not is_printing and self.insert_gcode is not None:
@@ -81,14 +79,14 @@ class RunoutHelper:
self.min_event_systime = self.reactor.NEVER self.min_event_systime = self.reactor.NEVER
logging.info( logging.info(
"Filament Sensor %s: insert event detected, Time %.2f" % "Filament Sensor %s: insert event detected, Time %.2f" %
(self.name, now)) (self.name, eventtime))
self.reactor.register_callback(self._insert_event_handler) self.reactor.register_callback(self._insert_event_handler)
elif is_printing and self.runout_gcode is not None: elif is_printing and self.runout_gcode is not None:
# runout detected # runout detected
self.min_event_systime = self.reactor.NEVER self.min_event_systime = self.reactor.NEVER
logging.info( logging.info(
"Filament Sensor %s: runout event detected, Time %.2f" % "Filament Sensor %s: runout event detected, Time %.2f" %
(self.name, now)) (self.name, eventtime))
self.reactor.register_callback(self._runout_event_handler) self.reactor.register_callback(self._runout_event_handler)
def get_status(self, eventtime): def get_status(self, eventtime):
return { return {
@@ -110,12 +108,11 @@ class SwitchSensor:
printer = config.get_printer() printer = config.get_printer()
buttons = printer.load_object(config, 'buttons') buttons = printer.load_object(config, 'buttons')
switch_pin = config.get('switch_pin') switch_pin = config.get('switch_pin')
buttons.register_debounce_button(switch_pin, self._button_handler buttons.register_buttons([switch_pin], self._button_handler)
, config)
self.runout_helper = RunoutHelper(config) self.runout_helper = RunoutHelper(config)
self.get_status = self.runout_helper.get_status self.get_status = self.runout_helper.get_status
def _button_handler(self, eventtime, state): def _button_handler(self, eventtime, state):
self.runout_helper.note_filament_present(eventtime, state) self.runout_helper.note_filament_present(state)
def load_config_prefix(config): def load_config_prefix(config):
return SwitchSensor(config) return SwitchSensor(config)

View File

@@ -43,7 +43,7 @@ class FirmwareRetraction:
self.unretract_length = (self.retract_length self.unretract_length = (self.retract_length
+ self.unretract_extra_length) + self.unretract_extra_length)
self.is_retracted = False self.is_retracted = False
cmd_GET_RETRACTION_help = ("Report firmware retraction parameters") cmd_GET_RETRACTION_help = ("Report firmware retraction paramters")
def cmd_GET_RETRACTION(self, gcmd): def cmd_GET_RETRACTION(self, gcmd):
gcmd.respond_info("RETRACT_LENGTH=%.5f RETRACT_SPEED=%.5f" gcmd.respond_info("RETRACT_LENGTH=%.5f RETRACT_SPEED=%.5f"
" UNRETRACT_EXTRA_LENGTH=%.5f UNRETRACT_SPEED=%.5f" " UNRETRACT_EXTRA_LENGTH=%.5f UNRETRACT_SPEED=%.5f"

Some files were not shown because too many files have changed in this diff Show More