Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Desuuuu 2021-06-17 15:57:31 +02:00
commit 46b86f0f6c
No known key found for this signature in database
GPG Key ID: 85943F4B2C2CE0DC
78 changed files with 2649 additions and 927 deletions

View File

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

View File

@ -9,6 +9,10 @@
# See docs/Config_Reference.md for a description of parameters.
# Note: The initial revision of this board has a flaw that can cause
# damage to itself and other boards. Be sure to verify the board is
# not impacted by this flaw before using it.
[stepper_x]
step_pin: PE2
dir_pin: PE1
@ -84,6 +88,10 @@ pin: PB7
#[heater_fan fan2]
#pin: PB5
[output_pin motor_power]
pin: PC13
value: 1
[mcu]
serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00
@ -104,7 +112,7 @@ aliases:
EXP1_1=PC5, EXP1_3=PB1, EXP1_5=PE10, EXP1_7=PE12, EXP1_9=<GND>,
EXP1_2=PB0, EXP1_4=PE9, EXP1_6=PE11, EXP1_8=PE13, EXP1_10=<5V>,
# EXP2 header
EXP2_1=PA6, EXP2_3=PE7, EXP2_5=PB2, EXP2_7=PC4, EXP2_9=<GND>,
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.

View File

@ -0,0 +1,124 @@
# This file contains common pin mappings for the BIGTREETECH SKR CR6
# V1.0. To use this config, the firmware should be compiled for the
# STM32F103 with a "28KiB bootloader" and USB communication. Also,
# select "Enable extra low-level configuration options" and configure
# "GPIO pins to set at micro-controller startup" to "!PA14".
# The "make flash" command does not work on the SKR CR6. 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
# CR6 V1.0 with that SD card.
# See docs/Config_Reference.md for a description of parameters.
[stepper_x]
step_pin: PB13
dir_pin: !PB12
enable_pin: !PB14
microsteps: 16
rotation_distance: 40
endstop_pin: PC0
position_min: -5
position_endstop: -5
position_max: 235
homing_speed: 50
[tmc2209 stepper_x]
uart_pin: PC11
tx_pin: PC10
run_current: 0.550
hold_current: 0.450
stealthchop_threshold: 999999
uart_address: 0
[stepper_y]
step_pin: PB10
dir_pin: PB2
enable_pin: !PB11
microsteps: 16
rotation_distance: 40
endstop_pin: PC1
position_min: -2
position_endstop: -2
position_max: 235
homing_speed: 50
[tmc2209 stepper_y]
uart_pin: PC11
tx_pin: PC10
uart_address: 2
run_current: 0.550
hold_current: 0.450
stealthchop_threshold: 999999
[stepper_z]
step_pin: PB0
dir_pin: !PC5
enable_pin: !PB1
microsteps: 16
rotation_distance: 8
endstop_pin: PC2
position_endstop: 0.0
position_min: -1.0
position_max: 250
[tmc2209 stepper_z]
uart_pin: PC11
tx_pin: PC10
uart_address: 1
run_current: 0.550
hold_current: 0.450
stealthchop_threshold: 999999
[extruder]
step_pin: PB3
dir_pin: !PB4
enable_pin: !PD2
microsteps: 16
rotation_distance: 30.4768
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PC8
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PA0
control: pid
pid_Kp: 14.32
pid_Ki: 0.81
pid_Kd: 63.12
min_temp: 0
max_temp: 275
[tmc2209 extruder]
uart_pin: PC11
tx_pin: PC10
uart_address: 3
run_current: 0.600
hold_current: 0.400
stealthchop_threshold: 999999
[heater_bed]
heater_pin: PC9
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PC3
control: pid
pid_Kp: 79.49
pid_Ki: 1.17
pid_Kd: 1349.52
min_temp: 0
max_temp: 120
[fan]
pin: PC6
[mcu]
serial: /dev/serial/by-id/usb-Klipper_stm32f103xe_000000000000000000000000-if00
[printer]
kinematics: cartesian
max_velocity: 500
max_accel: 500
max_z_velocity: 5
max_z_accel: 100
[static_digital_output usb_pullup_enable]
pins: !PA14

View File

@ -126,7 +126,7 @@ pins: !PA14
[board_pins]
aliases:
# EXP1 header
EXP1_1=PB5, EXP1_3=PA9, EXP1_5=PA10, EXP1_7=PB8, EXP1_9=<GND>,
EXP1_1=PB5, EXP1_3=PA9, EXP1_5=PA10, EXP1_7=PB8, EXP1_9=<GND>,
EXP1_2=PA15, EXP1_4=<RST>, EXP1_6=PB9, EXP1_8=PB15, EXP1_10=<5V>
# See the sample-lcd.cfg file for definitions of common LCD displays.

View File

@ -129,7 +129,7 @@ pins: !PA14
[board_pins]
aliases:
# EXP1 header
EXP1_1=PB5, EXP1_3=PA9, EXP1_5=PA10, EXP1_7=PB8, EXP1_9=<GND>,
EXP1_1=PB5, EXP1_3=PA9, EXP1_5=PA10, EXP1_7=PB8, EXP1_9=<GND>,
EXP1_2=PA15, EXP1_4=<RST>, EXP1_6=PB9, EXP1_8=PB15, EXP1_10=<5V>
# See the sample-lcd.cfg file for definitions of common LCD displays.

View File

@ -90,3 +90,9 @@ max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100
[board_pins]
aliases:
EXP1_1=PC6,EXP1_3=PB10,EXP1_5=PB14,EXP1_7=PB12,EXP1_9=<GND>
EXP1_2=PB2,EXP1_4=PB11,EXP1_6=PB13,EXP1_8=PB15,EXP1_10=<5V>
PROBE_IN=PB0,PROBE_OUT=PB1,FIL_RUNOUT=PC6

View File

@ -0,0 +1,148 @@
# This file contains common pin mappings for the Duet3 Mini 5+. To use
# this config, the firmware should be compiled for the ATSAMD51P20
# with a "25Mhz crystal", "16KiB bootloader", and USB communication.
# To flash the board, double tap the board's reset button to enter the
# bootloader and then run: make flash FLASH_DEVICE=/dev/ttyACM0
# See docs/Config_Reference.md for a description of parameters.
# Pins for reference:
# Driver Step Pins - 0:PC26, 1:PC25, 2:PC24, 3:PC19, 4:PC16, 5:PC30, 6:PC18
# Driver Dir pins - 0:PB3, 1:PB29, 2:PB28, 3:PD20, 4:PD21, 5:PB0, 6:PA27
# Driver Enable - !PC28
# Uart addresses - 0:0 1:1 2:2 3:3 4:!0 5:!1 6:!2 | "!" is for inverted select pin
# Thermistor Pins - T0:PC0, T1:PC1, T2:PC2
# Vssa Sense:PB4 | Vref Sense:PB5
# Current Sense resistor for drivers - .076ohm
# SPI lines:{PD11, PC7} -> Shared SerCom#7, SPIMosi:PC12, SPIMiso:PC15, SPISCLK:PC13
# Vin Monitor:PC3, uses 11:1 voltage divider
# LED's - Diag:PA31, Act:PA30
# 12864 LCD - LCDCSPIN:PC6, ENCA:PC11, ENCB:PD1, ENCSW:PB9, LCD A0:PA2, LCDBeep:PA0, LCD Neopixel Out:PB12 (shared with IO3.out)
# Neopixel Out - PA8
# Serial0 - TX:PB25, RX:PB24 (USB)
# Serial1 - TX:PB31, RX:PB30
# SBC SPISS pin:PA6, SBCTfrReady:PA3, SerComPins:{PA4, PA5, PA6, PA7}
# 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
# 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}
# Driver Diag - {D0:PA10, D1:PB8, D2:PA22, D3:PA23, D4:PC21, D5:PB10, D6:PA27}
# Mux Pin - PD0
# EXP headers only support 12864 LCD's
[stepper_x]
#driver0
step_pin: PC26
dir_pin: !PB3
enable_pin: !PC28
microsteps: 16
rotation_distance: 40
endstop_pin: ^PC31
position_endstop: 0
position_max: 450
[tmc2209 stepper_x]
uart_pin: PA1
tx_pin: PA0
select_pins: PD0
uart_address: 0
run_current: 1
sense_resistor: 0.056
[stepper_y]
#driver1
step_pin: PC25
dir_pin: PB29
enable_pin: !PC28
microsteps: 16
rotation_distance: 40
endstop_pin: ^PC4
position_endstop: 0
position_max: 450
[tmc2209 stepper_y]
uart_pin: PA1
tx_pin: PA0
select_pins: PD0
uart_address: 1
run_current: 1
sense_resistor: 0.056
[stepper_z]
#driver2
step_pin: PC24
dir_pin: PB28
enable_pin: !PC28
microsteps: 16
rotation_distance: 8
endstop_pin: PC5
position_endstop: 0
position_max: 400
[tmc2209 stepper_z]
uart_pin: PA1
tx_pin: PA0
select_pins: PD0
uart_address: 2
run_current: 1
sense_resistor: 0.056
[adc_scaled vref_scaled]
vref_pin: PB5
vssa_pin: PB4
[extruder]
#driver3
step_pin: PC19
dir_pin: PD20
enable_pin: !PC28
microsteps: 16
rotation_distance: 33.500
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PB13 # out2
sensor_type: ATC Semitec 104GT-2
pullup_resistor: 2200
sensor_pin: vref_scaled:PC1
control: pid
pid_Kp: 30.089
pid_Ki: 2.229
pid_Kd: 101.550
min_temp: 0
max_temp: 285
[tmc2209 extruder]
uart_pin: PA1
tx_pin: PA0
uart_address: 3
select_pins: PD0
run_current: 1
sense_resistor: 0.056
[heater_bed]
heater_pin: PB17 #out1
sensor_type: NTC 100K beta 3950
sensor_pin: vref_scaled:PC0
control: pid
pullup_resistor: 2200
pid_Kp: 61.049
pid_Ki: 2.339
pid_Kd: 398.344
min_temp: 0
max_temp: 130
[heater_fan heatbreak_fan]
pin: PB11
[fan]
pin: PA11
[mcu]
serial: /dev/ttyACM0
[printer]
kinematics: cartesian
max_velocity: 350
max_accel: 3000

View File

@ -0,0 +1,159 @@
# This file contains common configurations and pin mappings for the Prusa Buddy
# board. The LCD is not currently supported by Klipper, so the touchscreen will
# permanently display the bootloader screen after the Klipper firmware is flashed;
# use Fluidd, Mainsail, or OctoPrint etc. to control the printer.
# To use this config, the firmware should be compiled for the STM32F407. When
# running "make menuconfig", enable "extra low-level configuration setup",
# select the "128KiB + 512 byte offset" bootloader, and USB communication.
# Connect the printer to your Raspberry Pi using the printer's micro-USB port.
# If you prefer to remove Prusa's stock bootloader entirely, select the
# "No bootloader" option.
# When flashing for the first time, you will need to break the "appendix"
# on the Buddy board, then put the device into DFU mode by moving the jumper
# on the 3-pin header (older boards) or shorting the 2-pin header (newer boards)
# and resetting, and finally use "make flash" to install Klipper. Once Klipper is
# installed, you no longer need the jumper - just use "make flash" which will
# automatically put the device into DFU mode.
# Note that if you were previously running Prusa firmware, you must fully
# power cycle the board after flashing. Otherwise, Klipper will be unable to
# communicate with the TMC2209s due to the abrupt change in the baud rate,
# and will show this error: "Unable to read tmc uart register IFCNT".
# See docs/Config_Reference.md for a description of parameters.
[stepper_x]
step_pin: PD1
dir_pin: PD0
enable_pin: !PD3
microsteps: 16
rotation_distance: 32
endstop_pin: tmc2209_stepper_x:virtual_endstop
position_endstop: 200
position_min: 0
position_max: 200
homing_speed: 50
homing_retract_dist: 0
[stepper_y]
step_pin: PD13
dir_pin: PD12
enable_pin: !PD14
microsteps: 16
rotation_distance: 32
endstop_pin: tmc2209_stepper_y:virtual_endstop
position_endstop: 0
position_min: 0
position_max: 200
homing_speed: 50
homing_retract_dist: 0
[stepper_z]
step_pin: PD4
dir_pin: !PD15
enable_pin: !PD2
microsteps: 16
rotation_distance: 4
endstop_pin: probe:z_virtual_endstop
position_min: 0
position_max: 200
[extruder]
step_pin: PD9
dir_pin: !PD8
enable_pin: !PD10
microsteps: 16
rotation_distance: 33.500
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PB1
sensor_type: ATC Semitec 104GT-2
sensor_pin: PC0
control: pid
pid_Kp: 7
pid_Ki: 0.5
pid_Kd: 45
min_temp: 10
max_temp: 305
[tmc2209 stepper_x]
uart_pin: PD5
uart_address: 1
diag_pin: ^PE2
driver_SGTHRS: 130
run_current: 0.35
sense_resistor: 0.22
stealthchop_threshold: 999999
[tmc2209 stepper_y]
uart_pin: PD5
uart_address: 3
diag_pin: ^PE1
driver_SGTHRS: 130
run_current: 0.35
sense_resistor: 0.22
stealthchop_threshold: 999999
[tmc2209 stepper_z]
uart_pin: PD5
uart_address: 0
diag_pin: ^PE3
driver_SGTHRS: 100
run_current: 0.35
sense_resistor: 0.22
stealthchop_threshold: 999999
[tmc2209 extruder]
uart_pin: PD5
uart_address: 2
diag_pin: ^PA15
driver_SGTHRS: 100
run_current: 0.4
sense_resistor: 0.22
[heater_bed]
heater_pin: PB0
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PA4
control: pid
pid_Kp: 120
pid_Ki: 1.5
pid_Kd: 600
min_temp: 10
max_temp: 110
# Hotend fan.
[heater_fan hotend_fan]
pin: PE9
tachometer_pin: PE14
# Part cooling fan.
[fan]
pin: PE11
tachometer_pin: PE10
# The SuperPINDA has built-in temperature compensation and no thermistor output,
# so no compensation table is needed. The PINDA thermistor is otherwise on pin PA6.
[probe]
pin: PA8
x_offset: -29
y_offset: -3
z_offset: 0
speed: 6.0
[filament_switch_sensor filament_sensor]
switch_pin: ^PB4
pause_on_runout: True
[mcu]
serial: /dev/serial/by-id/usb-Klipper_stm32f407xx_3100380013504E4E53353420-if00
restart_method: command
[printer]
kinematics: cartesian
max_velocity: 180
max_accel: 1250
max_z_velocity: 12
max_z_accel: 400

View File

@ -0,0 +1,129 @@
# This file contains common pin mappings for the TH3D EZBoard Lite v1.2.
# To use this config, the firmware should be compiled for the
# LPC1769 with USB communication.
# The "make flash" command does not work on this board. 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 board
# with that SD card.
# See docs/Config_Reference.md for a description of parameters.
[mcu]
serial: /dev/serial/by-id/usb-Klipper_lpc1768_00000000000000000000000000000000-if00
[printer]
kinematics: cartesian
max_velocity: 500
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100
[stepper_x]
step_pin: P2.0
dir_pin: P1.16
enable_pin: !P1.17
microsteps: 16
rotation_distance: 40
endstop_pin: ^P1.24
position_endstop: 0
position_max: 350
[tmc2208 stepper_x]
uart_pin: P0.5
tx_pin: P0.4
run_current: 0.600
hold_current: 0.500
stealthchop_threshold: 999999
[stepper_y]
step_pin: P2.1
dir_pin: P1.10
enable_pin: !P1.9
microsteps: 16
rotation_distance: 40
endstop_pin: ^P1.25
position_endstop: 0
position_max: 350
[tmc2208 stepper_y]
uart_pin: P0.11
tx_pin: P0.10
run_current: 0.600
hold_current: 0.500
stealthchop_threshold: 999999
[stepper_z]
step_pin: P2.2
dir_pin: P1.15
enable_pin: !P1.14
microsteps: 16
rotation_distance: 8
endstop_pin: ^P1.26
position_endstop: 0.5
position_max: 400
[tmc2208 stepper_z]
uart_pin: P0.20
tx_pin: P0.19
run_current: 0.700
hold_current: 0.600
stealthchop_threshold: 999999
[extruder]
step_pin: P2.3
dir_pin: P1.4
enable_pin: !P1.8
microsteps: 16
rotation_distance: 34.406
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: P2.7
sensor_type: EPCOS 100K B57560G104F
sensor_pin: P0.23
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 210
[tmc2208 extruder]
uart_pin: P0.21
tx_pin: P0.22
run_current: 0.800
hold_current: 0.700
stealthchop_threshold: 999999
[heater_bed]
heater_pin: P2.5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: P0.24
control: pid
pid_Kp: 54.027
pid_Ki: 0.770
pid_Kd: 948.182
min_temp: 0
max_temp: 130
[fan]
pin: P2.6
#[bltouch]
#sensor_pin: P1.26
#control_pin: P2.4
#[filament_switch_sensor my_sensor]
#switch_pin: P1.27
########################################
# EXP1 / EXP2 (display) pins
########################################
[board_pins]
aliases:
# EXP1 header
EXP1_1=P1.31, EXP1_3=P3.26, EXP1_5=P3.25, EXP1_7=P0.16, EXP1_9=<GND>,
EXP1_2=P1.30, EXP1_4=<RST>, EXP1_6=P0.15, EXP1_8=P0.18, EXP1_10=<5V>
# See the sample-lcd.cfg file for definitions of common LCD displays.

View File

@ -0,0 +1,184 @@
# This file contains common configurations and pin mappings for the Prusa
# Mini+, which uses the Prusa Buddy board. The printer's LCD is not currently
# supported by Klipper, so the touchscreen will permanently display the
# bootloader screen after the Klipper firmware is flashed; use Fluidd, Mainsail,
# or OctoPrint etc. to control the printer.
# To use this config, the firmware should be compiled for the STM32F407. When
# running "make menuconfig", enable "extra low-level configuration setup",
# select the "128KiB + 512 byte offset" bootloader, and USB communication.
# Connect the printer to your Raspberry Pi using the printer's micro-USB port.
# If you prefer to remove Prusa's stock bootloader entirely, select the
# "No bootloader" option.
# When flashing for the first time, you will need to break the "appendix"
# on the Buddy board, then put the device into DFU mode by moving the jumper
# on the 3-pin header (older boards) or shorting the 2-pin header (newer boards)
# and resetting, and finally use "make flash" to install Klipper. Once Klipper is
# installed, you no longer need the jumper - just use "make flash" which will
# automatically put the device into DFU mode.
# Note that if you were previously running Prusa firmware, you must fully
# power cycle the board after flashing. Otherwise, Klipper will be unable to
# communicate with the TMC2209s due to the abrupt change in the baud rate,
# and will show this error: "Unable to read tmc uart register IFCNT".
# See docs/Config_Reference.md for a description of parameters.
[stepper_x]
step_pin: PD1
dir_pin: PD0
enable_pin: !PD3
microsteps: 16
rotation_distance: 32 # 200 * 16 / 100
endstop_pin: tmc2209_stepper_x:virtual_endstop
position_endstop: 180.4
position_min: -2
position_max: 180.4
homing_speed: 50
homing_retract_dist: 0
[stepper_y]
step_pin: PD13
dir_pin: PD12
enable_pin: !PD14
microsteps: 16
rotation_distance: 32 # 200 * 16 / 100
endstop_pin: tmc2209_stepper_y:virtual_endstop
position_endstop: -3
position_min: -3
position_max: 180
homing_speed: 50
homing_retract_dist: 0
[stepper_z]
step_pin: PD4
dir_pin: !PD15
enable_pin: !PD2
microsteps: 16
rotation_distance: 4
endstop_pin: probe:z_virtual_endstop
position_min: 0
position_max: 185
[extruder]
step_pin: PD9
dir_pin: !PD8
enable_pin: !PD10
microsteps: 16
rotation_distance: 9.84615 # 200 * 16 / 325
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PB1
sensor_type: ATC Semitec 104GT-2
sensor_pin: PC0
control: pid
# Prusa's firmware defaults.
pid_Kp: 7
pid_Ki: 0.5
pid_Kd: 45
min_temp: 10
max_temp: 305
[tmc2209 stepper_x]
uart_pin: PD5
uart_address: 1
diag_pin: ^PE2
driver_SGTHRS: 130
run_current: 0.35
sense_resistor: 0.22
stealthchop_threshold: 999999
[tmc2209 stepper_y]
uart_pin: PD5
uart_address: 3
diag_pin: ^PE1
driver_SGTHRS: 130
run_current: 0.35
sense_resistor: 0.22
stealthchop_threshold: 999999
[tmc2209 stepper_z]
uart_pin: PD5
uart_address: 0
diag_pin: ^PE3
driver_SGTHRS: 100
run_current: 0.35
sense_resistor: 0.22
stealthchop_threshold: 999999
[tmc2209 extruder]
uart_pin: PD5
uart_address: 2
diag_pin: ^PA15
driver_SGTHRS: 100
run_current: 0.4
sense_resistor: 0.22
[heater_bed]
heater_pin: PB0
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PA4
control: pid
# Prusa's firmware defaults.
pid_Kp: 120
pid_Ki: 1.5
pid_Kd: 600
min_temp: 10
max_temp: 110
# Hotend fan.
# The stock firmware uses control ranges of PWM 0-50%, RPM 1000-8000.
# Change fan_speed below to match your preference. Measured speeds:
# fan_speed 0.5: 50% PWM = 4000RPM (Prusa stock default speed)
# fan_speed 1.0: 100% PWM = 8000RPM (safe but loud)
[heater_fan hotend_fan]
pin: PE9
tachometer_pin: PE14
fan_speed: 0.5
# Part cooling fan.
# The stock firmware uses control ranges of PWM 10-50%, RPM 500-5000.
# To match stock firmware, set the Klipper fan speed to 50%. This speed
# can be safely increased to 100% for better part cooling. Measured speeds:
# 50% PWM = 2500RPM (Prusa stock default speed)
# 100% PWM = 5000RPM (better cooling, still quiet)
[fan]
pin: PE11
tachometer_pin: PE10
# The SuperPINDA has built-in temperature compensation and no thermistor output,
# so no compensation table is needed here.
[probe]
pin: PA8
x_offset: -29
y_offset: -3
z_offset: 0 # set this to your Live Z Offset, but negated (invert the sign)
speed: 6.0
[safe_z_home]
home_xy_position: 147.4,21.1
z_hop: 4
[bed_mesh]
speed: 100
horizontal_move_z: 5
mesh_min: 10,10
mesh_max: 141,167
probe_count: 4,4
[filament_switch_sensor filament_sensor]
switch_pin: ^PB4
pause_on_runout: True
[mcu]
serial: /dev/serial/by-id/usb-Klipper_stm32f407xx_3100380013504E4E53353420-if00
restart_method: command
[printer]
kinematics: cartesian
# Prusa firmware defaults.
max_velocity: 180
max_accel: 1250
max_z_velocity: 12
max_z_accel: 400

View File

@ -112,7 +112,7 @@ pin: PD6
pause_on_runout: True
runout_gcode:
M25
switch_pin: PA15
switch_pin: !PA15
[output_pin beeper]
pin: PB0

View File

@ -170,7 +170,7 @@ gcode:
When the PANELDUE_BEEP gcode macro is executed, Klipper would send something
like the following over the socket:
`{"action": "run_paneldue_beep",
"params": {"frequency": 300, "duration": 1.0}}
"params": {"frequency": 300, "duration": 1.0}}`
### objects/list

View File

@ -430,3 +430,14 @@ probing the "Probe" points will refer to both the tool and nozzle locations.
`BED_MESH_CLEAR`
This gcode may be used to clear the internal mesh state.
### Apply X/Y offsets
`BED_MESH_OFFSET [X=<value>] [Y=<value>]`
This is useful for printers with multiple independent extruders, as an offset
is necessary to produce correct Z adjustment after a tool change. Offsets
should be specified relative to the primary extruder. That is, a positive
X offset should be specified if the secondary extruder is mounted to the
right of the primary extruder, and a positive Y offset should be specified
if the secondary extruder is mounted "behind" the primary extruder.

View File

