From 8a3d2afd796414d64b995ac753148484b77198dd Mon Sep 17 00:00:00 2001 From: Alexander Bazarov <63168142+bazarovdev@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:32:07 -0500 Subject: [PATCH 1/6] config: Config for Geeetech A-series printers: A10/M/T and A20/M/T (#6767) Based on few configs found on the discourse forum, facebook groups. In addition, using official schematics from: https://www.geeetech.com/download.html https://github.com/Geeetech3D/Diagram/files/8199212/GT2560V4.1BSCHA20T.pdf Contains macros for filament mixing based on: https://klipper.discourse.group/t/mixing-color-support/2246/12 https://klipper.discourse.group/t/mixing-hotend-m163-emulation/11423/2 Signed-off-by: Alexander Bazarov --- config/printer-geeetech-A10T-A20T-2021.cfg | 256 +++++++++++++++++++++ test/klippy/printers.test | 1 + 2 files changed, 257 insertions(+) create mode 100644 config/printer-geeetech-A10T-A20T-2021.cfg diff --git a/config/printer-geeetech-A10T-A20T-2021.cfg b/config/printer-geeetech-A10T-A20T-2021.cfg new file mode 100644 index 000000000000..7a2fb6ee9554 --- /dev/null +++ b/config/printer-geeetech-A10T-A20T-2021.cfg @@ -0,0 +1,256 @@ +# This file contains common pin mappings for the Geeetech GT2560 v4.0 and v4.1b +# boards. These boards use a firmware compiled for the AVR atmega2560. +# For default Geeetech A10/A20 (1 extruder), +# A10M/A20M (mixing 2 in 1 out), +# A10T/A20T (mixing 3 in 1 out) printers +# Installation: https://www.klipper3d.org/Installation.html +# Always read for first start: https://www.klipper3d.org/Config_checks.html + +[mcu] +# Might need to be changed: https://www.klipper3d.org/Installation.html +serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0 + +[printer] +kinematics: cartesian +max_velocity: 200 +max_accel: 1500 +max_z_velocity: 20 +max_z_accel: 500 + +# # uncomment for BLTouch/3DTouch +# [bltouch] +# sensor_pin: PC7 # there is an external pull up so no need in ^ +# control_pin: PB5 +# speed: 3.0 +# samples: 2 +# x_offset: -42.0 +# y_offset: -1.0 +# z_offset: 1.0 # during calibration this line is commented out and new record added at the end of file + +[safe_z_home] +home_xy_position: 100, 100 # Change coordinates to the center of your print bed +speed: 50 +z_hop: 10 # Move up 10mm +z_hop_speed: 5 + +[stepper_x] +enable_pin: !PC2 +dir_pin: !PG2 +step_pin: PC0 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PA2 # there are external pull ups +position_endstop: 0 +position_max: 220 # for A10/M/T / change to 250 for A20/M/T +homing_speed: 40 + +[stepper_y] +enable_pin: !PA7 +dir_pin: !PC4 +step_pin: PC6 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PA6 # there are external pull ups +position_endstop: 0 +position_max: 220 # for A10/M/T / change to 250 for A20/M/T +homing_speed: 40 + +[stepper_z] +enable_pin: !PA5 +dir_pin: PA1 +step_pin: PA3 +microsteps: 16 +rotation_distance: 8 +#endstop_pin: probe:z_virtual_endstop # uncomment for BLTouch/3DTouch +endstop_pin: !PC7 # comment for BLTouch/3DTouch +position_endstop: 0 # comment for BLTouch/3DTouch +position_max: 230 # for A10/M/T / change to 250 for A20/M/T +position_min: -5 +homing_speed: 20 + +[extruder] +enable_pin: !PB6 +dir_pin: PL5 +step_pin: PL3 +microsteps: 16 +rotation_distance: 8 # Needs to be optimized: https://www.klipper3d.org/Rotation_Distance.html#calibrating-rotation_distance-on-extruders +nozzle_diameter: 0.4 +filament_diameter: 1.750 +heater_pin: PB4 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK3 +min_temp: 0 +max_temp: 250 +max_extrude_only_distance: 200.0 +# Parameters for stock hotend on A10M +# Please recalibrate according to https://www.klipper3d.org/Config_checks.html#calibrate-pid-settings +control: pid +pid_kp: 54.722 +pid_ki: 4.800 +pid_kd: 155.958 + +[extruder_stepper extruder_1] +extruder: +enable_pin: !PL1 +dir_pin: PL2 +step_pin: PL0 +microsteps: 16 +rotation_distance: 8 # Needs to be optimized: https://www.klipper3d.org/Rotation_Distance.html#calibrating-rotation_distance-on-extruders + +[extruder_stepper extruder_2] +extruder: +enable_pin: !PG0 +dir_pin: PL4 +step_pin: PL6 +microsteps: 16 +rotation_distance: 8 # Needs to be optimized: https://www.klipper3d.org/Rotation_Distance.html#calibrating-rotation_distance-on-extruders + +[heater_bed] +heater_pin: PG5 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK2 +min_temp: 0 +max_temp: 120 +# Parameters for `SuperPlate` on A10M +# Please recalibrate according to https://www.klipper3d.org/Config_checks.html#calibrate-pid-settings +control: pid +pid_kp: 70.936 +pid_ki: 1.785 +pid_kd: 704.924 + +[fan] +pin: PH6 +cycle_time: 0.150 +kick_start_time: 0.300 + +# # for GT2560V4.0 with 20pin flat cable toward the display +# [display] +# lcd_type: hd44780 +# hd44780_protocol_init: True +# rs_pin: PD1 +# e_pin: PH0 +# d4_pin: PH1 +# d5_pin: PD0 +# d6_pin: PE3 +# d7_pin: PC1 +# encoder_pins: ^PG1, ^PL7 +# click_pin: ^!PD2 + + +# for GT2560V4.1B with 12pin flat cable toward the display YHCB2004-06 ver3.0 +# the aip31068_spi driver was added to Klipper on 2024-12-02, commit aecb29d2 +[display] +lcd_type: aip31068_spi +latch_pin: PE3 +spi_software_sclk_pin: PD0 +spi_software_mosi_pin: PC1 +spi_software_miso_pin: PH7 # any unused pin +encoder_pins: ^PH0, ^PH1 +click_pin: ^!PD2 + + +[filament_switch_sensor sensor_e0] +switch_pin: !PK4 + +[filament_switch_sensor sensor_e1] +switch_pin: !PK5 + +[filament_switch_sensor sensor_e2] +# switch_pin: !PE2 # for GT2560V4.0 +switch_pin: !PF0 # for GT2560V4.1B + +# to enable M118 echo command +[respond] + +# Specific macros for mixing colors. +# Add in slicer new filament color and in filament start G-Code add desired mixing factor: +# M163 S0 P50 ; set extruder 0 to 50% +# M163 S1 P40 ; set extruder 1 to 40% +# M163 S2 P10 ; set extruder 2 to 10% +# M164 ; commit the mix factors +[gcode_macro M163] +description: M163 [P] [S] Set a single mix factor (in proportion to the sum total of all mix factors). The mix must be committed to a virtual tool by M164 before it takes effect. +gcode: + {% if 'P' in params %} + {% set s = params.S|default(0)| int %} + {% if s == 0 %} + SET_GCODE_VARIABLE MACRO=M164 VARIABLE=e0_parts VALUE={params.P|default(0)|float} + M118 Set Mixing factor for extruder 0 to {params.P|default(0)|float} + {% elif s == 1 %} + SET_GCODE_VARIABLE MACRO=M164 VARIABLE=e1_parts VALUE={params.P|default(0)|float} + M118 Set Mixing factor for extruder 1 to {params.P|default(0)|float} + {% elif s == 2 %} + SET_GCODE_VARIABLE MACRO=M164 VARIABLE=e2_parts VALUE={params.P|default(0)|float} + M118 Set Mixing factor for extruder 2 to {params.P|default(0)|float} + {% endif %} + {% else %} + M118 No Mixing factor set, missing value for P + {% endif %} + M118 {e0_parts} {e1_parts} {e2_parts} + + +[gcode_macro M164] +description: Applies the set mixing factors to the extruders +# default values: +variable_e0_parts : 100 +variable_e1_parts : 0 +variable_e2_parts : 0 +gcode: + # normalize the parts to sum of 1 + {% set e0 = e0_parts / (e0_parts + e1_parts + e2_parts) | float %} + {% set e1 = e1_parts / (e0_parts + e1_parts + e2_parts) | float %} + {% set e2 = e2_parts / (e0_parts + e1_parts + e2_parts) | float %} + M118 scaled rot-dist_e0 { printer.configfile.settings.extruder.rotation_distance / (e0 + 0.000001) | float } + M118 scaled rot-dist_e1 { printer.configfile.settings['extruder_stepper extruder_1'].rotation_distance / (e1 + 0.000001) | float } + M118 scaled rot-dist_e2 { printer.configfile.settings['extruder_stepper extruder_2'].rotation_distance / (e2 + 0.000001) |float } + # activate stepper percentages + SYNC_EXTRUDER_MOTION EXTRUDER=extruder MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder_1 MOTION_QUEUE=extruder + SYNC_EXTRUDER_MOTION EXTRUDER=extruder_2 MOTION_QUEUE=extruder + SET_EXTRUDER_ROTATION_DISTANCE EXTRUDER=extruder DISTANCE={ printer.configfile.settings.extruder.rotation_distance / (e0+0.000001)|float } + SET_EXTRUDER_ROTATION_DISTANCE EXTRUDER=extruder_1 DISTANCE={ printer.configfile.settings['extruder_stepper extruder_1'].rotation_distance / (e1+0.000001)|float } + SET_EXTRUDER_ROTATION_DISTANCE EXTRUDER=extruder_2 DISTANCE={ printer.configfile.settings['extruder_stepper extruder_2'].rotation_distance / (e2+0.000001)|float } + M118 Mixing factors {e0} {e1} {e2} are activated + +# In PrusaSlicer: +# - you can add as many extruders as mixing ratios you want +# - in Printer Settings -> Custom G-code -> Tool change G-code add: +# TOOL_CHANGE EXTRUDER={next_extruder} +# - in this config file add: +# [gcode_macro TOOL_CHANGE] +# description: Tool change macro with mix ratio setup for 11 extruders +# variable_extruder: 0 +# gcode: +# {% set extruder = params.EXTRUDER|default(0)| int %} +# {% if extruder == 0 %} +# M163 S0 P100 +# M163 S1 P0 +# M163 S2 P0 +# M164 +# M118 Switching to Extruder 0 +# {% elif extruder == 1 %} +# M163 S0 P90 +# M163 S1 P10 +# M163 S2P0 +# M164 +# M118 Switching to Extruder 1 +# {% elif extruder == 2 %} +# # and so on ... +# {% else %} +# M118 Unknown extruder number: {extruder} +# {% endif %} + +# In OrcaSlicer: +# you can add as many filaments as mixing ratios you want +# in Material settings -> Advanced -> Filament start G-code add desired mixing ratio: +# ; filament start gcode +# M163 S0 P100 ; set extruder 0 +# M163 S1 P0 ; set extruder 1 +# M163 S2 P0 ; set extruder 2 +# M164 ; commit the mix factors + +# For gradient over Z axis: +# In `Printer -> Custom G-code -> After layer change G-code` add: +# M163 S0 P{ layer_num * 100 / total_layer_count } ; Gradient 0-100 +# M163 S1 P{(total_layer_count-layer_num) * 100 / total_layer_count} ; Gradient 100-0 +# M164 ; commit the mix factors diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 40a6118c0eeb..92c340cb815e 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -40,6 +40,7 @@ CONFIG ../../config/printer-creality-cr20-pro-2019.cfg CONFIG ../../config/printer-creality-ender5plus-2019.cfg CONFIG ../../config/printer-eryone-thinker-series-v2-2020.cfg CONFIG ../../config/printer-flashforge-creator-pro-2018.cfg +CONFIG ../../config/printer-geeetech-A10T-A20T-2021.cfg CONFIG ../../config/printer-hiprecy-leo-2019.cfg CONFIG ../../config/printer-longer-lk4-pro-2019.cfg CONFIG ../../config/printer-lulzbot-mini1-2016.cfg From 9ca71d8608f4a6013c3453ffa9e75c3739078712 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 9 Jan 2025 20:43:06 -0500 Subject: [PATCH 2/6] github: Change to upload-artifact@v4 Signed-off-by: Kevin O'Connor --- .github/workflows/build-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index fda7baf861df..fcfbc5dbd738 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -21,7 +21,7 @@ jobs: run: ./scripts/ci-build.sh 2>&1 - name: Upload micro-controller data dictionaries - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: data-dict path: ci_build/dict From 7083879700800710c624c0bf08220215ba5f4a83 Mon Sep 17 00:00:00 2001 From: Dennis Marttinen <38858901+twelho@users.noreply.github.com> Date: Fri, 10 Jan 2025 15:41:09 +0000 Subject: [PATCH 3/6] force_move: Implement CLEAR for SET_KINEMATIC_POSITION (#6262) `CLEAR` clears the homing status (resets the axis limits) without turning off the motors. This is particularly useful when implementing safe Z homing in `[homing_override]` on printers with multiple independent Z steppers (where `FORCE_MOVE` can't be used). Signed-off-by: Dennis Marttinen --- docs/Code_Overview.md | 8 ++++---- docs/G-Codes.md | 21 ++++++++++++--------- klippy/extras/force_move.py | 7 ++++++- klippy/extras/safe_z_home.py | 3 +-- klippy/kinematics/cartesian.py | 9 +++++---- klippy/kinematics/corexy.py | 9 +++++---- klippy/kinematics/corexz.py | 9 +++++---- klippy/kinematics/delta.py | 8 ++++++-- klippy/kinematics/deltesian.py | 4 ++++ klippy/kinematics/hybrid_corexy.py | 9 +++++---- klippy/kinematics/hybrid_corexz.py | 9 +++++---- klippy/kinematics/none.py | 2 ++ klippy/kinematics/polar.py | 12 +++++++----- klippy/kinematics/rotary_delta.py | 8 ++++++-- klippy/kinematics/winch.py | 3 +++ klippy/stepper.py | 2 +- 16 files changed, 77 insertions(+), 46 deletions(-) diff --git a/docs/Code_Overview.md b/docs/Code_Overview.md index 0e4836acf5ec..9ccaa60be173 100644 --- a/docs/Code_Overview.md +++ b/docs/Code_Overview.md @@ -359,10 +359,10 @@ Useful steps: 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. - However, at the start of development one can use boiler-plate code - here. + `get_steppers()`, `home()`, `clear_homing_state()`, and `set_position()` + methods. These functions are typically used to provide kinematic + specific checks. However, at the start of development one can use + boiler-plate code here. 6. Implement test cases. Create a g-code file with a series of moves that can test important cases for the given kinematics. Follow the [debugging documentation](Debugging.md) to convert this g-code file diff --git a/docs/G-Codes.md b/docs/G-Codes.md index d44017deac9c..ff6182fafee0 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -585,15 +585,18 @@ state; issue a G28 afterwards to reset the kinematics. This command is intended for low-level diagnostics and debugging. #### SET_KINEMATIC_POSITION -`SET_KINEMATIC_POSITION [X=] [Y=] [Z=]`: Force -the low-level kinematic code to believe the toolhead is at the given -cartesian position. This is a diagnostic and debugging command; use -SET_GCODE_OFFSET and/or G92 for regular axis transformations. If an -axis is not specified then it will default to the position that the -head was last commanded to. Setting an incorrect or invalid position -may lead to internal software errors. This command may invalidate -future boundary checks; issue a G28 afterwards to reset the -kinematics. +`SET_KINEMATIC_POSITION [X=] [Y=] [Z=] +[CLEAR=<[X][Y][Z]>]`: Force the low-level kinematic code to believe the +toolhead is at the given cartesian position. This is a diagnostic and +debugging command; use SET_GCODE_OFFSET and/or G92 for regular axis +transformations. If an axis is not specified then it will default to the +position that the head was last commanded to. Setting an incorrect or +invalid position may lead to internal software errors. Use the CLEAR +parameter to forget the homing state for the given axes. Note that CLEAR +will not override the previous functionality; if an axis is not specified +to CLEAR it will have its kinematic position set as per above. This +command may invalidate future boundary checks; issue a G28 afterwards to +reset the kinematics. ### [gcode] diff --git a/klippy/extras/force_move.py b/klippy/extras/force_move.py index 50b801412dbd..598783a481a1 100644 --- a/klippy/extras/force_move.py +++ b/klippy/extras/force_move.py @@ -131,8 +131,13 @@ def cmd_SET_KINEMATIC_POSITION(self, gcmd): x = gcmd.get_float('X', curpos[0]) y = gcmd.get_float('Y', curpos[1]) z = gcmd.get_float('Z', curpos[2]) - logging.info("SET_KINEMATIC_POSITION pos=%.3f,%.3f,%.3f", x, y, z) + clear = gcmd.get('CLEAR', '').upper() + axes = ['X', 'Y', 'Z'] + clear_axes = [axes.index(a) for a in axes if a in clear] + logging.info("SET_KINEMATIC_POSITION pos=%.3f,%.3f,%.3f clear=%s", + x, y, z, ','.join((axes[i] for i in clear_axes))) toolhead.set_position([x, y, z, curpos[3]], homing_axes=(0, 1, 2)) + toolhead.get_kinematics().clear_homing_state(clear_axes) def load_config(config): return ForceMove(config) diff --git a/klippy/extras/safe_z_home.py b/klippy/extras/safe_z_home.py index ca9b8eb59ea3..ca0756cfa710 100644 --- a/klippy/extras/safe_z_home.py +++ b/klippy/extras/safe_z_home.py @@ -40,8 +40,7 @@ def cmd_G28(self, gcmd): toolhead.set_position(pos, homing_axes=[2]) toolhead.manual_move([None, None, self.z_hop], self.z_hop_speed) - if hasattr(toolhead.get_kinematics(), "note_z_not_homed"): - toolhead.get_kinematics().note_z_not_homed() + toolhead.get_kinematics().clear_homing_state((2,)) elif pos[2] < self.z_hop: # If the Z axis is homed, and below z_hop, lift it to z_hop toolhead.manual_move([None, None, self.z_hop], diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index 0c4bb9255853..2715d1720d68 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -73,9 +73,10 @@ def set_position(self, newpos, homing_axes): else: rail = self.rails[axis] self.limits[axis] = rail.get_range() - def note_z_not_homed(self): - # Helper for Safe Z Home - self.limits[2] = (1.0, -1.0) + def clear_homing_state(self, axes): + for i, _ in enumerate(self.limits): + if i in axes: + self.limits[i] = (1.0, -1.0) def home_axis(self, homing_state, axis, rail): # Determine movement position_min, position_max = rail.get_range() @@ -97,7 +98,7 @@ def home(self, homing_state): else: self.home_axis(homing_state, axis, self.rails[axis]) def _motor_off(self, print_time): - self.limits = [(1.0, -1.0)] * 3 + self.clear_homing_state((0, 1, 2)) def _check_endstops(self, move): end_pos = move.end_pos for i in (0, 1, 2): diff --git a/klippy/kinematics/corexy.py b/klippy/kinematics/corexy.py index d3b755001916..dc80d1eeca0b 100644 --- a/klippy/kinematics/corexy.py +++ b/klippy/kinematics/corexy.py @@ -43,9 +43,10 @@ def set_position(self, newpos, homing_axes): rail.set_position(newpos) if i in homing_axes: self.limits[i] = rail.get_range() - def note_z_not_homed(self): - # Helper for Safe Z Home - self.limits[2] = (1.0, -1.0) + def clear_homing_state(self, axes): + for i, _ in enumerate(self.limits): + if i in axes: + self.limits[i] = (1.0, -1.0) def home(self, homing_state): # Each axis is homed independently and in order for axis in homing_state.get_axes(): @@ -63,7 +64,7 @@ def home(self, homing_state): # Perform homing homing_state.home_rails([rail], forcepos, homepos) def _motor_off(self, print_time): - self.limits = [(1.0, -1.0)] * 3 + self.clear_homing_state((0, 1, 2)) def _check_endstops(self, move): end_pos = move.end_pos for i in (0, 1, 2): diff --git a/klippy/kinematics/corexz.py b/klippy/kinematics/corexz.py index 72134c32741b..b1c46146b937 100644 --- a/klippy/kinematics/corexz.py +++ b/klippy/kinematics/corexz.py @@ -43,9 +43,10 @@ def set_position(self, newpos, homing_axes): rail.set_position(newpos) if i in homing_axes: self.limits[i] = rail.get_range() - def note_z_not_homed(self): - # Helper for Safe Z Home - self.limits[2] = (1.0, -1.0) + def clear_homing_state(self, axes): + for i, _ in enumerate(self.limits): + if i in axes: + self.limits[i] = (1.0, -1.0) def home(self, homing_state): # Each axis is homed independently and in order for axis in homing_state.get_axes(): @@ -63,7 +64,7 @@ def home(self, homing_state): # Perform homing homing_state.home_rails([rail], forcepos, homepos) def _motor_off(self, print_time): - self.limits = [(1.0, -1.0)] * 3 + self.clear_homing_state((0, 1, 2)) def _check_endstops(self, move): end_pos = move.end_pos for i in (0, 1, 2): diff --git a/klippy/kinematics/delta.py b/klippy/kinematics/delta.py index bb81ab18abeb..04a75547b3cd 100644 --- a/klippy/kinematics/delta.py +++ b/klippy/kinematics/delta.py @@ -105,6 +105,11 @@ def set_position(self, newpos, homing_axes): self.limit_xy2 = -1. if tuple(homing_axes) == (0, 1, 2): self.need_home = False + def clear_homing_state(self, axes): + # Clearing homing state for each axis individually is not implemented + if 0 in axes or 1 in axes or 2 in axes: + self.limit_xy2 = -1 + self.need_home = True def home(self, homing_state): # All axes are homed simultaneously homing_state.set_axes([0, 1, 2]) @@ -112,8 +117,7 @@ def home(self, homing_state): forcepos[2] = -1.5 * math.sqrt(max(self.arm2)-self.max_xy2) homing_state.home_rails(self.rails, forcepos, self.home_position) def _motor_off(self, print_time): - self.limit_xy2 = -1. - self.need_home = True + self.clear_homing_state((0, 1, 2)) def check_move(self, move): end_pos = move.end_pos end_xy2 = end_pos[0]**2 + end_pos[1]**2 diff --git a/klippy/kinematics/deltesian.py b/klippy/kinematics/deltesian.py index bb23137084a9..e48949af2c79 100644 --- a/klippy/kinematics/deltesian.py +++ b/klippy/kinematics/deltesian.py @@ -117,6 +117,10 @@ def set_position(self, newpos, homing_axes): rail.set_position(newpos) for n in homing_axes: self.homed_axis[n] = True + def clear_homing_state(self, axes): + for i, _ in enumerate(self.limits): + if i in axes: + self.homed_axis[i] = False def home(self, homing_state): homing_axes = homing_state.get_axes() home_xz = 0 in homing_axes or 2 in homing_axes diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py index 265a0e6da33d..85f20bcc6040 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -75,9 +75,10 @@ def set_position(self, newpos, homing_axes): else: rail = self.rails[axis] self.limits[axis] = rail.get_range() - def note_z_not_homed(self): - # Helper for Safe Z Home - self.limits[2] = (1.0, -1.0) + def clear_homing_state(self, axes): + for i, _ in enumerate(self.limits): + if i in axes: + self.limits[i] = (1.0, -1.0) def home_axis(self, homing_state, axis, rail): position_min, position_max = rail.get_range() hi = rail.get_homing_info() @@ -97,7 +98,7 @@ def home(self, homing_state): else: self.home_axis(homing_state, axis, self.rails[axis]) def _motor_off(self, print_time): - self.limits = [(1.0, -1.0)] * 3 + self.clear_homing_state((0, 1, 2)) def _check_endstops(self, move): end_pos = move.end_pos for i in (0, 1, 2): diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py index 2d89e3f7bd03..8e5c3d92e74a 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -75,9 +75,10 @@ def set_position(self, newpos, homing_axes): else: rail = self.rails[axis] self.limits[axis] = rail.get_range() - def note_z_not_homed(self): - # Helper for Safe Z Home - self.limits[2] = (1.0, -1.0) + def clear_homing_state(self, axes): + for i, _ in enumerate(self.limits): + if i in axes: + self.limits[i] = (1.0, -1.0) def home_axis(self, homing_state, axis, rail): position_min, position_max = rail.get_range() hi = rail.get_homing_info() @@ -97,7 +98,7 @@ def home(self, homing_state): else: self.home_axis(homing_state, axis, self.rails[axis]) def _motor_off(self, print_time): - self.limits = [(1.0, -1.0)] * 3 + self.clear_homing_state((0, 1, 2)) def _check_endstops(self, move): end_pos = move.end_pos for i in (0, 1, 2): diff --git a/klippy/kinematics/none.py b/klippy/kinematics/none.py index ff3c57a9a2e5..fda1f073a38c 100644 --- a/klippy/kinematics/none.py +++ b/klippy/kinematics/none.py @@ -13,6 +13,8 @@ def calc_position(self, stepper_positions): return [0, 0, 0] def set_position(self, newpos, homing_axes): pass + def clear_homing_state(self, axes): + pass def home(self, homing_state): pass def check_move(self, move): diff --git a/klippy/kinematics/polar.py b/klippy/kinematics/polar.py index deed26d06009..fdd428a60edb 100644 --- a/klippy/kinematics/polar.py +++ b/klippy/kinematics/polar.py @@ -51,9 +51,12 @@ def set_position(self, newpos, homing_axes): self.limit_z = self.rails[1].get_range() if 0 in homing_axes and 1 in homing_axes: self.limit_xy2 = self.rails[0].get_range()[1]**2 - def note_z_not_homed(self): - # Helper for Safe Z Home - self.limit_z = (1.0, -1.0) + def clear_homing_state(self, axes): + if 0 in axes or 1 in axes: + # X and Y cannot be cleared separately + self.limit_xy2 = -1. + if 2 in axes: + self.limit_z = (1.0, -1.0) def _home_axis(self, homing_state, axis, rail): # Determine movement position_min, position_max = rail.get_range() @@ -86,8 +89,7 @@ def home(self, homing_state): if home_z: self._home_axis(homing_state, 2, self.rails[1]) def _motor_off(self, print_time): - self.limit_z = (1.0, -1.0) - self.limit_xy2 = -1. + self.clear_homing_state((0, 1, 2)) def check_move(self, move): end_pos = move.end_pos xy2 = end_pos[0]**2 + end_pos[1]**2 diff --git a/klippy/kinematics/rotary_delta.py b/klippy/kinematics/rotary_delta.py index 1eb050baa57d..46a63b822e5d 100644 --- a/klippy/kinematics/rotary_delta.py +++ b/klippy/kinematics/rotary_delta.py @@ -88,6 +88,11 @@ def set_position(self, newpos, homing_axes): self.limit_xy2 = -1. if tuple(homing_axes) == (0, 1, 2): self.need_home = False + def clear_homing_state(self, axes): + # Clearing homing state for each axis individually is not implemented + if 0 in axes or 1 in axes or 2 in axes: + self.limit_xy2 = -1 + self.need_home = True def home(self, homing_state): # All axes are homed simultaneously homing_state.set_axes([0, 1, 2]) @@ -97,8 +102,7 @@ def home(self, homing_state): forcepos[2] = -1. homing_state.home_rails(self.rails, forcepos, self.home_position) def _motor_off(self, print_time): - self.limit_xy2 = -1. - self.need_home = True + self.clear_homing_state((0, 1, 2)) def check_move(self, move): end_pos = move.end_pos end_xy2 = end_pos[0]**2 + end_pos[1]**2 diff --git a/klippy/kinematics/winch.py b/klippy/kinematics/winch.py index 11475d24cdd5..c69d1b6dcc54 100644 --- a/klippy/kinematics/winch.py +++ b/klippy/kinematics/winch.py @@ -36,6 +36,9 @@ def calc_position(self, stepper_positions): def set_position(self, newpos, homing_axes): for s in self.steppers: s.set_position(newpos) + def clear_homing_state(self, axes): + # XXX - homing not implemented + pass def home(self, homing_state): # XXX - homing not implemented homing_state.set_axes([0, 1, 2]) diff --git a/klippy/stepper.py b/klippy/stepper.py index 05e56cca4327..fd44effb6ffa 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -403,7 +403,7 @@ def add_extra_stepper(self, config): changed_invert = pin_params['invert'] != endstop['invert'] changed_pullup = pin_params['pullup'] != endstop['pullup'] if changed_invert or changed_pullup: - raise error("Pinter rail %s shared endstop pin %s " + raise error("Printer rail %s shared endstop pin %s " "must specify the same pullup/invert settings" % ( self.get_name(), pin_name)) mcu_endstop.add_stepper(stepper) From 485c8f2ef008e10f9d2a2b497c291c4ec716428c Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 2 Jan 2025 10:55:51 -0500 Subject: [PATCH 4/6] lib: Update can2040 to v1.7.0 This provides improved support on rp2350 chips. Signed-off-by: Kevin O'Connor --- lib/README | 2 +- lib/can2040/can2040.c | 129 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 119 insertions(+), 12 deletions(-) diff --git a/lib/README b/lib/README index 367e07659d4a..a106abfd06c1 100644 --- a/lib/README +++ b/lib/README @@ -174,7 +174,7 @@ used to upload firmware to devices flashed with the CanBoot bootloader. The can2040 directory contains code from: https://github.com/KevinOConnor/can2040 -commit 13321ce2bc046e059a47def70f977a579a984462. +version v1.7.0 (90515f53ce89442f1bcc3033aae222e9eb77818c). The Huada HC32F460 directory contains code from: https://www.hdsc.com.cn/Category83-1490 diff --git a/lib/can2040/can2040.c b/lib/can2040/can2040.c index 4e5108ca22f5..f11b3818486d 100644 --- a/lib/can2040/can2040.c +++ b/lib/can2040/can2040.c @@ -1,6 +1,6 @@ // Software CANbus implementation for rp2040 // -// Copyright (C) 2022,2023 Kevin O'Connor +// Copyright (C) 2022-2024 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -20,6 +20,13 @@ * rp2040 and low-level helper functions ****************************************************************/ +// Determine if the target is an rp2350 +#ifdef PICO_RP2350 + #define IS_RP2350 1 +#else + #define IS_RP2350 0 +#endif + // Helper compiler definitions #define barrier() __asm__ __volatile__("": : :"memory") #define likely(x) __builtin_expect(!!(x), 1) @@ -123,19 +130,29 @@ static const uint16_t can2040_program_instructions[] = { #define SI_RX_DATA PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS #define SI_TXPENDING PIO_IRQ0_INTE_SM1_BITS // Misc bit manually forced +// Return the gpio bank offset (on rp2350 chips) +static uint32_t +pio_gpiobase(struct can2040 *cd) +{ + if (!IS_RP2350) + return 0; + return (cd->gpio_rx > 31 || cd->gpio_tx > 31) ? 16 : 0; +} + // Setup PIO "sync" state machine (state machine 0) static void pio_sync_setup(struct can2040 *cd) { pio_hw_t *pio_hw = cd->pio_hw; pio_sm_hw_t *sm = &pio_hw->sm[0]; + uint32_t gpio_rx = (cd->gpio_rx - pio_gpiobase(cd)) & 0x1f; sm->execctrl = ( - cd->gpio_rx << PIO_SM0_EXECCTRL_JMP_PIN_LSB + gpio_rx << PIO_SM0_EXECCTRL_JMP_PIN_LSB | (can2040_offset_sync_end - 1) << PIO_SM0_EXECCTRL_WRAP_TOP_LSB | can2040_offset_sync_signal_start << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB); sm->pinctrl = ( 1 << PIO_SM0_PINCTRL_SET_COUNT_LSB - | cd->gpio_rx << PIO_SM0_PINCTRL_SET_BASE_LSB); + | gpio_rx << PIO_SM0_PINCTRL_SET_BASE_LSB); sm->instr = 0xe080; // set pindirs, 0 sm->pinctrl = 0; pio_hw->txf[0] = 9 + 6 * PIO_CLOCK_PER_BIT / 2; @@ -149,10 +166,11 @@ pio_rx_setup(struct can2040 *cd) { pio_hw_t *pio_hw = cd->pio_hw; pio_sm_hw_t *sm = &pio_hw->sm[1]; + uint32_t gpio_rx = (cd->gpio_rx - pio_gpiobase(cd)) & 0x1f; sm->execctrl = ( (can2040_offset_shared_rx_end - 1) << PIO_SM0_EXECCTRL_WRAP_TOP_LSB | can2040_offset_shared_rx_read << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB); - sm->pinctrl = cd->gpio_rx << PIO_SM0_PINCTRL_IN_BASE_LSB; + sm->pinctrl = gpio_rx << PIO_SM0_PINCTRL_IN_BASE_LSB; sm->shiftctrl = 0; // flush fifo on a restart sm->shiftctrl = (PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS | PIO_RX_WAKE_BITS << PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB @@ -169,7 +187,8 @@ pio_match_setup(struct can2040 *cd) sm->execctrl = ( (can2040_offset_match_end - 1) << PIO_SM0_EXECCTRL_WRAP_TOP_LSB | can2040_offset_shared_rx_read << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB); - sm->pinctrl = cd->gpio_rx << PIO_SM0_PINCTRL_IN_BASE_LSB; + uint32_t gpio_rx = (cd->gpio_rx - pio_gpiobase(cd)) & 0x1f; + sm->pinctrl = gpio_rx << PIO_SM0_PINCTRL_IN_BASE_LSB; sm->shiftctrl = 0; sm->instr = 0xe040; // set y, 0 sm->instr = 0xa0e2; // mov osr, y @@ -183,16 +202,18 @@ pio_tx_setup(struct can2040 *cd) { pio_hw_t *pio_hw = cd->pio_hw; pio_sm_hw_t *sm = &pio_hw->sm[3]; + uint32_t gpio_rx = (cd->gpio_rx - pio_gpiobase(cd)) & 0x1f; + uint32_t gpio_tx = (cd->gpio_tx - pio_gpiobase(cd)) & 0x1f; sm->execctrl = ( - cd->gpio_rx << PIO_SM0_EXECCTRL_JMP_PIN_LSB + gpio_rx << PIO_SM0_EXECCTRL_JMP_PIN_LSB | can2040_offset_tx_conflict << PIO_SM0_EXECCTRL_WRAP_TOP_LSB | can2040_offset_tx_conflict << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB); sm->shiftctrl = (PIO_SM0_SHIFTCTRL_FJOIN_TX_BITS | PIO_SM0_SHIFTCTRL_AUTOPULL_BITS); sm->pinctrl = (1 << PIO_SM0_PINCTRL_SET_COUNT_LSB | 1 << PIO_SM0_PINCTRL_OUT_COUNT_LSB - | cd->gpio_tx << PIO_SM0_PINCTRL_SET_BASE_LSB - | cd->gpio_tx << PIO_SM0_PINCTRL_OUT_BASE_LSB); + | gpio_tx << PIO_SM0_PINCTRL_SET_BASE_LSB + | gpio_tx << PIO_SM0_PINCTRL_OUT_BASE_LSB); sm->instr = 0xe001; // set pins, 1 sm->instr = 0xe081; // set pindirs, 1 } @@ -382,6 +403,10 @@ pio_setup(struct can2040 *cd, uint32_t sys_clock, uint32_t bitrate) { // Configure pio0 clock uint32_t rb = cd->pio_num ? RESETS_RESET_PIO1_BITS : RESETS_RESET_PIO0_BITS; +#if IS_RP2350 + if (cd->pio_num == 2) + rb = RESETS_RESET_PIO2_BITS; +#endif rp2040_clear_reset(rb); // Setup and sync pio state machine clocks @@ -391,11 +416,16 @@ pio_setup(struct can2040 *cd, uint32_t sys_clock, uint32_t bitrate) for (i=0; i<4; i++) pio_hw->sm[i].clkdiv = div << PIO_SM0_CLKDIV_FRAC_LSB; + // Configure gpiobase (on rp2350) +#if IS_RP2350 + pio_hw->gpiobase = pio_gpiobase(cd); +#endif + // Configure state machines pio_sm_setup(cd); // Map Rx/Tx gpios - uint32_t pio_func = cd->pio_num ? 7 : 6; + uint32_t pio_func = 6 + cd->pio_num; rp2040_gpio_peripheral(cd->gpio_rx, pio_func, 1); rp2040_gpio_peripheral(cd->gpio_tx, pio_func, 0); } @@ -495,7 +525,7 @@ unstuf_restore_state(struct can2040_bitunstuffer *bu, uint32_t data) // Pull bits from unstuffer (as specified in unstuf_set_count() ) static int -unstuf_pull_bits(struct can2040_bitunstuffer *bu) +unstuf_pull_bits_rp2040(struct can2040_bitunstuffer *bu) { uint32_t sb = bu->stuffed_bits, edges = sb ^ (sb >> 1); uint32_t e2 = edges | (edges >> 1), e4 = e2 | (e2 >> 2), rm_bits = ~e4; @@ -539,6 +569,49 @@ unstuf_pull_bits(struct can2040_bitunstuffer *bu) } } +// Pull bits from unstuffer (optimized for rp2350) +static int +unstuf_pull_bits(struct can2040_bitunstuffer *bu) +{ + if (!IS_RP2350) + return unstuf_pull_bits_rp2040(bu); + uint32_t sb = bu->stuffed_bits, edges = sb ^ (sb >> 1); + uint32_t e2 = edges | (edges >> 1), e4 = e2 | (e2 >> 2), rm_bits = ~e4; + uint32_t cs = bu->count_stuff, cu = bu->count_unstuff; + for (;;) { + if (!cs) + // Need more data + return 1; + uint32_t try_cnt = cs > cu ? cu : cs; + uint32_t try_mask = ((1 << try_cnt) - 1) << (cs + 1 - try_cnt); + uint32_t rm_masked_bits = rm_bits & try_mask; + if (likely(!rm_masked_bits)) { + // No stuff bits in try_cnt bits - copy into unstuffed_bits + bu->count_unstuff = cu = cu - try_cnt; + bu->count_stuff = cs = cs - try_cnt; + bu->unstuffed_bits |= ((sb >> cs) & ((1 << try_cnt) - 1)) << cu; + if (! cu) + // Extracted desired bits + return 0; + // Need more data + return 1; + } + // Copy any leading bits prior to stuff bit (may be zero) + uint32_t copy_cnt = cs - (31 - __builtin_clz(rm_masked_bits)); + cs -= copy_cnt; + bu->count_unstuff = cu = cu - copy_cnt; + bu->unstuffed_bits |= ((sb >> cs) & ((1 << copy_cnt) - 1)) << cu; + // High bit is now a stuff bit - remove it + bu->count_stuff = cs = cs - 1; + if (unlikely(rm_bits & (1 << cs))) { + // Six consecutive bits - a bitstuff error + if (sb & (1 << cs)) + return -1; + return -2; + } + } +} + // Return most recent raw (still stuffed) bits static uint32_t unstuf_get_raw(struct can2040_bitunstuffer *bu) @@ -553,7 +626,7 @@ unstuf_get_raw(struct can2040_bitunstuffer *bu) // Stuff 'num_bits' bits in '*pb' - upper bits must already be stuffed static uint32_t -bitstuff(uint32_t *pb, uint32_t num_bits) +bitstuff_rp2040(uint32_t *pb, uint32_t num_bits) { uint32_t b = *pb, count = num_bits; for (;;) { @@ -590,6 +663,34 @@ bitstuff(uint32_t *pb, uint32_t num_bits) return count; } +// Stuff 'num_bits' bits in '*pb' (optimized for rp2350) +static uint32_t +bitstuff(uint32_t *pb, uint32_t num_bits) +{ + if (!IS_RP2350) + return bitstuff_rp2040(pb, num_bits); + uint32_t b = *pb, count = num_bits; + for (;;) { + uint32_t edges = b ^ (b >> 1); + uint32_t e2 = edges | (edges >> 1), e4 = e2 | (e2 >> 2), add_bits = ~e4; + uint32_t mask = (1 << num_bits) - 1, add_masked_bits = add_bits & mask; + if (!add_masked_bits) + // No more stuff bits needed + break; + // Insert a stuff bit + uint32_t stuff_pos = 1 + 31 - __builtin_clz(add_masked_bits); + uint32_t low_mask = (1 << stuff_pos) - 1, low = b & low_mask; + uint32_t high = (b & ~(low_mask >> 1)) << 1; + b = high ^ low ^ (1 << (stuff_pos - 1)); + count += 1; + if (stuff_pos <= 4) + break; + num_bits = stuff_pos - 4; + } + *pb = b; + return count; +} + // State storage for building bit stuffed transmit messages struct bitstuffer_s { uint32_t prev_stuffed, bitpos, *buf; @@ -1326,6 +1427,12 @@ can2040_setup(struct can2040 *cd, uint32_t pio_num) memset(cd, 0, sizeof(*cd)); cd->pio_num = !!pio_num; cd->pio_hw = cd->pio_num ? pio1_hw : pio0_hw; +#if IS_RP2350 + if (pio_num == 2) { + cd->pio_num = pio_num; + cd->pio_hw = pio2_hw; + } +#endif } // API function to configure callback From aae29ba48b2b15594f3b39ca0d6a5df9263dff0a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 19 Dec 2024 11:45:13 -0500 Subject: [PATCH 5/6] heaters: Disable heater if it appears main thread has stopped updating Update the heating code to periodically check that the main thread is operating properly. This is a mitigation for some rare cases where the main thread may lockup while the background heater updating code continues to run. The goal is to detect these rare failures and disable future heater updates. Signed-off-by: Kevin O'Connor --- klippy/extras/heaters.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py index 7cb663a4a279..1454354272d2 100644 --- a/klippy/extras/heaters.py +++ b/klippy/extras/heaters.py @@ -14,6 +14,7 @@ MAX_HEAT_TIME = 5.0 AMBIENT_TEMP = 25. PID_PARAM_BASE = 255. +MAX_MAINTHREAD_TIME = 5.0 class Heater: def __init__(self, config, sensor): @@ -37,7 +38,7 @@ def __init__(self, config, sensor): self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.) self.smooth_time = config.getfloat('smooth_time', 1., above=0.) self.inv_smooth_time = 1. / self.smooth_time - self.is_shutdown = False + self.verify_mainthread_time = -999. self.lock = threading.Lock() self.last_temp = self.smoothed_temp = self.target_temp = 0. self.last_temp_time = 0. @@ -66,7 +67,7 @@ def __init__(self, config, sensor): self.printer.register_event_handler("klippy:shutdown", self._handle_shutdown) def set_pwm(self, read_time, value): - if self.target_temp <= 0. or self.is_shutdown: + if self.target_temp <= 0. or read_time > self.verify_mainthread_time: value = 0. if ((read_time < self.next_pwm_time or not self.last_pwm_value) and abs(value - self.last_pwm_value) < 0.05): @@ -91,7 +92,7 @@ def temperature_callback(self, read_time, temp): self.can_extrude = (self.smoothed_temp >= self.min_extrude_temp) #logging.debug("temp: %.3f %f = %f", read_time, temp) def _handle_shutdown(self): - self.is_shutdown = True + self.verify_mainthread_time = -999. # External commands def get_name(self): return self.name @@ -129,6 +130,9 @@ def alter_target(self, target_temp): target_temp = max(self.min_temp, min(self.max_temp, target_temp)) self.target_temp = target_temp def stats(self, eventtime): + est_print_time = self.mcu_pwm.get_mcu().estimated_print_time(eventtime) + if not self.printer.is_shutdown(): + self.verify_mainthread_time = est_print_time + MAX_MAINTHREAD_TIME with self.lock: target_temp = self.target_temp last_temp = self.last_temp From cf3b0475daa0d7154d2f986f94d8c184c7cf39c1 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 10 Jan 2025 12:29:41 -0500 Subject: [PATCH 6/6] tmc2240: Allow the slope_control field to be configured via printer.cfg Signed-off-by: Kevin O'Connor --- docs/Config_Reference.md | 1 + klippy/extras/tmc2240.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index f797d2b06905..52ba3a4900f4 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -3846,6 +3846,7 @@ run_current: #driver_SEIMIN: 0 #driver_SFILT: 0 #driver_SG4_ANGLE_OFFSET: 1 +#driver_SLOPE_CONTROL: 0 # Set the given register during the configuration of the TMC2240 # chip. This may be used to set custom motor parameters. The # defaults for each parameter are next to the parameter name in the diff --git a/klippy/extras/tmc2240.py b/klippy/extras/tmc2240.py index 8816ea2e3bd0..214896e782b2 100644 --- a/klippy/extras/tmc2240.py +++ b/klippy/extras/tmc2240.py @@ -408,6 +408,8 @@ def __init__(self, config): set_config_field(config, "tpowerdown", 10) # SG4_THRS set_config_field(config, "sg4_angle_offset", 1) + # DRV_CONF + set_config_field(config, "slope_control", 0) def load_config_prefix(config): return TMC2240(config)