mirror of https://github.com/Desuuuu/klipper.git
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
46b86f0f6c
4
Makefile
4
Makefile
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
|
@ -112,7 +112,7 @@ pin: PD6
|
|||
pause_on_runout: True
|
||||
runout_gcode:
|
||||
M25
|
||||
switch_pin: PA15
|
||||
switch_pin: !PA15
|
||||
|
||||
[output_pin beeper]
|
||||
pin: PB0
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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)",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
259
klippy/mcu.py
259
klippy/mcu.py
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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* )
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue