Merge remote-tracking branch 'upstream/master' into dgus-display

This commit is contained in:
Desuuuu 2022-08-28 17:35:44 +02:00
commit b466915998
No known key found for this signature in database
GPG Key ID: 85943F4B2C2CE0DC
151 changed files with 6919 additions and 847 deletions

3
.github/FUNDING.yml vendored
View File

@ -1 +1,2 @@
patreon: koconnor ko_fi: koconnor
custom: https://www.klipper3d.org/Sponsors.html#klipper-developers

View File

@ -7,7 +7,6 @@ on:
- master - master
paths: paths:
- docs/** - docs/**
- mkdocs.yml
- .github/workflows/klipper3d-deploy.yaml - .github/workflows/klipper3d-deploy.yaml
jobs: jobs:
deploy: deploy:

View File

@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007 Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. of this license document, but changing it is not allowed.
@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail. Also add information on how to contact you by electronic and paper mail.
@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school, You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary. if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>. <https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>. <https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@ -31,7 +31,7 @@ cc-option=$(shell if test -z "`$(1) $(2) -S -o /dev/null -xc /dev/null 2>&1`" \
CFLAGS := -I$(OUT) -Isrc -I$(OUT)board-generic/ -std=gnu11 -O2 -MD \ CFLAGS := -I$(OUT) -Isrc -I$(OUT)board-generic/ -std=gnu11 -O2 -MD \
-Wall -Wold-style-definition $(call cc-option,$(CC),-Wtype-limits,) \ -Wall -Wold-style-definition $(call cc-option,$(CC),-Wtype-limits,) \
-ffunction-sections -fdata-sections -ffunction-sections -fdata-sections -fno-delete-null-pointer-checks
CFLAGS += -flto -fwhole-program -fno-use-linker-plugin -ggdb3 CFLAGS += -flto -fwhole-program -fno-use-linker-plugin -ggdb3
OBJS_klipper.elf = $(patsubst %.c, $(OUT)src/%.o,$(src-y)) OBJS_klipper.elf = $(patsubst %.c, $(OUT)src/%.o,$(src-y))

View File

@ -13,4 +13,6 @@ To begin using Klipper start by
[installing](https://www.klipper3d.org/Installation.html) it. [installing](https://www.klipper3d.org/Installation.html) it.
Klipper is Free Software. See the [license](COPYING) or read the Klipper is Free Software. See the [license](COPYING) or read the
[documentation](https://www.klipper3d.org/Overview.html). [documentation](https://www.klipper3d.org/Overview.html). We depend on
the generous support from our
[sponsors](https://www.klipper3d.org/Sponsors.html).

View File

@ -129,7 +129,7 @@ max_temp: 130
[fan] [fan]
pin: PC26 pin: PC26
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: PC25 pin: PC25
[mcu] [mcu]

View File

@ -90,7 +90,7 @@ pid_Kd: 948.182
min_temp: 0 min_temp: 0
max_temp: 130 max_temp: 130
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: PB6 pin: PB6
[fan] [fan]

View File

@ -0,0 +1,183 @@
# This file contains common pin mappings for the BigTreeTech SKR 3.
# To use this config, during "make menuconfig" enable "low-level
# options", "STM32H743", "128KiB bootloader", and "25MHz clock".
# See docs/Config_Reference.md for a description of parameters.
[stepper_x]
step_pin: PD4
dir_pin: PD3
enable_pin: !PD6
microsteps: 16
rotation_distance: 40
endstop_pin: ^PC1
position_endstop: 0
position_max: 200
homing_speed: 50
[stepper_y]
step_pin: PA15
dir_pin: !PA8
enable_pin: !PD1
microsteps: 16
rotation_distance: 40
endstop_pin: ^PC3
position_endstop: 0
position_max: 200
homing_speed: 50
[stepper_z]
step_pin: PE2
dir_pin: PE3
enable_pin: !PE0
microsteps: 16
rotation_distance: 40
endstop_pin: ^PC0
position_endstop: 0.5
position_max: 200
[extruder]
step_pin: PD15
dir_pin: PD14
enable_pin: !PC7
microsteps: 16
rotation_distance: 33.500
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PB3
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PA2
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
#[extruder1]
#step_pin: PD11
#dir_pin: PD10
#enable_pin: !PD13
#heater_pin: PB4
#sensor_pin: PA3
#...
[heater_bed]
heater_pin: PD7
sensor_type: Generic 3950
sensor_pin: PA1
control: watermark
min_temp: 0
max_temp: 130
[fan]
pin: PB7
#[heater_fan fan1]
#pin: PB6
#[heater_fan fan2]
#pin: PB5
[mcu]
serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100
########################################
# EXP1 / EXP2 (display) pins
########################################
[board_pins]
aliases:
# EXP1 header
EXP1_1=PC5, EXP1_3=PB1, EXP1_5=PE9, EXP1_7=PE11, EXP1_9=<GND>,
EXP1_2=PB0, EXP1_4=PE8, EXP1_6=PE10, EXP1_8=PE12, EXP1_10=<5V>,
# EXP2 header
EXP2_1=PA6, EXP2_3=PE7, EXP2_5=PB2, EXP2_7=PC4, 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.
########################################
# TMC2209 configuration
########################################
#[tmc2209 stepper_x]
#uart_pin: PD5
#run_current: 0.800
#diag_pin:
#[tmc2209 stepper_y]
#uart_pin: PD0
#run_current: 0.800
#diag_pin:
#[tmc2209 stepper_z]
#uart_pin: PE1
#run_current: 0.800
#diag_pin:
#[tmc2209 extruder]
#uart_pin: PC6
#run_current: 0.600
#diag_pin:
#[tmc2209 extruder1]
#uart_pin: PD12
#run_current: 0.600
#diag_pin:
########################################
# TMC2130 configuration
########################################
#[tmc2130 stepper_x]
#cs_pin: PD5
#spi_software_miso_pin: PE15
#spi_software_mosi_pin: PE13
#spi_software_sclk_pin: PE14
#run_current: 0.800
#stealthchop_threshold: 999999
#diag1_pin: PC1
#[tmc2130 stepper_y]
#cs_pin: PD0
#spi_software_miso_pin: PE15
#spi_software_mosi_pin: PE13
#spi_software_sclk_pin: PE14
#run_current: 0.800
#stealthchop_threshold: 999999
#diag1_pin: PC3
#[tmc2130 stepper_z]
#cs_pin: PE1
#spi_software_miso_pin: PE15
#spi_software_mosi_pin: PE13
#spi_software_sclk_pin: PE14
#run_current: 0.650
#stealthchop_threshold: 999999
#diag1_pin: PC0
#[tmc2130 extruder]
#cs_pin: PC6
#spi_software_miso_pin: PE15
#spi_software_mosi_pin: PE13
#spi_software_sclk_pin: PE14
#run_current: 0.800
#stealthchop_threshold: 999999
#diag1_pin: PC2
#[tmc2130 extruder1]
#cs_pin: PD12
#spi_software_miso_pin: PE15
#spi_software_mosi_pin: PE13
#spi_software_sclk_pin: PE14
#run_current: 0.800
#stealthchop_threshold: 999999
#diag1_pin: PA0

View File

@ -105,7 +105,7 @@ max_temp: 130
[fan] [fan]
pin: P2.1 pin: P2.1
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: P2.2 pin: P2.2
[mcu] [mcu]

View File

@ -100,7 +100,7 @@ pid_Kd: 948.182
min_temp: 0 min_temp: 0
max_temp: 130 max_temp: 130
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: PC7 pin: PC7
[fan] [fan]

View File

@ -98,7 +98,7 @@ pid_Kd: 948.182
min_temp: 0 min_temp: 0
max_temp: 130 max_temp: 130
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: PC7 pin: PC7
[heater_fan controller_fan] [heater_fan controller_fan]

View File

@ -103,7 +103,7 @@ pid_Kd: 948.182
min_temp: 0 min_temp: 0
max_temp: 130 max_temp: 130
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: PC7 pin: PC7
[fan] [fan]

View File

@ -100,7 +100,7 @@ max_temp: 130
[fan] [fan]
pin: gpio17 pin: gpio17
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: gpio18 pin: gpio18
[heater_fan controller_fan] [heater_fan controller_fan]

View File

@ -288,7 +288,7 @@ max_temp: 130
pin: PC23 pin: PC23
# Fan1 controlled by extruder # Fan1 controlled by extruder
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: PC26 pin: PC26
heater: extruder heater: extruder
heater_temp: 45 heater_temp: 45

View File

@ -123,7 +123,7 @@ max_temp: 130
[fan] [fan]
pin: PC23 # FAN0 pin: PC23 # FAN0
#[heater_fan nozzle_cooling_fan] #[heater_fan heatbreak_cooling_fan]
#pin: PC22 # FAN1 #pin: PC22 # FAN1
#[heater_fan board_cooling_fan] #[heater_fan board_cooling_fan]

View File

@ -101,7 +101,7 @@ max_temp: 130
[fan] [fan]
pin: PC23 # FAN0 pin: PC23 # FAN0
#[heater_fan nozzle_cooling_fan] #[heater_fan heatbreak_cooling_fan]
#pin: PC26 # FAN1 #pin: PC26 # FAN1
#[heater_fan board_cooling_fan] #[heater_fan board_cooling_fan]

View File

@ -26,6 +26,7 @@
# SBC SPISS pin:PA6, SBCTfrReady:PA3, SerComPins:{PA4, PA5, PA6, PA7} # SBC SPISS pin:PA6, SBCTfrReady:PA3, SerComPins:{PA4, PA5, PA6, PA7}
# CAN Pins - TX:PB14 RX:PB15 # CAN Pins - TX:PB14 RX:PB15
# Heaters, Fan outputs - {Out0:PB17 Out1:PC10 Out2:PB13 Out3:PB11 Out4:PA11, Out5:PB2, Out6:PB1} | Out6 is shared with VFD_Out # Heaters, Fan outputs - {Out0:PB17 Out1:PC10 Out2:PB13 Out3:PB11 Out4:PA11, Out5:PB2, Out6:PB1} | Out6 is shared with VFD_Out
# Tach Pins for Fans - {Out3.Tach:PB27 Out4.Tach:PB26}
# GPIO_out - {IO1:PB31 IO2:PD9 IO3:PB12 IO4:PD10} IO4 is shared with PSON # GPIO_out - {IO1:PB31 IO2:PD9 IO3:PB12 IO4:PD10} IO4 is shared with PSON
# GPIO_in - {IO1:PB30 IO2:PD8 IO3:PB7 IO4:PC5 IO5:PC4 IO6:PC31} # GPIO_in - {IO1:PB30 IO2:PD8 IO3:PB7 IO4:PC5 IO5:PC4 IO6:PC31}
# Driver Diag - {D0:PA10, D1:PB8, D2:PA22, D3:PA23, D4:PC21, D5:PB10, D6:PA27} # Driver Diag - {D0:PA10, D1:PB8, D2:PA22, D3:PA23, D4:PC21, D5:PB10, D6:PA27}

View File

@ -95,7 +95,7 @@ max_temp: 130
[fan] [fan]
pin: PH5 pin: PH5
#[heater_fan nozzle_cooling_fan] #[heater_fan heatbreak_cooling_fan]
#pin: PH3 #pin: PH3
[temperature_sensor board_sensor] [temperature_sensor board_sensor]

View File

@ -97,7 +97,7 @@ max_temp: 130
[fan] [fan]
pin: PC8 pin: PC8
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: PB0 pin: PB0
[mcu] [mcu]

View File

@ -65,7 +65,7 @@ max_temp: 130
[fan] [fan]
pin: PH5 pin: PH5
#[heater_fan nozzle_cooling_fan] #[heater_fan heatbreak_cooling_fan]
#pin: PH3 #pin: PH3
[mcu] [mcu]

View File

@ -104,7 +104,7 @@ max_temp: 290
[fan] [fan]
pin: PB27 pin: PB27
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: PA6 pin: PA6
[mcu] [mcu]

View File

@ -81,7 +81,7 @@ max_temp: 130
[fan] [fan]
pin: PC21 pin: PC21
#[heater_fan nozzle_cooling_fan] #[heater_fan heatbreak_cooling_fan]
#pin: PC22 #pin: PC22
[mcu] [mcu]

View File

@ -75,7 +75,7 @@ max_temp: 130
[fan] [fan]
pin: PH5 pin: PH5
#[heater_fan nozzle_cooling_fan] #[heater_fan heatbreak_cooling_fan]
#pin: PH3 #pin: PH3
[mcu] [mcu]

View File

@ -1,6 +1,6 @@
# This file contains common pin mappings for the TH3D EZBoard Lite v2. # This file contains common pin mappings for the TH3D EZBoard Lite v2.
# To use this config, the firmware should be compiled for the # To use this config, the firmware should be compiled for the
# STM32F407 with 12mhz Crystal, 48KiB Bootloader, and USB communication. # STM32F405 with 12mhz Crystal, 48KiB Bootloader, and USB communication.
# The "make flash" command does not work on this board. Instead, # The "make flash" command does not work on this board. Instead,
# after running "make", copy the generated "out/klipper.bin" file to a # after running "make", copy the generated "out/klipper.bin" file to a

View File

@ -11,7 +11,7 @@
# For Anycubic 4Max Pro (not 2.0) owners: # For Anycubic 4Max Pro (not 2.0) owners:
# Be careful when using this config! This config tested only on Anycubic # Be careful when using this config! This config tested only on Anycubic
# 4Max Pro 2.0 with klipper v0.9.1-667-g31761500! At first, you should # 4Max Pro 2.0! At first, you should
# set homing_speed on 5, and run homing and click on the endstops with # set homing_speed on 5, and run homing and click on the endstops with
# your fingers. It is necessary to make sure that all the motors are # your fingers. It is necessary to make sure that all the motors are
# spinning in the right direction, all the temperature sensors show the # spinning in the right direction, all the temperature sensors show the
@ -139,3 +139,50 @@ screw4: 265, 5
[filament_switch_sensor filament_sensor] [filament_switch_sensor filament_sensor]
switch_pin: ^!PC4 switch_pin: ^!PC4
[output_pin buzz]
pin: PC6
pwm: True
[output_pin AUTO_POWEROFF]
pin: PD0
pwm: True
cycle_time: 0.02
value: 1
# This macro (M300) uses internal integrated beeper
# Just use it in your G-code for making sounds. Example: M300 S1000 P500
[gcode_macro M300]
gcode:
{% set S = params.S|default(800)|float %}
{% set P = params.P|default(100)|int %}
SET_PIN PIN=buzz VALUE=0.5 CYCLE_TIME={ 1.0 / S | float }
G4 P{P}
SET_PIN PIN=buzz VALUE=0
# This macro (M81) uses internal integrated PSU control-relay.
# Just use M81 in your end_gcode if you want to poweroff your printer after print.
# Note: as in original Marlin firmware, before powerdown, printer will be cool hotend
# until temperature will be below 45°С / 113°F.
[gcode_macro M81]
gcode:
{% set required_extruder_temp = params.T|default(45)|int %}
{% if printer.extruder.temperature > required_extruder_temp|default(45)|int %}
M300
M300
M300
M117 COOLING DOWN BEFORE POWER OFF
M109 S{required_extruder_temp}
SET_PIN PIN=AUTO_POWEROFF VALUE=0.5
G4 P60
SET_PIN PIN=AUTO_POWEROFF VALUE=1
{% else %}
M300
M117 POWER OFF SOON
G4 P10000
SET_PIN PIN=AUTO_POWEROFF VALUE=0.5
G4 P60
SET_PIN PIN=AUTO_POWEROFF VALUE=1
{% endif %}

View File

@ -0,0 +1,194 @@
# This file contains common pin mappings for the Biqu B1 SE Plus.
# To use this config, the firmware should be compiled for the
# STM32F407 with a "32KiB bootloader".
# In newer versions of this board shipped in late 2021 the STM32F429
# is used, if this is the case compile for this with a "32KiB bootloader"
# You will need to check the chip on your board to identify which you have.
#
# The "make flash" command does not work on the SKR 2. Instead,
# after running "make", copy the generated "out/klipper.bin" file to a
# file named "firmware.bin" on an SD card and then restart the SKR 2
# with that SD card.
# See docs/Config_Reference.md for a description of parameters.
[mcu]
serial: /dev/serial/by-id/usb-Klipper_stm32f407xx_1D0039000F47393438343535-if00
########################################
# Stepper X Pins and TMC2208 configuration
########################################
[stepper_x]
step_pin: PE2
dir_pin: !PE1
enable_pin: !PE3
microsteps: 16
rotation_distance: 40
endstop_pin: !PC1
position_endstop: 0
position_max: 310
homing_speed: 50
[tmc2208 stepper_x]
uart_pin: PE0
run_current: 0.800
stealthchop_threshold: 999999
########################################
# Stepper Y Pins and TMC2208 configuration
########################################
[stepper_y]
step_pin: PD5
dir_pin: PD4
enable_pin: !PD6
microsteps: 16
rotation_distance: 40
endstop_pin: !PC3
position_endstop: 0
position_max: 310
homing_speed: 50
[tmc2208 stepper_y]
uart_pin: PD3
run_current: 0.800
stealthchop_threshold: 999999
########################################
# Stepper Z Pins and TMC2208 configuration
########################################
[stepper_z]
step_pin: PA15
dir_pin: PA8
enable_pin: !PD1
microsteps: 16
rotation_distance: 8
endstop_pin: probe:z_virtual_endstop
homing_speed: 10
second_homing_speed: 1
position_min: -2
position_max: 340
[tmc2208 stepper_z]
uart_pin: PD0
run_current: 0.800
stealthchop_threshold: 999999
########################################
# Extruder Pins and TMC2208 configuration
########################################
[extruder]
step_pin: PD15
dir_pin: !PD14
enable_pin: !PC7
microsteps: 16
rotation_distance: 34.2152 # Calibrar - ver https://www.klipper3d.org/Rotation_Distance.html
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PB3
sensor_type: Generic 3950
sensor_pin: PA2 #thermistor pin
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[tmc2208 extruder]
uart_pin: PC6
run_current: 0.800
stealthchop_threshold: 999999
########################################
# Heater Bed Pins
########################################
[heater_bed]
heater_pin: PD7
sensor_type: Generic 3950
sensor_pin: PA1
control: pid
pid_Kp: 54
pid_Ki: 0.77
pid_Kd: 900
min_temp: 0
max_temp: 110
########################################
# Printer Configuration
########################################
[printer]
kinematics: cartesian
max_velocity: 200
max_accel: 1000
max_z_velocity: 5
max_z_accel: 100
########################################
# Probe configuration
########################################
[probe]
pin: ^!PE4
z_offset: 0.0
x_offset: 0.0
y_offset: 0.0
speed: 10.0
samples: 2
samples_result: average
sample_retract_dist: 2.0
samples_tolerance: 0.2
[safe_z_home]
home_xy_position: 155,155
speed: 100
z_hop: 5
z_hop_speed: 5
[output_pin probe_enable]
pin: PE5
value: 1
########################################
# Bed Mesh configuration
########################################
[bed_mesh]
speed: 2000
horizontal_move_z: 3
mesh_min: 20, 20
mesh_max: 290, 290
probe_count: 7, 7
mesh_pps: 2,2
algorithm: bicubic
bicubic_tension: 0.2
########################################
# Fan Nozzle configuration
########################################
[fan]
pin: PB7
[heater_fan Cooling_fan]
pin: PB6
max_power: 1.0
kick_start_time: 0.100
heater: heater_bed
[heater_fan Board_fan]
pin: PB5
max_power: 1.0
kick_start_time: 0.100
heater: extruder
########################################
# Filament Sensor configuration
########################################
[filament_switch_sensor Sensor_Filamento]
switch_pin: !PC2
pause_on_runout: true #pause handled by macro
########################################
# Motor Power Pin
########################################
[output_pin motor_power]
pin: PC13
value: 1

View File

@ -0,0 +1,107 @@
# This file contains pin mappings for the BQ Prusa i3 Hephestos from 2014
# (https://www.reprap.org/wiki/Prusa_i3_Hephestos)
# It was sold in kit form, and uses a RAMPS board with HD44780 display without
# heated bed or any modern amenities.
# To use this config, the firmware should be compiled for the AVR atmega2560.
# See docs/Config_Reference.md for a description of parameters.
[display]
lcd_type: hd44780
rs_pin: PH1
e_pin: PH0
d4_pin: PA1
d5_pin: PA3
d6_pin: PA5
d7_pin: PA7
encoder_pins: ^PC4, ^PC6
click_pin: ^!PC2
kill_pin: ^!PG0
[stepper_x]
step_pin: PF0
dir_pin: !PF1
enable_pin: !PD7
microsteps: 16
rotation_distance: 40
endstop_pin: ^!PE5
position_endstop: 0
position_max: 215
homing_speed: 50
[stepper_y]
step_pin: PF6
dir_pin: PF7
enable_pin: !PF2
microsteps: 16
rotation_distance: 40
endstop_pin: ^!PJ1
position_endstop: 0
position_max: 210
homing_speed: 50
[stepper_z]
step_pin: PL3
dir_pin: !PL1
enable_pin: !PK0
microsteps: 16
rotation_distance: 0.8
endstop_pin: ^!PD3
position_endstop: 0
position_max: 200
homing_speed: 3
[extruder]
step_pin: PA4
dir_pin: PA6
enable_pin: !PA2
microsteps: 16
# measured extruding 100mm of filament with stock Hephestos extruder
rotation_distance: 31.825
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PB4
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PK5
min_temp: 0
max_temp: 250
control: pid
pid_kp: 19.462
pid_ki: 0.713
pid_kd: 132.830
# 5 points for manual bed leveling that still leave room for accessing the stock screws
[bed_screws]
screw1: 40, 40
screw2: 180, 40
screw3: 180, 160
screw4: 40, 160
screw5: 110, 100
[fan]
pin: PH6
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
# Must limit Z velocity, since RAMPS does not have enough timer resolution
max_z_velocity: 3
max_z_accel: 100
[mcu]
serial: /dev/ttyUSB0
# Common EXP1 / EXP2 (display) pins
[board_pins]
aliases:
# Common EXP1 header found on many "all-in-one" ramps clones
EXP1_1=PC0, EXP1_3=PH0, EXP1_5=PA1, EXP1_7=PA5, EXP1_9=<GND>,
EXP1_2=PC2, EXP1_4=PH1, EXP1_6=PA3, EXP1_8=PA7, EXP1_10=<5V>,
# EXP2 header
EXP2_1=PB3, EXP2_3=PC6, EXP2_5=PC4, EXP2_7=PL0, EXP2_9=<GND>,
EXP2_2=PB1, EXP2_4=PB0, EXP2_6=PB2, EXP2_8=PG0, EXP2_10=<RST>
# Pins EXP2_1, EXP2_6, EXP2_2 are also MISO, MOSI, SCK of bus "spi"
# Note, some boards wire: EXP2_8=<RST>, EXP2_10=PG0

View File

@ -0,0 +1,162 @@
# This file contains common pin mappings for the 2020 Creality CR-10
# V3. The mainboard is a Creality 3D v2.5.2 (8-bit mainboard with
# ATMega2560). To use this config, the firmware should be compiled for
# the AVR atmega2560.
# See docs/Config_Reference.md for a description of parameters.
# For better compatibility with GCodes generated for Marlin, you
# may wish to add the following section, if you have BLTouch:
#[gcode_macro G29]
#gcode:
# BED_MESH_CALIBRATE
[stepper_x]
step_pin: PF0 #ar54
dir_pin: PF1 #ar55
enable_pin: !PD7 #!ar38
microsteps: 16
rotation_distance: 40
endstop_pin: ^PE5 #^ar3
position_endstop: 0
position_max: 300
homing_speed: 50
[stepper_y]
step_pin: PF6 #ar60
dir_pin: PF7 #ar61
enable_pin: !PF2 #!ar56
microsteps: 16
rotation_distance: 40
endstop_pin: ^PJ1 #^ar14
position_endstop: 0
position_max: 300
homing_speed: 50
[stepper_z]
step_pin: PL3 #ar46
dir_pin: !PL1 #!ar48
enable_pin: !PK0 #!ar62
microsteps: 16
rotation_distance: 8
position_max: 400
#Uncomment if you have a BL-Touch:
#position_min: -4
#endstop_pin: probe:z_virtual_endstop
#and comment the follwing lines:
position_endstop: 0.0
endstop_pin: ^PD3 #ar18
[safe_z_home]
home_xy_position: 104.25,147.6
speed: 80
z_hop: 10
z_hop_speed: 10
[extruder]
step_pin: PA4 # ar26
dir_pin: !PA6 # !ar28
enable_pin: !PA2 # !ar24
microsteps: 16
rotation_distance: 7.7201944 # 16 microsteps * 200 steps/rotation / steps/mm
#Correction formula is new_rotation_distance = old_rotation_distance * mmsExtracted / 100.0
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PB4 #ar10
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PK5 #analog13
control: pid
pid_kp: 22.107
pid_ki: 1.170
pid_kd: 104.458
min_temp: 0
max_temp: 255
[heater_bed]
heater_pin: PH5 #ar8
sensor_type: ATC Semitec 104GT-2
sensor_pin: PK6 #analog14
control: pid
#Stock PID configuration taken from Marlin
pid_Kp: 201.86
pid_Ki: 10.67
pid_Kd: 954.96
min_temp: 0
max_temp: 130
[fan]
pin: PH6 #ar9
[mcu]
serial: /dev/ttyUSB0
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100
[display]
lcd_type: st7920
cs_pin: PH1 #ar16
sclk_pin: PA1 #ar23
sid_pin: PH0 #ar17
encoder_pins: ^PC4, ^PC6 #^ar33, ^ar31
click_pin: ^!PC2 #^!ar35
#Uncomment the following lines if you have a BL-Touch
#[bltouch]
#sensor_pin: ^PD2 #^ar19
#control_pin: PB5 #ar11
#set_output_mode: 5V
#pin_move_time: 0.4
#stow_on_each_sample: False
#probe_with_touch_mode: False
#x_offset: 45.75
#y_offset: -3.40
#z_offset: 3.28
#samples: 2
#sample_retract_dist: 2
#samples_result: average
#Uncomment the following lines if you have a BL-Touch
#[bed_mesh]
#speed: 50
#horizontal_move_z: 6
#mesh_min: 46.50,0.75
#mesh_max: 253.5,295.85
#probe_count: 7,7
#algorithm: bicubic
[pause_resume]
recover_velocity: 50
[filament_switch_sensor fil_runout_sensor]
pause_on_runout: True
switch_pin: PE4 #ar2
[bed_screws]
screw1: 33,29
screw1_name: front left screw
screw2: 273,29
screw2_name: front right screw
screw3: 273,269
screw3_name: rear right screw
screw4: 33,269
screw4_name: rear left screw
#Uncomment the following lines if you have a BL-Touch
#[screws_tilt_adjust]
#screw1: 0,29
#screw1_name: front left screw
#screw2: 228,29
#screw2_name: front right screw
#screw3: 228,269
#screw3_name: rear right screw
#screw4: 0,269
#screw4_name: rear left screw
#speed: 50
#horizontal_move_z: 10
#screw_thread: CW-M3

View File

@ -0,0 +1,131 @@
# This file contains pin mappings for the stock 2021 Creality Ender 3
# S1 (and S1 pro). To use this config, check the STM32 Chip on the
# V2.4S1 Board then during "make menuconfig" select either the
# STM32F103 with a "28KiB bootloader" or select the STM32F401 with a
# "64KiB bootloader" and serial (on USART1 PA10/PA9) communication for
# both depending on the STM32 chip installed on your printer's
# motherboard.
# If you prefer a direct serial connection, in "make menuconfig"
# select "Enable extra low-level configuration options" and select
# Serial (on USART2 PA3/PA2), which is broken out on the 10 pin IDC
# cable used for the LCD module as follows:
# 3: Tx, 4: Rx, 9: GND, 10: VCC
# Flash this firmware by copying "out/klipper.bin" to a SD card and
# turning on the printer with the card inserted. The firmware
# filename must changed to "firmware.bin"
# See docs/Config_Reference.md for a description of parameters.
[stepper_x]
step_pin: PC2
dir_pin: PB9
enable_pin: !PC3
microsteps: 16
rotation_distance: 40
endstop_pin: !PA5
position_endstop: -10
position_max: 235
position_min: -15
homing_speed: 50
[stepper_y]
step_pin: PB8
dir_pin: PB7
enable_pin: !PC3
microsteps: 16
rotation_distance: 40
endstop_pin: !PA6
position_endstop: -10
position_max: 241
position_min: -15
homing_speed: 50
[stepper_z]
step_pin: PB6
dir_pin: !PB5
enable_pin: !PC3
microsteps: 16
rotation_distance: 8
endstop_pin: probe:z_virtual_endstop
position_max: 270
position_min: -4
[extruder]
step_pin: PB4
dir_pin: PB3
enable_pin: !PC3
microsteps: 16
gear_ratio: 42:12
rotation_distance: 26.359
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PA1
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PC5
control: pid
pid_Kp: 23.561
pid_Ki: 1.208
pid_Kd: 114.859
min_temp: 0
max_temp: 250
[heater_bed]
heater_pin: PA7
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PC4
control: pid
pid_Kp: 71.867
pid_Ki: 1.536
pid_Kd: 840.843
min_temp: 0
max_temp: 110
[heater_fan hotend_fan]
pin: PC0
[fan]
pin: PA0
[mcu]
serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0
restart_method: command
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 2000
max_z_velocity: 5
max_z_accel: 100
[bltouch]
sensor_pin: ^PC14
control_pin: PC13
x_offset: -31.8
y_offset: -40.5
z_offset: 0
probe_with_touch_mode: true
stow_on_each_sample: false
[bed_mesh]
speed: 120
mesh_min: 20, 20
mesh_max: 200, 200
probe_count: 4,4
algorithm: bicubic
[safe_z_home]
home_xy_position: 147, 154
speed: 75
z_hop: 5
z_hop_speed: 5
move_to_previous: true
[filament_switch_sensor e0_sensor]
switch_pin: !PC15
pause_on_runout: true
runout_gcode: PAUSE
[pause_resume]
recover_velocity: 25

View File

@ -0,0 +1,109 @@
# This file contains pin mappings for the Creality Sermoon V1
# with CR-FDM-v2.4.S1.200 motherboard.
# To use this config, during "make menuconfig" select the STM32F401
# with a "64KiB bootloader" and serial (on USART1 PA10/PA9)
# communication.
# If you prefer a direct serial connection, in "make menuconfig"
# select "Enable extra low-level configuration options" and select
# Serial (on USART2 PA3/PA2), which is broken out on the 10 pin IDC
# cable used for the LCD module as follows:
# 3: Tx, 4: Rx, 9: GND, 10: VCC
# Flash this firmware by copying "out/klipper.bin" to a SD card and
# turning on the printer with the card inserted. The firmware
# filename must changed to "firmware.bin"
# See docs/Config_Reference.md for a description of parameters.
[stepper_x]
step_pin: PA7
dir_pin: !PA4
enable_pin: !PB8
microsteps: 16
rotation_distance: 40
endstop_pin: PC4
position_endstop: 175
position_max: 175
position_min: 0
homing_speed: 50
[stepper_y]
step_pin: PB0
dir_pin: PB10
enable_pin: !PB8
microsteps: 16
rotation_distance: 40
endstop_pin: PB13
position_endstop: 0
position_max: 175
position_min: 0
homing_speed: 50
[stepper_z]
step_pin: PB7
dir_pin: PB6
enable_pin: !PB8
microsteps: 16
rotation_distance: 8
endstop_pin: PB3
position_endstop: 165
position_max: 168
position_min: -3
[extruder]
step_pin: PB1
dir_pin: PB12
enable_pin: !PB8
microsteps: 16
gear_ratio: 42:12
rotation_distance: 26.359
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PC5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PC1
control: pid
pid_Kp: 30.090
pid_Ki: 1.875
pid_Kd: 120.735
min_temp: 0
max_temp: 290
[heater_bed]
heater_pin: PB9
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PC0
control: pid
pid_Kp: 75.694
pid_Ki: 1.160
pid_Kd: 1234.759
min_temp: 0
max_temp: 90
[fan]
pin: PA5
[fan_generic side_fan]
pin: PC15
# [controller_fan controller_fan]
# In order to access the controller fan, the controller fan needs to be plugged
# in another location. See https://github.com/Klipper3d/klipper/pull/5621
# for more information.
# pin: PB4
[mcu]
serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0
restart_method: command
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 2000
max_z_velocity: 5
max_z_accel: 100
[pause_resume]
recover_velocity: 25

View File

@ -112,7 +112,7 @@ max_temp: 100
[fan] [fan]
pin: PB5 pin: PB5
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: PB4 pin: PB4
[mcu] [mcu]

View File

@ -67,7 +67,7 @@ max_extrude_only_distance: 300
[fan] [fan]
pin: PH5 pin: PH5
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: PH3 pin: PH3
[heater_bed] [heater_bed]

View File

@ -114,7 +114,7 @@ max_temp: 130
#define FAN_PIN 8 #define FAN_PIN 8
pin: PH5 pin: PH5
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
#define FAN1_PIN 6 #define FAN1_PIN 6
pin: PH3 pin: PH3

View File

@ -97,7 +97,7 @@ max_temp: 130
[fan] [fan]
pin: PH5 pin: PH5
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: PH3 pin: PH3
[mcu] [mcu]

View File

@ -128,7 +128,7 @@ max_temp: 130
#On Dual v3 heat break fan is connected to PH3 (part cooling fan on single extruder) #On Dual v3 heat break fan is connected to PH3 (part cooling fan on single extruder)
pin: PH3 pin: PH3
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
#On Dual v3 part fans are connected to PH5 (heat break fan on single extruder) #On Dual v3 part fans are connected to PH5 (heat break fan on single extruder)
pin: PH5 pin: PH5

View File

@ -70,7 +70,7 @@ max_temp: 90
[fan] [fan]
pin: PH5 pin: PH5
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: PH3 pin: PH3
[mcu] [mcu]

View File

@ -104,7 +104,7 @@ mesh_max: 225, 225
[fan] [fan]
pin: PH5 pin: PH5
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: PH3 pin: PH3
[mcu] [mcu]

View File

@ -63,7 +63,7 @@ max_temp: 300
[fan] [fan]
pin: PH5 pin: PH5
[heater_fan nozzle_cooling_fan] [heater_fan heatbreak_cooling_fan]
pin: PH4 pin: PH4
heater: extruder heater: extruder

View File

@ -0,0 +1,66 @@
# This file contains common pin mappings for the BIGTREETECH EBBCan
# Canbus board. To use this config, the firmware should be compiled for the
# STM32F072 with "8 MHz crystal" and "USB (on PA11/PA12)" or "CAN bus (on PB8/PB9)".
# The "EBB Can" micro-controller will be used to control the components on the nozzle.
# See docs/Config_Reference.md for a description of parameters.
[mcu EBBCan]
serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00
#canbus_uuid: 0e0d81e4210c
[adxl345]
cs_pin: EBBCan: PB12
spi_bus: spi2
axes_map: x,y,z
[extruder]
step_pin: EBBCan: PA9
dir_pin: !EBBCan: PA8
enable_pin: !EBBCan: PA10
microsteps: 16
rotation_distance: 33.500
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: EBBCan: PB1
sensor_type: EPCOS 100K B57560G104F
sensor_pin: EBBCan: PA0
control: pid
pid_Kp: 21.527
pid_Ki: 1.063
pid_Kd: 108.982
min_temp: 0
max_temp: 250
#sensor_type:MAX31865
#sensor_pin: EBBCan: PA15
#spi_bus: spi1a
#rtd_nominal_r: 100
#rtd_reference_r: 430
#rtd_num_of_wires: 2
[tmc2209 extruder]
uart_pin: EBBCan: PA13
run_current: 0.650
stealthchop_threshold: 999999
[fan]
pin: EBBCan: PA1
[heater_fan hotend_fan]
pin: EBBCan: PA2
heater: extruder
heater_temp: 50.0
#[neopixel hotend_rgb]
#pin: EBBCan:PA3
#[bltouch]
#sensor_pin: ^EBBCan:PA5
#control_pin: EBBCan:PA4
#[filament_switch_sensor switch_sensor]
#switch_pin: EBBCan:PB6
#[filament_motion_sensor motion_sensor]
#switch_pin: ^EBBCan:PB7

View File

@ -0,0 +1,68 @@
# This file contains common pin mappings for the BIGTREETECH EBBCan
# Canbus board. To use this config, the firmware should be compiled for the
# STM32G0B1 with "8 MHz crystal" and "USB (on PA11/PA12)" or "CAN bus (on PB0/PB1)".
# The "EBB Can" micro-controller will be used to control the components on the nozzle.
# See docs/Config_Reference.md for a description of parameters.
[mcu EBBCan]
serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00
#canbus_uuid: 0e0d81e4210c
[adxl345]
cs_pin: EBBCan: PB12
spi_software_sclk_pin: EBBCan: PB10
spi_software_mosi_pin: EBBCan: PB11
spi_software_miso_pin: EBBCan: PB2
axes_map: x,y,z
[extruder]
step_pin: EBBCan: PD0
dir_pin: !EBBCan: PD1
enable_pin: !EBBCan: PD2
microsteps: 16
rotation_distance: 33.500
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: EBBCan: PA2
sensor_type: EPCOS 100K B57560G104F
sensor_pin: EBBCan: PA3
control: pid
pid_Kp: 21.527
pid_Ki: 1.063
pid_Kd: 108.982
min_temp: 0
max_temp: 250
# sensor_type:MAX31865
# sensor_pin: EBBCan: PA4
# spi_bus: spi1
# rtd_nominal_r: 100
# rtd_reference_r: 430
# rtd_num_of_wires: 2
[tmc2209 extruder]
uart_pin: EBBCan: PA15
run_current: 0.650
stealthchop_threshold: 999999
[fan]
pin: EBBCan: PA0
[heater_fan hotend_fan]
pin: EBBCan: PA1
heater: extruder
heater_temp: 50.0
#[neopixel hotend_rgb]
#pin: EBBCan:PD3
#[bltouch]
#sensor_pin: ^EBBCan:PB8
#control_pin: EBBCan:PB9
#[filament_switch_sensor switch_sensor]
#switch_pin: EBBCan:PB4
#[filament_motion_sensor motion_sensor]
#switch_pin: ^EBBCan:PB3

View File

@ -0,0 +1,58 @@
# This file contains common pin mappings for the Huvud V0.61 by Bondus.
# https://github.com/bondus/Klipperhuvudboard
# To use this config, copy the contents into your main config file.
# The huvud is not capable of running a printer on it's own. It
# needs to be paired with another board that will control other
# aspects of the printer.
# The firmware should be compiled for the STM32F103 with a "2KiB
# bootloader" and a "8MHz crystal" clock reference.
# Select CAN bus (on PB8/PB9) or USB under communication interface.
# Flash by running make flash FLASH_DEVICE=1209:beba
# See docs/Config_Reference.md for a description of parameters.
[mcu huvud]
canbus_uuid: ac20f0bbda05
# Identify your canbus_uuid by running:
# ~/klippy-env/bin/python ~/klipper/scripts/canbus_query.py can0
[extruder]
step_pin: huvud: PB3
dir_pin: huvud: PB4
enable_pin: !huvud: PB5
rotation_distance: 22.52453125
nozzle_diameter: 0.400
filament_diameter: 1.75
heater_pin: huvud: PA6
sensor_type: NTC 100K MGB18-104F39050L32
sensor_pin: huvud: PA0
pullup_resistor: 2200
min_temp: 0
max_temp: 300
control: pid
pid_kp: 26.213
pid_ki: 1.304
pid_kd: 131.721
[tmc2209 extruder]
uart_pin: huvud: PA10
tx_pin: huvud: PA9
run_current: 0.35
[probe]
pin: huvud: PB12
z_offset: 0
[fan]
pin: huvud: PA8
[heater_fan extruder_fan]
pin: huvud: PA7
[adxl345]
cs_pin: PB1
[led huvud_led]
blue_pin: huvud: PC13

View File

@ -95,6 +95,21 @@ click_pin: ^!EXP1_2
pin: EXP1_1 pin: EXP1_1
########################################################################################
# Anet 128x64 LCD with alternative wiring (ANET_FULL_GRAPHICS_LCD_ALT_WIRING in Marlin)
########################################################################################
[display]
lcd_type: st7920
cs_pin: EXP1_8
sclk_pin: EXP1_4
sid_pin: EXP1_6
encoder_pins: ^!EXP1_7, ^!EXP1_5
click_pin: ^!EXP1_3
[output_pin beeper]
pin: EXP1_1
###################################################################### ######################################################################
# Fysetc Mini 12864Panel v2.1 (with neopixel backlight leds) # Fysetc Mini 12864Panel v2.1 (with neopixel backlight leds)
###################################################################### ######################################################################

View File

@ -175,6 +175,29 @@ gcode:
sensor.temperature, sensor.temperature,
sensor.humidity))} sensor.humidity))}
######################################################################
# Override M117 command with rawparams
######################################################################
# The macro below will override the default M117 command to echo the message.
#
# It uses the rawparams pseudo-variable that contains the full unparsed
# parameters that was passed to the M117 command.
#
# As this can include comments, we are trimming the text when a `;` or `#` is
# found, and escaping any existing `"`
[gcode_macro M117]
rename_existing: M117.1
gcode:
{% if rawparams %}
{% set escaped_msg = rawparams.split(';', 1)[0].split('\x23', 1)[0]|replace('"', '\\"') %}
SET_DISPLAY_TEXT MSG="{escaped_msg}"
RESPOND TYPE=command MSG="{escaped_msg}"
{% else %}
SET_DISPLAY_TEXT
{% endif %}
# SDCard 'looping' (aka Marlin M808 commands) support # SDCard 'looping' (aka Marlin M808 commands) support
# #
# Support SDCard looping # Support SDCard looping
@ -186,3 +209,55 @@ gcode:
{% if params.K is not defined and params.L is defined %}SDCARD_LOOP_BEGIN COUNT={params.L|int}{% endif %} {% if params.K is not defined and params.L is defined %}SDCARD_LOOP_BEGIN COUNT={params.L|int}{% endif %}
{% if params.K is not defined and params.L is not defined %}SDCARD_LOOP_END{% endif %} {% if params.K is not defined and params.L is not defined %}SDCARD_LOOP_END{% endif %}
{% if params.K is defined and params.L is not defined %}SDCARD_LOOP_DESIST{% endif %} {% if params.K is defined and params.L is not defined %}SDCARD_LOOP_DESIST{% endif %}
# Cancel object (aka Marlin/RRF M486 commands) support
#
# Enable object exclusion
[exclude_object]
[gcode_macro M486]
gcode:
# Parameters known to M486 are as follows:
# [C<flag>] Cancel the current object
# [P<index>] Cancel the object with the given index
# [S<index>] Set the index of the current object.
# If the object with the given index has been canceled, this will cause
# the firmware to skip to the next object. The value -1 is used to
# indicate something that isnt an object and shouldnt be skipped.
# [T<count>] Reset the state and set the number of objects
# [U<index>] Un-cancel the object with the given index. This command will be
# ignored if the object has already been skipped
{% if 'exclude_object' not in printer %}
{action_raise_error("[exclude_object] is not enabled")}
{% endif %}
{% if 'T' in params %}
EXCLUDE_OBJECT RESET=1
{% for i in range(params.T | int) %}
EXCLUDE_OBJECT_DEFINE NAME={i}
{% endfor %}
{% endif %}
{% if 'C' in params %}
EXCLUDE_OBJECT CURRENT=1
{% endif %}
{% if 'P' in params %}
EXCLUDE_OBJECT NAME={params.P}
{% endif %}
{% if 'S' in params %}
{% if params.S == '-1' %}
{% if printer.exclude_object.current_object %}
EXCLUDE_OBJECT_END NAME={printer.exclude_object.current_object}
{% endif %}
{% else %}
EXCLUDE_OBJECT_START NAME={params.S}
{% endif %}
{% endif %}
{% if 'U' in params %}
EXCLUDE_OBJECT RESET=1 NAME={params.U}
{% endif %}

View File

@ -4,9 +4,9 @@ This document describes Klipper's CAN bus support.
## Device Hardware ## Device Hardware
Klipper currently only supports CAN on stm32 chips. In addition, the Klipper currently supports CAN on stm32 and rp2040 chips. In addition,
micro-controller chip must support CAN and it must be on a board that the micro-controller chip must be on a board that has a CAN
has a CAN transceiver. transceiver.
To compile for CAN, run `make menuconfig` and select "CAN bus" as the To compile for CAN, run `make menuconfig` and select "CAN bus" as the
communication interface. Finally, compile the micro-controller code communication interface. Finally, compile the micro-controller code
@ -73,7 +73,7 @@ powered and wired correctly, and then run:
If uninitialized CAN devices are detected the above command will If uninitialized CAN devices are detected the above command will
report lines like the following: report lines like the following:
``` ```
Found canbus_uuid=11aa22bb33cc Found canbus_uuid=11aa22bb33cc, Application: Klipper
``` ```
Each device will have a unique identifier. In the above example, Each device will have a unique identifier. In the above example,
@ -91,3 +91,40 @@ the CAN bus to communicate with the device - for example:
[mcu my_can_mcu] [mcu my_can_mcu]
canbus_uuid: 11aa22bb33cc canbus_uuid: 11aa22bb33cc
``` ```
## USB to CAN bus bridge mode
Some micro-controllers support selecting "USB to CAN bus bridge" mode
during "make menuconfig". This mode may allow one to use a
micro-controller as both a "USB to CAN bus adapter" and as a Klipper
node.
When Klipper uses this mode the micro-controller appears as a "USB CAN
bus adapter" under Linux. The "Klipper bridge mcu" itself will appear
as if was on this CAN bus - it can be identified via `canbus_query.py`
and configured like other CAN bus Klipper nodes. It will appear
alongside other devices that are actually on the CAN bus.
Some important notes when using this mode:
* The "bridge mcu" is not actually on the CAN bus. Messages to and
from it do not consume bandwidth on the CAN bus. The mcu can not be
seen by other adapters that may be on the CAN bus.
* It is necessary to configure the `can0` (or similar) interface in
Linux in order to communicate with the bus. However, Linux CAN bus
speed and CAN bus bit-timing options are ignored by Klipper.
Currently, the CAN bus frequency is specified during "make
menuconfig" and the bus speed specified in Linux is ignored.
* Whenever the "bridge mcu" is reset, Linux will disable the
corresponding `can0` interface. To ensure proper handling of
FIRMWARE_RESTART and RESTART commands, it is recommended to replace
`auto` with `allow-hotplug` in the `/etc/network/interfaces.d/can0`
file. For example:
```
allow-hotplug can0
iface can0 can static
bitrate 500000
up ifconfig $IFACE txqueuelen 128
```

View File

@ -38,23 +38,23 @@ with a RESP_NEED_NODEID response message.
The CMD_QUERY_UNASSIGNED message format is: The CMD_QUERY_UNASSIGNED message format is:
`<1-byte message_id = 0x00>` `<1-byte message_id = 0x00>`
### CMD_SET_NODEID message ### CMD_SET_KLIPPER_NODEID message
This command assigns a `canbus_nodeid` to the micro-controller with a This command assigns a `canbus_nodeid` to the micro-controller with a
given `canbus_uuid`. given `canbus_uuid`.
The CMD_SET_NODEID message format is: The CMD_SET_KLIPPER_NODEID message format is:
`<1-byte message_id = 0x01><6-byte canbus_uuid><1-byte canbus_nodeid>` `<1-byte message_id = 0x01><6-byte canbus_uuid><1-byte canbus_nodeid>`
### RESP_NEED_NODEID message ### RESP_NEED_NODEID message
The RESP_NEED_NODEID message format is: The RESP_NEED_NODEID message format is:
`<1-byte message_id = 0x20><6-byte canbus_uuid>` `<1-byte message_id = 0x20><6-byte canbus_uuid><1-byte set_klipper_nodeid = 0x01>`
## Data Packets ## Data Packets
A micro-controller that has been assigned a nodeid via the A micro-controller that has been assigned a nodeid via the
CMD_SET_NODEID command can send and receive data packets. CMD_SET_KLIPPER_NODEID command can send and receive data packets.
The packet data in messages using the node's receive CAN bus id The packet data in messages using the node's receive CAN bus id
(`canbus_nodeid * 2 + 256`) are simply appended to a buffer, and when (`canbus_nodeid * 2 + 256`) are simply appended to a buffer, and when

View File

@ -130,17 +130,13 @@ gcode:
### The "rawparams" variable ### The "rawparams" variable
The full unparsed parameters for the running macro can be access via the `rawparams` pseudo-variable. The full unparsed parameters for the running macro can be access via the
`rawparams` pseudo-variable.
This is quite useful if you want to change the behavior of certain commands like the `M117`. For example: Note that this will include any comments that were part of the original command.
``` See the [sample-macros.cfg](../config/sample-macros.cfg) file for an example
[gcode_macro M117] showing how to override the `M117` command using `rawparams`.
rename_existing: M117.1
gcode:
M117.1 { rawparams }
M118 { rawparams }
```
### The "printer" Variable ### The "printer" Variable

View File

@ -8,6 +8,16 @@ All dates in this document are approximate.
## Changes ## Changes
20220616: It was previously possible to flash an rp2040 in bootloader
mode by running `make flash FLASH_DEVICE=first`. The equivalent
command is now `make flash FLASH_DEVICE=2e8a:0003`.
20220612: The rp2040 micro-controller now has a workaround for the
"rp2040-e5" USB errata. This should make initial USB connections more
reliable. However, it may result in a change in behavior for the
gpio15 pin. It is unlikely the gpio15 behavior change will be
noticeable.
20220407: The temperature_fan `pid_integral_max` config option has 20220407: The temperature_fan `pid_integral_max` config option has
been removed (it was deprecated on 20210612). been removed (it was deprecated on 20210612).

View File

@ -1154,9 +1154,9 @@ home_xy_position:
# than z_hop, then this will lift the head to a height of z_hop. If # than z_hop, then this will lift the head to a height of z_hop. If
# the Z axis is not already homed the head is lifted by z_hop. # the Z axis is not already homed the head is lifted by z_hop.
# The default is to not implement Z hop. # The default is to not implement Z hop.
#z_hop_speed: 20.0 #z_hop_speed: 15.0
# Speed (in mm/s) at which the Z axis is lifted prior to homing. The # Speed (in mm/s) at which the Z axis is lifted prior to homing. The
# default is 20mm/s. # default is 15 mm/s.
#move_to_previous: False #move_to_previous: False
# When set to True, the X and Y axes are reset to their previous # When set to True, the X and Y axes are reset to their previous
# positions after Z axis homing. The default is False. # positions after Z axis homing. The default is False.
@ -1334,6 +1334,9 @@ path:
# are not supported). One may point this to OctoPrint's upload # are not supported). One may point this to OctoPrint's upload
# directory (generally ~/.octoprint/uploads/ ). This parameter must # directory (generally ~/.octoprint/uploads/ ). This parameter must
# be provided. # be provided.
#on_error_gcode:
# A list of G-Code commands to execute when an error is reported.
``` ```
### [sdcard_loop] ### [sdcard_loop]
@ -1432,6 +1435,20 @@ Enable the "M118" and "RESPOND" extended
# override the "default_type". # override the "default_type".
``` ```
### [exclude_object]
Enables support to exclude or cancel individual objects during the printing
process.
See the [exclude objects guide](Exclude_Object.md) and
[command reference](G-Codes.md#excludeobject)
for additional information. See the
[sample-macros.cfg](../config/sample-macros.cfg) file for a
Marlin/RepRapFirmware compatible M486 G-Code macro.
```
[exclude_object]
```
## Resonance compensation ## Resonance compensation
### [input_shaper] ### [input_shaper]
@ -1508,6 +1525,24 @@ cs_pin:
# measurements. # measurements.
``` ```
### [mpu9250]
Support for mpu9250 and mpu6050 accelerometers (one may define any
number of sections with an "mpu9250" prefix).
```
[mpu9250 my_accelerometer]
#i2c_address:
# Default is 104 (0x68).
#i2c_mcu:
#i2c_bus:
#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.
```
### [resonance_tester] ### [resonance_tester]
Support for resonance testing and automatic input shaper calibration. Support for resonance testing and automatic input shaper calibration.
@ -2109,7 +2144,7 @@ temperature sensors that are reported via the M105 command.
Klipper includes definitions for many types of temperature sensors. Klipper includes definitions for many types of temperature sensors.
These sensors may be used in any config section that requires a These sensors may be used in any config section that requires a
temperature sensor (such as an `[extruder]` or `[heated_bed]` temperature sensor (such as an `[extruder]` or `[heater_bed]`
section). section).
### Common thermistors ### Common thermistors
@ -4463,6 +4498,22 @@ SPI bus.
The following parameters are generally available for devices using an The following parameters are generally available for devices using an
I2C bus. I2C bus.
Note that Klipper's current micro-controller support for i2c is
generally not tolerant to line noise. Unexpected errors on the i2c
wires may result in Klipper raising a run-time error. Klipper's
support for error recovery varies between each micro-controller type.
It is generally recommended to only use i2c devices that are on the
same printed circuit board as the micro-controller.
Most Klipper micro-controller implementations only support an
`i2c_speed` of 100000. The Klipper "linux" micro-controller supports a
400000 speed, but it must be
[set in the operating system](RPi_microcontroller.md#optional-enabling-i2c)
and the `i2c_speed` parameter is otherwise ignored. The Klipper
"rp2040" micro-controller supports a rate of 400000 via the
`i2c_speed` parameter. All other Klipper micro-controllers use a
100000 rate and ignore the `i2c_speed` parameter.
``` ```
#i2c_address: #i2c_address:
# The i2c address of the device. This must specified as a decimal # The i2c address of the device. This must specified as a decimal
@ -4476,8 +4527,9 @@ I2C bus.
# the type of micro-controller. # the type of micro-controller.
#i2c_speed: #i2c_speed:
# The I2C speed (in Hz) to use when communicating with the device. # The I2C speed (in Hz) to use when communicating with the device.
# On some micro-controllers changing this value has no effect. The # The Klipper implementation on most micro-controllers is hard-coded
# default is 100000. # to 100000 and changing this value has no effect. The default is
# 100000.
``` ```
### Common UART settings ### Common UART settings

99
docs/Exclude_Object.md Normal file
View File

@ -0,0 +1,99 @@
# Exclude Objects
The `[exclude_object]` module allows Klipper to exclude objects while a print is
in progress. To enable this feature include an [exclude_object config
section](Config_Reference.md#exclude_object) (also see the [command
reference](G-Codes.md#exclude-object) and
[sample-macros.cfg](../config/sample-macros.cfg) file for a
Marlin/RepRapFirmware compatible M486 G-Code macro.)
Unlike other 3D printer firmware options, a printer running Klipper utilizes a
suite of components and users have many options to choose from. Therefore, in
order to provide a a consistent user experience, the `[exclude_object]` module
will establish a contract or API of sorts. The contract covers the contents of
the gcode file, how the internal state of the module is controlled, and how that
state is provided to clients.
## Workflow Overview
A typical workflow for printing a file might look like this:
1. Slicing is completed and the file is uploaded for printing. During the
upload, the file is processed and `[exclude_object]` markers are added to
the file. Alternately, slicers may be configured to prepare object exclusion
markers natively, or in it's own pre-processing step.
2. When printing starts, Klipper will reset the `[exclude_object]`
[status](Status_Reference.md#exclude_object).
3. When Klipper processes the `EXCLUDE_OBJECT_DEFINE` block, it will update the
status with the known objects and pass it on to clients.
4. The client may use that information to present a UI to the user so that
progress can be tracked. Klipper will update the status to include the
currently printing object which the client can use for display purposes.
5. If the user requests that an object be cancelled, the client will issue an
`EXCLUDE_OBJECT NAME=<name>` command to Klipper.
6. When Klipper process the command, it will add the object to the list of
excluded objects and update the status for the client.
7. The client will receive the updated status from Klipper and can use that
information to reflect the object's status in the UI.
8. When printing finishes, the `[exclude_object]` status will continue to be
available until another action resets it.
## The GCode File
The specialized gcode processing needed to support excluding objects does not
fit into Klipper's core design goals. Therefore, this module requires that the
file is processed before being sent to Klipper for printing. Using a
post-process script in the slicer or having middleware process the file on
upload are two possibilities for preparing the file for Klipper. A reference
post-processing script is available both as an executable and a python library,
see
[cancelobject-preprocessor](https://github.com/kageurufu/cancelobject-preprocessor).
### Object Definitions
The `EXCLUDE_OBJECT_DEFINE` command is used to provide a summary of each object
in the gcode file to be printed. Provides a summary of an object in the file.
Objects don't need to be defined in order to be referenced by other commands.
The primary purpose of this command is to provide information to the UI without
needing to parse the entire gcode file.
Object definitions are named, to allow users to easily select an object to be
excluded, and additional metadata may be provided to allow for graphical
cancellation displays. Currently defined metadata includes a `CENTER` X,Y
coordinate, and a `POLYGON` list of X,Y points representing a minimal outline of
the object. This could be a simple bounding box, or a complicated hull for
showing more detailed visualizations of the printed objects. Especially when
gcode files include multiple parts with overlapping bounding regions, center
points become hard to visually distinguish. `POLYGONS` must be a json-compatible
array of point `[X,Y]` tuples without whitespace. Additional parameters will be
saved as strings in the object definition and provided in status updates.
`EXCLUDE_OBJECT_DEFINE NAME=calibration_pyramid CENTER=50,50
POLYGON=[[40,40],[50,60],[60,40]]`
All available G-Code commands are documented in the [G-Code
Reference](./G-Codes.md#excludeobject)
## Status Information
The state of this module is provided to clients by the [exclude_object
status](Status_Reference.md#exclude_object).
The status is reset when:
- The Klipper firmware is restarted.
- There is a reset of the `[virtual_sdcard]`. Notably, this is reset by Klipper
at the start of a print.
- When an `EXCLUDE_OBJECT_DEFINE RESET=1` command is issued.
The list of defined objects is represented in the `exclude_object.objects`
status field. In a well defined gcode file, this will be done with
`EXCLUDE_OBJECT_DEFINE` commands at the beginning of the file. This will
provide clients with object names and coordinates so the UI can provide a
graphical representation of the objects if desired.
As the print progresses, the `exclude_object.current_object` status field will
be updated as Klipper processes `EXCLUDE_OBJECT_START` and `EXCLUDE_OBJECT_END`
commands. The `current_object` field will be set even if the object has been
excluded. Undefined objects marked with a `EXCLUDE_OBJECT_START` will be added
to the known objects to assist in UI hinting, without any additional metadata.
As `EXCLUDE_OBJECT` commands are issued, the list of excluded objects is
provided in the `exclude_object.excluded_objects` array. Since Klipper looks
ahead to process upcoming gcode, there may be a delay between when the command
is issued and when the status is updated.

View File

@ -1,34 +1,9 @@
# Frequently Asked Questions # Frequently Asked Questions
1. [How can I donate to the project?](#how-can-i-donate-to-the-project)
2. [How do I calculate the rotation_distance config parameter?](#how-do-i-calculate-the-rotation_distance-config-parameter)
3. [Where's my serial port?](#wheres-my-serial-port)
4. [When the micro-controller restarts the device changes to /dev/ttyUSB1](#when-the-micro-controller-restarts-the-device-changes-to-devttyusb1)
5. [The "make flash" command doesn't work](#the-make-flash-command-doesnt-work)
6. [How do I change the serial baud rate?](#how-do-i-change-the-serial-baud-rate)
7. [Can I run Klipper on something other than a Raspberry Pi 3?](#can-i-run-klipper-on-something-other-than-a-raspberry-pi-3)
8. [Can I run multiple instances of Klipper on the same host machine?](#can-i-run-multiple-instances-of-klipper-on-the-same-host-machine)
9. [Do I have to use OctoPrint?](#do-i-have-to-use-octoprint)
10. [Why can't I move the stepper before homing the printer?](#why-cant-i-move-the-stepper-before-homing-the-printer)
11. [Why is the Z position_endstop set to 0.5 in the default configs?](#why-is-the-z-position_endstop-set-to-05-in-the-default-configs)
12. [I converted my config from Marlin and the X/Y axes work fine, but I just get a screeching noise when homing the Z axis](#i-converted-my-config-from-marlin-and-the-xy-axes-work-fine-but-i-just-get-a-screeching-noise-when-homing-the-z-axis)
13. [My TMC motor driver turns off in the middle of a print](#my-tmc-motor-driver-turns-off-in-the-middle-of-a-print)
14. [I keep getting random "Lost communication with MCU" errors](#i-keep-getting-random-lost-communication-with-mcu-errors)
15. [My Raspberry Pi keeps rebooting during prints](#my-raspberry-pi-keeps-rebooting-during-prints)
16. [When I set `restart_method=command` my AVR device just hangs on a restart](#when-i-set-restart_methodcommand-my-avr-device-just-hangs-on-a-restart)
17. [Will the heaters be left on if the Raspberry Pi crashes?](#will-the-heaters-be-left-on-if-the-raspberry-pi-crashes)
18. [How do I convert a Marlin pin number to a Klipper pin name?](#how-do-i-convert-a-marlin-pin-number-to-a-klipper-pin-name)
19. [Do I have to wire my device to a specific type of micro-controller pin?](#do-i-have-to-wire-my-device-to-a-specific-type-of-micro-controller-pin)
20. [How do I cancel an M109/M190 "wait for temperature" request?](#how-do-i-cancel-an-m109m190-wait-for-temperature-request)
21. [Can I find out whether the printer has lost steps?](#can-i-find-out-whether-the-printer-has-lost-steps)
22. [Why does Klipper report errors? I lost my print!](#why-does-klipper-report-errors-i-lost-my-print)
23. [How do I upgrade to the latest software?](#how-do-i-upgrade-to-the-latest-software)
24. [How do I uninstall klipper?](#how-do-i-uninstall-klipper)
## How can I donate to the project? ## How can I donate to the project?
Thanks. Kevin has a Patreon page at: Thank you for your support. See the [Sponsors page](Sponsors.md) for
[https://www.patreon.com/koconnor](https://www.patreon.com/koconnor) information.
## How do I calculate the rotation_distance config parameter? ## How do I calculate the rotation_distance config parameter?

View File

@ -295,6 +295,11 @@ provides the following standard G-Code commands:
- Display Message: `M117 <message>` - Display Message: `M117 <message>`
- Set build percentage: `M73 P<percent>` - Set build percentage: `M73 P<percent>`
Also provided is the following extended G-Code command:
- `SET_DISPLAY_TEXT MSG=<message>`: Performs the equivalent of M117,
setting the supplied `MSG` as the current display message. If
`MSG` is omitted the display will be cleared.
### [dual_carriage] ### [dual_carriage]
The following command is available when the The following command is available when the
@ -320,6 +325,57 @@ parameter is provided it arranges for the given endstop phase setting
to be written to the config file (in conjunction with the SAVE_CONFIG to be written to the config file (in conjunction with the SAVE_CONFIG
command). command).
### [exclude_object]
The following commands are available when an
[exclude_object config section](Config_Reference.md#exclude_object) is
enabled (also see the [exclude object guide](Exclude_Object.md)):
#### `EXCLUDE_OBJECT`
`EXCLUDE_OBJECT [NAME=object_name] [CURRENT=1] [RESET=1]`:
With no parameters, this will return a list of all currently excluded objects.
When the `NAME` parameter is given, the named object will be excluded from
printing.
When the `CURRENT` parameter is given, the current object will be excluded from
printing.
When the `RESET` parameter is given, the list of excluded objects will be
cleared. Additionally including `NAME` will only reset the named object. This
**can** cause print failures, if layers were already skipped.
#### `EXCLUDE_OBJECT_DEFINE`
`EXCLUDE_OBJECT_DEFINE [NAME=object_name [CENTER=X,Y] [POLYGON=[[x,y],...]]
[RESET=1] [JSON=1]`:
Provides a summary of an object in the file.
With no parameters provided, this will list the defined objects known to
Klipper. Returns a list of strings, unless the `JSON` parameter is given,
when it will return object details in json format.
When the `NAME` parameter is included, this defines an object to be excluded.
- `NAME`: This parameter is required. It is the identifier used by other
commands in this module.
- `CENTER`: An X,Y coordinate for the object.
- `POLYGON`: An array of X,Y coordinates that provide an outline for the
object.
When the `RESET` parameter is provided, all defined objects will be cleared, and
the `[exclude_object]` module will be reset.
#### `EXCLUDE_OBJECT_START`
`EXCLUDE_OBJECT_START NAME=object_name`:
This command takes a `NAME` parameter and denotes the start of the gcode for an
object on the current layer.
#### `EXCLUDE_OBJECT_END`
`EXCLUDE_OBJECT_END [NAME=object_name]`:
Denotes the end of the object's gcode for the layer. It is paired with
`EXCLUDE_OBJECT_START`. A `NAME` parameter is optional, and will only warn when
the provided name does not match the current object.
### [extruder] ### [extruder]
The following commands are available if an The following commands are available if an
@ -896,23 +952,28 @@ 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>]
[HZ_PER_SEC=<hz_per_sec>] [INPUT_SHAPING=[<0:1>]]`: Runs the resonance [HZ_PER_SEC=<hz_per_sec>] [CHIPS=<adxl345_chip_name>]
[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. If `INPUT_SHAPING=0` or not set and `AXIS=-dx,-dy` is equivalent. `adxl345_chip_name` can be one or
(default), disables input shaping for the resonance testing, because more configured adxl345 chip,delimited with comma, for example
`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),
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
enabled. `OUTPUT` parameter is a comma-separated list of which outputs enabled. `OUTPUT` parameter is a comma-separated list of which outputs
will be written. If `raw_data` is requested, then the raw will be written. If `raw_data` is requested, then the raw
accelerometer data is written into a file or a series of files accelerometer data is written into a file or a series of files
`/tmp/raw_data_<axis>_[<point>_]<name>.csv` with (`<point>_` part of `/tmp/raw_data_<axis>_[<chip_name>_][<point>_]<name>.csv` with
the name generated only if more than 1 probe point is configured). If (`<point>_` part of the name generated only if more than 1 probe point
`resonances` is specified, the frequency response is calculated is configured or POINT is specified). If `resonances` is specified, the
(across all probe points) and written into frequency response is calculated (across all probe points) and written into
`/tmp/resonances_<axis>_<name>.csv` file. If unset, OUTPUT defaults to `/tmp/resonances_<axis>_<name>.csv` file. If unset, OUTPUT defaults to
`resonances`, and NAME defaults to the current time in `resonances`, and NAME defaults to the current time in
"YYYYMMDD_HHMMSS" format. "YYYYMMDD_HHMMSS" format.
@ -949,6 +1010,8 @@ The following additional commands are also available.
configured default prefix (or `echo: ` if no prefix is configured). configured default prefix (or `echo: ` if no prefix is configured).
- `RESPOND TYPE=echo MSG="<message>"`: echo the message prepended with - `RESPOND TYPE=echo MSG="<message>"`: echo the message prepended with
`echo: `. `echo: `.
- `RESPOND TYPE=echo_no_space MSG="<message>"`: echo the message prepended with
`echo:` without a space between prefix and message, helpful for compatibility with some octoprint plugins that expect very specific formatting.
- `RESPOND TYPE=command MSG="<message>"`: echo the message prepended - `RESPOND TYPE=command MSG="<message>"`: echo the message prepended
with `// `. OctoPrint can be configured to respond to these messages with `// `. OctoPrint can be configured to respond to these messages
(e.g. `RESPOND TYPE=command MSG=action:pause`). (e.g. `RESPOND TYPE=command MSG=action:pause`).
@ -979,7 +1042,7 @@ is enabled (also see the
[manual level guide](Manual_Level.md#adjusting-bed-leveling-screws-using-the-bed-probe)). [manual level guide](Manual_Level.md#adjusting-bed-leveling-screws-using-the-bed-probe)).
#### SCREWS_TILT_CALCULATE #### SCREWS_TILT_CALCULATE
`SCREWS_TILT_CALCULATE [DIRECTION=CW|CCW] `SCREWS_TILT_CALCULATE [DIRECTION=CW|CCW] [MAX_DEVIATION=<value>]
[<probe_parameter>=<value>]`: This command will invoke the bed screws [<probe_parameter>=<value>]`: This command will invoke the bed screws
adjustment tool. It will command the nozzle to different locations (as adjustment tool. It will command the nozzle to different locations (as
defined in the config file) probing the z height and calculate the defined in the config file) probing the z height and calculate the
@ -987,7 +1050,9 @@ number of knob turns to adjust the bed level. If DIRECTION is
specified, the knob turns will all be in the same direction, clockwise specified, the knob turns will all be in the same direction, clockwise
(CW) or counterclockwise (CCW). See the PROBE command for details on (CW) or counterclockwise (CCW). See the PROBE command for details on
the optional probe parameters. IMPORTANT: You MUST always do a G28 the optional probe parameters. IMPORTANT: You MUST always do a G28
before using this command. before using this command. If MAX_DEVIATION is specified, the command
will raise a gcode error if any difference in the screw height
relative to the base screw height is greater than the value provided.
### [sdcard_loop] ### [sdcard_loop]

View File

@ -31,6 +31,18 @@ and **will not work**. The recommended connection scheme:
| SDA | 19 | GPIO10 (SPI0_MOSI) | | SDA | 19 | GPIO10 (SPI0_MOSI) |
| SCL | 23 | GPIO11 (SPI0_SCLK) | | SCL | 23 | GPIO11 (SPI0_SCLK) |
An alternative to the ADXL345 is the MPU-9250 (or MPU-6050). This
accelerometer has been tested to work over I2C on the RPi at 400kbaud.
Recommended connection scheme for I2C:
| MPU-9250 pin | RPi pin | RPi pin name |
|:--:|:--:|:--:|
| 3V3 (or VCC) | 01 | 3.3v DC power |
| GND | 09 | Ground |
| SDA | 03 | GPIO02 (SDA1) |
| SCL | 05 | GPIO03 (SCL1) |
Fritzing wiring diagrams for some of the ADXL345 boards: Fritzing wiring diagrams for some of the ADXL345 boards:
![ADXL345-Rpi](img/adxl345-fritzing.png) ![ADXL345-Rpi](img/adxl345-fritzing.png)
@ -64,21 +76,21 @@ the system that may damage the electronics.
### Software installation ### Software installation
Note that resonance measurements and shaper auto-calibration require additional Note that resonance measurements and shaper auto-calibration require additional
software dependencies not installed by default. First, you will have to run on software dependencies not installed by default. First, run on your Raspberry Pi
your Raspberry Pi the following command: the following commands:
```
sudo apt update
sudo apt install python3-numpy python3-matplotlib libatlas-base-dev
```
Next, in order to install NumPy in the Klipper environment, run the command:
``` ```
~/klippy-env/bin/pip install -v numpy ~/klippy-env/bin/pip install -v numpy
``` ```
to install `numpy` package. Note that, depending on the performance of the Note that, depending on the performance of the CPU, it may take *a lot*
CPU, it may take *a lot* of time, up to 10-20 minutes. Be patient and wait of time, up to 10-20 minutes. Be patient and wait for the completion of
for the completion of the installation. On some occasions, if the board has the installation. On some occasions, if the board has too little RAM
too little RAM, the installation may fail and you will need to enable swap. the installation may fail and you will need to enable swap.
Next, run the following commands to install the additional dependencies:
```
sudo apt update
sudo apt install python3-numpy python3-matplotlib
```
Afterwards, check and follow the instructions in the Afterwards, check and follow the instructions in the
[RPi Microcontroller document](RPi_microcontroller.md) to setup the [RPi Microcontroller document](RPi_microcontroller.md) to setup the
@ -87,7 +99,7 @@ Afterwards, check and follow the instructions in the
Make sure the Linux SPI driver is enabled by running `sudo Make sure the Linux SPI driver is enabled by running `sudo
raspi-config` and enabling SPI under the "Interfacing options" menu. raspi-config` and enabling SPI under the "Interfacing options" menu.
Add the following to the printer.cfg file: For the ADXL345, add the following to the printer.cfg file:
``` ```
[mcu rpi] [mcu rpi]
serial: /tmp/klipper_host_mcu serial: /tmp/klipper_host_mcu
@ -103,6 +115,23 @@ probe_points:
It is advised to start with 1 probe point, in the middle of the print bed, It is advised to start with 1 probe point, in the middle of the print bed,
slightly above it. slightly above it.
For the MPU-9250, make sure the Linux I2C driver is enabled and the baud rate is
set to 400000 (see [Enabling I2C](RPi_microcontroller.md#optional-enabling-i2c)
section for more details). Then, add the following to the printer.cfg:
```
[mcu rpi]
serial: /tmp/klipper_host_mcu
[mpu9250]
i2c_mcu: rpi
i2c_bus: i2c.1
[resonance_tester]
accel_chip: mpu9250
probe_points:
100, 100, 20 # an example
```
Restart Klipper via the `RESTART` command. Restart Klipper via the `RESTART` command.
## Measuring the resonances ## Measuring the resonances

View File

@ -54,6 +54,8 @@ communication with the Klipper developers.
perfectly square. perfectly square.
- [PWM tools](Using_PWM_Tools.md): Guide on how to use PWM controlled - [PWM tools](Using_PWM_Tools.md): Guide on how to use PWM controlled
tools such as lasers or spindles. tools such as lasers or spindles.
- [Exclude Object](Exclude_Object.md): The guide to the Exclude Objecs
implementation.
## Developer Documentation ## Developer Documentation

View File

@ -69,6 +69,15 @@ Make sure the Linux SPI driver is enabled by running
`sudo raspi-config` and enabling SPI under the "Interfacing options" `sudo raspi-config` and enabling SPI under the "Interfacing options"
menu. menu.
## Optional: Enabling I2C
Make sure the Linux I2C driver is enabled by running `sudo raspi-config`
and enabling I2C under the "Interfacing options" menu.
If planning to use I2C for the MPU accelerometer, it is also required
to set the baud rate to 400000 by: adding/uncommenting
`dtparam=i2c_arm=on,i2c_arm_baudrate=400000` in `/boot/config.txt`
(or `/boot/firmware/config.txt` in some distros).
## Optional: Identify the correct gpiochip ## Optional: Identify the correct gpiochip
On Raspberry Pi and on many clones the pins exposed on the GPIO belong On Raspberry Pi and on many clones the pins exposed on the GPIO belong

40
docs/Sponsors.md Normal file
View File

@ -0,0 +1,40 @@
# Sponsors
Klipper is Free Software. We depend on the generous support from
sponsors. Please consider sponsoring Klipper or supporting our
sponsors.
## BIGTREETECH
[<img src="./img/sponsors/BTT_BTT.png" width="200" />](https://bigtree-tech.com/collections/all-products)
BIGTREETECH is the official mainboard sponsor of Klipper. BIGTREETECH
is committed to developing innovative and competitive products to
serve the 3D printing community better. Follow them on
[Facebook](https://www.facebook.com/BIGTREETECH) or
[Twitter](https://twitter.com/BigTreeTech).
## Klipper Developers
### Kevin O'Connor
Kevin is the original author and current maintainer of Klipper. Donate
at: [https://ko-fi.com/koconnor](https://ko-fi.com/koconnor) or
[https://www.patreon.com/koconnor](https://www.patreon.com/koconnor)
### Eric Callahan
Eric is the author of bed_mesh, spi_flash, and several other Klipper
modules. Eric has a donations page at:
[https://ko-fi.com/arksine](https://ko-fi.com/arksine)
## Related Klipper Projects
Klipper is frequently used with other Free Software. Consider using or
supporting these projects.
* [Moonraker](https://github.com/Arksine/moonraker)
* [Mainsail](https://github.com/mainsail-crew/mainsail)
* [Fluidd](https://github.com/fluidd-core/fluidd)
* [OctoPrint](https://octoprint.org/)
* [KlipperScreen](https://github.com/jordanruthe/KlipperScreen)

View File

@ -28,6 +28,17 @@ The following information is available in the
- `profiles`: The set of currently defined profiles as setup - `profiles`: The set of currently defined profiles as setup
using BED_MESH_PROFILE. using BED_MESH_PROFILE.
## bed_screws
The following information is available in the
`Config_Reference.md#bed_screws` object:
- `is_active`: Returns True if the bed screws adjustment tool is currently
active.
- `state`: The bed screws adjustment tool state. It is one of
the following strings: "adjust", "fine".
- `current_screw`: The index for the current screw being adjusted.
- `accepted_screws`: The number of accepted screws.
## configfile ## configfile
The following information is available in the `configfile` object The following information is available in the `configfile` object
@ -41,6 +52,8 @@ The following information is available in the `configfile` object
here.) All values are returned as strings. here.) All values are returned as strings.
- `save_config_pending`: Returns true if there are updates that a - `save_config_pending`: Returns true if there are updates that a
`SAVE_CONFIG` command may persist to disk. `SAVE_CONFIG` command may persist to disk.
- `save_config_pending_items`: Contains the sections and options that
were changed and would be persisted by a `SAVE_CONFIG`.
- `warnings`: A list of warnings about config options. Each entry in - `warnings`: A list of warnings about config options. Each entry in
the list will be a dictionary containing a `type` and `message` the list will be a dictionary containing a `type` and `message`
field (both strings). Additional fields may be available depending field (both strings). Additional fields may be available depending
@ -69,6 +82,44 @@ The following information is available in the
forward direction minus the total number of steps taken in the forward direction minus the total number of steps taken in the
reverse direction since the micro-controller was last restarted. reverse direction since the micro-controller was last restarted.
## exclude_object
The following information is available in the
[exclude_object](Exclude_Object.md) object:
- `objects`: An array of the known objects as provided by the
`EXCLUDE_OBJECT_DEFINE` command. This is the same information provided by
the `EXCLUDE_OBJECT VERBOSE=1` command. The `center` and `polygon` fields will
only be present if provided in the original `EXCLUDE_OBJECT_DEFINE`
Here is a JSON sample:
```
[
{
"polygon": [
[ 156.25, 146.2511675 ],
[ 156.25, 153.7488325 ],
[ 163.75, 153.7488325 ],
[ 163.75, 146.2511675 ]
],
"name": "CYLINDER_2_STL_ID_2_COPY_0",
"center": [ 160, 150 ]
},
{
"polygon": [
[ 146.25, 146.2511675 ],
[ 146.25, 153.7488325 ],
[ 153.75, 153.7488325 ],
[ 153.75, 146.2511675 ]
],
"name": "CYLINDER_2_STL_ID_1_COPY_0",
"center": [ 150, 150 ]
}
]
```
- `excluded_objects`: An array of strings listing the names of excluded objects.
- `current_object`: The name of the object currently being printed.
## fan ## fan
The following information is available in The following information is available in
@ -204,6 +255,17 @@ The following information is available for each `[led led_name]`,
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]`.
## manual_probe
The following information is available in the
`manual_probe` object:
- `is_active`: Returns True if a manual probing helper script is currently
active.
- `z_position`: The current height of the nozzle (as the printer currently
understands it).
- `z_position_lower`: Last probe attempt just lower than the current height.
- `z_position_upper`: Last probe attempt just greater than the current height.
## mcu ## mcu
The following information is available in The following information is available in
@ -376,6 +438,8 @@ The following information is available in the `toolhead` object
- `axis_minimum`, `axis_maximum`: The axis travel limits (mm) after - `axis_minimum`, `axis_maximum`: The axis travel limits (mm) after
homing. It is possible to access the x, y, z components of this homing. It is possible to access the x, y, z components of this
limit value (eg, `axis_minimum.x`, `axis_maximum.z`). limit value (eg, `axis_minimum.x`, `axis_maximum.z`).
- For Delta printers the `cone_start_z` is the max z height at
maximum radius (`printer.toolhead.cone_start_z`).
- `max_velocity`, `max_accel`, `max_accel_to_decel`, - `max_velocity`, `max_accel`, `max_accel_to_decel`,
`square_corner_velocity`: The current printing limits that are in `square_corner_velocity`: The current printing limits that are in
effect. This may differ from the config file settings if a effect. This may differ from the config file settings if a

View File

@ -27,6 +27,16 @@ while IFS="," read dirname langsite langdesc langsearch; do
new_docs_dir="${WORK_DIR}lang/${langsite}/docs/" new_docs_dir="${WORK_DIR}lang/${langsite}/docs/"
locale_dir="${TRANS_DIR}/docs/locales/${dirname}" locale_dir="${TRANS_DIR}/docs/locales/${dirname}"
# read toc
title=$(sed -n '1p' ${locale_dir}/Navigation.md)
installation_and_configuration=$(sed -n '3p' ${locale_dir}/Navigation.md)
configuration_reference=$(sed -n '5p' ${locale_dir}/Navigation.md)
bed_level=$(sed -n '7p' ${locale_dir}/Navigation.md)
resonance_compensation=$(sed -n '9p' ${locale_dir}/Navigation.md)
command_template=$(sed -n '11p' ${locale_dir}/Navigation.md)
developer_documentation=$(sed -n '13p' ${locale_dir}/Navigation.md)
device_specific_documents=$(sed -n '15p' ${locale_dir}/Navigation.md)
# Copy markdown files to new_docs_dir # Copy markdown files to new_docs_dir
echo "Copying $dirname to $langsite" echo "Copying $dirname to $langsite"
mkdir -p "${new_docs_dir}" mkdir -p "${new_docs_dir}"
@ -56,6 +66,16 @@ while IFS="," read dirname langsite langdesc langsearch; do
echo "replace site language" echo "replace site language"
sed -i "s%^ language: en$% language: ${langsite}%" "${new_mkdocs_file}" sed -i "s%^ language: en$% language: ${langsite}%" "${new_mkdocs_file}"
echo "replace toc"
sed -i "s%Klipper documentation$%${title}%" "${new_mkdocs_file}"
sed -i "s%Installation and Configuration:$%${installation_and_configuration}:%" "${new_mkdocs_file}"
sed -i "s%Configuration Reference:$%${configuration_reference}:%" "${new_mkdocs_file}"
sed -i "s%Bed Level:$%${bed_level}:%" "${new_mkdocs_file}"
sed -i "s%Resonance Compensation:$%${resonance_compensation}:%" "${new_mkdocs_file}"
sed -i "s%Command templates:$%${command_template}:%" "${new_mkdocs_file}"
sed -i "s%Developer Documentation:$%${developer_documentation}:%" "${new_mkdocs_file}"
sed -i "s%Device Specific Documents:$%${device_specific_documents}:%" "${new_mkdocs_file}"
# Build site # Build site
echo "building site for ${langsite}" echo "building site for ${langsite}"
mkdir -p "${PWD}/site/${langsite}/" mkdir -p "${PWD}/site/${langsite}/"

View File

@ -7,3 +7,4 @@ mkdocs-exclude==1.0.2
mdx-truly-sane-lists==1.2 mdx-truly-sane-lists==1.2
mdx-breakless-lists==1.0.1 mdx-breakless-lists==1.0.1
py-gfm==1.0.2 py-gfm==1.0.2
markdown==3.3.7

View File

@ -113,6 +113,7 @@ nav:
- Multi_MCU_Homing.md - Multi_MCU_Homing.md
- Slicers.md - Slicers.md
- Skew_Correction.md - Skew_Correction.md
- Exclude_Object.md
- Using_PWM_Tools.md - Using_PWM_Tools.md
- Developer Documentation: - Developer Documentation:
- Code_Overview.md - Code_Overview.md
@ -134,3 +135,4 @@ nav:
- CANBUS.md - CANBUS.md
- TSL1401CL_Filament_Width_Sensor.md - TSL1401CL_Filament_Width_Sensor.md
- Hall_Filament_Width_Sensor.md - Hall_Filament_Width_Sensor.md
- Sponsors.md

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -15,3 +15,4 @@ To begin using Klipper start by [installing](Installation.md) it.
Klipper is Free Software. Read the [documentation](Overview.md) or Klipper is Free Software. Read the [documentation](Overview.md) or
view [the Klipper code on github](https://github.com/Klipper3d/klipper). view [the Klipper code on github](https://github.com/Klipper3d/klipper).
We depend on the generous support from our [sponsors](Sponsors.md).

View File

@ -168,8 +168,8 @@ defs_serialqueue = """
, uint64_t notify_id); , uint64_t notify_id);
void serialqueue_pull(struct serialqueue *sq void serialqueue_pull(struct serialqueue *sq
, struct pull_queue_message *pqm); , struct pull_queue_message *pqm);
void serialqueue_set_baud_adjust(struct serialqueue *sq void serialqueue_set_wire_frequency(struct serialqueue *sq
, double baud_adjust); , double frequency);
void serialqueue_set_receive_window(struct serialqueue *sq void serialqueue_set_receive_window(struct serialqueue *sq
, int receive_window); , int receive_window);
void serialqueue_set_clock_est(struct serialqueue *sq, double est_freq void serialqueue_set_clock_est(struct serialqueue *sq, double est_freq

View File

@ -49,7 +49,7 @@ struct serialqueue {
int receive_waiting; int receive_waiting;
// Baud / clock tracking // Baud / clock tracking
int receive_window; int receive_window;
double baud_adjust, idle_time; double bittime_adjust, idle_time;
struct clock_estimate ce; struct clock_estimate ce;
double last_receive_sent_time; double last_receive_sent_time;
// Retransmit support // Retransmit support
@ -136,6 +136,23 @@ kick_bg_thread(struct serialqueue *sq)
report_errno("pipe write", ret); report_errno("pipe write", ret);
} }
// Minimum number of bits in a canbus message
#define CANBUS_PACKET_BITS ((1 + 11 + 3 + 4) + (16 + 2 + 7 + 3))
#define CANBUS_IFS_BITS 4
// Determine minimum time needed to transmit a given number of bytes
static double
calculate_bittime(struct serialqueue *sq, uint32_t bytes)
{
if (sq->serial_fd_type == SQT_CAN) {
uint32_t pkts = DIV_ROUND_UP(bytes, 8);
uint32_t bits = bytes * 8 + pkts * CANBUS_PACKET_BITS - CANBUS_IFS_BITS;
return sq->bittime_adjust * bits;
} else {
return sq->bittime_adjust * bytes;
}
}
// Update internal state when the receive sequence increases // Update internal state when the receive sequence increases
static void static void
update_receive_seq(struct serialqueue *sq, double eventtime, uint64_t rseq) update_receive_seq(struct serialqueue *sq, double eventtime, uint64_t rseq)
@ -192,7 +209,7 @@ update_receive_seq(struct serialqueue *sq, double eventtime, uint64_t rseq)
} else { } else {
struct queue_message *sent = list_first_entry( struct queue_message *sent = list_first_entry(
&sq->sent_queue, struct queue_message, node); &sq->sent_queue, struct queue_message, node);
double nr = eventtime + sq->rto + sent->len * sq->baud_adjust; double nr = eventtime + sq->rto + calculate_bittime(sq, sent->len);
pollreactor_update_timer(sq->pr, SQPT_RETRANSMIT, nr); pollreactor_update_timer(sq->pr, SQPT_RETRANSMIT, nr);
} }
} }
@ -251,7 +268,7 @@ handle_message(struct serialqueue *sq, double eventtime, int len)
qm->sent_time = (rseq > sq->retransmit_seq qm->sent_time = (rseq > sq->retransmit_seq
? sq->last_receive_sent_time : 0.); ? sq->last_receive_sent_time : 0.);
qm->receive_time = get_monotonic(); // must be time post read() qm->receive_time = get_monotonic(); // must be time post read()
qm->receive_time -= sq->baud_adjust * len; qm->receive_time -= calculate_bittime(sq, len);
list_add_tail(&qm->node, &sq->receive_queue); list_add_tail(&qm->node, &sq->receive_queue);
must_wake = 1; must_wake = 1;
} }
@ -407,8 +424,8 @@ retransmit_event(struct serialqueue *sq, double eventtime)
} }
sq->retransmit_seq = sq->send_seq; sq->retransmit_seq = sq->send_seq;
sq->rtt_sample_seq = 0; sq->rtt_sample_seq = 0;
sq->idle_time = eventtime + buflen * sq->baud_adjust; sq->idle_time = eventtime + calculate_bittime(sq, buflen);
double waketime = eventtime + first_buflen * sq->baud_adjust + sq->rto; double waketime = eventtime + sq->rto + calculate_bittime(sq, first_buflen);
pthread_mutex_unlock(&sq->lock); pthread_mutex_unlock(&sq->lock);
return waketime; return waketime;
@ -416,7 +433,8 @@ retransmit_event(struct serialqueue *sq, double eventtime)
// Construct a block of data to be sent to the serial port // Construct a block of data to be sent to the serial port
static int static int
build_and_send_command(struct serialqueue *sq, uint8_t *buf, double eventtime) build_and_send_command(struct serialqueue *sq, uint8_t *buf, int pending
, double eventtime)
{ {
int len = MESSAGE_HEADER_SIZE; int len = MESSAGE_HEADER_SIZE;
while (sq->ready_bytes) { while (sq->ready_bytes) {
@ -463,17 +481,15 @@ build_and_send_command(struct serialqueue *sq, uint8_t *buf, double eventtime)
buf[len - MESSAGE_TRAILER_SYNC] = MESSAGE_SYNC; buf[len - MESSAGE_TRAILER_SYNC] = MESSAGE_SYNC;
// Store message block // Store message block
if (eventtime > sq->idle_time) double idletime = eventtime > sq->idle_time ? eventtime : sq->idle_time;
sq->idle_time = eventtime; idletime += calculate_bittime(sq, pending + len);
sq->idle_time += len * sq->baud_adjust;
struct queue_message *out = message_alloc(); struct queue_message *out = message_alloc();
memcpy(out->msg, buf, len); memcpy(out->msg, buf, len);
out->len = len; out->len = len;
out->sent_time = eventtime; out->sent_time = eventtime;
out->receive_time = sq->idle_time; out->receive_time = idletime;
if (list_empty(&sq->sent_queue)) if (list_empty(&sq->sent_queue))
pollreactor_update_timer(sq->pr, SQPT_RETRANSMIT pollreactor_update_timer(sq->pr, SQPT_RETRANSMIT, idletime + sq->rto);
, sq->idle_time + sq->rto);
if (!sq->rtt_sample_seq) if (!sq->rtt_sample_seq)
sq->rtt_sample_seq = sq->send_seq; sq->rtt_sample_seq = sq->send_seq;
sq->send_seq++; sq->send_seq++;
@ -484,7 +500,7 @@ build_and_send_command(struct serialqueue *sq, uint8_t *buf, double eventtime)
// Determine the time the next serial data should be sent // Determine the time the next serial data should be sent
static double static double
check_send_command(struct serialqueue *sq, double eventtime) check_send_command(struct serialqueue *sq, int pending, double eventtime)
{ {
if (sq->send_seq - sq->receive_seq >= MAX_PENDING_BLOCKS if (sq->send_seq - sq->receive_seq >= MAX_PENDING_BLOCKS
&& sq->receive_seq != (uint64_t)-1) && sq->receive_seq != (uint64_t)-1)
@ -501,7 +517,7 @@ check_send_command(struct serialqueue *sq, double eventtime)
// Check for stalled messages now ready // Check for stalled messages now ready
double idletime = eventtime > sq->idle_time ? eventtime : sq->idle_time; double idletime = eventtime > sq->idle_time ? eventtime : sq->idle_time;
idletime += MESSAGE_MIN * sq->baud_adjust; idletime += calculate_bittime(sq, pending + MESSAGE_MIN);
uint64_t ack_clock = clock_from_time(&sq->ce, idletime); uint64_t ack_clock = clock_from_time(&sq->ce, idletime);
uint64_t min_stalled_clock = MAX_CLOCK, min_ready_clock = MAX_CLOCK; uint64_t min_stalled_clock = MAX_CLOCK, min_ready_clock = MAX_CLOCK;
struct command_queue *cq; struct command_queue *cq;
@ -525,9 +541,10 @@ check_send_command(struct serialqueue *sq, double eventtime)
struct queue_message *qm = list_first_entry( struct queue_message *qm = list_first_entry(
&cq->ready_queue, struct queue_message, node); &cq->ready_queue, struct queue_message, node);
uint64_t req_clock = qm->req_clock; uint64_t req_clock = qm->req_clock;
double bgtime = pending ? idletime : sq->idle_time;
double bgoffset = MIN_REQTIME_DELTA + MIN_BACKGROUND_DELTA; double bgoffset = MIN_REQTIME_DELTA + MIN_BACKGROUND_DELTA;
if (req_clock == BACKGROUND_PRIORITY_CLOCK) if (req_clock == BACKGROUND_PRIORITY_CLOCK)
req_clock = clock_from_time(&sq->ce, sq->idle_time + bgoffset); req_clock = clock_from_time(&sq->ce, bgtime + bgoffset);
if (req_clock < min_ready_clock) if (req_clock < min_ready_clock)
min_ready_clock = req_clock; min_ready_clock = req_clock;
} }
@ -561,18 +578,21 @@ command_event(struct serialqueue *sq, double eventtime)
int buflen = 0; int buflen = 0;
double waketime; double waketime;
for (;;) { for (;;) {
waketime = check_send_command(sq, eventtime); waketime = check_send_command(sq, buflen, eventtime);
if (waketime != PR_NOW || buflen + MESSAGE_MAX > sizeof(buf)) { if (waketime != PR_NOW || buflen + MESSAGE_MAX > sizeof(buf)) {
if (buflen) { if (buflen) {
// Write message blocks // Write message blocks
do_write(sq, buf, buflen); do_write(sq, buf, buflen);
sq->bytes_write += buflen; sq->bytes_write += buflen;
double idletime = (eventtime > sq->idle_time
? eventtime : sq->idle_time);
sq->idle_time = idletime + calculate_bittime(sq, buflen);
buflen = 0; buflen = 0;
} }
if (waketime != PR_NOW) if (waketime != PR_NOW)
break; break;
} }
buflen += build_and_send_command(sq, &buf[buflen], eventtime); buflen += build_and_send_command(sq, &buf[buflen], buflen, eventtime);
} }
pthread_mutex_unlock(&sq->lock); pthread_mutex_unlock(&sq->lock);
return waketime; return waketime;
@ -847,10 +867,15 @@ exit:
} }
void __visible void __visible
serialqueue_set_baud_adjust(struct serialqueue *sq, double baud_adjust) serialqueue_set_wire_frequency(struct serialqueue *sq, double frequency)
{ {
pthread_mutex_lock(&sq->lock); pthread_mutex_lock(&sq->lock);
sq->baud_adjust = baud_adjust; if (sq->serial_fd_type == SQT_CAN) {
sq->bittime_adjust = 1. / frequency;
} else {
// An 8N1 serial line is 10 bits per byte (1 start, 8 data, 1 stop)
sq->bittime_adjust = 10. / frequency;
}
pthread_mutex_unlock(&sq->lock); pthread_mutex_unlock(&sq->lock);
} }

View File

@ -42,7 +42,7 @@ void serialqueue_send(struct serialqueue *sq, struct command_queue *cq
, uint8_t *msg, int len, uint64_t min_clock , uint8_t *msg, int len, uint64_t min_clock
, uint64_t req_clock, uint64_t notify_id); , uint64_t req_clock, uint64_t notify_id);
void serialqueue_pull(struct serialqueue *sq, struct pull_queue_message *pqm); void serialqueue_pull(struct serialqueue *sq, struct pull_queue_message *pqm);
void serialqueue_set_baud_adjust(struct serialqueue *sq, double baud_adjust); void serialqueue_set_wire_frequency(struct serialqueue *sq, double frequency);
void serialqueue_set_receive_window(struct serialqueue *sq, int receive_window); void serialqueue_set_receive_window(struct serialqueue *sq, int receive_window);
void serialqueue_set_clock_est(struct serialqueue *sq, double est_freq void serialqueue_set_clock_est(struct serialqueue *sq, double est_freq
, double conv_time, uint64_t conv_clock , double conv_time, uint64_t conv_clock

View File

@ -140,6 +140,7 @@ class PrinterConfig:
self.autosave = None self.autosave = None
self.deprecated = {} self.deprecated = {}
self.status_raw_config = {} self.status_raw_config = {}
self.status_save_pending = {}
self.status_settings = {} self.status_settings = {}
self.status_warnings = [] self.status_warnings = []
self.save_config_pending = False self.save_config_pending = False
@ -331,17 +332,35 @@ class PrinterConfig:
return {'config': self.status_raw_config, return {'config': self.status_raw_config,
'settings': self.status_settings, 'settings': self.status_settings,
'warnings': self.status_warnings, 'warnings': self.status_warnings,
'save_config_pending': self.save_config_pending} 'save_config_pending': self.save_config_pending,
'save_config_pending_items': self.status_save_pending}
# Autosave functions # Autosave functions
def set(self, section, option, value): def set(self, section, option, value):
if not self.autosave.fileconfig.has_section(section): if not self.autosave.fileconfig.has_section(section):
self.autosave.fileconfig.add_section(section) self.autosave.fileconfig.add_section(section)
svalue = str(value) svalue = str(value)
self.autosave.fileconfig.set(section, option, svalue) self.autosave.fileconfig.set(section, option, svalue)
pending = dict(self.status_save_pending)
if not section in pending or pending[section] is None:
pending[section] = {}
else:
pending[section] = dict(pending[section])
pending[section][option] = svalue
self.status_save_pending = pending
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.autosave.fileconfig.has_section(section):
self.autosave.fileconfig.remove_section(section) self.autosave.fileconfig.remove_section(section)
pending = dict(self.status_save_pending)
pending[section] = None
self.status_save_pending = pending
self.save_config_pending = True
elif (section in self.status_save_pending and
self.status_save_pending[section] is not None):
pending = dict(self.status_save_pending)
del pending[section]
self.status_save_pending = pending
self.save_config_pending = True self.save_config_pending = True
def _disallow_include_conflicts(self, regular_data, cfgname, gcode): def _disallow_include_conflicts(self, regular_data, cfgname, gcode):
config = self._build_config_wrapper(regular_data, cfgname) config = self._build_config_wrapper(regular_data, cfgname)

View File

@ -11,11 +11,12 @@ help_txt = """
This is a debugging console for the Klipper micro-controller. This is a debugging console for the Klipper micro-controller.
In addition to mcu commands, the following artificial commands are In addition to mcu commands, the following artificial commands are
available: available:
PINS : Load pin name aliases (eg, "PINS arduino")
DELAY : Send a command at a clock time (eg, "DELAY 9999 get_uptime") DELAY : Send a command at a clock time (eg, "DELAY 9999 get_uptime")
FLOOD : Send a command many times (eg, "FLOOD 22 .01 get_uptime") FLOOD : Send a command many times (eg, "FLOOD 22 .01 get_uptime")
SUPPRESS : Suppress a response message (eg, "SUPPRESS analog_in_state 4") SUPPRESS : Suppress a response message (eg, "SUPPRESS analog_in_state 4")
SET : Create a local variable (eg, "SET myvar 123.4") SET : Create a local variable (eg, "SET myvar 123.4")
DUMP : Dump memory (eg, "DUMP 0x12345678 100 32")
FILEDUMP : Dump to file (eg, "FILEDUMP data.bin 0x12345678 100 32")
STATS : Report serial statistics STATS : Report serial statistics
LIST : List available mcu commands, local commands, and local variables LIST : List available mcu commands, local commands, and local variables
HELP : Show this text HELP : Show this text
@ -48,6 +49,7 @@ class KeyboardReader:
reactor.register_callback(self.connect) reactor.register_callback(self.connect)
self.local_commands = { self.local_commands = {
"SET": self.command_SET, "SET": self.command_SET,
"DUMP": self.command_DUMP, "FILEDUMP": self.command_FILEDUMP,
"DELAY": self.command_DELAY, "FLOOD": self.command_FLOOD, "DELAY": self.command_DELAY, "FLOOD": self.command_FLOOD,
"SUPPRESS": self.command_SUPPRESS, "STATS": self.command_STATS, "SUPPRESS": self.command_SUPPRESS, "STATS": self.command_STATS,
"LIST": self.command_LIST, "HELP": self.command_HELP, "LIST": self.command_LIST, "HELP": self.command_HELP,
@ -98,6 +100,55 @@ class KeyboardReader:
except ValueError: except ValueError:
pass pass
self.eval_globals[parts[1]] = val self.eval_globals[parts[1]] = val
def command_DUMP(self, parts, filename=None):
# Extract command args
try:
addr = int(parts[1], 0)
count = int(parts[2], 0)
order = [2, 0, 1, 0][(addr | count) & 3]
if len(parts) > 3:
order = {'32': 2, '16': 1, '8': 0}[parts[3]]
except ValueError as e:
self.output("Error: %s" % (str(e),))
return
bsize = 1 << order
# Query data from mcu
vals = []
for i in range((count + bsize - 1) >> order):
caddr = addr + (i << order)
cmd = "debug_read order=%d addr=%d" % (order, caddr)
params = self.ser.send_with_response(cmd, "debug_result")
vals.append(params['val'])
# Report data
if filename is None and order == 2:
# Common 32bit hex dump
for i in range((len(vals) + 3) // 4):
p = i * 4
hexvals = " ".join(["%08x" % (v,) for v in vals[p:p+4]])
self.output("%08x %s" % (addr + p * 4, hexvals))
return
# Convert to byte format
data = bytearray()
for val in vals:
for b in range(bsize):
data.append((val >> (8 * b)) & 0xff)
data = data[:count]
if filename is not None:
f = open(filename, 'wb')
f.write(data)
f.close()
self.output("Wrote %d bytes to '%s'" % (len(data), filename))
return
for i in range((count + 15) // 16):
p = i * 16
paddr = addr + p
d = data[p:p+16]
hexbytes = " ".join(["%02x" % (v,) for v in d])
pb = "".join([chr(v) if v >= 0x20 and v < 0x7f else '.' for v in d])
o = "%08x %-47s |%s|" % (paddr, hexbytes, pb)
self.output("%s %s" % (o[:34], o[34:]))
def command_FILEDUMP(self, parts):
self.command_DUMP(parts[1:], filename=parts[1])
def command_DELAY(self, parts): def command_DELAY(self, parts):
try: try:
val = int(parts[1]) val = int(parts[1])

View File

@ -30,7 +30,7 @@ Accel_Measurement = collections.namedtuple(
'Accel_Measurement', ('time', 'accel_x', 'accel_y', 'accel_z')) 'Accel_Measurement', ('time', 'accel_x', 'accel_y', 'accel_z'))
# Helper class to obtain measurements # Helper class to obtain measurements
class ADXL345QueryHelper: class AccelQueryHelper:
def __init__(self, printer, cconn): def __init__(self, printer, cconn):
self.printer = printer self.printer = printer
self.cconn = cconn self.cconn = cconn
@ -101,14 +101,17 @@ class ADXL345QueryHelper:
write_proc.start() write_proc.start()
# Helper class for G-Code commands # Helper class for G-Code commands
class ADXLCommandHelper: class AccelCommandHelper:
def __init__(self, config, chip): def __init__(self, config, chip):
self.printer = config.get_printer() self.printer = config.get_printer()
self.chip = chip self.chip = chip
self.bg_client = None self.bg_client = None
self.name = config.get_name().split()[-1] name_parts = config.get_name().split()
self.base_name = name_parts[0]
self.name = name_parts[-1]
self.register_commands(self.name) self.register_commands(self.name)
if self.name == "adxl345": if len(name_parts) == 1:
if self.name == "adxl345" or not config.has_section("adxl345"):
self.register_commands(None) self.register_commands(None)
def register_commands(self, name): def register_commands(self, name):
# Register commands # Register commands
@ -130,20 +133,20 @@ class ADXLCommandHelper:
if self.bg_client is None: if self.bg_client is None:
# Start measurements # Start measurements
self.bg_client = self.chip.start_internal_client() self.bg_client = self.chip.start_internal_client()
gcmd.respond_info("adxl345 measurements started") gcmd.respond_info("accelerometer measurements started")
return return
# End measurements # End measurements
name = gcmd.get("NAME", time.strftime("%Y%m%d_%H%M%S")) name = gcmd.get("NAME", time.strftime("%Y%m%d_%H%M%S"))
if not name.replace('-', '').replace('_', '').isalnum(): if not name.replace('-', '').replace('_', '').isalnum():
raise gcmd.error("Invalid adxl345 NAME parameter") raise gcmd.error("Invalid NAME parameter")
bg_client = self.bg_client bg_client = self.bg_client
self.bg_client = None self.bg_client = None
bg_client.finish_measurements() bg_client.finish_measurements()
# Write data to file # Write data to file
if self.name == "adxl345": if self.base_name == self.name:
filename = "/tmp/adxl345-%s.csv" % (name,) filename = "/tmp/%s-%s.csv" % (self.base_name, name)
else: else:
filename = "/tmp/adxl345-%s-%s.csv" % (self.name, name,) filename = "/tmp/%s-%s-%s.csv" % (self.base_name, self.name, name)
bg_client.write_to_file(filename) bg_client.write_to_file(filename)
gcmd.respond_info("Writing raw accelerometer data to %s file" gcmd.respond_info("Writing raw accelerometer data to %s file"
% (filename,)) % (filename,))
@ -154,18 +157,18 @@ class ADXLCommandHelper:
aclient.finish_measurements() aclient.finish_measurements()
values = aclient.get_samples() values = aclient.get_samples()
if not values: if not values:
raise gcmd.error("No adxl345 measurements found") raise gcmd.error("No accelerometer measurements found")
_, accel_x, accel_y, accel_z = values[-1] _, accel_x, accel_y, accel_z = values[-1]
gcmd.respond_info("adxl345 values (x, y, z): %.6f, %.6f, %.6f" gcmd.respond_info("accelerometer values (x, y, z): %.6f, %.6f, %.6f"
% (accel_x, accel_y, accel_z)) % (accel_x, accel_y, accel_z))
cmd_ACCELEROMETER_DEBUG_READ_help = "Query adxl345 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=29, maxval=57, 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("ADXL345 REG[0x%x] = 0x%x" % (reg, val)) gcmd.respond_info("Accelerometer REG[0x%x] = 0x%x" % (reg, val))
cmd_ACCELEROMETER_DEBUG_WRITE_help = "Set adxl345 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=29, maxval=57, 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)
@ -226,7 +229,7 @@ SAMPLES_PER_BLOCK = 10
class ADXL345: class ADXL345:
def __init__(self, config): def __init__(self, config):
self.printer = config.get_printer() self.printer = config.get_printer()
ADXLCommandHelper(config, self) AccelCommandHelper(config, self)
self.query_rate = 0 self.query_rate = 0
am = {'x': (0, SCALE), 'y': (1, SCALE), 'z': (2, SCALE), am = {'x': (0, SCALE), 'y': (1, SCALE), 'z': (2, SCALE),
'-x': (0, -SCALE), '-y': (1, -SCALE), '-z': (2, -SCALE)} '-x': (0, -SCALE), '-y': (1, -SCALE), '-z': (2, -SCALE)}
@ -432,7 +435,7 @@ class ADXL345:
web_request.send({'header': hdr}) web_request.send({'header': hdr})
def start_internal_client(self): def start_internal_client(self):
cconn = self.api_dump.add_internal_client() cconn = self.api_dump.add_internal_client()
return ADXL345QueryHelper(self.printer, cconn) return AccelQueryHelper(self.printer, cconn)
def load_config(config): def load_config(config):
return ADXL345(config) return ADXL345(config)

View File

@ -124,6 +124,8 @@ class BedMesh:
# Register transform # Register transform
gcode_move = self.printer.load_object(config, 'gcode_move') gcode_move = self.printer.load_object(config, 'gcode_move')
gcode_move.set_move_transform(self) gcode_move.set_move_transform(self)
# initialize status dict
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) self.bmc.print_generated_points(logging.info)
@ -162,6 +164,7 @@ class BedMesh:
# cache the current position before a transform takes place # cache the current position before a transform takes place
gcode_move = self.printer.lookup_object('gcode_move') gcode_move = self.printer.lookup_object('gcode_move')
gcode_move.reset_last_position() gcode_move.reset_last_position()
self.update_status()
def get_z_factor(self, z_pos): def get_z_factor(self, z_pos):
if z_pos >= self.fade_end: if z_pos >= self.fade_end:
return 0. return 0.
@ -216,7 +219,9 @@ class BedMesh:
"Mesh Leveling: Error splitting move ") "Mesh Leveling: Error splitting move ")
self.last_position[:] = newpos self.last_position[:] = newpos
def get_status(self, eventtime=None): def get_status(self, eventtime=None):
status = { return self.status
def update_status(self):
self.status = {
"profile_name": "", "profile_name": "",
"mesh_min": (0., 0.), "mesh_min": (0., 0.),
"mesh_max": (0., 0.), "mesh_max": (0., 0.),
@ -230,12 +235,11 @@ class BedMesh:
mesh_max = (params['max_x'], params['max_y']) mesh_max = (params['max_x'], params['max_y'])
probed_matrix = self.z_mesh.get_probed_matrix() probed_matrix = self.z_mesh.get_probed_matrix()
mesh_matrix = self.z_mesh.get_mesh_matrix() mesh_matrix = self.z_mesh.get_mesh_matrix()
status['profile_name'] = self.pmgr.get_current_profile() self.status['profile_name'] = self.pmgr.get_current_profile()
status['mesh_min'] = mesh_min self.status['mesh_min'] = mesh_min
status['mesh_max'] = mesh_max self.status['mesh_max'] = mesh_max
status['probed_matrix'] = probed_matrix self.status['probed_matrix'] = probed_matrix
status['mesh_matrix'] = mesh_matrix self.status['mesh_matrix'] = mesh_matrix
return status
def get_mesh(self): def get_mesh(self):
return self.z_mesh return self.z_mesh
cmd_BED_MESH_OUTPUT_help = "Retrieve interpolated grid of probed z-points" cmd_BED_MESH_OUTPUT_help = "Retrieve interpolated grid of probed z-points"
@ -1180,6 +1184,7 @@ class ProfileManager:
profile['mesh_params'] = collections.OrderedDict(mesh_params) profile['mesh_params'] = collections.OrderedDict(mesh_params)
self.profiles = profiles self.profiles = profiles
self.current_profile = prof_name self.current_profile = prof_name
self.bedmesh.update_status()
self.gcode.respond_info( self.gcode.respond_info(
"Bed Mesh state has been saved to profile [%s]\n" "Bed Mesh state has been saved to profile [%s]\n"
"for the current session. The SAVE_CONFIG command will\n" "for the current session. The SAVE_CONFIG command will\n"
@ -1206,6 +1211,7 @@ class ProfileManager:
profiles = dict(self.profiles) profiles = dict(self.profiles)
del profiles[prof_name] del profiles[prof_name]
self.profiles = profiles self.profiles = profiles
self.bedmesh.update_status()
self.gcode.respond_info( self.gcode.respond_info(
"Profile [%s] removed from storage for this session.\n" "Profile [%s] removed from storage for this session.\n"
"The SAVE_CONFIG command will update the printer\n" "The SAVE_CONFIG command will update the printer\n"

View File

@ -7,9 +7,7 @@
class BedScrews: class BedScrews:
def __init__(self, config): def __init__(self, config):
self.printer = config.get_printer() self.printer = config.get_printer()
self.state = None self.reset()
self.current_screw = 0
self.accepted_screws = 0
self.number_of_screws = 0 self.number_of_screws = 0
# Read config # Read config
screws = [] screws = []
@ -39,6 +37,10 @@ class BedScrews:
self.gcode.register_command("BED_SCREWS_ADJUST", self.gcode.register_command("BED_SCREWS_ADJUST",
self.cmd_BED_SCREWS_ADJUST, self.cmd_BED_SCREWS_ADJUST,
desc=self.cmd_BED_SCREWS_ADJUST_help) desc=self.cmd_BED_SCREWS_ADJUST_help)
def reset(self):
self.state = None
self.current_screw = 0
self.accepted_screws = 0
def move(self, coord, speed): def move(self, coord, speed):
self.printer.lookup_object('toolhead').manual_move(coord, speed) self.printer.lookup_object('toolhead').manual_move(coord, speed)
def move_to_screw(self, state, screw): def move_to_screw(self, state, screw):
@ -64,6 +66,13 @@ class BedScrews:
self.gcode.register_command('ACCEPT', None) self.gcode.register_command('ACCEPT', None)
self.gcode.register_command('ADJUSTED', None) self.gcode.register_command('ADJUSTED', None)
self.gcode.register_command('ABORT', None) self.gcode.register_command('ABORT', None)
def get_status(self, eventtime):
return {
'is_active': self.state is not None,
'state': self.state,
'current_screw': self.current_screw,
'accepted_screws': self.accepted_screws
}
cmd_BED_SCREWS_ADJUST_help = "Tool to help adjust bed leveling screws" cmd_BED_SCREWS_ADJUST_help = "Tool to help adjust bed leveling screws"
def cmd_BED_SCREWS_ADJUST(self, gcmd): def cmd_BED_SCREWS_ADJUST(self, gcmd):
if self.state is not None: if self.state is not None:
@ -92,7 +101,7 @@ class BedScrews:
self.move_to_screw('fine', 0) self.move_to_screw('fine', 0)
return return
# Done # Done
self.state = None self.reset()
self.move((None, None, self.horizontal_move_z), self.lift_speed) self.move((None, None, self.horizontal_move_z), self.lift_speed)
gcmd.respond_info("Bed screws tool completed successfully") gcmd.respond_info("Bed screws tool completed successfully")
cmd_ADJUSTED_help = "Accept bed screw position after notable adjustment" cmd_ADJUSTED_help = "Accept bed screw position after notable adjustment"
@ -103,7 +112,7 @@ class BedScrews:
cmd_ABORT_help = "Abort bed screws tool" cmd_ABORT_help = "Abort bed screws tool"
def cmd_ABORT(self, gcmd): def cmd_ABORT(self, gcmd):
self.unregister_commands() self.unregister_commands()
self.state = None self.reset()
def load_config(config): def load_config(config):
return BedScrews(config) return BedScrews(config)

View File

@ -4,6 +4,8 @@
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
NODEID_FIRST = 4
class PrinterCANBus: class PrinterCANBus:
def __init__(self, config): def __init__(self, config):
self.printer = config.get_printer() self.printer = config.get_printer()
@ -11,7 +13,7 @@ class PrinterCANBus:
def add_uuid(self, config, canbus_uuid, canbus_iface): def add_uuid(self, config, canbus_uuid, canbus_iface):
if canbus_uuid in self.ids: if canbus_uuid in self.ids:
raise config.error("Duplicate canbus_uuid") raise config.error("Duplicate canbus_uuid")
new_id = len(self.ids) new_id = len(self.ids) + NODEID_FIRST
self.ids[canbus_uuid] = new_id self.ids[canbus_uuid] = new_id
return new_id return new_id
def get_nodeid(self, canbus_uuid): def get_nodeid(self, canbus_uuid):

View File

@ -45,7 +45,7 @@ def measurements_to_distances(measured_params, delta_params):
od - opw od - opw
for od, opw in zip(mp['OUTER_DISTS'], mp['OUTER_PILLAR_WIDTHS']) ] for od, opw in zip(mp['OUTER_DISTS'], mp['OUTER_PILLAR_WIDTHS']) ]
# Convert angles in degrees to an XY multiplier # Convert angles in degrees to an XY multiplier
obj_angles = map(math.radians, MeasureAngles) obj_angles = list(map(math.radians, MeasureAngles))
xy_angles = list(zip(map(math.cos, obj_angles), map(math.sin, obj_angles))) xy_angles = list(zip(map(math.cos, obj_angles), map(math.sin, obj_angles)))
# Calculate stable positions for center measurements # Calculate stable positions for center measurements
inner_ridge = MeasureRidgeRadius * scale inner_ridge = MeasureRidgeRadius * scale

View File

@ -16,6 +16,9 @@ class DisplayStatus:
gcode = self.printer.lookup_object('gcode') gcode = self.printer.lookup_object('gcode')
gcode.register_command('M73', self.cmd_M73) gcode.register_command('M73', self.cmd_M73)
gcode.register_command('M117', self.cmd_M117) gcode.register_command('M117', self.cmd_M117)
gcode.register_command(
'SET_DISPLAY_TEXT', self.cmd_SET_DISPLAY_TEXT,
desc=self.cmd_SET_DISPLAY_TEXT_help)
def get_status(self, eventtime): def get_status(self, eventtime):
progress = self.progress progress = self.progress
if progress is not None and eventtime > self.expire_progress: if progress is not None and eventtime > self.expire_progress:
@ -39,6 +42,9 @@ class DisplayStatus:
def cmd_M117(self, gcmd): def cmd_M117(self, gcmd):
msg = gcmd.get_raw_command_parameters() or None msg = gcmd.get_raw_command_parameters() or None
self.message = msg self.message = msg
cmd_SET_DISPLAY_TEXT_help = "Set or clear the display message"
def cmd_SET_DISPLAY_TEXT(self, gcmd):
self.message = gcmd.get("MSG", None)
def load_config(config): def load_config(config):
return DisplayStatus(config) return DisplayStatus(config)

View File

@ -10,6 +10,7 @@ DS18_REPORT_TIME = 3.0
# Temperature can be sampled at any time but conversion time is ~750ms, so # Temperature can be sampled at any time but conversion time is ~750ms, so
# setting the time too low will not make the reports come faster. # setting the time too low will not make the reports come faster.
DS18_MIN_REPORT_TIME = 1.0 DS18_MIN_REPORT_TIME = 1.0
DS18_MAX_CONSECUTIVE_ERRORS = 4
class DS18B20: class DS18B20:
def __init__(self, config): def __init__(self, config):
@ -31,8 +32,9 @@ class DS18B20:
def _build_config(self): def _build_config(self):
sid = "".join(["%02x" % (x,) for x in self.sensor_id]) sid = "".join(["%02x" % (x,) for x in self.sensor_id])
self._mcu.add_config_cmd("config_ds18b20 oid=%d serial=%s" self._mcu.add_config_cmd(
% (self.oid, sid)) "config_ds18b20 oid=%d serial=%s max_error_count=%d"
% (self.oid, sid, DS18_MAX_CONSECUTIVE_ERRORS))
clock = self._mcu.get_query_slot(self.oid) clock = self._mcu.get_query_slot(self.oid)
self._report_clock = self._mcu.seconds_to_clock(self.report_time) self._report_clock = self._mcu.seconds_to_clock(self.report_time)
@ -44,10 +46,10 @@ class DS18B20:
def _handle_ds18b20_response(self, params): def _handle_ds18b20_response(self, params):
temp = params['value'] / 1000.0 temp = params['value'] / 1000.0
if temp < self.min_temp or temp > self.max_temp: if params["fault"]:
self.printer.invoke_shutdown( logging.info("ds18b20 reports fault %d (temp=%0.1f)",
"DS18B20 temperature %0.1f outside range of %0.1f:%.01f" params["fault"], temp)
% (temp, self.min_temp, self.max_temp)) return
next_clock = self._mcu.clock32_to_clock64(params['next_clock']) next_clock = self._mcu.clock32_to_clock64(params['next_clock'])
last_read_clock = next_clock - self._report_clock last_read_clock = next_clock - self._report_clock

View File

@ -0,0 +1,302 @@
# Exclude moves toward and inside objects
#
# Copyright (C) 2019 Eric Callahan <arksine.code@gmail.com>
# Copyright (C) 2021 Troy Jacobson <troy.d.jacobson@gmail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
import json
class ExcludeObject:
def __init__(self, config):
self.printer = config.get_printer()
self.gcode = self.printer.lookup_object('gcode')
self.gcode_move = self.printer.load_object(config, 'gcode_move')
self.printer.register_event_handler('klippy:connect',
self._handle_connect)
self.printer.register_event_handler("virtual_sdcard:reset_file",
self._reset_file)
self.next_transform = None
self.last_position_extruded = [0., 0., 0., 0.]
self.last_position_excluded = [0., 0., 0., 0.]
self._reset_state()
self.gcode.register_command(
'EXCLUDE_OBJECT_START', self.cmd_EXCLUDE_OBJECT_START,
desc=self.cmd_EXCLUDE_OBJECT_START_help)
self.gcode.register_command(
'EXCLUDE_OBJECT_END', self.cmd_EXCLUDE_OBJECT_END,
desc=self.cmd_EXCLUDE_OBJECT_END_help)
self.gcode.register_command(
'EXCLUDE_OBJECT', self.cmd_EXCLUDE_OBJECT,
desc=self.cmd_EXCLUDE_OBJECT_help)
self.gcode.register_command(
'EXCLUDE_OBJECT_DEFINE', self.cmd_EXCLUDE_OBJECT_DEFINE,
desc=self.cmd_EXCLUDE_OBJECT_DEFINE_help)
def _register_transform(self):
if self.next_transform is None:
tuning_tower = self.printer.lookup_object('tuning_tower')
if tuning_tower.is_active():
logging.info('The ExcludeObject move transform is not being '
'loaded due to Tuning tower being Active')
return
self.next_transform = self.gcode_move.set_move_transform(self,
force=True)
self.extrusion_offsets = {}
self.max_position_extruded = 0
self.max_position_excluded = 0
self.extruder_adj = 0
self.initial_extrusion_moves = 5
self.last_position = [0., 0., 0., 0.]
self.get_position()
self.last_position_extruded[:] = self.last_position
self.last_position_excluded[:] = self.last_position
def _handle_connect(self):
self.toolhead = self.printer.lookup_object('toolhead')
def _unregister_transform(self):
if self.next_transform:
tuning_tower = self.printer.lookup_object('tuning_tower')
if tuning_tower.is_active():
logging.error('The Exclude Object move transform was not '
'unregistered because it is not at the head of the '
'transform chain.')
return
self.gcode_move.set_move_transform(self.next_transform, force=True)
self.next_transform = None
self.gcode_move.reset_last_position()
def _reset_state(self):
self.objects = []
self.excluded_objects = []
self.current_object = None
self.in_excluded_region = False
def _reset_file(self):
self._reset_state()
self._unregister_transform()
def _get_extrusion_offsets(self):
offset = self.extrusion_offsets.get(
self.toolhead.get_extruder().get_name())
if offset is None:
offset = [0., 0., 0., 0.]
self.extrusion_offsets[self.toolhead.get_extruder().get_name()] = \
offset
return offset
def get_position(self):
offset = self._get_extrusion_offsets()
pos = self.next_transform.get_position()
for i in range(4):
self.last_position[i] = pos[i] + offset[i]
return list(self.last_position)
def _normal_move(self, newpos, speed):
offset = self._get_extrusion_offsets()
if self.initial_extrusion_moves > 0 and \
self.last_position[3] != newpos[3]:
# Since the transform is not loaded until there is a request to
# exclude an object, the transform needs to track a few extrusions
# to get the state of the extruder
self.initial_extrusion_moves -= 1
self.last_position[:] = newpos
self.last_position_extruded[:] = self.last_position
self.max_position_extruded = max(self.max_position_extruded, newpos[3])
# These next few conditionals handle the moves immediately after leaving
# and excluded object. The toolhead is at the end of the last printed
# object and the gcode is at the end of the last excluded object.
#
# Ideally, there will be Z and E moves right away to adjust any offsets
# before moving away from the last position. Any remaining corrections
# will be made on the firs XY move.
if (offset[0] != 0 or offset[1] != 0) and \
(newpos[0] != self.last_position_excluded[0] or \
newpos[1] != self.last_position_excluded[1]):
offset[0] = 0
offset[1] = 0
offset[2] = 0
offset[3] += self.extruder_adj
self.extruder_adj = 0
if offset[2] != 0 and newpos[2] != self.last_position_excluded[2]:
offset[2] = 0
if self.extruder_adj != 0 and \
newpos[3] != self.last_position_excluded[3]:
offset[3] += self.extruder_adj
self.extruder_adj = 0
tx_pos = newpos[:]
for i in range(4):
tx_pos[i] = newpos[i] - offset[i]
self.next_transform.move(tx_pos, speed)
def _ignore_move(self, newpos, speed):
offset = self._get_extrusion_offsets()
for i in range(3):
offset[i] = newpos[i] - self.last_position_extruded[i]
offset[3] = offset[3] + newpos[3] - self.last_position[3]
self.last_position[:] = newpos
self.last_position_excluded[:] =self.last_position
self.max_position_excluded = max(self.max_position_excluded, newpos[3])
def _move_into_excluded_region(self, newpos, speed):
self.in_excluded_region = True
self._ignore_move(newpos, speed)
def _move_from_excluded_region(self, newpos, speed):
self.in_excluded_region = False
# This adjustment value is used to compensate for any retraction
# differences between the last object printed and excluded one.
self.extruder_adj = self.max_position_excluded \
- self.last_position_excluded[3] \
- (self.max_position_extruded - self.last_position_extruded[3])
self._normal_move(newpos, speed)
def _test_in_excluded_region(self):
# Inside cancelled object
return self.current_object in self.excluded_objects \
and self.initial_extrusion_moves == 0
def get_status(self, eventtime=None):
status = {
"objects": self.objects,
"excluded_objects": self.excluded_objects,
"current_object": self.current_object
}
return status
def move(self, newpos, speed):
move_in_excluded_region = self._test_in_excluded_region()
self.last_speed = speed
if move_in_excluded_region:
if self.in_excluded_region:
self._ignore_move(newpos, speed)
else:
self._move_into_excluded_region(newpos, speed)
else:
if self.in_excluded_region:
self._move_from_excluded_region(newpos, speed)
else:
self._normal_move(newpos, speed)
cmd_EXCLUDE_OBJECT_START_help = "Marks the beginning the current object" \
" as labeled"
def cmd_EXCLUDE_OBJECT_START(self, gcmd):
name = gcmd.get('NAME').upper()
if not any(obj["name"] == name for obj in self.objects):
self._add_object_definition({"name": name})
self.current_object = name
self.was_excluded_at_start = self._test_in_excluded_region()
cmd_EXCLUDE_OBJECT_END_help = "Marks the end the current object"
def cmd_EXCLUDE_OBJECT_END(self, gcmd):
if self.current_object == None and self.next_transform:
gcmd.respond_info("EXCLUDE_OBJECT_END called, but no object is"
" currently active")
return
name = gcmd.get('NAME', default=None)
if name != None and name.upper() != self.current_object:
gcmd.respond_info("EXCLUDE_OBJECT_END NAME=%s does not match the"
" current object NAME=%s" %
(name.upper(), self.current_object))
self.current_object = None
cmd_EXCLUDE_OBJECT_help = "Cancel moves inside a specified objects"
def cmd_EXCLUDE_OBJECT(self, gcmd):
reset = gcmd.get('RESET', None)
current = gcmd.get('CURRENT', None)
name = gcmd.get('NAME', '').upper()
if reset:
if name:
self._unexclude_object(name)
else:
self.excluded_objects = []
elif name:
if name.upper() not in self.excluded_objects:
self._exclude_object(name.upper())
elif current:
if not self.current_object:
gcmd.respond_error('There is no current object to cancel')
else:
self._exclude_object(self.current_object)
else:
self._list_excluded_objects(gcmd)
cmd_EXCLUDE_OBJECT_DEFINE_help = "Provides a summary of an object"
def cmd_EXCLUDE_OBJECT_DEFINE(self, gcmd):
reset = gcmd.get('RESET', None)
name = gcmd.get('NAME', '').upper()
if reset:
self._reset_file()
elif name:
parameters = gcmd.get_command_parameters().copy()
parameters.pop('NAME')
center = parameters.pop('CENTER', None)
polygon = parameters.pop('POLYGON', None)
obj = {"name": name.upper()}
obj.update(parameters)
if center != None:
obj['center'] = json.loads('[%s]' % center)
if polygon != None:
obj['polygon'] = json.loads(polygon)
self._add_object_definition(obj)
else:
self._list_objects(gcmd)
def _add_object_definition(self, definition):
self.objects = sorted(self.objects + [definition],
key=lambda o: o["name"])
def _exclude_object(self, name):
self._register_transform()
self.gcode.respond_info('Excluding object {}'.format(name.upper()))
if name not in self.excluded_objects:
self.excluded_objects = sorted(self.excluded_objects + [name])
def _unexclude_object(self, name):
self.gcode.respond_info('Unexcluding object {}'.format(name.upper()))
if name in self.excluded_objects:
excluded_objects = list(self.excluded_objects)
excluded_objects.remove(name)
self.excluded_objects = sorted(excluded_objects)
def _list_objects(self, gcmd):
if gcmd.get('JSON', None) is not None:
object_list = json.dumps(self.objects)
else:
object_list = " ".join(obj['name'] for obj in self.objects)
gcmd.respond_info('Known objects: {}'.format(object_list))
def _list_excluded_objects(self, gcmd):
object_list = " ".join(self.excluded_objects)
gcmd.respond_info('Excluded objects: {}'.format(object_list))
def load_config(config):
return ExcludeObject(config)

View File

@ -24,9 +24,19 @@ class ManualProbe:
'Z_OFFSET_APPLY_ENDSTOP', 'Z_OFFSET_APPLY_ENDSTOP',
self.cmd_Z_OFFSET_APPLY_ENDSTOP, self.cmd_Z_OFFSET_APPLY_ENDSTOP,
desc=self.cmd_Z_OFFSET_APPLY_ENDSTOP_help) desc=self.cmd_Z_OFFSET_APPLY_ENDSTOP_help)
self.reset_status()
def manual_probe_finalize(self, kin_pos): def manual_probe_finalize(self, kin_pos):
if kin_pos is not None: if kin_pos is not None:
self.gcode.respond_info("Z position is %.3f" % (kin_pos[2],)) self.gcode.respond_info("Z position is %.3f" % (kin_pos[2],))
def reset_status(self):
self.status = {
'is_active': False,
'z_position': None,
'z_position_lower': None,
'z_position_upper': None
}
def get_status(self, eventtime):
return self.status
cmd_MANUAL_PROBE_help = "Start manual probe helper script" cmd_MANUAL_PROBE_help = "Start manual probe helper script"
def cmd_MANUAL_PROBE(self, gcmd): def cmd_MANUAL_PROBE(self, gcmd):
ManualProbeHelper(self.printer, gcmd, self.manual_probe_finalize) ManualProbeHelper(self.printer, gcmd, self.manual_probe_finalize)
@ -78,6 +88,7 @@ class ManualProbeHelper:
self.finalize_callback = finalize_callback self.finalize_callback = finalize_callback
self.gcode = self.printer.lookup_object('gcode') self.gcode = self.printer.lookup_object('gcode')
self.toolhead = self.printer.lookup_object('toolhead') self.toolhead = self.printer.lookup_object('toolhead')
self.manual_probe = self.printer.lookup_object('manual_probe')
self.speed = gcmd.get_float("SPEED", 5.) self.speed = gcmd.get_float("SPEED", 5.)
self.past_positions = [] self.past_positions = []
self.last_toolhead_pos = self.last_kinematics_pos = None self.last_toolhead_pos = self.last_kinematics_pos = None
@ -130,11 +141,20 @@ class ManualProbeHelper:
prev_pos = next_pos - 1 prev_pos = next_pos - 1
if next_pos < len(pp) and pp[next_pos] == z_pos: if next_pos < len(pp) and pp[next_pos] == z_pos:
next_pos += 1 next_pos += 1
prev_pos_val = next_pos_val = None
prev_str = next_str = "??????" prev_str = next_str = "??????"
if prev_pos >= 0: if prev_pos >= 0:
prev_str = "%.3f" % (pp[prev_pos],) prev_pos_val = pp[prev_pos]
prev_str = "%.3f" % (prev_pos_val,)
if next_pos < len(pp): if next_pos < len(pp):
next_str = "%.3f" % (pp[next_pos],) next_pos_val = pp[next_pos]
next_str = "%.3f" % (next_pos_val,)
self.manual_probe.status = {
'is_active': True,
'z_position': z_pos,
'z_position_lower': prev_pos_val,
'z_position_upper': next_pos_val,
}
# Find recent positions # Find recent positions
self.gcode.respond_info("Z position: %s --> %.3f <-- %s" self.gcode.respond_info("Z position: %s --> %.3f <-- %s"
% (prev_str, z_pos, next_str)) % (prev_str, z_pos, next_str))
@ -183,6 +203,7 @@ class ManualProbeHelper:
self.move_z(next_z_pos) self.move_z(next_z_pos)
self.report_z_status(next_z_pos != z_pos, z_pos) self.report_z_status(next_z_pos != z_pos, z_pos)
def finalize(self, success): def finalize(self, success):
self.manual_probe.reset_status()
self.gcode.register_command('ACCEPT', None) self.gcode.register_command('ACCEPT', None)
self.gcode.register_command('NEXT', None) self.gcode.register_command('NEXT', None)
self.gcode.register_command('ABORT', None) self.gcode.register_command('ABORT', None)

268
klippy/extras/mpu9250.py Normal file
View File

@ -0,0 +1,268 @@
# Support for reading acceleration data from an mpu9250 chip
#
# Copyright (C) 2022 Harry Beyel <harry3b9@gmail.com>
# Copyright (C) 2020-2021 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging, time, collections, threading, multiprocessing, os
from . import bus, motion_report, adxl345
MPU9250_ADDR = 0x68
MPU9250_DEV_ID = 0x73
MPU6050_DEV_ID = 0x68
# MPU9250 registers
REG_DEVID = 0x75
REG_FIFO_EN = 0x23
REG_SMPLRT_DIV = 0x19
REG_CONFIG = 0x1A
REG_ACCEL_CONFIG = 0x1C
REG_ACCEL_CONFIG2 = 0x1D
REG_USER_CTRL = 0x6A
REG_PWR_MGMT_1 = 0x6B
REG_PWR_MGMT_2 = 0x6C
SAMPLE_RATE_DIVS = { 4000:0x00 }
SET_CONFIG = 0x01 # FIFO mode 'stream' style
SET_ACCEL_CONFIG = 0x10 # 8g full scale
SET_ACCEL_CONFIG2 = 0x08 # 1046Hz BW, 0.503ms delay 4kHz sample rate
SET_PWR_MGMT_1_WAKE = 0x00
SET_PWR_MGMT_1_SLEEP= 0x40
SET_PWR_MGMT_2_ACCEL_ON = 0x07
SET_PWR_MGMT_2_OFF = 0x3F
FREEFALL_ACCEL = 9.80665 * 1000.
# SCALE = 1/4096 g/LSB @8g scale * Earth gravity in mm/s**2
SCALE = 0.000244140625 * FREEFALL_ACCEL
FIFO_SIZE = 512
Accel_Measurement = collections.namedtuple(
'Accel_Measurement', ('time', 'accel_x', 'accel_y', 'accel_z'))
MIN_MSG_TIME = 0.100
BYTES_PER_SAMPLE = 6
SAMPLES_PER_BLOCK = 8
# Printer class that controls MPU9250 chip
class MPU9250:
def __init__(self, config):
self.printer = config.get_printer()
adxl345.AccelCommandHelper(config, self)
self.query_rate = 0
am = {'x': (0, SCALE), 'y': (1, SCALE), 'z': (2, SCALE),
'-x': (0, -SCALE), '-y': (1, -SCALE), '-z': (2, -SCALE)}
axes_map = config.getlist('axes_map', ('x','y','z'), count=3)
if any([a not in am for a in axes_map]):
raise config.error("Invalid mpu9250 axes_map parameter")
self.axes_map = [am[a.strip()] for a in axes_map]
self.data_rate = config.getint('rate', 4000)
if self.data_rate not in SAMPLE_RATE_DIVS:
raise config.error("Invalid rate parameter: %d" % (self.data_rate,))
# Measurement storage (accessed from background thread)
self.lock = threading.Lock()
self.raw_samples = []
# Setup mcu sensor_mpu9250 bulk query code
self.i2c = bus.MCU_I2C_from_config(config,
default_addr=MPU9250_ADDR,
default_speed=400000)
self.mcu = mcu = self.i2c.get_mcu()
self.oid = oid = mcu.create_oid()
self.query_mpu9250_cmd = self.query_mpu9250_end_cmd = None
self.query_mpu9250_status_cmd = None
mcu.register_config_callback(self._build_config)
mcu.register_response(self._handle_mpu9250_data, "mpu9250_data", oid)
# Clock tracking
self.last_sequence = self.max_query_duration = 0
self.last_limit_count = self.last_error_count = 0
self.clock_sync = adxl345.ClockSyncRegression(self.mcu, 640)
# API server endpoints
self.api_dump = motion_report.APIDumpHelper(
self.printer, self._api_update, self._api_startstop, 0.100)
self.name = config.get_name().split()[-1]
wh = self.printer.lookup_object('webhooks')
wh.register_mux_endpoint("mpu9250/dump_mpu9250", "sensor", self.name,
self._handle_dump_mpu9250)
def _build_config(self):
cmdqueue = self.i2c.get_command_queue()
self.mcu.add_config_cmd("config_mpu9250 oid=%d i2c_oid=%d"
% (self.oid, self.i2c.get_oid()))
self.mcu.add_config_cmd("query_mpu9250 oid=%d clock=0 rest_ticks=0"
% (self.oid,), on_restart=True)
self.query_mpu9250_cmd = self.mcu.lookup_command(
"query_mpu9250 oid=%c clock=%u rest_ticks=%u", cq=cmdqueue)
self.query_mpu9250_end_cmd = self.mcu.lookup_query_command(
"query_mpu9250 oid=%c clock=%u rest_ticks=%u",
"mpu9250_status oid=%c clock=%u query_ticks=%u next_sequence=%hu"
" buffered=%c fifo=%u limit_count=%hu", oid=self.oid, cq=cmdqueue)
self.query_mpu9250_status_cmd = self.mcu.lookup_query_command(
"query_mpu9250_status oid=%c",
"mpu9250_status oid=%c clock=%u query_ticks=%u next_sequence=%hu"
" buffered=%c fifo=%u limit_count=%hu", oid=self.oid, cq=cmdqueue)
def read_reg(self, reg):
params = self.i2c.i2c_read([reg], 1)
return bytearray(params['response'])[0]
def set_reg(self, reg, val, minclock=0):
self.i2c.i2c_write([reg, val & 0xFF], minclock=minclock)
# Measurement collection
def is_measuring(self):
return self.query_rate > 0
def _handle_mpu9250_data(self, params):
with self.lock:
self.raw_samples.append(params)
def _extract_samples(self, raw_samples):
# Load variables to optimize inner loop below
(x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map
last_sequence = self.last_sequence
time_base, chip_base, inv_freq = self.clock_sync.get_time_translation()
# Process every message in raw_samples
count = seq = 0
samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK)
for params in raw_samples:
seq_diff = (last_sequence - params['sequence']) & 0xffff
seq_diff -= (seq_diff & 0x8000) << 1
seq = last_sequence - seq_diff
d = bytearray(params['data'])
msg_cdiff = seq * SAMPLES_PER_BLOCK - chip_base
for i in range(len(d) // BYTES_PER_SAMPLE):
d_xyz = d[i*BYTES_PER_SAMPLE:(i+1)*BYTES_PER_SAMPLE]
xhigh, xlow, yhigh, ylow, zhigh, zlow = d_xyz
# Merge and perform twos-complement
rx = ((xhigh << 8) | xlow) - ((xhigh & 0x80) << 9)
ry = ((yhigh << 8) | ylow) - ((yhigh & 0x80) << 9)
rz = ((zhigh << 8) | zlow) - ((zhigh & 0x80) << 9)
raw_xyz = (rx, ry, rz)
x = round(raw_xyz[x_pos] * x_scale, 6)
y = round(raw_xyz[y_pos] * y_scale, 6)
z = round(raw_xyz[z_pos] * z_scale, 6)
ptime = round(time_base + (msg_cdiff + i) * inv_freq, 6)
samples[count] = (ptime, x, y, z)
count += 1
self.clock_sync.set_last_chip_clock(seq * SAMPLES_PER_BLOCK + i)
del samples[count:]
return samples
def _update_clock(self, minclock=0):
# Query current state
for retry in range(5):
params = self.query_mpu9250_status_cmd.send([self.oid],
minclock=minclock)
fifo = params['fifo'] & 0x1fff
if fifo <= FIFO_SIZE:
break
else:
raise self.printer.command_error("Unable to query mpu9250 fifo")
mcu_clock = self.mcu.clock32_to_clock64(params['clock'])
sequence = (self.last_sequence & ~0xffff) | params['next_sequence']
if sequence < self.last_sequence:
sequence += 0x10000
self.last_sequence = sequence
buffered = params['buffered']
limit_count = (self.last_limit_count & ~0xffff) | params['limit_count']
if limit_count < self.last_limit_count:
limit_count += 0x10000
self.last_limit_count = limit_count
duration = params['query_ticks']
if duration > self.max_query_duration:
# Skip measurement as a high query time could skew clock tracking
self.max_query_duration = max(2 * self.max_query_duration,
self.mcu.seconds_to_clock(.000005))
return
self.max_query_duration = 2 * duration
msg_count = (sequence * SAMPLES_PER_BLOCK
+ buffered // BYTES_PER_SAMPLE + fifo)
# The "chip clock" is the message counter plus .5 for average
# inaccuracy of query responses and plus .5 for assumed offset
# of mpu9250 hw processing time.
chip_clock = msg_count + 1
self.clock_sync.update(mcu_clock + duration // 2, chip_clock)
def _start_measurements(self):
if self.is_measuring():
return
# In case of miswiring, testing MPU9250 device ID prevents treating
# noise or wrong signal as a correctly initialized device
dev_id = self.read_reg(REG_DEVID)
if dev_id != MPU9250_DEV_ID and dev_id != MPU6050_DEV_ID:
raise self.printer.command_error(
"Invalid mpu9250/mpu6050 id (got %x).\n"
"This is generally indicative of connection problems\n"
"(e.g. faulty wiring) or a faulty chip."
% (dev_id))
# Setup chip in requested query rate
self.set_reg(REG_PWR_MGMT_1, SET_PWR_MGMT_1_WAKE)
self.set_reg(REG_PWR_MGMT_2, SET_PWR_MGMT_2_ACCEL_ON)
time.sleep(20. / 1000) # wait for accelerometer chip wake up
self.set_reg(REG_SMPLRT_DIV, SAMPLE_RATE_DIVS[self.data_rate])
self.set_reg(REG_CONFIG, SET_CONFIG)
self.set_reg(REG_ACCEL_CONFIG, SET_ACCEL_CONFIG)
self.set_reg(REG_ACCEL_CONFIG2, SET_ACCEL_CONFIG2)
# Setup samples
with self.lock:
self.raw_samples = []
# Start bulk reading
systime = self.printer.get_reactor().monotonic()
print_time = self.mcu.estimated_print_time(systime) + MIN_MSG_TIME
reqclock = self.mcu.print_time_to_clock(print_time)
rest_ticks = self.mcu.seconds_to_clock(1. / self.data_rate)
self.query_rate = self.data_rate
self.query_mpu9250_cmd.send([self.oid, reqclock, rest_ticks],
reqclock=reqclock)
logging.info("MPU9250 starting '%s' measurements", self.name)
# Initialize clock tracking
self.last_sequence = 0
self.last_limit_count = self.last_error_count = 0
self.clock_sync.reset(reqclock, 0)
self.max_query_duration = 1 << 31
self._update_clock(minclock=reqclock)
self.max_query_duration = 1 << 31
def _finish_measurements(self):
if not self.is_measuring():
return
# Halt bulk reading
params = self.query_mpu9250_end_cmd.send([self.oid, 0, 0])
self.query_rate = 0
with self.lock:
self.raw_samples = []
logging.info("MPU9250 finished '%s' measurements", self.name)
self.set_reg(REG_PWR_MGMT_1, SET_PWR_MGMT_1_SLEEP)
self.set_reg(REG_PWR_MGMT_2, SET_PWR_MGMT_2_OFF)
# API interface
def _api_update(self, eventtime):
self._update_clock()
with self.lock:
raw_samples = self.raw_samples
self.raw_samples = []
if not raw_samples:
return {}
samples = self._extract_samples(raw_samples)
if not samples:
return {}
return {'data': samples, 'errors': self.last_error_count,
'overflows': self.last_limit_count}
def _api_startstop(self, is_start):
if is_start:
self._start_measurements()
else:
self._finish_measurements()
def _handle_dump_mpu9250(self, web_request):
self.api_dump.add_client(web_request)
hdr = ('time', 'x_acceleration', 'y_acceleration', 'z_acceleration')
web_request.send({'header': hdr})
def start_internal_client(self):
cconn = self.api_dump.add_internal_client()
return adxl345.AccelQueryHelper(self.printer, cconn)
def load_config(config):
return MPU9250(config)
def load_config_prefix(config):
return MPU9250(config)

View File

@ -544,13 +544,15 @@ class Palette2:
self.cmd_Disconnect() self.cmd_Disconnect()
return self.reactor.NEVER return self.reactor.NEVER
if len(raw_bytes): if len(raw_bytes):
text_buffer = self.read_buffer + str(raw_bytes.decode()) new_buffer = str(raw_bytes.decode(encoding='UTF-8',
errors='ignore'))
text_buffer = self.read_buffer + new_buffer
while True: while True:
i = text_buffer.find("\n") i = text_buffer.find("\n")
if i >= 0: if i >= 0:
line = text_buffer[0:i+1] line = text_buffer[0:i + 1]
self.read_queue.put(line.strip()) self.read_queue.put(line.strip())
text_buffer = text_buffer[i+1:] text_buffer = text_buffer[i + 1:]
else: else:
break break
self.read_buffer = text_buffer self.read_buffer = text_buffer
@ -566,7 +568,7 @@ class Palette2:
heartbeat_strings = [COMMAND_HEARTBEAT, "Connection Okay"] heartbeat_strings = [COMMAND_HEARTBEAT, "Connection Okay"]
if not any(x in text_line for x in heartbeat_strings): if not any(x in text_line for x in heartbeat_strings):
logging.debug("%0.3f P2 -> : %s" %(eventtime, text_line)) logging.debug("%0.3f P2 -> : %s" % (eventtime, text_line))
# Received a heartbeat from the device # Received a heartbeat from the device
if text_line == COMMAND_HEARTBEAT: if text_line == COMMAND_HEARTBEAT:
@ -646,5 +648,6 @@ class Palette2:
status["ping"] = self.omega_pings[-1] status["ping"] = self.omega_pings[-1]
return status return status
def load_config(config): def load_config(config):
return Palette2(config) return Palette2(config)

View File

@ -147,15 +147,21 @@ class ResonanceTester:
(chip_axis, self.printer.lookup_object(chip_name)) (chip_axis, self.printer.lookup_object(chip_name))
for chip_axis, chip_name in self.accel_chip_names] for chip_axis, chip_name in self.accel_chip_names]
def _run_test(self, gcmd, axes, helper, raw_name_suffix=None): def _run_test(self, gcmd, axes, helper, raw_name_suffix=None,
accel_chips=None, test_point=None):
toolhead = self.printer.lookup_object('toolhead') toolhead = self.printer.lookup_object('toolhead')
calibration_data = {axis: None for axis in axes} calibration_data = {axis: None for axis in axes}
self.test.prepare_test(gcmd) self.test.prepare_test(gcmd)
if test_point is not None:
test_points = [test_point]
else:
test_points = self.test.get_start_test_points() test_points = self.test.get_start_test_points()
for point in test_points: for point in test_points:
toolhead.manual_move(point, self.move_speed) toolhead.manual_move(point, self.move_speed)
if len(test_points) > 1: if len(test_points) > 1 or test_point is not None:
gcmd.respond_info( gcmd.respond_info(
"Probing point (%.3f, %.3f, %.3f)" % tuple(point)) "Probing point (%.3f, %.3f, %.3f)" % tuple(point))
for axis in axes: for axis in axes:
@ -165,29 +171,36 @@ class ResonanceTester:
gcmd.respond_info("Testing axis %s" % axis.get_name()) gcmd.respond_info("Testing axis %s" % axis.get_name())
raw_values = [] raw_values = []
if accel_chips is None:
for chip_axis, chip in self.accel_chips: for chip_axis, chip in self.accel_chips:
if axis.matches(chip_axis): if axis.matches(chip_axis):
aclient = chip.start_internal_client() aclient = chip.start_internal_client()
raw_values.append((chip_axis, aclient)) raw_values.append((chip_axis, aclient, chip.name))
else:
for chip in accel_chips:
aclient = chip.start_internal_client()
raw_values.append((axis, aclient, chip.name))
# Generate moves # Generate moves
self.test.run_test(axis, gcmd) self.test.run_test(axis, gcmd)
for chip_axis, aclient in raw_values: for chip_axis, aclient, chip_name in raw_values:
aclient.finish_measurements() aclient.finish_measurements()
if raw_name_suffix is not None: if raw_name_suffix is not None:
raw_name = self.get_filename( raw_name = self.get_filename(
'raw_data', raw_name_suffix, axis, 'raw_data', raw_name_suffix, axis,
point if len(test_points) > 1 else None) point if len(test_points) > 1 else None,
chip_name if accel_chips is not None else None,)
aclient.write_to_file(raw_name) aclient.write_to_file(raw_name)
gcmd.respond_info( gcmd.respond_info(
"Writing raw accelerometer data to " "Writing raw accelerometer data to "
"%s file" % (raw_name,)) "%s file" % (raw_name,))
if helper is None: if helper is None:
continue continue
for chip_axis, aclient in raw_values: for chip_axis, aclient, chip_name in raw_values:
if not aclient.has_valid_samples(): if not aclient.has_valid_samples():
raise gcmd.error( raise gcmd.error(
"%s-axis accelerometer measured no data" % ( "accelerometer '%s' measured no data" % (
chip_axis,)) chip_name,))
new_data = helper.process_accelerometer_data(aclient) new_data = helper.process_accelerometer_data(aclient)
if calibration_data[axis] is None: if calibration_data[axis] is None:
calibration_data[axis] = new_data calibration_data[axis] = new_data
@ -198,6 +211,28 @@ class ResonanceTester:
def cmd_TEST_RESONANCES(self, gcmd): def cmd_TEST_RESONANCES(self, gcmd):
# Parse parameters # Parse parameters
axis = _parse_axis(gcmd, gcmd.get("AXIS").lower()) axis = _parse_axis(gcmd, gcmd.get("AXIS").lower())
accel_chips = gcmd.get("CHIPS", None)
test_point = gcmd.get("POINT", None)
if test_point:
test_coords = test_point.split(',')
if len(test_coords) != 3:
raise gcmd.error("Invalid POINT parameter, must be 'x,y,z'")
try:
test_point = [float(p.strip()) for p in test_coords]
except ValueError:
raise gcmd.error("Invalid POINT parameter, must be 'x,y,z'"
" where x, y and z are valid floating point numbers")
if accel_chips:
parsed_chips = []
for chip_name in accel_chips.split(','):
if "adxl345" in chip_name:
chip_lookup_name = chip_name.strip()
else:
chip_lookup_name = "adxl345 " + chip_name.strip();
chip = self.printer.lookup_object(chip_lookup_name)
parsed_chips.append(chip)
outputs = gcmd.get("OUTPUT", "resonances").lower().split(',') outputs = gcmd.get("OUTPUT", "resonances").lower().split(',')
for output in outputs: for output in outputs:
@ -221,10 +256,13 @@ class ResonanceTester:
data = self._run_test( data = self._run_test(
gcmd, [axis], helper, gcmd, [axis], helper,
raw_name_suffix=name_suffix if raw_output else None)[axis] raw_name_suffix=name_suffix if raw_output else None,
accel_chips=parsed_chips if accel_chips else None,
test_point=test_point)[axis]
if csv_output: if csv_output:
csv_name = self.save_calibration_data('resonances', name_suffix, csv_name = self.save_calibration_data('resonances', name_suffix,
helper, axis, data) helper, axis, data,
point=test_point)
gcmd.respond_info( gcmd.respond_info(
"Resonances data written to %s file" % (csv_name,)) "Resonances data written to %s file" % (csv_name,))
cmd_SHAPER_CALIBRATE_help = ( cmd_SHAPER_CALIBRATE_help = (
@ -287,7 +325,8 @@ class ResonanceTester:
for chip_axis, aclient in raw_values: for chip_axis, aclient in raw_values:
if not aclient.has_valid_samples(): if not aclient.has_valid_samples():
raise gcmd.error( raise gcmd.error(
"%s-axis accelerometer measured no data" % (chip_axis,)) "%s-axis accelerometer measured no data" % (
chip_axis,))
data = helper.process_accelerometer_data(aclient) data = helper.process_accelerometer_data(aclient)
vx = data.psd_x.mean() vx = data.psd_x.mean()
vy = data.psd_y.mean() vy = data.psd_y.mean()
@ -299,18 +338,22 @@ class ResonanceTester:
def is_valid_name_suffix(self, name_suffix): def is_valid_name_suffix(self, name_suffix):
return name_suffix.replace('-', '').replace('_', '').isalnum() return name_suffix.replace('-', '').replace('_', '').isalnum()
def get_filename(self, base, name_suffix, axis=None, point=None): def get_filename(self, base, name_suffix, axis=None,
point=None, chip_name=None):
name = base name = base
if axis: if axis:
name += '_' + axis.get_name() name += '_' + axis.get_name()
if chip_name:
name += '_' + chip_name.replace(" ", "_")
if point: if point:
name += "_%.3f_%.3f_%.3f" % (point[0], point[1], point[2]) name += "_%.3f_%.3f_%.3f" % (point[0], point[1], point[2])
name += '_' + name_suffix name += '_' + name_suffix
return os.path.join("/tmp", name + ".csv") return os.path.join("/tmp", name + ".csv")
def save_calibration_data(self, base_name, name_suffix, shaper_calibrate, def save_calibration_data(self, base_name, name_suffix, shaper_calibrate,
axis, calibration_data, all_shapers=None): axis, calibration_data,
output = self.get_filename(base_name, name_suffix, axis) all_shapers=None, point=None):
output = self.get_filename(base_name, name_suffix, axis, point)
shaper_calibrate.save_calibration_data(output, calibration_data, shaper_calibrate.save_calibration_data(output, calibration_data,
all_shapers) all_shapers)
return output return output

View File

@ -10,6 +10,10 @@ respond_types = {
'error' : '!!', 'error' : '!!',
} }
respond_types_no_space = {
'echo_no_space': 'echo:',
}
class HostResponder: class HostResponder:
def __init__(self, config): def __init__(self, config):
self.printer = config.get_printer() self.printer = config.get_printer()
@ -26,18 +30,25 @@ class HostResponder:
gcmd.respond_raw("%s %s" % (self.default_prefix, msg)) gcmd.respond_raw("%s %s" % (self.default_prefix, msg))
cmd_RESPOND_help = ("Echo the message prepended with a prefix") cmd_RESPOND_help = ("Echo the message prepended with a prefix")
def cmd_RESPOND(self, gcmd): def cmd_RESPOND(self, gcmd):
no_space = False
respond_type = gcmd.get('TYPE', None) respond_type = gcmd.get('TYPE', None)
prefix = self.default_prefix prefix = self.default_prefix
if(respond_type != None): if(respond_type != None):
respond_type = respond_type.lower() respond_type = respond_type.lower()
if(respond_type in respond_types): if(respond_type in respond_types):
prefix = respond_types[respond_type] prefix = respond_types[respond_type]
elif(respond_type in respond_types_no_space):
prefix = respond_types_no_space[respond_type]
no_space = True
else: else:
raise gcmd.error( raise gcmd.error(
"RESPOND TYPE '%s' is invalid. Must be one" "RESPOND TYPE '%s' is invalid. Must be one"
" of 'echo', 'command', or 'error'" % (respond_type,)) " of 'echo', 'command', or 'error'" % (respond_type,))
prefix = gcmd.get('PREFIX', prefix) prefix = gcmd.get('PREFIX', prefix)
msg = gcmd.get('MSG', '') msg = gcmd.get('MSG', '')
if(no_space):
gcmd.respond_raw("%s%s" % (prefix, msg))
else:
gcmd.respond_raw("%s %s" % (prefix, msg)) gcmd.respond_raw("%s %s" % (prefix, msg))
def load_config(config): def load_config(config):

View File

@ -72,6 +72,7 @@ class PrinterTemperatureMCU:
('stm32f070', self.config_stm32f070), ('stm32f070', self.config_stm32f070),
('stm32f072', self.config_stm32f0x2), ('stm32f072', self.config_stm32f0x2),
('stm32g0', self.config_stm32g0), ('stm32g0', self.config_stm32g0),
('stm32h7', self.config_stm32h7),
('', self.config_unknown)] ('', self.config_unknown)]
for name, func in cfg_funcs: for name, func in cfg_funcs:
if self.mcu_type.startswith(name): if self.mcu_type.startswith(name):
@ -143,6 +144,11 @@ class PrinterTemperatureMCU:
cal_adc_130 = self.read16(0x1FFF75CA) * 3.0 / (3.3 * 4095.) cal_adc_130 = self.read16(0x1FFF75CA) * 3.0 / (3.3 * 4095.)
self.slope = (130. - 30.) / (cal_adc_130 - cal_adc_30) self.slope = (130. - 30.) / (cal_adc_130 - cal_adc_30)
self.base_temperature = self.calc_base(30., cal_adc_30) self.base_temperature = self.calc_base(30., cal_adc_30)
def config_stm32h7(self):
cal_adc_30 = self.read16(0x1FF1E820) / 65535.
cal_adc_110 = self.read16(0x1FF1E840) / 65535.
self.slope = (110. - 30.) / (cal_adc_110 - cal_adc_30)
self.base_temperature = self.calc_base(30., cal_adc_30)
def read16(self, addr): def read16(self, addr):
params = self.debug_read_cmd.send([1, addr]) params = self.debug_read_cmd.send([1, addr])
return params['val'] return params['val']

View File

@ -99,6 +99,8 @@ class TuningTower:
self.gcode.respond_info("Ending tuning test mode") self.gcode.respond_info("Ending tuning test mode")
self.gcode_move.set_move_transform(self.normal_transform, force=True) self.gcode_move.set_move_transform(self.normal_transform, force=True)
self.normal_transform = None self.normal_transform = None
def is_active(self):
return self.normal_transform is not None
def load_config(config): def load_config(config):
return TuningTower(config) return TuningTower(config)

View File

@ -9,22 +9,27 @@ VALID_GCODE_EXTS = ['gcode', 'g', 'gco']
class VirtualSD: class VirtualSD:
def __init__(self, config): def __init__(self, config):
printer = config.get_printer() self.printer = config.get_printer()
printer.register_event_handler("klippy:shutdown", self.handle_shutdown) self.printer.register_event_handler("klippy:shutdown",
self.handle_shutdown)
# sdcard state # sdcard state
sd = config.get('path') sd = config.get('path')
self.sdcard_dirname = os.path.normpath(os.path.expanduser(sd)) self.sdcard_dirname = os.path.normpath(os.path.expanduser(sd))
self.current_file = None self.current_file = None
self.file_position = self.file_size = 0 self.file_position = self.file_size = 0
# Print Stat Tracking # Print Stat Tracking
self.print_stats = printer.load_object(config, 'print_stats') self.print_stats = self.printer.load_object(config, 'print_stats')
# Work timer # Work timer
self.reactor = printer.get_reactor() self.reactor = self.printer.get_reactor()
self.must_pause_work = self.cmd_from_sd = False self.must_pause_work = self.cmd_from_sd = False
self.next_file_position = 0 self.next_file_position = 0
self.work_timer = None self.work_timer = None
# Error handling
gcode_macro = self.printer.load_object(config, 'gcode_macro')
self.on_error_gcode = gcode_macro.load_template(
config, 'on_error_gcode', '')
# Register commands # Register commands
self.gcode = printer.lookup_object('gcode') self.gcode = self.printer.lookup_object('gcode')
for cmd in ['M20', 'M21', 'M23', 'M24', 'M25', 'M26', 'M27']: for cmd in ['M20', 'M21', 'M23', 'M24', 'M25', 'M26', 'M27']:
self.gcode.register_command(cmd, getattr(self, 'cmd_' + cmd)) self.gcode.register_command(cmd, getattr(self, 'cmd_' + cmd))
for cmd in ['M28', 'M29', 'M30']: for cmd in ['M28', 'M29', 'M30']:
@ -125,6 +130,7 @@ class VirtualSD:
self.current_file = None self.current_file = None
self.file_position = self.file_size = 0. self.file_position = self.file_size = 0.
self.print_stats.reset() self.print_stats.reset()
self.printer.send_event("virtual_sdcard:reset_file")
cmd_SDCARD_RESET_FILE_help = "Clears a loaded SD File. Stops the print "\ cmd_SDCARD_RESET_FILE_help = "Clears a loaded SD File. Stops the print "\
"if necessary" "if necessary"
def cmd_SDCARD_RESET_FILE(self, gcmd): def cmd_SDCARD_RESET_FILE(self, gcmd):
@ -258,6 +264,10 @@ class VirtualSD:
self.gcode.run_script(line) self.gcode.run_script(line)
except self.gcode.error as e: except self.gcode.error as e:
error_message = str(e) error_message = str(e)
try:
self.gcode.run_script(self.on_error_gcode.render())
except:
logging.exception("virtual_sdcard on_error")
break break
except: except:
logging.exception("virtual_sdcard dispatch") logging.exception("virtual_sdcard dispatch")

View File

@ -150,6 +150,7 @@ class DeltaKinematics:
'homed_axes': '' if self.need_home else 'xyz', 'homed_axes': '' if self.need_home else 'xyz',
'axis_minimum': self.axes_min, 'axis_minimum': self.axes_min,
'axis_maximum': self.axes_max, 'axis_maximum': self.axes_max,
'cone_start_z': self.limit_z,
} }
def get_calibration(self): def get_calibration(self):
endstops = [rail.get_homing_info().position_endstop endstops = [rail.get_homing_info().position_endstop

View File

@ -231,8 +231,7 @@ class Printer:
run_result = self.run_result run_result = self.run_result
try: try:
if run_result == 'firmware_restart': if run_result == 'firmware_restart':
for n, m in self.lookup_objects(module='mcu'): self.send_event("klippy:firmware_restart")
m.microcontroller_restart()
self.send_event("klippy:disconnect") self.send_event("klippy:disconnect")
except: except:
logging.exception("Unhandled exception during post run") logging.exception("Unhandled exception during post run")
@ -342,7 +341,7 @@ def main():
start_args['log_file'] = options.logfile start_args['log_file'] = options.logfile
bglogger = queuelogger.setup_bg_logging(options.logfile, debuglevel) bglogger = queuelogger.setup_bg_logging(options.logfile, debuglevel)
else: else:
logging.basicConfig(level=debuglevel) logging.getLogger().setLevel(debuglevel)
logging.info("Starting Klippy...") logging.info("Starting Klippy...")
start_args['software_version'] = util.get_git_version() start_args['software_version'] = util.get_git_version()
start_args['cpu_info'] = util.get_cpu_info() start_args['cpu_info'] = util.get_cpu_info()

View File

@ -563,6 +563,7 @@ class MCU:
self._restart_method = config.getchoice('restart_method', self._restart_method = config.getchoice('restart_method',
rmethods, None) rmethods, None)
self._reset_cmd = self._config_reset_cmd = None self._reset_cmd = self._config_reset_cmd = None
self._is_mcu_bridge = False
self._emergency_stop_cmd = None self._emergency_stop_cmd = None
self._is_shutdown = self._is_timeout = False self._is_shutdown = self._is_timeout = False
self._shutdown_clock = 0 self._shutdown_clock = 0
@ -589,9 +590,11 @@ class MCU:
self._mcu_tick_stddev = 0. self._mcu_tick_stddev = 0.
self._mcu_tick_awake = 0. self._mcu_tick_awake = 0.
# Register handlers # Register handlers
printer.register_event_handler("klippy:connect", self._connect) printer.register_event_handler("klippy:firmware_restart",
self._firmware_restart)
printer.register_event_handler("klippy:mcu_identify", printer.register_event_handler("klippy:mcu_identify",
self._mcu_identify) self._mcu_identify)
printer.register_event_handler("klippy:connect", self._connect)
printer.register_event_handler("klippy:shutdown", self._shutdown) printer.register_event_handler("klippy:shutdown", self._shutdown)
printer.register_event_handler("klippy:disconnect", self._disconnect) printer.register_event_handler("klippy:disconnect", self._disconnect)
# Serial callbacks # Serial callbacks
@ -797,6 +800,10 @@ class MCU:
mbaud = msgparser.get_constant('SERIAL_BAUD', None) mbaud = msgparser.get_constant('SERIAL_BAUD', None)
if self._restart_method is None and mbaud is None and not ext_only: if self._restart_method is None and mbaud is None and not ext_only:
self._restart_method = 'command' self._restart_method = 'command'
if msgparser.get_constant('CANBUS_BRIDGE', 0):
self._is_mcu_bridge = True
self._printer.register_event_handler("klippy:firmware_restart",
self._firmware_restart_bridge)
version, build_versions = msgparser.get_version_info() version, build_versions = msgparser.get_version_info()
self._get_status_info['mcu_version'] = version self._get_status_info['mcu_version'] = version
self._get_status_info['mcu_build_versions'] = build_versions self._get_status_info['mcu_build_versions'] = build_versions
@ -914,7 +921,9 @@ class MCU:
chelper.run_hub_ctrl(0) chelper.run_hub_ctrl(0)
self._reactor.pause(self._reactor.monotonic() + 2.) self._reactor.pause(self._reactor.monotonic() + 2.)
chelper.run_hub_ctrl(1) chelper.run_hub_ctrl(1)
def microcontroller_restart(self): def _firmware_restart(self, force=False):
if self._is_mcu_bridge and not force:
return
if self._restart_method == 'rpi_usb': if self._restart_method == 'rpi_usb':
self._restart_rpi_usb() self._restart_rpi_usb()
elif self._restart_method == 'command': elif self._restart_method == 'command':
@ -923,6 +932,8 @@ class MCU:
self._restart_cheetah() self._restart_cheetah()
else: else:
self._restart_arduino() self._restart_arduino()
def _firmware_restart_bridge(self):
self._firmware_restart(True)
# Misc external commands # Misc external commands
def is_fileoutput(self): def is_fileoutput(self):
return self._printer.get_start_args().get('debugoutput') is not None return self._printer.get_start_args().get('debugoutput') is not None

View File

@ -50,9 +50,10 @@ class ReactorCallback:
return self.reactor.NEVER return self.reactor.NEVER
class ReactorFileHandler: class ReactorFileHandler:
def __init__(self, fd, callback): def __init__(self, fd, read_callback, write_callback):
self.fd = fd self.fd = fd
self.callback = callback self.read_callback = read_callback
self.write_callback = write_callback
def fileno(self): def fileno(self):
return self.fd return self.fd
@ -107,7 +108,8 @@ class SelectReactor:
self._pipe_fds = None self._pipe_fds = None
self._async_queue = queue.Queue() self._async_queue = queue.Queue()
# File descriptors # File descriptors
self._fds = [] self._read_fds = []
self._write_fds = []
# Greenlets # Greenlets
self._g_dispatch = None self._g_dispatch = None
self._greenlets = [] self._greenlets = []
@ -236,12 +238,26 @@ class SelectReactor:
def mutex(self, is_locked=False): def mutex(self, is_locked=False):
return ReactorMutex(self, is_locked) return ReactorMutex(self, is_locked)
# File descriptors # File descriptors
def register_fd(self, fd, callback): def register_fd(self, fd, read_callback, write_callback=None):
file_handler = ReactorFileHandler(fd, callback) file_handler = ReactorFileHandler(fd, read_callback, write_callback)
self._fds.append(file_handler) self.set_fd_wake(file_handle, True, False)
return file_handler return file_handler
def unregister_fd(self, file_handler): def unregister_fd(self, file_handler):
self._fds.pop(self._fds.index(file_handler)) if file_handler in self._read_fds:
self._read_fds.pop(self._read_fds.index(file_handler))
if file_handler in self._write_fds:
self._write_fds.pop(self._write_fds.index(file_handler))
def set_fd_wake(self, file_handler, is_readable=True, is_writeable=False):
if file_hander in self._read_fds:
if not is_readable:
self._read_fds.pop(self._read_fds.index(file_handler))
elif is_readable:
self._read_fds.append(file_handler)
if file_hander in self._write_fds:
if not is_writeable:
self._write_fds.pop(self._write_fds.index(file_handler))
elif is_writeable:
self._write_fds.append(file_handler)
# Main loop # Main loop
def _dispatch_loop(self): def _dispatch_loop(self):
self._g_dispatch = g_dispatch = greenlet.getcurrent() self._g_dispatch = g_dispatch = greenlet.getcurrent()
@ -250,11 +266,18 @@ class SelectReactor:
while self._process: while self._process:
timeout = self._check_timers(eventtime, busy) timeout = self._check_timers(eventtime, busy)
busy = False busy = False
res = select.select(self._fds, [], [], timeout) res = select.select(self._read_fds, self.write_fds, [], timeout)
eventtime = self.monotonic() eventtime = self.monotonic()
for fd in res[0]: for fd in res[0]:
busy = True busy = True
fd.callback(eventtime) fd.read_callback(eventtime)
if g_dispatch is not self._g_dispatch:
self._end_greenlet(g_dispatch)
eventtime = self.monotonic()
break
for fd in res[1]:
busy = True
fd.write_callback(eventtime)
if g_dispatch is not self._g_dispatch: if g_dispatch is not self._g_dispatch:
self._end_greenlet(g_dispatch) self._end_greenlet(g_dispatch)
eventtime = self.monotonic() eventtime = self.monotonic()
@ -289,10 +312,10 @@ class PollReactor(SelectReactor):
self._poll = select.poll() self._poll = select.poll()
self._fds = {} self._fds = {}
# File descriptors # File descriptors
def register_fd(self, fd, callback): def register_fd(self, fd, read_callback, write_callback=None):
file_handler = ReactorFileHandler(fd, callback) file_handler = ReactorFileHandler(fd, read_callback, write_callback)
fds = self._fds.copy() fds = self._fds.copy()
fds[fd] = callback fds[fd] = file_handler
self._fds = fds self._fds = fds
self._poll.register(file_handler, select.POLLIN | select.POLLHUP) self._poll.register(file_handler, select.POLLIN | select.POLLHUP)
return file_handler return file_handler
@ -301,6 +324,13 @@ class PollReactor(SelectReactor):
fds = self._fds.copy() fds = self._fds.copy()
del fds[file_handler.fd] del fds[file_handler.fd]
self._fds = fds self._fds = fds
def set_fd_wake(self, file_handler, is_readable=True, is_writeable=False):
flags = select.POLLHUP
if is_readable:
flags |= select.POLLIN
if is_writeable:
flags |= select.POLLOUT
self._poll.modify(file_handler, flags)
# Main loop # Main loop
def _dispatch_loop(self): def _dispatch_loop(self):
self._g_dispatch = g_dispatch = greenlet.getcurrent() self._g_dispatch = g_dispatch = greenlet.getcurrent()
@ -313,7 +343,14 @@ class PollReactor(SelectReactor):
eventtime = self.monotonic() eventtime = self.monotonic()
for fd, event in res: for fd, event in res:
busy = True busy = True
self._fds[fd](eventtime) if event & (select.POLLIN | select.POLLHUP):
self._fds[fd].read_callback(eventtime)
if g_dispatch is not self._g_dispatch:
self._end_greenlet(g_dispatch)
eventtime = self.monotonic()
break
if event & select.POLLOUT:
self._fds[fd].write_callback(eventtime)
if g_dispatch is not self._g_dispatch: if g_dispatch is not self._g_dispatch:
self._end_greenlet(g_dispatch) self._end_greenlet(g_dispatch)
eventtime = self.monotonic() eventtime = self.monotonic()
@ -326,8 +363,8 @@ class EPollReactor(SelectReactor):
self._epoll = select.epoll() self._epoll = select.epoll()
self._fds = {} self._fds = {}
# File descriptors # File descriptors
def register_fd(self, fd, callback): def register_fd(self, fd, read_callback, write_callback=None):
file_handler = ReactorFileHandler(fd, callback) file_handler = ReactorFileHandler(fd, read_callback, write_callback)
fds = self._fds.copy() fds = self._fds.copy()
fds[fd] = callback fds[fd] = callback
self._fds = fds self._fds = fds
@ -338,6 +375,13 @@ class EPollReactor(SelectReactor):
fds = self._fds.copy() fds = self._fds.copy()
del fds[file_handler.fd] del fds[file_handler.fd]
self._fds = fds self._fds = fds
def set_fd_wake(self, file_handler, is_readable=True, is_writeable=False):
flags = select.POLLHUP
if is_readable:
flags |= select.EPOLLIN
if is_writeable:
flags |= select.EPOLLOUT
self._epoll.modify(file_handler, flags)
# Main loop # Main loop
def _dispatch_loop(self): def _dispatch_loop(self):
self._g_dispatch = g_dispatch = greenlet.getcurrent() self._g_dispatch = g_dispatch = greenlet.getcurrent()
@ -350,7 +394,14 @@ class EPollReactor(SelectReactor):
eventtime = self.monotonic() eventtime = self.monotonic()
for fd, event in res: for fd, event in res:
busy = True busy = True
self._fds[fd](eventtime) if event & (select.EPOLLIN | select.EPOLLHUP):
self._fds[fd].read_callback(eventtime)
if g_dispatch is not self._g_dispatch:
self._end_greenlet(g_dispatch)
eventtime = self.monotonic()
break
if event & select.EPOLLOUT:
self._fds[fd].write_callback(eventtime)
if g_dispatch is not self._g_dispatch: if g_dispatch is not self._g_dispatch:
self._end_greenlet(g_dispatch) self._end_greenlet(g_dispatch)
eventtime = self.monotonic() eventtime = self.monotonic()

View File

@ -12,7 +12,6 @@ class error(Exception):
pass pass
class SerialReader: class SerialReader:
BITS_PER_BYTE = 10.
def __init__(self, reactor, warn_prefix=""): def __init__(self, reactor, warn_prefix=""):
self.reactor = reactor self.reactor = reactor
self.warn_prefix = warn_prefix self.warn_prefix = warn_prefix
@ -97,11 +96,13 @@ class SerialReader:
self.msgparser = msgparser self.msgparser = msgparser
self.register_response(self.handle_unknown, '#unknown') self.register_response(self.handle_unknown, '#unknown')
# Setup baud adjust # Setup baud adjust
mcu_baud = msgparser.get_constant_float('SERIAL_BAUD', None) if serial_fd_type == b'c':
if mcu_baud is not None: wire_freq = msgparser.get_constant_float('CANBUS_FREQUENCY', None)
baud_adjust = self.BITS_PER_BYTE / mcu_baud else:
self.ffi_lib.serialqueue_set_baud_adjust( wire_freq = msgparser.get_constant_float('SERIAL_BAUD', None)
self.serialqueue, baud_adjust) if wire_freq is not None:
self.ffi_lib.serialqueue_set_wire_frequency(self.serialqueue,
wire_freq)
receive_window = msgparser.get_constant_int('RECEIVE_WINDOW', None) receive_window = msgparser.get_constant_int('RECEIVE_WINDOW', None)
if receive_window is not None: if receive_window is not None:
self.ffi_lib.serialqueue_set_receive_window( self.ffi_lib.serialqueue_set_receive_window(

View File

@ -162,6 +162,16 @@ class ServerSocket:
def pop_client(self, client_id): def pop_client(self, client_id):
self.clients.pop(client_id, None) self.clients.pop(client_id, None)
def stats(self, eventtime):
# Called once per second - check for idle clients
for client in list(self.clients.values()):
if client.is_blocking:
client.blocking_count -= 1
if client.blocking_count < 0:
logging.info("Closing unresponsive client %s", client.uid)
client.close()
return False, ""
class ClientConnection: class ClientConnection:
def __init__(self, server, sock): def __init__(self, server, sock):
self.printer = server.printer self.printer = server.printer
@ -171,9 +181,10 @@ class ClientConnection:
self.uid = id(self) self.uid = id(self)
self.sock = sock self.sock = sock
self.fd_handle = self.reactor.register_fd( self.fd_handle = self.reactor.register_fd(
self.sock.fileno(), self.process_received) self.sock.fileno(), self.process_received, self._do_send)
self.partial_data = self.send_buffer = b"" self.partial_data = self.send_buffer = b""
self.is_sending_data = False self.is_blocking = False
self.blocking_count = 0
self.set_client_info("?", "New connection") self.set_client_info("?", "New connection")
self.request_log = collections.deque([], REQUEST_LOG_SIZE) self.request_log = collections.deque([], REQUEST_LOG_SIZE)
@ -259,33 +270,29 @@ class ClientConnection:
def send(self, data): def send(self, data):
jmsg = json.dumps(data, separators=(',', ':')) jmsg = json.dumps(data, separators=(',', ':'))
self.send_buffer += jmsg.encode() + b"\x03" self.send_buffer += jmsg.encode() + b"\x03"
if not self.is_sending_data: if not self.is_blocking:
self.is_sending_data = True self._do_send()
self.reactor.register_callback(self._do_send)
def _do_send(self, eventtime): def _do_send(self, eventtime=None):
retries = 10 if self.fd_handle is None:
while self.send_buffer: return
try: try:
sent = self.sock.send(self.send_buffer) sent = self.sock.send(self.send_buffer)
except socket.error as e: except socket.error as e:
if e.errno == errno.EBADF or e.errno == errno.EPIPE \ if e.errno not in [errno.EAGAIN, errno.EWOULDBLOCK]:
or not retries: logging.info("webhooks: socket write error %d" % (self.uid,))
sent = 0
else:
retries -= 1
waketime = self.reactor.monotonic() + .001
self.reactor.pause(waketime)
continue
retries = 10
if sent > 0:
self.send_buffer = self.send_buffer[sent:]
else:
logging.info(
"webhooks: Error sending server data, closing socket")
self.close() self.close()
break return
self.is_sending_data = False sent = 0
if sent < len(self.send_buffer):
if not self.is_blocking:
self.reactor.set_fd_wake(self.fd_handle, False, True)
self.is_blocking = True
self.blocking_count = 5
elif self.is_blocking:
self.reactor.set_fd_wake(self.fd_handle, True, False)
self.is_blocking = False
self.send_buffer = self.send_buffer[sent:]
class WebHooks: class WebHooks:
def __init__(self, printer): def __init__(self, printer):
@ -375,6 +382,9 @@ class WebHooks:
state_message, state = self.printer.get_state_message() state_message, state = self.printer.get_state_message()
return {'state': state, 'state_message': state_message} return {'state': state, 'state_message': state_message}
def stats(self, eventtime):
return self.sconn.stats(eventtime)
def call_remote_method(self, method, **kwargs): def call_remote_method(self, method, **kwargs):
if method not in self._remote_methods: if method not in self._remote_methods:
raise self.printer.command_error( raise self.printer.command_error(

View File

@ -125,6 +125,10 @@ callbacks.
The canboot directory contains code from: The canboot directory contains code from:
https://github.com/Arksine/CanBoot https://github.com/Arksine/CanBoot
revision ae687a404d0edb2724a084f78e565dbc7dd505aa. The Python module, revision 870200826561b150137913d42fd7edc6515229ff. The Python module,
flash_can.py, is taken from the repo's scripts directory. It may be flash_can.py, is taken from the repo's scripts directory. It may be
used to upload firmware to devices flashed with the CanBoot bootloader. used to upload firmware to devices flashed with the CanBoot bootloader.
The can2040 directory contains code from:
https://github.com/KevinOConnor/can2040
revision 9ca095c939a48391de60dd353f0cd91999bb9257.

1258
lib/can2040/can2040.c Normal file

File diff suppressed because it is too large Load Diff

79
lib/can2040/can2040.h Normal file
View File

@ -0,0 +1,79 @@
#ifndef _CAN2040_H
#define _CAN2040_H
#include <stdint.h> // uint32_t
struct can2040_msg {
uint32_t id;
uint32_t dlc;
union {
uint8_t data[8];
uint32_t data32[2];
};
};
enum {
CAN2040_ID_RTR = 1<<30,
CAN2040_ID_EFF = 1<<31,
};
enum {
CAN2040_NOTIFY_RX = 1<<20,
CAN2040_NOTIFY_TX = 1<<21,
CAN2040_NOTIFY_ERROR = 1<<23,
};
struct can2040;
typedef void (*can2040_rx_cb)(struct can2040 *cd, uint32_t notify
, struct can2040_msg *msg);
void can2040_setup(struct can2040 *cd, uint32_t pio_num);
void can2040_callback_config(struct can2040 *cd, can2040_rx_cb rx_cb);
void can2040_start(struct can2040 *cd, uint32_t sys_clock, uint32_t bitrate
, uint32_t gpio_rx, uint32_t gpio_tx);
void can2040_shutdown(struct can2040 *cd);
void can2040_pio_irq_handler(struct can2040 *cd);
int can2040_check_transmit(struct can2040 *cd);
int can2040_transmit(struct can2040 *cd, struct can2040_msg *msg);
/****************************************************************
* Internal definitions
****************************************************************/
struct can2040_bitunstuffer {
uint32_t stuffed_bits, count_stuff;
uint32_t unstuffed_bits, count_unstuff;
};
struct can2040_transmit {
struct can2040_msg msg;
uint32_t crc, stuffed_words, stuffed_data[5];
};
struct can2040 {
// Setup
uint32_t pio_num;
void *pio_hw;
uint32_t gpio_rx, gpio_tx;
can2040_rx_cb rx_cb;
// Bit unstuffing
struct can2040_bitunstuffer unstuf;
uint32_t raw_bit_count;
// Input data state
uint32_t parse_state;
uint32_t parse_crc;
struct can2040_msg parse_msg;
// Reporting
uint32_t report_state;
uint32_t report_eof_key;
// Transmits
uint32_t tx_state;
uint32_t tx_pull_pos, tx_push_pos;
struct can2040_transmit tx_queue[4];
};
#endif // can2040.h

View File

@ -496,9 +496,70 @@ class CanSocket:
self._loop.remove_reader(self.cansock.fileno()) self._loop.remove_reader(self.cansock.fileno())
self.cansock.close() self.cansock.close()
class SerialSocket:
def __init__(self, loop: asyncio.AbstractEventLoop):
self._loop = loop
self.serial = self.serial_error = None
self.node = CanNode(0, self)
def _handle_response(self) -> None:
try:
data = self.serial.read(4096)
except self.serial_error as e:
logging.exception("Error on serial read")
self.close()
self.node.feed_data(data)
def send(self, can_id: int, payload: bytes = b"") -> None:
try:
self.serial.write(payload)
except self.serial_error as e:
logging.exception("Error on serial write")
self.close()
async def run(self, intf: str, baud: int, fw_path: pathlib.Path) -> None:
if not fw_path.is_file():
raise FlashCanError("Invalid firmware path '%s'" % (fw_path))
import serial
self.serial_error = serial.SerialException
try:
serial_dev = serial.Serial(baudrate=baud, timeout=0,
exclusive=True)
serial_dev.port = intf
serial_dev.open()
except (OSError, IOError, self.serial_error) as e:
raise FlashCanError("Unable to open serial port: %s" % (e,))
self.serial = serial_dev
self._loop.add_reader(self.serial.fileno(), self._handle_response)
flasher = CanFlasher(self.node, fw_path)
try:
await flasher.connect_btl()
await flasher.send_file()
await flasher.verify_file()
finally:
# always attempt to send the complete command. If
# there is an error it will exit the bootloader
# unless comms were broken
await flasher.finish()
def close(self):
if self.serial is None:
return
self._loop.remove_reader(self.serial.fileno())
self.serial.close()
self.serial = None
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Can Bootloader Flash Utility") description="Can Bootloader Flash Utility")
parser.add_argument(
"-d", "--device", metavar='<serial device>',
help="Serial Device"
)
parser.add_argument(
"-b", "--baud", default=250000, metavar='<baud rate>',
help="Serial baud rate"
)
parser.add_argument( parser.add_argument(
"-i", "--interface", default="can0", metavar='<can interface>', "-i", "--interface", default="can0", metavar='<can interface>',
help="Can Interface" help="Can Interface"
@ -522,27 +583,37 @@ def main():
args = parser.parse_args() args = parser.parse_args()
if not args.verbose: if not args.verbose:
logging.getLogger().setLevel(logging.CRITICAL) logging.getLogger().setLevel(logging.ERROR)
intf = args.interface intf = args.interface
fpath = pathlib.Path(args.firmware).expanduser().resolve() fpath = pathlib.Path(args.firmware).expanduser().resolve()
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
iscan = args.device is None
sock = None
try: try:
cansock = CanSocket(loop) if iscan:
sock = CanSocket(loop)
if args.query: if args.query:
loop.run_until_complete(cansock.run_query(intf)) loop.run_until_complete(sock.run_query(intf))
else: else:
if args.uuid is None: if args.uuid is None:
raise FlashCanError( raise FlashCanError(
"The 'uuid' option must be specified to flash a device" "The 'uuid' option must be specified to flash a device"
) )
uuid = int(args.uuid, 16) uuid = int(args.uuid, 16)
loop.run_until_complete(cansock.run(intf, uuid, fpath)) loop.run_until_complete(sock.run(intf, uuid, fpath))
else:
if args.device is None:
raise FlashCanError(
"The 'device' option must be specified to flash a device"
)
sock = SerialSocket(loop)
loop.run_until_complete(sock.run(args.device, args.baud, fpath))
except Exception as e: except Exception as e:
logging.exception("Can Flash Error") logging.exception("Can Flash Error")
sys.exit(-1) sys.exit(-1)
finally: finally:
if cansock is not None: if sock is not None:
cansock.close() sock.close()
if args.query: if args.query:
output_line("Query Complete") output_line("Query Complete")
else: else:

View File

@ -16,7 +16,7 @@
// 4-byte checksum. Therefore code size cannot exceed 252 bytes. // 4-byte checksum. Therefore code size cannot exceed 252 bytes.
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#include "pico/asm_helper.S" //#include "pico/asm_helper.S"
#include "hardware/regs/addressmap.h" #include "hardware/regs/addressmap.h"
#include "hardware/regs/ssi.h" #include "hardware/regs/ssi.h"

View File

@ -1,3 +1,16 @@
diff --git a/lib/rp2040/boot_stage2/boot2_generic_03h.S b/lib/rp2040/boot_stage2/boot2_generic_03h.S
index a10e66abd..cc7e4fbc7 100644
--- a/lib/rp2040/boot_stage2/boot2_generic_03h.S
+++ b/lib/rp2040/boot_stage2/boot2_generic_03h.S
@@ -16,7 +16,7 @@
// 4-byte checksum. Therefore code size cannot exceed 252 bytes.
// ----------------------------------------------------------------------------
-#include "pico/asm_helper.S"
+//#include "pico/asm_helper.S"
#include "hardware/regs/addressmap.h"
#include "hardware/regs/ssi.h"
diff --git a/lib/rp2040/boot_stage2/boot2_w25q080.S b/lib/rp2040/boot_stage2/boot2_w25q080.S diff --git a/lib/rp2040/boot_stage2/boot2_w25q080.S b/lib/rp2040/boot_stage2/boot2_w25q080.S
index ad3238e2..8fb3def4 100644 index ad3238e2..8fb3def4 100644
--- a/lib/rp2040/boot_stage2/boot2_w25q080.S --- a/lib/rp2040/boot_stage2/boot2_w25q080.S

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# Tool to enter a USB bootloader and flash Klipper # Tool to enter a USB bootloader and flash Klipper
# #
# Copyright (C) 2019 Kevin O'Connor <kevin@koconnor.net> # Copyright (C) 2019 Kevin O'Connor <kevin@koconnor.net>
@ -27,6 +27,8 @@ def enter_bootloader(device):
# Translate a serial device name to a stable serial name in /dev/serial/by-path/ # Translate a serial device name to a stable serial name in /dev/serial/by-path/
def translate_serial_to_tty(device): def translate_serial_to_tty(device):
ttyname = os.path.realpath(device) ttyname = os.path.realpath(device)
if not os.path.exists('/dev/serial/by-path/'):
raise error("Unable to find serial 'by-path' folder")
for fname in os.listdir('/dev/serial/by-path/'): for fname in os.listdir('/dev/serial/by-path/'):
fname = '/dev/serial/by-path/' + fname fname = '/dev/serial/by-path/' + fname
if os.path.realpath(fname) == ttyname: if os.path.realpath(fname) == ttyname:
@ -71,6 +73,42 @@ def wait_path(path, alt_path=None):
if cur_time > end_time: if cur_time > end_time:
return path return path
CANBOOT_ID ="1d50:6177"
def detect_canboot(devpath):
usbdir = os.path.dirname(devpath)
try:
with open(os.path.join(usbdir, "idVendor")) as f:
vid = f.read().strip().lower()
with open(os.path.join(usbdir, "idProduct")) as f:
pid = f.read().strip().lower()
except Exception:
return False
usbid = "%s:%s" % (vid, pid)
return usbid == CANBOOT_ID
def call_flashcan(device, binfile):
try:
import serial
except ModuleNotFoundError:
sys.stderr.write(
"Python's pyserial module is required to update. Install\n"
"with the following command:\n"
" %s -m pip install pyserial\n\n" % (sys.executable,)
)
sys.exit(-1)
args = [sys.executable, "lib/canboot/flash_can.py", "-d",
device, "-f", binfile]
sys.stderr.write(" ".join(args) + '\n\n')
res = subprocess.call(args)
if res != 0:
sys.stderr.write("Error running flash_can.py\n")
sys.exit(-1)
def flash_canboot(options, binfile):
ttyname, pathname = translate_serial_to_tty(options.device)
call_flashcan(pathname, binfile)
# Flash via a call to bossac # Flash via a call to bossac
def flash_bossac(device, binfile, extra_flags=[]): def flash_bossac(device, binfile, extra_flags=[]):
ttyname, pathname = translate_serial_to_tty(device) ttyname, pathname = translate_serial_to_tty(device)
@ -108,9 +146,13 @@ def flash_dfuutil(device, binfile, extra_flags=[], sudo=True):
if hexfmt_r.match(device.strip()): if hexfmt_r.match(device.strip()):
call_dfuutil(["-d", ","+device.strip()] + extra_flags, binfile, sudo) call_dfuutil(["-d", ","+device.strip()] + extra_flags, binfile, sudo)
return return
ttyname, serbypath = translate_serial_to_tty(device)
buspath, devpath = translate_serial_to_usb_path(device) buspath, devpath = translate_serial_to_usb_path(device)
enter_bootloader(device) enter_bootloader(device)
pathname = wait_path(devpath) pathname = wait_path(devpath)
if detect_canboot(devpath):
call_flashcan(serbypath, binfile)
else:
call_dfuutil(["-p", buspath] + extra_flags, binfile, sudo) call_dfuutil(["-p", buspath] + extra_flags, binfile, sudo)
def call_hidflash(binfile, sudo): def call_hidflash(binfile, sudo):
@ -128,11 +170,41 @@ def flash_hidflash(device, binfile, sudo=True):
if hexfmt_r.match(device.strip()): if hexfmt_r.match(device.strip()):
call_hidflash(binfile, sudo) call_hidflash(binfile, sudo)
return return
ttyname, serbypath = translate_serial_to_tty(device)
buspath, devpath = translate_serial_to_usb_path(device) buspath, devpath = translate_serial_to_usb_path(device)
enter_bootloader(device) enter_bootloader(device)
pathname = wait_path(devpath) pathname = wait_path(devpath)
if detect_canboot(devpath):
call_flashcan(serbypath, binfile)
else:
call_hidflash(binfile, sudo) call_hidflash(binfile, sudo)
# Call Klipper modified "picoboot"
def call_picoboot(bus, addr, binfile, sudo):
args = ["lib/rp2040_flash/rp2040_flash", binfile]
if bus is not None:
args.extend([bus, addr])
if sudo:
args.insert(0, "sudo")
sys.stderr.write(" ".join(args) + '\n\n')
res = subprocess.call(args)
if res != 0:
raise error("Error running rp2040_flash")
# Flash via Klipper modified "picoboot"
def flash_picoboot(device, binfile, sudo):
buspath, devpath = translate_serial_to_usb_path(device)
# We need one level up to get access to busnum/devnum files
usbdir = os.path.dirname(devpath)
enter_bootloader(device)
wait_path(usbdir)
with open(usbdir + "/busnum") as f:
bus = f.read().strip()
with open(usbdir + "/devnum") as f:
addr = f.read().strip()
call_picoboot(bus, addr, binfile, sudo)
###################################################################### ######################################################################
# Device specific helpers # Device specific helpers
###################################################################### ######################################################################
@ -162,22 +234,6 @@ def flash_atsamd(options, binfile):
options.device, str(e))) options.device, str(e)))
sys.exit(-1) sys.exit(-1)
# Look for an rp2040 and flash it with rp2040_flash.
def rp2040_flash(devpath, binfile, sudo):
args = ["lib/rp2040_flash/rp2040_flash", binfile]
if len(devpath) > 0:
with open(devpath + "/busnum") as f:
bus = f.read().strip()
with open(devpath + "/devnum") as f:
addr = f.read().strip()
args += [bus, addr]
sys.stderr.write(" ".join(args) + '\n\n')
if sudo:
args.insert(0, "sudo")
res = subprocess.call(args)
if res != 0:
raise error("Error running rp2040_flash")
SMOOTHIE_HELP = """ SMOOTHIE_HELP = """
Failed to flash to %s: %s Failed to flash to %s: %s
@ -259,27 +315,22 @@ def flash_stm32f4(options, binfile):
RP2040_HELP = """ RP2040_HELP = """
Failed to flash to %s: %s Failed to flash to %s: %s
If the device is already in bootloader mode, use 'first' as FLASH_DEVICE. If the device is already in bootloader mode it can be flashed with the
This will use rp2040_flash to flash the first available rp2040. following command:
make flash FLASH_DEVICE=2e8a:0003
Alternatively, one can flash rp2040 boards like the Pico by manually Alternatively, one can flash rp2040 boards like the Pico by manually
entering bootloader mode(hold bootsel button during powerup), mount the entering bootloader mode(hold bootsel button during powerup), mount the
device as a usb drive, and copy klipper.uf2 to the device. device as a usb drive, and copy klipper.uf2 to the device.
""" """
def flash_rp2040(options, binfile): def flash_rp2040(options, binfile):
try: try:
if options.device.lower() == "first": if options.device.lower() == "2e8a:0003":
rp2040_flash("", binfile, options.sudo) call_picoboot(None, None, binfile, options.sudo)
return else:
flash_picoboot(options.device, binfile, options.sudo)
buspath, devpath = translate_serial_to_usb_path(options.device)
# We need one level up to get access to busnum/devnum files
devpath = os.path.dirname(devpath)
enter_bootloader(options.device)
wait_path(devpath)
rp2040_flash(devpath, binfile, options.sudo)
except error as e: except error as e:
sys.stderr.write(RP2040_HELP % (options.device, str(e))) sys.stderr.write(RP2040_HELP % (options.device, str(e)))
sys.exit(-1) sys.exit(-1)
@ -288,7 +339,8 @@ MCUTYPES = {
'sam3': flash_atsam3, 'sam4': flash_atsam4, 'samd': flash_atsamd, 'sam3': flash_atsam3, 'sam4': flash_atsam4, 'samd': flash_atsamd,
'same70': flash_atsam4, 'lpc176': flash_lpc176x, 'stm32f103': flash_stm32f1, 'same70': flash_atsam4, 'lpc176': flash_lpc176x, 'stm32f103': flash_stm32f1,
'stm32f4': flash_stm32f4, 'stm32f042': flash_stm32f4, 'stm32f4': flash_stm32f4, 'stm32f042': flash_stm32f4,
'stm32f072': flash_stm32f4, 'rp2040': flash_rp2040 'stm32f072': flash_stm32f4, 'stm32g0b1': flash_stm32f4,
'stm32h7': flash_stm32f4, 'rp2040': flash_rp2040
} }

View File

@ -181,14 +181,48 @@ def plot_system(data):
ax1.grid(True) ax1.grid(True)
return fig return fig
def plot_frequency(data, mcu): def plot_mcu_frequencies(data):
all_keys = {} all_keys = {}
for d in data: for d in data:
all_keys.update(d) all_keys.update(d)
one_mcu = mcu is not None
graph_keys = { key: ([], []) for key in all_keys graph_keys = { key: ([], []) for key in all_keys
if (key in ("freq", "adj") or (not one_mcu and ( if (key in ("freq", "adj")
key.endswith(":freq") or key.endswith(":adj")))) } or (key.endswith(":freq") or key.endswith(":adj"))) }
for d in data:
st = datetime.datetime.utcfromtimestamp(d['#sampletime'])
for key, (times, values) in graph_keys.items():
val = d.get(key)
if val not in (None, '0', '1'):
times.append(st)
values.append(float(val))
est_mhz = { key: round((sum(values)/len(values)) / 1000000.)
for key, (times, values) in graph_keys.items() }
# Build plot
fig, ax1 = matplotlib.pyplot.subplots()
ax1.set_title("MCU frequencies")
ax1.set_xlabel('Time')
ax1.set_ylabel('Microsecond deviation')
for key in sorted(graph_keys):
times, values = graph_keys[key]
mhz = est_mhz[key]
label = "%s(%dMhz)" % (key, mhz)
hz = mhz * 1000000.
ax1.plot_date(times, [(v - hz)/mhz for v in values], '.', label=label)
fontP = matplotlib.font_manager.FontProperties()
fontP.set_size('x-small')
ax1.legend(loc='best', prop=fontP)
ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M'))
ax1.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter('%d'))
ax1.grid(True)
return fig
def plot_mcu_frequency(data, mcu):
all_keys = {}
for d in data:
all_keys.update(d)
graph_keys = { key: ([], []) for key in all_keys
if key in ("freq", "adj") }
for d in data: for d in data:
st = datetime.datetime.utcfromtimestamp(d['#sampletime']) st = datetime.datetime.utcfromtimestamp(d['#sampletime'])
for key, (times, values) in graph_keys.items(): for key, (times, values) in graph_keys.items():
@ -199,10 +233,7 @@ def plot_frequency(data, mcu):
# Build plot # Build plot
fig, ax1 = matplotlib.pyplot.subplots() fig, ax1 = matplotlib.pyplot.subplots()
if one_mcu:
ax1.set_title("MCU '%s' frequency" % (mcu,)) ax1.set_title("MCU '%s' frequency" % (mcu,))
else:
ax1.set_title("MCU frequency")
ax1.set_xlabel('Time') ax1.set_xlabel('Time')
ax1.set_ylabel('Frequency') ax1.set_ylabel('Frequency')
for key in sorted(graph_keys): for key in sorted(graph_keys):
@ -286,7 +317,10 @@ def main():
if options.heater is not None: if options.heater is not None:
fig = plot_temperature(data, options.heater) fig = plot_temperature(data, options.heater)
elif options.frequency: elif options.frequency:
fig = plot_frequency(data, options.mcu) if options.mcu is not None:
fig = plot_mcu_frequency(data, options.mcu)
else:
fig = plot_mcu_frequencies(data)
elif options.system: elif options.system:
fig = plot_system(data) fig = plot_system(data)
else: else:

View File

@ -20,7 +20,7 @@ install_packages()
PKGLIST="${PKGLIST} avrdude gcc-avr binutils-avr avr-libc" PKGLIST="${PKGLIST} avrdude gcc-avr binutils-avr avr-libc"
# ARM chip installation and building # ARM chip installation and building
PKGLIST="${PKGLIST} stm32flash libnewlib-arm-none-eabi" PKGLIST="${PKGLIST} stm32flash libnewlib-arm-none-eabi"
PKGLIST="${PKGLIST} gcc-arm-none-eabi binutils-arm-none-eabi libusb-1.0" PKGLIST="${PKGLIST} gcc-arm-none-eabi binutils-arm-none-eabi libusb-1.0 pkg-config"
# Update system package info # Update system package info
report_status "Running apt-get update..." report_status "Running apt-get update..."

View File

@ -65,6 +65,16 @@ BOARD_DEFS = {
'mcu': 'stm32h743xx', 'mcu': 'stm32h743xx',
'spi_bus': 'spi3a', 'spi_bus': 'spi3a',
'cs_pin': 'PA15' 'cs_pin': 'PA15'
},
'monster8': {
'mcu': "stm32f407xx",
'spi_bus': "spi3a",
"cs_pin": "PC9"
},
'fly-gemini-v2': {
'mcu': "stm32f405xx",
'spi_bus': "spi1",
"cs_pin": "PA4"
} }
} }
@ -99,7 +109,9 @@ BOARD_ALIASES = {
'mks-robin-e3d': BOARD_DEFS['mks-robin-e3'], 'mks-robin-e3d': BOARD_DEFS['mks-robin-e3'],
'fysetc-spider-v1': BOARD_DEFS['fysetc-spider'], 'fysetc-spider-v1': BOARD_DEFS['fysetc-spider'],
'fysetc-s6-v1.2': BOARD_DEFS['fysetc-spider'], 'fysetc-s6-v1.2': BOARD_DEFS['fysetc-spider'],
'fysetc-s6-v2': BOARD_DEFS['fysetc-spider'] 'fysetc-s6-v2': BOARD_DEFS['fysetc-spider'],
'monster8': BOARD_DEFS['monster8'],
'robin_v3': BOARD_DEFS['monster8']
} }
def list_boards(): def list_boards():