@ -331,12 +331,11 @@ Useful steps:
seconds) to a cartesian coordinate (in millimeters), and then
calculate the desired stepper position (in millimeters) from that
cartesian coordinate.
4. Implement the `calc_tag_position()` method in the new kinematics
class. This method calculates the position of the toolhead in
cartesian coordinates from the position of each stepper (as
returned by `stepper.get_tag_position()`). It does not need to be
efficient as it is typically only called during homing and probing
operations.
4. Implement the `calc_position()` method in the new kinematics class.
This method calculates the position of the toolhead in cartesian
coordinates from the position of each stepper. It does not need to
be efficient as it is typically only called during homing and
probing operations.
5. Other methods. Implement the `check_move()`, `get_status()`,
`get_steppers()`, `home()`, and `set_position()` methods. These
functions are typically used to provide kinematic specific checks.
@ -448,17 +447,16 @@ but does not include moves on the look-ahead queue. One may use the
`toolhead.flush_step_generation()` or `toolhead.wait_moves()` calls to
fully flush the look-ahead and step generation code.
The "kinematic" position (`stepper.set_tag_position()` and
`kin.calc_tag_position()`) is the cartesian position of the toolhead
as derived from the "stepper" position and is relative to the
coordinate system specified in the config file. This may differ from
the requested cartesian position due to the granularity of the stepper
motors. If the robot is in motion when `stepper.set_tag_position()` is
issued then the reported value includes moves buffered on the
micro-controller, but does not include moves on the look-ahead
queue. One may use the `toolhead.flush_step_generation()` or
`toolhead.wait_moves()` calls to fully flush the look-ahead and step
generation code.
The "kinematic" position (`kin.calc_position()`) is the cartesian
position of the toolhead as derived from "stepper" positions and is
relative to the coordinate system specified in the config file. This
may differ from the requested cartesian position due to the
granularity of the stepper motors. If the robot is in motion when the
"stepper" positions are taken then the reported value includes moves
buffered on the micro-controller, but does not include moves on the
look-ahead queue. One may use the `toolhead.flush_step_generation()`
or `toolhead.wait_moves()` calls to fully flush the look-ahead and
step generation code.
The "toolhead" position (`toolhead.get_position()`) is the last
requested position of the toolhead in cartesian coordinates relative

View File

@ -6,6 +6,10 @@ All dates in this document are approximate.
# Changes
20210612: The `pid_integral_max` config option in heater and
temperature_fan sections is deprecated. The option will be removed in
the near future.
20210503: The gcode_macro `default_parameter_<name>` config option is
deprecated. Use the `params` pseudo-variable to access macro
parameters. Other methods for accessing macro parameters will be

View File

@ -714,9 +714,6 @@ pid_Ki:
pid_Kd:
# Kd is the "derivative" constant for the pid. This parameter must
# be provided for PID heaters.
#pid_integral_max:
# The maximum "windup" the integral term may accumulate. The default
# is to use the same value as max_power.
#max_delta: 2.0
# On 'watermark' controlled heaters this is the number of degrees in
# Celsius above the target temperature before disabling the heater
@ -1989,7 +1986,6 @@ temperature.
#pid_Kp:
#pid_Ki:
#pid_Kd:
#pid_integral_max:
#pwm_cycle_time:
#min_temp:
#max_temp:
@ -2429,7 +2425,6 @@ additional information.
#pid_Ki:
#pid_Kd:
#pid_deriv_time:
#pid_integral_max:
#max_delta:
#min_temp:
#max_temp:

View File

@ -161,8 +161,7 @@ The following standard commands are supported:
MINIMUM and/or at or below the supplied MAXIMUM.
- `SET_VELOCITY_LIMIT [VELOCITY=<value>] [ACCEL=<value>]
[ACCEL_TO_DECEL=<value>] [SQUARE_CORNER_VELOCITY=<value>]`: Modify
the printer's velocity limits. Note that one may only set values
less than or equal to the limits specified in the config file.
the printer's velocity limits.
- `SET_HEATER_TEMPERATURE HEATER=<heater_name> [TARGET=<target_temperature>]`:
Sets the target temperature for a heater. If a target temperature is
not supplied, the target is 0.
@ -272,7 +271,7 @@ The following command is available when a
[neopixel config section](Config_Reference.md#neopixel) or
[dotstar config section](Config_Reference.md#dotstar) is enabled:
- `SET_LED LED=<config_name> RED=<value> GREEN=<value> BLUE=<value>
WHITE=<value> [INDEX=<index>] [TRANSMIT=0]`: This sets the LED
WHITE=<value> [INDEX=<index>] [TRANSMIT=0] [SYNC=1]`: This sets the LED
output. Each color `<value>` must be between 0.0 and 1.0. The WHITE
option is only valid on RGBW LEDs. If multiple LED chips are
daisy-chained then one may specify INDEX to alter the color of just
@ -281,7 +280,12 @@ The following command is available when a
to the provided color. If TRANSMIT=0 is specified then the color
change will only be made on the next SET_LED command that does not
specify TRANSMIT=0; this may be useful in combination with the INDEX
parameter to batch multiple updates in a daisy-chain.
parameter to batch multiple updates in a daisy-chain. By default, the
SET_LED command will sync it's changes with other ongoing gcode commands.
This can lead to undesirable behavior if LEDs are being set while the
printer is not printing as it will reset the idle timeout. If careful
timing is not needed, the optional SYNC=0 parameter can be specified to
apply the changes instantly and not reset the idle timeout.
## Servo Commands
@ -429,6 +433,10 @@ The following commands are available when the
supplied name from persistent memory. Note that after SAVE or
REMOVE operations have been run the SAVE_CONFIG gcode must be run
to make the changes to peristent memory permanent.
- `BED_MESH_OFFSET [X=<value>] [Y=<value>]`: Applies X and/or Y
offsets to the mesh lookup. This is useful for printers with
independent extruders, as an offset is necessary to produce
correct Z adjustment after a tool change.
## Bed Screws Helper
@ -579,6 +587,7 @@ enabled:
print. This is useful if one decides to cancel a print after a
PAUSE. It is recommended to add this to your start gcode to make
sure the paused state is fresh for each print.
- `CANCEL_PRINT`: Cancels the current print.
## Filament Sensor

View File

@ -139,17 +139,7 @@ noisy imbalanced fans on a 3D printer.
## Measuring the resonances
Now you can run some real-life tests. In `printer.cfg` add or replace the
following values:
```
[printer]
max_accel: 10000
max_accel_to_decel: 10000
```
(after you are done with the measurements, revert these values to their old,
or the newly suggested values).
Run the following command:
Now you can run some real-life tests. Run the following command:
```
TEST_RESONANCES AXIS=X
```

View File

@ -36,6 +36,20 @@ The following information is available in the `display_status` object
`virtual_sdcard.progress` if no recent `M73` received).
- `message`: The message contained in the last `M117` G-Code command.
# endstop_phase
The following information is available in the
[endstop_phase](Config_Reference.md#endstop_phase) object:
- `last_home.<stepper name>.phase`: The phase of the stepper motor at
the end of the last home attempt.
- `last_home.<stepper name>.phases`: The total number of phases
available on the stepper motor.
- `last_home.<stepper name>.mcu_position`: The position (as tracked by
the micro-controller) of the stepper motor at the end of the last
home attempt. The position is the total number of steps taken in a
forward direction minus the total number of steps taken in the
reverse direction since the micro-controller was last restarted.
# fan
The following information is available in
@ -130,6 +144,8 @@ The following information is available for heater objects such as
the given heater.
- `power`: The last setting of the PWM pin (a value between 0.0 and
1.0) associated with the heater.
- `can_extrude`: If extruder can extrude (defined by `min_extrude_temp`),
available only for [extruder](Config_Reference.md#extruder)
# heaters
@ -218,6 +234,13 @@ is defined):
template expansion, the PROBE (or similar) command must be run prior
to the macro containing this reference.
# quad_gantry_level
The following information is available in the `quad_gantry_level` object
(this object is available if quad_gantry_level is defined):
- `applied`: True if the gantry leveling process has been run and completed
successfully.
# query_endstops
The following information is available in the `query_endstops` object
@ -316,3 +339,10 @@ object is always available):
state. Possible values are: "ready", "startup", "shutdown", "error".
- `state_message`: A human readable string giving additional context
on the current Klipper state.
# z_tilt
The following information is available in the `z_tilt` object (this
object is available if z_tilt is defined):
- `applied`: True if the z-tilt leveling process has been run and completed
successfully.

View File

@ -258,10 +258,13 @@ After sensorless homing completes the carriage will be pressed against
the end of the rail and the stepper will exert a force on the frame
until the carriage is moved away. It is a good idea to create a macro
to home the axis and immediately move the carriage away from the end
of the rail. It is recommended to set the speed of this subsequent
move so that it lasts at least two seconds (eg, `G1 X40 F1200`) to
ensure the stall flag in the TMC driver is cleared after the move
completes.
of the rail.
It is a good idea for the macro to pause at least 2 seconds prior to
starting sensorless homing (or otherwise ensure that there has been no
movement on the stepper for 2 seconds). Without a delay it is possible
for the driver's internal stall flag to still be set from a previous
move.
It can also be useful to have that macro set the driver current before
homing and set a new current after the carriage has moved away. This
@ -279,11 +282,13 @@ gcode:
{% set HOLD_CUR = driver_config.hold_current %}
# Set current for sensorless homing
SET_TMC_CURRENT STEPPER=stepper_x CURRENT={HOME_CUR} HOLDCURRENT={HOME_CUR}
# Pause to ensure driver stall flag is clear
G4 P2000
# Home
G28 X0
# Move away
G90
G1 X40 F1200
G1 X5 F1200
# Set current during print
SET_TMC_CURRENT STEPPER=stepper_x CURRENT={RUN_CUR} HOLDCURRENT={HOLD_CUR}
```
@ -308,16 +313,15 @@ sensitivity" for each carriage, but be aware of the following
restrictions:
1. When using sensorless homing on CoreXY, make sure there is no
`hold_current` in effect for either stepper during homing.
2. Make sure both the X and Y carriages are near the center of their
rails before each home attempt.
2. While tuning, make sure both the X and Y carriages are near the
center of their rails before each home attempt.
3. After tuning is complete, when homing both X and Y, use macros to
ensure that one axis is homed first, then move that carriage away
from the axis limit using a move that lasts at least two seconds,
and then start the homing of the other carriage. The move away from
the axis helps ensure the stall flag is cleared from both stepper
drivers before starting the next home attempt. It also avoids
homing one axis while the other is pressed against the axis limit
(which may skew the stall detection).
from the axis limit, pause for at least 2 seconds, and then start
the homing of the other carriage. The move away from the axis
avoids homing one axis while the other is pressed against the axis
limit (which may skew the stall detection). The pause is necessary
to ensure the driver's stall flag is cleared prior to homing again.
# Querying and diagnosing driver settings
@ -377,6 +381,12 @@ Make sure that the motor power is enabled, as the stepper motor driver
generally needs motor power before it can communicate with the
micro-controller.
If this error occurs after flashing Klipper for the first time, then
the stepper driver may have been previously programmed in a state that
is not compatible with Klipper. To reset the state, remove all power
from the printer for several seconds (physically unplug both USB and
power plugs).
Otherwise, this error is typically the result of incorrect UART pin
wiring or an incorrect Klipper configuration of the UART pin settings.

View File

@ -5,11 +5,10 @@ Building an OS image
====================
Start by installing the
[Debian 9.9 2019-08-03 4GB SD IoT]
(https://beagleboard.org/latest-images) image.
One may run the image from either a micro-SD card or from
builtin eMMC. If using the eMMC, install it to eMMC now by
following the instructions from the above link.
[Debian 9.9 2019-08-03 4GB SD IoT](https://beagleboard.org/latest-images)
image. One may run the image from either a micro-SD card or from
builtin eMMC. If using the eMMC, install it to eMMC now by following
the instructions from the above link.
Then ssh into the beaglebone machine (ssh debian@beaglebone --
password is "temppwd") and install Klipper by running the following

View File

@ -18,6 +18,7 @@ COMPILE_ARGS = ("-Wall -g -O2 -shared -fPIC"
SSE_FLAGS = "-mfpmath=sse -msse2"
SOURCE_FILES = [
'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'itersolve.c', 'trapq.c',
'pollreactor.c', 'msgblock.c', 'trdispatch.c',
'kin_cartesian.c', 'kin_corexy.c', 'kin_corexz.c', 'kin_delta.c',
'kin_polar.c', 'kin_rotary_delta.c', 'kin_winch.c', 'kin_extruder.c',
'kin_shaper.c',
@ -25,7 +26,7 @@ SOURCE_FILES = [
DEST_LIB = "c_helper.so"
OTHER_FILES = [
'list.h', 'serialqueue.h', 'stepcompress.h', 'itersolve.h', 'pyhelper.h',
'trapq.h',
'trapq.h', 'pollreactor.h', 'msgblock.h'
]
defs_stepcompress = """
@ -161,12 +162,25 @@ defs_serialqueue = """
void serialqueue_set_receive_window(struct serialqueue *sq
, int receive_window);
void serialqueue_set_clock_est(struct serialqueue *sq, double est_freq
, double last_clock_time, uint64_t last_clock);
, double conv_time, uint64_t conv_clock, uint64_t last_clock);
void serialqueue_get_stats(struct serialqueue *sq, char *buf, int len);
int serialqueue_extract_old(struct serialqueue *sq, int sentq
, struct pull_queue_message *q, int max);
"""
defs_trdispatch = """
void trdispatch_start(struct trdispatch *td, uint32_t dispatch_reason);
void trdispatch_stop(struct trdispatch *td);
struct trdispatch *trdispatch_alloc(void);
struct trdispatch_mcu *trdispatch_mcu_alloc(struct trdispatch *td
, struct serialqueue *sq, struct command_queue *cq, uint32_t trsync_oid
, uint32_t set_timeout_msgtag, uint32_t trigger_msgtag
, uint32_t state_msgtag);
void trdispatch_mcu_setup(struct trdispatch_mcu *tdm
, uint64_t last_status_clock, uint64_t expire_clock
, uint64_t expire_ticks, uint64_t min_extend_ticks);
"""
defs_pyhelper = """
void set_python_logging_callback(void (*func)(const char *));
double get_monotonic(void);
@ -178,9 +192,10 @@ defs_std = """
defs_all = [
defs_pyhelper, defs_serialqueue, defs_std, defs_stepcompress,
defs_itersolve, defs_trapq, defs_kin_cartesian, defs_kin_corexy,
defs_kin_corexz, defs_kin_delta, defs_kin_polar, defs_kin_rotary_delta,
defs_kin_winch, defs_kin_extruder, defs_kin_shaper,
defs_itersolve, defs_trapq, defs_trdispatch,
defs_kin_cartesian, defs_kin_corexy, defs_kin_corexz, defs_kin_delta,
defs_kin_polar, defs_kin_rotary_delta, defs_kin_winch, defs_kin_extruder,
defs_kin_shaper,
]
# Update filenames to an absolute path

209
klippy/chelper/msgblock.c Normal file
View File

@ -0,0 +1,209 @@
// Helper code for the Klipper mcu protocol "message blocks"
//
// Copyright (C) 2016-2021 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <stddef.h> // offsetof
#include <stdlib.h> // malloc
#include <string.h> // memset
#include "msgblock.h" // message_alloc
#include "pyhelper.h" // errorf
/****************************************************************
* Serial protocol helpers
****************************************************************/
// Implement the standard crc "ccitt" algorithm on the given buffer
uint16_t
msgblock_crc16_ccitt(uint8_t *buf, uint8_t len)
{
uint16_t crc = 0xffff;
while (len--) {
uint8_t data = *buf++;
data ^= crc & 0xff;
data ^= data << 4;
crc = ((((uint16_t)data << 8) | (crc >> 8)) ^ (uint8_t)(data >> 4)
^ ((uint16_t)data << 3));
}
return crc;
}
// Verify a buffer starts with a valid mcu message
int
msgblock_check(uint8_t *need_sync, uint8_t *buf, int buf_len)
{
if (buf_len < MESSAGE_MIN)
// Need more data
return 0;
if (*need_sync)
goto error;
uint8_t msglen = buf[MESSAGE_POS_LEN];
if (msglen < MESSAGE_MIN || msglen > MESSAGE_MAX)
goto error;
uint8_t msgseq = buf[MESSAGE_POS_SEQ];
if ((msgseq & ~MESSAGE_SEQ_MASK) != MESSAGE_DEST)
goto error;
if (buf_len < msglen)
// Need more data
return 0;
if (buf[msglen-MESSAGE_TRAILER_SYNC] != MESSAGE_SYNC)
goto error;
uint16_t msgcrc = ((buf[msglen-MESSAGE_TRAILER_CRC] << 8)
| (uint8_t)buf[msglen-MESSAGE_TRAILER_CRC+1]);
uint16_t crc = msgblock_crc16_ccitt(buf, msglen-MESSAGE_TRAILER_SIZE);
if (crc != msgcrc)
goto error;
return msglen;
error: ;
// Discard bytes until next SYNC found
uint8_t *next_sync = memchr(buf, MESSAGE_SYNC, buf_len);
if (next_sync) {
*need_sync = 0;
return -(next_sync - buf + 1);
}
*need_sync = 1;
return -buf_len;
}
// Encode an integer as a variable length quantity (vlq)
static uint8_t *
encode_int(uint8_t *p, uint32_t v)
{
int32_t sv = v;
if (sv < (3L<<5) && sv >= -(1L<<5)) goto f4;
if (sv < (3L<<12) && sv >= -(1L<<12)) goto f3;
if (sv < (3L<<19) && sv >= -(1L<<19)) goto f2;
if (sv < (3L<<26) && sv >= -(1L<<26)) goto f1;
*p++ = (v>>28) | 0x80;
f1: *p++ = ((v>>21) & 0x7f) | 0x80;
f2: *p++ = ((v>>14) & 0x7f) | 0x80;
f3: *p++ = ((v>>7) & 0x7f) | 0x80;
f4: *p++ = v & 0x7f;
return p;
}
// Parse an integer that was encoded as a "variable length quantity"
static uint32_t
parse_int(uint8_t **pp)
{
uint8_t *p = *pp, c = *p++;
uint32_t v = c & 0x7f;
if ((c & 0x60) == 0x60)
v |= -0x20;
while (c & 0x80) {
c = *p++;
v = (v<<7) | (c & 0x7f);
}
*pp = p;
return v;
}
// Parse the VLQ contents of a message
int
msgblock_decode(uint32_t *data, int data_len, uint8_t *msg, int msg_len)
{
uint8_t *p = &msg[MESSAGE_HEADER_SIZE];
uint8_t *end = &msg[msg_len - MESSAGE_TRAILER_SIZE];
while (data_len--) {
if (p >= end)
return -1;
*data++ = parse_int(&p);
}
if (p != end)
// Invalid message
return -1;
return 0;
}
/****************************************************************
* Command queues
****************************************************************/
// Allocate a 'struct queue_message' object
struct queue_message *
message_alloc(void)
{
struct queue_message *qm = malloc(sizeof(*qm));
memset(qm, 0, sizeof(*qm));
return qm;
}
// Allocate a queue_message and fill it with the specified data
struct queue_message *
message_fill(uint8_t *data, int len)
{
struct queue_message *qm = message_alloc();
memcpy(qm->msg, data, len);
qm->len = len;
return qm;
}
// Allocate a queue_message and fill it with a series of encoded vlq integers
struct queue_message *
message_alloc_and_encode(uint32_t *data, int len)
{
struct queue_message *qm = message_alloc();
int i;
uint8_t *p = qm->msg;
for (i=0; i<len; i++) {
p = encode_int(p, data[i]);
if (p > &qm->msg[MESSAGE_PAYLOAD_MAX])
goto fail;
}
qm->len = p - qm->msg;
return qm;
fail:
errorf("Encode error");
qm->len = 0;
return qm;
}
// Free the storage from a previous message_alloc() call
void
message_free(struct queue_message *qm)
{
free(qm);
}
// Free all the messages on a queue
void
message_queue_free(struct list_head *root)
{
while (!list_empty(root)) {
struct queue_message *qm = list_first_entry(
root, struct queue_message, node);
list_del(&qm->node);
message_free(qm);
}
}
/****************************************************************
* Clock estimation
****************************************************************/
// Extend a 32bit clock value to its full 64bit value
uint64_t
clock_from_clock32(struct clock_estimate *ce, uint32_t clock32)
{
return ce->last_clock + (int32_t)(clock32 - ce->last_clock);
}
// Convert a clock to its estimated time
double
clock_to_time(struct clock_estimate *ce, uint64_t clock)
{
return ce->conv_time + (int64_t)(clock - ce->conv_clock) / ce->est_freq;
}
// Convert a time to the nearest clock value
uint64_t
clock_from_time(struct clock_estimate *ce, double time)
{
return (int64_t)((time - ce->conv_time)*ce->est_freq + .5) + ce->conv_clock;
}

54
klippy/chelper/msgblock.h Normal file
View File

@ -0,0 +1,54 @@
#ifndef MSGBLOCK_H
#define MSGBLOCK_H
#include <stdint.h> // uint8_t
#include "list.h" // struct list_node
#define MESSAGE_MIN 5
#define MESSAGE_MAX 64
#define MESSAGE_HEADER_SIZE 2
#define MESSAGE_TRAILER_SIZE 3
#define MESSAGE_POS_LEN 0
#define MESSAGE_POS_SEQ 1
#define MESSAGE_TRAILER_CRC 3
#define MESSAGE_TRAILER_SYNC 1
#define MESSAGE_PAYLOAD_MAX (MESSAGE_MAX - MESSAGE_MIN)
#define MESSAGE_SEQ_MASK 0x0f
#define MESSAGE_DEST 0x10
#define MESSAGE_SYNC 0x7E
struct queue_message {
int len;
uint8_t msg[MESSAGE_MAX];
union {
// Filled when on a command queue
struct {
uint64_t min_clock, req_clock;
};
// Filled when in sent/receive queues
struct {
double sent_time, receive_time;
};
};
uint64_t notify_id;
struct list_node node;
};
struct clock_estimate {
uint64_t last_clock, conv_clock;
double conv_time, est_freq;
};
uint16_t msgblock_crc16_ccitt(uint8_t *buf, uint8_t len);
int msgblock_check(uint8_t *need_sync, uint8_t *buf, int buf_len);
int msgblock_decode(uint32_t *data, int data_len, uint8_t *msg, int msg_len);
struct queue_message *message_alloc(void);
struct queue_message *message_fill(uint8_t *data, int len);
struct queue_message *message_alloc_and_encode(uint32_t *data, int len);
void message_free(struct queue_message *qm);
void message_queue_free(struct list_head *root);
uint64_t clock_from_clock32(struct clock_estimate *ce, uint32_t clock32);
double clock_to_time(struct clock_estimate *ce, uint64_t clock);
uint64_t clock_from_time(struct clock_estimate *ce, double time);
#endif // msgblock.h

View File

@ -0,0 +1,179 @@
// Code for dispatching timer and file descriptor events
//
// Copyright (C) 2016-2021 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <fcntl.h> // fcntl
#include <math.h> // ceil
#include <poll.h> // poll
#include <stdlib.h> // malloc
#include <string.h> // memset
#include "pollreactor.h" // pollreactor_alloc
#include "pyhelper.h" // report_errno
struct pollreactor_timer {
double waketime;
double (*callback)(void *data, double eventtime);
};
struct pollreactor {
int num_fds, num_timers, must_exit;
void *callback_data;
double next_timer;
struct pollfd *fds;
void (**fd_callbacks)(void *data, double eventtime);
struct pollreactor_timer *timers;
};
// Allocate a new 'struct pollreactor' object
struct pollreactor *
pollreactor_alloc(int num_fds, int num_timers, void *callback_data)
{
struct pollreactor *pr = malloc(sizeof(*pr));
memset(pr, 0, sizeof(*pr));
pr->num_fds = num_fds;
pr->num_timers = num_timers;
pr->must_exit = 0;
pr->callback_data = callback_data;
pr->next_timer = PR_NEVER;
pr->fds = malloc(num_fds * sizeof(*pr->fds));
memset(pr->fds, 0, num_fds * sizeof(*pr->fds));
pr->fd_callbacks = malloc(num_fds * sizeof(*pr->fd_callbacks));
memset(pr->fd_callbacks, 0, num_fds * sizeof(*pr->fd_callbacks));
pr->timers = malloc(num_timers * sizeof(*pr->timers));
memset(pr->timers, 0, num_timers * sizeof(*pr->timers));
int i;
for (i=0; i<num_timers; i++)
pr->timers[i].waketime = PR_NEVER;
return pr;
}
// Free resources associated with a 'struct pollreactor' object
void
pollreactor_free(struct pollreactor *pr)
{
free(pr->fds);
pr->fds = NULL;
free(pr->fd_callbacks);
pr->fd_callbacks = NULL;
free(pr->timers);
pr->timers = NULL;
free(pr);
}
// Add a callback for when a file descriptor (fd) becomes readable
void
pollreactor_add_fd(struct pollreactor *pr, int pos, int fd, void *callback
, int write_only)
{
pr->fds[pos].fd = fd;
pr->fds[pos].events = POLLHUP | (write_only ? 0 : POLLIN);
pr->fds[pos].revents = 0;
pr->fd_callbacks[pos] = callback;
}
// Add a timer callback
void
pollreactor_add_timer(struct pollreactor *pr, int pos, void *callback)
{
pr->timers[pos].callback = callback;
pr->timers[pos].waketime = PR_NEVER;
}
// Return the last schedule wake-up time for a timer
double
pollreactor_get_timer(struct pollreactor *pr, int pos)
{
return pr->timers[pos].waketime;
}
// Set the wake-up time for a given timer
void
pollreactor_update_timer(struct pollreactor *pr, int pos, double waketime)
{
pr->timers[pos].waketime = waketime;
if (waketime < pr->next_timer)
pr->next_timer = waketime;
}
// Internal code to invoke timer callbacks
static int
pollreactor_check_timers(struct pollreactor *pr, double eventtime, int busy)
{
if (eventtime >= pr->next_timer) {
// Find and run pending timers
pr->next_timer = PR_NEVER;
int i;
for (i=0; i<pr->num_timers; i++) {
struct pollreactor_timer *timer = &pr->timers[i];
double t = timer->waketime;
if (eventtime >= t) {
busy = 1;
t = timer->callback(pr->callback_data, eventtime);
timer->waketime = t;
}
if (t < pr->next_timer)
pr->next_timer = t;
}
}
if (busy)
return 0;
// Calculate sleep duration
double timeout = ceil((pr->next_timer - eventtime) * 1000.);
return timeout < 1. ? 1 : (timeout > 1000. ? 1000 : (int)timeout);
}
// Repeatedly check for timer and fd events and invoke their callbacks
void
pollreactor_run(struct pollreactor *pr)
{
double eventtime = get_monotonic();
int busy = 1;
while (! pr->must_exit) {
int timeout = pollreactor_check_timers(pr, eventtime, busy);
busy = 0;
int ret = poll(pr->fds, pr->num_fds, timeout);
eventtime = get_monotonic();
if (ret > 0) {
busy = 1;
int i;
for (i=0; i<pr->num_fds; i++)
if (pr->fds[i].revents)
pr->fd_callbacks[i](pr->callback_data, eventtime);
} else if (ret < 0) {
report_errno("poll", ret);
pr->must_exit = 1;
}
}
}
// Request that a currently running pollreactor_run() loop exit
void
pollreactor_do_exit(struct pollreactor *pr)
{
pr->must_exit = 1;
}
// Check if a pollreactor_run() loop has been requested to exit
int
pollreactor_is_exit(struct pollreactor *pr)
{
return pr->must_exit;
}
int
fd_set_non_blocking(int fd)
{
int flags = fcntl(fd, F_GETFL);
if (flags < 0) {
report_errno("fcntl getfl", flags);
return -1;
}
int ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
if (ret < 0) {
report_errno("fcntl setfl", flags);
return -1;
}
return 0;
}

View File

@ -0,0 +1,20 @@
#ifndef POLLREACTOR_H
#define POLLREACTOR_H
#define PR_NOW 0.
#define PR_NEVER 9999999999999999.
struct pollreactor *pollreactor_alloc(int num_fds, int num_timers
, void *callback_data);
void pollreactor_free(struct pollreactor *pr);
void pollreactor_add_fd(struct pollreactor *pr, int pos, int fd, void *callback
, int write_only);
void pollreactor_add_timer(struct pollreactor *pr, int pos, void *callback);
double pollreactor_get_timer(struct pollreactor *pr, int pos);
void pollreactor_update_timer(struct pollreactor *pr, int pos, double waketime);
void pollreactor_run(struct pollreactor *pr);
void pollreactor_do_exit(struct pollreactor *pr);
int pollreactor_is_exit(struct pollreactor *pr);
int fd_set_non_blocking(int fd);
#endif // pollreactor.h

View File

@ -12,10 +12,8 @@
// clock times, prioritizes commands, and handles retransmissions. A
// background thread is launched to do this work and minimize latency.
#include <fcntl.h> // fcntl
#include <linux/can.h> // // struct can_frame
#include <math.h> // ceil
#include <poll.h> // poll
#include <math.h> // fabs
#include <pthread.h> // pthread_mutex_lock
#include <stddef.h> // offsetof
#include <stdint.h> // uint64_t
@ -26,335 +24,19 @@
#include <unistd.h> // pipe
#include "compiler.h" // __visible
#include "list.h" // list_add_tail
#include "msgblock.h" // message_alloc
#include "pollreactor.h" // pollreactor_alloc
#include "pyhelper.h" // get_monotonic
#include "serialqueue.h" // struct queue_message
/****************************************************************
* Poll reactor
****************************************************************/
// The 'poll reactor' code is a mechanism for dispatching timer and
// file descriptor events.
#define PR_NOW 0.
#define PR_NEVER 9999999999999999.
struct pollreactor_timer {
double waketime;
double (*callback)(void *data, double eventtime);
};
struct pollreactor {
int num_fds, num_timers, must_exit;
void *callback_data;
double next_timer;
struct pollfd *fds;
void (**fd_callbacks)(void *data, double eventtime);
struct pollreactor_timer *timers;
};
// Allocate a new 'struct pollreactor' object
static void
pollreactor_setup(struct pollreactor *pr, int num_fds, int num_timers
, void *callback_data)
{
pr->num_fds = num_fds;
pr->num_timers = num_timers;
pr->must_exit = 0;
pr->callback_data = callback_data;
pr->next_timer = PR_NEVER;
pr->fds = malloc(num_fds * sizeof(*pr->fds));
memset(pr->fds, 0, num_fds * sizeof(*pr->fds));
pr->fd_callbacks = malloc(num_fds * sizeof(*pr->fd_callbacks));
memset(pr->fd_callbacks, 0, num_fds * sizeof(*pr->fd_callbacks));
pr->timers = malloc(num_timers * sizeof(*pr->timers));
memset(pr->timers, 0, num_timers * sizeof(*pr->timers));
int i;
for (i=0; i<num_timers; i++)
pr->timers[i].waketime = PR_NEVER;
}
// Free resources associated with a 'struct pollreactor' object
static void
pollreactor_free(struct pollreactor *pr)
{
free(pr->fds);
pr->fds = NULL;
free(pr->fd_callbacks);
pr->fd_callbacks = NULL;
free(pr->timers);
pr->timers = NULL;
}
// Add a callback for when a file descriptor (fd) becomes readable
static void
pollreactor_add_fd(struct pollreactor *pr, int pos, int fd, void *callback
, int write_only)
{
pr->fds[pos].fd = fd;
pr->fds[pos].events = POLLHUP | (write_only ? 0 : POLLIN);
pr->fds[pos].revents = 0;
pr->fd_callbacks[pos] = callback;
}
// Add a timer callback
static void
pollreactor_add_timer(struct pollreactor *pr, int pos, void *callback)
{
pr->timers[pos].callback = callback;
pr->timers[pos].waketime = PR_NEVER;
}
// Return the last schedule wake-up time for a timer
static double
pollreactor_get_timer(struct pollreactor *pr, int pos)
{
return pr->timers[pos].waketime;
}
// Set the wake-up time for a given timer
static void
pollreactor_update_timer(struct pollreactor *pr, int pos, double waketime)
{
pr->timers[pos].waketime = waketime;
if (waketime < pr->next_timer)
pr->next_timer = waketime;
}
// Internal code to invoke timer callbacks
static int
pollreactor_check_timers(struct pollreactor *pr, double eventtime, int busy)
{
if (eventtime >= pr->next_timer) {
// Find and run pending timers
pr->next_timer = PR_NEVER;
int i;
for (i=0; i<pr->num_timers; i++) {
struct pollreactor_timer *timer = &pr->timers[i];
double t = timer->waketime;
if (eventtime >= t) {
busy = 1;
t = timer->callback(pr->callback_data, eventtime);
timer->waketime = t;
}
if (t < pr->next_timer)
pr->next_timer = t;
}
}
if (busy)
return 0;
// Calculate sleep duration
double timeout = ceil((pr->next_timer - eventtime) * 1000.);
return timeout < 1. ? 1 : (timeout > 1000. ? 1000 : (int)timeout);
}
// Repeatedly check for timer and fd events and invoke their callbacks
static void
pollreactor_run(struct pollreactor *pr)
{
double eventtime = get_monotonic();
int busy = 1;
while (! pr->must_exit) {
int timeout = pollreactor_check_timers(pr, eventtime, busy);
busy = 0;
int ret = poll(pr->fds, pr->num_fds, timeout);
eventtime = get_monotonic();
if (ret > 0) {
busy = 1;
int i;
for (i=0; i<pr->num_fds; i++)
if (pr->fds[i].revents)
pr->fd_callbacks[i](pr->callback_data, eventtime);
} else if (ret < 0) {
report_errno("poll", ret);
pr->must_exit = 1;
}
}
}
// Request that a currently running pollreactor_run() loop exit
static void
pollreactor_do_exit(struct pollreactor *pr)
{
pr->must_exit = 1;
}
// Check if a pollreactor_run() loop has been requested to exit
static int
pollreactor_is_exit(struct pollreactor *pr)
{
return pr->must_exit;
}
static int
set_non_blocking(int fd)
{
int flags = fcntl(fd, F_GETFL);
if (flags < 0) {
report_errno("fcntl getfl", flags);
return -1;
}
int ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
if (ret < 0) {
report_errno("fcntl setfl", flags);
return -1;
}
return 0;
}
/****************************************************************
* Serial protocol helpers
****************************************************************/
// Implement the standard crc "ccitt" algorithm on the given buffer
static uint16_t
crc16_ccitt(uint8_t *buf, uint8_t len)
{
uint16_t crc = 0xffff;
while (len--) {
uint8_t data = *buf++;
data ^= crc & 0xff;
data ^= data << 4;
crc = ((((uint16_t)data << 8) | (crc >> 8)) ^ (uint8_t)(data >> 4)
^ ((uint16_t)data << 3));
}
return crc;
}
// Verify a buffer starts with a valid mcu message
static int
check_message(uint8_t *need_sync, uint8_t *buf, int buf_len)
{
if (buf_len < MESSAGE_MIN)
// Need more data
return 0;
if (*need_sync)
goto error;
uint8_t msglen = buf[MESSAGE_POS_LEN];
if (msglen < MESSAGE_MIN || msglen > MESSAGE_MAX)
goto error;
uint8_t msgseq = buf[MESSAGE_POS_SEQ];
if ((msgseq & ~MESSAGE_SEQ_MASK) != MESSAGE_DEST)
goto error;
if (buf_len < msglen)
// Need more data
return 0;
if (buf[msglen-MESSAGE_TRAILER_SYNC] != MESSAGE_SYNC)
goto error;
uint16_t msgcrc = ((buf[msglen-MESSAGE_TRAILER_CRC] << 8)
| (uint8_t)buf[msglen-MESSAGE_TRAILER_CRC+1]);
uint16_t crc = crc16_ccitt(buf, msglen-MESSAGE_TRAILER_SIZE);
if (crc != msgcrc)
goto error;
return msglen;
error: ;
// Discard bytes until next SYNC found
uint8_t *next_sync = memchr(buf, MESSAGE_SYNC, buf_len);
if (next_sync) {
*need_sync = 0;
return -(next_sync - buf + 1);
}
*need_sync = 1;
return -buf_len;
}
// Encode an integer as a variable length quantity (vlq)
static uint8_t *
encode_int(uint8_t *p, uint32_t v)
{
int32_t sv = v;
if (sv < (3L<<5) && sv >= -(1L<<5)) goto f4;
if (sv < (3L<<12) && sv >= -(1L<<12)) goto f3;
if (sv < (3L<<19) && sv >= -(1L<<19)) goto f2;
if (sv < (3L<<26) && sv >= -(1L<<26)) goto f1;
*p++ = (v>>28) | 0x80;
f1: *p++ = ((v>>21) & 0x7f) | 0x80;
f2: *p++ = ((v>>14) & 0x7f) | 0x80;
f3: *p++ = ((v>>7) & 0x7f) | 0x80;
f4: *p++ = v & 0x7f;
return p;
}
/****************************************************************
* Command queues
****************************************************************/
struct command_queue {
struct list_head stalled_queue, ready_queue;
struct list_node node;
};
// Allocate a 'struct queue_message' object
static struct queue_message *
message_alloc(void)
{
struct queue_message *qm = malloc(sizeof(*qm));
memset(qm, 0, sizeof(*qm));
return qm;
}
// Allocate a queue_message and fill it with the specified data
static struct queue_message *
message_fill(uint8_t *data, int len)
{
struct queue_message *qm = message_alloc();
memcpy(qm->msg, data, len);
qm->len = len;
return qm;
}
// Allocate a queue_message and fill it with a series of encoded vlq integers
struct queue_message *
message_alloc_and_encode(uint32_t *data, int len)
{
struct queue_message *qm = message_alloc();
int i;
uint8_t *p = qm->msg;
for (i=0; i<len; i++) {
p = encode_int(p, data[i]);
if (p > &qm->msg[MESSAGE_PAYLOAD_MAX])
goto fail;
}
qm->len = p - qm->msg;
return qm;
fail:
errorf("Encode error");
qm->len = 0;
return qm;
}
// Free the storage from a previous message_alloc() call
static void
message_free(struct queue_message *qm)
{
free(qm);
}
// Free all the messages on a queue
void
message_queue_free(struct list_head *root)
{
while (!list_empty(root)) {
struct queue_message *qm = list_first_entry(
root, struct queue_message, node);
list_del(&qm->node);
message_free(qm);
}
}
/****************************************************************
* Serialqueue interface
****************************************************************/
struct serialqueue {
// Input reading
struct pollreactor pr;
struct pollreactor *pr;
int serial_fd, serial_fd_type, client_id;
int pipe_fds[2];
uint8_t input_buf[4096];
@ -368,8 +50,7 @@ struct serialqueue {
// Baud / clock tracking
int receive_window;
double baud_adjust, idle_time;
double est_freq, last_clock_time;
uint64_t last_clock;
struct clock_estimate ce;
double last_receive_sent_time;
// Retransmit support
uint64_t send_seq, receive_seq;
@ -383,6 +64,9 @@ struct serialqueue {
struct list_head notify_queue;
// Received messages
struct list_head receive_queue;
// Fastreader support
pthread_mutex_t fast_reader_dispatch_lock;
struct list_head fast_readers;
// Debugging
struct list_head old_sent, old_receive;
// Stats
@ -479,7 +163,7 @@ update_receive_seq(struct serialqueue *sq, double eventtime, uint64_t rseq)
}
}
sq->receive_seq = rseq;
pollreactor_update_timer(&sq->pr, SQPT_COMMAND, PR_NOW);
pollreactor_update_timer(sq->pr, SQPT_COMMAND, PR_NOW);
// Update retransmit info
if (sq->rtt_sample_seq && rseq > sq->rtt_sample_seq
@ -504,19 +188,21 @@ update_receive_seq(struct serialqueue *sq, double eventtime, uint64_t rseq)
sq->rtt_sample_seq = 0;
}
if (list_empty(&sq->sent_queue)) {
pollreactor_update_timer(&sq->pr, SQPT_RETRANSMIT, PR_NEVER);
pollreactor_update_timer(sq->pr, SQPT_RETRANSMIT, PR_NEVER);
} else {
struct queue_message *sent = list_first_entry(
&sq->sent_queue, struct queue_message, node);
double nr = eventtime + sq->rto + sent->len * sq->baud_adjust;
pollreactor_update_timer(&sq->pr, SQPT_RETRANSMIT, nr);
pollreactor_update_timer(sq->pr, SQPT_RETRANSMIT, nr);
}
}
// Process a well formed input message
static int
static void
handle_message(struct serialqueue *sq, double eventtime, int len)
{
pthread_mutex_lock(&sq->lock);
// Calculate receive sequence number
uint64_t rseq = ((sq->receive_seq & ~MESSAGE_SEQ_MASK)
| (sq->input_buf[MESSAGE_POS_SEQ] & MESSAGE_SEQ_MASK));
@ -524,11 +210,15 @@ handle_message(struct serialqueue *sq, double eventtime, int len)
// New sequence number
if (rseq < sq->receive_seq)
rseq += MESSAGE_SEQ_MASK+1;
if (rseq > sq->send_seq && sq->receive_seq != 1)
if (rseq > sq->send_seq && sq->receive_seq != 1) {
// An ack for a message not sent? Out of order message?
return -1;
sq->bytes_invalid += len;
pthread_mutex_unlock(&sq->lock);
return;
}
update_receive_seq(sq, eventtime, rseq);
}
sq->bytes_read += len;
// Check for pending messages on notify_queue
int must_wake = 0;
@ -554,7 +244,7 @@ handle_message(struct serialqueue *sq, double eventtime, int len)
sq->last_ack_seq = rseq;
else if (rseq > sq->ignore_nak_seq && !list_empty(&sq->sent_queue))
// Duplicate Ack is a Nak - do fast retransmit
pollreactor_update_timer(&sq->pr, SQPT_RETRANSMIT, PR_NOW);
pollreactor_update_timer(sq->pr, SQPT_RETRANSMIT, PR_NOW);
} else {
// Data message - add to receive queue
struct queue_message *qm = message_fill(sq->input_buf, len);
@ -566,9 +256,26 @@ handle_message(struct serialqueue *sq, double eventtime, int len)
must_wake = 1;
}
// Check fast readers
struct fastreader *fr;
list_for_each_entry(fr, &sq->fast_readers, node) {
if (len < fr->prefix_len + MESSAGE_MIN
|| memcmp(&sq->input_buf[MESSAGE_HEADER_SIZE]
, fr->prefix, fr->prefix_len) != 0)
continue;
// Release main lock and invoke callback
pthread_mutex_lock(&sq->fast_reader_dispatch_lock);
if (must_wake)
check_wake_receive(sq);
pthread_mutex_unlock(&sq->lock);
fr->func(fr, sq->input_buf, len);
pthread_mutex_unlock(&sq->fast_reader_dispatch_lock);
return;
}
if (must_wake)
check_wake_receive(sq);
return 0;
pthread_mutex_unlock(&sq->lock);
}
// Callback for input activity on the serial fd
@ -580,7 +287,7 @@ input_event(struct serialqueue *sq, double eventtime)
int ret = read(sq->serial_fd, &cf, sizeof(cf));
if (ret <= 0) {
report_errno("can read", ret);
pollreactor_do_exit(&sq->pr);
pollreactor_do_exit(sq->pr);
return;
}
if (cf.can_id != sq->client_id + 1)
@ -595,25 +302,19 @@ input_event(struct serialqueue *sq, double eventtime)
report_errno("read", ret);
else
errorf("Got EOF when reading from device");
pollreactor_do_exit(&sq->pr);
pollreactor_do_exit(sq->pr);
return;
}
sq->input_pos += ret;
}
for (;;) {
int len = check_message(&sq->need_sync, sq->input_buf, sq->input_pos);
int len = msgblock_check(&sq->need_sync, sq->input_buf, sq->input_pos);
if (!len)
// Need more data
return;
if (len > 0) {
// Received a valid message
pthread_mutex_lock(&sq->lock);
int ret = handle_message(sq, eventtime, len);
if (ret)
sq->bytes_invalid += len;
else
sq->bytes_read += len;
pthread_mutex_unlock(&sq->lock);
handle_message(sq, eventtime, len);
} else {
// Skip bad data at beginning of input
len = -len;
@ -635,7 +336,7 @@ kick_event(struct serialqueue *sq, double eventtime)
int ret = read(sq->pipe_fds[0], dummy, sizeof(dummy));
if (ret < 0)
report_errno("pipe read", ret);
pollreactor_update_timer(&sq->pr, SQPT_COMMAND, PR_NOW);
pollreactor_update_timer(sq->pr, SQPT_COMMAND, PR_NOW);
}
static void
@ -691,7 +392,7 @@ retransmit_event(struct serialqueue *sq, double eventtime)
sq->bytes_retransmit += buflen;
// Update rto
if (pollreactor_get_timer(&sq->pr, SQPT_RETRANSMIT) == PR_NOW) {
if (pollreactor_get_timer(sq->pr, SQPT_RETRANSMIT) == PR_NOW) {
// Retransmit due to nak
sq->ignore_nak_seq = sq->receive_seq;
if (sq->receive_seq < sq->retransmit_seq)
@ -756,7 +457,7 @@ build_and_send_command(struct serialqueue *sq, uint8_t *buf, double eventtime)
len += MESSAGE_TRAILER_SIZE;
buf[MESSAGE_POS_LEN] = len;
buf[MESSAGE_POS_SEQ] = MESSAGE_DEST | (sq->send_seq & MESSAGE_SEQ_MASK);
uint16_t crc = crc16_ccitt(buf, len - MESSAGE_TRAILER_SIZE);
uint16_t crc = msgblock_crc16_ccitt(buf, len - MESSAGE_TRAILER_SIZE);
buf[len - MESSAGE_TRAILER_CRC] = crc >> 8;
buf[len - MESSAGE_TRAILER_CRC+1] = crc & 0xff;
buf[len - MESSAGE_TRAILER_SYNC] = MESSAGE_SYNC;
@ -771,7 +472,7 @@ build_and_send_command(struct serialqueue *sq, uint8_t *buf, double eventtime)
out->sent_time = eventtime;
out->receive_time = sq->idle_time;
if (list_empty(&sq->sent_queue))
pollreactor_update_timer(&sq->pr, SQPT_RETRANSMIT
pollreactor_update_timer(sq->pr, SQPT_RETRANSMIT
, sq->idle_time + sq->rto);
if (!sq->rtt_sample_seq)
sq->rtt_sample_seq = sq->send_seq;
@ -801,9 +502,7 @@ check_send_command(struct serialqueue *sq, double eventtime)
// Check for stalled messages now ready
double idletime = eventtime > sq->idle_time ? eventtime : sq->idle_time;
idletime += MESSAGE_MIN * sq->baud_adjust;
double timedelta = idletime - sq->last_clock_time;
uint64_t ack_clock = ((uint64_t)(timedelta * sq->est_freq)
+ sq->last_clock);
uint64_t ack_clock = clock_from_time(&sq->ce, idletime);
uint64_t min_stalled_clock = MAX_CLOCK, min_ready_clock = MAX_CLOCK;
struct command_queue *cq;
list_for_each_entry(cq, &sq->pending_queues, node) {
@ -826,11 +525,9 @@ check_send_command(struct serialqueue *sq, double eventtime)
struct queue_message *qm = list_first_entry(
&cq->ready_queue, struct queue_message, node);
uint64_t req_clock = qm->req_clock;
double bgoffset = MIN_REQTIME_DELTA + MIN_BACKGROUND_DELTA;
if (req_clock == BACKGROUND_PRIORITY_CLOCK)
req_clock = (uint64_t)(
(sq->idle_time - sq->last_clock_time
+ MIN_REQTIME_DELTA + MIN_BACKGROUND_DELTA)
* sq->est_freq) + sq->last_clock;
req_clock = clock_from_time(&sq->ce, sq->idle_time + bgoffset);
if (req_clock < min_ready_clock)
min_ready_clock = req_clock;
}
@ -839,20 +536,20 @@ check_send_command(struct serialqueue *sq, double eventtime)
// Check for messages to send
if (sq->ready_bytes >= MESSAGE_PAYLOAD_MAX)
return PR_NOW;
if (! sq->est_freq) {
if (! sq->ce.est_freq) {
if (sq->ready_bytes)
return PR_NOW;
sq->need_kick_clock = MAX_CLOCK;
return PR_NEVER;
}
uint64_t reqclock_delta = MIN_REQTIME_DELTA * sq->est_freq;
uint64_t reqclock_delta = MIN_REQTIME_DELTA * sq->ce.est_freq;
if (min_ready_clock <= ack_clock + reqclock_delta)
return PR_NOW;
uint64_t wantclock = min_ready_clock - reqclock_delta;
if (min_stalled_clock < wantclock)
wantclock = min_stalled_clock;
sq->need_kick_clock = wantclock;
return idletime + (wantclock - ack_clock) / sq->est_freq;
return idletime + (wantclock - ack_clock) / sq->ce.est_freq;
}
// Callback timer to send data to the serial port
@ -886,7 +583,7 @@ static void *
background_thread(void *data)
{
struct serialqueue *sq = data;
pollreactor_run(&sq->pr);
pollreactor_run(sq->pr);
pthread_mutex_lock(&sq->lock);
check_wake_receive(sq);
@ -910,15 +607,15 @@ serialqueue_alloc(int serial_fd, char serial_fd_type, int client_id)
goto fail;
// Reactor setup
pollreactor_setup(&sq->pr, SQPF_NUM, SQPT_NUM, sq);
pollreactor_add_fd(&sq->pr, SQPF_SERIAL, serial_fd, input_event
sq->pr = pollreactor_alloc(SQPF_NUM, SQPT_NUM, sq);
pollreactor_add_fd(sq->pr, SQPF_SERIAL, serial_fd, input_event
, serial_fd_type==SQT_DEBUGFILE);
pollreactor_add_fd(&sq->pr, SQPF_PIPE, sq->pipe_fds[0], kick_event, 0);
pollreactor_add_timer(&sq->pr, SQPT_RETRANSMIT, retransmit_event);
pollreactor_add_timer(&sq->pr, SQPT_COMMAND, command_event);
set_non_blocking(serial_fd);
set_non_blocking(sq->pipe_fds[0]);
set_non_blocking(sq->pipe_fds[1]);
pollreactor_add_fd(sq->pr, SQPF_PIPE, sq->pipe_fds[0], kick_event, 0);
pollreactor_add_timer(sq->pr, SQPT_RETRANSMIT, retransmit_event);
pollreactor_add_timer(sq->pr, SQPT_COMMAND, command_event);
fd_set_non_blocking(serial_fd);
fd_set_non_blocking(sq->pipe_fds[0]);
fd_set_non_blocking(sq->pipe_fds[1]);
// Retransmit setup
sq->send_seq = 1;
@ -937,6 +634,7 @@ serialqueue_alloc(int serial_fd, char serial_fd_type, int client_id)
list_init(&sq->sent_queue);
list_init(&sq->receive_queue);
list_init(&sq->notify_queue);
list_init(&sq->fast_readers);
// Debugging
list_init(&sq->old_sent);
@ -949,6 +647,9 @@ serialqueue_alloc(int serial_fd, char serial_fd_type, int client_id)
if (ret)
goto fail;
ret = pthread_cond_init(&sq->cond, NULL);
if (ret)
goto fail;
ret = pthread_mutex_init(&sq->fast_reader_dispatch_lock, NULL);
if (ret)
goto fail;
ret = pthread_create(&sq->tid, NULL, background_thread, sq);
@ -966,7 +667,7 @@ fail:
void __visible
serialqueue_exit(struct serialqueue *sq)
{
pollreactor_do_exit(&sq->pr);
pollreactor_do_exit(sq->pr);
kick_bg_thread(sq);
int ret = pthread_join(sq->tid, NULL);
if (ret)
@ -979,7 +680,7 @@ serialqueue_free(struct serialqueue *sq)
{
if (!sq)
return;
if (!pollreactor_is_exit(&sq->pr))
if (!pollreactor_is_exit(sq->pr))
serialqueue_exit(sq);
pthread_mutex_lock(&sq->lock);
message_queue_free(&sq->sent_queue);
@ -995,7 +696,7 @@ serialqueue_free(struct serialqueue *sq)
message_queue_free(&cq->stalled_queue);
}
pthread_mutex_unlock(&sq->lock);
pollreactor_free(&sq->pr);
pollreactor_free(sq->pr);
free(sq);
}
@ -1023,6 +724,27 @@ serialqueue_free_commandqueue(struct command_queue *cq)
free(cq);
}
// Add a low-latency message handler
void
serialqueue_add_fastreader(struct serialqueue *sq, struct fastreader *fr)
{
pthread_mutex_lock(&sq->lock);
list_add_tail(&fr->node, &sq->fast_readers);
pthread_mutex_unlock(&sq->lock);
}
// Remove a previously registered low-latency message handler
void
serialqueue_rm_fastreader(struct serialqueue *sq, struct fastreader *fr)
{
pthread_mutex_lock(&sq->lock);
list_del(&fr->node);
pthread_mutex_unlock(&sq->lock);
pthread_mutex_lock(&sq->fast_reader_dispatch_lock); // XXX - goofy locking
pthread_mutex_unlock(&sq->fast_reader_dispatch_lock);
}
// Add a batch of messages to the given command_queue
void
serialqueue_send_batch(struct serialqueue *sq, struct command_queue *cq
@ -1059,6 +781,17 @@ serialqueue_send_batch(struct serialqueue *sq, struct command_queue *cq
kick_bg_thread(sq);
}
// Helper to send a single message
void
serialqueue_send_one(struct serialqueue *sq, struct command_queue *cq
, struct queue_message *qm)
{
struct list_head msgs;
list_init(&msgs);
list_add_tail(&qm->node, &msgs);
serialqueue_send_batch(sq, cq, &msgs);
}
// Schedule the transmission of a message on the serial port at a
// given time and priority.
void __visible
@ -1070,11 +803,7 @@ serialqueue_send(struct serialqueue *sq, struct command_queue *cq, uint8_t *msg
qm->min_clock = min_clock;
qm->req_clock = req_clock;
qm->notify_id = notify_id;
struct list_head msgs;
list_init(&msgs);
list_add_tail(&qm->node, &msgs);
serialqueue_send_batch(sq, cq, &msgs);
serialqueue_send_one(sq, cq, qm);
}
// Return a message read from the serial port (or wait for one if none
@ -1085,7 +814,7 @@ serialqueue_pull(struct serialqueue *sq, struct pull_queue_message *pqm)
pthread_mutex_lock(&sq->lock);
// Wait for message to be available
while (list_empty(&sq->receive_queue)) {
if (pollreactor_is_exit(&sq->pr))
if (pollreactor_is_exit(sq->pr))
goto exit;
sq->receive_waiting = 1;
int ret = pthread_cond_wait(&sq->cond, &sq->lock);
@ -1137,12 +866,23 @@ serialqueue_set_receive_window(struct serialqueue *sq, int receive_window)
// serial port
void __visible
serialqueue_set_clock_est(struct serialqueue *sq, double est_freq
, double last_clock_time, uint64_t last_clock)
, double conv_time, uint64_t conv_clock
, uint64_t last_clock)
{
pthread_mutex_lock(&sq->lock);
sq->est_freq = est_freq;
sq->last_clock_time = last_clock_time;
sq->last_clock = last_clock;
sq->ce.est_freq = est_freq;
sq->ce.conv_time = conv_time;
sq->ce.conv_clock = conv_clock;
sq->ce.last_clock = last_clock;
pthread_mutex_unlock(&sq->lock);
}
// Return the latest clock estimate
void
serialqueue_get_clock_est(struct serialqueue *sq, struct clock_estimate *ce)
{
pthread_mutex_lock(&sq->lock);
memcpy(ce, &sq->ce, sizeof(sq->ce));
pthread_mutex_unlock(&sq->lock);
}

View File

@ -1,44 +1,23 @@
#ifndef SERIALQUEUE_H
#define SERIALQUEUE_H
#include <stdint.h> // uint8_t
#include "list.h" // struct list_head
#include "msgblock.h" // MESSAGE_MAX
#define MAX_CLOCK 0x7fffffffffffffffLL
#define BACKGROUND_PRIORITY_CLOCK 0x7fffffff00000000LL
#define MESSAGE_MIN 5
#define MESSAGE_MAX 64
#define MESSAGE_HEADER_SIZE 2
#define MESSAGE_TRAILER_SIZE 3
#define MESSAGE_POS_LEN 0
#define MESSAGE_POS_SEQ 1
#define MESSAGE_TRAILER_CRC 3
#define MESSAGE_TRAILER_SYNC 1
#define MESSAGE_PAYLOAD_MAX (MESSAGE_MAX - MESSAGE_MIN)
#define MESSAGE_SEQ_MASK 0x0f
#define MESSAGE_DEST 0x10
#define MESSAGE_SYNC 0x7E
struct fastreader;
typedef void (*fastreader_cb)(struct fastreader *fr, uint8_t *data, int len);
struct queue_message {
int len;
uint8_t msg[MESSAGE_MAX];
union {
// Filled when on a command queue
struct {
uint64_t min_clock, req_clock;
};
// Filled when in sent/receive queues
struct {
double sent_time, receive_time;
};
};
uint64_t notify_id;
struct fastreader {
struct list_node node;
fastreader_cb func;
int prefix_len;
uint8_t prefix[MESSAGE_MAX];
};
struct queue_message *message_alloc_and_encode(uint32_t *data, int len);
void message_queue_free(struct list_head *root);
struct pull_queue_message {
uint8_t msg[MESSAGE_MAX];
int len;
@ -53,8 +32,12 @@ void serialqueue_exit(struct serialqueue *sq);
void serialqueue_free(struct serialqueue *sq);
struct command_queue *serialqueue_alloc_commandqueue(void);
void serialqueue_free_commandqueue(struct command_queue *cq);
void serialqueue_add_fastreader(struct serialqueue *sq, struct fastreader *fr);
void serialqueue_rm_fastreader(struct serialqueue *sq, struct fastreader *fr);
void serialqueue_send_batch(struct serialqueue *sq, struct command_queue *cq
, struct list_head *msgs);
void serialqueue_send_one(struct serialqueue *sq, struct command_queue *cq
, struct queue_message *qm);
void serialqueue_send(struct serialqueue *sq, struct command_queue *cq
, uint8_t *msg, int len, uint64_t min_clock
, uint64_t req_clock, uint64_t notify_id);
@ -62,7 +45,10 @@ 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_receive_window(struct serialqueue *sq, int receive_window);
void serialqueue_set_clock_est(struct serialqueue *sq, double est_freq
, double last_clock_time, uint64_t last_clock);
, double conv_time, uint64_t conv_clock
, uint64_t last_clock);
void serialqueue_get_clock_est(struct serialqueue *sq
, struct clock_estimate *ce);
void serialqueue_get_stats(struct serialqueue *sq, char *buf, int len);
int serialqueue_extract_old(struct serialqueue *sq, int sentq
, struct pull_queue_message *q, int max);

View File

@ -16,6 +16,10 @@ int stepcompress_append(struct stepcompress *sc, int sdir
, double print_time, double step_time);
int stepcompress_commit(struct stepcompress *sc);
int stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock);
int stepcompress_set_last_position(struct stepcompress *sc
, int64_t last_position);
int64_t stepcompress_find_past_position(struct stepcompress *sc
, uint64_t clock);
int stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len);
struct serialqueue;

226
klippy/chelper/trdispatch.c Normal file
View File

@ -0,0 +1,226 @@
// Trigger sync "trsync" message dispatch
//
// Copyright (C) 2021 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <pthread.h> // pthread_mutex_lock
#include <stddef.h> // offsetof
#include <stdlib.h> // malloc
#include <string.h> // memset
#include "compiler.h" // ARRAY_SIZE
#include "list.h" // list_add_tail
#include "pollreactor.h" // PR_NEVER
#include "pyhelper.h" // report_errno
#include "serialqueue.h" // serialqueue_add_fastreader
struct trdispatch {
struct list_head tdm_list;
pthread_mutex_t lock; // protects variables below
uint32_t is_active, can_trigger, dispatch_reason;
};
struct trdispatch_mcu {
struct fastreader fr;
struct trdispatch *td;
struct list_node node;
struct serialqueue *sq;
struct command_queue *cq;
uint32_t trsync_oid, set_timeout_msgtag, trigger_msgtag;
// Remaining fields protected by trdispatch lock
uint64_t last_status_clock, expire_clock;
uint64_t expire_ticks, min_extend_ticks;
struct clock_estimate ce;
};
// Send: trsync_trigger oid=%c reason=%c
static void
send_trsync_trigger(struct trdispatch_mcu *tdm)
{
uint32_t msg[3] = {
tdm->trigger_msgtag, tdm->trsync_oid, tdm->td->dispatch_reason
};
struct queue_message *qm = message_alloc_and_encode(msg, ARRAY_SIZE(msg));
serialqueue_send_one(tdm->sq, tdm->cq, qm);
}
// Send: trsync_set_timeout oid=%c clock=%u
static void
send_trsync_set_timeout(struct trdispatch_mcu *tdm)
{
uint32_t msg[3] = {
tdm->set_timeout_msgtag, tdm->trsync_oid, tdm->expire_clock
};
struct queue_message *qm = message_alloc_and_encode(msg, ARRAY_SIZE(msg));
qm->req_clock = tdm->expire_clock;
serialqueue_send_one(tdm->sq, tdm->cq, qm);
}
// Handle a trsync_state message (callback from serialqueue fastreader)
static void
handle_trsync_state(struct fastreader *fr, uint8_t *data, int len)
{
struct trdispatch_mcu *tdm = container_of(fr, struct trdispatch_mcu, fr);
// Parse: trsync_state oid=%c can_trigger=%c trigger_reason=%c clock=%u
uint32_t fields[5];
int ret = msgblock_decode(fields, ARRAY_SIZE(fields), data, len);
if (ret || fields[1] != tdm->trsync_oid)
return;
uint32_t can_trigger=fields[2], clock=fields[4];
// Process message
struct trdispatch *td = tdm->td;
pthread_mutex_lock(&td->lock);
if (!td->can_trigger)
goto done;
if (!can_trigger) {
// mcu reports trigger or timeout - propagate to all mcus
td->can_trigger = 0;
struct trdispatch_mcu *m;
list_for_each_entry(m, &td->tdm_list, node) {
send_trsync_trigger(m);
}
goto done;
}
// mcu is still working okay - update last_status_clock
serialqueue_get_clock_est(tdm->sq, &tdm->ce);
tdm->last_status_clock = clock_from_clock32(&tdm->ce, clock);
// Determine minimum acknowledged time among all mcus
double min_time = PR_NEVER, next_min_time = PR_NEVER;
struct trdispatch_mcu *m, *min_tdm = NULL;
list_for_each_entry(m, &td->tdm_list, node) {
double status_time = clock_to_time(&m->ce, m->last_status_clock);
if (status_time < next_min_time) {
next_min_time = status_time;
if (status_time < min_time) {
next_min_time = min_time;
min_time = status_time;
min_tdm = m;
}
}
}
if (next_min_time == PR_NEVER)
next_min_time = min_time;
// Send trsync_set_timeout messages to other mcus (if needed)
list_for_each_entry(m, &td->tdm_list, node) {
double status_time = m == min_tdm ? next_min_time : min_time;
uint64_t expire=clock_from_time(&m->ce, status_time) + m->expire_ticks;
if ((int64_t)(expire - m->expire_clock) >= m->min_extend_ticks) {
m->expire_clock = expire;
send_trsync_set_timeout(m);
}
}
done:
pthread_mutex_unlock(&td->lock);
}
// Begin synchronization
void __visible
trdispatch_start(struct trdispatch *td, uint32_t dispatch_reason)
{
pthread_mutex_lock(&td->lock);
if (td->is_active || list_empty(&td->tdm_list)) {
pthread_mutex_unlock(&td->lock);
return;
}
td->dispatch_reason = dispatch_reason;
td->is_active = td->can_trigger = 1;
pthread_mutex_unlock(&td->lock);
// Register handle_trsync_state message parser for each mcu
struct trdispatch_mcu *tdm;
list_for_each_entry(tdm, &td->tdm_list, node) {
serialqueue_add_fastreader(tdm->sq, &tdm->fr);
}
}
// Cleanup after a test completes
void __visible
trdispatch_stop(struct trdispatch *td)
{
pthread_mutex_lock(&td->lock);
if (!td->is_active) {
pthread_mutex_unlock(&td->lock);
return;
}
td->is_active = 0;
pthread_mutex_unlock(&td->lock);
// Unregister handle_trsync_state message parsers
struct trdispatch_mcu *tdm;
list_for_each_entry(tdm, &td->tdm_list, node) {
serialqueue_rm_fastreader(tdm->sq, &tdm->fr);
}
}
// Create a new 'struct trdispatch' object
struct trdispatch * __visible
trdispatch_alloc(void)
{
struct trdispatch *td = malloc(sizeof(*td));
memset(td, 0, sizeof(*td));
list_init(&td->tdm_list);
int ret = pthread_mutex_init(&td->lock, NULL);
if (ret) {
report_errno("trdispatch_alloc pthread_mutex_init", ret);
return NULL;
}
return td;
}
// Create a new 'struct trdispatch_mcu' object
struct trdispatch_mcu * __visible
trdispatch_mcu_alloc(struct trdispatch *td, struct serialqueue *sq
, struct command_queue *cq, uint32_t trsync_oid
, uint32_t set_timeout_msgtag, uint32_t trigger_msgtag
, uint32_t state_msgtag)
{
struct trdispatch_mcu *tdm = malloc(sizeof(*tdm));
memset(tdm, 0, sizeof(*tdm));
tdm->sq = sq;
tdm->cq = cq;
tdm->trsync_oid = trsync_oid;
tdm->set_timeout_msgtag = set_timeout_msgtag;
tdm->trigger_msgtag = trigger_msgtag;
// Setup fastreader to match trsync_state messages
uint32_t state_prefix[] = {state_msgtag, trsync_oid};
struct queue_message *dummy = message_alloc_and_encode(
state_prefix, ARRAY_SIZE(state_prefix));
memcpy(tdm->fr.prefix, dummy->msg, dummy->len);
tdm->fr.prefix_len = dummy->len;
free(dummy);
tdm->fr.func = handle_trsync_state;
tdm->td = td;
list_add_tail(&tdm->node, &td->tdm_list);
return tdm;
}
// Setup for a trigger test
void __visible
trdispatch_mcu_setup(struct trdispatch_mcu *tdm
, uint64_t last_status_clock, uint64_t expire_clock
, uint64_t expire_ticks, uint64_t min_extend_ticks)
{
struct trdispatch *td = tdm->td;
pthread_mutex_lock(&td->lock);
tdm->last_status_clock = last_status_clock;
tdm->expire_clock = expire_clock;
tdm->expire_ticks = expire_ticks;
tdm->min_extend_ticks = min_extend_ticks;
serialqueue_get_clock_est(tdm->sq, &tdm->ce);
pthread_mutex_unlock(&td->lock);
}

View File

@ -54,7 +54,7 @@ class ClockSync:
freq = 1000000000000.
if pace:
freq = self.mcu_freq
serial.set_clock_est(freq, self.reactor.monotonic(), 0)
serial.set_clock_est(freq, self.reactor.monotonic(), 0, 0)
# MCU clock querying (_handle_clock is invoked from background thread)
def _get_clock_event(self, eventtime):
self.serial.raw_send(self.get_clock_cmd, 0, 0, self.cmd_queue)
@ -116,7 +116,7 @@ class ClockSync:
new_freq = self.clock_covariance / self.time_variance
pred_stddev = math.sqrt(self.prediction_variance)
self.serial.set_clock_est(new_freq, self.time_avg + TRANSMIT_EXTRA,
int(self.clock_avg - 3. * pred_stddev))
int(self.clock_avg - 3. * pred_stddev), clock)
self.clock_est = (self.time_avg + self.min_half_rtt,
self.clock_avg, new_freq)
#logging.debug("regr %.3f: freq=%.3f d=%d(%.3f)",

View File

@ -93,6 +93,9 @@ class BedMesh:
self.gcode.register_command(
'BED_MESH_CLEAR', self.cmd_BED_MESH_CLEAR,
desc=self.cmd_BED_MESH_CLEAR_help)
self.gcode.register_command(
'BED_MESH_OFFSET', self.cmd_BED_MESH_OFFSET,
desc=self.cmd_BED_MESH_OFFSET_help)
# Register transform
gcode_move = self.printer.load_object(config, 'gcode_move')
gcode_move.set_move_transform(self)
@ -118,8 +121,6 @@ class BedMesh:
"bed_mesh: ERROR, fade_target lies outside of mesh z "
"range\nmin: %.4f, max: %.4f, fade_target: %.4f"
% (min_z, max_z, err_target))
if self.fade_target:
mesh.offset_mesh(self.fade_target)
min_z, max_z = mesh.get_z_range()
if self.fade_dist <= max(abs(min_z), abs(max_z)):
self.z_mesh = None
@ -132,7 +133,7 @@ class BedMesh:
else:
self.fade_target = 0.
self.z_mesh = mesh
self.splitter.initialize(mesh)
self.splitter.initialize(mesh, self.fade_target)
# cache the current position before a transform takes place
gcode_move = self.printer.lookup_object('gcode_move')
gcode_move.reset_last_position()
@ -152,9 +153,9 @@ class BedMesh:
else:
# return current position minus the current z-adjustment
x, y, z, e = self.toolhead.get_position()
z_adj = self.z_mesh.calc_z(x, y)
max_adj = self.z_mesh.calc_z(x, y)
factor = 1.
max_adj = z_adj + self.fade_target
z_adj = max_adj - self.fade_target
if min(z, (z - max_adj)) >= self.fade_end:
# Fade out is complete, no factor
factor = 0.
@ -232,9 +233,20 @@ class BedMesh:
gcmd.respond_raw("mesh_map_output " + json.dumps(outdict))
else:
gcmd.respond_info("Bed has not been probed")
cmd_BED_MESH_CLEAR_help = "Clear the Mesh so no z-adjusment is made"
cmd_BED_MESH_CLEAR_help = "Clear the Mesh so no z-adjustment is made"
def cmd_BED_MESH_CLEAR(self, gcmd):
self.set_mesh(None)
cmd_BED_MESH_OFFSET_help = "Add X/Y offsets to the mesh lookup"
def cmd_BED_MESH_OFFSET(self, gcmd):
if self.z_mesh is not None:
offsets = [None, None]
for i, axis in enumerate(['X', 'Y']):
offsets[i] = gcmd.get_float(axis, None)
self.z_mesh.set_mesh_offsets(offsets)
gcode_move = self.printer.lookup_object('gcode_move')
gcode_move.reset_last_position()
else:
gcmd.respond_info("No mesh loaded to offset")
class BedMeshCalibrate:
@ -707,9 +719,11 @@ class MoveSplitter:
self.move_check_distance = config.getfloat(
'move_check_distance', 5., minval=3.)
self.z_mesh = None
self.fade_offset = 0.
self.gcode = gcode
def initialize(self, mesh):
def initialize(self, mesh, fade_offset):
self.z_mesh = mesh
self.fade_offset = fade_offset
def build_move(self, prev_pos, next_pos, factor):
self.prev_pos = tuple(prev_pos)
self.next_pos = tuple(next_pos)
@ -723,7 +737,8 @@ class MoveSplitter:
self.axis_move = [not isclose(d, 0., abs_tol=1e-10) for d in axes_d]
def _calc_z_offset(self, pos):
z = self.z_mesh.calc_z(pos[0], pos[1])
return self.z_factor * z + self.z_mesh.mesh_offset
offset = self.fade_offset
return self.z_factor * (z - offset) + offset
def _set_next_move(self, distance_from_prev):
t = distance_from_prev / self.total_move_length
if t > 1. or t < 0.:
@ -766,7 +781,7 @@ class ZMesh:
self.probed_matrix = self.mesh_matrix = None
self.mesh_params = params
self.avg_z = 0.
self.mesh_offset = 0.
self.mesh_offsets = [0., 0.]
logging.debug('bed_mesh: probe/mesh parameters:')
for key, value in self.mesh_params.items():
logging.debug("%s : %s" % (key, value))
@ -802,7 +817,7 @@ class ZMesh:
(self.mesh_y_count - 1)
def get_mesh_matrix(self):
if self.mesh_matrix is not None:
return [[round(z + self.mesh_offset, 6) for z in line]
return [[round(z, 6) for z in line]
for line in self.mesh_matrix]
return [[]]
def get_probed_matrix(self):
@ -828,6 +843,8 @@ class ZMesh:
msg = "Mesh X,Y: %d,%d\n" % (self.mesh_x_count, self.mesh_y_count)
if move_z is not None:
msg += "Search Height: %d\n" % (move_z)
msg += "Mesh Offsets: X=%.4f, Y=%.4f\n" % (
self.mesh_offsets[0], self.mesh_offsets[1])
msg += "Mesh Average: %.2f\n" % (self.avg_z)
rng = self.get_z_range()
msg += "Mesh Range: min=%.4f max=%.4f\n" % (rng[0], rng[1])
@ -851,12 +868,10 @@ class ZMesh:
# z step distances
self.avg_z = round(self.avg_z, 2)
self.print_mesh(logging.debug)
def offset_mesh(self, offset):
if self.mesh_matrix:
self.mesh_offset = offset
for y_line in self.mesh_matrix:
for idx, z in enumerate(y_line):
y_line[idx] = z - self.mesh_offset
def set_mesh_offsets(self, offsets):
for i, o in enumerate(offsets):
if o is not None:
self.mesh_offsets[i] = o
def get_x_coordinate(self, index):
return self.mesh_x_min + self.mesh_x_dist * index
def get_y_coordinate(self, index):
@ -864,8 +879,8 @@ class ZMesh:
def calc_z(self, x, y):
if self.mesh_matrix is not None:
tbl = self.mesh_matrix
tx, xidx = self._get_linear_index(x, 0)
ty, yidx = self._get_linear_index(y, 1)
tx, xidx = self._get_linear_index(x + self.mesh_offsets[0], 0)
ty, yidx = self._get_linear_index(y + self.mesh_offsets[1], 1)
z0 = lerp(tx, tbl[yidx][xidx], tbl[yidx][xidx+1])
z1 = lerp(tx, tbl[yidx+1][xidx], tbl[yidx+1][xidx+1])
return lerp(ty, z0, z1)

View File

@ -28,6 +28,8 @@ class BLTouchEndstopWrapper:
self.printer = config.get_printer()
self.printer.register_event_handler("klippy:connect",
self.handle_connect)
self.printer.register_event_handler('klippy:mcu_identify',
self.handle_mcu_identify)
self.position_endstop = config.getfloat('z_offset')
self.stow_on_each_sample = config.getboolean('stow_on_each_sample',
True)
@ -45,7 +47,6 @@ class BLTouchEndstopWrapper:
pin = config.get('sensor_pin')
pin_params = ppins.lookup_pin(pin, can_invert=True, can_pullup=True)
mcu = pin_params['chip']
mcu.register_config_callback(self._build_config)
self.mcu_endstop = mcu.setup_pin('endstop', pin_params)
# output mode
omodes = {'5V': '5V', 'OD': 'OD', None: None}
@ -72,7 +73,7 @@ class BLTouchEndstopWrapper:
desc=self.cmd_BLTOUCH_STORE_help)
# multi probes state
self.multi = 'OFF'
def _build_config(self):
def handle_mcu_identify(self):
kin = self.printer.lookup_object('toolhead').get_kinematics()
for stepper in kin.get_steppers():
if stepper.is_active_axis('z'):

View File

@ -233,9 +233,9 @@ class DeltaCalibrate:
toolhead = self.printer.lookup_object('toolhead')
toolhead.flush_step_generation()
kin = toolhead.get_kinematics()
for s in kin.get_steppers():
s.set_tag_position(s.get_commanded_position())
kin_pos = kin.calc_tag_position()
kin_spos = {s.get_name(): s.get_commanded_position()
for s in kin.get_steppers()}
kin_pos = kin.calc_position(kin_spos)
# Convert location to a stable position
delta_params = kin.get_calibration()
stable_pos = tuple(delta_params.calc_stable_position(kin_pos))

View File

@ -11,6 +11,7 @@ class PrinterDotstar:
def __init__(self, config):
self.printer = config.get_printer()
name = config.get_name().split()[1]
self.mutex = self.printer.get_reactor().mutex()
# Configure a software spi bus
ppins = self.printer.lookup_object('pins')
data_pin_params = ppins.lookup_pin(config.get('data_pin'))
@ -26,17 +27,29 @@ class PrinterDotstar:
red = config.getfloat('initial_RED', 0., minval=0., maxval=1.)
green = config.getfloat('initial_GREEN', 0., minval=0., maxval=1.)
blue = config.getfloat('initial_BLUE', 0., minval=0., maxval=1.)
red = int(red * 255. + .5)
blue = int(blue * 255. + .5)
green = int(green * 255. + .5)
color_data = [0xff, blue, green, red] * self.chain_count
self.color_data = [0, 0, 0, 0] + color_data + [0xff, 0xff, 0xff, 0xff]
self.printer.register_event_handler("klippy:connect", self.send_data)
self.update_color_data(red, green, blue)
self.old_color_data = bytearray([d ^ 1 for d in self.color_data])
# Register commands
self.printer.register_event_handler("klippy:connect", self.send_data)
gcode = self.printer.lookup_object('gcode')
gcode.register_mux_command("SET_LED", "LED", name, self.cmd_SET_LED,
desc=self.cmd_SET_LED_help)
def update_color_data(self, red, green, blue, white=None, index=None):
red = int(red * 255. + .5)
blue = int(blue * 255. + .5)
green = int(green * 255. + .5)
color_data = [0xff, blue, green, red]
if index is not None:
self.color_data[index*4:(index+1)*4] = color_data
else:
self.color_data[4:-4] = color_data * self.chain_count
def send_data(self, print_time=None):
old_data, new_data = self.old_color_data, self.color_data
if new_data == old_data:
return
minclock = 0
if print_time is not None:
minclock = self.spi.get_mcu().print_time_to_clock(print_time)
@ -50,20 +63,25 @@ class PrinterDotstar:
red = gcmd.get_float('RED', 0., minval=0., maxval=1.)
green = gcmd.get_float('GREEN', 0., minval=0., maxval=1.)
blue = gcmd.get_float('BLUE', 0., minval=0., maxval=1.)
transmit = gcmd.get_int('TRANSMIT', 1)
red = int(red * 255. + .5)
blue = int(blue * 255. + .5)
green = int(green * 255. + .5)
color_data = [0xff, blue, green, red]
white = 0.0 #dotstar's dont have white yet
index = gcmd.get_int('INDEX', None, minval=1, maxval=self.chain_count)
if index is not None:
self.color_data[index*4:(index+1)*4] = color_data
else:
self.color_data[4:-4] = color_data * self.chain_count
# Send command
if transmit:
transmit = gcmd.get_int('TRANSMIT', 1)
sync = gcmd.get_int('SYNC', 1)
def reactor_bgfunc(print_time):
with self.mutex:
self.update_color_data(red, green, blue, index=index)
if transmit:
self.send_data(print_time)
def lookahead_bgfunc(print_time):
reactor = self.printer.get_reactor()
reactor.register_callback(lambda et: reactor_bgfunc(print_time))
if sync:
#Sync LED Update with print time and send
toolhead = self.printer.lookup_object('toolhead')
toolhead.register_lookahead_callback(self.send_data)
toolhead.register_lookahead_callback(lookahead_bgfunc)
else:
#Send update now (so as not to wake toolhead and reset idle_timeout)
lookahead_bgfunc(None)
def load_config_prefix(config):
return PrinterDotstar(config)

View File

@ -8,23 +8,65 @@ import stepper
TRINAMIC_DRIVERS = ["tmc2130", "tmc2208", "tmc2209", "tmc2660", "tmc5160"]
def convert_phase(driver_phase, driver_phases, phases):
return (int(float(driver_phase) / driver_phases * phases + .5) % phases)
# Calculate the trigger phase of a stepper motor
class PhaseCalc:
def __init__(self, printer, name, phases=None):
self.printer = printer
self.name = name
self.phases = phases
self.tmc_module = None
# Statistics tracking for ENDSTOP_PHASE_CALIBRATE
self.phase_history = self.last_phase = self.last_mcu_position = None
self.is_primary = self.stats_only = False
def lookup_tmc(self):
for driver in TRINAMIC_DRIVERS:
driver_name = "%s %s" % (driver, self.name)
module = self.printer.lookup_object(driver_name, None)
if module is not None:
self.tmc_module = module
if self.phases is None:
self.phases = module.get_microsteps() * 4
break
if self.phases is not None:
self.phase_history = [0] * self.phases
def convert_phase(self, driver_phase, driver_phases):
phases = self.phases
return (int(float(driver_phase) / driver_phases * phases + .5) % phases)
def calc_phase(self, stepper):
mcu_pos = stepper.get_mcu_position()
if self.tmc_module is None:
phase = mcu_pos % self.phases
else:
try:
driver_phase, driver_phases = self.tmc_module.get_phase()
except Exception as e:
msg = "Unable to get stepper %s phase: %s" % (self.name, str(e))
logging.exception(msg)
raise self.printer.command_error(msg)
if stepper.is_dir_inverted():
driver_phase = (driver_phases - 1) - driver_phase
phase = self.convert_phase(driver_phase, driver_phases)
self.phase_history[phase] += 1
self.last_phase = phase
self.last_mcu_position = mcu_pos
return phase
# Adjusted endstop trigger positions
class EndstopPhase:
def __init__(self, config):
self.printer = config.get_printer()
self.name = config.get_name().split()[1]
# Register event handlers
self.printer.register_event_handler("klippy:connect",
self.handle_connect)
self.printer.register_event_handler("homing:home_rails_end",
self.handle_home_rails_end)
self.printer.load_object(config, "endstop_phase")
# Obtain step_distance and microsteps from stepper config section
sconfig = config.getsection(self.name)
self.step_dist = stepper.parse_step_distance(sconfig)
self.phases = sconfig.getint("microsteps", note_valid=False) * 4
self.phase_calc = PhaseCalc(self.printer, self.name, self.phases)
# Register event handlers
self.printer.register_event_handler("klippy:connect",
self.phase_calc.lookup_tmc)
self.printer.register_event_handler("homing:home_rails_end",
self.handle_home_rails_end)
self.printer.load_object(config, "endstop_phase")
# Read config
self.endstop_phase = None
trigger_phase = config.get('trigger_phase', None)
@ -37,7 +79,7 @@ class EndstopPhase:
if p >= ps:
raise config.error("Invalid trigger_phase '%s'"
% (trigger_phase,))
self.endstop_phase = convert_phase(p, ps, self.phases)
self.endstop_phase = self.phase_calc.convert_phase(p, ps)
self.endstop_align_zero = config.getboolean('endstop_align_zero', False)
self.endstop_accuracy = config.getfloat('endstop_accuracy', None,
above=0.)
@ -55,16 +97,6 @@ class EndstopPhase:
" stepper phase adjustment" % (self.name,))
if self.printer.get_start_args().get('debugoutput') is not None:
self.endstop_phase_accuracy = self.phases
self.phase_history = [0] * self.phases
self.get_phase = None
def handle_connect(self):
# Check for trinamic driver with get_phase() method
for driver in TRINAMIC_DRIVERS:
driver_name = "%s %s" % (driver, self.name)
module = self.printer.lookup_object(driver_name, None)
if module is not None:
self.get_phase = module.get_phase
break
def align_endstop(self, pos):
if not self.endstop_align_zero or self.endstop_phase is None:
return pos
@ -76,19 +108,7 @@ class EndstopPhase:
full_step = microsteps * self.step_dist
return int(pos / full_step + .5) * full_step + phase_offset
def get_homed_offset(self, stepper):
if self.get_phase is not None:
try:
driver_phase, driver_phases = self.get_phase()
except Exception as e:
msg = "Unable to get stepper %s phase: %s" % (self.name, str(e))
logging.exception(msg)
raise self.printer.command_error(msg)
if stepper.is_dir_inverted():
driver_phase = (driver_phases - 1) - driver_phase
phase = convert_phase(driver_phase, driver_phases, self.phases)
else:
phase = stepper.get_mcu_position() % self.phases
self.phase_history[phase] += 1
phase = self.phase_calc.calc_phase(stepper)
if self.endstop_phase is None:
logging.info("Setting %s endstop phase to %d", self.name, phase)
self.endstop_phase = phase
@ -102,18 +122,18 @@ class EndstopPhase:
self.name, phase, self.endstop_phase))
return delta * self.step_dist
def handle_home_rails_end(self, homing_state, rails):
kin_spos = homing_state.get_stepper_trigger_positions()
orig_pos = kin_spos.get(self.name)
if orig_pos is None:
return
for rail in rails:
stepper = rail.get_steppers()[0]
if stepper.get_name() != self.name:
continue
orig_pos = rail.get_tag_position()
offset = self.get_homed_offset(stepper)
pos = self.align_endstop(orig_pos) + offset
if pos == orig_pos:
return False
rail.set_tag_position(pos)
return True
if stepper.get_name() == self.name:
offset = self.get_homed_offset(stepper)
kin_spos[self.name] = self.align_endstop(orig_pos) + offset
return
# Support for ENDSTOP_PHASE_CALIBRATE command
class EndstopPhases:
def __init__(self, config):
self.printer = config.get_printer()
@ -125,49 +145,46 @@ class EndstopPhases:
self.gcode.register_command("ENDSTOP_PHASE_CALIBRATE",
self.cmd_ENDSTOP_PHASE_CALIBRATE,
desc=self.cmd_ENDSTOP_PHASE_CALIBRATE_help)
def lookup_rail(self, stepper, stepper_name):
mod_name = "endstop_phase %s" % (stepper_name,)
m = self.printer.lookup_object(mod_name, None)
if m is not None:
return (None, m.phase_history)
for driver in TRINAMIC_DRIVERS:
mod_name = "%s %s" % (driver, stepper_name)
def update_stepper(self, stepper, is_primary):
stepper_name = stepper.get_name()
phase_calc = self.tracking.get(stepper_name)
if phase_calc is None:
# Check if stepper has an endstop_phase config section defined
mod_name = "endstop_phase %s" % (stepper_name,)
m = self.printer.lookup_object(mod_name, None)
if m is not None:
return (m.get_phase, [0] * (m.get_microsteps() * 4))
return None
def update_rail(self, info, stepper):
if info is None:
phase_calc = m.phase_calc
else:
# Create new PhaseCalc tracker
phase_calc = PhaseCalc(self.printer, stepper_name)
phase_calc.stats_only = True
phase_calc.lookup_tmc()
self.tracking[stepper_name] = phase_calc
if phase_calc.phase_history is None:
return
get_phase, phase_history = info
if get_phase is None:
return
try:
driver_phase, driver_phases = get_phase()
except:
logging.exception("Error in EndstopPhases get_phase")
return
phase = convert_phase(driver_phase, driver_phases, len(phase_history))
phase_history[phase] += 1
if is_primary:
phase_calc.is_primary = True
if phase_calc.stats_only:
phase_calc.calc_phase(stepper)
def handle_home_rails_end(self, homing_state, rails):
for rail in rails:
stepper = rail.get_steppers()[0]
stepper_name = stepper.get_name()
if stepper_name not in self.tracking:
info = self.lookup_rail(stepper, stepper_name)
self.tracking[stepper_name] = info
self.update_rail(self.tracking[stepper_name], stepper)
is_primary = True
for stepper in rail.get_steppers():
self.update_stepper(stepper, is_primary)
is_primary = False
cmd_ENDSTOP_PHASE_CALIBRATE_help = "Calibrate stepper phase"
def cmd_ENDSTOP_PHASE_CALIBRATE(self, gcmd):
stepper_name = gcmd.get('STEPPER', None)
if stepper_name is None:
self.report_stats()
return
info = self.tracking.get(stepper_name)
if info is None:
phase_calc = self.tracking.get(stepper_name)
if phase_calc is None or phase_calc.phase_history is None:
raise gcmd.error("Stats not available for stepper %s"
% (stepper_name,))
endstop_phase, phases = self.generate_stats(stepper_name, info)
endstop_phase, phases = self.generate_stats(stepper_name, phase_calc)
if not phase_calc.is_primary:
return
configfile = self.printer.lookup_object('configfile')
section = 'endstop_phase %s' % (stepper_name,)
configfile.remove_section(section)
@ -176,8 +193,8 @@ class EndstopPhases:
gcmd.respond_info(
"The SAVE_CONFIG command will update the printer config\n"
"file with these parameters and restart the printer.")
def generate_stats(self, stepper_name, info):
get_phase, phase_history = info
def generate_stats(self, stepper_name, phase_calc):
phase_history = phase_calc.phase_history
wph = phase_history + phase_history
count = sum(phase_history)
phases = len(phase_history)
@ -201,10 +218,17 @@ class EndstopPhases:
self.gcode.respond_info(
"No steppers found. (Be sure to home at least once.)")
return
for stepper_name, info in sorted(self.tracking.items()):
if info is None:
for stepper_name in sorted(self.tracking.keys()):
phase_calc = self.tracking[stepper_name]
if phase_calc is None or not phase_calc.is_primary:
continue
self.generate_stats(stepper_name, info)
self.generate_stats(stepper_name, phase_calc)
def get_status(self, eventtime):
lh = { name: {'phase': pc.last_phase, 'phases': pc.phases,
'mcu_position': pc.last_mcu_position}
for name, pc in self.tracking.items()
if pc.phase_history is not None }
return { 'last_home': lh }
def load_config_prefix(config):
return EndstopPhase(config)

View File

@ -16,8 +16,10 @@ class FirmwareRetraction:
+ self.unretract_extra_length)
self.is_retracted = False
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command('SET_RETRACTION', self.cmd_SET_RETRACTION)
self.gcode.register_command('GET_RETRACTION', self.cmd_GET_RETRACTION)
self.gcode.register_command('SET_RETRACTION', self.cmd_SET_RETRACTION,
desc=self.cmd_SET_RETRACTION_help)
self.gcode.register_command('GET_RETRACTION', self.cmd_GET_RETRACTION,
desc=self.cmd_GET_RETRACTION_help)
self.gcode.register_command('G10', self.cmd_G10)
self.gcode.register_command('G11', self.cmd_G11)
@ -28,7 +30,7 @@ class FirmwareRetraction:
"unretract_extra_length": self.unretract_extra_length,
"unretract_speed": self.unretract_speed,
}
cmd_SET_RETRACTION_help = ("Set firmware retraction parameters")
def cmd_SET_RETRACTION(self, gcmd):
self.retract_length = gcmd.get_float('RETRACT_LENGTH',
self.retract_length, minval=0.)
@ -41,7 +43,7 @@ class FirmwareRetraction:
self.unretract_length = (self.retract_length
+ self.unretract_extra_length)
self.is_retracted = False
cmd_GET_RETRACTION_help = ("Report firmware retraction paramters")
def cmd_GET_RETRACTION(self, gcmd):
gcmd.respond_info("RETRACT_LENGTH=%.5f RETRACT_SPEED=%.5f"
" UNRETRACT_EXTRA_LENGTH=%.5f UNRETRACT_SPEED=%.5f"

View File

@ -34,7 +34,8 @@ class GCodeMove:
gcode.register_command(cmd, func, False, desc)
gcode.register_command('G0', self.cmd_G1)
gcode.register_command('M114', self.cmd_M114, True)
gcode.register_command('GET_POSITION', self.cmd_GET_POSITION, True)
gcode.register_command('GET_POSITION', self.cmd_GET_POSITION, True,
desc=self.cmd_GET_POSITION_help)
self.Coord = gcode.Coord
# G-Code coordinate manipulation
self.absolute_coord = self.absolute_extrude = True
@ -239,6 +240,8 @@ class GCodeMove:
speed = gcmd.get_float('MOVE_SPEED', self.speed, above=0.)
self.last_position[:3] = state['last_position'][:3]
self.move_with_transform(self.last_position, speed)
cmd_GET_POSITION_help = (
"Return information on the current location of the toolhead")
def cmd_GET_POSITION(self, gcmd):
toolhead = self.printer.lookup_object('toolhead', None)
if toolhead is None:
@ -247,12 +250,10 @@ class GCodeMove:
steppers = kin.get_steppers()
mcu_pos = " ".join(["%s:%d" % (s.get_name(), s.get_mcu_position())
for s in steppers])
for s in steppers:
s.set_tag_position(s.get_commanded_position())
stepper_pos = " ".join(["%s:%.6f" % (s.get_name(), s.get_tag_position())
for s in steppers])
kin_pos = " ".join(["%s:%.6f" % (a, v)
for a, v in zip("XYZ", kin.calc_tag_position())])
cinfo = [(s.get_name(), s.get_commanded_position()) for s in steppers]
stepper_pos = " ".join(["%s:%.6f" % (a, v) for a, v in cinfo])
kinfo = zip("XYZ", kin.calc_position(dict(cinfo)))
kin_pos = " ".join(["%s:%.6f" % (a, v) for a, v in kinfo])
toolhead_pos = " ".join(["%s:%.6f" % (a, v) for a, v in zip(
"XYZE", toolhead.get_position())])
gcode_pos = " ".join(["%s:%.6f" % (a, v)

View File

@ -13,8 +13,13 @@ ENDSTOP_SAMPLE_COUNT = 4
def multi_complete(printer, completions):
if len(completions) == 1:
return completions[0]
cb = (lambda e: all([c.wait() for c in completions]))
return printer.get_reactor().register_callback(cb)
# Build completion that waits for all completions
reactor = printer.get_reactor()
cp = reactor.register_callback(lambda e: [c.wait() for c in completions])
# If any completion indicates an error, then exit main completion early
for c in completions:
reactor.register_callback(lambda e: cp.complete(1) if c.wait() else 0)
return cp
# Implementation of homing/probing moves
class HomingMove:
@ -46,8 +51,8 @@ class HomingMove:
# Note start location
self.toolhead.flush_step_generation()
kin = self.toolhead.get_kinematics()
for s in kin.get_steppers():
s.set_tag_position(s.get_commanded_position())
kin_spos = {s.get_name(): s.get_commanded_position()
for s in kin.get_steppers()}
start_mcu_pos = [(s, name, s.get_mcu_position())
for es, name in self.endstops
for s in es.get_steppers()]
@ -80,9 +85,10 @@ class HomingMove:
for s, name, spos in start_mcu_pos]
if probe_pos:
for s, name, spos, epos in self.end_mcu_pos:
md = (epos - spos) * s.get_step_dist()
s.set_tag_position(s.get_tag_position() + md)
movepos = list(kin.calc_tag_position())[:3] + movepos[3:]
sname = s.get_name()
if sname in kin_spos:
kin_spos[sname] += (epos - spos) * s.get_step_dist()
movepos = list(kin.calc_position(kin_spos))[:3] + movepos[3:]
self.toolhead.set_position(movepos)
# Signal homing/probing move complete
try:
@ -107,10 +113,13 @@ class Homing:
self.printer = printer
self.toolhead = printer.lookup_object('toolhead')
self.changed_axes = []
self.kin_spos = {}
def set_axes(self, axes):
self.changed_axes = axes
def get_axes(self):
return self.changed_axes
def get_stepper_trigger_positions(self):
return self.kin_spos
def _fill_coord(self, coord):
# Fill in any None entries in 'coord' with current toolhead position
thcoord = list(self.toolhead.get_position())
@ -155,12 +164,13 @@ class Homing:
# Signal home operation complete
self.toolhead.flush_step_generation()
kin = self.toolhead.get_kinematics()
for s in kin.get_steppers():
s.set_tag_position(s.get_commanded_position())
ret = self.printer.send_event("homing:home_rails_end", self, rails)
if any(ret):
kin_spos = {s.get_name(): s.get_commanded_position()
for s in kin.get_steppers()}
self.kin_spos = dict(kin_spos)
self.printer.send_event("homing:home_rails_end", self, rails)
if kin_spos != self.kin_spos:
# Apply any homing offsets
adjustpos = kin.calc_tag_position()
adjustpos = kin.calc_position(self.kin_spos)
for axis in homing_axes:
movepos[axis] = adjustpos[axis]
self.toolhead.set_position(movepos)

View File

@ -82,9 +82,9 @@ class ManualProbeHelper:
return self.last_kinematics_pos
self.toolhead.flush_step_generation()
kin = self.toolhead.get_kinematics()
for s in kin.get_steppers():
s.set_tag_position(s.get_commanded_position())
kin_pos = kin.calc_tag_position()
kin_spos = {s.get_name(): s.get_commanded_position()
for s in kin.get_steppers()}
kin_pos = kin.calc_position(kin_spos)
self.last_toolhead_pos = toolhead_pos
self.last_kinematics_pos = kin_pos
return kin_pos

View File

@ -119,6 +119,7 @@ class PrinterNeoPixel:
white = gcmd.get_float('WHITE', 0., minval=0., maxval=1.)
index = gcmd.get_int('INDEX', None, minval=1, maxval=self.chain_count)
transmit = gcmd.get_int('TRANSMIT', 1)
sync = gcmd.get_int('SYNC', 1)
# Update and transmit data
def reactor_bgfunc(print_time):
with self.mutex:
@ -128,8 +129,13 @@ class PrinterNeoPixel:
def lookahead_bgfunc(print_time):
reactor = self.printer.get_reactor()
reactor.register_callback(lambda et: reactor_bgfunc(print_time))
toolhead = self.printer.lookup_object('toolhead')
toolhead.register_lookahead_callback(lookahead_bgfunc)
if sync:
#Sync LED Update with print time and send
toolhead = self.printer.lookup_object('toolhead')
toolhead.register_lookahead_callback(lookahead_bgfunc)
else:
#Send update now (so as not to wake toolhead and reset idle_timeout)
lookahead_bgfunc(None)
def load_config_prefix(config):
return PrinterNeoPixel(config)

View File

@ -15,10 +15,14 @@ class PauseResume:
self.pause_command_sent = False
self.printer.register_event_handler("klippy:connect",
self.handle_connect)
self.gcode.register_command("PAUSE", self.cmd_PAUSE)
self.gcode.register_command("RESUME", self.cmd_RESUME)
self.gcode.register_command("CLEAR_PAUSE", self.cmd_CLEAR_PAUSE)
self.gcode.register_command("CANCEL_PRINT", self.cmd_CANCEL_PRINT)
self.gcode.register_command("PAUSE", self.cmd_PAUSE,
desc=self.cmd_PAUSE_help)
self.gcode.register_command("RESUME", self.cmd_RESUME,
desc=self.cmd_RESUME_help)
self.gcode.register_command("CLEAR_PAUSE", self.cmd_CLEAR_PAUSE,
desc=self.cmd_CLEAR_PAUSE_help)
self.gcode.register_command("CANCEL_PRINT", self.cmd_CANCEL_PRINT,
desc=self.cmd_CANCEL_PRINT_help)
webhooks = self.printer.lookup_object('webhooks')
webhooks.register_endpoint("pause_resume/cancel",
self._handle_cancel_request)
@ -38,12 +42,14 @@ class PauseResume:
return {
'is_paused': self.is_paused
}
def is_sd_active(self):
return self.v_sd is not None and self.v_sd.is_active()
def send_pause_command(self):
# This sends the appropriate pause command from an event. Note
# the difference between pause_command_sent and is_paused, the
# module isn't officially paused until the PAUSE gcode executes.
if not self.pause_command_sent:
if self.v_sd is not None and self.v_sd.is_active():
if self.is_sd_active():
# Printing from virtual sd, run pause command
self.sd_paused = True
self.v_sd.do_pause()
@ -51,6 +57,7 @@ class PauseResume:
self.sd_paused = False
self.gcode.respond_info("action:paused")
self.pause_command_sent = True
cmd_PAUSE_help = ("Pauses the current print")
def cmd_PAUSE(self, gcmd):
if self.is_paused:
gcmd.respond_info("Print already paused")
@ -66,6 +73,7 @@ class PauseResume:
else:
self.gcode.respond_info("action:resumed")
self.pause_command_sent = False
cmd_RESUME_help = ("Resumes the print from a pause")
def cmd_RESUME(self, gcmd):
if not self.is_paused:
gcmd.respond_info("Print is not paused, resume aborted")
@ -76,11 +84,15 @@ class PauseResume:
% (velocity))
self.send_resume_command()
self.is_paused = False
cmd_CLEAR_PAUSE_help = (
"Clears the current paused state without resuming the print")
def cmd_CLEAR_PAUSE(self, gcmd):
self.is_paused = self.pause_command_sent = False
cmd_CANCEL_PRINT_help = ("Cancel the current print")
def cmd_CANCEL_PRINT(self, gcmd):
self.cmd_PAUSE(gcmd)
if not self.sd_paused:
if self.is_sd_active() or self.sd_paused:
self.v_sd.do_cancel()
else:
gcmd.respond_info("action:cancel")
self.cmd_CLEAR_PAUSE(gcmd)

View File

@ -41,11 +41,15 @@ class PrintStats:
self._update_filament_usage(curtime)
if self.state != "error":
self.state = "paused"
def note_error(self, message):
self.state = "error"
self.error_message = message
def note_complete(self):
self.state = "complete"
self._note_finish("complete")
def note_error(self, message):
self._note_finish("error", message)
def note_cancel(self):
self._note_finish("cancelled")
def _note_finish(self, state, error_message = ""):
self.state = state
self.error_message = error_message
eventtime = self.reactor.monotonic()
self.total_duration = eventtime - self.print_start_time
if self.filament_used < 0.0000001:

View File

@ -280,8 +280,9 @@ class ProbeEndstopWrapper:
pin = config.get('pin')
pin_params = ppins.lookup_pin(pin, can_invert=True, can_pullup=True)
mcu = pin_params['chip']
mcu.register_config_callback(self._build_config)
self.mcu_endstop = mcu.setup_pin('endstop', pin_params)
self.printer.register_event_handler('klippy:mcu_identify',
self._handle_mcu_identify)
# Wrappers
self.get_mcu = self.mcu_endstop.get_mcu
self.add_stepper = self.mcu_endstop.add_stepper
@ -291,7 +292,7 @@ class ProbeEndstopWrapper:
self.query_endstop = self.mcu_endstop.query_endstop
# multi probes state
self.multi = 'OFF'
def _build_config(self):
def _handle_mcu_identify(self):
kin = self.printer.lookup_object('toolhead').get_kinematics()
for stepper in kin.get_steppers():
if stepper.is_active_axis('z'):

View File

@ -33,6 +33,7 @@ class QuadGantryLevel:
if len(self.probe_helper.probe_points) != 4:
raise config.error(
"Need exactly 4 probe points for quad_gantry_level")
self.z_status = z_tilt.ZAdjustStatus(self.printer)
self.z_helper = z_tilt.ZAdjustHelper(config, 4)
gantry_corners = config.get('gantry_corners').split('\n')
try:
@ -54,6 +55,7 @@ class QuadGantryLevel:
cmd_QUAD_GANTRY_LEVEL_help = (
"Conform a moving, twistable gantry to the shape of a stationary bed")
def cmd_QUAD_GANTRY_LEVEL(self, gcmd):
self.z_status.reset()
self.retry_helper.start(gcmd)
self.probe_helper.start_probe(gcmd)
def probe_finalize(self, offsets, positions):
@ -114,7 +116,9 @@ class QuadGantryLevel:
speed = self.probe_helper.get_lift_speed()
self.z_helper.adjust_steppers(z_adjust, speed)
return self.retry_helper.check_retry(z_positions)
return self.z_status.check_retry_result(
self.retry_helper.check_retry(z_positions))
def linefit(self,p1,p2):
if p1[1] == p2[1]:
# Straight line
@ -124,6 +128,8 @@ class QuadGantryLevel:
return m,b
def plot(self,f,x):
return f[0]*x + f[1]
def get_status(self, eventtime):
return self.z_status.get_status(eventtime)
def load_config(config):
return QuadGantryLevel(config)

View File

@ -32,25 +32,30 @@ class VibrationPulseTest:
return ['x', 'y']
def get_start_test_points(self):
return self.probe_points
def prepare_test(self, toolhead, gcmd):
def prepare_test(self, gcmd):
self.freq_start = gcmd.get_float("FREQ_START", self.min_freq, minval=1.)
self.freq_end = gcmd.get_float("FREQ_END", self.max_freq,
minval=self.freq_start, maxval=200.)
self.hz_per_sec = gcmd.get_float("HZ_PER_SEC", self.hz_per_sec,
above=0., maxval=2.)
# Attempt to adjust maximum acceleration and acceleration to
# deceleration based on the maximum test frequency.
max_accel = self.freq_end * self.accel_per_hz
toolhead.cmd_SET_VELOCITY_LIMIT(self.gcode.create_gcode_command(
"SET_VELOCITY_LIMIT", "SET_VELOCITY_LIMIT",
{"ACCEL": max_accel, "ACCEL_TO_DECEL": max_accel}))
def run_test(self, toolhead, axis, gcmd):
def run_test(self, axis, gcmd):
toolhead = self.printer.lookup_object('toolhead')
X, Y, Z, E = toolhead.get_position()
if axis not in self.get_supported_axes():
raise gcmd.error("Test axis '%s' is not supported", axis)
vib_dir = (1, 0) if axis == 'x' else (0., 1.)
sign = 1.
freq = self.freq_start
# Override maximum acceleration and acceleration to
# deceleration based on the maximum test frequency
systime = self.printer.get_reactor().monotonic()
toolhead_info = toolhead.get_status(systime)
old_max_accel = toolhead_info['max_accel']
old_max_accel_to_decel = toolhead_info['max_accel_to_decel']
max_accel = self.freq_end * self.accel_per_hz
self.gcode.run_script_from_command(
"SET_VELOCITY_LIMIT ACCEL=%.3f ACCEL_TO_DECEL=%.3f" % (
max_accel, max_accel))
input_shaper = self.printer.lookup_object('input_shaper', None)
if input_shaper is not None and not gcmd.get_int('INPUT_SHAPING', 0):
input_shaper.disable_shaping()
@ -58,23 +63,26 @@ class VibrationPulseTest:
else:
input_shaper = None
gcmd.respond_info("Testing frequency %.0f Hz" % (freq,))
_, max_accel = toolhead.get_max_velocity()
while freq <= self.freq_end + 0.000001:
t_seg = .25 / freq
accel = min(self.accel_per_hz * freq, max_accel)
V = accel * t_seg
accel = self.accel_per_hz * freq
max_v = accel * t_seg
toolhead.cmd_M204(self.gcode.create_gcode_command(
"M204", "M204", {"S": accel}))
L = .5 * accel * t_seg**2
nX = X + sign * vib_dir[0] * L
nY = Y + sign * vib_dir[1] * L
toolhead.move([nX, nY, Z, E], V)
toolhead.move([X, Y, Z, E], V)
toolhead.move([nX, nY, Z, E], max_v)
toolhead.move([X, Y, Z, E], max_v)
sign = -sign
old_freq = freq
freq += 2. * t_seg * self.hz_per_sec
if math.floor(freq) > math.floor(old_freq):
gcmd.respond_info("Testing frequency %.0f Hz" % (freq,))
# Restore the original acceleration values
self.gcode.run_script_from_command(
"SET_VELOCITY_LIMIT ACCEL=%.3f ACCEL_TO_DECEL=%.3f" % (
old_max_accel, old_max_accel_to_decel))
# Restore input shaper if it was disabled for resonance testing
if input_shaper is not None:
input_shaper.enable_shaping()
@ -97,11 +105,14 @@ class ResonanceTester:
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command("MEASURE_AXES_NOISE",
self.cmd_MEASURE_AXES_NOISE)
self.cmd_MEASURE_AXES_NOISE,
desc=self.cmd_MEASURE_AXES_NOISE_help)
self.gcode.register_command("TEST_RESONANCES",
self.cmd_TEST_RESONANCES)
self.cmd_TEST_RESONANCES,
desc=self.cmd_TEST_RESONANCES_help)
self.gcode.register_command("SHAPER_CALIBRATE",
self.cmd_SHAPER_CALIBRATE)
self.cmd_SHAPER_CALIBRATE,
desc=self.cmd_SHAPER_CALIBRATE_help)
self.printer.register_event_handler("klippy:connect", self.connect)
def connect(self):
@ -113,7 +124,7 @@ class ResonanceTester:
toolhead = self.printer.lookup_object('toolhead')
calibration_data = {axis: None for axis in axes}
self.test.prepare_test(toolhead, gcmd)
self.test.prepare_test(gcmd)
test_points = self.test.get_start_test_points()
for point in test_points:
toolhead.manual_move(point, self.move_speed)
@ -130,7 +141,7 @@ class ResonanceTester:
if axis in chip_axis or chip_axis in axis:
chip.start_measurements()
# Generate moves
self.test.run_test(toolhead, axis, gcmd)
self.test.run_test(axis, gcmd)
raw_values = []
for chip_axis, chip in self.accel_chips:
if axis in chip_axis or chip_axis in axis:
@ -159,7 +170,7 @@ class ResonanceTester:
else:
calibration_data[axis].add_data(new_data)
return calibration_data
cmd_TEST_RESONANCES_help = ("Runs the resonance test for a specifed axis")
def cmd_TEST_RESONANCES(self, gcmd):
# Parse parameters
if len(self.test.get_supported_axes()) > 1:
@ -197,7 +208,8 @@ class ResonanceTester:
helper, axis, data)
gcmd.respond_info(
"Resonances data written to %s file" % (csv_name,))
cmd_SHAPER_CALIBRATE_help = (
"Simular to TEST_RESONANCES but suggest input shaper config")
def cmd_SHAPER_CALIBRATE(self, gcmd):
# Parse parameters
axis = gcmd.get("AXIS", None)
@ -241,7 +253,8 @@ class ResonanceTester:
gcmd.respond_info(
"The SAVE_CONFIG command will update the printer config file\n"
"with these parameters and restart the printer.")
cmd_MEASURE_AXES_NOISE_help = (
"Measures noise of all enabled accelerometer chips")
def cmd_MEASURE_AXES_NOISE(self, gcmd):
meas_time = gcmd.get_float("MEAS_TIME", 2.)
for _, chip in self.accel_chips:

View File

@ -19,7 +19,8 @@ class HostResponder:
self.default_prefix = config.get('default_prefix', self.default_prefix)
gcode = self.printer.lookup_object('gcode')
gcode.register_command('M118', self.cmd_M118, True)
gcode.register_command('RESPOND', self.cmd_RESPOND, True)
gcode.register_command('RESPOND', self.cmd_RESPOND, True,
desc=self.cmd_RESPOND_help)
def cmd_M118(self, gcmd):
msg = gcmd.get_commandline()
umsg = msg.upper()
@ -33,6 +34,7 @@ class HostResponder:
else:
msg = ''
gcmd.respond_raw("%s %s" % (self.default_prefix, msg))
cmd_RESPOND_help = ("Echo the message prepended with a prefix")
def cmd_RESPOND(self, gcmd):
respond_type = gcmd.get('TYPE', None)
prefix = self.default_prefix

View File

@ -98,6 +98,13 @@ class VirtualSD:
self.must_pause_work = False
self.work_timer = self.reactor.register_timer(
self.work_handler, self.reactor.NOW)
def do_cancel(self):
if self.current_file is not None:
self.do_pause()
self.current_file.close()
self.current_file = None
self.print_stats.note_cancel()
self.file_position = self.file_size = 0.
# G-Code commands
def cmd_error(self, gcmd):
raise gcmd.error("SD write not supported")
@ -212,6 +219,7 @@ class VirtualSD:
gcode_mutex = self.gcode.get_mutex()
partial_input = ""
lines = []
error_message = None
while not self.must_pause_work:
if not lines:
# Read more data
@ -245,7 +253,7 @@ class VirtualSD:
try:
self.gcode.run_script(line)
except self.gcode.error as e:
self.print_stats.note_error(str(e))
error_message = str(e)
break
except:
logging.exception("virtual_sdcard dispatch")
@ -265,7 +273,9 @@ class VirtualSD:
logging.info("Exiting SD card print (position %d)", self.file_position)
self.work_timer = None
self.cmd_from_sd = False
if self.current_file is not None:
if error_message is not None:
self.print_stats.note_error(error_message)
elif self.current_file is not None:
self.print_stats.note_pause()
else:
self.print_stats.note_complete()

View File

@ -66,6 +66,22 @@ class ZAdjustHelper:
curpos[2] += first_stepper_offset
toolhead.set_position(curpos)
class ZAdjustStatus:
def __init__(self, printer):
self.applied = False
printer.register_event_handler("stepper_enable:motor_off",
self._motor_off)
def check_retry_result(self, retry_result):
if retry_result == "done":
self.applied = True
return retry_result
def reset(self):
self.applied = False
def get_status(self, eventtime):
return {'applied': self.applied}
def _motor_off(self, print_time):
self.reset()
class RetryHelper:
def __init__(self, config, error_msg_extra = ""):
self.gcode = config.get_printer().lookup_object('gcode')
@ -123,6 +139,7 @@ class ZTilt:
self.retry_helper = RetryHelper(config)
self.probe_helper = probe.ProbePointsHelper(config, self.probe_finalize)
self.probe_helper.minimum_points(2)
self.z_status = ZAdjustStatus(self.printer)
self.z_helper = ZAdjustHelper(config, len(self.z_positions))
# Register Z_TILT_ADJUST command
gcode = self.printer.lookup_object('gcode')
@ -130,6 +147,7 @@ class ZTilt:
desc=self.cmd_Z_TILT_ADJUST_help)
cmd_Z_TILT_ADJUST_help = "Adjust the Z tilt"
def cmd_Z_TILT_ADJUST(self, gcmd):
self.z_status.reset()
self.retry_helper.start(gcmd)
self.probe_helper.start_probe(gcmd)
def probe_finalize(self, offsets, positions):
@ -159,7 +177,10 @@ class ZTilt:
adjustments = [x*x_adjust + y*y_adjust + z_adjust
for x, y in self.z_positions]
self.z_helper.adjust_steppers(adjustments, speed)
return self.retry_helper.check_retry([p[2] for p in positions])
return self.z_status.check_retry_result(
self.retry_helper.check_retry([p[2] for p in positions]))
def get_status(self, eventtime):
return self.z_status.get_status(eventtime)
def load_config(config):
return ZTilt(config)

View File

@ -325,6 +325,7 @@ class GCodeDispatch:
msg = self.printer.get_state_message()[0]
msg = msg.rstrip() + "\nKlipper state: Not ready"
raise gcmd.error(msg)
cmd_HELP_help = "Report the list of available extended G-Code commands"
def cmd_HELP(self, gcmd):
cmdhelp = []
if not self.is_printer_ready:

View File

@ -51,8 +51,8 @@ class CartKinematics:
dca = self.dual_carriage_axis
rails = rails[:dca] + self.dual_carriage_rails + rails[dca+1:]
return [s for rail in rails for s in rail.get_steppers()]
def calc_tag_position(self):
return [rail.get_tag_position() for rail in self.rails]
def calc_position(self, stepper_positions):
return [stepper_positions[rail.get_name()] for rail in self.rails]
def set_position(self, newpos, homing_axes):
for i, rail in enumerate(self.rails):
rail.set_position(newpos)

View File

@ -36,8 +36,8 @@ class CoreXYKinematics:
self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.)
def get_steppers(self):
return [s for rail in self.rails for s in rail.get_steppers()]
def calc_tag_position(self):
pos = [rail.get_tag_position() for rail in self.rails]
def calc_position(self, stepper_positions):
pos = [stepper_positions[rail.get_name()] for rail in self.rails]
return [0.5 * (pos[0] + pos[1]), 0.5 * (pos[0] - pos[1]), pos[2]]
def set_position(self, newpos, homing_axes):
for i, rail in enumerate(self.rails):

View File

@ -36,8 +36,8 @@ class CoreXZKinematics:
self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.)
def get_steppers(self):
return [s for rail in self.rails for s in rail.get_steppers()]
def calc_tag_position(self):
pos = [rail.get_tag_position() for rail in self.rails]
def calc_position(self, stepper_positions):
pos = [stepper_positions[rail.get_name()] for rail in self.rails]
return [0.5 * (pos[0] + pos[2]), pos[1], 0.5 * (pos[0] - pos[2])]
def set_position(self, newpos, homing_axes):
for i, rail in enumerate(self.rails):

View File

@ -92,8 +92,8 @@ class DeltaKinematics:
def _actuator_to_cartesian(self, spos):
sphere_coords = [(t[0], t[1], sp) for t, sp in zip(self.towers, spos)]
return mathutil.trilateration(sphere_coords, self.arm2)
def calc_tag_position(self):
spos = [rail.get_tag_position() for rail in self.rails]
def calc_position(self, stepper_positions):
spos = [stepper_positions[rail.get_name()] for rail in self.rails]
return self._actuator_to_cartesian(spos)
def set_position(self, newpos, homing_axes):
for rail in self.rails:

View File

@ -91,6 +91,7 @@ class PrinterExtruder:
self.pressure_advance_smooth_time = smooth_time
def get_status(self, eventtime):
return dict(self.heater.get_status(eventtime),
can_extrude=self.heater.can_extrude,
pressure_advance=self.pressure_advance,
smooth_time=self.pressure_advance_smooth_time)
def get_name(self):
@ -152,9 +153,7 @@ class PrinterExtruder:
1., pressure_advance, 0.,
start_v, cruise_v, accel)
def find_past_position(self, print_time):
mcu = self.stepper.get_mcu()
clock = mcu.print_time_to_clock(print_time)
return self.stepper.get_past_commanded_position(clock)
return self.stepper.get_past_commanded_position(print_time)
def cmd_M104(self, gcmd, wait=False):
# Set Extruder Temperature
temp = gcmd.get_float('S', 0.)

View File

@ -37,8 +37,8 @@ class HybridCoreXYKinematics:
self.limits = [(1.0, -1.0)] * 3
def get_steppers(self):
return [s for rail in self.rails for s in rail.get_steppers()]
def calc_tag_position(self):
pos = [rail.get_tag_position() for rail in self.rails]
def calc_position(self, stepper_positions):
pos = [stepper_positions[rail.get_name()] for rail in self.rails]
return [pos[0] + pos[1], pos[1], pos[2]]
def set_position(self, newpos, homing_axes):
for i, rail in enumerate(self.rails):

View File

@ -37,8 +37,8 @@ class HybridCoreXZKinematics:
self.limits = [(1.0, -1.0)] * 3
def get_steppers(self):
return [s for rail in self.rails for s in rail.get_steppers()]
def calc_tag_position(self):
pos = [rail.get_tag_position() for rail in self.rails]
def calc_position(self, stepper_positions):
pos = [stepper_positions[rail.get_name()] for rail in self.rails]
return [pos[0] + pos[2], pos[1], pos[2]]
def set_position(self, newpos, homing_axes):
for i, rail in enumerate(self.rails):

View File

@ -9,7 +9,7 @@ class NoneKinematics:
self.axes_minmax = toolhead.Coord(0., 0., 0., 0.)
def get_steppers(self):
return []
def calc_tag_position(self):
def calc_position(self, stepper_positions):
return [0, 0, 0]
def set_position(self, newpos, homing_axes):
pass

View File

@ -38,10 +38,10 @@ class PolarKinematics:
self.axes_max = toolhead.Coord(max_xy, max_xy, max_z, 0.)
def get_steppers(self):
return list(self.steppers)
def calc_tag_position(self):
bed_angle = self.steppers[0].get_tag_position()
arm_pos = self.rails[0].get_tag_position()
z_pos = self.rails[1].get_tag_position()
def calc_position(self, stepper_positions):
bed_angle = stepper_positions[self.steppers[0].get_name()]
arm_pos = stepper_positions[self.rails[0].get_name()]
z_pos = stepper_positions[self.rails[1].get_name()]
return [math.cos(bed_angle) * arm_pos, math.sin(bed_angle) * arm_pos,
z_pos]
def set_position(self, newpos, homing_axes):

View File

@ -79,8 +79,8 @@ class RotaryDeltaKinematics:
self.set_position([0., 0., 0.], ())
def get_steppers(self):
return [s for rail in self.rails for s in rail.get_steppers()]
def calc_tag_position(self):
spos = [rail.get_tag_position() for rail in self.rails]
def calc_position(self, stepper_positions):
spos = [stepper_positions[rail.get_name()] for rail in self.rails]
return self.calibration.actuator_to_cartesian(spos)
def set_position(self, newpos, homing_axes):
for rail in self.rails:

View File

@ -29,10 +29,10 @@ class WinchKinematics:
self.set_position([0., 0., 0.], ())
def get_steppers(self):
return list(self.steppers)
def calc_tag_position(self):
def calc_position(self, stepper_positions):
# Use only first three steppers to calculate cartesian position
spos = [s.get_tag_position() for s in self.steppers[:3]]
return mathutil.trilateration(self.anchors[:3], [sp*sp for sp in spos])
pos = [stepper_positions[rail.get_name()] for rail in self.steppers[:3]]
return mathutil.trilateration(self.anchors[:3], [sp*sp for sp in pos])
def set_position(self, newpos, homing_axes):
for s in self.steppers:
s.set_position(newpos)

View File

@ -162,11 +162,11 @@ class Printer:
cb()
except (self.config_error, pins.error) as e:
logging.exception("Config error")
self._set_state("%s%s" % (str(e), message_restart))
self._set_state("%s\n%s" % (str(e), message_restart))
return
except msgproto.error as e:
logging.exception("Protocol error")
self._set_state("%s%s%s%s" % (str(e), message_protocol_error1,
self._set_state("%s\n%s%s%s" % (str(e), message_protocol_error1,
self._get_versions(),
message_protocol_error2))
util.dump_mcu_build()

View File

@ -4,53 +4,162 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, os, zlib, logging, math
import serialhdl, pins, chelper, clocksync
import serialhdl, msgproto, pins, chelper, clocksync
class error(Exception):
pass
class MCU_endstop:
RETRY_QUERY = 1.000
def __init__(self, mcu, pin_params):
class MCU_trsync:
REASON_ENDSTOP_HIT = 1
REASON_COMMS_TIMEOUT = 2
REASON_HOST_REQUEST = 3
REASON_PAST_END_TIME = 4
def __init__(self, mcu, trdispatch):
self._mcu = mcu
self._steppers = []
self._pin = pin_params['pin']
self._pullup = pin_params['pullup']
self._invert = pin_params['invert']
self._trdispatch = trdispatch
self._reactor = mcu.get_printer().get_reactor()
self._oid = self._home_cmd = self._requery_cmd = self._query_cmd = None
self._mcu.register_config_callback(self._build_config)
self._min_query_time = self._last_sent_time = 0.
self._next_query_print_time = self._end_home_time = 0.
self._trigger_completion = self._home_completion = None
self._steppers = []
self._trdispatch_mcu = None
self._oid = mcu.create_oid()
self._cmd_queue = mcu.alloc_command_queue()
self._trsync_start_cmd = self._trsync_set_timeout_cmd = None
self._trsync_trigger_cmd = self._trsync_query_cmd = None
self._stepper_stop_cmd = None
self._trigger_completion = None
self._home_end_clock = None
mcu.register_config_callback(self._build_config)
printer = mcu.get_printer()
printer.register_event_handler("klippy:shutdown", self._shutdown)
def get_mcu(self):
return self._mcu
def get_oid(self):
return self._oid
def get_command_queue(self):
return self._cmd_queue
def add_stepper(self, stepper):
if stepper.get_mcu() is not self._mcu:
raise pins.error("Endstop and stepper must be on the same mcu")
if stepper in self._steppers:
return
self._steppers.append(stepper)
def get_steppers(self):
return list(self._steppers)
def _build_config(self):
mcu = self._mcu
# Setup config
mcu.add_config_cmd("config_trsync oid=%d" % (self._oid,))
mcu.add_config_cmd(
"trsync_start oid=%d report_clock=0 report_ticks=0 expire_reason=0"
% (self._oid,), on_restart=True)
# Lookup commands
self._trsync_start_cmd = mcu.lookup_command(
"trsync_start oid=%c report_clock=%u report_ticks=%u"
" expire_reason=%c", cq=self._cmd_queue)
self._trsync_set_timeout_cmd = mcu.lookup_command(
"trsync_set_timeout oid=%c clock=%u", cq=self._cmd_queue)
self._trsync_trigger_cmd = mcu.lookup_command(
"trsync_trigger oid=%c reason=%c", cq=self._cmd_queue)
self._trsync_query_cmd = mcu.lookup_query_command(
"trsync_trigger oid=%c reason=%c",
"trsync_state oid=%c can_trigger=%c trigger_reason=%c clock=%u",
oid=self._oid, cq=self._cmd_queue)
self._stepper_stop_cmd = mcu.lookup_command(
"stepper_stop_on_trigger oid=%c trsync_oid=%c", cq=self._cmd_queue)
# Create trdispatch_mcu object
set_timeout_tag = mcu.lookup_command_tag(
"trsync_set_timeout oid=%c clock=%u")
trigger_tag = mcu.lookup_command_tag("trsync_trigger oid=%c reason=%c")
state_tag = mcu.lookup_command_tag(
"trsync_state oid=%c can_trigger=%c trigger_reason=%c clock=%u")
ffi_main, ffi_lib = chelper.get_ffi()
self._trdispatch_mcu = ffi_main.gc(ffi_lib.trdispatch_mcu_alloc(
self._trdispatch, mcu._serial.serialqueue, # XXX
self._cmd_queue, self._oid, set_timeout_tag, trigger_tag,
state_tag), ffi_lib.free)
def _shutdown(self):
tc = self._trigger_completion
if tc is not None:
self._trigger_completion = None
tc.complete(False)
def _handle_trsync_state(self, params):
if not params['can_trigger']:
tc = self._trigger_completion
if tc is not None:
self._trigger_completion = None
reason = params['trigger_reason']
is_failure = (reason == self.REASON_COMMS_TIMEOUT)
self._reactor.async_complete(tc, is_failure)
elif self._home_end_clock is not None:
clock = self._mcu.clock32_to_clock64(params['clock'])
if clock >= self._home_end_clock:
self._home_end_clock = None
self._trsync_trigger_cmd.send([self._oid,
self.REASON_PAST_END_TIME])
def start(self, print_time, trigger_completion, expire_timeout):
self._trigger_completion = trigger_completion
self._home_end_clock = None
clock = self._mcu.print_time_to_clock(print_time)
expire_ticks = self._mcu.seconds_to_clock(expire_timeout)
expire_clock = clock + expire_ticks
report_ticks = self._mcu.seconds_to_clock(expire_timeout * .4)
min_extend_ticks = self._mcu.seconds_to_clock(expire_timeout * .4 * .8)
ffi_main, ffi_lib = chelper.get_ffi()
ffi_lib.trdispatch_mcu_setup(self._trdispatch_mcu, clock, expire_clock,
expire_ticks, min_extend_ticks)
self._mcu.register_response(self._handle_trsync_state,
"trsync_state", self._oid)
self._trsync_start_cmd.send([self._oid, clock, report_ticks,
self.REASON_COMMS_TIMEOUT])
for s in self._steppers:
self._stepper_stop_cmd.send([s.get_oid(), self._oid])
self._trsync_set_timeout_cmd.send([self._oid, expire_clock])
def set_home_end_time(self, home_end_time):
self._home_end_clock = self._mcu.print_time_to_clock(home_end_time)
def stop(self):
self._mcu.register_response(None, "trsync_state", self._oid)
self._trigger_completion = None
if self._mcu.is_fileoutput():
return self.REASON_ENDSTOP_HIT
params = self._trsync_query_cmd.send([self._oid,
self.REASON_HOST_REQUEST])
for s in self._steppers:
s.note_homing_end(did_trigger=True) # XXX
return params['trigger_reason']
class MCU_endstop:
RETRY_QUERY = 1.000
def __init__(self, mcu, pin_params):
self._mcu = mcu
self._pin = pin_params['pin']
self._pullup = pin_params['pullup']
self._invert = pin_params['invert']
self._oid = self._mcu.create_oid()
self._mcu.add_config_cmd(
"config_endstop oid=%d pin=%s pull_up=%d stepper_count=%d" % (
self._oid, self._pin, self._pullup, len(self._steppers)))
self._home_cmd = self._query_cmd = None
self._mcu.register_config_callback(self._build_config)
self._trigger_completion = None
ffi_main, ffi_lib = chelper.get_ffi()
self._trdispatch = ffi_main.gc(ffi_lib.trdispatch_alloc(), ffi_lib.free)
self._trsync = MCU_trsync(mcu, self._trdispatch)
def get_mcu(self):
return self._mcu
def add_stepper(self, stepper):
if stepper.get_mcu() is not self._mcu:
raise pins.error("Endstop and stepper must be on the same mcu")
self._trsync.add_stepper(stepper)
def get_steppers(self):
return self._trsync.get_steppers()
def _build_config(self):
# Setup config
self._mcu.add_config_cmd("config_endstop oid=%d pin=%s pull_up=%d"
% (self._oid, self._pin, self._pullup))
self._mcu.add_config_cmd(
"endstop_home oid=%d clock=0 sample_ticks=0 sample_count=0"
" rest_ticks=0 pin_value=0" % (self._oid,), on_restart=True)
for i, s in enumerate(self._steppers):
self._mcu.add_config_cmd(
"endstop_set_stepper oid=%d pos=%d stepper_oid=%d" % (
self._oid, i, s.get_oid()), is_init=True)
cmd_queue = self._mcu.alloc_command_queue()
" rest_ticks=0 pin_value=0 trsync_oid=0 trigger_reason=0"
% (self._oid,), on_restart=True)
# Lookup commands
cmd_queue = self._trsync.get_command_queue()
self._home_cmd = self._mcu.lookup_command(
"endstop_home oid=%c clock=%u sample_ticks=%u sample_count=%c"
" rest_ticks=%u pin_value=%c", cq=cmd_queue)
self._requery_cmd = self._mcu.lookup_command(
"endstop_query_state oid=%c", cq=cmd_queue)
" rest_ticks=%u pin_value=%c trsync_oid=%c trigger_reason=%c",
cq=cmd_queue)
self._query_cmd = self._mcu.lookup_query_command(
"endstop_query_state oid=%c",
"endstop_state oid=%c homing=%c next_clock=%u pin_value=%c",
@ -59,56 +168,28 @@ class MCU_endstop:
triggered=True):
clock = self._mcu.print_time_to_clock(print_time)
rest_ticks = self._mcu.print_time_to_clock(print_time+rest_time) - clock
self._next_query_print_time = print_time + self.RETRY_QUERY
self._min_query_time = self._reactor.monotonic()
self._last_sent_time = 0.
self._home_end_time = self._reactor.NEVER
self._trigger_completion = self._reactor.completion()
self._mcu.register_response(self._handle_endstop_state,
"endstop_state", self._oid)
reactor = self._mcu.get_printer().get_reactor()
self._trigger_completion = reactor.completion()
etrsync = self._trsync
etrsync.start(print_time, self._trigger_completion, .250)
ffi_main, ffi_lib = chelper.get_ffi()
ffi_lib.trdispatch_start(self._trdispatch, etrsync.REASON_HOST_REQUEST)
self._home_cmd.send(
[self._oid, clock, self._mcu.seconds_to_clock(sample_time),
sample_count, rest_ticks, triggered ^ self._invert],
reqclock=clock)
self._home_completion = self._reactor.register_callback(
self._home_retry)
sample_count, rest_ticks, triggered ^ self._invert,
etrsync.get_oid(), etrsync.REASON_ENDSTOP_HIT], reqclock=clock)
return self._trigger_completion
def _handle_endstop_state(self, params):
logging.debug("endstop_state %s", params)
if params['#sent_time'] >= self._min_query_time:
if params['homing']:
self._last_sent_time = params['#sent_time']
else:
self._min_query_time = self._reactor.NEVER
self._reactor.async_complete(self._trigger_completion, True)
def _home_retry(self, eventtime):
if self._mcu.is_fileoutput():
return True
while 1:
did_trigger = self._trigger_completion.wait(eventtime + 0.100)
if did_trigger is not None:
# Homing completed successfully
return True
# Check for timeout
last = self._mcu.estimated_print_time(self._last_sent_time)
if last > self._home_end_time or self._mcu.is_shutdown():
return False
# Check for resend
eventtime = self._reactor.monotonic()
est_print_time = self._mcu.estimated_print_time(eventtime)
if est_print_time >= self._next_query_print_time:
self._next_query_print_time = est_print_time + self.RETRY_QUERY
self._requery_cmd.send([self._oid])
def home_wait(self, home_end_time):
self._home_end_time = home_end_time
did_trigger = self._home_completion.wait()
self._mcu.register_response(None, "endstop_state", self._oid)
self._home_cmd.send([self._oid, 0, 0, 0, 0, 0])
for s in self._steppers:
s.note_homing_end(did_trigger=did_trigger)
if not self._trigger_completion.test():
self._trigger_completion.complete(False)
return did_trigger
etrsync = self._trsync
etrsync.set_home_end_time(home_end_time)
if self._mcu.is_fileoutput():
self._trigger_completion.complete(True)
self._trigger_completion.wait()
self._home_cmd.send([self._oid, 0, 0, 0, 0, 0, 0, 0])
ffi_main, ffi_lib = chelper.get_ffi()
ffi_lib.trdispatch_stop(self._trdispatch)
res = etrsync.stop()
return res == etrsync.REASON_ENDSTOP_HIT
def query_endstop(self, print_time):
clock = self._mcu.print_time_to_clock(print_time)
if self._mcu.is_fileoutput():
@ -415,7 +496,8 @@ class MCU:
if self._name.startswith('mcu '):
self._name = self._name[4:]
# Serial port
self._serial = serialhdl.SerialReader(self._reactor)
wp = "mcu '%s': " % (self._name)
self._serial = serialhdl.SerialReader(self._reactor, warn_prefix=wp)
self._baud = 0
self._canbus_iface = None
canbus_uuid = config.get('canbus_uuid', None)
@ -547,17 +629,26 @@ class MCU:
raise error("MCU '%s' CRC does not match config" % (self._name,))
# Transmit config messages (if needed)
self.register_response(self._handle_starting, 'starting')
if prev_crc is None:
logging.info("Sending MCU '%s' printer configuration...",
self._name)
for c in self._config_cmds:
try:
if prev_crc is None:
logging.info("Sending MCU '%s' printer configuration...",
self._name)
for c in self._config_cmds:
self._serial.send(c)
else:
for c in self._restart_cmds:
self._serial.send(c)
# Transmit init messages
for c in self._init_cmds:
self._serial.send(c)
else:
for c in self._restart_cmds:
self._serial.send(c)
# Transmit init messages
for c in self._init_cmds:
self._serial.send(c)
except msgproto.enumeration_error as e:
enum_name, enum_value = e.get_enum_params()
if enum_name == 'pin':
# Raise pin name errors as a config error (not a protocol error)
raise self._printer.config_error(
"Pin '%s' is not a valid pin name on mcu '%s'"
% (enum_value, self._name))
raise
def _send_get_config(self):
get_config_cmd = self.lookup_query_command(
"get_config",

View File

@ -86,12 +86,14 @@ class PT_progmem_buffer(PT_string):
class PT_buffer(PT_string):
pass
MessageTypes = {
'%u': PT_uint32(), '%i': PT_int32(),
'%hu': PT_uint16(), '%hi': PT_int16(),
'%c': PT_byte(),
'%s': PT_string(), '%.*s': PT_progmem_buffer(), '%*s': PT_buffer(),
}
class enumeration_error(error):
def __init__(self, enum_name, value):
self.enum_name = enum_name
self.value = value
error.__init__(self, "Unknown value '%s' in enumeration '%s'"
% (value, enum_name))
def get_enum_params(self):
return self.enum_name, self.value
class Enumeration:
is_int = False
@ -105,8 +107,7 @@ class Enumeration:
def encode(self, out, v):
tv = self.enums.get(v)
if tv is None:
raise error("Unknown value '%s' in enumeration '%s'" % (
v, self.enum_name))
raise enumeration_error(self.enum_name, v)
self.pt.encode(out, tv)
def parse(self, s, pos):
v, pos = self.pt.parse(s, pos)
@ -115,6 +116,13 @@ class Enumeration:
tv = "?%d" % (v,)
return tv, pos
MessageTypes = {
'%u': PT_uint32(), '%i': PT_int32(),
'%hu': PT_uint16(), '%hi': PT_int16(),
'%c': PT_byte(),
'%s': PT_string(), '%.*s': PT_progmem_buffer(), '%*s': PT_buffer(),
}
# Lookup the message types for a format string
def lookup_params(msgformat, enumerations={}):
out = []
@ -221,7 +229,8 @@ class UnknownFormat:
class MessageParser:
error = error
def __init__(self):
def __init__(self, warn_prefix=""):
self.warn_prefix = warn_prefix
self.unknown = UnknownFormat()
self.enumerations = {}
self.messages = []
@ -231,6 +240,8 @@ class MessageParser:
self.version = self.build_versions = ""
self.raw_identify_data = ""
self._init_messages(DefaultMessages)
def _error(self, msg, *params):
raise error(self.warn_prefix + (msg % params))
def check_packet(self, s):
if len(s) < MESSAGE_MIN:
return 0
@ -277,7 +288,7 @@ class MessageParser:
mid = self.messages_by_id.get(msgid, self.unknown)
params, pos = mid.parse(s, MESSAGE_HEADER_SIZE)
if pos != len(s)-MESSAGE_TRAILER_SIZE:
raise error("Extra data at end of message")
self._error("Extra data at end of message")
params['#name'] = mid.name
return params
def encode(self, seq, cmd):
@ -302,10 +313,10 @@ class MessageParser:
msgname = parts[0]
mp = self.messages_by_name.get(msgname)
if mp is None:
raise error("Unknown command: %s" % (msgname,))
self._error("Unknown command: %s", msgname)
if msgformat != mp.msgformat:
raise error("Command format mismatch: %s vs %s" % (
msgformat, mp.msgformat))
self._error("Command format mismatch: %s vs %s",
msgformat, mp.msgformat)
return mp
def create_command(self, msg):
parts = msg.strip().split()
@ -314,7 +325,7 @@ class MessageParser:
msgname = parts[0]
mp = self.messages_by_name.get(msgname)
if mp is None:
raise error("Unknown command: %s" % (msgname,))
self._error("Unknown command: %s", msgname)
try:
argparts = dict(arg.split('=', 1) for arg in parts[1:])
for name, value in argparts.items():
@ -330,14 +341,14 @@ class MessageParser:
raise
except:
#logging.exception("Unable to extract params")
raise error("Unable to extract params from: %s" % (msgname,))
self._error("Unable to extract params from: %s", msgname)
try:
cmd = mp.encode_by_name(**argparts)
except error as e:
raise
except:
#logging.exception("Unable to encode")
raise error("Unable to encode: %s" % (msgname,))
self._error("Unable to encode: %s", msgname)
return cmd
def fill_enumerations(self, enumerations):
for add_name, add_enums in enumerations.items():
@ -366,7 +377,7 @@ class MessageParser:
msgtype = 'output'
self.messages.append((msgtag, msgtype, msgformat))
if msgtag < -32 or msgtag > 95:
raise error("Multi-byte msgtag not supported")
self._error("Multi-byte msgtag not supported")
msgid = msgtag & 0x7f
if msgtype == 'output':
self.messages_by_id[msgid] = OutputFormat(msgid, msgformat)
@ -396,7 +407,7 @@ class MessageParser:
raise
except Exception as e:
logging.exception("process_identify error")
raise error("Error during identify: %s" % (str(e),))
self._error("Error during identify: %s", str(e))
def get_version_info(self):
return self.version, self.build_versions
def get_messages(self):
@ -410,12 +421,12 @@ class MessageParser:
if name not in self.config:
if default is not self.sentinel:
return default
raise error("Firmware constant '%s' not found" % (name,))
self._error("Firmware constant '%s' not found", name)
try:
value = parser(self.config[name])
except:
raise error("Unable to parse firmware constant %s: %s" % (
name, self.config[name]))
self._error("Unable to parse firmware constant %s: %s",
name, self.config[name])
return value
def get_constant_float(self, name, default=sentinel):
return self.get_constant(name, default, parser=float)

View File

@ -13,11 +13,12 @@ class error(Exception):
class SerialReader:
BITS_PER_BYTE = 10.
def __init__(self, reactor):
def __init__(self, reactor, warn_prefix=""):
self.reactor = reactor
self.warn_prefix = warn_prefix
# Serial port
self.serial_dev = None
self.msgparser = msgproto.MessageParser()
self.msgparser = msgproto.MessageParser(warn_prefix=warn_prefix)
# C interface
self.ffi_main, self.ffi_lib = chelper.get_ffi()
self.serialqueue = None
@ -55,7 +56,10 @@ class SerialReader:
hdl = self.handlers.get(hdl, self.handle_default)
hdl(params)
except:
logging.exception("Exception in serial callback")
logging.exception("%sException in serial callback",
self.warn_prefix)
def _error(self, msg, *params):
raise error(self.warn_prefix + (msg % params))
def _get_identify_data(self, eventtime):
# Query the "data dictionary" from the micro-controller
identify_data = ""
@ -64,7 +68,8 @@ class SerialReader:
try:
params = self.send_with_response(msg, 'identify_response')
except error as e:
logging.exception("Wait for identify_response")
logging.exception("%sWait for identify_response",
self.warn_prefix)
return None
if params['offset'] == len(identify_data):
msgdata = params['data']
@ -84,10 +89,10 @@ class SerialReader:
completion = self.reactor.register_callback(self._get_identify_data)
identify_data = completion.wait(self.reactor.monotonic() + 5.)
if identify_data is None:
logging.info("Timeout on connect")
logging.info("%sTimeout on connect", self.warn_prefix)
self.disconnect()
return False
msgparser = msgproto.MessageParser()
msgparser = msgproto.MessageParser(warn_prefix=self.warn_prefix)
msgparser.process_identify(identify_data)
self.msgparser = msgparser
self.register_response(self.handle_unknown, '#unknown')
@ -112,7 +117,7 @@ class SerialReader:
except ValueError:
uuid = -1
if uuid < 0 or uuid > 0xffffffffffff:
raise error("Invalid CAN uuid")
self._error("Invalid CAN uuid")
uuid = [(uuid >> (40 - i*8)) & 0xff for i in range(6)]
CANBUS_ID_ADMIN = 0x3f0
CMD_SET_NODEID = 0x01
@ -120,18 +125,19 @@ class SerialReader:
set_id_msg = can.Message(arbitration_id=CANBUS_ID_ADMIN,
data=set_id_cmd, is_extended_id=False)
# Start connection attempt
logging.info("Starting CAN connect")
logging.info("%sStarting CAN connect", self.warn_prefix)
start_time = self.reactor.monotonic()
while 1:
if self.reactor.monotonic() > start_time + 90.:
raise error("Unable to connect")
self._error("Unable to connect")
try:
bus = can.interface.Bus(channel=canbus_iface,
can_filters=filters,
bustype='socketcan')
bus.send(set_id_msg)
except can.CanError as e:
logging.warn("Unable to open CAN port: %s", e)
logging.warn("%sUnable to open CAN port: %s",
self.warn_prefix, e)
self.reactor.pause(self.reactor.monotonic() + 5.)
continue
bus.close = bus.shutdown # XXX
@ -145,19 +151,21 @@ class SerialReader:
if got_uuid == bytearray(uuid):
break
except:
logging.exception("Error in canbus_uuid check")
logging.info("Failed to match canbus_uuid - retrying..")
logging.exception("%sError in canbus_uuid check",
self.warn_prefix)
logging.info("%sFailed to match canbus_uuid - retrying..",
self.warn_prefix)
self.disconnect()
def connect_pipe(self, filename):
logging.info("Starting connect")
logging.info("%sStarting connect", self.warn_prefix)
start_time = self.reactor.monotonic()
while 1:
if self.reactor.monotonic() > start_time + 90.:
raise error("Unable to connect")
self._error("Unable to connect")
try:
fd = os.open(filename, os.O_RDWR | os.O_NOCTTY)
except OSError as e:
logging.warn("Unable to open port: %s", e)
logging.warn("%sUnable to open port: %s", self.warn_prefix, e)
self.reactor.pause(self.reactor.monotonic() + 5.)
continue
serial_dev = os.fdopen(fd, 'rb+', 0)
@ -166,11 +174,11 @@ class SerialReader:
break
def connect_uart(self, serialport, baud, rts=True):
# Initial connection
logging.info("Starting serial connect")
logging.info("%sStarting serial connect", self.warn_prefix)
start_time = self.reactor.monotonic()
while 1:
if self.reactor.monotonic() > start_time + 90.:
raise error("Unable to connect")
self._error("Unable to connect")
try:
serial_dev = serial.Serial(baudrate=baud, timeout=0,
exclusive=True)
@ -178,7 +186,8 @@ class SerialReader:
serial_dev.rts = rts
serial_dev.open()
except (OSError, IOError, serial.SerialException) as e:
logging.warn("Unable to open serial port: %s", e)
logging.warn("%sUnable to open serial port: %s",
self.warn_prefix, e)
self.reactor.pause(self.reactor.monotonic() + 5.)
continue
stk500v2_leave(serial_dev, self.reactor)
@ -191,9 +200,9 @@ class SerialReader:
self.serialqueue = self.ffi_main.gc(
self.ffi_lib.serialqueue_alloc(self.serial_dev.fileno(), 'f', 0),
self.ffi_lib.serialqueue_free)
def set_clock_est(self, freq, last_time, last_clock):
def set_clock_est(self, freq, conv_time, conv_clock, last_clock):
self.ffi_lib.serialqueue_set_clock_est(
self.serialqueue, freq, last_time, last_clock)
self.serialqueue, freq, conv_time, conv_clock, last_clock)
def disconnect(self):
if self.serialqueue is not None:
self.ffi_lib.serialqueue_exit(self.serialqueue)
@ -238,7 +247,7 @@ class SerialReader:
cmd, len(cmd), minclock, reqclock, nid)
params = completion.wait()
if params is None:
raise error("Serial connection closed")
self._error("Serial connection closed")
return params
def send(self, msg, minclock=0, reqclock=0):
cmd = self.msgparser.create_command(msg)
@ -276,15 +285,16 @@ class SerialReader:
return '\n'.join(out)
# Default message handlers
def _handle_unknown_init(self, params):
logging.debug("Unknown message %d (len %d) while identifying",
params['#msgid'], len(params['#msg']))
logging.debug("%sUnknown message %d (len %d) while identifying",
self.warn_prefix, params['#msgid'], len(params['#msg']))
def handle_unknown(self, params):
logging.warn("Unknown message type %d: %s",
params['#msgid'], repr(params['#msg']))
logging.warn("%sUnknown message type %d: %s",
self.warn_prefix, params['#msgid'], repr(params['#msg']))
def handle_output(self, params):
logging.info("%s: %s", params['#name'], params['#msg'])
logging.info("%s%s: %s", self.warn_prefix,
params['#name'], params['#msg'])
def handle_default(self, params):
logging.warn("got %s", params)
logging.warn("%sgot %s", self.warn_prefix, params)
# Class to send a query command and return the received response
class SerialRetryCommand:

View File

@ -31,7 +31,7 @@ class MCU_stepper:
"Stepper dir pin must be on same mcu as step pin")
self._dir_pin = dir_pin_params['pin']
self._invert_dir = dir_pin_params['invert']
self._mcu_position_offset = self._tag_position = 0.
self._mcu_position_offset = 0.
self._reset_cmd_tag = self._get_position_cmd = None
self._active_callbacks = []
ffi_main, ffi_lib = chelper.get_ffi()
@ -84,8 +84,10 @@ class MCU_stepper:
def get_step_dist(self):
return self._step_dist
def set_step_dist(self, dist):
mcu_pos = self.get_mcu_position()
self._step_dist = dist
self.set_stepper_kinematics(self._stepper_kinematics)
self._set_mcu_position(mcu_pos)
def is_dir_inverted(self):
return self._invert_dir
def calc_position_from_coord(self, coord):
@ -93,38 +95,40 @@ class MCU_stepper:
return ffi_lib.itersolve_calc_position_from_coord(
self._stepper_kinematics, coord[0], coord[1], coord[2])
def set_position(self, coord):
opos = self.get_commanded_position()
mcu_pos = self.get_mcu_position()
sk = self._stepper_kinematics
ffi_main, ffi_lib = chelper.get_ffi()
ffi_lib.itersolve_set_position(sk, coord[0], coord[1], coord[2])
self._mcu_position_offset += opos - self.get_commanded_position()
self._set_mcu_position(mcu_pos)
def get_commanded_position(self):
sk = self._stepper_kinematics
ffi_main, ffi_lib = chelper.get_ffi()
return ffi_lib.itersolve_get_commanded_pos(sk)
return ffi_lib.itersolve_get_commanded_pos(self._stepper_kinematics)
def get_mcu_position(self):
mcu_pos_dist = self.get_commanded_position() + self._mcu_position_offset
mcu_pos = mcu_pos_dist / self._step_dist
if mcu_pos >= 0.:
return int(mcu_pos + 0.5)
return int(mcu_pos - 0.5)
def get_tag_position(self):
return self._tag_position
def set_tag_position(self, position):
self._tag_position = position
def get_past_commanded_position(self, clock):
def _set_mcu_position(self, mcu_pos):
mcu_pos_dist = mcu_pos * self._step_dist
self._mcu_position_offset = mcu_pos_dist - self.get_commanded_position()
def get_past_mcu_position(self, print_time):
clock = self._mcu.print_time_to_clock(print_time)
ffi_main, ffi_lib = chelper.get_ffi()
sq = self._stepqueue
mcu_pos = ffi_lib.stepcompress_find_past_position(sq, clock)
return ffi_lib.stepcompress_find_past_position(self._stepqueue, clock)
def get_past_commanded_position(self, print_time):
mcu_pos = self.get_past_mcu_position(print_time)
return mcu_pos * self._step_dist - self._mcu_position_offset
def set_stepper_kinematics(self, sk):
old_sk = self._stepper_kinematics
mcu_pos = 0
if old_sk is not None:
mcu_pos = self.get_mcu_position()
self._stepper_kinematics = sk
if sk is not None:
ffi_main, ffi_lib = chelper.get_ffi()
ffi_lib.itersolve_set_stepcompress(sk, self._stepqueue,
self._step_dist)
self.set_trapq(self._trapq)
ffi_main, ffi_lib = chelper.get_ffi()
ffi_lib.itersolve_set_stepcompress(sk, self._stepqueue, self._step_dist)
self.set_trapq(self._trapq)
self._set_mcu_position(mcu_pos)
return old_sk
def note_homing_end(self, did_trigger=False):
ffi_main, ffi_lib = chelper.get_ffi()
@ -139,13 +143,12 @@ class MCU_stepper:
return
params = self._get_position_cmd.send([self._oid])
last_pos = params['pos']
if self._invert_dir:
last_pos = -last_pos
ret = ffi_lib.stepcompress_set_last_position(self._stepqueue, last_pos)
if ret:
raise error("Internal error in stepcompress")
mcu_pos_dist = last_pos * self._step_dist
if self._invert_dir:
mcu_pos_dist = -mcu_pos_dist
self._mcu_position_offset = mcu_pos_dist - self.get_commanded_position()
self._set_mcu_position(last_pos)
def set_trapq(self, tq):
ffi_main, ffi_lib = chelper.get_ffi()
if tq is None:
@ -159,16 +162,16 @@ class MCU_stepper:
def generate_steps(self, flush_time):
# Check for activity if necessary
if self._active_callbacks:
ret = self._itersolve_check_active(self._stepper_kinematics,
flush_time)
sk = self._stepper_kinematics
ret = self._itersolve_check_active(sk, flush_time)
if ret:
cbs = self._active_callbacks
self._active_callbacks = []
for cb in cbs:
cb(ret)
# Generate steps
ret = self._itersolve_generate_steps(self._stepper_kinematics,
flush_time)
sk = self._stepper_kinematics
ret = self._itersolve_generate_steps(sk, flush_time)
if ret:
raise error("Internal error in stepcompress")
def is_active_axis(self, axis):
@ -256,9 +259,8 @@ class PrinterRail:
self.endstops = []
self.add_extra_stepper(config)
mcu_stepper = self.steppers[0]
self.get_name = mcu_stepper.get_name
self.get_commanded_position = mcu_stepper.get_commanded_position
self.get_tag_position = mcu_stepper.get_tag_position
self.set_tag_position = mcu_stepper.set_tag_position
self.calc_position_from_coord = mcu_stepper.calc_position_from_coord
# Primary endstop position
mcu_endstop = self.endstops[0][0]

View File

@ -1,7 +1,8 @@
# Main code build rules
src-y += sched.c command.c basecmd.c debugcmds.c
src-$(CONFIG_HAVE_GPIO) += initial_pins.c gpiocmds.c stepper.c endstop.c
src-$(CONFIG_HAVE_GPIO) += initial_pins.c gpiocmds.c stepper.c endstop.c \
trsync.c
src-$(CONFIG_HAVE_GPIO_ADC) += adccmds.c
src-$(CONFIG_HAVE_GPIO_SPI) += spicmds.c thermocouple.c
src-$(CONFIG_HAVE_GPIO_I2C) += i2ccmds.c

View File

@ -90,8 +90,7 @@ choice
config CLOCK_REF_X32K
bool "32.768Khz crystal"
config CLOCK_REF_X25M
depends on MACH_SAMD51
bool "25Mhz crystal (on PB22/PB23)"
bool "25Mhz crystal" if MACH_SAMD51
config CLOCK_REF_INTERNAL
bool "Internal clock"
endchoice

View File

@ -154,6 +154,33 @@ clock_init_32k(void)
gen_clock(CLKGEN_48M, GCLK_GENCTRL_SRC_DPLL1);
}
// Initialize the clocks using an external 25M crystal
static void
clock_init_25m(void)
{
// Enable XOSC1
uint32_t freq_xosc = 25000000;
uint32_t val = (OSCCTRL_XOSCCTRL_ENABLE | OSCCTRL_XOSCCTRL_XTALEN
| OSCCTRL_XOSCCTRL_IPTAT(3) | OSCCTRL_XOSCCTRL_IMULT(6));
OSCCTRL->XOSCCTRL[1].reg = val;
while (!(OSCCTRL->STATUS.reg & OSCCTRL_STATUS_XOSCRDY1))
;
// Generate 120Mhz clock on PLL0 (with XOSC1 as reference)
uint32_t p0div = 10, p0mul = DIV_ROUND_CLOSEST(FREQ_MAIN, freq_xosc/p0div);
uint32_t p0ctrlb = OSCCTRL_DPLLCTRLB_DIV(p0div / 2 - 1);
config_dpll(0, p0mul, p0ctrlb | OSCCTRL_DPLLCTRLB_REFCLK_XOSC1);
// Switch main clock to 120Mhz PLL0
gen_clock(CLKGEN_MAIN, GCLK_GENCTRL_SRC_DPLL0);
// Generate 48Mhz clock on PLL1 (with XOSC1 as reference)
uint32_t p1div = 50, p1mul = DIV_ROUND_CLOSEST(FREQ_48M, freq_xosc/p1div);
uint32_t p1ctrlb = OSCCTRL_DPLLCTRLB_DIV(p1div / 2 - 1);
config_dpll(1, p1mul, p1ctrlb | OSCCTRL_DPLLCTRLB_REFCLK_XOSC1);
gen_clock(CLKGEN_48M, GCLK_GENCTRL_SRC_DPLL1);
}
// Initialize clocks from factory calibrated internal clock
static void
clock_init_internal(void)

View File

@ -1,6 +1,6 @@
// Handling of end stops.
//
// Copyright (C) 2016-2019 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2016-2021 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
@ -9,30 +9,17 @@
#include "board/irq.h" // irq_disable
#include "command.h" // DECL_COMMAND
#include "sched.h" // struct timer
#include "stepper.h" // stepper_stop
#include "trsync.h" // trsync_do_trigger
struct endstop {
struct timer time;
struct gpio_in pin;
uint32_t rest_time, sample_time, nextwake;
uint8_t flags, stepper_count, sample_count, trigger_count;
struct stepper *steppers[0];
struct trsync *ts;
uint8_t flags, sample_count, trigger_count, trigger_reason;
};
enum { ESF_PIN_HIGH=1<<0, ESF_HOMING=1<<1, ESF_REPORT=1<<2 };
static struct task_wake endstop_wake;
static void
stop_steppers(struct endstop *e)
{
e->flags = ESF_REPORT;
uint8_t count = e->stepper_count;
while (count--)
if (e->steppers[count])
stepper_stop(e->steppers[count]);
sched_wake_task(&endstop_wake);
}
enum { ESF_PIN_HIGH=1<<0, ESF_HOMING=1<<1 };
static uint_fast8_t endstop_oversample_event(struct timer *t);
@ -68,7 +55,7 @@ endstop_oversample_event(struct timer *t)
}
uint8_t count = e->trigger_count - 1;
if (!count) {
stop_steppers(e);
trsync_do_trigger(e->ts, e->trigger_reason);
return SF_DONE;
}
e->trigger_count = count;
@ -79,28 +66,10 @@ endstop_oversample_event(struct timer *t)
void
command_config_endstop(uint32_t *args)
{
uint8_t stepper_count = args[3];
struct endstop *e = oid_alloc(
args[0], command_config_endstop
, sizeof(*e) + sizeof(e->steppers[0]) * stepper_count);
struct endstop *e = oid_alloc(args[0], command_config_endstop, sizeof(*e));
e->pin = gpio_in_setup(args[1], args[2]);
e->stepper_count = stepper_count;
e->sample_count = 1;
}
DECL_COMMAND(command_config_endstop,
"config_endstop oid=%c pin=%c pull_up=%c stepper_count=%c");
void
command_endstop_set_stepper(uint32_t *args)
{
struct endstop *e = oid_lookup(args[0], command_config_endstop);
uint8_t pos = args[1];
if (pos >= e->stepper_count)
shutdown("Set stepper past maximum stepper count");
e->steppers[pos] = stepper_oid_lookup(args[2]);
}
DECL_COMMAND(command_endstop_set_stepper,
"endstop_set_stepper oid=%c pos=%c stepper_oid=%c");
DECL_COMMAND(command_config_endstop, "config_endstop oid=%c pin=%c pull_up=%c");
// Home an axis
void
@ -113,6 +82,7 @@ command_endstop_home(uint32_t *args)
e->sample_count = args[3];
if (!e->sample_count) {
// Disable end stop checking
e->ts = NULL;
e->flags = 0;
return;
}
@ -120,45 +90,26 @@ command_endstop_home(uint32_t *args)
e->time.func = endstop_event;
e->trigger_count = e->sample_count;
e->flags = ESF_HOMING | (args[5] ? ESF_PIN_HIGH : 0);
e->ts = trsync_oid_lookup(args[6]);
e->trigger_reason = args[7];
sched_add_timer(&e->time);
}
DECL_COMMAND(command_endstop_home,
"endstop_home oid=%c clock=%u sample_ticks=%u sample_count=%c"
" rest_ticks=%u pin_value=%c");
static void
endstop_report(uint8_t oid, struct endstop *e)
{
irq_disable();
uint8_t eflags = e->flags;
e->flags &= ~ESF_REPORT;
uint32_t nextwake = e->nextwake;
irq_enable();
sendf("endstop_state oid=%c homing=%c next_clock=%u pin_value=%c"
, oid, !!(eflags & ESF_HOMING), nextwake, gpio_in_read(e->pin));
}
" rest_ticks=%u pin_value=%c trsync_oid=%c trigger_reason=%c");
void
command_endstop_query_state(uint32_t *args)
{
uint8_t oid = args[0];
struct endstop *e = oid_lookup(oid, command_config_endstop);
endstop_report(oid, e);
irq_disable();
uint8_t eflags = e->flags;
uint32_t nextwake = e->nextwake;
irq_enable();
sendf("endstop_state oid=%c homing=%c next_clock=%u pin_value=%c"
, oid, !!(eflags & ESF_HOMING), nextwake, gpio_in_read(e->pin));
}
DECL_COMMAND(command_endstop_query_state, "endstop_query_state oid=%c");
void
endstop_task(void)
{
if (!sched_check_wake(&endstop_wake))
return;
uint8_t oid;
struct endstop *e;
foreach_oid(oid, e, command_config_endstop) {
if (!(e->flags & ESF_REPORT))
continue;
endstop_report(oid, e);
}
}
DECL_TASK(endstop_task);

View File

@ -3,7 +3,9 @@ SECTIONS
{
/* binutils on the PRU doesn't support --gc-sections so manually
* discard the .compile_time_request section */
/* and GDB extended debugging information */
/DISCARD/ : {
*( .compile_time_request )
*( .gnu.debug* )
}
}

View File

@ -1,6 +1,6 @@
// Handling of stepper drivers.
//
// Copyright (C) 2016-2019 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2016-2021 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
@ -11,7 +11,8 @@
#include "board/misc.h" // timer_is_before
#include "command.h" // DECL_COMMAND
#include "sched.h" // struct timer
#include "stepper.h" // command_config_stepper
#include "stepper.h" // stepper_event
#include "trsync.h" // trsync_add_signal
DECL_CONSTANT("STEP_DELAY", CONFIG_STEP_DELAY);
@ -44,6 +45,7 @@ struct stepper {
struct gpio_out step_pin, dir_pin;
uint32_t position;
struct move_queue_head mq;
struct trsync_signal stop_signal;
// gcc (pre v6) does better optimization when uint8_t are bitfields
uint8_t flags : 8;
};
@ -192,7 +194,7 @@ DECL_COMMAND(command_config_stepper,
"config_stepper oid=%c step_pin=%c dir_pin=%c invert_step=%c");
// Return the 'struct stepper' for a given stepper oid
struct stepper *
static struct stepper *
stepper_oid_lookup(uint8_t oid)
{
return oid_lookup(oid, command_config_stepper);
@ -290,11 +292,11 @@ command_stepper_get_position(uint32_t *args)
}
DECL_COMMAND(command_stepper_get_position, "stepper_get_position oid=%c");
// Stop all moves for a given stepper (used in end stop homing). IRQs
// must be off.
void
stepper_stop(struct stepper *s)
// Stop all moves for a given stepper (caller must disable IRQs)
static void
stepper_stop(struct trsync_signal *tss, uint8_t reason)
{
struct stepper *s = container_of(tss, struct stepper, stop_signal);
sched_del_timer(&s->time);
s->next_step_time = 0;
s->position = -stepper_get_position(s);
@ -309,6 +311,17 @@ stepper_stop(struct stepper *s)
}
}
// Set the stepper to stop on a "trigger event" (used in homing)
void
command_stepper_stop_on_trigger(uint32_t *args)
{
struct stepper *s = stepper_oid_lookup(args[0]);
struct trsync *ts = trsync_oid_lookup(args[1]);
trsync_add_signal(ts, &s->stop_signal, stepper_stop);
}
DECL_COMMAND(command_stepper_stop_on_trigger,
"stepper_stop_on_trigger oid=%c trsync_oid=%c");
void
stepper_shutdown(void)
{
@ -316,7 +329,7 @@ stepper_shutdown(void)
struct stepper *s;
foreach_oid(i, s, command_config_stepper) {
move_queue_clear(&s->mq);
stepper_stop(s);
stepper_stop(&s->stop_signal, 0);
}
}
DECL_SHUTDOWN(stepper_shutdown);

View File

@ -4,7 +4,5 @@
#include <stdint.h> // uint8_t
uint_fast8_t stepper_event(struct timer *t);
struct stepper *stepper_oid_lookup(uint8_t oid);
void stepper_stop(struct stepper *s);
#endif // stepper.h

View File

@ -142,6 +142,8 @@ choice
bool "32KiB bootloader" if MACH_STM32F207 || MACH_STM32F407 || MACH_STM32F446
config STM32_FLASH_START_8800
bool "34KiB bootloader (Chitu v6 Bootloader)" if MACH_STM32F103
config STM32_FLASH_START_20200
bool "128KiB bootloader with 512 byte offset (Prusa Buddy)" if MACH_STM32F407
config STM32_FLASH_START_C000
bool "48KiB bootloader (MKS Robin Nano V3)" if MACH_STM32F407
config STM32_FLASH_START_10000
@ -166,6 +168,7 @@ config FLASH_START
default 0x8008800 if STM32_FLASH_START_8800
default 0x800C000 if STM32_FLASH_START_C000
default 0x8010000 if STM32_FLASH_START_10000
default 0x8020200 if STM32_FLASH_START_20200
default 0x8000000
config ARMCM_RAM_VECTORTABLE

200
src/trsync.c Normal file
View File

@ -0,0 +1,200 @@
// Handling of synchronized "trigger" dispatch
//
// Copyright (C) 2016-2021 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include "basecmd.h" // oid_alloc
#include "board/gpio.h" // struct gpio
#include "board/irq.h" // irq_disable
#include "board/misc.h" // timer_read_time
#include "command.h" // DECL_COMMAND
#include "sched.h" // struct timer
#include "trsync.h" // trsync_do_trigger
struct trsync {
struct timer report_time, expire_time;
uint32_t report_ticks;
struct trsync_signal *signals;
uint8_t flags, trigger_reason, expire_reason;
};
enum { TSF_CAN_TRIGGER=1<<0, TSF_REPORT=1<<2 };
static struct task_wake trsync_wake;
// Activate a trigger (caller must disable IRQs)
void
trsync_do_trigger(struct trsync *ts, uint8_t reason)
{
uint8_t flags = ts->flags;
if (!(flags & TSF_CAN_TRIGGER))
return;
ts->trigger_reason = reason;
ts->flags = (flags & ~TSF_CAN_TRIGGER) | TSF_REPORT;
// Dispatch signals
while (ts->signals) {
struct trsync_signal *tss = ts->signals;
trsync_callback_t func = tss->func;
ts->signals = tss->next;
tss->next = NULL;
tss->func = NULL;
func(tss, reason);
}
sched_wake_task(&trsync_wake);
}
// Timeout handler
static uint_fast8_t
trsync_expire_event(struct timer *t)
{
struct trsync *ts = container_of(t, struct trsync, expire_time);
trsync_do_trigger(ts, ts->expire_reason);
return SF_DONE;
}
// Report handler
static uint_fast8_t
trsync_report_event(struct timer *t)
{
struct trsync *ts = container_of(t, struct trsync, report_time);
ts->flags |= TSF_REPORT;
sched_wake_task(&trsync_wake);
ts->report_time.waketime += ts->report_ticks;
return SF_RESCHEDULE;
}
void
command_config_trsync(uint32_t *args)
{
struct trsync *ts = oid_alloc(args[0], command_config_trsync, sizeof(*ts));
ts->report_time.func = trsync_report_event;
ts->expire_time.func = trsync_expire_event;
}
DECL_COMMAND(command_config_trsync, "config_trsync oid=%c");
// Return the 'struct trsync' for a given trsync oid
struct trsync *
trsync_oid_lookup(uint8_t oid)
{
return oid_lookup(oid, command_config_trsync);
}
// Add a callback to invoke on a trigger
void
trsync_add_signal(struct trsync *ts, struct trsync_signal *tss
, trsync_callback_t func)
{
irqstatus_t flag = irq_save();
if (tss->func || !func)
shutdown("Can't add signal that is already active");
tss->func = func;
tss->next = ts->signals;
ts->signals = tss;
irq_restore(flag);
}
// Disable trigger and unregister any signal handlers (caller must disable IRQs)
static void
trsync_clear(struct trsync *ts)
{
sched_del_timer(&ts->report_time);
sched_del_timer(&ts->expire_time);
struct trsync_signal *tss = ts->signals;
while (tss) {
struct trsync_signal *next = tss->next;
tss->func = NULL;
tss->next = NULL;
tss = next;
}
ts->signals = NULL;
ts->flags = ts->trigger_reason = ts->expire_reason = 0;
}
void
command_trsync_start(uint32_t *args)
{
struct trsync *ts = trsync_oid_lookup(args[0]);
irq_disable();
trsync_clear(ts);
ts->flags = TSF_CAN_TRIGGER;
ts->report_time.waketime = args[1];
ts->report_ticks = args[2];
if (ts->report_ticks)
sched_add_timer(&ts->report_time);
ts->expire_reason = args[3];
irq_enable();
}
DECL_COMMAND(command_trsync_start,
"trsync_start oid=%c report_clock=%u report_ticks=%u"
" expire_reason=%c");
void
command_trsync_set_timeout(uint32_t *args)
{
struct trsync *ts = trsync_oid_lookup(args[0]);
irq_disable();
uint8_t flags = ts->flags;
if (flags & TSF_CAN_TRIGGER) {
sched_del_timer(&ts->expire_time);
ts->expire_time.waketime = args[1];
sched_add_timer(&ts->expire_time);
}
irq_enable();
}
DECL_COMMAND(command_trsync_set_timeout, "trsync_set_timeout oid=%c clock=%u");
static void
trsync_report(uint8_t oid, uint8_t flags, uint8_t reason, uint32_t clock)
{
sendf("trsync_state oid=%c can_trigger=%c trigger_reason=%c clock=%u"
, oid, !!(flags & TSF_CAN_TRIGGER), reason, clock);
}
void
command_trsync_trigger(uint32_t *args)
{
uint8_t oid = args[0];
struct trsync *ts = trsync_oid_lookup(oid);
irq_disable();
trsync_do_trigger(ts, args[1]);
sched_del_timer(&ts->report_time);
sched_del_timer(&ts->expire_time);
ts->flags = 0;
uint8_t trigger_reason = ts->trigger_reason;
irq_enable();
trsync_report(oid, 0, trigger_reason, 0);
}
DECL_COMMAND(command_trsync_trigger, "trsync_trigger oid=%c reason=%c");
void
trsync_task(void)
{
if (!sched_check_wake(&trsync_wake))
return;
uint8_t oid;
struct trsync *ts;
foreach_oid(oid, ts, command_config_trsync) {
if (!(ts->flags & TSF_REPORT))
continue;
uint32_t time = timer_read_time();
irq_disable();
uint8_t trigger_reason = ts->trigger_reason, flags = ts->flags;
ts->flags = flags & ~TSF_REPORT;
irq_enable();
trsync_report(oid, flags, trigger_reason, time);
}
}
DECL_TASK(trsync_task);
void
trsync_shutdown(void)
{
uint8_t oid;
struct trsync *ts;
foreach_oid(oid, ts, command_config_trsync) {
trsync_clear(ts);
}
}
DECL_SHUTDOWN(trsync_shutdown);

19
src/trsync.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef __TRSYNC_H
#define __TRSYNC_H
#include <stdint.h> // uint16_t
struct trsync_signal;
typedef void (*trsync_callback_t)(struct trsync_signal *tss, uint8_t reason);
struct trsync_signal {
struct trsync_signal *next;
trsync_callback_t func;
};
struct trsync *trsync_oid_lookup(uint8_t oid);
void trsync_do_trigger(struct trsync *ts, uint8_t reason);
void trsync_add_signal(struct trsync *ts, struct trsync_signal *tss
, trsync_callback_t func);
#endif // trsync.h

View File

@ -0,0 +1,4 @@
# Base config file for Atmel SAMD51P20 ARM processor
CONFIG_MACH_ATSAMD=y
CONFIG_MACH_SAMD51P20=y
CONFIG_CLOCK_REF_X25M=y

View File

@ -107,6 +107,10 @@ DICTIONARY sam4e8e.dict
CONFIG ../../config/generic-duet2.cfg
CONFIG ../../config/generic-duet2-duex.cfg
# Printers using the samd51
DICTIONARY samd51p20.dict
CONFIG ../../config/generic-duet3-mini.cfg
# Printers using the lpc176x
DICTIONARY lpc176x.dict
CONFIG ../../config/generic-azteeg-x5-mini-v3.cfg
@ -114,6 +118,7 @@ CONFIG ../../config/generic-bigtreetech-skr-e3-turbo.cfg
CONFIG ../../config/generic-bigtreetech-skr-v1.1.cfg
CONFIG ../../config/generic-bigtreetech-skr-v1.3.cfg
CONFIG ../../config/generic-bigtreetech-skr-v1.4.cfg
CONFIG ../../config/generic-th3d-ezboard-lite-v1.2.cfg
CONFIG ../../config/generic-mks-sgenl.cfg
CONFIG ../../config/generic-re-arm.cfg
CONFIG ../../config/generic-smoothieboard.cfg
@ -130,6 +135,7 @@ CONFIG ../../config/generic-bigtreetech-skr-mini-e3-v1.0.cfg
CONFIG ../../config/generic-bigtreetech-skr-mini-e3-v1.2.cfg
CONFIG ../../config/generic-bigtreetech-skr-mini-e3-v2.0.cfg
CONFIG ../../config/generic-bigtreetech-skr-mini-mz.cfg
CONFIG ../../config/generic-bigtreetech-skr-cr6-v1.0.cfg
CONFIG ../../config/generic-bigtreetech-skr-e3-dip.cfg
# Printers using the stm32f103 via serial
@ -163,6 +169,8 @@ CONFIG ../../config/generic-bigtreetech-skr-pro.cfg
CONFIG ../../config/generic-bigtreetech-skr-2.cfg
CONFIG ../../config/generic-flyboard.cfg
CONFIG ../../config/generic-mks-robin-nano-v3.cfg
CONFIG ../../config/generic-prusa-buddy.cfg
CONFIG ../../config/printer-prusa-mini-plus-2020.cfg
# Printers using the stm32f446
DICTIONARY stm32f407.dict