View File

@ -42,6 +42,8 @@ source "src/linux/Kconfig"
source "src/simulator/Kconfig" source "src/simulator/Kconfig"
# Generic configuration options for serial ports # Generic configuration options for serial ports
config SERIAL
bool
config SERIAL_BAUD config SERIAL_BAUD
depends on SERIAL depends on SERIAL
int "Baud rate for serial port" if LOW_LEVEL_OPTIONS int "Baud rate for serial port" if LOW_LEVEL_OPTIONS
@ -51,28 +53,49 @@ config SERIAL_BAUD
to 250000. Read the FAQ before changing this value. to 250000. Read the FAQ before changing this value.
# Generic configuration options for USB # Generic configuration options for USB
config USBSERIAL
bool
config USBCANBUS
bool
config USB
bool
default y if USBSERIAL || USBCANBUS
config USB_VENDOR_ID config USB_VENDOR_ID
default 0x1d50 default 0x1d50
config USB_DEVICE_ID config USB_DEVICE_ID
default 0x614e default 0x614e
config USB_SERIAL_NUMBER_CHIPID config USB_SERIAL_NUMBER_CHIPID
depends on HAVE_CHIPID depends on USB && HAVE_CHIPID
default y default y
config USB_SERIAL_NUMBER config USB_SERIAL_NUMBER
default "12345" default "12345"
menu "USB ids" menu "USB ids"
depends on USBSERIAL && LOW_LEVEL_OPTIONS depends on USB && LOW_LEVEL_OPTIONS
config USB_VENDOR_ID config USB_VENDOR_ID
hex "USB vendor ID" hex "USB vendor ID" if USBSERIAL
config USB_DEVICE_ID config USB_DEVICE_ID
hex "USB device ID" hex "USB device ID" if USBSERIAL
config USB_SERIAL_NUMBER_CHIPID config USB_SERIAL_NUMBER_CHIPID
bool "USB serial number from CHIPID" if HAVE_CHIPID bool "USB serial number from CHIPID" if HAVE_CHIPID
config USB_SERIAL_NUMBER config USB_SERIAL_NUMBER
string "USB serial number" if !USB_SERIAL_NUMBER_CHIPID string "USB serial number" if !USB_SERIAL_NUMBER_CHIPID
endmenu endmenu
# Generic configuration options for CANbus
config CANSERIAL
bool
config CANBUS
bool
default y if CANSERIAL || USBCANBUS
config CANBUS_FREQUENCY
int "CAN bus speed" if LOW_LEVEL_OPTIONS && CANBUS
default 500000
config CANBUS_FILTER
bool
default y if CANSERIAL
# Support setting gpio state at startup
config INITIAL_PINS config INITIAL_PINS
string "GPIO pins to set at micro-controller startup" string "GPIO pins to set at micro-controller startup"
depends on LOW_LEVEL_OPTIONS depends on LOW_LEVEL_OPTIONS

View File

@ -9,5 +9,6 @@ src-$(CONFIG_HAVE_GPIO_I2C) += i2ccmds.c
src-$(CONFIG_HAVE_GPIO_UART) += uartcmds.c src-$(CONFIG_HAVE_GPIO_UART) += uartcmds.c
src-$(CONFIG_HAVE_GPIO_HARD_PWM) += pwmcmds.c src-$(CONFIG_HAVE_GPIO_HARD_PWM) += pwmcmds.c
bb-src-$(CONFIG_HAVE_GPIO_SPI) := spi_software.c sensor_adxl345.c sensor_angle.c bb-src-$(CONFIG_HAVE_GPIO_SPI) := spi_software.c sensor_adxl345.c sensor_angle.c
bb-src-$(CONFIG_HAVE_GPIO_I2C) += sensor_mpu9250.c
src-$(CONFIG_HAVE_GPIO_BITBANGING) += $(bb-src-y) lcd_st7920.c lcd_hd44780.c \ src-$(CONFIG_HAVE_GPIO_BITBANGING) += $(bb-src-y) lcd_st7920.c lcd_hd44780.c \
buttons.c tmcuart.c neopixel.c pulse_counter.c buttons.c tmcuart.c neopixel.c pulse_counter.c

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